diff --git a/ruoyi-fastapi-backend b/ruoyi-fastapi-backend deleted file mode 160000 index 443d4d7..0000000 --- a/ruoyi-fastapi-backend +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 443d4d7159d02f696efe2b26808b582bf8738160 diff --git a/ruoyi-fastapi-backend/.env.dev b/ruoyi-fastapi-backend/.env.dev new file mode 100644 index 0000000..940e203 --- /dev/null +++ b/ruoyi-fastapi-backend/.env.dev @@ -0,0 +1,150 @@ +# -------- 应用配置 -------- +# 应用运行环境 +APP_ENV = 'dev' +# 应用名称 +APP_NAME = 'Kangda_robot' +# 应用代理路径 +APP_ROOT_PATH = '/dev-api' +# 应用主机 +APP_HOST = '0.0.0.0' +# 应用端口 +APP_PORT = 9099 +# 应用版本 +APP_VERSION= '1.6.2' +# 应用是否开启热重载 +APP_RELOAD = false +# 应用是否开启IP归属区域查询 +APP_IP_LOCATION_QUERY = true +# 应用是否允许账号同时登录 +APP_SAME_TIME_LOGIN = true + +APP_DOCS_URL = +APP_REDOC_URL = +APP_OPENAPI_URL = + + +# -------- Jwt配置 -------- +# Jwt秘钥 +JWT_SECRET_KEY = 'b01c66dc2c58dc6a0aabfe2144256be36226de378bf87f72c0c795dda67f4d55' +# Jwt算法 +JWT_ALGORITHM = 'HS256' +# 令牌过期时间 +JWT_EXPIRE_MINUTES = 43200 +# redis中令牌过期时间 +JWT_REDIS_EXPIRE_MINUTES = 43100 + + +# -------- 数据库配置 -------- +# 数据库类型,可选的有'mysql'、'postgresql',默认为'mysql' +DB_TYPE = 'mysql' +# 数据库主机 +DB_HOST = '10.0.0.17' +# 数据库端口 +DB_PORT = 3306 +# 数据库用户名 +DB_USERNAME = 'root' +# 数据库密码 +DB_PASSWORD = 'root' +# 数据库名称 +DB_DATABASE = 'ruoyi-fastapi' +# 是否开启sqlalchemy日志 +DB_ECHO = true +# 允许溢出连接池大小的最大连接数 +DB_MAX_OVERFLOW = 10 +# 连接池大小,0表示连接数无限制 +DB_POOL_SIZE = 50 +# 连接回收时间(单位:秒) +DB_POOL_RECYCLE = 3600 +# 连接池中没有线程可用时,最多等待的时间(单位:秒) +DB_POOL_TIMEOUT = 30 + +# -------- Redis配置 -------- +# Redis主机 +REDIS_HOST = '10.0.0.58' +# Redis端口 +REDIS_PORT = 6379 +# Redis用户名 +REDIS_USERNAME = '' +# Redis密码 +REDIS_PASSWORD = '' +# Redis数据库 +REDIS_DATABASE = 3 + + +#------------------海康平台配置------------------ +HAIKANG_URL = 'https://192.168.89.230' +HAIKANG_PORT = 443 + +HAIKANG_AK = '29862915' +HAIKANG_SK = '3QlsmcyadBqu4OpVoxsJ' +HAIKANG_ACCESS_TOKEN_URL = '/api/v1/oauth/token' +# HAIKANG_DOOR_STATES_URL = '/api/v1/door/states' +HAIKANG_DOOR_STATES_URL = '/api/acs/v1/door/states' +HAIKANG_DOOR_DOCONTROL_URL = '/api/acs/v1/door/doControl' +HAIKANG_DOOR_ENVENTS_URL = '/api/acs/v2/door/events' +HAIKANG_DOOR_SEARCH = '/api/resource/v2/door/search' +HAIKANG_DOOR_ONLINE_STATUS = '/api/nms/v1/online/acs_device/get' +HAIKANG_APPLICATION_ONETOMANY_URL = '/api/frs/v1/application/oneToMany' +HAIKANG_PICTURE_CHECK_URL = '/api/frs/v1/face/picture/check' +HAIKANG_FACECAPATURE_SEARCH = '/api/frs/v1/event/face_capture/search' +HAIKANG_FACE_GROUP_URL = '/api/frs/v1/face/group' + +HAIKANG_VISITOR_RECORD_SEARCH = '/api/visitor/v2/appointment/records' +HAIKANG_VISITOR_RECORD_PICTURES = '/api/visitor/v1/record/pictures' + +HAIKANG_PERSON_LIST = '/api/resource/v2/person/personList' +HAIKANG_GET_PERSON_PICTURE = '/api/resource/v1/person/picture' + +HAIKANG_FACE_GROUP_ADDITION = '/api/frs/v1/face/group/single/addition' + +HAIKANG_FACE_SIGLE_ADDITION = '/api/frs/v1/face/single/addition' +HAIKANG_FACE_DELETE = '/api/frs/v1/face/deletion' + +HAIKANG_FACE_SAVE_PATH = "./face_images" + +HAIKANG_VISITOR_PICTURES_SAVE_PATH = "./visitor/face_images/" +HAIKANG_VISITOR_RECORD_TXT = "./visitor/visitorIds.txt" +#---------------------------------海康平台配置end---------------------------------- + + +# # 人脸分组1vN检索 +# HAIKANG_APPLICATION_ONETOMANY_URL = '/api/frs/v1/application/oneToMany' + +# # 人脸评分 +# HAIKANG_PICTURE_CHECK_URL = '/api/frs/v1/face/picture/check' + +# # 按条件查询人脸分组 +# HAIKANG_FACE_GROUP_URL = '/api/frs/v1/face/group' + +# # 查询人脸识别事件 +# HAIKANG_FACECAPATURE_SEARCH = '/api/frs/v1/event/face_capture/search' + +# # 查询访客预约记录 +# HAIKANG_VISITOR_RECORD_SEARCH = '/api/visitor/v2/appointment/records' + + + +#-------------------RAGFlow配置--------------------- + +RAGFLOW_BASE_URL = "http://10.0.0.202:82" +RAGFLOW_API_KEY = "ragflow-hlMjRmNzE2ODNiNTExZjA4ZTNlMDI0Mm" + +#-------------------RAGFlow配置end------------------ + +#-------------------compreface配置------------------- +COMPREFACE_BASE_URL = "http://10.0.0.202" +COMPERFACE_BASE_PORT = "8000" +COMPREFACE_API_KEY = "a5924457-62c9-47dc-a6e7-15462c502d2c" +COMPREFACE_API_KEY_DETECTION = "070283a2-faa3-423b-9772-2cd48ecc5362" +COMPREFACE_FACE_FILE = "./image_face" +COMPREFACE_SIMILARITY_THRESHOLD = 0.995 +#-------------------compreface配置end------------------- + +#-------------------天气服务配置------------------- +WEATHER_API_BASE = 'https://api.openweathermap.org/data/2.5' +WEATHER_API_KEY = '' +WEATHER_DEFAULT_CITY = '北京' +WEATHER_UNITS = 'metric' +WEATHER_LANG = 'zh_cn' +WEATHER_CACHE_TTL = 600 +#-------------------天气服务配置end------------------- \ No newline at end of file diff --git a/ruoyi-fastapi-backend/.env.prod b/ruoyi-fastapi-backend/.env.prod new file mode 100644 index 0000000..28c8919 --- /dev/null +++ b/ruoyi-fastapi-backend/.env.prod @@ -0,0 +1,75 @@ +# -------- 应用配置 -------- +# 应用运行环境 +APP_ENV = 'prod' +# 应用名称 +APP_NAME = 'RuoYi-FastAPI' +# 应用代理路径 +APP_ROOT_PATH = '/prod-api' +# 应用主机 +APP_HOST = '0.0.0.0' +# 应用端口 +APP_PORT = 9099 +# 应用版本 +APP_VERSION= '1.6.2' +# 应用是否开启热重载 +APP_RELOAD = false +# 应用是否开启IP归属区域查询 +APP_IP_LOCATION_QUERY = true +# 应用是否允许账号同时登录 +APP_SAME_TIME_LOGIN = true + +# -------- Jwt配置 -------- +# Jwt秘钥 +JWT_SECRET_KEY = 'b01c66dc2c58dc6a0aabfe2144256be36226de378bf87f72c0c795dda67f4d55' +# Jwt算法 +JWT_ALGORITHM = 'HS256' +# 令牌过期时间 +JWT_EXPIRE_MINUTES = 1440 +# redis中令牌过期时间 +JWT_REDIS_EXPIRE_MINUTES = 30 + + +# -------- 数据库配置 -------- +# 数据库类型,可选的有'mysql'、'postgresql',默认为'mysql' +DB_TYPE = 'mysql' +# 数据库主机 +DB_HOST = '127.0.0.1' +# 数据库端口 +DB_PORT = 3306 +# 数据库用户名 +DB_USERNAME = 'root' +# 数据库密码 +DB_PASSWORD = 'root' +# 数据库名称 +DB_DATABASE = 'ruoyi-fastapi' +# 是否开启sqlalchemy日志 +DB_ECHO = true +# 允许溢出连接池大小的最大连接数 +DB_MAX_OVERFLOW = 10 +# 连接池大小,0表示连接数无限制 +DB_POOL_SIZE = 50 +# 连接回收时间(单位:秒) +DB_POOL_RECYCLE = 3600 +# 连接池中没有线程可用时,最多等待的时间(单位:秒) +DB_POOL_TIMEOUT = 30 + +# -------- Redis配置 -------- +# Redis主机 +REDIS_HOST = '127.0.0.1' +# Redis端口 +REDIS_PORT = 6379 +# Redis用户名 +REDIS_USERNAME = '' +# Redis密码 +REDIS_PASSWORD = '' +# Redis数据库 +REDIS_DATABASE = 2 + +#-------------------天气服务配置------------------- +WEATHER_API_BASE = 'https://api.openweathermap.org/data/2.5' +WEATHER_API_KEY = '' +WEATHER_DEFAULT_CITY = '北京' +WEATHER_UNITS = 'metric' +WEATHER_LANG = 'zh_cn' +WEATHER_CACHE_TTL = 600 +#-------------------天气服务配置end------------------- \ No newline at end of file diff --git a/ruoyi-fastapi-backend/.gitignore b/ruoyi-fastapi-backend/.gitignore new file mode 100644 index 0000000..5686ba6 --- /dev/null +++ b/ruoyi-fastapi-backend/.gitignore @@ -0,0 +1,145 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +vf_admin/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +.idea/ + +# VSCode +.vscode/ + diff --git a/ruoyi-fastapi-backend/001测试海康api.py b/ruoyi-fastapi-backend/001测试海康api.py new file mode 100644 index 0000000..9840850 --- /dev/null +++ b/ruoyi-fastapi-backend/001测试海康api.py @@ -0,0 +1,96 @@ +from utils.haikang_util import HaikangUtil +import asyncio +import base64 + + + + + + +# 查询门禁点列表 +async def get_door_list_service(pageNo: int = 1, pageSize: int = 10): + result = await HaikangUtil.get_door_list_v2(pageNo, pageSize) + print(result) + +# 查询门禁状态 +async def get_door_status_service(door_index_codes): + result = await HaikangUtil.get_door_status(door_index_codes) + print(result) + +# 门禁控制 +async def door_do_control_service(door_index_codes, control_type): + result = await HaikangUtil.door_do_control(door_index_codes, control_type) + print(result) + +# 查询门禁点事件 +async def query_door_events_service(door_index_code,pageNo, pageSize, startTime, endTime): + result = await HaikangUtil.query_door_events_v2(door_index_code, pageNo=pageNo, pageSize=pageSize ,startTime=startTime, endTime=endTime) + print(result) + +# 查看门禁点在线状态 +async def door_online_status_service(door_index_codes): + result = await HaikangUtil.door_online_status(door_index_codes) + print(result) + +# 按条件查询人脸分组, 很重要 +async def get_face_group_service(): + result = await HaikangUtil.get_face_group() + print(result) + +# 人脸分组1vN搜索 +async def face_group_1vN_search_service(image_path): + + with open(image_path, 'rb') as f: + image_data = f.read() + + encoded_image = base64.b64encode(image_data).decode('utf-8') + + result = await HaikangUtil.face_group_1vN_search( + facePicBinaryData=encoded_image, + pageNo=1, + pageSize=10, + searchNum=99, + minSimilarity=50, + faceGroupIndexCodes=['5dc82633-a4cb-4107-b55e-f21bf952f9'] + ) + print(result) + +# 人脸评分 +async def face_picture_check(image_path): + + with open(image_path, 'rb') as f: + image_data = f.read() + + encoded_image = base64.b64encode(image_data).decode('utf-8') + + + result = await HaikangUtil.face_picture_check( + facePicBinaryData=encoded_image + ) + print(result) + +# 查询访客预约记录 +async def query_visitor_record(): + result = await HaikangUtil.query_visitor_record() + print(result) + +if __name__ == '__main__': + # asyncio.run(get_door_list_service()) + # print("*"*100) + # asyncio.run(get_door_status_service(['D01'])) + # print("*"*100) + + # asyncio.run(door_do_control_service(['D01'], 1)) + + # asyncio.run(query_door_events_service('D01',1,10,1640995200,1640995200)) + + # asyncio.run(get_face_group_service()) + + # image_path = "75c03e462769c81b6a8513d90ff2a27d.jpg" + # asyncio.run(face_group_1vN_search_service(image_path)) + # asyncio.run(face_picture_check(image_path)) + + # asyncio.run(query_visitor_record()) + + asyncio.run(door_online_status_service(["xxxxxxxx"])) + diff --git a/ruoyi-fastapi-backend/002简单http服务.py b/ruoyi-fastapi-backend/002简单http服务.py new file mode 100644 index 0000000..a429e5d --- /dev/null +++ b/ruoyi-fastapi-backend/002简单http服务.py @@ -0,0 +1,536 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from http.server import HTTPServer, BaseHTTPRequestHandler +import json +import urllib.parse +import logging + +# 配置日志 +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +class CustomHTTPRequestHandler(BaseHTTPRequestHandler): + """自定义HTTP请求处理器""" + + def do_GET(self): + """处理GET请求""" + # 解析URL和查询参数 + parsed_path = urllib.parse.urlparse(self.path) + query_params = urllib.parse.parse_qs(parsed_path.query) + + logger.info(f"GET 请求: {self.path}") + + # 根据路径返回不同的响应 + if parsed_path.path == "/": + self._send_response(200, "text/html", self._get_home_page()) + elif parsed_path.path == "/api/user": + self._handle_user_api(query_params) + elif parsed_path.path == "/api/status": + self._send_json_response(200, {"status": "ok", "message": "服务运行正常"}) + elif parsed_path.path == "/api/time": + import datetime + + current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self._send_json_response(200, {"time": current_time}) + else: + self._send_json_response(404, {"error": "页面未找到"}) + + def do_POST(self): + """处理POST请求""" + content_length = int(self.headers.get("Content-Length", 0)) + post_data = self.rfile.read(content_length) + + logger.info(f"POST 请求: {self.path}") + + try: + # 尝试解析JSON数据 + if content_length == 0: + logger.info("POST 请求数据为空") + elif self.headers.get("Content-Type") == "application/json": + data = json.loads(post_data.decode("utf-8")) + logger.info(f"接收到JSON数据: {data}") + else: + # 解析表单数据 + data = urllib.parse.parse_qs(post_data.decode("utf-8")) + logger.info(f"接收到表单数据: {data}") + + # print("x"*100) + # 根据路径处理不同的POST请求 + # if self.path == '/api/login': + # self._handle_login(data) + # elif self.path == '/api/echo': + # self._send_json_response(200, {'echo': data, 'message': '数据已接收'}) + # else: + # self._send_json_response(404, {'error': '接口未找到'}) + if self.path == "/api/resource/v2/door/search": + self._send_json_response( + 200, + { + "code": "0", + "msg": "SUCCESS", + "data": { + "total": 3, + "pageNo": 1, + "pageSize": 1, + "list": [ + { + "indexCode": "df8w8cr800283c24c", + "resourceType": "door", + "name": "资源 1", + "doorNo": "123", + "channelNo": "1", + "parentIndexCode": "80d9099q9e991231", + "controlOneId": "11111111", + "controlTwoId": "2222222222", + "readerInId": "ac789y2c0019c", + "readerOutId": "arcew78c710", + "doorSerial": 1, + "treatyType": "hiksdk_net", + "regionIndexCode": "d8a5476e-25c0-4aa2-b7e3-db3788ba1f77", + "regionPath": "@root000000@", + "createTime": "2018-11-28T16:47:27:358+08:00", + "updateTime": "2018-11-28T16:48:34:011+08:00", + "description": "Test", + "channelType": "door", + "regionName": "acs_setUp_42054", + "regionPathName": "@root000000@9ca1eef0-4579-4e7e-a601-caf486442d54@", + "installLocation": "位置 1", + } + ], + }, + }, + ) + elif self.path == "/api/v1/door/states": + self._send_json_response( + 200, + { + "code": "0", + "msg": "success", + "data": { + "authDoorList": [ + { + "doorIndexCode": "e8e3ef5c149243abb4341124ab38fcfc", + "doorState": 0, + } + ], + "noAuthDoorIndexCodeList": [ + "e8e3ef5c149243abb4341124ab38fcfc" + ], + }, + }, + ) + elif self.path == "/api/acs/v1/door/doControl": + self._send_json_response( + 200, + { + "code": "0", + "msg": "success", + "data": [ + { + "doorIndexCode": "2c95c028a809448f962a969e3ab34f", + "controlResultCode": 0, # 0表示反控成功, 其他表示失败 + "controlResultDesc": "success", + } + ], + }, + ) + elif self.path == "/api/acs/v2/door/events": + self._send_json_response( + 200, + { + "code": "0", + "msg": "success", + "data": { + "total": 1, + "totalPage": 1, + "pageNo": 1, + "pageSize": 100, + "list": [ + { + "eventId": "207dd3b1-37a7-4d6c-8e4d-c8bfd343051b", + "eventName": "acs.acs.eventType.successCard", + "eventTime": "2019-11-16T15:44:33+08:00", + "personId": "216e2ba145824269a1cbb423cdc85cb1", + "cardNo": "3891192334", + "personName": "sdk 人员 1zzzcb", + "orgIndexCode": "root000000", + "orgName": "默认组织", + "doorName": "10.40.239.69new_test2_门_1", + "doorIndexCode": "f0b50050d3434f15b4e34f885d5dacfe", + "doorRegionIndexCode": "fd2df06b-1afb-4c9b-b058-5740c2c00076", + "picUri": "no-pcnvr", + "svrIndexCode": "/pic?=d62i7f6e*6a7i125-c838b9--a8c67dea96e65icb1*=sd*=5dpi*=1dpi*m2i1t=4ed35444bb4s=-39", + "eventType": 198914, + "inAndOutType": 1, + "readerDevIndexCode": "378e563bf3e84d5ba6ef5742bbaa8933", + "readerDevName": "读卡器_1", + "devIndexCode": "dcff422aad9c4d60a47b8b2fe2757b71", + "devName": "10.40.239.69new_test2", + "identityCardUri": "/pic?=d62i7f6e*6a7i125-c838b9--a8c67dea96e65icb1*=sd*=5dpi*=1dpi*m2i1t=4ed35444bb4s=-39z422d3", + "receiveTime": "2019-11-16T15:45:13.525+08:00", + "jobNo": "23333", + "studentId": "201900001", + "certNo": "320826199012110005", + } + ], + }, + }, + ) + elif self.path == "/api/frs/v1/face/group": + self._send_json_response( + 200, + { + "code": "0", + "msg": "Success", + "data": [ + { + "indexCode": "5dc82633-a4cb-4107-b55e-f21bf952f9", + "name": "仓库值守人员", + "description": "仓库值守人员是指守着仓库的人", + } + ], + }, + ) + elif self.path == "/api/frs/v1/application/oneToMany": + self._send_json_response( + 200, + { + "code": "0", + "msg": "Success.", + "data": { + "total": 500, + "pageNo": 1, + "pageSize": 10, + "list": [ + { + "similarity": 80, + "indexCode": "7cc0adb2-a3c3-48fd-b432-718103e85c28", + "faceInfo": { + "name": "张三", + "sex": "1", + "certificateType": "111", + "certificateNum": "420204199605121656", + }, + "facePic": { + "faceUrl": "http://10.166.165.121:8080/frs/facepicturetemp/test.jpg" + }, + } + ], + }, + }, + ) + elif self.path == "/api/frs/v1/face/picture/check": + self._send_json_response( + 200, + { + "code": "0", + "msg": "Success", + "data": { + "checkResult": True, + "faceScore": 90, + "facePicAnalysisResult": { + "id": 5566, + "age": 16, + "ageRange": 1, + "ageGroup": "TEENAGER", + "": "male", + "glasses": "NO", + "smile": "NO", + "facePose": { + "pitch": 45, + "yaw": 25, + "roll": 10, + "clearityScore": 0.5, + "colorConfidence": 0.5, + "eyeDistance": 300, + "grayMean": 120, + "visibleScore": 0.5, + }, + "targetModelData": "DD", + "faceRect": { + "height": 12.1, + "width": 16, + "x": 15, + "y": 3, + }, + "recommendFaceRect": { + "height": 4, + "width": 6, + "x": 2, + "y": 1, + }, + "faceMark": { + "leftEye": {"x": 33, "y": 22}, + "rightEye": {"x": 44, "y": 33}, + "noseTip": {"x": 43, "y": 12}, + "leftMouth": {"x": 32, "y": 54}, + "rightMouth": {"x": 67, "y": 12}, + }, + "mask": "NO", + "faceScore": 90, + }, + }, + }, + ) + elif self.path == "/api/visitor/v2/appointment/records": + self._send_json_response( + 200, + { + "code": "0", + "msg": "success", + "data": { + "total": 1, + "pageNo": 1, + "pageSize": 20, + "list": [ + { + "appointRecordId": "321654987", + "receptionistId": "3124126241412", + "receptionistName": "王五", + "receptionistCode": "323JH234KJH23", + "visitStartTime": "2018-07-26T15:00:00 + 08:00", + "visitEndTime": "2018-07-26T19:00:00 + 08:00", + "visitPurpose": "参考", + "visitorName": "张三", + "visitorId": "ASDF454SDAF565613JHU7712332", + "verificationCode": "1234", + "QRCode": "2015468421", + "": 1, + "phoneNo": "13576361254", + "plateNo": "浙 A12345", + "certificateType": 111, + "certificateNo": "311256196602145692", + "picUri": "/pic?adsdqwe21-asafdd-12sfsdfsdf", + "svrIndexCode": "sadsa123-asd21edsfhgsd-23rfdvsr", + "visitorStatus": 1, + "certAddr": "杭州滨江", + "certIssuer": "滨江分局", + "nation": 1, + "birthplace": "杭州", + "visitorWorkUnit": "中国工商银行", + "visitorAddress": "杭州滨江", + "orderId": "d089ady8a0dud87018d0y90ay9d901", + "designatedResources": { + "paramKey": "1", + "paramValues": ["52v72v35762587n75b26"], + }, + "privilegeGroupNames": ["one"], + "identityUri": "/pic?123-scccdf334-3216516516516", + "identitySvrCode": "12ddf53ggg56sss6554", + } + ], + }, + }, + ) + elif self.path == "/api/nms/v1/online/acs_device/get": + self._send_json_response( + 200, + { + "code": "0", + "msg": "success", + "data": { + "pageNo": 1, + "pageSize": 10, + "totalPage": 0, + "total": 1, + "list": [ + { + "deviceType": "HIK%2FDS-9116HW-ST%2F-AF-DVR", + "deviceIndexCode": "null", + "regionIndexCode": "ce91c758-5af4-4539-845a", + "collectTime": "2018-12-28T10:21:40.000+08:00", + "regionName": "NMS 自动化", + "indexCode": "82896441ced946d5a51c6d6ca8e65851", + "cn": "Onvif-IPC(10.67.172.13 )", + "treatyType": "onvif_net", + "manufacturer": "hikvision", + "ip": "10.67.172.13", + "port": 80, + "online": 1, + } + ], + }, + }, + ) + except json.JSONDecodeError: + self._send_json_response(400, {"error": "无效的JSON数据"}) + except Exception as e: + self._send_json_response(500, {"code": 0, "error": f"服务器错误: {str(e)}"}) + + def do_PUT(self): + """处理PUT请求""" + content_length = int(self.headers.get("Content-Length", 0)) + put_data = self.rfile.read(content_length) + + logger.info(f"PUT 请求: {self.path}") + + try: + data = json.loads(put_data.decode("utf-8")) + self._send_json_response(200, {"message": "数据已更新", "data": data}) + except json.JSONDecodeError: + self._send_json_response(400, {"error": "无效的JSON数据"}) + + def do_DELETE(self): + """处理DELETE请求""" + logger.info(f"DELETE 请求: {self.path}") + self._send_json_response(200, {"message": "删除成功", "path": self.path}) + + def _handle_user_api(self, query_params): + """处理用户API请求""" + user_id = query_params.get("id", [""])[0] + if user_id: + user_data = { + "id": user_id, + "name": f"用户{user_id}", + "email": f"user{user_id}@example.com", + "status": "active", + } + self._send_json_response(200, user_data) + else: + # 返回用户列表 + users = [ + {"id": "1", "name": "张三", "email": "zhangsan@example.com"}, + {"id": "2", "name": "李四", "email": "lisi@example.com"}, + {"id": "3", "name": "王五", "email": "wangwu@example.com"}, + ] + self._send_json_response(200, {"users": users}) + + def _handle_login(self, data): + """处理登录请求""" + username = ( + data.get("username", [""])[0] + if isinstance(data, dict) + else data.get("username", "") + ) + password = ( + data.get("password", [""])[0] + if isinstance(data, dict) + else data.get("password", "") + ) + + # 简单的用户验证(仅做演示) + if username == "admin" and password == "123456": + response_data = { + "success": True, + "message": "登录成功", + "token": "fake_jwt_token_here", + "user": {"username": username, "role": "admin"}, + } + self._send_json_response(200, response_data) + else: + self._send_json_response( + 401, {"success": False, "message": "用户名或密码错误"} + ) + + def _send_response(self, status_code, content_type, content): + """发送HTTP响应""" + self.send_response(status_code) + self.send_header("Content-Type", f"{content_type}; charset=utf-8") + self.send_header("Access-Control-Allow-Origin", "*") # 允许跨域 + self.send_header( + "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS" + ) + self.send_header("Access-Control-Allow-Headers", "Content-Type") + self.end_headers() + self.wfile.write(content.encode("utf-8")) + + def _send_json_response(self, status_code, data): + """发送JSON响应""" + json_data = json.dumps(data, ensure_ascii=False, indent=2) + self._send_response(status_code, "application/json", json_data) + + def _get_home_page(self): + """获取首页HTML""" + return """ + + + + + + Python HTTP 服务端 + + + +

🚀 Python HTTP 服务端

+

服务运行成功!以下是可用的API接口:

+ +
+
+ GET /api/status - 获取服务状态 +
+
+ GET /api/time - 获取当前时间 +
+
+ GET /api/user - 获取用户列表 +
+
+ GET /api/user?id=1 - 获取指定用户 +
+
+ POST /api/login - 用户登录 (username: admin, password: 123456) +
+
+ POST /api/echo - 回显接收到的数据 +
+
+ +

测试示例:

+
+# 获取状态
+curl http://localhost:8080/api/status
+
+# 用户登录
+curl -X POST -H "Content-Type: application/json" \\
+     -d '{"username":"admin","password":"123456"}' \\
+     http://localhost:8080/api/login
+
+# 获取用户信息
+curl http://localhost:8080/api/user?id=1
+            
+ + + """ + + def do_OPTIONS(self): + """处理预检请求(CORS)""" + self.send_response(200) + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header( + "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS" + ) + self.send_header("Access-Control-Allow-Headers", "Content-Type") + self.end_headers() + + def log_message(self, format, *args): + """自定义日志格式""" + logger.info(f"{self.address_string()} - {format % args}") + + +def run_server(host="localhost", port=8080): + """启动HTTP服务器""" + server_address = (host, port) + httpd = HTTPServer(server_address, CustomHTTPRequestHandler) + + print(f"🌟 HTTP服务器启动成功!") + print(f"📍 地址: http://{host}:{port}") + print(f"🔗 在浏览器中访问: http://{host}:{port}") + print(f"⏹️ 按 Ctrl+C 停止服务器\n") + + try: + httpd.serve_forever() + except KeyboardInterrupt: + print("\n🛑 服务器已停止") + httpd.server_close() + + +if __name__ == "__main__": + # 启动服务器 + run_server(host="10.0.0.202", port=9909) diff --git a/ruoyi-fastapi-backend/003测试海康url.py b/ruoyi-fastapi-backend/003测试海康url.py new file mode 100644 index 0000000..be27311 --- /dev/null +++ b/ruoyi-fastapi-backend/003测试海康url.py @@ -0,0 +1,11 @@ +from urllib.parse import urlparse + +url = "https://192.168.10.251:8001/artemis/test_v1" + +parsed = urlparse(url) + +path_and_query = parsed.path +if parsed.query: + path_and_query += "?" + parsed.query + +print(path_and_query) \ No newline at end of file diff --git a/ruoyi-fastapi-backend/004测试compreface方法.py b/ruoyi-fastapi-backend/004测试compreface方法.py new file mode 100644 index 0000000..aba0084 --- /dev/null +++ b/ruoyi-fastapi-backend/004测试compreface方法.py @@ -0,0 +1,57 @@ +from utils.compreface_util import ComprefaceUtil +import asyncio +import time +import json + +image_path = "./haotian1.jpg" + +with open(image_path, "rb") as f: + image_bytes = f.read() # 获取图片的字节流 + +# # 测试上传人脸图片 +# print(asyncio.run(ComprefaceUtil.face_addition(image_path, '刘昊天_访客'))) + +# #-----------------------------------------测试人脸识别-------------------------------------------- +# start_time = time.time() +# result = asyncio.run( +# ComprefaceUtil.face_recognition( +# image_bytes, +# options={ +# "limit": 0, +# "det_prob_threshold": 0.8, +# "prediction_count": 1, +# # 可选参数 age,gender,landmarks,calculator +# "face_plugins": "gender", +# "status": "true", +# }, +# ) +# ) +# print(result) +# with open("compreface_face_recognition.json", "w", encoding="utf-8") as f: +# f.write(json.dumps(result, ensure_ascii=False, indent=4)) + +# print("spend time:", time.time() - start_time) + +# #-----------------------------------------测试人脸识别end------------------------------------------ + +# -----------------------------------------测试人脸检测-------------------------------------------- +start_time = time.time() +result = asyncio.run( + ComprefaceUtil.face_detection( + image_bytes, + options={ + "limit": 0, + "det_prob_threshold": 0.8, + "prediction_count": 1, + # 可选参数 age,gender,landmarks,calculator + "face_plugins": "pose", + "status": "false", + } + ) +) +print(result) +with open("compreface_face_detection.json", "w", encoding="utf-8") as f: + f.write(json.dumps(result, ensure_ascii=False, indent=4)) + +print("spend time:", time.time() - start_time) +# -----------------------------------------测试人脸检测end----------------------------------------- diff --git a/ruoyi-fastapi-backend/005测试compreface人脸识别服务.py b/ruoyi-fastapi-backend/005测试compreface人脸识别服务.py new file mode 100644 index 0000000..195a2c9 --- /dev/null +++ b/ruoyi-fastapi-backend/005测试compreface人脸识别服务.py @@ -0,0 +1,56 @@ +import requests +from compreface import CompreFace +from compreface.service import RecognitionService, DetectionService +from compreface.collections import FaceCollection +from compreface.collections.face_collections import Subjects +import time + +DOMAIN: str = 'http://localhost' +PORT: str = '8000' + +def face_recognition_stream(image_path, url): + with open(image_path, 'rb') as f: + response = requests.post(url, data=f.read()) + return response.json() + +def face_recognition_file(image_path, url): + + files = {"file": ("test.jpg", open(image_path, "rb"), "image/jpeg")} + response = requests.post(url, files=files) + return response.json() +def face_recognition_compreface(image_path): + + + API_KEY: str = 'a5924457-62c9-47dc-a6e7-15462c502d2c' + + compre_face: CompreFace = CompreFace(DOMAIN, PORT) + + recognition: RecognitionService = compre_face.init_face_recognition(API_KEY) + + start_time = time.time() + result = recognition.recognize(image_path=image_path) + print("--- %s seconds ---" % (time.time() - start_time)) + print(result) + +def face_detection_compreface(image_path): + + + API_KEY: str = '070283a2-faa3-423b-9772-2cd48ecc5362' + + compre_face: CompreFace = CompreFace(DOMAIN, PORT) + + detection: DetectionService = compre_face.init_face_detection(API_KEY) + + start_time = time.time() + result = detection.detect(image_path=image_path) + print("--- %s seconds ---" % (time.time() - start_time)) + print(result) + + +if __name__ == '__main__': + image_path = './moshengren.jpg' + url = 'http://10.0.0.202:9099/system/compreface/face_recognition' + # print(face_recognition_stream(image_path, url)) + # print(face_recognition_file(image_path, url)) + # face_recognition_compreface(image_path) + face_detection_compreface(image_path) \ No newline at end of file diff --git a/ruoyi-fastapi-backend/006测试定时任务.py b/ruoyi-fastapi-backend/006测试定时任务.py new file mode 100644 index 0000000..1bd35ad --- /dev/null +++ b/ruoyi-fastapi-backend/006测试定时任务.py @@ -0,0 +1,25 @@ +from apscheduler.schedulers.blocking import BlockingScheduler +from apscheduler.triggers.cron import CronTrigger +import datetime +import time + +def my_job(): + print("任务执行时间:", datetime.datetime.now()) + +if __name__ == '__main__': + scheduler = BlockingScheduler(timezone="Asia/Shanghai") + + # # 每天凌晨 1 点执行 + # scheduler.add_job(my_job, trigger="cron", hour=1, minute=0) + + # 或者用 CronTrigger + trigger = CronTrigger(minute='*', second=0) + scheduler.add_job(my_job, trigger=trigger) + + scheduler.start() + + try: + while True: + time.sleep(1) + except (KeyboardInterrupt, SystemExit): + scheduler.shutdown() \ No newline at end of file diff --git a/ruoyi-fastapi-backend/007测试python文件.py b/ruoyi-fastapi-backend/007测试python文件.py new file mode 100644 index 0000000..9b53e56 --- /dev/null +++ b/ruoyi-fastapi-backend/007测试python文件.py @@ -0,0 +1,5 @@ +import os + +txt_path = "./test/image_base64.txt" +print(os.path.dirname(txt_path)) +# os.makedirs(, exist_ok=True) \ No newline at end of file diff --git a/ruoyi-fastapi-backend/75c03e462769c81b6a8513d90ff2a27d.jpg b/ruoyi-fastapi-backend/75c03e462769c81b6a8513d90ff2a27d.jpg new file mode 100644 index 0000000..94a8bd4 Binary files /dev/null and b/ruoyi-fastapi-backend/75c03e462769c81b6a8513d90ff2a27d.jpg differ diff --git a/ruoyi-fastapi-backend/app.py b/ruoyi-fastapi-backend/app.py new file mode 100644 index 0000000..1ee7695 --- /dev/null +++ b/ruoyi-fastapi-backend/app.py @@ -0,0 +1,12 @@ +import uvicorn +from server import app, AppConfig # noqa: F401 + + +if __name__ == '__main__': + uvicorn.run( + app='app:app', + host=AppConfig.app_host, + port=AppConfig.app_port, + root_path=AppConfig.app_root_path, + reload=AppConfig.app_reload, + ) diff --git a/ruoyi-fastapi-backend/assets/font/Arial.ttf b/ruoyi-fastapi-backend/assets/font/Arial.ttf new file mode 100644 index 0000000..9512aea Binary files /dev/null and b/ruoyi-fastapi-backend/assets/font/Arial.ttf differ diff --git a/ruoyi-fastapi-backend/compreface_face_detection.json b/ruoyi-fastapi-backend/compreface_face_detection.json new file mode 100644 index 0000000..58b1125 --- /dev/null +++ b/ruoyi-fastapi-backend/compreface_face_detection.json @@ -0,0 +1,18 @@ +{ + "result": [ + { + "pose": { + "pitch": -13.898311223378556, + "roll": 0.8241647740114217, + "yaw": 0.30893387084981927 + }, + "box": { + "probability": 0.9989770650863647, + "x_max": 874, + "y_max": 1127, + "x_min": 375, + "y_min": 418 + } + } + ] +} \ No newline at end of file diff --git a/ruoyi-fastapi-backend/compreface_face_recognition.json b/ruoyi-fastapi-backend/compreface_face_recognition.json new file mode 100644 index 0000000..aed763c --- /dev/null +++ b/ruoyi-fastapi-backend/compreface_face_recognition.json @@ -0,0 +1,33 @@ +{ + "result": [ + { + "gender": { + "probability": 1.0, + "value": "male" + }, + "box": { + "probability": 0.99898, + "x_max": 874, + "y_max": 1127, + "x_min": 375, + "y_min": 418 + }, + "subjects": [ + { + "subject": "刘昊天_访客", + "similarity": 0.99991 + } + ], + "execution_time": { + "gender": 3.0, + "detector": 32.0, + "calculator": 9.0 + } + } + ], + "plugins_versions": { + "gender": "insightface.GenderDetector", + "detector": "insightface.FaceDetector@retinaface_r50_v1", + "calculator": "insightface.Calculator@arcface-r100-msfdrop75" + } +} \ No newline at end of file diff --git a/ruoyi-fastapi-backend/config/__init__.py b/ruoyi-fastapi-backend/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ruoyi-fastapi-backend/config/constant.py b/ruoyi-fastapi-backend/config/constant.py new file mode 100644 index 0000000..eb77464 --- /dev/null +++ b/ruoyi-fastapi-backend/config/constant.py @@ -0,0 +1,483 @@ +from config.env import DataBaseConfig + + +class CommonConstant: + """ + 常用常量 + + WWW: www主域 + HTTP: http请求 + HTTPS: https请求 + LOOKUP_RMI: RMI远程方法调用 + LOOKUP_LDAP: LDAP远程方法调用 + LOOKUP_LDAPS: LDAPS远程方法调用 + YES: 是否为系统默认(是) + NO: 是否为系统默认(否) + DEPT_NORMAL: 部门正常状态 + DEPT_DISABLE: 部门停用状态 + UNIQUE: 校验是否唯一的返回标识(是) + NOT_UNIQUE: 校验是否唯一的返回标识(否) + """ + + WWW = 'www.' + HTTP = 'http://' + HTTPS = 'https://' + LOOKUP_RMI = 'rmi:' + LOOKUP_LDAP = 'ldap:' + LOOKUP_LDAPS = 'ldaps:' + YES = 'Y' + NO = 'N' + DEPT_NORMAL = '0' + DEPT_DISABLE = '1' + UNIQUE = True + NOT_UNIQUE = False + + +class HttpStatusConstant: + """ + 返回状态码 + + SUCCESS: 操作成功 + CREATED: 对象创建成功 + ACCEPTED: 请求已经被接受 + NO_CONTENT: 操作已经执行成功,但是没有返回数据 + MOVED_PERM: 资源已被移除 + SEE_OTHER: 重定向 + NOT_MODIFIED: 资源没有被修改 + BAD_REQUEST: 参数列表错误(缺少,格式不匹配) + UNAUTHORIZED: 未授权 + FORBIDDEN: 访问受限,授权过期 + NOT_FOUND: 资源,服务未找到 + BAD_METHOD: 不允许的http方法 + CONFLICT: 资源冲突,或者资源被锁 + UNSUPPORTED_TYPE: 不支持的数据,媒体类型 + ERROR: 系统内部错误 + NOT_IMPLEMENTED: 接口未实现 + WARN: 系统警告消息 + """ + + SUCCESS = 200 + CREATED = 201 + ACCEPTED = 202 + NO_CONTENT = 204 + MOVED_PERM = 301 + SEE_OTHER = 303 + NOT_MODIFIED = 304 + BAD_REQUEST = 400 + UNAUTHORIZED = 401 + FORBIDDEN = 403 + NOT_FOUND = 404 + BAD_METHOD = 405 + CONFLICT = 409 + UNSUPPORTED_TYPE = 415 + ERROR = 500 + NOT_IMPLEMENTED = 501 + WARN = 601 + + +class JobConstant: + """ + 定时任务常量 + + JOB_ERROR_LIST: 定时任务禁止调用模块及违规字符串列表 + JOB_WHITE_LIST: 定时任务允许调用模块列表 + """ + + JOB_ERROR_LIST = [ + 'app', + 'config', + 'exceptions', + 'import ', + 'middlewares', + 'module_admin', + 'open(', + 'os.', + 'server', + 'sub_applications', + 'subprocess.', + 'sys.', + 'utils', + 'while ', + '__import__', + '"', + "'", + ',', + '?', + ':', + ';', + '/', + '|', + '+', + '-', + '=', + '~', + '!', + '#', + '$', + '%', + '^', + '&', + '*', + '<', + '>', + '(', + ')', + '[', + ']', + '{', + '}', + ' ', + ] + JOB_WHITE_LIST = ['module_task'] + + +class MenuConstant: + """ + 菜单常量 + + TYPE_DIR: 菜单类型(目录) + TYPE_MENU: 菜单类型(菜单) + TYPE_BUTTON: 菜单类型(按钮) + YES_FRAME: 是否菜单外链(是) + NO_FRAME: 是否菜单外链(否) + LAYOUT: Layout组件标识 + PARENT_VIEW: ParentView组件标识 + INNER_LINK: InnerLink组件标识 + """ + + TYPE_DIR = 'M' + TYPE_MENU = 'C' + TYPE_BUTTON = 'F' + YES_FRAME = 0 + NO_FRAME = 1 + LAYOUT = 'Layout' + PARENT_VIEW = 'ParentView' + INNER_LINK = 'InnerLink' + + +class GenConstant: + """ + 代码生成常量 + + TPL_CRUD: 单表(增删改查 + TPL_TREE: 树表(增删改查) + TPL_SUB: 主子表(增删改查) + TREE_CODE: 树编码字段 + TREE_PARENT_CODE: 树父编码字段 + TREE_NAME: 树名称字段 + PARENT_MENU_ID: 上级菜单ID字段 + PARENT_MENU_NAME: 上级菜单名称字段 + COLUMNTYPE_STR: 数据库字符串类型 + COLUMNTYPE_TEXT: 数据库文本类型 + COLUMNTYPE_TIME: 数据库时间类型 + COLUMNTYPE_GEOMETRY: 数据库字空间类型 + COLUMNTYPE_NUMBER: 数据库数字类型 + COLUMNNAME_NOT_EDIT: 页面不需要编辑字段 + COLUMNNAME_NOT_LIST: 页面不需要显示的列表字段 + COLUMNNAME_NOT_QUERY: 页面不需要查询字段 + BASE_ENTITY: Entity基类字段 + TREE_ENTITY: Tree基类字段 + HTML_INPUT: 文本框 + HTML_TEXTAREA: 文本域 + HTML_SELECT: 下拉框 + HTML_RADIO: 单选框 + HTML_CHECKBOX: 复选框 + HTML_DATETIME: 日期控件 + HTML_IMAGE_UPLOAD: 图片上传控件 + HTML_FILE_UPLOAD: 文件上传控件 + HTML_EDITOR: 富文本控件 + TYPE_DECIMAL: 高精度计算类型 + TYPE_DATE: 时间类型 + QUERY_LIKE: 模糊查询 + QUERY_EQ: 相等查询 + REQUIRE: 需要 + DB_TO_SQLALCHEMY_TYPE_MAPPING: 数据库类型与sqlalchemy类型映射 + DB_TO_PYTHON_TYPE_MAPPING: 数据库类型与python类型映射 + """ + + TPL_CRUD = 'crud' + TPL_TREE = 'tree' + TPL_SUB = 'sub' + TREE_CODE = 'treeCode' + TREE_PARENT_CODE = 'treeParentCode' + TREE_NAME = 'treeName' + PARENT_MENU_ID = 'parentMenuId' + PARENT_MENU_NAME = 'parentMenuName' + COLUMNTYPE_STR = ( + ['character varying', 'varchar', 'character', 'char'] + if DataBaseConfig.db_type == 'postgresql' + else ['char', 'varchar', 'nvarchar', 'varchar2'] + ) + COLUMNTYPE_TEXT = ( + ['text', 'citext'] if DataBaseConfig.db_type == 'postgresql' else ['tinytext', 'text', 'mediumtext', 'longtext'] + ) + COLUMNTYPE_TIME = ( + [ + 'date', + 'time', + 'time with time zone', + 'time without time zone', + 'timestamp', + 'timestamp with time zone', + 'timestamp without time zone', + 'interval', + ] + if DataBaseConfig.db_type == 'postgresql' + else ['datetime', 'time', 'date', 'timestamp'] + ) + COLUMNTYPE_GEOMETRY = ( + ['point', 'line', 'lseg', 'box', 'path', 'polygon', 'circle'] + if DataBaseConfig.db_type == 'postgresql' + else [ + 'geometry', + 'point', + 'linestring', + 'polygon', + 'multipoint', + 'multilinestring', + 'multipolygon', + 'geometrycollection', + ] + ) + COLUMNTYPE_NUMBER = [ + 'tinyint', + 'smallint', + 'mediumint', + 'int', + 'number', + 'integer', + 'bit', + 'bigint', + 'float', + 'double', + 'decimal', + ] + COLUMNNAME_NOT_ADD_SHOW = ['create_by', 'create_time'] + COLUMNNAME_NOT_EDIT_SHOW = ['update_by', 'update_time'] + COLUMNNAME_NOT_EDIT = ['id', 'create_by', 'create_time', 'del_flag'] + COLUMNNAME_NOT_LIST = ['id', 'create_by', 'create_time', 'del_flag', 'update_by', 'update_time'] + COLUMNNAME_NOT_QUERY = ['id', 'create_by', 'create_time', 'del_flag', 'update_by', 'update_time', 'remark'] + BASE_ENTITY = ['createBy', 'createTime', 'updateBy', 'updateTime', 'remark'] + TREE_ENTITY = ['parentName', 'parentId', 'orderNum', 'ancestors', 'children'] + HTML_INPUT = 'input' + HTML_TEXTAREA = 'textarea' + HTML_SELECT = 'select' + HTML_RADIO = 'radio' + HTML_CHECKBOX = 'checkbox' + HTML_DATETIME = 'datetime' + HTML_IMAGE_UPLOAD = 'imageUpload' + HTML_FILE_UPLOAD = 'fileUpload' + HTML_EDITOR = 'editor' + TYPE_DECIMAL = 'Decimal' + TYPE_DATE = ['date', 'time', 'datetime'] + QUERY_LIKE = 'LIKE' + QUERY_EQ = 'EQ' + REQUIRE = '1' + DB_TO_SQLALCHEMY_TYPE_MAPPING = ( + { + 'boolean': 'Boolean', + 'smallint': 'SmallInteger', + 'integer': 'Integer', + 'bigint': 'BigInteger', + 'real': 'Float', + 'double precision': 'Float', + 'numeric': 'Numeric', + 'character varying': 'String', + 'character': 'String', + 'text': 'Text', + 'bytea': 'LargeBinary', + 'date': 'Date', + 'time': 'Time', + 'time with time zone': 'Time', + 'time without time zone': 'Time', + 'timestamp': 'DateTime', + 'timestamp with time zone': 'DateTime', + 'timestamp without time zone': 'DateTime', + 'interval': 'Interval', + 'json': 'JSON', + 'jsonb': 'JSONB', + 'uuid': 'Uuid', + 'inet': 'INET', + 'cidr': 'CIDR', + 'macaddr': 'MACADDR', + 'point': 'Geometry', + 'line': 'Geometry', + 'lseg': 'Geometry', + 'box': 'Geometry', + 'path': 'Geometry', + 'polygon': 'Geometry', + 'circle': 'Geometry', + 'bit': 'Bit', + 'bit varying': 'Bit', + 'tsvector': 'TSVECTOR', + 'tsquery': 'TSQUERY', + 'xml': 'String', + 'array': 'ARRAY', + 'composite': 'JSON', + 'enum': 'Enum', + 'range': 'Range', + 'money': 'Numeric', + 'pg_lsn': 'BigInteger', + 'txid_snapshot': 'String', + 'oid': 'BigInteger', + 'regproc': 'String', + 'regclass': 'String', + 'regtype': 'String', + 'regrole': 'String', + 'regnamespace': 'String', + 'int2vector': 'ARRAY', + 'oidvector': 'ARRAY', + 'pg_node_tree': 'Text', + } + if DataBaseConfig.db_type == 'postgresql' + else { + # 数值类型 + 'TINYINT': 'SmallInteger', + 'SMALLINT': 'SmallInteger', + 'MEDIUMINT': 'Integer', + 'INT': 'Integer', + 'INTEGER': 'Integer', + 'BIGINT': 'BigInteger', + 'FLOAT': 'Float', + 'DOUBLE': 'Float', + 'DECIMAL': 'DECIMAL', + 'BIT': 'Integer', + # 日期和时间类型 + 'DATE': 'Date', + 'TIME': 'Time', + 'DATETIME': 'DateTime', + 'TIMESTAMP': 'TIMESTAMP', + 'YEAR': 'Integer', + # 字符串类型 + 'CHAR': 'CHAR', + 'VARCHAR': 'String', + 'TINYTEXT': 'Text', + 'TEXT': 'Text', + 'MEDIUMTEXT': 'Text', + 'LONGTEXT': 'Text', + 'BINARY': 'BINARY', + 'VARBINARY': 'VARBINARY', + 'TINYBLOB': 'LargeBinary', + 'BLOB': 'LargeBinary', + 'MEDIUMBLOB': 'LargeBinary', + 'LONGBLOB': 'LargeBinary', + # 枚举和集合类型 + 'ENUM': 'Enum', + 'SET': 'String', + # JSON 类型 + 'JSON': 'JSON', + # 空间数据类型(需要扩展支持,如 GeoAlchemy2) + 'GEOMETRY': 'Geometry', # 需要安装 geoalchemy2 + 'POINT': 'Geometry', + 'LINESTRING': 'Geometry', + 'POLYGON': 'Geometry', + 'MULTIPOINT': 'Geometry', + 'MULTILINESTRING': 'Geometry', + 'MULTIPOLYGON': 'Geometry', + 'GEOMETRYCOLLECTION': 'Geometry', + } + ) + DB_TO_PYTHON_TYPE_MAPPING = ( + { + 'boolean': 'bool', + 'smallint': 'int', + 'integer': 'int', + 'bigint': 'int', + 'real': 'float', + 'double precision': 'float', + 'numeric': 'Decimal', + 'character varying': 'str', + 'character': 'str', + 'text': 'str', + 'bytea': 'bytes', + 'date': 'date', + 'time': 'time', + 'time with time zone': 'time', + 'time without time zone': 'time', + 'timestamp': 'datetime', + 'timestamp with time zone': 'datetime', + 'timestamp without time zone': 'datetime', + 'interval': 'timedelta', + 'json': 'dict', + 'jsonb': 'dict', + 'uuid': 'str', + 'inet': 'str', + 'cidr': 'str', + 'macaddr': 'str', + 'point': 'list', + 'line': 'list', + 'lseg': 'list', + 'box': 'list', + 'path': 'list', + 'polygon': 'list', + 'circle': 'list', + 'bit': 'int', + 'bit varying': 'int', + 'tsvector': 'str', + 'tsquery': 'str', + 'xml': 'str', + 'array': 'list', + 'composite': 'dict', + 'enum': 'str', + 'range': 'list', + 'money': 'Decimal', + 'pg_lsn': 'int', + 'txid_snapshot': 'str', + 'oid': 'int', + 'regproc': 'str', + 'regclass': 'str', + 'regtype': 'str', + 'regrole': 'str', + 'regnamespace': 'str', + 'int2vector': 'list', + 'oidvector': 'list', + 'pg_node_tree': 'str', + } + if DataBaseConfig.db_type == 'postgresql' + else { + # 数值类型 + 'TINYINT': 'int', + 'SMALLINT': 'int', + 'MEDIUMINT': 'int', + 'INT': 'int', + 'INTEGER': 'int', + 'BIGINT': 'int', + 'FLOAT': 'float', + 'DOUBLE': 'float', + 'DECIMAL': 'Decimal', + 'BIT': 'int', + # 日期和时间类型 + 'DATE': 'date', + 'TIME': 'time', + 'DATETIME': 'datetime', + 'TIMESTAMP': 'datetime', + 'YEAR': 'int', + # 字符串类型 + 'CHAR': 'str', + 'VARCHAR': 'str', + 'TINYTEXT': 'str', + 'TEXT': 'str', + 'MEDIUMTEXT': 'str', + 'LONGTEXT': 'str', + 'BINARY': 'bytes', + 'VARBINARY': 'bytes', + 'TINYBLOB': 'bytes', + 'BLOB': 'bytes', + 'MEDIUMBLOB': 'bytes', + 'LONGBLOB': 'bytes', + # 枚举和集合类型 + 'ENUM': 'str', + 'SET': 'str', + # JSON 类型 + 'JSON': 'dict', + # 空间数据类型(通常需要特殊处理) + 'GEOMETRY': 'bytes', + 'POINT': 'bytes', + 'LINESTRING': 'bytes', + 'POLYGON': 'bytes', + 'MULTIPOINT': 'bytes', + 'MULTILINESTRING': 'bytes', + 'MULTIPOLYGON': 'bytes', + 'GEOMETRYCOLLECTION': 'bytes', + } + ) diff --git a/ruoyi-fastapi-backend/config/database.py b/ruoyi-fastapi-backend/config/database.py new file mode 100644 index 0000000..006b6a5 --- /dev/null +++ b/ruoyi-fastapi-backend/config/database.py @@ -0,0 +1,30 @@ +from sqlalchemy.ext.asyncio import create_async_engine +from sqlalchemy.ext.asyncio import async_sessionmaker +from sqlalchemy.ext.asyncio import AsyncAttrs +from sqlalchemy.orm import DeclarativeBase +from urllib.parse import quote_plus +from config.env import DataBaseConfig + +ASYNC_SQLALCHEMY_DATABASE_URL = ( + f'mysql+asyncmy://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@' + f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}' +) +if DataBaseConfig.db_type == 'postgresql': + ASYNC_SQLALCHEMY_DATABASE_URL = ( + f'postgresql+asyncpg://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@' + f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}' + ) + +async_engine = create_async_engine( + ASYNC_SQLALCHEMY_DATABASE_URL, + echo=DataBaseConfig.db_echo, + max_overflow=DataBaseConfig.db_max_overflow, + pool_size=DataBaseConfig.db_pool_size, + pool_recycle=DataBaseConfig.db_pool_recycle, + pool_timeout=DataBaseConfig.db_pool_timeout, +) +AsyncSessionLocal = async_sessionmaker(autocommit=False, autoflush=False, bind=async_engine) + + +class Base(AsyncAttrs, DeclarativeBase): + pass diff --git a/ruoyi-fastapi-backend/config/enums.py b/ruoyi-fastapi-backend/config/enums.py new file mode 100644 index 0000000..6b62ce7 --- /dev/null +++ b/ruoyi-fastapi-backend/config/enums.py @@ -0,0 +1,78 @@ +from enum import Enum + + +class GuideWordType(Enum): + """ + 引导词类型 + NEWVISITOR 新访客 0 + BOOKEDVISITOR 已预约访客 1 + STAFF 员工 2 + CHAT 聊天关键词 3 + """ + NEWVISITOR = 0 + BOOKEDVISITOR = 1 + STAFF = 2 + CHAT = 3 + +class ExplanationContentStatus(Enum): + """ + 讲解内容状态 + + NOTSTART: 未开始 + READY: 已就绪 + PLAYING: 播放中 + FINISH: 已完成 + """ + NOTSTART = '0' + READY = '1' + PLAYING = '2' + FINISH = '3' + +class BusinessType(Enum): + """ + 业务操作类型 + + OTHER: 其它 + INSERT: 新增 + UPDATE: 修改 + DELETE: 删除 + GRANT: 授权 + EXPORT: 导出 + IMPORT: 导入 + FORCE: 强退 + GENCODE: 生成代码 + CLEAN: 清空数据 + """ + + OTHER = 0 + INSERT = 1 + UPDATE = 2 + DELETE = 3 + GRANT = 4 + EXPORT = 5 + IMPORT = 6 + FORCE = 7 + GENCODE = 8 + CLEAN = 9 + + +class RedisInitKeyConfig(Enum): + """ + 系统内置Redis键名 + """ + + @property + def key(self): + return self.value.get('key') + + @property + def remark(self): + return self.value.get('remark') + + ACCESS_TOKEN = {'key': 'access_token', 'remark': '登录令牌信息'} + SYS_DICT = {'key': 'sys_dict', 'remark': '数据字典'} + SYS_CONFIG = {'key': 'sys_config', 'remark': '配置信息'} + CAPTCHA_CODES = {'key': 'captcha_codes', 'remark': '图片验证码'} + ACCOUNT_LOCK = {'key': 'account_lock', 'remark': '用户锁定'} + PASSWORD_ERROR_COUNT = {'key': 'password_error_count', 'remark': '密码错误次数'} + SMS_CODE = {'key': 'sms_code', 'remark': '短信验证码'} diff --git a/ruoyi-fastapi-backend/config/env.py b/ruoyi-fastapi-backend/config/env.py new file mode 100644 index 0000000..5840641 --- /dev/null +++ b/ruoyi-fastapi-backend/config/env.py @@ -0,0 +1,353 @@ +import argparse +import os +import sys +from dotenv import load_dotenv +from functools import lru_cache +from pydantic import computed_field +from pydantic_settings import BaseSettings +from typing import Literal + + +class AppSettings(BaseSettings): + """ + 应用配置 + """ + + app_env: str = 'dev' + app_name: str = 'RuoYi-FasAPI' + app_root_path: str = '/dev-api' + app_host: str = '0.0.0.0' + app_port: int = 9099 + app_version: str = '1.0.0' + app_reload: bool = False + app_ip_location_query: bool = True + app_same_time_login: bool = True + + app_docs_url: str = None + app_redoc_url: str = None + app_openapi_url: str = None + + + +class JwtSettings(BaseSettings): + """ + Jwt配置 + """ + + jwt_secret_key: str = os.getenv('JWT_SECRET_KEY', '') + jwt_algorithm: str = os.getenv('JWT_ALGORITHM', 'HS256') + jwt_expire_minutes: int = int(os.getenv('JWT_EXPIRE_MINUTES', '1440')) + jwt_redis_expire_minutes: int = int(os.getenv('JWT_REDIS_EXPIRE_MINUTES', '30')) + + +class DataBaseSettings(BaseSettings): + """ + 数据库配置 + """ + + db_type: Literal['mysql', 'postgresql'] = os.getenv('DB_TYPE', 'mysql') + db_host: str = os.getenv('DB_HOST', '127.0.0.1') + db_port: int = int(os.getenv('DB_PORT', '3306')) + db_username: str = os.getenv('DB_USERNAME', '') + db_password: str = os.getenv('DB_PASSWORD', '') + db_database: str = os.getenv('DB_DATABASE', '') + db_echo: bool = os.getenv('DB_ECHO', 'True').lower() == 'true' + db_max_overflow: int = int(os.getenv('DB_MAX_OVERFLOW', '10')) + db_pool_size: int = int(os.getenv('DB_POOL_SIZE', '50')) + db_pool_recycle: int = int(os.getenv('DB_POOL_RECYCLE', '3600')) + db_pool_timeout: int = int(os.getenv('DB_POOL_TIMEOUT', '30')) + + @computed_field + @property + def sqlglot_parse_dialect(self) -> str: + if self.db_type == 'postgresql': + return 'postgres' + return self.db_type + + +class RedisSettings(BaseSettings): + """ + Redis配置 + """ + + redis_host: str = os.getenv('REDIS_HOST', '127.0.0.1') + redis_port: int = int(os.getenv('REDIS_PORT', '6379')) + redis_username: str = os.getenv('REDIS_USERNAME', '') + redis_password: str = os.getenv('REDIS_PASSWORD', '') + redis_database: int = int(os.getenv('REDIS_DATABASE', '2')) + + +class GenSettings: + """ + 代码生成配置 + """ + + author = 'insistence' + package_name = 'module_admin.system' + auto_remove_pre = False + table_prefix = 'sys_' + allow_overwrite = False + + GEN_PATH = 'vf_admin/gen_path' + + def __init__(self): + if not os.path.exists(self.GEN_PATH): + os.makedirs(self.GEN_PATH) + + +class UploadSettings: + """ + 上传配置 + """ + + UPLOAD_PREFIX = '/profile' + UPLOAD_PATH = 'vf_admin/upload_path' + UPLOAD_MACHINE = 'A' + DEFAULT_ALLOWED_EXTENSION = [ + # 图片 + 'bmp', + 'gif', + 'jpg', + 'jpeg', + 'png', + # word excel powerpoint + 'doc', + 'docx', + 'xls', + 'xlsx', + 'ppt', + 'pptx', + 'html', + 'htm', + 'txt', + # 压缩文件 + 'rar', + 'zip', + 'gz', + 'bz2', + # 视频格式 + 'mp4', + 'avi', + 'rmvb', + # pdf + 'pdf', + ] + DOWNLOAD_PATH = 'vf_admin/download_path' + + def __init__(self): + if not os.path.exists(self.UPLOAD_PATH): + os.makedirs(self.UPLOAD_PATH) + if not os.path.exists(self.DOWNLOAD_PATH): + os.makedirs(self.DOWNLOAD_PATH) + + +class CachePathConfig: + """ + 缓存目录配置 + """ + + PATH = os.path.join(os.path.abspath(os.getcwd()), 'caches') + PATHSTR = 'caches' + +class ComprefaceSettings: + """ + Compreface配置 + """ + COMPREFACE_BASE_URL = os.getenv("COMPREFACE_BASE_URL", "http://10.0.0.202") + COMPERFACE_BASE_PORT = os.getenv("COMPERFACE_BASE_PORT", "8000") + COMPREFACE_API_KEY = os.getenv("COMPREFACE_API_KEY", "") + COMPREFACE_API_KEY_DETECTION = os.getenv("COMPREFACE_API_KEY_DETECTION", "") + COMPREFACE_FACE_FILE = os.getenv("COMPREFACE_FACE_FILE", "./image_face") + COMPREFACE_SIMILARITY_THRESHOLD = float(os.getenv("COMPREFACE_SIMILARITY_THRESHOLD", "0.995")) + COMPREFACE_POSE_MAX_ANGLE = float(os.getenv("COMPREFACE_POSE_MAX_ANGLE", "10")) + COMPREFACE_MIN_FACE_WIDTH = int(os.getenv("COMPREFACE_MIN_FACE_WIDTH", "200")) + COMPREFACE_MIN_FACE_HEIGHT = int(os.getenv("COMPREFACE_MIN_FACE_HEIGHT", "200")) + COMPREFACE_MIN_DETECTION_PROBABILITY = float(os.getenv("COMPREFACE_MIN_DETECTION_PROBABILITY", "0.9")) + +class HaiKangSettings: + """ + 海康平台配置 + """ + HAIKANG_URL = os.getenv('HAIKANG_URL', 'https://127.0.0.1') + HAIKANG_PORT = int(os.getenv('HAIKANG_PORT', '443')) + + HAIKANG_AK = os.getenv('HAIKANG_AK', '') + HAIKANG_SK = os.getenv('HAIKANG_SK', '') + HAIKANG_ACCESS_TOKEN_URL = os.getenv('HAIKANG_ACCESS_TOKEN_URL', '/api/v1/oauth/token') + # HAIKANG_DOOR_STATES_URL = '/api/v1/door/states' + HAIKANG_DOOR_STATES_URL = os.getenv('HAIKANG_DOOR_STATES_URL', '/api/acs/v1/door/states') + HAIKANG_DOOR_DOCONTROL_URL = os.getenv('HAIKANG_DOOR_DOCONTROL_URL', '/api/acs/v1/door/doControl') + HAIKANG_DOOR_ENVENTS_URL = os.getenv('HAIKANG_DOOR_EVENTS_URL', '/api/acs/v2/door/events') + HAIKANG_DOOR_SEARCH = os.getenv('HAIKANG_DOOR_SEARCH', '/api/resource/v2/door/search') + HAIKANG_DOOR_ONLINE_STATUS = os.getenv('HAIKANG_DOOR_ONLINE_STATUS', '/api/nms/v1/online/acs_device/get') + HAIKANG_APPLICATION_ONETOMANY_URL = os.getenv('HAIKANG_APPLICATION_ONETOMANY_URL', '/api/frs/v1/application/oneToMany') + HAIKANG_PICTURE_CHECK_URL = os.getenv('HAIKANG_PICTURE_CHECK_URL', '/api/frs/v1/face/picture/check') + HAIKANG_FACECAPATURE_SEARCH = os.getenv('HAIKANG_FACE_CAPTURE_SEARCH', '/api/frs/v1/event/face_capture/search') + HAIKANG_FACE_GROUP_URL = os.getenv('HAIKANG_FACE_GROUP_URL', '/api/frs/v1/face/group') + + HAIKANG_VISITOR_RECORD_SEARCH = os.getenv('HAIKANG_VISITOR_RECORD_SEARCH', '/api/visitor/v2/appointment/records') + HAIKANG_VISITOR_RECORD_PICTURES = os.getenv('HAIKANG_VISITOR_RECORD_PICTURES', '/api/visitor/v1/record/pictures') + + HAIKANG_PERSON_LIST = os.getenv('HAIKANG_PERSON_LIST', '/api/resource/v2/person/personList') + HAIKANG_GET_PERSON_PICTURE = os.getenv('HAIKANG_GET_PERSON_PICTURE', '/api/resource/v1/person/picture') + + HAIKANG_FACE_GROUP_ADDITION = os.getenv('HAIKANG_FACE_GROUP_ADDITION', '/api/frs/v1/face/group/single/addition') + + HAIKANG_FACE_SIGLE_ADDITION = os.getenv('HAIKANG_FACE_SINGLE_ADDITION', '/api/frs/v1/face/single/addition') + HAIKANG_FACE_DELETE = os.getenv('HAIKANG_FACE_DELETE', '/api/frs/v1/face/deletion') + + HAIKANG_FACE_SAVE_PATH = os.getenv('HAIKANG_FACE_SAVE_PATH', "./face_images") + HAIKANG_VISITOR_PICTURES_SAVE_PATH = os.getenv('HAIKANG_VISITOR_PICTURES_SAVE_PATH', "./visitor/face_images/") + HAIKANG_VISITOR_RECORD_TXT = os.getenv('HAIKANG_VISITOR_RECORD_TXT', "./visitor/visitorIds.txt") + + + +class RAGFlowSettings: + """ + RAGFlowSettings + """ + RAGFLOW_BASE_URL = os.getenv("RAGFLOW_BASE_URL", "http://10.0.0.202:82") + RAGFLOW_API_KEY = os.getenv("RAGFLOW_API_KEY", "") + +class WeatherSettings: + """天气服务配置""" + + WEATHER_API_BASE = os.getenv("WEATHER_API_BASE", "https://api.openweathermap.org/data/2.5") + WEATHER_API_KEY = os.getenv("WEATHER_API_KEY", "") + WEATHER_DEFAULT_CITY = os.getenv("WEATHER_DEFAULT_CITY", "北京") + WEATHER_UNITS = os.getenv("WEATHER_UNITS", "metric") + WEATHER_LANG = os.getenv("WEATHER_LANG", "zh_cn") + WEATHER_CACHE_TTL = int(os.getenv("WEATHER_CACHE_TTL", "600")) + + +class GetConfig: + """ + 获取配置 + """ + + def __init__(self): + self.parse_cli_args() + + @lru_cache() + def get_compreface_config(self): + """ + 获取Compreface配置 + """ + # 获取Compreface配置 + return ComprefaceSettings() + @lru_cache() + def get_app_config(self): + """ + 获取应用配置 + """ + # 实例化应用配置模型 + return AppSettings() + + @lru_cache() + def get_jwt_config(self): + """ + 获取Jwt配置 + """ + # 实例化Jwt配置模型 + return JwtSettings() + + @lru_cache() + def get_database_config(self): + """ + 获取数据库配置 + """ + # 实例化数据库配置模型 + return DataBaseSettings() + + @lru_cache() + def get_redis_config(self): + """ + 获取Redis配置 + """ + # 实例化Redis配置模型 + return RedisSettings() + + @lru_cache() + def get_gen_config(self): + """ + 获取代码生成配置 + """ + # 实例化代码生成配置 + return GenSettings() + + @lru_cache() + def get_upload_config(self): + """ + 获取数据库配置 + """ + # 实例上传配置 + return UploadSettings() + + @lru_cache() + def get_haikang_config(self): + """ + 获取海康平台配置 + """ + return HaiKangSettings() + + @lru_cache() + def get_ragflow_config(self): + """ + 获取RAGFlow配置 + """ + return RAGFlowSettings() + + @lru_cache() + def get_weather_config(self): + """获取天气配置""" + return WeatherSettings() + + @staticmethod + def parse_cli_args(): + """ + 解析命令行参数 + """ + if 'uvicorn' in sys.argv[0]: + # 使用uvicorn启动时,命令行参数需要按照uvicorn的文档进行配置,无法自定义参数 + pass + else: + # 使用argparse定义命令行参数 + parser = argparse.ArgumentParser(description='命令行参数') + parser.add_argument('--env', type=str, default='dev', help='运行环境') + # 解析命令行参数 + args = parser.parse_args() + # 设置环境变量,如果未设置命令行参数,默认APP_ENV为dev + os.environ['APP_ENV'] = args.env if args.env else 'dev' + # 读取运行环境 + run_env = os.environ.get('APP_ENV', '') + # 运行环境未指定时默认加载.env.dev + env_file = '.env.dev' + # 运行环境不为空时按命令行参数加载对应.env文件 + if run_env != '': + env_file = f'.env.{run_env}' + # 加载配置 + load_dotenv(env_file) + + +# 实例化获取配置类 +get_config = GetConfig() +# 应用配置 +AppConfig = get_config.get_app_config() +# Jwt配置 +JwtConfig = get_config.get_jwt_config() +# 数据库配置 +DataBaseConfig = get_config.get_database_config() +# Redis配置 +RedisConfig = get_config.get_redis_config() +# 代码生成配置 +GenConfig = get_config.get_gen_config() +# 上传配置 +UploadConfig = get_config.get_upload_config() +# 海康平台配置 +HaiKangConfig = get_config.get_haikang_config() +# RAGFlow配置 +RAGFlowConfig = get_config.get_ragflow_config() +# compreface配置 +ComprefaceConfig = get_config.get_compreface_config() +# 天气配置 +WeatherConfig = get_config.get_weather_config() \ No newline at end of file diff --git a/ruoyi-fastapi-backend/config/get_db.py b/ruoyi-fastapi-backend/config/get_db.py new file mode 100644 index 0000000..20986ae --- /dev/null +++ b/ruoyi-fastapi-backend/config/get_db.py @@ -0,0 +1,24 @@ +from config.database import async_engine, AsyncSessionLocal, Base +from utils.log_util import logger + + +async def get_db(): + """ + 每一个请求处理完毕后会关闭当前连接,不同的请求使用不同的连接 + + :return: + """ + async with AsyncSessionLocal() as current_db: + yield current_db + + +async def init_create_table(): + """ + 应用启动时初始化数据库连接 + + :return: + """ + logger.info('初始化数据库连接...') + async with async_engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + logger.info('数据库连接成功') diff --git a/ruoyi-fastapi-backend/config/get_redis.py b/ruoyi-fastapi-backend/config/get_redis.py new file mode 100644 index 0000000..9d78cad --- /dev/null +++ b/ruoyi-fastapi-backend/config/get_redis.py @@ -0,0 +1,77 @@ +from redis import asyncio as aioredis +from redis.exceptions import AuthenticationError, TimeoutError, RedisError +from config.database import AsyncSessionLocal +from config.env import RedisConfig +from module_admin.service.config_service import ConfigService +from module_admin.service.dict_service import DictDataService +from utils.log_util import logger + + +class RedisUtil: + """ + Redis相关方法 + """ + + @classmethod + async def create_redis_pool(cls) -> aioredis.Redis: + """ + 应用启动时初始化redis连接 + + :return: Redis连接对象 + """ + logger.info('开始连接redis...') + redis = await aioredis.from_url( + url=f'redis://{RedisConfig.redis_host}', + port=RedisConfig.redis_port, + username=RedisConfig.redis_username, + password=RedisConfig.redis_password, + db=RedisConfig.redis_database, + encoding='utf-8', + decode_responses=True, + ) + try: + connection = await redis.ping() + if connection: + logger.info('redis连接成功') + else: + logger.error('redis连接失败') + except AuthenticationError as e: + logger.error(f'redis用户名或密码错误,详细错误信息:{e}') + except TimeoutError as e: + logger.error(f'redis连接超时,详细错误信息:{e}') + except RedisError as e: + logger.error(f'redis连接错误,详细错误信息:{e}') + return redis + + @classmethod + async def close_redis_pool(cls, app): + """ + 应用关闭时关闭redis连接 + + :param app: fastapi对象 + :return: + """ + await app.state.redis.close() + logger.info('关闭redis连接成功') + + @classmethod + async def init_sys_dict(cls, redis): + """ + 应用启动时缓存字典表 + + :param redis: redis对象 + :return: + """ + async with AsyncSessionLocal() as session: + await DictDataService.init_cache_sys_dict_services(session, redis) + + @classmethod + async def init_sys_config(cls, redis): + """ + 应用启动时缓存参数配置表 + + :param redis: redis对象 + :return: + """ + async with AsyncSessionLocal() as session: + await ConfigService.init_cache_sys_config_services(session, redis) diff --git a/ruoyi-fastapi-backend/config/get_scheduler.py b/ruoyi-fastapi-backend/config/get_scheduler.py new file mode 100644 index 0000000..4b0c298 --- /dev/null +++ b/ruoyi-fastapi-backend/config/get_scheduler.py @@ -0,0 +1,279 @@ +import json +from apscheduler.events import EVENT_ALL +from apscheduler.executors.asyncio import AsyncIOExecutor +from apscheduler.executors.pool import ProcessPoolExecutor +from apscheduler.jobstores.memory import MemoryJobStore +from apscheduler.jobstores.redis import RedisJobStore +from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from apscheduler.triggers.combining import OrTrigger +from apscheduler.triggers.cron import CronTrigger +from apscheduler.triggers.date import DateTrigger +from asyncio import iscoroutinefunction +from datetime import datetime, timedelta +from sqlalchemy.engine import create_engine +from sqlalchemy.orm import sessionmaker +from typing import Union +from config.database import AsyncSessionLocal, quote_plus +from config.env import DataBaseConfig, RedisConfig +from module_admin.dao.job_dao import JobDao +from module_admin.entity.vo.job_vo import JobLogModel, JobModel +from module_admin.service.job_log_service import JobLogService +from utils.log_util import logger +import module_task # noqa: F401 + + +# 重写Cron定时 +class MyCronTrigger(CronTrigger): + @classmethod + def from_crontab(cls, expr: str, timezone=None): + values = expr.split() + if len(values) != 6 and len(values) != 7: + raise ValueError('Wrong number of fields; got {}, expected 6 or 7'.format(len(values))) + + second = values[0] + minute = values[1] + hour = values[2] + if '?' in values[3]: + day = None + elif 'L' in values[5]: + day = f"last {values[5].replace('L', '')}" + elif 'W' in values[3]: + day = cls.__find_recent_workday(int(values[3].split('W')[0])) + else: + day = values[3].replace('L', 'last') + month = values[4] + if '?' in values[5] or 'L' in values[5]: + week = None + elif '#' in values[5]: + week = int(values[5].split('#')[1]) + else: + week = values[5] + if '#' in values[5]: + day_of_week = int(values[5].split('#')[0]) - 1 + else: + day_of_week = None + year = values[6] if len(values) == 7 else None + return cls( + second=second, + minute=minute, + hour=hour, + day=day, + month=month, + week=week, + day_of_week=day_of_week, + year=year, + timezone=timezone, + ) + + @classmethod + def __find_recent_workday(cls, day: int): + now = datetime.now() + date = datetime(now.year, now.month, day) + if date.weekday() < 5: + return date.day + else: + diff = 1 + while True: + previous_day = date - timedelta(days=diff) + if previous_day.weekday() < 5: + return previous_day.day + else: + diff += 1 + + +SQLALCHEMY_DATABASE_URL = ( + f'mysql+pymysql://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@' + f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}' +) +if DataBaseConfig.db_type == 'postgresql': + SQLALCHEMY_DATABASE_URL = ( + f'postgresql+psycopg2://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@' + f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}' + ) +engine = create_engine( + SQLALCHEMY_DATABASE_URL, + echo=DataBaseConfig.db_echo, + max_overflow=DataBaseConfig.db_max_overflow, + pool_size=DataBaseConfig.db_pool_size, + pool_recycle=DataBaseConfig.db_pool_recycle, + pool_timeout=DataBaseConfig.db_pool_timeout, +) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +job_stores = { + 'default': MemoryJobStore(), + 'sqlalchemy': SQLAlchemyJobStore(url=SQLALCHEMY_DATABASE_URL, engine=engine), + 'redis': RedisJobStore( + **dict( + host=RedisConfig.redis_host, + port=RedisConfig.redis_port, + username=RedisConfig.redis_username, + password=RedisConfig.redis_password, + db=RedisConfig.redis_database, + ) + ), +} +executors = {'default': AsyncIOExecutor(), 'processpool': ProcessPoolExecutor(5)} +job_defaults = {'coalesce': False, 'max_instance': 1} +scheduler = AsyncIOScheduler() +scheduler.configure(jobstores=job_stores, executors=executors, job_defaults=job_defaults) + + +class SchedulerUtil: + """ + 定时任务相关方法 + """ + + @classmethod + async def init_system_scheduler(cls): + """ + 应用启动时初始化定时任务 + + :return: + """ + logger.info('开始启动定时任务...') + scheduler.start() + async with AsyncSessionLocal() as session: + job_list = await JobDao.get_job_list_for_scheduler(session) + for item in job_list: + cls.remove_scheduler_job(job_id=str(item.job_id)) + cls.add_scheduler_job(item) + scheduler.add_listener(cls.scheduler_event_listener, EVENT_ALL) + logger.info('系统初始定时任务加载成功') + + @classmethod + async def close_system_scheduler(cls): + """ + 应用关闭时关闭定时任务 + + :return: + """ + scheduler.shutdown() + logger.info('关闭定时任务成功') + + @classmethod + def get_scheduler_job(cls, job_id: Union[str, int]): + """ + 根据任务id获取任务对象 + + :param job_id: 任务id + :return: 任务对象 + """ + query_job = scheduler.get_job(job_id=str(job_id)) + + return query_job + + @classmethod + def add_scheduler_job(cls, job_info: JobModel): + """ + 根据输入的任务对象信息添加任务 + + :param job_info: 任务对象信息 + :return: + """ + job_func = eval(job_info.invoke_target) + job_executor = job_info.job_executor + if iscoroutinefunction(job_func): + job_executor = 'default' + scheduler.add_job( + func=eval(job_info.invoke_target), + trigger=MyCronTrigger.from_crontab(job_info.cron_expression), + args=job_info.job_args.split(',') if job_info.job_args else None, + kwargs=json.loads(job_info.job_kwargs) if job_info.job_kwargs else None, + id=str(job_info.job_id), + name=job_info.job_name, + misfire_grace_time=1000000000000 if job_info.misfire_policy == '3' else None, + coalesce=True if job_info.misfire_policy == '2' else False, + max_instances=3 if job_info.concurrent == '0' else 1, + jobstore=job_info.job_group, + executor=job_executor, + ) + + @classmethod + def execute_scheduler_job_once(cls, job_info: JobModel): + """ + 根据输入的任务对象执行一次任务 + + :param job_info: 任务对象信息 + :return: + """ + job_func = eval(job_info.invoke_target) + job_executor = job_info.job_executor + if iscoroutinefunction(job_func): + job_executor = 'default' + job_trigger = DateTrigger() + if job_info.status == '0': + job_trigger = OrTrigger(triggers=[DateTrigger(), MyCronTrigger.from_crontab(job_info.cron_expression)]) + scheduler.add_job( + func=eval(job_info.invoke_target), + trigger=job_trigger, + args=job_info.job_args.split(',') if job_info.job_args else None, + kwargs=json.loads(job_info.job_kwargs) if job_info.job_kwargs else None, + id=str(job_info.job_id), + name=job_info.job_name, + misfire_grace_time=1000000000000 if job_info.misfire_policy == '3' else None, + coalesce=True if job_info.misfire_policy == '2' else False, + max_instances=3 if job_info.concurrent == '0' else 1, + jobstore=job_info.job_group, + executor=job_executor, + ) + + @classmethod + def remove_scheduler_job(cls, job_id: Union[str, int]): + """ + 根据任务id移除任务 + + :param job_id: 任务id + :return: + """ + query_job = cls.get_scheduler_job(job_id=job_id) + if query_job: + scheduler.remove_job(job_id=str(job_id)) + + @classmethod + def scheduler_event_listener(cls, event): + # 获取事件类型和任务ID + event_type = event.__class__.__name__ + # 获取任务执行异常信息 + status = '0' + exception_info = '' + if event_type == 'JobExecutionEvent' and event.exception: + exception_info = str(event.exception) + status = '1' + if hasattr(event, 'job_id'): + job_id = event.job_id + query_job = cls.get_scheduler_job(job_id=job_id) + if query_job: + query_job_info = query_job.__getstate__() + # 获取任务名称 + job_name = query_job_info.get('name') + # 获取任务组名 + job_group = query_job._jobstore_alias + # 获取任务执行器 + job_executor = query_job_info.get('executor') + # 获取调用目标字符串 + invoke_target = query_job_info.get('func') + # 获取调用函数位置参数 + job_args = ','.join(query_job_info.get('args')) + # 获取调用函数关键字参数 + job_kwargs = json.dumps(query_job_info.get('kwargs')) + # 获取任务触发器 + job_trigger = str(query_job_info.get('trigger')) + # 构造日志消息 + job_message = f"事件类型: {event_type}, 任务ID: {job_id}, 任务名称: {job_name}, 执行于{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + job_log = JobLogModel( + jobName=job_name, + jobGroup=job_group, + jobExecutor=job_executor, + invokeTarget=invoke_target, + jobArgs=job_args, + jobKwargs=job_kwargs, + jobTrigger=job_trigger, + jobMessage=job_message, + status=status, + exceptionInfo=exception_info, + createTime=datetime.now(), + ) + session = SessionLocal() + JobLogService.add_job_log_services(session, job_log) + session.close() diff --git a/ruoyi-fastapi-backend/exceptions/__init__.py b/ruoyi-fastapi-backend/exceptions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ruoyi-fastapi-backend/exceptions/exception.py b/ruoyi-fastapi-backend/exceptions/exception.py new file mode 100644 index 0000000..b86f50d --- /dev/null +++ b/ruoyi-fastapi-backend/exceptions/exception.py @@ -0,0 +1,58 @@ +class LoginException(Exception): + """ + 自定义登录异常LoginException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +class AuthException(Exception): + """ + 自定义令牌异常AuthException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +class PermissionException(Exception): + """ + 自定义权限异常PermissionException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +class ServiceException(Exception): + """ + 自定义服务异常ServiceException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +class ServiceWarning(Exception): + """ + 自定义服务警告ServiceWarning + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +class ModelValidatorException(Exception): + """ + 自定义模型校验异常ModelValidatorException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message diff --git a/ruoyi-fastapi-backend/exceptions/handle.py b/ruoyi-fastapi-backend/exceptions/handle.py new file mode 100644 index 0000000..dec516a --- /dev/null +++ b/ruoyi-fastapi-backend/exceptions/handle.py @@ -0,0 +1,71 @@ +from fastapi import FastAPI, Request +from fastapi.exceptions import HTTPException +from pydantic_validation_decorator import FieldValidationError +from exceptions.exception import ( + AuthException, + LoginException, + ModelValidatorException, + PermissionException, + ServiceException, + ServiceWarning, +) +from utils.log_util import logger +from utils.response_util import jsonable_encoder, JSONResponse, ResponseUtil + + +def handle_exception(app: FastAPI): + """ + 全局异常处理 + """ + + # 自定义token检验异常 + @app.exception_handler(AuthException) + async def auth_exception_handler(request: Request, exc: AuthException): + return ResponseUtil.unauthorized(data=exc.data, msg=exc.message) + + # 自定义登录检验异常 + @app.exception_handler(LoginException) + async def login_exception_handler(request: Request, exc: LoginException): + return ResponseUtil.failure(data=exc.data, msg=exc.message) + + # 自定义模型检验异常 + @app.exception_handler(ModelValidatorException) + async def model_validator_exception_handler(request: Request, exc: ModelValidatorException): + logger.warning(exc.message) + return ResponseUtil.failure(data=exc.data, msg=exc.message) + + # 自定义字段检验异常 + @app.exception_handler(FieldValidationError) + async def field_validation_error_handler(request: Request, exc: FieldValidationError): + logger.warning(exc.message) + return ResponseUtil.failure(msg=exc.message) + + # 自定义权限检验异常 + @app.exception_handler(PermissionException) + async def permission_exception_handler(request: Request, exc: PermissionException): + return ResponseUtil.forbidden(data=exc.data, msg=exc.message) + + # 自定义服务异常 + @app.exception_handler(ServiceException) + async def service_exception_handler(request: Request, exc: ServiceException): + logger.error(exc.message) + return ResponseUtil.error(data=exc.data, msg=exc.message) + + # 自定义服务警告 + @app.exception_handler(ServiceWarning) + async def service_warning_handler(request: Request, exc: ServiceWarning): + logger.warning(exc.message) + return ResponseUtil.failure(data=exc.data, msg=exc.message) + + # 处理其他http请求异常 + @app.exception_handler(HTTPException) + async def http_exception_handler(request: Request, exc: HTTPException): + return JSONResponse( + content=jsonable_encoder({'code': exc.status_code, 'msg': exc.detail}), status_code=exc.status_code + ) + + # 处理其他异常 + @app.exception_handler(Exception) + async def exception_handler(request: Request, exc: Exception): + logger.exception(exc) + return ResponseUtil.error(msg=str(exc)) diff --git a/ruoyi-fastapi-backend/middlewares/__init__.py b/ruoyi-fastapi-backend/middlewares/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ruoyi-fastapi-backend/middlewares/cors_middleware.py b/ruoyi-fastapi-backend/middlewares/cors_middleware.py new file mode 100644 index 0000000..55508e7 --- /dev/null +++ b/ruoyi-fastapi-backend/middlewares/cors_middleware.py @@ -0,0 +1,25 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + + +def add_cors_middleware(app: FastAPI): + """ + 添加跨域中间件 + + :param app: FastAPI对象 + :return: + """ + # 前端页面url + origins = [ + 'http://localhost:80', + 'http://127.0.0.1:80', + ] + + # 后台api允许跨域 + app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=['*'], + allow_headers=['*'], + ) diff --git a/ruoyi-fastapi-backend/middlewares/gzip_middleware.py b/ruoyi-fastapi-backend/middlewares/gzip_middleware.py new file mode 100644 index 0000000..eb371ce --- /dev/null +++ b/ruoyi-fastapi-backend/middlewares/gzip_middleware.py @@ -0,0 +1,12 @@ +from fastapi import FastAPI +from starlette.middleware.gzip import GZipMiddleware + + +def add_gzip_middleware(app: FastAPI): + """ + 添加gzip压缩中间件 + + :param app: FastAPI对象 + :return: + """ + app.add_middleware(GZipMiddleware, minimum_size=1000, compresslevel=9) diff --git a/ruoyi-fastapi-backend/middlewares/handle.py b/ruoyi-fastapi-backend/middlewares/handle.py new file mode 100644 index 0000000..abb2d0d --- /dev/null +++ b/ruoyi-fastapi-backend/middlewares/handle.py @@ -0,0 +1,16 @@ +from fastapi import FastAPI +from middlewares.cors_middleware import add_cors_middleware +from middlewares.gzip_middleware import add_gzip_middleware +from middlewares.trace_middleware import add_trace_middleware + + +def handle_middleware(app: FastAPI): + """ + 全局中间件处理 + """ + # 加载跨域中间件 + add_cors_middleware(app) + # 加载gzip压缩中间件 + add_gzip_middleware(app) + # 加载trace中间件 + add_trace_middleware(app) diff --git a/ruoyi-fastapi-backend/middlewares/trace_middleware/__init__.py b/ruoyi-fastapi-backend/middlewares/trace_middleware/__init__.py new file mode 100644 index 0000000..76f8d85 --- /dev/null +++ b/ruoyi-fastapi-backend/middlewares/trace_middleware/__init__.py @@ -0,0 +1,17 @@ +from fastapi import FastAPI +from .ctx import TraceCtx +from .middle import TraceASGIMiddleware + +__all__ = ('TraceASGIMiddleware', 'TraceCtx') + +__version__ = '0.1.0' + + +def add_trace_middleware(app: FastAPI): + """ + 添加trace中间件 + + :param app: FastAPI对象 + :return: + """ + app.add_middleware(TraceASGIMiddleware) diff --git a/ruoyi-fastapi-backend/middlewares/trace_middleware/ctx.py b/ruoyi-fastapi-backend/middlewares/trace_middleware/ctx.py new file mode 100644 index 0000000..558a5c9 --- /dev/null +++ b/ruoyi-fastapi-backend/middlewares/trace_middleware/ctx.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +""" +@author: peng +@file: ctx.py +@time: 2025/1/17 16:57 +""" + +import contextvars +from uuid import uuid4 + +CTX_REQUEST_ID: contextvars.ContextVar[str] = contextvars.ContextVar('request-id', default='') + + +class TraceCtx: + @staticmethod + def set_id(): + _id = uuid4().hex + CTX_REQUEST_ID.set(_id) + return _id + + @staticmethod + def get_id(): + return CTX_REQUEST_ID.get() diff --git a/ruoyi-fastapi-backend/middlewares/trace_middleware/middle.py b/ruoyi-fastapi-backend/middlewares/trace_middleware/middle.py new file mode 100644 index 0000000..a071692 --- /dev/null +++ b/ruoyi-fastapi-backend/middlewares/trace_middleware/middle.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +""" +@author: peng +@file: middle.py +@time: 2025/1/17 16:57 +""" + +from functools import wraps +from starlette.types import ASGIApp, Message, Receive, Scope, Send +from .span import get_current_span, Span + + +class TraceASGIMiddleware: + """ + fastapi-example: + app = FastAPI() + app.add_middleware(TraceASGIMiddleware) + """ + + def __init__(self, app: ASGIApp) -> None: + self.app = app + + @staticmethod + async def my_receive(receive: Receive, span: Span): + await span.request_before() + + @wraps(receive) + async def my_receive(): + message = await receive() + await span.request_after(message) + return message + + return my_receive + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope['type'] != 'http': + await self.app(scope, receive, send) + return + + async with get_current_span(scope) as span: + handle_outgoing_receive = await self.my_receive(receive, span) + + async def handle_outgoing_request(message: 'Message') -> None: + await span.response(message) + await send(message) + + await self.app(scope, handle_outgoing_receive, handle_outgoing_request) diff --git a/ruoyi-fastapi-backend/middlewares/trace_middleware/span.py b/ruoyi-fastapi-backend/middlewares/trace_middleware/span.py new file mode 100644 index 0000000..1e38eab --- /dev/null +++ b/ruoyi-fastapi-backend/middlewares/trace_middleware/span.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +""" +@author: peng +@file: span.py +@time: 2025/1/17 16:57 +""" + +from contextlib import asynccontextmanager +from starlette.types import Scope, Message +from .ctx import TraceCtx + + +class Span: + """ + 整个http生命周期: + request(before) --> request(after) --> response(before) --> response(after) + """ + + def __init__(self, scope: Scope): + self.scope = scope + + async def request_before(self): + """ + request_before: 处理header信息等, 如记录请求体信息 + """ + TraceCtx.set_id() + + async def request_after(self, message: Message): + """ + request_after: 处理请求bytes, 如记录请求参数 + + example: + message: {'type': 'http.request', 'body': b'{\r\n "name": "\xe8\x8b\x8f\xe8\x8b\x8f\xe8\x8b\x8f"\r\n}', 'more_body': False} + """ + return message + + async def response(self, message: Message): + """ + if message['type'] == "http.response.start": -----> request-before + pass + if message['type'] == "http.response.body": -----> request-after + message.get('body', b'') + pass + """ + if message['type'] == 'http.response.start': + message['headers'].append((b'request-id', TraceCtx.get_id().encode())) + return message + + +@asynccontextmanager +async def get_current_span(scope: Scope): + yield Span(scope) diff --git a/ruoyi-fastapi-backend/module_admin/__init__.py b/ruoyi-fastapi-backend/module_admin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ruoyi-fastapi-backend/module_admin/annotation/__init__.py b/ruoyi-fastapi-backend/module_admin/annotation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py b/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py new file mode 100644 index 0000000..19ac84e --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py @@ -0,0 +1,266 @@ +import inspect +import json +import os +import requests +import time +from datetime import datetime +from fastapi import Request +from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse +from functools import lru_cache, wraps +from sqlalchemy.ext.asyncio import AsyncSession +from typing import Any, Callable, Literal, Optional +from user_agents import parse +from config.enums import BusinessType +from config.env import AppConfig +from exceptions.exception import LoginException, ServiceException, ServiceWarning +from module_admin.entity.vo.log_vo import LogininforModel, OperLogModel +from module_admin.service.log_service import LoginLogService, OperationLogService +from module_admin.service.login_service import LoginService +from utils.log_util import logger +from utils.response_util import ResponseUtil + + +class Log: + """ + 日志装饰器 + """ + + def __init__( + self, + title: str, + business_type: BusinessType, + log_type: Optional[Literal['login', 'operation']] = 'operation', + ): + """ + 日志装饰器 + + :param title: 当前日志装饰器装饰的模块标题 + :param business_type: 业务类型(OTHER其它 INSERT新增 UPDATE修改 DELETE删除 GRANT授权 EXPORT导出 IMPORT导入 FORCE强退 GENCODE生成代码 CLEAN清空数据) + :param log_type: 日志类型(login表示登录日志,operation表示为操作日志) + :return: + """ + self.title = title + self.business_type = business_type.value + self.log_type = log_type + + def __call__(self, func): + @wraps(func) + async def wrapper(*args, **kwargs): + start_time = time.time() + # 获取被装饰函数的文件路径 + file_path = inspect.getfile(func) + # 获取项目根路径 + project_root = os.getcwd() + # 处理文件路径,去除项目根路径部分 + relative_path = os.path.relpath(file_path, start=project_root)[0:-2].replace('\\', '.').replace('/', '.') + # 获取当前被装饰函数所在路径 + func_path = f'{relative_path}{func.__name__}()' + # 获取上下文信息 + request_name_list = get_function_parameters_name_by_type(func, Request) + request = get_function_parameters_value_by_name(func, request_name_list[0], *args, **kwargs) + token = request.headers.get('Authorization') + session_name_list = get_function_parameters_name_by_type(func, AsyncSession) + query_db = get_function_parameters_value_by_name(func, session_name_list[0], *args, **kwargs) + request_method = request.method + operator_type = 0 + user_agent = request.headers.get('User-Agent') + if 'Windows' in user_agent or 'Macintosh' in user_agent or 'Linux' in user_agent: + operator_type = 1 + if 'Mobile' in user_agent or 'Android' in user_agent or 'iPhone' in user_agent: + operator_type = 2 + # 获取请求的url + oper_url = request.url.path + # 获取请求的ip及ip归属区域 + oper_ip = request.headers.get('X-Forwarded-For') + oper_location = '内网IP' + if AppConfig.app_ip_location_query: + oper_location = get_ip_location(oper_ip) + # 根据不同的请求类型使用不同的方法获取请求参数 + content_type = request.headers.get('Content-Type') + if content_type and ( + 'multipart/form-data' in content_type or 'application/x-www-form-urlencoded' in content_type + ): + payload = await request.form() + oper_param = '\n'.join([f'{key}: {value}' for key, value in payload.items()]) + else: + payload = await request.body() + # 通过 request.path_params 直接访问路径参数 + path_params = request.path_params + oper_param = {} + if payload: + oper_param.update(json.loads(str(payload, 'utf-8'))) + if path_params: + oper_param.update(path_params) + oper_param = json.dumps(oper_param, ensure_ascii=False) + # 日志表请求参数字段长度最大为2000,因此在此处判断长度 + if len(oper_param) > 2000: + oper_param = '请求参数过长' + + # 获取操作时间 + oper_time = datetime.now() + # 此处在登录之前向原始函数传递一些登录信息,用于监测在线用户的相关信息 + login_log = {} + if self.log_type == 'login': + user_agent_info = parse(user_agent) + browser = f'{user_agent_info.browser.family}' + system_os = f'{user_agent_info.os.family}' + if user_agent_info.browser.version != (): + browser += f' {user_agent_info.browser.version[0]}' + if user_agent_info.os.version != (): + system_os += f' {user_agent_info.os.version[0]}' + login_log = dict( + ipaddr=oper_ip, + loginLocation=oper_location, + browser=browser, + os=system_os, + loginTime=oper_time.strftime('%Y-%m-%d %H:%M:%S'), + ) + kwargs['form_data'].login_info = login_log + try: + # 调用原始函数 + result = await func(*args, **kwargs) + except (LoginException, ServiceWarning) as e: + logger.warning(e.message) + result = ResponseUtil.failure(data=e.data, msg=e.message) + except ServiceException as e: + logger.error(e.message) + result = ResponseUtil.error(data=e.data, msg=e.message) + except Exception as e: + logger.exception(e) + result = ResponseUtil.error(msg=str(e)) + # 获取请求耗时 + cost_time = float(time.time() - start_time) * 100 + # 判断请求是否来自api文档 + request_from_swagger = ( + request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False + ) + request_from_redoc = ( + request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False + ) + # 根据响应结果的类型使用不同的方法获取响应结果参数 + if ( + isinstance(result, JSONResponse) + or isinstance(result, ORJSONResponse) + or isinstance(result, UJSONResponse) + ): + result_dict = json.loads(str(result.body, 'utf-8')) + else: + if request_from_swagger or request_from_redoc: + result_dict = {} + else: + if result.status_code == 200: + result_dict = {'code': result.status_code, 'message': '获取成功'} + else: + result_dict = {'code': result.status_code, 'message': '获取失败'} + json_result = json.dumps(result_dict, ensure_ascii=False) + # 根据响应结果获取响应状态及异常信息 + status = 1 + error_msg = '' + if result_dict.get('code') == 200: + status = 0 + else: + error_msg = result_dict.get('msg') + # 根据日志类型向对应的日志表插入数据 + if self.log_type == 'login': + # 登录请求来自于api文档时不记录登录日志,其余情况则记录 + if request_from_swagger or request_from_redoc: + pass + else: + user = kwargs.get('form_data') + user_name = user.username + login_log['loginTime'] = oper_time + login_log['userName'] = user_name + login_log['status'] = str(status) + login_log['msg'] = result_dict.get('msg') + + await LoginLogService.add_login_log_services(query_db, LogininforModel(**login_log)) + else: + try: + current_user = await LoginService.get_current_user(request, token, query_db) + oper_name = current_user.user.user_name if current_user.user else None + dept_name = current_user.user.dept.dept_name if current_user.user.dept else None + except: + oper_name = None + dept_name = None + + operation_log = OperLogModel( + title=self.title, + businessType=self.business_type, + method=func_path, + requestMethod=request_method, + operatorType=operator_type, + operName=oper_name, + deptName=dept_name, + operUrl=oper_url, + operIp=oper_ip, + operLocation=oper_location, + operParam=oper_param, + jsonResult=json_result, + status=status, + errorMsg=error_msg, + operTime=oper_time, + costTime=int(cost_time), + ) + await OperationLogService.add_operation_log_services(query_db, operation_log) + + return result + + return wrapper + + +@lru_cache() +def get_ip_location(oper_ip: str): + """ + 查询ip归属区域 + + :param oper_ip: 需要查询的ip + :return: ip归属区域 + """ + oper_location = '内网IP' + try: + if oper_ip != '127.0.0.1' and oper_ip != 'localhost': + oper_location = '未知' + ip_result = requests.get(f'https://qifu-api.baidubce.com/ip/geo/v1/district?ip={oper_ip}') + if ip_result.status_code == 200: + prov = ip_result.json().get('data').get('prov') + city = ip_result.json().get('data').get('city') + if prov or city: + oper_location = f'{prov}-{city}' + except Exception as e: + oper_location = '未知' + print(e) + return oper_location + + +def get_function_parameters_name_by_type(func: Callable, param_type: Any): + """ + 获取函数指定类型的参数名称 + + :param func: 函数 + :param arg_type: 参数类型 + :return: 函数指定类型的参数名称 + """ + # 获取函数的参数信息 + parameters = inspect.signature(func).parameters + # 找到指定类型的参数名称 + parameters_name_list = [] + for name, param in parameters.items(): + if param.annotation == param_type: + parameters_name_list.append(name) + return parameters_name_list + + +def get_function_parameters_value_by_name(func: Callable, name: str, *args, **kwargs): + """ + 获取函数指定参数的值 + + :param func: 函数 + :param name: 参数名 + :return: 参数值 + """ + # 获取参数值 + bound_parameters = inspect.signature(func).bind(*args, **kwargs) + bound_parameters.apply_defaults() + parameters_value = bound_parameters.arguments.get(name) + + return parameters_value diff --git a/ruoyi-fastapi-backend/module_admin/annotation/pydantic_annotation.py b/ruoyi-fastapi-backend/module_admin/annotation/pydantic_annotation.py new file mode 100644 index 0000000..11e8d7f --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/annotation/pydantic_annotation.py @@ -0,0 +1,84 @@ +import inspect +from fastapi import Form, Query +from pydantic import BaseModel +from pydantic.fields import FieldInfo +from typing import Type, TypeVar + + +BaseModelVar = TypeVar('BaseModelVar', bound=BaseModel) + + +def as_query(cls: Type[BaseModelVar]) -> Type[BaseModelVar]: + """ + pydantic模型查询参数装饰器,将pydantic模型用于接收查询参数 + """ + new_parameters = [] + + for field_name, model_field in cls.model_fields.items(): + model_field: FieldInfo # type: ignore + + if not model_field.is_required(): + new_parameters.append( + inspect.Parameter( + model_field.alias, + inspect.Parameter.POSITIONAL_ONLY, + default=Query(default=model_field.default, description=model_field.description), + annotation=model_field.annotation, + ) + ) + else: + new_parameters.append( + inspect.Parameter( + model_field.alias, + inspect.Parameter.POSITIONAL_ONLY, + default=Query(..., description=model_field.description), + annotation=model_field.annotation, + ) + ) + + async def as_query_func(**data): + return cls(**data) + + sig = inspect.signature(as_query_func) + sig = sig.replace(parameters=new_parameters) + as_query_func.__signature__ = sig # type: ignore + setattr(cls, 'as_query', as_query_func) + return cls + + +def as_form(cls: Type[BaseModelVar]) -> Type[BaseModelVar]: + """ + pydantic模型表单参数装饰器,将pydantic模型用于接收表单参数 + """ + new_parameters = [] + + for field_name, model_field in cls.model_fields.items(): + model_field: FieldInfo # type: ignore + + if not model_field.is_required(): + new_parameters.append( + inspect.Parameter( + model_field.alias, + inspect.Parameter.POSITIONAL_ONLY, + default=Form(default=model_field.default, description=model_field.description), + annotation=model_field.annotation, + ) + ) + else: + new_parameters.append( + inspect.Parameter( + model_field.alias, + inspect.Parameter.POSITIONAL_ONLY, + default=Form(..., description=model_field.description), + annotation=model_field.annotation, + ) + ) + + async def as_form_func(**data): + return cls(**data) + + sig = inspect.signature(as_form_func) + sig = sig.replace(parameters=new_parameters) + as_form_func.__signature__ = sig # type: ignore + setattr(cls, 'as_form', as_form_func) + return cls diff --git a/ruoyi-fastapi-backend/module_admin/aspect/__init__.py b/ruoyi-fastapi-backend/module_admin/aspect/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ruoyi-fastapi-backend/module_admin/aspect/data_scope.py b/ruoyi-fastapi-backend/module_admin/aspect/data_scope.py new file mode 100644 index 0000000..5a7afbb --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/aspect/data_scope.py @@ -0,0 +1,75 @@ +from fastapi import Depends +from typing import Optional +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService + + +class GetDataScope: + """ + 获取当前用户数据权限对应的查询sql语句 + """ + + DATA_SCOPE_ALL = '1' + DATA_SCOPE_CUSTOM = '2' + DATA_SCOPE_DEPT = '3' + DATA_SCOPE_DEPT_AND_CHILD = '4' + DATA_SCOPE_SELF = '5' + + def __init__( + self, + query_alias: Optional[str] = '', + db_alias: Optional[str] = 'db', + user_alias: Optional[str] = 'user_id', + dept_alias: Optional[str] = 'dept_id', + ): + """ + 获取当前用户数据权限对应的查询sql语句 + + :param query_alias: 所要查询表对应的sqlalchemy模型名称,默认为'' + :param db_alias: orm对象别名,默认为'db' + :param user_alias: 用户id字段别名,默认为'user_id' + :param dept_alias: 部门id字段别名,默认为'dept_id' + """ + self.query_alias = query_alias + self.db_alias = db_alias + self.user_alias = user_alias + self.dept_alias = dept_alias + + def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + user_id = current_user.user.user_id + dept_id = current_user.user.dept_id + custom_data_scope_role_id_list = [ + item.role_id for item in current_user.user.role if item.data_scope == self.DATA_SCOPE_CUSTOM + ] + param_sql_list = [] + for role in current_user.user.role: + if current_user.user.admin or role.data_scope == self.DATA_SCOPE_ALL: + param_sql_list = ['1 == 1'] + break + elif role.data_scope == self.DATA_SCOPE_CUSTOM: + if len(custom_data_scope_role_id_list) > 1: + param_sql_list.append( + f"{self.query_alias}.{self.dept_alias}.in_(select(SysRoleDept.dept_id).where(SysRoleDept.role_id.in_({custom_data_scope_role_id_list}))) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0" + ) + else: + param_sql_list.append( + f"{self.query_alias}.{self.dept_alias}.in_(select(SysRoleDept.dept_id).where(SysRoleDept.role_id == {role.role_id})) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0" + ) + elif role.data_scope == self.DATA_SCOPE_DEPT: + param_sql_list.append( + f"{self.query_alias}.{self.dept_alias} == {dept_id} if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0" + ) + elif role.data_scope == self.DATA_SCOPE_DEPT_AND_CHILD: + param_sql_list.append( + f"{self.query_alias}.{self.dept_alias}.in_(select(SysDept.dept_id).where(or_(SysDept.dept_id == {dept_id}, func.find_in_set({dept_id}, SysDept.ancestors)))) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0" + ) + elif role.data_scope == self.DATA_SCOPE_SELF: + param_sql_list.append( + f"{self.query_alias}.{self.user_alias} == {user_id} if hasattr({self.query_alias}, '{self.user_alias}') else 1 == 0" + ) + else: + param_sql_list.append('1 == 0') + param_sql_list = list(dict.fromkeys(param_sql_list)) + param_sql = f"or_({', '.join(param_sql_list)})" + + return param_sql diff --git a/ruoyi-fastapi-backend/module_admin/aspect/interface_auth.py b/ruoyi-fastapi-backend/module_admin/aspect/interface_auth.py new file mode 100644 index 0000000..8f8349d --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/aspect/interface_auth.py @@ -0,0 +1,68 @@ +from fastapi import Depends +from typing import List, Union +from exceptions.exception import PermissionException +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService + + +class CheckUserInterfaceAuth: + """ + 校验当前用户是否具有相应的接口权限 + """ + + def __init__(self, perm: Union[str, List], is_strict: bool = False): + """ + 校验当前用户是否具有相应的接口权限 + + :param perm: 权限标识 + :param is_strict: 当传入的权限标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个权限标识,所有的校验结果都需要为True才会通过 + """ + self.perm = perm + self.is_strict = is_strict + + def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + user_auth_list = current_user.permissions + if '*:*:*' in user_auth_list: + return True + if isinstance(self.perm, str): + if self.perm in user_auth_list: + return True + if isinstance(self.perm, list): + if self.is_strict: + if all([perm_str in user_auth_list for perm_str in self.perm]): + return True + else: + if any([perm_str in user_auth_list for perm_str in self.perm]): + return True + raise PermissionException(data='', message='该用户无此接口权限') + + +class CheckRoleInterfaceAuth: + """ + 根据角色校验当前用户是否具有相应的接口权限 + """ + + def __init__(self, role_key: Union[str, List], is_strict: bool = False): + """ + 根据角色校验当前用户是否具有相应的接口权限 + + :param role_key: 角色标识 + :param is_strict: 当传入的角色标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个角色标识,所有的校验结果都需要为True才会通过 + """ + self.role_key = role_key + self.is_strict = is_strict + + def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + user_role_list = current_user.user.role + user_role_key_list = [role.role_key for role in user_role_list] + if isinstance(self.role_key, str): + if self.role_key in user_role_key_list: + return True + if isinstance(self.role_key, list): + if self.is_strict: + if all([role_key_str in user_role_key_list for role_key_str in self.role_key]): + return True + else: + if any([role_key_str in user_role_key_list for role_key_str in self.role_key]): + return True + raise PermissionException(data='', message='该用户无此接口权限') diff --git a/ruoyi-fastapi-backend/module_admin/controller/__init__.py b/ruoyi-fastapi-backend/module_admin/controller/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ruoyi-fastapi-backend/module_admin/controller/cache_controller.py b/ruoyi-fastapi-backend/module_admin/controller/cache_controller.py new file mode 100644 index 0000000..9e72713 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/cache_controller.py @@ -0,0 +1,89 @@ +from fastapi import APIRouter, Depends, Request +from typing import List +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.cache_vo import CacheInfoModel, CacheMonitorModel +from module_admin.service.cache_service import CacheService +from module_admin.service.login_service import LoginService +from utils.log_util import logger +from utils.response_util import ResponseUtil + + +cacheController = APIRouter(prefix='/monitor/cache', dependencies=[Depends(LoginService.get_current_user)]) + + +@cacheController.get( + '', response_model=CacheMonitorModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))] +) +async def get_monitor_cache_info(request: Request): + # 获取全量数据 + cache_info_query_result = await CacheService.get_cache_monitor_statistical_info_services(request) + logger.info('获取成功') + + return ResponseUtil.success(data=cache_info_query_result) + + +@cacheController.get( + '/getNames', + response_model=List[CacheInfoModel], + dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))], +) +async def get_monitor_cache_name(request: Request): + # 获取全量数据 + cache_name_list_result = await CacheService.get_cache_monitor_cache_name_services() + logger.info('获取成功') + + return ResponseUtil.success(data=cache_name_list_result) + + +@cacheController.get( + '/getKeys/{cache_name}', + response_model=List[str], + dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))], +) +async def get_monitor_cache_key(request: Request, cache_name: str): + # 获取全量数据 + cache_key_list_result = await CacheService.get_cache_monitor_cache_key_services(request, cache_name) + logger.info('获取成功') + + return ResponseUtil.success(data=cache_key_list_result) + + +@cacheController.get( + '/getValue/{cache_name}/{cache_key}', + response_model=CacheInfoModel, + dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))], +) +async def get_monitor_cache_value(request: Request, cache_name: str, cache_key: str): + # 获取全量数据 + cache_value_list_result = await CacheService.get_cache_monitor_cache_value_services(request, cache_name, cache_key) + logger.info('获取成功') + + return ResponseUtil.success(data=cache_value_list_result) + + +@cacheController.delete( + '/clearCacheName/{cache_name}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))] +) +async def clear_monitor_cache_name(request: Request, cache_name: str): + clear_cache_name_result = await CacheService.clear_cache_monitor_cache_name_services(request, cache_name) + logger.info(clear_cache_name_result.message) + + return ResponseUtil.success(msg=clear_cache_name_result.message) + + +@cacheController.delete( + '/clearCacheKey/{cache_key}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))] +) +async def clear_monitor_cache_key(request: Request, cache_key: str): + clear_cache_key_result = await CacheService.clear_cache_monitor_cache_key_services(request, cache_key) + logger.info(clear_cache_key_result.message) + + return ResponseUtil.success(msg=clear_cache_key_result.message) + + +@cacheController.delete('/clearCacheAll', dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]) +async def clear_monitor_cache_all(request: Request): + clear_cache_all_result = await CacheService.clear_cache_monitor_all_services(request) + logger.info(clear_cache_all_result.message) + + return ResponseUtil.success(msg=clear_cache_all_result.message) diff --git a/ruoyi-fastapi-backend/module_admin/controller/captcha_controller.py b/ruoyi-fastapi-backend/module_admin/controller/captcha_controller.py new file mode 100644 index 0000000..83d35b8 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/captcha_controller.py @@ -0,0 +1,40 @@ +import uuid +from datetime import timedelta +from fastapi import APIRouter, Request +from config.enums import RedisInitKeyConfig +from module_admin.entity.vo.login_vo import CaptchaCode +from module_admin.service.captcha_service import CaptchaService +from utils.response_util import ResponseUtil +from utils.log_util import logger + + +captchaController = APIRouter() + + +@captchaController.get('/captchaImage') +async def get_captcha_image(request: Request): + captcha_enabled = ( + True + if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled') + == 'true' + else False + ) + register_enabled = ( + True + if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.registerUser') == 'true' + else False + ) + session_id = str(uuid.uuid4()) + captcha_result = await CaptchaService.create_captcha_image_service() + image = captcha_result[0] + computed_result = captcha_result[1] + await request.app.state.redis.set( + f'{RedisInitKeyConfig.CAPTCHA_CODES.key}:{session_id}', computed_result, ex=timedelta(minutes=2) + ) + logger.info(f'编号为{session_id}的会话获取图片验证码成功') + + return ResponseUtil.success( + model_content=CaptchaCode( + captchaEnabled=captcha_enabled, registerEnabled=register_enabled, img=image, uuid=session_id + ) + ) diff --git a/ruoyi-fastapi-backend/module_admin/controller/common_controller.py b/ruoyi-fastapi-backend/module_admin/controller/common_controller.py new file mode 100644 index 0000000..d2fd621 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/common_controller.py @@ -0,0 +1,36 @@ +from fastapi import APIRouter, BackgroundTasks, Depends, File, Query, Request, UploadFile +from module_admin.service.common_service import CommonService +from module_admin.service.login_service import LoginService +from utils.log_util import logger +from utils.response_util import ResponseUtil + +commonController = APIRouter(prefix='/common', dependencies=[Depends(LoginService.get_current_user)]) + + +@commonController.post('/upload') +async def common_upload(request: Request, file: UploadFile = File(...)): + upload_result = await CommonService.upload_service(request, file) + logger.info('上传成功') + + return ResponseUtil.success(model_content=upload_result.result) + + +@commonController.get('/download') +async def common_download( + request: Request, + background_tasks: BackgroundTasks, + file_name: str = Query(alias='fileName'), + delete: bool = Query(), +): + download_result = await CommonService.download_services(background_tasks, file_name, delete) + logger.info(download_result.message) + + return ResponseUtil.streaming(data=download_result.result) + + +@commonController.get('/download/resource') +async def common_download_resource(request: Request, resource: str = Query()): + download_resource_result = await CommonService.download_resource_services(resource) + logger.info(download_resource_result.message) + + return ResponseUtil.streaming(data=download_resource_result.result) diff --git a/ruoyi-fastapi-backend/module_admin/controller/compreface_controller.py b/ruoyi-fastapi-backend/module_admin/controller/compreface_controller.py new file mode 100644 index 0000000..8f08b7a --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/compreface_controller.py @@ -0,0 +1,58 @@ +from fastapi import APIRouter, Depends, Form, Request, Body, UploadFile, File +from module_admin.service.login_service import LoginService +from module_admin.annotation.log_annotation import Log +from module_admin.service.compreface_service import ComprefaceService +from config.enums import BusinessType +from utils.response_util import ResponseUtil +from utils.log_util import logger +import datetime + + + +comprefaceController = APIRouter(prefix='/system/compreface' + , dependencies=[Depends(LoginService.get_current_user)] + ) + +# 人脸检测 +@comprefaceController.post('/face_detection') +async def face_detection(request: Request, file: UploadFile = File(None)) -> dict: + """ + 人脸检测 + + """ + if file: # 说明是表单上传 + image = await file.read() + + else: # 尝试按字节流读取 + image = await request.body() + + result = await ComprefaceService.face_detection_service(image) + print(result) + return ResponseUtil.success(data=result) + +# 人脸识别 +@comprefaceController.post('/face_recognition') +# @Log(title='人脸识别', business_type=BusinessType.OTHER) +async def face_recognition(request: Request, file: UploadFile = File(None)) -> dict: + """ + 人脸识别 + + """ + if file: # 说明是表单上传 + image = await file.read() + + else: # 尝试按字节流读取 + image = await request.body() + + result = await ComprefaceService.face_recognition_service(image) + print(result) + return ResponseUtil.success(data=result) + + +# def my_job(): +# print("任务执行时间:", datetime.datetime.now()) + +# from config.get_scheduler import scheduler +# from apscheduler.triggers.cron import CronTrigger +# trigger = CronTrigger(minute='*', second=0) +# scheduler.add_job(my_job, trigger=trigger) \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_admin/controller/config_controller.py b/ruoyi-fastapi-backend/module_admin/controller/config_controller.py new file mode 100644 index 0000000..9b0184e --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/config_controller.py @@ -0,0 +1,123 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.config_vo import ConfigModel, ConfigPageQueryModel, DeleteConfigModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.config_service import ConfigService +from module_admin.service.login_service import LoginService +from utils.common_util import bytes2file_response, export_list2excel +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +configController = APIRouter(prefix='/system/config', dependencies=[Depends(LoginService.get_current_user)]) + + +@configController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:config:list'))] +) +async def get_system_config_list( + request: Request, + config_page_query: ConfigPageQueryModel = Depends(ConfigPageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + config_page_query_result = await ConfigService.get_config_list_services(query_db, config_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=config_page_query_result) + + +@configController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:config:add'))]) +@ValidateFields(validate_model='add_config') +@Log(title='参数管理', business_type=BusinessType.INSERT) +async def add_system_config( + request: Request, + add_config: ConfigModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_config.create_by = current_user.user.user_name + add_config.create_time = datetime.now() + add_config.update_by = current_user.user.user_name + add_config.update_time = datetime.now() + add_config_result = await ConfigService.add_config_services(request, query_db, add_config) + logger.info(add_config_result.message) + + return ResponseUtil.success(msg=add_config_result.message) + + +@configController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:config:edit'))]) +@ValidateFields(validate_model='edit_config') +@Log(title='参数管理', business_type=BusinessType.UPDATE) +async def edit_system_config( + request: Request, + edit_config: ConfigModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_config.update_by = current_user.user.user_name + edit_config.update_time = datetime.now() + edit_config_result = await ConfigService.edit_config_services(request, query_db, edit_config) + logger.info(edit_config_result.message) + + return ResponseUtil.success(msg=edit_config_result.message) + + +@configController.delete('/refreshCache', dependencies=[Depends(CheckUserInterfaceAuth('system:config:remove'))]) +@Log(title='参数管理', business_type=BusinessType.UPDATE) +async def refresh_system_config(request: Request, query_db: AsyncSession = Depends(get_db)): + refresh_config_result = await ConfigService.refresh_sys_config_services(request, query_db) + logger.info(refresh_config_result.message) + + return ResponseUtil.success(msg=refresh_config_result.message) + + +@configController.delete('/{config_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:config:remove'))]) +@Log(title='参数管理', business_type=BusinessType.DELETE) +async def delete_system_config(request: Request, config_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_config = DeleteConfigModel(configIds=config_ids) + delete_config_result = await ConfigService.delete_config_services(request, query_db, delete_config) + logger.info(delete_config_result.message) + + return ResponseUtil.success(msg=delete_config_result.message) + + +@configController.get( + '/{config_id}', response_model=ConfigModel, dependencies=[Depends(CheckUserInterfaceAuth('system:config:query'))] +) +async def query_detail_system_config(request: Request, config_id: int, query_db: AsyncSession = Depends(get_db)): + config_detail_result = await ConfigService.config_detail_services(query_db, config_id) + logger.info(f'获取config_id为{config_id}的信息成功') + + return ResponseUtil.success(data=config_detail_result) + + +@configController.get('/configKey/{config_key}') +async def query_system_config(request: Request, config_key: str): + # 获取全量数据 + config_query_result = await ConfigService.query_config_list_from_cache_services(request.app.state.redis, config_key) + logger.info('获取成功') + + return ResponseUtil.success(msg=config_query_result) + + +@configController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:config:export'))]) +@Log(title='参数管理', business_type=BusinessType.EXPORT) +async def export_system_config_list( + request: Request, + config_page_query: ConfigPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + config_query_result = await ConfigService.get_config_list_services(query_db, config_page_query, is_page=False) + config_export_result = await ConfigService.export_config_list_services(config_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=export_list2excel(config_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py b/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py new file mode 100644 index 0000000..2943206 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py @@ -0,0 +1,132 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.data_scope import GetDataScope +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.dept_vo import DeleteDeptModel, DeptModel, DeptQueryModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.dept_service import DeptService +from module_admin.service.login_service import LoginService +from utils.log_util import logger +from utils.response_util import ResponseUtil + + +deptController = APIRouter(prefix='/system/dept', dependencies=[Depends(LoginService.get_current_user)]) + + +@deptController.get( + '/list/exclude/{dept_id}', + response_model=List[DeptModel], + dependencies=[Depends(CheckUserInterfaceAuth('system:dept:list'))], +) +async def get_system_dept_tree_for_edit_option( + request: Request, + dept_id: int, + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + dept_query = DeptModel(deptId=dept_id) + dept_query_result = await DeptService.get_dept_for_edit_option_services(query_db, dept_query, data_scope_sql) + logger.info('获取成功') + + return ResponseUtil.success(data=dept_query_result) + + +@deptController.get( + '/list', response_model=List[DeptModel], dependencies=[Depends(CheckUserInterfaceAuth('system:dept:list'))] +) +async def get_system_dept_list( + request: Request, + dept_query: DeptQueryModel = Depends(DeptQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + dept_query_result = await DeptService.get_dept_list_services(query_db, dept_query, data_scope_sql) + logger.info('获取成功') + + return ResponseUtil.success(data=dept_query_result) + + +@deptController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:dept:add'))]) +@ValidateFields(validate_model='add_dept') +@Log(title='部门管理', business_type=BusinessType.INSERT) +async def add_system_dept( + request: Request, + add_dept: DeptModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_dept.create_by = current_user.user.user_name + add_dept.create_time = datetime.now() + add_dept.update_by = current_user.user.user_name + add_dept.update_time = datetime.now() + add_dept_result = await DeptService.add_dept_services(query_db, add_dept) + logger.info(add_dept_result.message) + + return ResponseUtil.success(data=add_dept_result) + + +@deptController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:dept:edit'))]) +@ValidateFields(validate_model='edit_dept') +@Log(title='部门管理', business_type=BusinessType.UPDATE) +async def edit_system_dept( + request: Request, + edit_dept: DeptModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + if not current_user.user.admin: + await DeptService.check_dept_data_scope_services(query_db, edit_dept.dept_id, data_scope_sql) + edit_dept.update_by = current_user.user.user_name + edit_dept.update_time = datetime.now() + edit_dept_result = await DeptService.edit_dept_services(query_db, edit_dept) + logger.info(edit_dept_result.message) + + return ResponseUtil.success(msg=edit_dept_result.message) + + +@deptController.delete('/{dept_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:dept:remove'))]) +@Log(title='部门管理', business_type=BusinessType.DELETE) +async def delete_system_dept( + request: Request, + dept_ids: str, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + dept_id_list = dept_ids.split(',') if dept_ids else [] + if dept_id_list: + for dept_id in dept_id_list: + if not current_user.user.admin: + await DeptService.check_dept_data_scope_services(query_db, int(dept_id), data_scope_sql) + delete_dept = DeleteDeptModel(deptIds=dept_ids) + delete_dept.update_by = current_user.user.user_name + delete_dept.update_time = datetime.now() + delete_dept_result = await DeptService.delete_dept_services(query_db, delete_dept) + logger.info(delete_dept_result.message) + + return ResponseUtil.success(msg=delete_dept_result.message) + + +@deptController.get( + '/{dept_id}', response_model=DeptModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dept:query'))] +) +async def query_detail_system_dept( + request: Request, + dept_id: int, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + if not current_user.user.admin: + await DeptService.check_dept_data_scope_services(query_db, dept_id, data_scope_sql) + detail_dept_result = await DeptService.dept_detail_services(query_db, dept_id) + logger.info(f'获取dept_id为{dept_id}的信息成功') + + return ResponseUtil.success(data=detail_dept_result) diff --git a/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py b/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py new file mode 100644 index 0000000..27fa24c --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py @@ -0,0 +1,239 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.dict_vo import ( + DeleteDictDataModel, + DeleteDictTypeModel, + DictDataModel, + DictDataPageQueryModel, + DictTypeModel, + DictTypePageQueryModel, +) +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.dict_service import DictDataService, DictTypeService +from module_admin.service.login_service import LoginService +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +dictController = APIRouter(prefix='/system/dict', dependencies=[Depends(LoginService.get_current_user)]) + + +@dictController.get( + '/type/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:list'))] +) +async def get_system_dict_type_list( + request: Request, + dict_type_page_query: DictTypePageQueryModel = Depends(DictTypePageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + dict_type_page_query_result = await DictTypeService.get_dict_type_list_services( + query_db, dict_type_page_query, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=dict_type_page_query_result) + + +@dictController.post('/type', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:add'))]) +@ValidateFields(validate_model='add_dict_type') +@Log(title='字典类型', business_type=BusinessType.INSERT) +async def add_system_dict_type( + request: Request, + add_dict_type: DictTypeModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_dict_type.create_by = current_user.user.user_name + add_dict_type.create_time = datetime.now() + add_dict_type.update_by = current_user.user.user_name + add_dict_type.update_time = datetime.now() + add_dict_type_result = await DictTypeService.add_dict_type_services(request, query_db, add_dict_type) + logger.info(add_dict_type_result.message) + + return ResponseUtil.success(msg=add_dict_type_result.message) + + +@dictController.put('/type', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:edit'))]) +@ValidateFields(validate_model='edit_dict_type') +@Log(title='字典类型', business_type=BusinessType.UPDATE) +async def edit_system_dict_type( + request: Request, + edit_dict_type: DictTypeModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_dict_type.update_by = current_user.user.user_name + edit_dict_type.update_time = datetime.now() + edit_dict_type_result = await DictTypeService.edit_dict_type_services(request, query_db, edit_dict_type) + logger.info(edit_dict_type_result.message) + + return ResponseUtil.success(msg=edit_dict_type_result.message) + + +@dictController.delete('/type/refreshCache', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:remove'))]) +@Log(title='字典类型', business_type=BusinessType.UPDATE) +async def refresh_system_dict(request: Request, query_db: AsyncSession = Depends(get_db)): + refresh_dict_result = await DictTypeService.refresh_sys_dict_services(request, query_db) + logger.info(refresh_dict_result.message) + + return ResponseUtil.success(msg=refresh_dict_result.message) + + +@dictController.delete('/type/{dict_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:remove'))]) +@Log(title='字典类型', business_type=BusinessType.DELETE) +async def delete_system_dict_type(request: Request, dict_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_dict_type = DeleteDictTypeModel(dictIds=dict_ids) + delete_dict_type_result = await DictTypeService.delete_dict_type_services(request, query_db, delete_dict_type) + logger.info(delete_dict_type_result.message) + + return ResponseUtil.success(msg=delete_dict_type_result.message) + + +@dictController.get('/type/optionselect', response_model=List[DictTypeModel]) +async def query_system_dict_type_options(request: Request, query_db: AsyncSession = Depends(get_db)): + dict_type_query_result = await DictTypeService.get_dict_type_list_services( + query_db, DictTypePageQueryModel(**dict()), is_page=False + ) + logger.info('获取成功') + + return ResponseUtil.success(data=dict_type_query_result) + + +@dictController.get( + '/type/{dict_id}', response_model=DictTypeModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:query'))] +) +async def query_detail_system_dict_type(request: Request, dict_id: int, query_db: AsyncSession = Depends(get_db)): + dict_type_detail_result = await DictTypeService.dict_type_detail_services(query_db, dict_id) + logger.info(f'获取dict_id为{dict_id}的信息成功') + + return ResponseUtil.success(data=dict_type_detail_result) + + +@dictController.post('/type/export', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:export'))]) +@Log(title='字典类型', business_type=BusinessType.EXPORT) +async def export_system_dict_type_list( + request: Request, + dict_type_page_query: DictTypePageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + dict_type_query_result = await DictTypeService.get_dict_type_list_services( + query_db, dict_type_page_query, is_page=False + ) + dict_type_export_result = await DictTypeService.export_dict_type_list_services(dict_type_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(dict_type_export_result)) + + +@dictController.get('/data/type/{dict_type}') +async def query_system_dict_type_data(request: Request, dict_type: str, query_db: AsyncSession = Depends(get_db)): + # 获取全量数据 + dict_data_query_result = await DictDataService.query_dict_data_list_from_cache_services( + request.app.state.redis, dict_type + ) + logger.info('获取成功') + + return ResponseUtil.success(data=dict_data_query_result) + + +@dictController.get( + '/data/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:list'))] +) +async def get_system_dict_data_list( + request: Request, + dict_data_page_query: DictDataPageQueryModel = Depends(DictDataPageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + dict_data_page_query_result = await DictDataService.get_dict_data_list_services( + query_db, dict_data_page_query, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=dict_data_page_query_result) + + +@dictController.post('/data', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:add'))]) +@ValidateFields(validate_model='add_dict_data') +@Log(title='字典数据', business_type=BusinessType.INSERT) +async def add_system_dict_data( + request: Request, + add_dict_data: DictDataModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_dict_data.create_by = current_user.user.user_name + add_dict_data.create_time = datetime.now() + add_dict_data.update_by = current_user.user.user_name + add_dict_data.update_time = datetime.now() + add_dict_data_result = await DictDataService.add_dict_data_services(request, query_db, add_dict_data) + logger.info(add_dict_data_result.message) + + return ResponseUtil.success(msg=add_dict_data_result.message) + + +@dictController.put('/data', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:edit'))]) +@ValidateFields(validate_model='edit_dict_data') +@Log(title='字典数据', business_type=BusinessType.UPDATE) +async def edit_system_dict_data( + request: Request, + edit_dict_data: DictDataModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_dict_data.update_by = current_user.user.user_name + edit_dict_data.update_time = datetime.now() + edit_dict_data_result = await DictDataService.edit_dict_data_services(request, query_db, edit_dict_data) + logger.info(edit_dict_data_result.message) + + return ResponseUtil.success(msg=edit_dict_data_result.message) + + +@dictController.delete('/data/{dict_codes}', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:remove'))]) +@Log(title='字典数据', business_type=BusinessType.DELETE) +async def delete_system_dict_data(request: Request, dict_codes: str, query_db: AsyncSession = Depends(get_db)): + delete_dict_data = DeleteDictDataModel(dictCodes=dict_codes) + delete_dict_data_result = await DictDataService.delete_dict_data_services(request, query_db, delete_dict_data) + logger.info(delete_dict_data_result.message) + + return ResponseUtil.success(msg=delete_dict_data_result.message) + + +@dictController.get( + '/data/{dict_code}', + response_model=DictDataModel, + dependencies=[Depends(CheckUserInterfaceAuth('system:dict:query'))], +) +async def query_detail_system_dict_data(request: Request, dict_code: int, query_db: AsyncSession = Depends(get_db)): + detail_dict_data_result = await DictDataService.dict_data_detail_services(query_db, dict_code) + logger.info(f'获取dict_code为{dict_code}的信息成功') + + return ResponseUtil.success(data=detail_dict_data_result) + + +@dictController.post('/data/export', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:export'))]) +@Log(title='字典数据', business_type=BusinessType.EXPORT) +async def export_system_dict_data_list( + request: Request, + dict_data_page_query: DictDataPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + dict_data_query_result = await DictDataService.get_dict_data_list_services( + query_db, dict_data_page_query, is_page=False + ) + dict_data_export_result = await DictDataService.export_dict_data_list_services(dict_data_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(dict_data_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/door_controller.py b/ruoyi-fastapi-backend/module_admin/controller/door_controller.py new file mode 100644 index 0000000..362f500 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/door_controller.py @@ -0,0 +1,162 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Request, Body +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.door_service import DoorService +from module_admin.service.haikang_service import HaiKangService +from module_admin.entity.vo.door_vo import DeleteDoorModel, DoorModel, DoorPageQueryModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + +import time + + + +doorController = APIRouter(prefix='/system/door', dependencies=[Depends(LoginService.get_current_user)]) + +""" + 实时监控页面权限 access:monitor:view ,dependencies=[Depends(CheckUserInterfaceAuth('access:monitor:view'))] + 识别记录表权限 access:record:list , dependencies=[Depends(CheckUserInterfaceAuth('access:record:list'))] + 门禁设备管理 access:device:list, dependencies=[Depends(CheckUserInterfaceAuth('access:device:list'))] +""" + +# 机器人控制开门 +@doorController.post('/control_door') +async def control_door(request: Request, door_index_code: str = Body(), control_type: int = Body()): + print("door_index_code: ", door_index_code) + print("control_type: ", control_type) + + start_time = time.time() + result = await HaiKangService.door_do_control_service(door_index_code, control_type) + print("end_time: ", time.time() - start_time) + if result[0]: + logger.info('门控制成功') + return ResponseUtil.success(data=result[1]) + logger.error('门控制失败') + return ResponseUtil.error(msg=result[1]) + +# 获取门禁设备列表 +@doorController.get( + '/list', response_model=PageResponseModel + # , dependencies=[Depends(CheckUserInterfaceAuth('system:door:list'))] + , dependencies=[Depends(CheckUserInterfaceAuth('access:device:list'))] +) +async def get_system_door_list( + request: Request, + door_page_query: DoorPageQueryModel = Depends(DoorPageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + + # 获取分页数据 + door_page_query_result = await DoorService.get_door_list_services(query_db, door_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=door_page_query_result) + + +# 添加门禁设备 +@doorController.post('/add' + # , dependencies=[Depends(CheckUserInterfaceAuth('system:door:add'))] + , dependencies=[Depends(CheckUserInterfaceAuth('access:device:list'))] + ) +@ValidateFields(validate_model='add_door') +@Log(title='门禁设备', business_type=BusinessType.INSERT) +async def add_system_door( + request: Request, + add_door: DoorModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_door.create_time = datetime.now() + add_door.create_by = current_user.user.user_name + add_door.update_time = datetime.now() + add_door.update_by = current_user.user.user_name + add_door_result = await DoorService.add_door_services(query_db, add_door) + logger.info(add_door_result.message) + + return ResponseUtil.success(msg=add_door_result.message) + + +@doorController.put('/edit' + # , dependencies=[Depends(CheckUserInterfaceAuth('system:door:edit'))] + , dependencies=[Depends(CheckUserInterfaceAuth('access:device:list'))] + ) +@ValidateFields(validate_model='edit_door') +@Log(title='门禁设备', business_type=BusinessType.UPDATE) +async def edit_system_door( + request: Request, + edit_door: DoorModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_door.update_by = current_user.user.user_name + edit_door.update_time = datetime.now() + edit_door_result = await DoorService.edit_door_services(query_db, edit_door) + logger.info(edit_door_result.message) + + return ResponseUtil.success(msg=edit_door_result.message) + + +@doorController.delete('/{ids}' + # , dependencies=[Depends(CheckUserInterfaceAuth('system:door:remove'))] + , dependencies=[Depends(CheckUserInterfaceAuth('access:device:list'))] + ) +@Log(title='门禁设备', business_type=BusinessType.DELETE) +async def delete_system_door(request: Request, ids: str, query_db: AsyncSession = Depends(get_db)): + delete_door = DeleteDoorModel(ids=ids) + delete_door_result = await DoorService.delete_door_services(query_db, delete_door) + logger.info(delete_door_result.message) + + return ResponseUtil.success(msg=delete_door_result.message) + +# 获取设备状态 +@doorController.get('/door_status', dependencies=[Depends(CheckUserInterfaceAuth('access:monitor:view'))]) +async def get_door_status(request: Request, query_db: AsyncSession = Depends(get_db)): + result = await DoorService.get_door_status_service(query_db) + + if result[0]: + return ResponseUtil.success(data=result[1]) + + return ResponseUtil.error(msg=result[1]) + +# 获取视频流地址 +@doorController.get('/video_uri/{robot_id}' ,dependencies=[Depends(CheckUserInterfaceAuth('access:monitor:view'))]) +async def get_video_uri(request: Request + , robot_id: int + , query_db: AsyncSession = Depends(get_db) + ): + result = await DoorService.get_video_uri_service(robot_id, query_db) + return ResponseUtil.success(data=result) + + +# @doorController.get( +# '/{id}', response_model=DoorModel, dependencies=[Depends(CheckUserInterfaceAuth('system:door:query'))] +# ) +# async def query_detail_system_door(request: Request, id: int, query_db: AsyncSession = Depends(get_db)): +# door_detail_result = await DoorService.door_detail_services(query_db, id) +# logger.info(f'获取id为{id}的信息成功') + +# return ResponseUtil.success(data=door_detail_result) + + +# @doorController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:door:export'))]) +# @Log(title='门禁设备', business_type=BusinessType.EXPORT) +# async def export_system_door_list( +# request: Request, +# door_page_query: DoorPageQueryModel = Form(), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取全量数据 +# door_query_result = await DoorService.get_door_list_services(query_db, door_page_query, is_page=False) +# door_export_result = await DoorService.export_door_list_services(door_query_result) +# logger.info('导出成功') + +# return ResponseUtil.streaming(data=bytes2file_response(door_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/explanation_content_controller.py b/ruoyi-fastapi-backend/module_admin/controller/explanation_content_controller.py new file mode 100644 index 0000000..19432e7 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/explanation_content_controller.py @@ -0,0 +1,143 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.explanation_content_type_vo import Explanation_content_typePageQueryModel, \ + Explanation_content_typeQueryModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.explanation_content_type_service import Explanation_content_typeService +from module_admin.service.login_service import LoginService +from module_admin.service.explanation_content_service import Explanation_contentService +from module_admin.entity.vo.explanation_content_vo import DeleteExplanation_contentModel, Explanation_contentModel, Explanation_contentPageQueryModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil +from config.enums import ExplanationContentStatus + + +explanationContentController = APIRouter(prefix='/system/explanation_content', dependencies=[Depends(LoginService.get_current_user)]) + +""" + 展厅讲解权限字符 exhibitionhall:guide:query, dependencies=[Depends(CheckUserInterfaceAuth('exhibitionhall:guide:query'))] +""" + +@explanationContentController.get( + '/list', response_model=PageResponseModel + # , dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_content:list'))] +) +async def get_system_explanation_content_list( + request: Request, +explanation_content_page_query: Explanation_contentPageQueryModel = Depends(Explanation_contentPageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + explanation_content_page_query_result = await Explanation_contentService.get_explanation_content_list_services(query_db, explanation_content_page_query, is_page=False) + logger.info('获取成功') + + return ResponseUtil.success(data=explanation_content_page_query_result) + + +@explanationContentController.post('/add_explanation' + # , dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_content:add'))] + ) +@ValidateFields(validate_model='add_explanation_content') +@Log(title='讲解内容', business_type=BusinessType.INSERT) +async def add_system_explanation_content( + request: Request, + add_explanation_content: Explanation_contentModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_explanation_content.create_time = datetime.now() + add_explanation_content.create_by = current_user.user.user_name + add_explanation_content.update_time = datetime.now() + add_explanation_content.update_by = current_user.user.user_name + # 新添加内容状态默认0 + add_explanation_content.status = ExplanationContentStatus.NOTSTART.value + add_explanation_content_result = await Explanation_contentService.add_explanation_content_services(query_db, add_explanation_content) + logger.info(add_explanation_content_result.message) + + return ResponseUtil.success(msg=add_explanation_content_result.message) + + +@explanationContentController.put('/edit_explanation' + # , dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_content:edit'))] + ) +@ValidateFields(validate_model='edit_explanation_content') +@Log(title='讲解内容', business_type=BusinessType.UPDATE) +async def edit_system_explanation_content( + request: Request, + edit_explanation_content: Explanation_contentModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_explanation_content.update_by = current_user.user.user_name + edit_explanation_content.update_time = datetime.now() + edit_explanation_content_result = await Explanation_contentService.edit_explanation_content_services(query_db, edit_explanation_content) + logger.info(edit_explanation_content_result.message) + + return ResponseUtil.success(msg=edit_explanation_content_result.message) + + +@explanationContentController.delete('/{explanation_content_ids}' + # , dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_content:remove'))] + ) +@Log(title='讲解内容', business_type=BusinessType.DELETE) +async def delete_system_explanation_content(request: Request, explanation_content_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_explanation_content = DeleteExplanation_contentModel(explanationContentIds=explanation_content_ids) + delete_explanation_content_result = await Explanation_contentService.delete_explanation_content_services(query_db, delete_explanation_content) + logger.info(delete_explanation_content_result.message) + + return ResponseUtil.success(msg=delete_explanation_content_result.message) + + +@explanationContentController.get( + '/type_list', response_model=PageResponseModel + # , dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_content_type:list'))] +) +async def get_system_explanation_content_type_list( + request: Request, +# explanation_content_type_page_query: Explanation_content_typePageQueryModel = Depends(Explanation_content_typePageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + # explanation_content_type_page_query_result = await Explanation_content_typeService.get_explanation_content_type_list_services(query_db, explanation_content_type_page_query, is_page=True) + + explanation_content_type_query_result = await Explanation_content_typeService.get_explanation_content_type_list_services(query_db, Explanation_content_typeQueryModel(), is_page=False) + + logger.info('获取成功') + + return ResponseUtil.success(data=explanation_content_type_query_result) + + +# @explanationContentController.get( +# '/{explanation_content_id}', response_model=Explanation_contentModel +# # , dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_content:query'))] +# ) +# async def query_detail_system_explanation_content(request: Request, explanation_content_id: int, query_db: AsyncSession = Depends(get_db)): +# explanation_content_detail_result = await Explanation_contentService.explanation_content_detail_services(query_db, explanation_content_id) +# logger.info(f'获取explanation_content_id为{explanation_content_id}的信息成功') +# +# return ResponseUtil.success(data=explanation_content_detail_result) + + +# @explanationContentController.post('/export' +# # , dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_content:export'))] +# ) +# @Log(title='讲解内容', business_type=BusinessType.EXPORT) +# async def export_system_explanation_content_list( +# request: Request, +# explanation_content_page_query: Explanation_contentPageQueryModel = Form(), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取全量数据 +# explanation_content_query_result = await Explanation_contentService.get_explanation_content_list_services(query_db, explanation_content_page_query, is_page=False) +# explanation_content_export_result = await Explanation_contentService.export_explanation_content_list_services(explanation_content_query_result) +# logger.info('导出成功') +# +# return ResponseUtil.streaming(data=bytes2file_response(explanation_content_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/explanation_content_type_controller.py b/ruoyi-fastapi-backend/module_admin/controller/explanation_content_type_controller.py new file mode 100644 index 0000000..3c2344c --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/explanation_content_type_controller.py @@ -0,0 +1,111 @@ +# from datetime import datetime +# from fastapi import APIRouter, Depends, Form, Request +# from pydantic_validation_decorator import ValidateFields +# from sqlalchemy.ext.asyncio import AsyncSession +# from config.enums import BusinessType +# from config.get_db import get_db +# from module_admin.annotation.log_annotation import Log +# from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +# from module_admin.entity.vo.user_vo import CurrentUserModel +# from module_admin.service.login_service import LoginService +# from module_admin.service.explanation_content_type_service import Explanation_content_typeService +# from module_admin.entity.vo.explanation_content_type_vo import DeleteExplanation_content_typeModel, Explanation_content_typeModel, Explanation_content_typePageQueryModel +# from utils.common_util import bytes2file_response +# from utils.log_util import logger +# from utils.page_util import PageResponseModel +# from utils.response_util import ResponseUtil + + +# explanation_content_typeController = APIRouter(prefix='/system/explanation_content_type' +# , dependencies=[Depends(LoginService.get_current_user)] +# ) +# +# +# @explanation_content_typeController.get( +# '/list', response_model=PageResponseModel +# # , dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_content_type:list'))] +# ) +# async def get_system_explanation_content_type_list( +# request: Request, +# # explanation_content_type_page_query: Explanation_content_typePageQueryModel = Depends(Explanation_content_typePageQueryModel.as_query), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取分页数据 +# # explanation_content_type_page_query_result = await Explanation_content_typeService.get_explanation_content_type_list_services(query_db, explanation_content_type_page_query, is_page=True) +# +# explanation_content_type_query_result = await Explanation_content_typeService.get_explanation_content_type_list_services(query_db, is_page=False) +# +# logger.info('获取成功') +# +# return ResponseUtil.success(model_content=explanation_content_type_query_result) + + +# @explanation_content_typeController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_content_type:add'))]) +# @ValidateFields(validate_model='add_explanation_content_type') +# @Log(title='讲解内容类型', business_type=BusinessType.INSERT) +# async def add_system_explanation_content_type( +# request: Request, +# add_explanation_content_type: Explanation_content_typeModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# add_explanation_content_type.create_time = datetime.now() +# add_explanation_content_type.create_by = current_user.user.user_name +# add_explanation_content_type.update_time = datetime.now() +# add_explanation_content_type.update_by = current_user.user.user_name +# add_explanation_content_type_result = await Explanation_content_typeService.add_explanation_content_type_services(query_db, add_explanation_content_type) +# logger.info(add_explanation_content_type_result.message) +# +# return ResponseUtil.success(msg=add_explanation_content_type_result.message) +# +# +# @explanation_content_typeController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_content_type:edit'))]) +# @ValidateFields(validate_model='edit_explanation_content_type') +# @Log(title='讲解内容类型', business_type=BusinessType.UPDATE) +# async def edit_system_explanation_content_type( +# request: Request, +# edit_explanation_content_type: Explanation_content_typeModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# edit_explanation_content_type.update_by = current_user.user.user_name +# edit_explanation_content_type.update_time = datetime.now() +# edit_explanation_content_type_result = await Explanation_content_typeService.edit_explanation_content_type_services(query_db, edit_explanation_content_type) +# logger.info(edit_explanation_content_type_result.message) +# +# return ResponseUtil.success(msg=edit_explanation_content_type_result.message) +# +# +# @explanation_content_typeController.delete('/{content_type_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_content_type:remove'))]) +# @Log(title='讲解内容类型', business_type=BusinessType.DELETE) +# async def delete_system_explanation_content_type(request: Request, content_type_ids: str, query_db: AsyncSession = Depends(get_db)): +# delete_explanation_content_type = DeleteExplanation_content_typeModel(contentTypeIds=content_type_ids) +# delete_explanation_content_type_result = await Explanation_content_typeService.delete_explanation_content_type_services(query_db, delete_explanation_content_type) +# logger.info(delete_explanation_content_type_result.message) +# +# return ResponseUtil.success(msg=delete_explanation_content_type_result.message) +# +# +# @explanation_content_typeController.get( +# '/{content_type_id}', response_model=Explanation_content_typeModel, dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_content_type:query'))] +# ) +# async def query_detail_system_explanation_content_type(request: Request, content_type_id: int, query_db: AsyncSession = Depends(get_db)): +# explanation_content_type_detail_result = await Explanation_content_typeService.explanation_content_type_detail_services(query_db, content_type_id) +# logger.info(f'获取content_type_id为{content_type_id}的信息成功') +# +# return ResponseUtil.success(data=explanation_content_type_detail_result) +# +# +# @explanation_content_typeController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_content_type:export'))]) +# @Log(title='讲解内容类型', business_type=BusinessType.EXPORT) +# async def export_system_explanation_content_type_list( +# request: Request, +# explanation_content_type_page_query: Explanation_content_typePageQueryModel = Form(), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取全量数据 +# explanation_content_type_query_result = await Explanation_content_typeService.get_explanation_content_type_list_services(query_db, explanation_content_type_page_query, is_page=False) +# explanation_content_type_export_result = await Explanation_content_typeService.export_explanation_content_type_list_services(explanation_content_type_query_result) +# logger.info('导出成功') +# +# return ResponseUtil.streaming(data=bytes2file_response(explanation_content_type_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/explanation_style_controller.py b/ruoyi-fastapi-backend/module_admin/controller/explanation_style_controller.py new file mode 100644 index 0000000..8cf8454 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/explanation_style_controller.py @@ -0,0 +1,105 @@ +# from datetime import datetime +# from fastapi import APIRouter, Depends, Form, Request +# from pydantic_validation_decorator import ValidateFields +# from sqlalchemy.ext.asyncio import AsyncSession +# from config.enums import BusinessType +# from config.get_db import get_db +# from module_admin.annotation.log_annotation import Log +# from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +# from module_admin.entity.vo.user_vo import CurrentUserModel +# from module_admin.service.login_service import LoginService +# from module_admin.service.explanation_style_service import Explanation_styleService +# from module_admin.entity.vo.explanation_style_vo import DeleteExplanation_styleModel, Explanation_styleModel, Explanation_stylePageQueryModel +# from utils.common_util import bytes2file_response +# from utils.log_util import logger +# from utils.page_util import PageResponseModel +# from utils.response_util import ResponseUtil +# +# +# explanation_styleController = APIRouter(prefix='/system/explanation_style', dependencies=[Depends(LoginService.get_current_user)]) +# +# +# @explanation_styleController.get( +# '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_style:list'))] +# ) +# async def get_system_explanation_style_list( +# request: Request, +# explanation_style_page_query: Explanation_stylePageQueryModel = Depends(Explanation_stylePageQueryModel.as_query), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取分页数据 +# explanation_style_page_query_result = await Explanation_styleService.get_explanation_style_list_services(query_db, explanation_style_page_query, is_page=True) +# logger.info('获取成功') +# +# return ResponseUtil.success(model_content=explanation_style_page_query_result) +# +# +# @explanation_styleController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_style:add'))]) +# @ValidateFields(validate_model='add_explanation_style') +# @Log(title='讲解风格', business_type=BusinessType.INSERT) +# async def add_system_explanation_style( +# request: Request, +# add_explanation_style: Explanation_styleModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# add_explanation_style.create_time = datetime.now() +# add_explanation_style.create_by = current_user.user.user_name +# add_explanation_style.update_time = datetime.now() +# add_explanation_style.update_by = current_user.user.user_name +# add_explanation_style_result = await Explanation_styleService.add_explanation_style_services(query_db, add_explanation_style) +# logger.info(add_explanation_style_result.message) +# +# return ResponseUtil.success(msg=add_explanation_style_result.message) +# +# +# @explanation_styleController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_style:edit'))]) +# @ValidateFields(validate_model='edit_explanation_style') +# @Log(title='讲解风格', business_type=BusinessType.UPDATE) +# async def edit_system_explanation_style( +# request: Request, +# edit_explanation_style: Explanation_styleModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# edit_explanation_style.update_by = current_user.user.user_name +# edit_explanation_style.update_time = datetime.now() +# edit_explanation_style_result = await Explanation_styleService.edit_explanation_style_services(query_db, edit_explanation_style) +# logger.info(edit_explanation_style_result.message) +# +# return ResponseUtil.success(msg=edit_explanation_style_result.message) +# +# +# @explanation_styleController.delete('/{explanation_style_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_style:remove'))]) +# @Log(title='讲解风格', business_type=BusinessType.DELETE) +# async def delete_system_explanation_style(request: Request, explanation_style_ids: str, query_db: AsyncSession = Depends(get_db)): +# delete_explanation_style = DeleteExplanation_styleModel(explanationStyleIds=explanation_style_ids) +# delete_explanation_style_result = await Explanation_styleService.delete_explanation_style_services(query_db, delete_explanation_style) +# logger.info(delete_explanation_style_result.message) +# +# return ResponseUtil.success(msg=delete_explanation_style_result.message) +# +# +# @explanation_styleController.get( +# '/{explanation_style_id}', response_model=Explanation_styleModel, dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_style:query'))] +# ) +# async def query_detail_system_explanation_style(request: Request, explanation_style_id: int, query_db: AsyncSession = Depends(get_db)): +# explanation_style_detail_result = await Explanation_styleService.explanation_style_detail_services(query_db, explanation_style_id) +# logger.info(f'获取explanation_style_id为{explanation_style_id}的信息成功') +# +# return ResponseUtil.success(data=explanation_style_detail_result) +# +# +# @explanation_styleController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_style:export'))]) +# @Log(title='讲解风格', business_type=BusinessType.EXPORT) +# async def export_system_explanation_style_list( +# request: Request, +# explanation_style_page_query: Explanation_stylePageQueryModel = Form(), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取全量数据 +# explanation_style_query_result = await Explanation_styleService.get_explanation_style_list_services(query_db, explanation_style_page_query, is_page=False) +# explanation_style_export_result = await Explanation_styleService.export_explanation_style_list_services(explanation_style_query_result) +# logger.info('导出成功') +# +# return ResponseUtil.streaming(data=bytes2file_response(explanation_style_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/explanation_style_robot_pair_controller.py b/ruoyi-fastapi-backend/module_admin/controller/explanation_style_robot_pair_controller.py new file mode 100644 index 0000000..7356a51 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/explanation_style_robot_pair_controller.py @@ -0,0 +1,130 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.explanation_style_robot_pair_service import Explanation_style_robot_pairService +from module_admin.entity.vo.explanation_style_robot_pair_vo import DeleteExplanation_style_robot_pairModel, \ + Explanation_style_robot_pairModel, Explanation_style_robot_pairPageQueryModel, SwitchExplanationStyleModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +explanation_style_robot_pairController = APIRouter(prefix='/system/explanation_style_robot_pair', dependencies=[Depends(LoginService.get_current_user)]) + + +# @explanation_style_robot_pairController.get( +# '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_style_robot_pair:list'))] +# ) +# async def get_system_explanation_style_robot_pair_list( +# request: Request, +# robot_id: int, +# # explanation_style_robot_pair_page_query: Explanation_style_robot_pairPageQueryModel = Depends(Explanation_style_robot_pairPageQueryModel.as_query), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取分页数据 +# # explanation_style_robot_pair_page_query_result = await Explanation_style_robot_pairService.get_explanation_style_robot_pair_list_services(query_db, explanation_style_robot_pair_page_query, is_page=False) + +# explanation_style_robot_pair_query_result = await Explanation_style_robot_pairService.get_style_robot_pair_list_services(query_db, robot_id) +# logger.info('获取成功') + +# return ResponseUtil.success(data=explanation_style_robot_pair_query_result) + +# 切换风格 +@explanation_style_robot_pairController.post('/switch_style') +async def switch_explanation_style( + request: Request, + switch_explanation_style: SwitchExplanationStyleModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + switch_explanation_style.update_by = current_user.user.user_name + switch_explanation_style_message = await Explanation_style_robot_pairService.switch_explanation_style_services(query_db, switch_explanation_style) + + if switch_explanation_style_message.is_success: + logger.info(switch_explanation_style_message.message) + + return ResponseUtil.success(msg=switch_explanation_style_message.message) + else: + return ResponseUtil.error(msg=switch_explanation_style_message.message) + + + +# @explanation_style_robot_pairController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_style_robot_pair:add'))]) +# @ValidateFields(validate_model='add_explanation_style_robot_pair') +# @Log(title='讲解风格--机器人配对', business_type=BusinessType.INSERT) +# async def add_system_explanation_style_robot_pair( +# request: Request, +# add_explanation_style_robot_pair: Explanation_style_robot_pairModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# add_explanation_style_robot_pair.create_time = datetime.now() +# add_explanation_style_robot_pair.create_by = current_user.user.user_name +# add_explanation_style_robot_pair.update_time = datetime.now() +# add_explanation_style_robot_pair.update_by = current_user.user.user_name +# add_explanation_style_robot_pair_result = await Explanation_style_robot_pairService.add_explanation_style_robot_pair_services(query_db, add_explanation_style_robot_pair) +# logger.info(add_explanation_style_robot_pair_result.message) +# +# return ResponseUtil.success(msg=add_explanation_style_robot_pair_result.message) + + + + +# @explanation_style_robot_pairController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_style_robot_pair:edit'))]) +# @ValidateFields(validate_model='edit_explanation_style_robot_pair') +# @Log(title='讲解风格--机器人配对', business_type=BusinessType.UPDATE) +# async def edit_system_explanation_style_robot_pair( +# request: Request, +# edit_explanation_style_robot_pair: Explanation_style_robot_pairModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# edit_explanation_style_robot_pair.update_by = current_user.user.user_name +# edit_explanation_style_robot_pair.update_time = datetime.now() +# edit_explanation_style_robot_pair_result = await Explanation_style_robot_pairService.edit_explanation_style_robot_pair_services(query_db, edit_explanation_style_robot_pair) +# logger.info(edit_explanation_style_robot_pair_result.message) +# +# return ResponseUtil.success(msg=edit_explanation_style_robot_pair_result.message) + + +# @explanation_style_robot_pairController.delete('/{pairing_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_style_robot_pair:remove'))]) +# @Log(title='讲解风格--机器人配对', business_type=BusinessType.DELETE) +# async def delete_system_explanation_style_robot_pair(request: Request, pairing_ids: str, query_db: AsyncSession = Depends(get_db)): +# delete_explanation_style_robot_pair = DeleteExplanation_style_robot_pairModel(pairingIds=pairing_ids) +# delete_explanation_style_robot_pair_result = await Explanation_style_robot_pairService.delete_explanation_style_robot_pair_services(query_db, delete_explanation_style_robot_pair) +# logger.info(delete_explanation_style_robot_pair_result.message) +# +# return ResponseUtil.success(msg=delete_explanation_style_robot_pair_result.message) + + +# @explanation_style_robot_pairController.get( +# '/{pairing_id}', response_model=Explanation_style_robot_pairModel, dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_style_robot_pair:query'))] +# ) +# async def query_detail_system_explanation_style_robot_pair(request: Request, pairing_id: int, query_db: AsyncSession = Depends(get_db)): +# explanation_style_robot_pair_detail_result = await Explanation_style_robot_pairService.explanation_style_robot_pair_detail_services(query_db, pairing_id) +# logger.info(f'获取pairing_id为{pairing_id}的信息成功') +# +# return ResponseUtil.success(data=explanation_style_robot_pair_detail_result) + + +# @explanation_style_robot_pairController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:explanation_style_robot_pair:export'))]) +# @Log(title='讲解风格--机器人配对', business_type=BusinessType.EXPORT) +# async def export_system_explanation_style_robot_pair_list( +# request: Request, +# explanation_style_robot_pair_page_query: Explanation_style_robot_pairPageQueryModel = Form(), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取全量数据 +# explanation_style_robot_pair_query_result = await Explanation_style_robot_pairService.get_explanation_style_robot_pair_list_services(query_db, explanation_style_robot_pair_page_query, is_page=False) +# explanation_style_robot_pair_export_result = await Explanation_style_robot_pairService.export_explanation_style_robot_pair_list_services(explanation_style_robot_pair_query_result) +# logger.info('导出成功') +# +# return ResponseUtil.streaming(data=bytes2file_response(explanation_style_robot_pair_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/haikang_controller.py b/ruoyi-fastapi-backend/module_admin/controller/haikang_controller.py new file mode 100644 index 0000000..dec22b9 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/haikang_controller.py @@ -0,0 +1,81 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.service.login_service import LoginService +from module_admin.service.haikang_service import HaiKangService +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil +from module_admin.entity.vo.haikang_vo import VisitorReservationQueryModel + +from typing import List + + +haikang_controller = APIRouter(prefix="/system/haikang", dependencies=[Depends(LoginService.get_current_user)]) + + +# # 查询门禁点列表 +# @haikang_controller.get("/door_list" +# # , response_model=PageResponseModel +# # , dependencies=[Depends(CheckUserInterfaceAuth("system:haikang:list"))] +# ) +# async def get_system_haikang_list( +# request: Request, +# pageNo:int, +# pageSize:int, +# ): +# """ +# 查询门禁点列表 +# """ +# result = await HaiKangService.get_door_list_service(pageNo, pageSize) +# return parse_result(result) + +# # 查询门禁点状态 +# @haikang_controller.post( +# "/door_status", +# # dependencies=[Depends(CheckUserInterfaceAuth("system:haikang:list"))] +# ) +# async def get_door_status( +# request: Request, +# door_index_codes: List[str], +# ): +# """ +# 查询门禁点列表 +# """ +# result = await HaiKangService.get_door_status_service(door_index_codes) +# return parse_result(result) + +# # 门禁点反控 +# @haikang_controller.post( +# "/door_do_control", +# # dependencies=[Depends(CheckUserInterfaceAuth("system:haikang:list"))] +# ) +# async def door_do_control( +# request: Request, +# door_index_codes: List[str], +# control_type: int, +# ): +# pass + +# 查询访客预约记录 +@haikang_controller.post( + "/get_visitor_list", + # dependencies=[Depends(CheckUserInterfaceAuth("system:haikang:list"))] +) +async def get_visitor( + request: Request, + visitor_query: VisitorReservationQueryModel +): + result = await HaiKangService.get_visitor_list_service(visitor_query) + return parse_result(result) + + +def parse_result(result): + if result[0]: + return ResponseUtil.success(data=result[1]) + return ResponseUtil.error(msg=f"code:{result[1]} msg:{result[2]}") \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_admin/controller/identification_record_controller.py b/ruoyi-fastapi-backend/module_admin/controller/identification_record_controller.py new file mode 100644 index 0000000..612a71a --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/identification_record_controller.py @@ -0,0 +1,106 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.identification_record_service import Identification_recordService +from module_admin.entity.vo.identification_record_vo import DeleteIdentification_recordModel, Identification_recordModel, Identification_recordPageQueryModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +identification_recordController = APIRouter(prefix='/system/identification_record', dependencies=[Depends(LoginService.get_current_user)]) + + +@identification_recordController.get( + '/list', response_model=PageResponseModel + , dependencies=[Depends(CheckUserInterfaceAuth('access:record:list'))] +) +async def get_system_identification_record_list( + request: Request, +identification_record_page_query: Identification_recordPageQueryModel = Depends(Identification_recordPageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + identification_record_page_query_result = await Identification_recordService.get_identification_record_list_services(query_db, identification_record_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(data=identification_record_page_query_result) + + +@identification_recordController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:identification_record:add'))]) +@ValidateFields(validate_model='add_identification_record') +@Log(title='识别记录', business_type=BusinessType.INSERT) +async def add_system_identification_record( + request: Request, + add_identification_record: Identification_recordModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_identification_record.create_time = datetime.now() + add_identification_record.create_by = current_user.user.user_name + add_identification_record_result = await Identification_recordService.add_identification_record_services(query_db, add_identification_record) + logger.info(add_identification_record_result.message) + + return ResponseUtil.success(msg=add_identification_record_result.message) + + +# @identification_recordController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:identification_record:edit'))]) +# @ValidateFields(validate_model='edit_identification_record') +# @Log(title='识别记录', business_type=BusinessType.UPDATE) +# async def edit_system_identification_record( +# request: Request, +# edit_identification_record: Identification_recordModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# edit_identification_record.update_by = current_user.user.user_name +# edit_identification_record.update_time = datetime.now() +# edit_identification_record_result = await Identification_recordService.edit_identification_record_services(query_db, edit_identification_record) +# logger.info(edit_identification_record_result.message) + +# return ResponseUtil.success(msg=edit_identification_record_result.message) + + +# @identification_recordController.delete('/{ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:identification_record:remove'))]) +# @Log(title='识别记录', business_type=BusinessType.DELETE) +# async def delete_system_identification_record(request: Request, ids: str, query_db: AsyncSession = Depends(get_db)): +# delete_identification_record = DeleteIdentification_recordModel(ids=ids) +# delete_identification_record_result = await Identification_recordService.delete_identification_record_services(query_db, delete_identification_record) +# logger.info(delete_identification_record_result.message) + +# return ResponseUtil.success(msg=delete_identification_record_result.message) + + +# @identification_recordController.get( +# '/{id}', response_model=Identification_recordModel, dependencies=[Depends(CheckUserInterfaceAuth('system:identification_record:query'))] +# ) +# async def query_detail_system_identification_record(request: Request, id: int, query_db: AsyncSession = Depends(get_db)): +# identification_record_detail_result = await Identification_recordService.identification_record_detail_services(query_db, id) +# logger.info(f'获取id为{id}的信息成功') + +# return ResponseUtil.success(data=identification_record_detail_result) + + +@identification_recordController.post('/export' + # , dependencies=[Depends(CheckUserInterfaceAuth('system:identification_record:export'))] + ) +@Log(title='识别记录', business_type=BusinessType.EXPORT) +async def export_system_identification_record_list( + request: Request, + identification_record_page_query: Identification_recordPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + identification_record_query_result = await Identification_recordService.get_identification_record_list_services(query_db, identification_record_page_query, is_page=False) + identification_record_export_result = await Identification_recordService.export_identification_record_list_services(identification_record_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(identification_record_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/identification_statistics_controller.py b/ruoyi-fastapi-backend/module_admin/controller/identification_statistics_controller.py new file mode 100644 index 0000000..b0c7314 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/identification_statistics_controller.py @@ -0,0 +1,131 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.identification_statistics_service import Identification_statisticsService +from module_admin.entity.vo.identification_statistics_vo import DeleteIdentification_statisticsModel, Identification_statisticsModel, Identification_statisticsPageQueryModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +identification_statisticsController = APIRouter(prefix='/system/identification_statistics', dependencies=[Depends(LoginService.get_current_user)]) + +# 获取今天的识别统计数据 +@identification_statisticsController.get('/today') +async def get_today_identification_statistics(request: Request, query_db: AsyncSession = Depends(get_db)): + """ + 获取今天的识别统计数据 + :param request: + :param query_db: + :return: + """ + logger.info('获取今天的识别统计数据') + identification_statistics = await Identification_statisticsService.get_today_identification_statistics_services(query_db) + return ResponseUtil.success(data = identification_statistics) + +# 获取总的识别统计数据 +@identification_statisticsController.get('/total') +async def get_total_identification_statistics(request: Request, query_db: AsyncSession = Depends(get_db)): + """ + 获取今天的识别统计数据 + :param request: + :param query_db: + :return: + """ + logger.info('获取今天的识别统计数据') + identification_statistics = await Identification_statisticsService.get_total_identification_statistics_services(query_db) + return ResponseUtil.success(data = identification_statistics) + + + + +# @identification_statisticsController.get( +# '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:identification_statistics:list'))] +# ) +# async def get_system_identification_statistics_list( +# request: Request, +# identification_statistics_page_query: Identification_statisticsPageQueryModel = Depends(Identification_statisticsPageQueryModel.as_query), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取分页数据 +# identification_statistics_page_query_result = await Identification_statisticsService.get_identification_statistics_list_services(query_db, identification_statistics_page_query, is_page=True) +# logger.info('获取成功') + +# return ResponseUtil.success(model_content=identification_statistics_page_query_result) + + +# @identification_statisticsController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:identification_statistics:add'))]) +# @ValidateFields(validate_model='add_identification_statistics') +# @Log(title='识别统计', business_type=BusinessType.INSERT) +# async def add_system_identification_statistics( +# request: Request, +# add_identification_statistics: Identification_statisticsModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# add_identification_statistics.create_time = datetime.now() +# add_identification_statistics.create_by = current_user.user.user_name +# add_identification_statistics_result = await Identification_statisticsService.add_identification_statistics_services(query_db, add_identification_statistics) +# logger.info(add_identification_statistics_result.message) + +# return ResponseUtil.success(msg=add_identification_statistics_result.message) + + +# @identification_statisticsController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:identification_statistics:edit'))]) +# @ValidateFields(validate_model='edit_identification_statistics') +# @Log(title='识别统计', business_type=BusinessType.UPDATE) +# async def edit_system_identification_statistics( +# request: Request, +# edit_identification_statistics: Identification_statisticsModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# edit_identification_statistics.update_by = current_user.user.user_name +# edit_identification_statistics.update_time = datetime.now() +# edit_identification_statistics_result = await Identification_statisticsService.edit_identification_statistics_services(query_db, edit_identification_statistics) +# logger.info(edit_identification_statistics_result.message) + +# return ResponseUtil.success(msg=edit_identification_statistics_result.message) + + +# @identification_statisticsController.delete('/{ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:identification_statistics:remove'))]) +# @Log(title='识别统计', business_type=BusinessType.DELETE) +# async def delete_system_identification_statistics(request: Request, ids: str, query_db: AsyncSession = Depends(get_db)): +# delete_identification_statistics = DeleteIdentification_statisticsModel(ids=ids) +# delete_identification_statistics_result = await Identification_statisticsService.delete_identification_statistics_services(query_db, delete_identification_statistics) +# logger.info(delete_identification_statistics_result.message) + +# return ResponseUtil.success(msg=delete_identification_statistics_result.message) + + +# @identification_statisticsController.get( +# '/{id}', response_model=Identification_statisticsModel, dependencies=[Depends(CheckUserInterfaceAuth('system:identification_statistics:query'))] +# ) +# async def query_detail_system_identification_statistics(request: Request, id: int, query_db: AsyncSession = Depends(get_db)): +# identification_statistics_detail_result = await Identification_statisticsService.identification_statistics_detail_services(query_db, id) +# logger.info(f'获取id为{id}的信息成功') + +# return ResponseUtil.success(data=identification_statistics_detail_result) + + +# @identification_statisticsController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:identification_statistics:export'))]) +# @Log(title='识别统计', business_type=BusinessType.EXPORT) +# async def export_system_identification_statistics_list( +# request: Request, +# identification_statistics_page_query: Identification_statisticsPageQueryModel = Form(), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取全量数据 +# identification_statistics_query_result = await Identification_statisticsService.get_identification_statistics_list_services(query_db, identification_statistics_page_query, is_page=False) +# identification_statistics_export_result = await Identification_statisticsService.export_identification_statistics_list_services(identification_statistics_query_result) +# logger.info('导出成功') + +# return ResponseUtil.streaming(data=bytes2file_response(identification_statistics_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/info_controller.py b/ruoyi-fastapi-backend/module_admin/controller/info_controller.py new file mode 100644 index 0000000..91469a1 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/info_controller.py @@ -0,0 +1,131 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.info_service import InfoService +from module_admin.entity.vo.info_vo import DeleteInfoModel, InfoModel, InfoPageQueryModel, InfoUpdateModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +infoController = APIRouter(prefix='/robot/info', dependencies=[Depends(LoginService.get_current_user)]) + + +@infoController.get( + '/list', response_model=PageResponseModel + # , dependencies=[Depends(CheckUserInterfaceAuth('robot:info:list'))] +) +async def get_system_info_list( + request: Request, +info_page_query: InfoPageQueryModel = Depends(InfoPageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + print('xxxxxxxxx', info_page_query.begin_create_time) + print('xxxxxxxxx',info_page_query.end_create_time) + print(info_page_query.name) + # 获取分页数据 + info_page_query_result = await InfoService.get_info_list_services(query_db, info_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=info_page_query_result) + + +@infoController.post('/add_robot' + # , dependencies=[Depends(CheckUserInterfaceAuth('robot:info:add'))] + ) +@ValidateFields(validate_model='add_info') +@Log(title='机器人信息', business_type=BusinessType.INSERT) +async def add_system_info( + request: Request, + add_info: InfoModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_info.create_time = datetime.now() + add_info.create_by = current_user.user.user_name + add_info.update_time = datetime.now() + add_info.update_by = current_user.user.user_name + add_info.del_flag = '0' + add_info.online = '1' + add_info.power = '100' + add_info_result = await InfoService.add_info_services(query_db, add_info) + logger.info(add_info_result.message) + + return ResponseUtil.success(msg=add_info_result.message) + + +@infoController.put('/edit_robot' + # , dependencies=[Depends(CheckUserInterfaceAuth('robot:info:edit'))] + ) +@ValidateFields(validate_model='edit_info') +@Log(title='机器人信息', business_type=BusinessType.UPDATE) +async def edit_system_info( + request: Request, + edit_info: InfoUpdateModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_info.update_by = current_user.user.user_name + edit_info.update_time = datetime.now() + edit_info_result = await InfoService.edit_info_services(query_db, edit_info) + logger.info(edit_info_result.message) + + return ResponseUtil.success(msg=edit_info_result.message) + + +@infoController.delete('/{robot_ids}' + # , dependencies=[Depends(CheckUserInterfaceAuth('robot:info:remove'))] + ) +@Log(title='机器人信息', business_type=BusinessType.DELETE) +async def delete_system_info(request: Request, robot_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_info = DeleteInfoModel(robotIds=robot_ids) + delete_info_result = await InfoService.delete_info_services(query_db, delete_info) + logger.info(delete_info_result.message) + + return ResponseUtil.success(msg=delete_info_result.message) + + +@infoController.get( + '/{robot_id}', response_model=InfoModel + # , dependencies=[Depends(CheckUserInterfaceAuth('robot:info:query'))] +) +async def query_detail_system_info(request: Request, robot_id: int, query_db: AsyncSession = Depends(get_db)): + info_detail_result = await InfoService.info_detail_services(query_db, robot_id) + logger.info(f'获取robot_id为{robot_id}的信息成功') + + return ResponseUtil.success(data=info_detail_result) + +# 根据机器人id刷新机器人信息 +@infoController.get('/refresh_robot/{robot_ids}' + # , dependencies=[Depends(CheckUserInterfaceAuth('system:info:add'))] +) +async def refresh_robot(request: Request,robot_ids: str , query_db: AsyncSession = Depends(get_db)): + refresh_result = await InfoService.refresh_robot_services(query_db, robot_ids) + logger.info(f'刷新机器人信息成功') + + return ResponseUtil.success(data=refresh_result) + + +@infoController.post('/export' + # , dependencies=[Depends(CheckUserInterfaceAuth('system:info:export'))] + ) +@Log(title='机器人信息', business_type=BusinessType.EXPORT) +async def export_system_info_list( + request: Request, + info_page_query: InfoPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + info_query_result = await InfoService.get_info_list_services(query_db, info_page_query, is_page=False) + info_export_result = await InfoService.export_info_list_services(info_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(info_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/job_controller.py b/ruoyi-fastapi-backend/module_admin/controller/job_controller.py new file mode 100644 index 0000000..c930569 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/job_controller.py @@ -0,0 +1,194 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.job_vo import ( + DeleteJobLogModel, + DeleteJobModel, + EditJobModel, + JobLogPageQueryModel, + JobModel, + JobPageQueryModel, +) +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.job_log_service import JobLogService +from module_admin.service.job_service import JobService +from module_admin.service.login_service import LoginService +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +jobController = APIRouter(prefix='/monitor', dependencies=[Depends(LoginService.get_current_user)]) + + +@jobController.get( + '/job/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:list'))] +) +async def get_system_job_list( + request: Request, + job_page_query: JobPageQueryModel = Depends(JobPageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + notice_page_query_result = await JobService.get_job_list_services(query_db, job_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=notice_page_query_result) + + +@jobController.post('/job', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:add'))]) +@ValidateFields(validate_model='add_job') +@Log(title='定时任务', business_type=BusinessType.INSERT) +async def add_system_job( + request: Request, + add_job: JobModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_job.create_by = current_user.user.user_name + add_job.create_time = datetime.now() + add_job.update_by = current_user.user.user_name + add_job.update_time = datetime.now() + add_job_result = await JobService.add_job_services(query_db, add_job) + logger.info(add_job_result.message) + + return ResponseUtil.success(msg=add_job_result.message) + + +@jobController.put('/job', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:edit'))]) +@ValidateFields(validate_model='edit_job') +@Log(title='定时任务', business_type=BusinessType.UPDATE) +async def edit_system_job( + request: Request, + edit_job: EditJobModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_job.update_by = current_user.user.user_name + edit_job.update_time = datetime.now() + edit_job_result = await JobService.edit_job_services(query_db, edit_job) + logger.info(edit_job_result.message) + + return ResponseUtil.success(msg=edit_job_result.message) + + +@jobController.put('/job/changeStatus', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:changeStatus'))]) +@Log(title='定时任务', business_type=BusinessType.UPDATE) +async def change_system_job_status( + request: Request, + change_job: EditJobModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_job = EditJobModel( + jobId=change_job.job_id, + status=change_job.status, + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + type='status', + ) + edit_job_result = await JobService.edit_job_services(query_db, edit_job) + logger.info(edit_job_result.message) + + return ResponseUtil.success(msg=edit_job_result.message) + + +@jobController.put('/job/run', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:changeStatus'))]) +@Log(title='定时任务', business_type=BusinessType.UPDATE) +async def execute_system_job(request: Request, execute_job: JobModel, query_db: AsyncSession = Depends(get_db)): + execute_job_result = await JobService.execute_job_once_services(query_db, execute_job) + logger.info(execute_job_result.message) + + return ResponseUtil.success(msg=execute_job_result.message) + + +@jobController.delete('/job/{job_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) +@Log(title='定时任务', business_type=BusinessType.DELETE) +async def delete_system_job(request: Request, job_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_job = DeleteJobModel(jobIds=job_ids) + delete_job_result = await JobService.delete_job_services(query_db, delete_job) + logger.info(delete_job_result.message) + + return ResponseUtil.success(msg=delete_job_result.message) + + +@jobController.get( + '/job/{job_id}', response_model=JobModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:query'))] +) +async def query_detail_system_job(request: Request, job_id: int, query_db: AsyncSession = Depends(get_db)): + job_detail_result = await JobService.job_detail_services(query_db, job_id) + logger.info(f'获取job_id为{job_id}的信息成功') + + return ResponseUtil.success(data=job_detail_result) + + +@jobController.post('/job/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:export'))]) +@Log(title='定时任务', business_type=BusinessType.EXPORT) +async def export_system_job_list( + request: Request, + job_page_query: JobPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + job_query_result = await JobService.get_job_list_services(query_db, job_page_query, is_page=False) + job_export_result = await JobService.export_job_list_services(request, job_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(job_export_result)) + + +@jobController.get( + '/jobLog/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:list'))] +) +async def get_system_job_log_list( + request: Request, + job_log_page_query: JobLogPageQueryModel = Depends(JobLogPageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + job_log_page_query_result = await JobLogService.get_job_log_list_services( + query_db, job_log_page_query, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=job_log_page_query_result) + + +@jobController.delete('/jobLog/clean', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) +@Log(title='定时任务调度日志', business_type=BusinessType.CLEAN) +async def clear_system_job_log(request: Request, query_db: AsyncSession = Depends(get_db)): + clear_job_log_result = await JobLogService.clear_job_log_services(query_db) + logger.info(clear_job_log_result.message) + + return ResponseUtil.success(msg=clear_job_log_result.message) + + +@jobController.delete('/jobLog/{job_log_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) +@Log(title='定时任务调度日志', business_type=BusinessType.DELETE) +async def delete_system_job_log(request: Request, job_log_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_job_log = DeleteJobLogModel(jobLogIds=job_log_ids) + delete_job_log_result = await JobLogService.delete_job_log_services(query_db, delete_job_log) + logger.info(delete_job_log_result.message) + + return ResponseUtil.success(msg=delete_job_log_result.message) + + +@jobController.post('/jobLog/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:export'))]) +@Log(title='定时任务调度日志', business_type=BusinessType.EXPORT) +async def export_system_job_log_list( + request: Request, + job_log_page_query: JobLogPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + job_log_query_result = await JobLogService.get_job_log_list_services(query_db, job_log_page_query, is_page=False) + job_log_export_result = await JobLogService.export_job_log_list_services(request, job_log_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(job_log_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/log_controller.py b/ruoyi-fastapi-backend/module_admin/controller/log_controller.py new file mode 100644 index 0000000..a01ba94 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/log_controller.py @@ -0,0 +1,150 @@ +from fastapi import APIRouter, Depends, Form, Request +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.log_vo import ( + DeleteLoginLogModel, + DeleteOperLogModel, + LoginLogPageQueryModel, + OperLogPageQueryModel, + UnlockUser, +) +from module_admin.service.log_service import LoginLogService, OperationLogService +from module_admin.service.login_service import LoginService +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +logController = APIRouter(prefix='/monitor', dependencies=[Depends(LoginService.get_current_user)]) + + +@logController.get( + '/operlog/list', + response_model=PageResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:list'))], +) +async def get_system_operation_log_list( + request: Request, + operation_log_page_query: OperLogPageQueryModel = Depends(OperLogPageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + operation_log_page_query_result = await OperationLogService.get_operation_log_list_services( + query_db, operation_log_page_query, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=operation_log_page_query_result) + + +@logController.delete('/operlog/clean', dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:remove'))]) +@Log(title='操作日志', business_type=BusinessType.CLEAN) +async def clear_system_operation_log(request: Request, query_db: AsyncSession = Depends(get_db)): + clear_operation_log_result = await OperationLogService.clear_operation_log_services(query_db) + logger.info(clear_operation_log_result.message) + + return ResponseUtil.success(msg=clear_operation_log_result.message) + + +@logController.delete('/operlog/{oper_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:remove'))]) +@Log(title='操作日志', business_type=BusinessType.DELETE) +async def delete_system_operation_log(request: Request, oper_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_operation_log = DeleteOperLogModel(operIds=oper_ids) + delete_operation_log_result = await OperationLogService.delete_operation_log_services( + query_db, delete_operation_log + ) + logger.info(delete_operation_log_result.message) + + return ResponseUtil.success(msg=delete_operation_log_result.message) + + +@logController.post('/operlog/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:export'))]) +@Log(title='操作日志', business_type=BusinessType.EXPORT) +async def export_system_operation_log_list( + request: Request, + operation_log_page_query: OperLogPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + operation_log_query_result = await OperationLogService.get_operation_log_list_services( + query_db, operation_log_page_query, is_page=False + ) + operation_log_export_result = await OperationLogService.export_operation_log_list_services( + request, operation_log_query_result + ) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(operation_log_export_result)) + + +@logController.get( + '/logininfor/list', + response_model=PageResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:list'))], +) +async def get_system_login_log_list( + request: Request, + login_log_page_query: LoginLogPageQueryModel = Depends(LoginLogPageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + login_log_page_query_result = await LoginLogService.get_login_log_list_services( + query_db, login_log_page_query, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=login_log_page_query_result) + + +@logController.delete('/logininfor/clean', dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:remove'))]) +@Log(title='登录日志', business_type=BusinessType.CLEAN) +async def clear_system_login_log(request: Request, query_db: AsyncSession = Depends(get_db)): + clear_login_log_result = await LoginLogService.clear_login_log_services(query_db) + logger.info(clear_login_log_result.message) + + return ResponseUtil.success(msg=clear_login_log_result.message) + + +@logController.delete( + '/logininfor/{info_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:remove'))] +) +@Log(title='登录日志', business_type=BusinessType.DELETE) +async def delete_system_login_log(request: Request, info_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_login_log = DeleteLoginLogModel(infoIds=info_ids) + delete_login_log_result = await LoginLogService.delete_login_log_services(query_db, delete_login_log) + logger.info(delete_login_log_result.message) + + return ResponseUtil.success(msg=delete_login_log_result.message) + + +@logController.get( + '/logininfor/unlock/{user_name}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:unlock'))] +) +@Log(title='账户解锁', business_type=BusinessType.OTHER) +async def unlock_system_user(request: Request, user_name: str, query_db: AsyncSession = Depends(get_db)): + unlock_user = UnlockUser(userName=user_name) + unlock_user_result = await LoginLogService.unlock_user_services(request, unlock_user) + logger.info(unlock_user_result.message) + + return ResponseUtil.success(msg=unlock_user_result.message) + + +@logController.post('/logininfor/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:export'))]) +@Log(title='登录日志', business_type=BusinessType.EXPORT) +async def export_system_login_log_list( + request: Request, + login_log_page_query: LoginLogPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + login_log_query_result = await LoginLogService.get_login_log_list_services( + query_db, login_log_page_query, is_page=False + ) + login_log_export_result = await LoginLogService.export_login_log_list_services(login_log_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(login_log_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/login_controller.py b/ruoyi-fastapi-backend/module_admin/controller/login_controller.py new file mode 100644 index 0000000..9a255ef --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/login_controller.py @@ -0,0 +1,155 @@ +import jwt +import uuid +from datetime import datetime, timedelta +from fastapi import APIRouter, Depends, Request +from sqlalchemy.ext.asyncio import AsyncSession +from typing import Optional +from config.enums import BusinessType, RedisInitKeyConfig +from config.env import AppConfig, JwtConfig +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.login_vo import UserLogin, UserRegister, Token +from module_admin.entity.vo.user_vo import CurrentUserModel, EditUserModel +from module_admin.service.login_service import CustomOAuth2PasswordRequestForm, LoginService, oauth2_scheme +from module_admin.service.user_service import UserService +from utils.log_util import logger +from utils.response_util import ResponseUtil + + +loginController = APIRouter() + + +@loginController.post('/login', response_model=Token) +@Log(title='用户登录', business_type=BusinessType.OTHER, log_type='login') +async def login( + request: Request, form_data: CustomOAuth2PasswordRequestForm = Depends(), query_db: AsyncSession = Depends(get_db) +): + # 是否启用验证码 + # captcha_enabled = ( + # True + # if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled') + # == 'true' + # else False + # ) + + captcha_enabled =False + + user = UserLogin( + userName=form_data.username, + password=form_data.password, + code=form_data.code, + uuid=form_data.uuid, + loginInfo=form_data.login_info, + captchaEnabled=captcha_enabled, + ) + result = await LoginService.authenticate_user(request, query_db, user) + access_token_expires = timedelta(minutes=JwtConfig.jwt_expire_minutes) + session_id = str(uuid.uuid4()) + access_token = await LoginService.create_access_token( + data={ + 'user_id': str(result[0].user_id), + 'user_name': result[0].user_name, + 'dept_name': result[1].dept_name if result[1] else None, + 'session_id': session_id, + 'login_info': user.login_info, + }, + expires_delta=access_token_expires, + ) + if AppConfig.app_same_time_login: + await request.app.state.redis.set( + f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}', + access_token, + ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes), + ) + else: + # 此方法可实现同一账号同一时间只能登录一次 + await request.app.state.redis.set( + f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{result[0].user_id}', + access_token, + ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes), + ) + await UserService.edit_user_services( + query_db, EditUserModel(userId=result[0].user_id, loginDate=datetime.now(), type='status') + ) + logger.info('登录成功') + # 判断请求是否来自于api文档,如果是返回指定格式的结果,用于修复api文档认证成功后token显示undefined的bug + request_from_swagger = request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False + request_from_redoc = request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False + if request_from_swagger or request_from_redoc: + return {'access_token': access_token, 'token_type': 'Bearer'} + return ResponseUtil.success(msg='登录成功', dict_content={'token': access_token}) + + +@loginController.get('/getInfo', response_model=CurrentUserModel) +async def get_login_user_info( + request: Request, current_user: CurrentUserModel = Depends(LoginService.get_current_user) +): + logger.info('获取成功') + + return ResponseUtil.success(model_content=current_user) + + +@loginController.get('/getRouters') +async def get_login_user_routers( + request: Request, + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + query_db: AsyncSession = Depends(get_db), +): + logger.info('获取成功') + user_routers = await LoginService.get_current_user_routers(current_user.user.user_id, query_db) + + return ResponseUtil.success(data=user_routers) + + +@loginController.post('/register', response_model=CrudResponseModel) +async def register_user(request: Request, user_register: UserRegister, query_db: AsyncSession = Depends(get_db)): + user_register_result = await LoginService.register_user_services(request, query_db, user_register) + logger.info(user_register_result.message) + + return ResponseUtil.success(data=user_register_result, msg=user_register_result.message) + + +# @loginController.post("/getSmsCode", response_model=SmsCode) +# async def get_sms_code(request: Request, user: ResetUserModel, query_db: AsyncSession = Depends(get_db)): +# try: +# sms_result = await LoginService.get_sms_code_services(request, query_db, user) +# if sms_result.is_success: +# logger.info('获取成功') +# return ResponseUtil.success(data=sms_result) +# else: +# logger.warning(sms_result.message) +# return ResponseUtil.failure(msg=sms_result.message) +# except Exception as e: +# logger.exception(e) +# return ResponseUtil.error(msg=str(e)) +# +# +# @loginController.post("/forgetPwd", response_model=CrudResponseModel) +# async def forget_user_pwd(request: Request, forget_user: ResetUserModel, query_db: AsyncSession = Depends(get_db)): +# try: +# forget_user_result = await LoginService.forget_user_services(request, query_db, forget_user) +# if forget_user_result.is_success: +# logger.info(forget_user_result.message) +# return ResponseUtil.success(data=forget_user_result, msg=forget_user_result.message) +# else: +# logger.warning(forget_user_result.message) +# return ResponseUtil.failure(msg=forget_user_result.message) +# except Exception as e: +# logger.exception(e) +# return ResponseUtil.error(msg=str(e)) + + +@loginController.post('/logout') +async def logout(request: Request + # , token: Optional[str] = Depends(oauth2_scheme) + ): + token = request.headers.get('Authorization').split(" ")[-1] + payload = jwt.decode( + token, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm], options={'verify_exp': False} + ) + session_id: str = payload.get('session_id') + await LoginService.logout_services(request, session_id) + logger.info('退出成功') + + return ResponseUtil.success(msg='退出成功') diff --git a/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py b/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py new file mode 100644 index 0000000..0e3124e --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py @@ -0,0 +1,114 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.menu_vo import DeleteMenuModel, MenuModel, MenuQueryModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.menu_service import MenuService +from utils.log_util import logger +from utils.response_util import ResponseUtil + + +menuController = APIRouter(prefix='/system/menu', dependencies=[Depends(LoginService.get_current_user)]) + + +@menuController.get('/treeselect') +async def get_system_menu_tree( + request: Request, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + menu_query_result = await MenuService.get_menu_tree_services(query_db, current_user) + logger.info('获取成功') + + return ResponseUtil.success(data=menu_query_result) + + +@menuController.get('/roleMenuTreeselect/{role_id}') +async def get_system_role_menu_tree( + request: Request, + role_id: int, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + role_menu_query_result = await MenuService.get_role_menu_tree_services(query_db, role_id, current_user) + logger.info('获取成功') + + return ResponseUtil.success(model_content=role_menu_query_result) + + +@menuController.get( + '/list', response_model=List[MenuModel], dependencies=[Depends(CheckUserInterfaceAuth('system:menu:list'))] +) +async def get_system_menu_list( + request: Request, + menu_query: MenuQueryModel = Depends(MenuQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + menu_query_result = await MenuService.get_menu_list_services(query_db, menu_query, current_user) + logger.info('获取成功') + + return ResponseUtil.success(data=menu_query_result) + + +@menuController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:menu:add'))]) +@ValidateFields(validate_model='add_menu') +@Log(title='菜单管理', business_type=BusinessType.INSERT) +async def add_system_menu( + request: Request, + add_menu: MenuModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_menu.create_by = current_user.user.user_name + add_menu.create_time = datetime.now() + add_menu.update_by = current_user.user.user_name + add_menu.update_time = datetime.now() + add_menu_result = await MenuService.add_menu_services(query_db, add_menu) + logger.info(add_menu_result.message) + + return ResponseUtil.success(msg=add_menu_result.message) + + +@menuController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:menu:edit'))]) +@ValidateFields(validate_model='edit_menu') +@Log(title='菜单管理', business_type=BusinessType.UPDATE) +async def edit_system_menu( + request: Request, + edit_menu: MenuModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_menu.update_by = current_user.user.user_name + edit_menu.update_time = datetime.now() + edit_menu_result = await MenuService.edit_menu_services(query_db, edit_menu) + logger.info(edit_menu_result.message) + + return ResponseUtil.success(msg=edit_menu_result.message) + + +@menuController.delete('/{menu_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:menu:remove'))]) +@Log(title='菜单管理', business_type=BusinessType.DELETE) +async def delete_system_menu(request: Request, menu_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_menu = DeleteMenuModel(menuIds=menu_ids) + delete_menu_result = await MenuService.delete_menu_services(query_db, delete_menu) + logger.info(delete_menu_result.message) + + return ResponseUtil.success(msg=delete_menu_result.message) + + +@menuController.get( + '/{menu_id}', response_model=MenuModel, dependencies=[Depends(CheckUserInterfaceAuth('system:menu:query'))] +) +async def query_detail_system_menu(request: Request, menu_id: int, query_db: AsyncSession = Depends(get_db)): + menu_detail_result = await MenuService.menu_detail_services(query_db, menu_id) + logger.info(f'获取menu_id为{menu_id}的信息成功') + + return ResponseUtil.success(data=menu_detail_result) diff --git a/ruoyi-fastapi-backend/module_admin/controller/message_controller.py b/ruoyi-fastapi-backend/module_admin/controller/message_controller.py new file mode 100644 index 0000000..3da00e6 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/message_controller.py @@ -0,0 +1,163 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.message_service import MessageService +from module_admin.entity.vo.message_vo import DeleteMessageModel, MessageModel, MessagePageQueryModel, EditMessageModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +messageController = APIRouter(prefix='/system/message', dependencies=[Depends(LoginService.get_current_user)]) + + +# 查看告警类型分布 +@messageController.get( + '/alarm_type_distribution' +) +async def get_alarm_type_distribution( + request: Request, + query_db: AsyncSession = Depends(get_db), +): + result = await MessageService.get_alarm_type_distribution_services(query_db) + return ResponseUtil.success(data=result) + + + +# 一键处理未处理的告警消息 +@messageController.get( + '/handle_all' +) +async def handle_all_message( + query_db: AsyncSession = Depends(get_db), +): + result = await MessageService.handle_all_message_services(query_db) + return ResponseUtil.success(msg= result.message) + + +# 查询告警数量信息 +@messageController.get( + '/count_message', +) +async def count_message(request: Request, query_db: AsyncSession = Depends(get_db)): + """""" + count_message_result = await MessageService.count_message(query_db) + return ResponseUtil.success(data = count_message_result) + + + +# # 获取所有消息列表 +# @messageController.get( +# '/list', response_model=PageResponseModel +# # , dependencies=[Depends(CheckUserInterfaceAuth('system:message:list'))] +# ) +# async def get_system_message_list( +# request: Request, +# message_page_query: MessagePageQueryModel = Depends(MessagePageQueryModel.as_query), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取分页数据 +# message_page_query_result = await MessageService.get_message_list_services(query_db, message_page_query, is_page=True) +# logger.info('获取成功') + +# return ResponseUtil.success(model_content=message_page_query_result) + +# 获取告警消息列表 +@messageController.get('/alert_message') +async def get_alert_message( + request: Request, + pageNum: int = 1, + pageSize: int = 10, + query_db: AsyncSession = Depends(get_db), +): + alert_message = await MessageService.get_alert_message_services(query_db, pageNum, pageSize) + return ResponseUtil.success(model_content=alert_message) + + +# 该接口只做测试用, 并不对外开放 +# @messageController.post('/add_message' +# # , dependencies=[Depends(CheckUserInterfaceAuth('system:message:add'))] +# ) +# @ValidateFields(validate_model='add_message') +# @Log(title='系统消息', business_type=BusinessType.INSERT) +# async def add_system_message( +# request: Request, +# add_message: MessageModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# add_message.create_time = datetime.now() +# add_message.create_by = current_user.user.user_name +# add_message.update_time = datetime.now() +# add_message.update_by = current_user.user.user_name +# add_message.status = '0' +# add_message_result = await MessageService.add_message_services(query_db, add_message) +# logger.info(add_message_result.message) + +# return ResponseUtil.success(msg=add_message_result.message) + + +# @messageController.put('/edit_message' +# # , dependencies=[Depends(CheckUserInterfaceAuth('system:message:edit'))] +# ) +# @ValidateFields(validate_model='edit_message') +# @Log(title='系统消息', business_type=BusinessType.UPDATE) +# async def edit_system_message( +# request: Request, +# edit_message: EditMessageModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# edit_message.update_by = current_user.user.user_name +# edit_message.update_time = datetime.now() +# edit_message_result = await MessageService.edit_message_services(query_db, edit_message) +# logger.info(edit_message_result.message) + +# return ResponseUtil.success(msg=edit_message_result.message) + + +# @messageController.delete('/{message_ids}' +# , dependencies=[Depends(CheckUserInterfaceAuth('system:message:remove'))] +# ) +# @Log(title='系统消息', business_type=BusinessType.DELETE) +# async def delete_system_message(request: Request, message_ids: str, query_db: AsyncSession = Depends(get_db)): +# delete_message = DeleteMessageModel(messageIds=message_ids) +# delete_message_result = await MessageService.delete_message_services(query_db, delete_message) +# logger.info(delete_message_result.message) + +# return ResponseUtil.success(msg=delete_message_result.message) + + +# @messageController.get( +# '/{message_id}', response_model=MessageModel +# # , dependencies=[Depends(CheckUserInterfaceAuth('system:message:query'))] +# ) +# async def query_detail_system_message(request: Request, message_id: int, query_db: AsyncSession = Depends(get_db)): +# message_detail_result = await MessageService.message_detail_services(query_db, message_id) +# logger.info(f'获取message_id为{message_id}的信息成功') + +# return ResponseUtil.success(data=message_detail_result) + + +# @messageController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:message:export'))]) +# @Log(title='系统消息', business_type=BusinessType.EXPORT) +# async def export_system_message_list( +# request: Request, +# message_page_query: MessagePageQueryModel = Form(), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取全量数据 +# message_query_result = await MessageService.get_message_list_services(query_db, message_page_query, is_page=False) +# message_export_result = await MessageService.export_message_list_services(message_query_result) +# logger.info('导出成功') +# +# return ResponseUtil.streaming(data=bytes2file_response(message_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py b/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py new file mode 100644 index 0000000..e1e4aa1 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py @@ -0,0 +1,89 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.notice_vo import DeleteNoticeModel, NoticeModel, NoticePageQueryModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.notice_service import NoticeService +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +noticeController = APIRouter(prefix='/system/notice', dependencies=[Depends(LoginService.get_current_user)]) + + +@noticeController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:notice:list'))] +) +async def get_system_notice_list( + request: Request, + notice_page_query: NoticePageQueryModel = Depends(NoticePageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + notice_page_query_result = await NoticeService.get_notice_list_services(query_db, notice_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=notice_page_query_result) + + +@noticeController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:notice:add'))]) +@ValidateFields(validate_model='add_notice') +@Log(title='通知公告', business_type=BusinessType.INSERT) +async def add_system_notice( + request: Request, + add_notice: NoticeModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_notice.create_by = current_user.user.user_name + add_notice.create_time = datetime.now() + add_notice.update_by = current_user.user.user_name + add_notice.update_time = datetime.now() + add_notice_result = await NoticeService.add_notice_services(query_db, add_notice) + logger.info(add_notice_result.message) + + return ResponseUtil.success(msg=add_notice_result.message) + + +@noticeController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:notice:edit'))]) +@ValidateFields(validate_model='edit_notice') +@Log(title='通知公告', business_type=BusinessType.UPDATE) +async def edit_system_notice( + request: Request, + edit_notice: NoticeModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_notice.update_by = current_user.user.user_name + edit_notice.update_time = datetime.now() + edit_notice_result = await NoticeService.edit_notice_services(query_db, edit_notice) + logger.info(edit_notice_result.message) + + return ResponseUtil.success(msg=edit_notice_result.message) + + +@noticeController.delete('/{notice_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:notice:remove'))]) +@Log(title='通知公告', business_type=BusinessType.DELETE) +async def delete_system_notice(request: Request, notice_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_notice = DeleteNoticeModel(noticeIds=notice_ids) + delete_notice_result = await NoticeService.delete_notice_services(query_db, delete_notice) + logger.info(delete_notice_result.message) + + return ResponseUtil.success(msg=delete_notice_result.message) + + +@noticeController.get( + '/{notice_id}', response_model=NoticeModel, dependencies=[Depends(CheckUserInterfaceAuth('system:notice:query'))] +) +async def query_detail_system_post(request: Request, notice_id: int, query_db: AsyncSession = Depends(get_db)): + notice_detail_result = await NoticeService.notice_detail_services(query_db, notice_id) + logger.info(f'获取notice_id为{notice_id}的信息成功') + + return ResponseUtil.success(data=notice_detail_result) diff --git a/ruoyi-fastapi-backend/module_admin/controller/online_controller.py b/ruoyi-fastapi-backend/module_admin/controller/online_controller.py new file mode 100644 index 0000000..bf65c8f --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/online_controller.py @@ -0,0 +1,40 @@ +from fastapi import APIRouter, Depends, Request +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.online_vo import DeleteOnlineModel, OnlineQueryModel +from module_admin.service.login_service import LoginService +from module_admin.service.online_service import OnlineService +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +onlineController = APIRouter(prefix='/monitor/online', dependencies=[Depends(LoginService.get_current_user)]) + + +@onlineController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:online:list'))] +) +async def get_monitor_online_list( + request: Request, online_page_query: OnlineQueryModel = Depends(OnlineQueryModel.as_query) +): + # 获取全量数据 + online_query_result = await OnlineService.get_online_list_services(request, online_page_query) + logger.info('获取成功') + + return ResponseUtil.success( + model_content=PageResponseModel(rows=online_query_result, total=len(online_query_result)) + ) + + +@onlineController.delete('/{token_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:online:forceLogout'))]) +@Log(title='在线用户', business_type=BusinessType.FORCE) +async def delete_monitor_online(request: Request, token_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_online = DeleteOnlineModel(tokenIds=token_ids) + delete_online_result = await OnlineService.delete_online_services(request, delete_online) + logger.info(delete_online_result.message) + + return ResponseUtil.success(msg=delete_online_result.message) diff --git a/ruoyi-fastapi-backend/module_admin/controller/post_controler.py b/ruoyi-fastapi-backend/module_admin/controller/post_controler.py new file mode 100644 index 0000000..e9c69c4 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/post_controler.py @@ -0,0 +1,105 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.service.login_service import LoginService +from module_admin.service.post_service import PostService +from module_admin.entity.vo.post_vo import DeletePostModel, PostModel, PostPageQueryModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +postController = APIRouter(prefix='/system/post', dependencies=[Depends(LoginService.get_current_user)]) + + +@postController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:post:list'))] +) +async def get_system_post_list( + request: Request, + post_page_query: PostPageQueryModel = Depends(PostPageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + post_page_query_result = await PostService.get_post_list_services(query_db, post_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=post_page_query_result) + + +@postController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:post:add'))]) +@ValidateFields(validate_model='add_post') +@Log(title='岗位管理', business_type=BusinessType.INSERT) +async def add_system_post( + request: Request, + add_post: PostModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_post.create_by = current_user.user.user_name + add_post.create_time = datetime.now() + add_post.update_by = current_user.user.user_name + add_post.update_time = datetime.now() + add_post_result = await PostService.add_post_services(query_db, add_post) + logger.info(add_post_result.message) + + return ResponseUtil.success(msg=add_post_result.message) + + +@postController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:post:edit'))]) +@ValidateFields(validate_model='edit_post') +@Log(title='岗位管理', business_type=BusinessType.UPDATE) +async def edit_system_post( + request: Request, + edit_post: PostModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_post.update_by = current_user.user.user_name + edit_post.update_time = datetime.now() + edit_post_result = await PostService.edit_post_services(query_db, edit_post) + logger.info(edit_post_result.message) + + return ResponseUtil.success(msg=edit_post_result.message) + + +@postController.delete('/{post_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:post:remove'))]) +@Log(title='岗位管理', business_type=BusinessType.DELETE) +async def delete_system_post(request: Request, post_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_post = DeletePostModel(postIds=post_ids) + delete_post_result = await PostService.delete_post_services(query_db, delete_post) + logger.info(delete_post_result.message) + + return ResponseUtil.success(msg=delete_post_result.message) + + +@postController.get( + '/{post_id}', response_model=PostModel, dependencies=[Depends(CheckUserInterfaceAuth('system:post:query'))] +) +async def query_detail_system_post(request: Request, post_id: int, query_db: AsyncSession = Depends(get_db)): + post_detail_result = await PostService.post_detail_services(query_db, post_id) + logger.info(f'获取post_id为{post_id}的信息成功') + + return ResponseUtil.success(data=post_detail_result) + + +@postController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:post:export'))]) +@Log(title='岗位管理', business_type=BusinessType.EXPORT) +async def export_system_post_list( + request: Request, + post_page_query: PostPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + post_query_result = await PostService.get_post_list_services(query_db, post_page_query, is_page=False) + post_export_result = await PostService.export_post_list_services(post_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(post_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/ragflow_controller.py b/ruoyi-fastapi-backend/module_admin/controller/ragflow_controller.py new file mode 100644 index 0000000..2264548 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/ragflow_controller.py @@ -0,0 +1,295 @@ +# from datetime import datetime +import hashlib +import json +import time +from typing import List +from fastapi import APIRouter, Depends, Request, UploadFile, File +from fastapi.responses import StreamingResponse +# from pydantic_validation_decorator import ValidateFields +# from sqlalchemy.ext.asyncio import AsyncSession +# from config.enums import BusinessType +# from config.get_db import get_db +from module_admin.aspect.interface_auth import CheckRoleInterfaceAuth +# from module_admin.entity.vo.notice_vo import DeleteNoticeModel, NoticeModel, NoticePageQueryModel +# from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.ragflow_service import RAGFlowService +from module_admin.service.weather_service import WeatherService, WeatherServiceError + +from utils.log_util import logger +# from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil +from module_admin.entity.vo.ragflow_vo import RagflowListQueryModel, ListDocumentsQueryModel, UpdateFileModel, DeleteFileModel, CreateDatasetModel, DocumentIdsModel, UpdateChatAssistantModel,\ + CreateSessionWithChatModel, ConverseWithChatAssistantModel +# from config.env import RAGFlowConfig + + + +ragflowController = APIRouter(prefix="/system/ragflow", dependencies=[Depends(LoginService.get_current_user)]) + + + +# 查看数据集列表 +@ragflowController.post("/dataset_list" + # , response_model=PageResponseModel + # , dependencies=[Depends(CheckUserInterfaceAuth("system:ragflow:list"))]" + ) +async def get_system_ragflow_list( + request: Request, + rage_flow_dastset_query: RagflowListQueryModel , + # query_db: AsyncSession = Depends(get_db), +): + + result = await RAGFlowService.get_ragflow_dataset_list_services(None, rage_flow_dastset_query) + + return parse_result(result) + +# 创建数据集 +@ragflowController.post('/create_dataset') +async def create_dataset( + request: Request, + create_dataset_params: CreateDatasetModel, +): + + result = await RAGFlowService.create_dataset_services(create_dataset_params) + return parse_result(result) + +# 更新数据集 +@ragflowController.post('/update_dataset/{dataset_id}') +async def update_dataset( + request: Request, + dataset_id: str, + update_dataset_params: CreateDatasetModel, +): + result = await RAGFlowService.update_dataset_services(dataset_id, update_dataset_params) + return parse_result(result) + + +# 列出数据集中文档列表 +@ragflowController.get("/list_documents/{dataset_id}") +async def list_documents_by_dataset_id( + request: Request, + dataset_id: str, + list_documents_query: ListDocumentsQueryModel = Depends(ListDocumentsQueryModel.as_query), + + # query_db: AsyncSession = Depends(get_db), +): + """ + 列出数据集中文档列表 + """ + print(list_documents_query) + result = await RAGFlowService.list_documents_services(None, dataset_id, list_documents_query) + + return parse_result(result) + + +# 上传文件到数据集 +@ragflowController.post("/upload_file/{dataset_id}") +async def upload_file_dataset( + dataset_id: str, + files: List[UploadFile] = File(...), + # query_db: AsyncSession = Depends(get_db), +): + """ + 上传文件到数据集 + """ + # print(file) + result = await RAGFlowService.upload_file_dataset_services(None, dataset_id ,files) + + return parse_result(result) + +# 更新文档 +@ragflowController.post("/update_file/{dataset_id}/{document_id}") +async def update_file_dataset( + dataset_id: str, + document_id: str, + update_params: UpdateFileModel, + # query_db: AsyncSession = Depends(get_db), +): + """ + 更新文件到数据集 + """ + # print(file) + result = await RAGFlowService.update_file_dataset_services(dataset_id ,document_id, update_params) + + return parse_result(result) + +# 开始解析文档 +@ragflowController.post('/parse_documents/{dataset_id}') +async def parse_documents( + dataset_id: str, + parse_params: DocumentIdsModel, + # query_db: AsyncSession = Depends(get_db), +): + result = await RAGFlowService.parse_documents_services(dataset_id, parse_params) + return parse_result(result) + +# 停止解析文档 +@ragflowController.post('/stop_parse_documents/{dataset_id}') +async def stop_parse_documents( + dataset_id: str, + parse_params: DocumentIdsModel, + # query_db: AsyncSession = Depends(get_db), +): + result = await RAGFlowService.stop_parse_documents_services(dataset_id, parse_params) + return parse_result(result) + +# 删除文档 +@ragflowController.post('/delete_file/{dataset_id}') +async def delete_file( + dataset_id: str, + delete_params: DeleteFileModel, + # query_db: AsyncSession = Depends(get_db), +): + """ + 删除文件 + """ + result = await RAGFlowService.delete_file_services(dataset_id, delete_params) + + return parse_result(result) + + +# 删除数据集 +@ragflowController.post('/delete_datasets') +async def delete_datasets( + delete_params: DeleteFileModel, + # query_db: AsyncSession = Depends(get_db), +): + """ + 删除数据集 + """ + result = await RAGFlowService.delete_datasets_services(delete_params) + return parse_result(result) + +# 查看聊天助手列表 +@ragflowController.post('/get_chat_assistant_list') +async def get_chat_assistant_list( + query_params: RagflowListQueryModel, +): + """ + 查看聊天助手列表 + """ + + result = await RAGFlowService.get_chat_assistant_list_services(query_params) + return parse_result(result) + + # pass + +# 更新聊天助手 +@ragflowController.post('/update_chat_assistant') +async def update_chat_assistant( + update_params: UpdateChatAssistantModel, +): + """ + 更新聊天助手 + """ + result = await RAGFlowService.update_chat_assistant_services(update_params) + return parse_result(result) + +# 创建属于聊天助手的会话 +@ragflowController.post('/create_session_with_chat') +async def create_session_with_chat( + create_params: CreateSessionWithChatModel, +): + """ + 创建属于聊天助手的会话 + """ + + result = await RAGFlowService.create_session_with_chat_services(create_params) + return parse_result(result) + +# 与聊天助手进行对话 +@ragflowController.post('/converse_with_chat_assistant') +async def converse_with_chat_assistant( + request: Request, + converse_params: ConverseWithChatAssistantModel, +): + """ + 与聊天助手进行对话 + """ + + start_time = time.perf_counter() + redis = getattr(request.app.state, 'redis', None) + cache_key = None + + if WeatherService.should_handle(converse_params.question): + try: + weather_payload = await WeatherService.get_weather_answer(converse_params.question, redis=redis) + except WeatherServiceError as exc: + return ResponseUtil.error(str(exc)) + + if converse_params.stream: + async def weather_stream(): + yield format_sse(weather_payload) + yield format_sse({'status': 'completed'}, event='end') + + return StreamingResponse(weather_stream(), media_type='text/event-stream') + + return ResponseUtil.success(weather_payload) + + if not converse_params.stream and redis: + cache_key = build_chat_cache_key(converse_params.chat_id, converse_params.question) + cached = await redis.get(cache_key) + if cached: + logger.info('ragflow对话命中缓存: chat=%s', converse_params.chat_id) + return ResponseUtil.success(json.loads(cached)) + + result = await RAGFlowService.converse_with_chat_assistant_services(converse_params) + + if converse_params.stream: + async def stream_response(): + try: + async for chunk in result: + payload = chunk.get('data') if isinstance(chunk, dict) else chunk + if not payload: + continue + body = payload if isinstance(payload, dict) else {'data': payload} + yield format_sse(body) + yield format_sse({'status': 'completed'}, event='end') + except Exception as exc: + logger.exception('ragflow流式对话异常: %s', exc) + yield format_sse({'message': str(exc)}, event='error') + finally: + logger.info('ragflow流式对话耗时 %.3fs', time.perf_counter() - start_time) + + return StreamingResponse(stream_response(), media_type='text/event-stream') + + response = parse_result(result) + if redis and cache_key and isinstance(result, dict) and result.get('code') == 0: + await redis.set(cache_key, json.dumps(result.get('data'), ensure_ascii=False), ex=60) + logger.info('ragflow对话耗时 %.3fs', time.perf_counter() - start_time) + return response + + + + # return parse_result(result) + +# 获取用户权限 +@ragflowController.get('/get_user_permission', dependencies=[Depends(CheckRoleInterfaceAuth('pad'))]) +async def get_user_permission(current_user = Depends(LoginService.get_current_user)): + """ + 获取用户权限 + """ + + user_auth_list = current_user.permissions + print(user_auth_list) + + + return ResponseUtil.success(data=user_auth_list) + +def parse_result(result): + code = result.get('code', 0) + if code != 0: + return ResponseUtil.error(result.get('data', None)) + return ResponseUtil.success(result.get('data', None)) + + +def build_chat_cache_key(chat_id: str, question: str) -> str: + digest = hashlib.sha256(question.encode('utf-8')).hexdigest() + return f'ragflow:chat:{chat_id}:{digest}' + + +def format_sse(data: dict, event: str | None = None) -> str: + payload = json.dumps(data, ensure_ascii=False) + prefix = f'event: {event}\n' if event else '' + return f'{prefix}data: {payload}\n\n' \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_admin/controller/robot_action_controller.py b/ruoyi-fastapi-backend/module_admin/controller/robot_action_controller.py new file mode 100644 index 0000000..72d7902 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/robot_action_controller.py @@ -0,0 +1,106 @@ +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth, CheckRoleInterfaceAuth +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.robot_action_service import Robot_actionService +from module_admin.entity.vo.robot_action_vo import DeleteRobot_actionModel, Robot_actionModel, Robot_actionPageQueryModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + +from datetime import datetime + + +robot_actionController = APIRouter(prefix='/system/robot_action', dependencies=[Depends(LoginService.get_current_user)]) + + +@robot_actionController.get( + '/list', response_model=PageResponseModel + # , dependencies=[Depends(CheckUserInterfaceAuth('system:robot_action:list'))] +) +async def get_system_robot_action_list( + request: Request, + robot_action_page_query: Robot_actionPageQueryModel = Depends(Robot_actionPageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + robot_action_page_query_result = await Robot_actionService.get_robot_action_list_services(query_db, robot_action_page_query, is_page=False) + logger.info('获取成功') + + return ResponseUtil.success(data=robot_action_page_query_result) + + +# @robot_actionController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:robot_action:add'))]) +# @ValidateFields(validate_model='add_robot_action') +# @Log(title='机器人动作', business_type=BusinessType.INSERT) +# async def add_system_robot_action( +# request: Request, +# add_robot_action: Robot_actionModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# add_robot_action.update_time = datetime.now() +# add_robot_action_result = await Robot_actionService.add_robot_action_services(query_db, add_robot_action) +# logger.info(add_robot_action_result.message) + +# return ResponseUtil.success(msg=add_robot_action_result.message) + + +@robot_actionController.put('' + # , dependencies=[Depends(CheckUserInterfaceAuth('system:robot_action:edit'))] + ) +@ValidateFields(validate_model='edit_robot_action') +@Log(title='机器人动作', business_type=BusinessType.UPDATE) +async def edit_system_robot_action( + request: Request, + edit_robot_action: Robot_actionModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_robot_action.update_by = current_user.user.user_name + edit_robot_action.update_time = datetime.now() + edit_robot_action_result = await Robot_actionService.edit_robot_action_services(query_db, edit_robot_action) + logger.info(edit_robot_action_result.message) + + return ResponseUtil.success(msg=edit_robot_action_result.message) + + +# @robot_actionController.delete('/{ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:robot_action:remove'))]) +# @Log(title='机器人动作', business_type=BusinessType.DELETE) +# async def delete_system_robot_action(request: Request, ids: str, query_db: AsyncSession = Depends(get_db)): +# delete_robot_action = DeleteRobot_actionModel(ids=ids) +# delete_robot_action_result = await Robot_actionService.delete_robot_action_services(query_db, delete_robot_action) +# logger.info(delete_robot_action_result.message) + +# return ResponseUtil.success(msg=delete_robot_action_result.message) + + +# @robot_actionController.get( +# '/{id}', response_model=Robot_actionModel, dependencies=[Depends(CheckUserInterfaceAuth('system:robot_action:query'))] +# ) +# async def query_detail_system_robot_action(request: Request, id: int, query_db: AsyncSession = Depends(get_db)): +# robot_action_detail_result = await Robot_actionService.robot_action_detail_services(query_db, id) +# logger.info(f'获取id为{id}的信息成功') + +# return ResponseUtil.success(data=robot_action_detail_result) + + +# @robot_actionController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:robot_action:export'))]) +# @Log(title='机器人动作', business_type=BusinessType.EXPORT) +# async def export_system_robot_action_list( +# request: Request, +# robot_action_page_query: Robot_actionPageQueryModel = Form(), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取全量数据 +# robot_action_query_result = await Robot_actionService.get_robot_action_list_services(query_db, robot_action_page_query, is_page=False) +# robot_action_export_result = await Robot_actionService.export_robot_action_list_services(robot_action_query_result) +# logger.info('导出成功') + +# return ResponseUtil.streaming(data=bytes2file_response(robot_action_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/robot_role_controller.py b/ruoyi-fastapi-backend/module_admin/controller/robot_role_controller.py new file mode 100644 index 0000000..2fd2d74 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/robot_role_controller.py @@ -0,0 +1,110 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.role_service import RoleService +from module_admin.entity.vo.role_vo import DeleteRoleModel, RoleModel, RolePageQueryModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +""" + 当前用不到这个接口 +""" + + +# roleController = APIRouter(prefix='/system/role', dependencies=[Depends(LoginService.get_current_user)]) +# +# +# @roleController.get( +# '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:role:list'))] +# ) +# async def get_system_role_list( +# request: Request, +# role_page_query: RolePageQueryModel = Depends(RolePageQueryModel.as_query), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取分页数据 +# role_page_query_result = await RoleService.get_role_list_services(query_db, role_page_query, is_page=True) +# logger.info('获取成功') +# +# return ResponseUtil.success(model_content=role_page_query_result) + + +# @roleController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:role:add'))]) +# @ValidateFields(validate_model='add_role') +# @Log(title='系统角色', business_type=BusinessType.INSERT) +# async def add_system_role( +# request: Request, +# add_role: RoleModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# add_role.create_time = datetime.now() +# add_role.create_by = current_user.user.user_name +# add_role.update_time = datetime.now() +# add_role.update_by = current_user.user.user_name +# add_role_result = await RoleService.add_role_services(query_db, add_role) +# logger.info(add_role_result.message) +# +# return ResponseUtil.success(msg=add_role_result.message) + + +# @roleController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) +# @ValidateFields(validate_model='edit_role') +# @Log(title='系统角色', business_type=BusinessType.UPDATE) +# async def edit_system_role( +# request: Request, +# edit_role: RoleModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# edit_role.update_by = current_user.user.user_name +# edit_role.update_time = datetime.now() +# edit_role_result = await RoleService.edit_role_services(query_db, edit_role) +# logger.info(edit_role_result.message) +# +# return ResponseUtil.success(msg=edit_role_result.message) + + +# @roleController.delete('/{robot_role_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:role:remove'))]) +# @Log(title='系统角色', business_type=BusinessType.DELETE) +# async def delete_system_role(request: Request, robot_role_ids: str, query_db: AsyncSession = Depends(get_db)): +# delete_role = DeleteRoleModel(robotRoleIds=robot_role_ids) +# delete_role_result = await RoleService.delete_role_services(query_db, delete_role) +# logger.info(delete_role_result.message) +# +# return ResponseUtil.success(msg=delete_role_result.message) + + +# @roleController.get( +# '/{robot_role_id}', response_model=RoleModel, dependencies=[Depends(CheckUserInterfaceAuth('system:role:query'))] +# ) +# async def query_detail_system_role(request: Request, robot_role_id: int, query_db: AsyncSession = Depends(get_db)): +# role_detail_result = await RoleService.role_detail_services(query_db, robot_role_id) +# logger.info(f'获取robot_role_id为{robot_role_id}的信息成功') +# +# return ResponseUtil.success(data=role_detail_result) + + +# @roleController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:role:export'))]) +# @Log(title='系统角色', business_type=BusinessType.EXPORT) +# async def export_system_role_list( +# request: Request, +# role_page_query: RolePageQueryModel = Form(), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取全量数据 +# role_query_result = await RoleService.get_role_list_services(query_db, role_page_query, is_page=False) +# role_export_result = await RoleService.export_role_list_services(role_query_result) +# logger.info('导出成功') +# +# return ResponseUtil.streaming(data=bytes2file_response(role_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/robot_role_pairing_controller.py b/ruoyi-fastapi-backend/module_admin/controller/robot_role_pairing_controller.py new file mode 100644 index 0000000..18a0670 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/robot_role_pairing_controller.py @@ -0,0 +1,155 @@ +from fastapi import APIRouter, Depends, Form, Request, Response +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.dao.robot_role_pairing_dao import PairingDao +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.robot_role_pairing_service import PairingService +from module_admin.entity.vo.robot_role_pairing_vo import DeletePairingModel, PairingModel, PairingPageQueryModel, \ + ChangeRobotRoleModel +from utils.common_util import bytes2file_response, export_list2excel +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil +from datetime import datetime + + +pairingController = APIRouter(prefix='/system/robot_role' + # , dependencies=[Depends(LoginService.get_current_user)] + ) + + +# 获取机器人角色 +@pairingController.get( + '/{robot_id}', +) +async def get_system_role_pairing_list( + request: Request, + robot_id: int, + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + pairing_list, style = await PairingService.get_role_pairing_list_services(query_db, robot_id) + + return ResponseUtil.success(data={"role": pairing_list, "style":style}) + +# 获取机器人角色新版 +@pairingController.get( + '/v1/{robot_id}', +) +async def get_system_role_pairing_list_v1( + request: Request, + robot_id: int, + query_db: AsyncSession = Depends(get_db), +): + + pairing_list, style = await PairingService.get_role_pairing_list_services_v1(query_db, robot_id) + + return ResponseUtil.success(data={"role": pairing_list, "style":style}) + + + +@pairingController.post( + '/change', +) +async def change_robot_role( + request: Request, + change_pairing: ChangeRobotRoleModel, + query_db: AsyncSession = Depends(get_db), +): + await PairingService.change_robot_role_services(query_db,change_pairing) + return ResponseUtil.success() + +# @pairingController.get( +# '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:pairing:list'))] +# ) +# async def get_system_pairing_list( +# request: Request, +# pairing_page_query: PairingPageQueryModel = Depends(PairingPageQueryModel.as_query), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取分页数据 +# pairing_page_query_result = await PairingService.get_pairing_list_services(query_db, pairing_page_query, is_page=True) +# logger.info('获取成功') +# +# return ResponseUtil.success(model_content=pairing_page_query_result) +# +# +# @pairingController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:pairing:add'))]) +# @ValidateFields(validate_model='add_pairing') +# @Log(title='角色-机器人-映射', business_type=BusinessType.INSERT) +# async def add_system_pairing( +# request: Request, +# add_pairing: PairingModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# add_pairing.update_time = datetime.now() +# add_pairing.update_by = current_user.user.user_name +# add_pairing_result = await PairingService.add_pairing_services(query_db, add_pairing) +# logger.info(add_pairing_result.message) +# +# return ResponseUtil.success(msg=add_pairing_result.message) +# +# +# @pairingController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:pairing:edit'))]) +# @ValidateFields(validate_model='edit_pairing') +# @Log(title='角色-机器人-映射', business_type=BusinessType.UPDATE) +# async def edit_system_pairing( +# request: Request, +# edit_pairing: PairingModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# edit_pairing.update_by = current_user.user.user_name +# edit_pairing.update_time = datetime.now() +# edit_pairing_result = await PairingService.edit_pairing_services(query_db, edit_pairing) +# logger.info(edit_pairing_result.message) +# +# return ResponseUtil.success(msg=edit_pairing_result.message) +# +# +# @pairingController.delete('/{pairing_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:pairing:remove'))]) +# @Log(title='角色-机器人-映射', business_type=BusinessType.DELETE) +# async def delete_system_pairing(request: Request, pairing_ids: str, query_db: AsyncSession = Depends(get_db)): +# delete_pairing = DeletePairingModel(pairingIds=pairing_ids) +# delete_pairing_result = await PairingService.delete_pairing_services(query_db, delete_pairing) +# logger.info(delete_pairing_result.message) +# +# return ResponseUtil.success(msg=delete_pairing_result.message) +# +# +# @pairingController.get( +# '/{pairing_id}', response_model=PairingModel, dependencies=[Depends(CheckUserInterfaceAuth('system:pairing:query'))] +# ) +# async def query_detail_system_pairing(request: Request, pairing_id: int, query_db: AsyncSession = Depends(get_db)): +# pairing_detail_result = await PairingService.pairing_detail_services(query_db, pairing_id) +# logger.info(f'获取pairing_id为{pairing_id}的信息成功') +# +# return ResponseUtil.success(data=pairing_detail_result) +# +# +# @pairingController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:pairing:export'))]) +# @Log(title='角色-机器人-映射', business_type=BusinessType.EXPORT) +# async def export_system_pairing_list( +# request: Request, +# pairing_page_query: PairingPageQueryModel = Form(), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取全量数据 +# pairing_query_result = await PairingService.get_pairing_list_services(query_db, pairing_page_query, is_page=False) +# pairing_export_result = await PairingService.export_pairing_list_services(pairing_query_result) +# logger.info('导出成功') + +# # return Response( +# # content=pairing_export_result, +# # media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", +# # headers={ +# # "Content-Disposition": "attachment; filename=export.xlsx" +# # } +# # ) +# return ResponseUtil.streaming(data=bytes2file_response(pairing_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/role_controller.py b/ruoyi-fastapi-backend/module_admin/controller/role_controller.py new file mode 100644 index 0000000..d4ab531 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/role_controller.py @@ -0,0 +1,283 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.data_scope import GetDataScope +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.dept_vo import DeptModel +from module_admin.entity.vo.role_vo import AddRoleModel, DeleteRoleModel, RoleModel, RolePageQueryModel +from module_admin.entity.vo.user_vo import CrudUserRoleModel, CurrentUserModel, UserRolePageQueryModel +from module_admin.service.dept_service import DeptService +from module_admin.service.login_service import LoginService +from module_admin.service.role_service import RoleService +from module_admin.service.user_service import UserService +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +roleController = APIRouter(prefix='/system/role', dependencies=[Depends(LoginService.get_current_user)]) + + +@roleController.get('/deptTree/{role_id}', dependencies=[Depends(CheckUserInterfaceAuth('system:role:query'))]) +async def get_system_role_dept_tree( + request: Request, + role_id: int, + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + dept_query_result = await DeptService.get_dept_tree_services(query_db, DeptModel(**{}), data_scope_sql) + role_dept_query_result = await RoleService.get_role_dept_tree_services(query_db, role_id) + role_dept_query_result.depts = dept_query_result + logger.info('获取成功') + + return ResponseUtil.success(model_content=role_dept_query_result) + + +@roleController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:role:list'))] +) +async def get_system_role_list( + request: Request, + role_page_query: RolePageQueryModel = Depends(RolePageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + role_page_query_result = await RoleService.get_role_list_services( + query_db, role_page_query, data_scope_sql, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=role_page_query_result) + + +@roleController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:role:add'))]) +@ValidateFields(validate_model='add_role') +@Log(title='角色管理', business_type=BusinessType.INSERT) +async def add_system_role( + request: Request, + add_role: AddRoleModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_role.create_by = current_user.user.user_name + add_role.create_time = datetime.now() + add_role.update_by = current_user.user.user_name + add_role.update_time = datetime.now() + add_role_result = await RoleService.add_role_services(query_db, add_role) + logger.info(add_role_result.message) + + return ResponseUtil.success(msg=add_role_result.message) + + +@roleController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) +@ValidateFields(validate_model='edit_role') +@Log(title='角色管理', business_type=BusinessType.UPDATE) +async def edit_system_role( + request: Request, + edit_role: AddRoleModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + await RoleService.check_role_allowed_services(edit_role) + if not current_user.user.admin: + await RoleService.check_role_data_scope_services(query_db, str(edit_role.role_id), data_scope_sql) + edit_role.update_by = current_user.user.user_name + edit_role.update_time = datetime.now() + edit_role_result = await RoleService.edit_role_services(query_db, edit_role) + logger.info(edit_role_result.message) + + return ResponseUtil.success(msg=edit_role_result.message) + + +@roleController.put('/dataScope', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) +@Log(title='角色管理', business_type=BusinessType.GRANT) +async def edit_system_role_datascope( + request: Request, + role_data_scope: AddRoleModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + await RoleService.check_role_allowed_services(role_data_scope) + if not current_user.user.admin: + await RoleService.check_role_data_scope_services(query_db, str(role_data_scope.role_id), data_scope_sql) + edit_role = AddRoleModel( + roleId=role_data_scope.role_id, + dataScope=role_data_scope.data_scope, + deptIds=role_data_scope.dept_ids, + deptCheckStrictly=role_data_scope.dept_check_strictly, + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + ) + role_data_scope_result = await RoleService.role_datascope_services(query_db, edit_role) + logger.info(role_data_scope_result.message) + + return ResponseUtil.success(msg=role_data_scope_result.message) + + +@roleController.delete('/{role_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:role:remove'))]) +@Log(title='角色管理', business_type=BusinessType.DELETE) +async def delete_system_role( + request: Request, + role_ids: str, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + role_id_list = role_ids.split(',') if role_ids else [] + if role_id_list: + for role_id in role_id_list: + await RoleService.check_role_allowed_services(RoleModel(roleId=int(role_id))) + if not current_user.user.admin: + await RoleService.check_role_data_scope_services(query_db, role_id, data_scope_sql) + delete_role = DeleteRoleModel(roleIds=role_ids, updateBy=current_user.user.user_name, updateTime=datetime.now()) + delete_role_result = await RoleService.delete_role_services(query_db, delete_role) + logger.info(delete_role_result.message) + + return ResponseUtil.success(msg=delete_role_result.message) + + +@roleController.get( + '/{role_id}', response_model=RoleModel, dependencies=[Depends(CheckUserInterfaceAuth('system:role:query'))] +) +async def query_detail_system_role( + request: Request, + role_id: int, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + if not current_user.user.admin: + await RoleService.check_role_data_scope_services(query_db, str(role_id), data_scope_sql) + role_detail_result = await RoleService.role_detail_services(query_db, role_id) + logger.info(f'获取role_id为{role_id}的信息成功') + + return ResponseUtil.success(data=role_detail_result.model_dump(by_alias=True)) + + +@roleController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:role:export'))]) +@Log(title='角色管理', business_type=BusinessType.EXPORT) +async def export_system_role_list( + request: Request, + role_page_query: RolePageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + # 获取全量数据 + role_query_result = await RoleService.get_role_list_services( + query_db, role_page_query, data_scope_sql, is_page=False + ) + role_export_result = await RoleService.export_role_list_services(role_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(role_export_result)) + + +@roleController.put('/changeStatus', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) +@Log(title='角色管理', business_type=BusinessType.UPDATE) +async def reset_system_role_status( + request: Request, + change_role: AddRoleModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + await RoleService.check_role_allowed_services(change_role) + if not current_user.user.admin: + await RoleService.check_role_data_scope_services(query_db, str(change_role.role_id), data_scope_sql) + edit_role = AddRoleModel( + roleId=change_role.role_id, + status=change_role.status, + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + type='status', + ) + edit_role_result = await RoleService.edit_role_services(query_db, edit_role) + logger.info(edit_role_result.message) + + return ResponseUtil.success(msg=edit_role_result.message) + + +@roleController.get( + '/authUser/allocatedList', + response_model=PageResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('system:role:list'))], +) +async def get_system_allocated_user_list( + request: Request, + user_role: UserRolePageQueryModel = Depends(UserRolePageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + role_user_allocated_page_query_result = await RoleService.get_role_user_allocated_list_services( + query_db, user_role, data_scope_sql, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=role_user_allocated_page_query_result) + + +@roleController.get( + '/authUser/unallocatedList', + response_model=PageResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('system:role:list'))], +) +async def get_system_unallocated_user_list( + request: Request, + user_role: UserRolePageQueryModel = Depends(UserRolePageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + role_user_unallocated_page_query_result = await RoleService.get_role_user_unallocated_list_services( + query_db, user_role, data_scope_sql, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=role_user_unallocated_page_query_result) + + +@roleController.put('/authUser/selectAll', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) +@Log(title='角色管理', business_type=BusinessType.GRANT) +async def add_system_role_user( + request: Request, + add_role_user: CrudUserRoleModel = Depends(CrudUserRoleModel.as_query), + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + if not current_user.user.admin: + await RoleService.check_role_data_scope_services(query_db, str(add_role_user.role_id), data_scope_sql) + add_role_user_result = await UserService.add_user_role_services(query_db, add_role_user) + logger.info(add_role_user_result.message) + + return ResponseUtil.success(msg=add_role_user_result.message) + + +@roleController.put('/authUser/cancel', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) +@Log(title='角色管理', business_type=BusinessType.GRANT) +async def cancel_system_role_user( + request: Request, cancel_user_role: CrudUserRoleModel, query_db: AsyncSession = Depends(get_db) +): + cancel_user_role_result = await UserService.delete_user_role_services(query_db, cancel_user_role) + logger.info(cancel_user_role_result.message) + + return ResponseUtil.success(msg=cancel_user_role_result.message) + + +@roleController.put('/authUser/cancelAll', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) +@Log(title='角色管理', business_type=BusinessType.GRANT) +async def batch_cancel_system_role_user( + request: Request, + batch_cancel_user_role: CrudUserRoleModel = Depends(CrudUserRoleModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + batch_cancel_user_role_result = await UserService.delete_user_role_services(query_db, batch_cancel_user_role) + logger.info(batch_cancel_user_role_result.message) + + return ResponseUtil.success(msg=batch_cancel_user_role_result.message) diff --git a/ruoyi-fastapi-backend/module_admin/controller/scheduler_controller.py b/ruoyi-fastapi-backend/module_admin/controller/scheduler_controller.py new file mode 100644 index 0000000..229d923 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/scheduler_controller.py @@ -0,0 +1,79 @@ +""" + 系统定时任务 +""" + +from config.get_scheduler import scheduler +from apscheduler.triggers.cron import CronTrigger +from apscheduler.triggers.interval import IntervalTrigger +from module_admin.service.haikang_service import HaiKangService +from module_admin.service.compreface_service import ComprefaceService +from utils.log_util import logger + + +# 定时获取访客图片并上传compreface +async def download_visitor_image_and_upload_compreface(): + """ + 定时任务:获取海康访客图片并上传到compreface进行人脸识别 + 同时删除已过期的访客人脸数据 + 执行频率:每2分钟一次 + """ + try: + logger.info('开始执行定时任务:获取访客图片并上传compreface') + + # 获取所有有效状态的访客图片(visitorStatus=1表示正常状态) + visitor_name_list = await HaiKangService.get_all_visitor_pictures(visitorStatus=1) + logger.info(f'获取到 {len(visitor_name_list)} 张有效访客图片') + + # 如果有新的访客图片,将访客图片上传到compreface + if visitor_name_list: + result = await ComprefaceService.face_addition_batch_service(visitor_name_list) + logger.info(f'访客图片上传compreface结果: {result}') + else: + logger.info('没有新的访客图片需要处理') + + # 删除过期的访客人脸数据 + # 从visitor_name_list中提取所有访客的名字 + active_visitor_names = [name for _, name in visitor_name_list] if visitor_name_list else [] + + logger.info(f'开始删除过期访客人脸,当前有效访客数量: {len(active_visitor_names)}') + delete_result = await ComprefaceService.delete_expired_visitor_faces_service(active_visitor_names) + + if delete_result.get('error'): + logger.error(f'删除过期访客失败: {delete_result.get("error")}') + else: + logger.info( + f'删除过期访客完成 - 成功删除: {delete_result.get("deleted_count")} 个, ' + f'失败: {delete_result.get("failed_count")} 个' + ) + if delete_result.get('deleted_subjects'): + logger.info(f'已删除的访客: {", ".join(delete_result.get("deleted_subjects"))}') + if delete_result.get('failed_subjects'): + logger.warning(f'删除失败的访客: {delete_result.get("failed_subjects")}') + + logger.info('定时任务执行完成:获取访客图片并上传compreface') + except Exception as e: + logger.error(f'定时任务执行失败:获取访客图片并上传compreface - {str(e)}', exc_info=True) + + +# 初始化定时任务 +def init_scheduled_tasks(): + """ + 初始化所有定时任务 + 在应用启动时调用此方法 + """ + # 添加访客图片上传任务,每2分钟执行一次 + scheduler.add_job( + func=download_visitor_image_and_upload_compreface, + trigger=IntervalTrigger(minutes=2), + id='download_visitor_image_task', + name='定时获取访客图片并上传compreface', + replace_existing=True, + max_instances=1, # 同一时间只允许一个实例运行 + ) + logger.info('定时任务已添加:download_visitor_image_and_upload_compreface (每2分钟执行一次)') + + + + + + diff --git a/ruoyi-fastapi-backend/module_admin/controller/server_controller.py b/ruoyi-fastapi-backend/module_admin/controller/server_controller.py new file mode 100644 index 0000000..f63fdf8 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/server_controller.py @@ -0,0 +1,21 @@ +from fastapi import APIRouter, Depends, Request +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.server_vo import ServerMonitorModel +from module_admin.service.login_service import LoginService +from module_admin.service.server_service import ServerService +from utils.response_util import ResponseUtil +from utils.log_util import logger + + +serverController = APIRouter(prefix='/monitor/server', dependencies=[Depends(LoginService.get_current_user)]) + + +@serverController.get( + '', response_model=ServerMonitorModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:server:list'))] +) +async def get_monitor_server_info(request: Request): + # 获取全量数据 + server_info_query_result = await ServerService.get_server_monitor_info() + logger.info('获取成功') + + return ResponseUtil.success(data=server_info_query_result) diff --git a/ruoyi-fastapi-backend/module_admin/controller/sys_alert_controller.py b/ruoyi-fastapi-backend/module_admin/controller/sys_alert_controller.py new file mode 100644 index 0000000..bacc616 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/sys_alert_controller.py @@ -0,0 +1,106 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.sys_alert_service import Sys_alertService +from module_admin.entity.vo.sys_alert_vo import DeleteSys_alertModel, Sys_alertModel, Sys_alertPageQueryModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +sys_alertController = APIRouter(prefix='/system/sys_alert', dependencies=[Depends(LoginService.get_current_user)]) + + +# @sys_alertController.get( +# '/list', response_model=PageResponseModel +# # , dependencies=[Depends(CheckUserInterfaceAuth('system:sys_alert:list'))] +# ) +# async def get_system_sys_alert_list( +# request: Request, +# sys_alert_page_query: Sys_alertPageQueryModel = Depends(Sys_alertPageQueryModel.as_query), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取分页数据 +# sys_alert_page_query_result = await Sys_alertService.get_sys_alert_list_services(query_db, sys_alert_page_query, is_page=True) +# logger.info('获取成功') + +# return ResponseUtil.success(model_content=sys_alert_page_query_result) + + +# @sys_alertController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:sys_alert:add'))]) +# @ValidateFields(validate_model='add_sys_alert') +# @Log(title='系统告警', business_type=BusinessType.INSERT) +# async def add_system_sys_alert( +# request: Request, +# add_sys_alert: Sys_alertModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# add_sys_alert.create_time = datetime.now() +# add_sys_alert.create_by = current_user.user.user_name +# add_sys_alert.update_time = datetime.now() +# add_sys_alert.update_by = current_user.user.user_name +# add_sys_alert_result = await Sys_alertService.add_sys_alert_services(query_db, add_sys_alert) +# logger.info(add_sys_alert_result.message) + +# return ResponseUtil.success(msg=add_sys_alert_result.message) + + +# @sys_alertController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:sys_alert:edit'))]) +# @ValidateFields(validate_model='edit_sys_alert') +# @Log(title='系统告警', business_type=BusinessType.UPDATE) +# async def edit_system_sys_alert( +# request: Request, +# edit_sys_alert: Sys_alertModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# edit_sys_alert.update_by = current_user.user.user_name +# edit_sys_alert.update_time = datetime.now() +# edit_sys_alert_result = await Sys_alertService.edit_sys_alert_services(query_db, edit_sys_alert) +# logger.info(edit_sys_alert_result.message) + +# return ResponseUtil.success(msg=edit_sys_alert_result.message) + + +# @sys_alertController.delete('/{ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:sys_alert:remove'))]) +# @Log(title='系统告警', business_type=BusinessType.DELETE) +# async def delete_system_sys_alert(request: Request, ids: str, query_db: AsyncSession = Depends(get_db)): +# delete_sys_alert = DeleteSys_alertModel(ids=ids) +# delete_sys_alert_result = await Sys_alertService.delete_sys_alert_services(query_db, delete_sys_alert) +# logger.info(delete_sys_alert_result.message) + +# return ResponseUtil.success(msg=delete_sys_alert_result.message) + + +# @sys_alertController.get( +# '/{id}', response_model=Sys_alertModel, dependencies=[Depends(CheckUserInterfaceAuth('system:sys_alert:query'))] +# ) +# async def query_detail_system_sys_alert(request: Request, id: int, query_db: AsyncSession = Depends(get_db)): +# sys_alert_detail_result = await Sys_alertService.sys_alert_detail_services(query_db, id) +# logger.info(f'获取id为{id}的信息成功') + +# return ResponseUtil.success(data=sys_alert_detail_result) + + +# @sys_alertController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:sys_alert:export'))]) +# @Log(title='系统告警', business_type=BusinessType.EXPORT) +# async def export_system_sys_alert_list( +# request: Request, +# sys_alert_page_query: Sys_alertPageQueryModel = Form(), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取全量数据 +# sys_alert_query_result = await Sys_alertService.get_sys_alert_list_services(query_db, sys_alert_page_query, is_page=False) +# sys_alert_export_result = await Sys_alertService.export_sys_alert_list_services(sys_alert_query_result) +# logger.info('导出成功') + +# return ResponseUtil.streaming(data=bytes2file_response(sys_alert_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/sys_statistics_controller.py b/ruoyi-fastapi-backend/module_admin/controller/sys_statistics_controller.py new file mode 100644 index 0000000..ef29d5a --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/sys_statistics_controller.py @@ -0,0 +1,240 @@ +from datetime import datetime, date +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.identification_record_service import Identification_recordService +from module_admin.service.sys_statistics_service import Sys_statisticsService +from module_admin.entity.vo.sys_statistics_vo import DeleteSys_statisticsModel, Sys_statisticsModel, Sys_statisticsPageQueryModel +from module_admin.entity.vo.identification_record_vo import Identification_recordModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +sys_statisticsController = APIRouter(prefix='/system/sys_statistics', dependencies=[Depends(LoginService.get_current_user)]) + +# 添加识别记录 +@sys_statisticsController.post( + '/add_identification_record' +) +async def add_system_identification_record( + request: Request, + add_identification_record: Identification_recordModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + try: + # 设置创建时间和创建者 + add_identification_record.create_time = datetime.now() + add_identification_record.create_by = current_user.user.user_name + + # 调用服务层添加识别记录 + result = await Identification_recordService.add_identification_record_services(query_db, add_identification_record) + + if result.is_success: + logger.info(f'添加识别记录成功: {add_identification_record.person_name}') + return ResponseUtil.success(msg=result.message) + else: + logger.error(f'添加识别记录失败: {result.message}') + return ResponseUtil.error(msg=result.message) + except Exception as e: + logger.error(f'添加识别记录异常: {str(e)}') + return ResponseUtil.error(msg=f'添加识别记录失败: {str(e)}') + + +# 获取统计数据 +@sys_statisticsController.get( + '/get_data' +) +async def get_statistics_data( + request: Request, + query_db: AsyncSession = Depends(get_db), +): + data = await Sys_statisticsService.get_statistics_data_services(query_db) + return ResponseUtil.success(data=data) + +# 获取访问趋势 +@sys_statisticsController.get( + '/get_visitor_count/{day}' +) +async def get_visitor_count( + request: Request, + day: int, + query_db: AsyncSession = Depends(get_db), +): + data = await Sys_statisticsService.get_visitor_count_services(query_db, day) + return ResponseUtil.success(data=data) + +# 获取门禁识别成功率 +@sys_statisticsController.get( + '/access_control_success_rate/{day}' +) +async def get_access_control_success_rate( + request: Request, + day: int, + query_db: AsyncSession = Depends(get_db), +): + data = await Sys_statisticsService.get_access_control_success_rate_services(query_db, day) + return ResponseUtil.success(data=data) + +# 大模型调用次数加1 +@sys_statisticsController.get( + '/add_model_call_count' +) +async def add_model_call_count( + request: Request, + query_db: AsyncSession = Depends(get_db), + user: CurrentUserModel = Depends(LoginService.get_current_user), +): + data = await Sys_statisticsService.add_model_call_count_services(query_db, user.user.user_name) + if data.is_success: + logger.info('大模型调用次数加1成功') + return ResponseUtil.success(msg=data.message) + else: + logger.error('大模型调用次数加1失败') + return ResponseUtil.error(msg=data.message) + +# 门禁通行量次数加1 +@sys_statisticsController.get( + '/add_door_pass_count' +) +async def add_door_pass_count( + request: Request, + query_db: AsyncSession = Depends(get_db), + user: CurrentUserModel = Depends(LoginService.get_current_user), +): + data = await Sys_statisticsService.add_door_pass_count_services(query_db, user.user.user_name) + if data.is_success: + logger.info('门禁通行量次数加1成功') + return ResponseUtil.success(msg=data.message) + else: + logger.error('门禁通行量次数加1失败') + return ResponseUtil.error(msg=data.message) +# 访客引导次数加1 +@sys_statisticsController.get( + '/add_visitor_guide_count' +) +async def add_visitor_guide_count( + request: Request, + query_db: AsyncSession = Depends(get_db), + user: CurrentUserModel = Depends(LoginService.get_current_user), +): + data = await Sys_statisticsService.add_visitor_guide_count_services(query_db, user.user.user_name) + if data.is_success: + logger.info('访客引导次数加1成功') + return ResponseUtil.success(msg=data.message) + else: + logger.error('访客引导次数加1失败') + return ResponseUtil.error(msg=data.message) + +# 展厅讲解次数加1 +@sys_statisticsController.get( + '/add_explain_count' +) +async def add_explain_count( + request: Request, + query_db: AsyncSession = Depends(get_db), + user: CurrentUserModel = Depends(LoginService.get_current_user), +): + data = await Sys_statisticsService.add_explain_count_services(query_db, user.user.user_name) + if data.is_success: + logger.info('展厅讲解次数加1成功') + return ResponseUtil.success(msg=data.message) + else: + logger.error('展厅讲解次数加1失败') + return ResponseUtil.error(msg=data.message) + +# @sys_statisticsController.get( +# '/list', response_model=PageResponseModel +# # , dependencies=[Depends(CheckUserInterfaceAuth('system:sys_statistics:list'))] +# ) +# async def get_system_sys_statistics_list( +# request: Request, +# sys_statistics_page_query: Sys_statisticsPageQueryModel = Depends(Sys_statisticsPageQueryModel.as_query), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取分页数据 +# sys_statistics_page_query_result = await Sys_statisticsService.get_sys_statistics_list_services(query_db, sys_statistics_page_query, is_page=True) +# logger.info('获取成功') +# +# return ResponseUtil.success(model_content=sys_statistics_page_query_result) + + +# @sys_statisticsController.post('' +# , dependencies=[Depends(CheckUserInterfaceAuth('system:sys_statistics:add'))] +# ) +# @ValidateFields(validate_model='add_sys_statistics') +# @Log(title='系统统计数据', business_type=BusinessType.INSERT) +# async def add_system_sys_statistics( +# request: Request, +# add_sys_statistics: Sys_statisticsModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# add_sys_statistics.create_time = datetime.now() +# add_sys_statistics.create_by = current_user.user.user_name +# add_sys_statistics.update_time = datetime.now() +# add_sys_statistics.update_by = current_user.user.user_name +# add_sys_statistics_result = await Sys_statisticsService.add_sys_statistics_services(query_db, add_sys_statistics) +# logger.info(add_sys_statistics_result.message) +# +# return ResponseUtil.success(msg=add_sys_statistics_result.message) +# +# +# @sys_statisticsController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:sys_statistics:edit'))]) +# @ValidateFields(validate_model='edit_sys_statistics') +# @Log(title='系统统计数据', business_type=BusinessType.UPDATE) +# async def edit_system_sys_statistics( +# request: Request, +# edit_sys_statistics: Sys_statisticsModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# edit_sys_statistics.update_by = current_user.user.user_name +# edit_sys_statistics.update_time = datetime.now() +# edit_sys_statistics_result = await Sys_statisticsService.edit_sys_statistics_services(query_db, edit_sys_statistics) +# logger.info(edit_sys_statistics_result.message) +# +# return ResponseUtil.success(msg=edit_sys_statistics_result.message) + + +# @sys_statisticsController.delete('/{statistic_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:sys_statistics:remove'))]) +# @Log(title='系统统计数据', business_type=BusinessType.DELETE) +# async def delete_system_sys_statistics(request: Request, statistic_ids: str, query_db: AsyncSession = Depends(get_db)): +# delete_sys_statistics = DeleteSys_statisticsModel(statisticIds=statistic_ids) +# delete_sys_statistics_result = await Sys_statisticsService.delete_sys_statistics_services(query_db, delete_sys_statistics) +# logger.info(delete_sys_statistics_result.message) +# +# return ResponseUtil.success(msg=delete_sys_statistics_result.message) +# +# +# @sys_statisticsController.get( +# '/{statistic_id}', response_model=Sys_statisticsModel, dependencies=[Depends(CheckUserInterfaceAuth('system:sys_statistics:query'))] +# ) +# async def query_detail_system_sys_statistics(request: Request, statistic_id: int, query_db: AsyncSession = Depends(get_db)): +# sys_statistics_detail_result = await Sys_statisticsService.sys_statistics_detail_services(query_db, statistic_id) +# logger.info(f'获取statistic_id为{statistic_id}的信息成功') +# +# return ResponseUtil.success(data=sys_statistics_detail_result) +# +# +# @sys_statisticsController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:sys_statistics:export'))]) +# @Log(title='系统统计数据', business_type=BusinessType.EXPORT) +# async def export_system_sys_statistics_list( +# request: Request, +# sys_statistics_page_query: Sys_statisticsPageQueryModel = Form(), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取全量数据 +# sys_statistics_query_result = await Sys_statisticsService.get_sys_statistics_list_services(query_db, sys_statistics_page_query, is_page=False) +# sys_statistics_export_result = await Sys_statisticsService.export_sys_statistics_list_services(sys_statistics_query_result) +# logger.info('导出成功') +# +# return ResponseUtil.streaming(data=bytes2file_response(sys_statistics_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/test_controller.py b/ruoyi-fastapi-backend/module_admin/controller/test_controller.py new file mode 100644 index 0000000..a5b73d0 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/test_controller.py @@ -0,0 +1,58 @@ +from fastapi import APIRouter, Body, Depends, Form, Request +from module_admin.service.login_service import LoginService +from module_admin.entity.vo.test_vo import TestUserName, TestPostForm, TestUserNamePage +from typing import List, Optional +from module_admin.service.test_service import TestService +from config.get_db import get_db +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.annotation.log_annotation import Log +from config.enums import BusinessType, RedisInitKeyConfig +from utils.response_util import ResponseUtil +from utils.log_util import logger + + +testController = APIRouter(prefix='/test') + +@testController.get('/hello', response_model= List[TestUserName] + # ,dependencies=[Depends(LoginService.get_current_user)] + ) +async def hello(request: Request, + name: Optional[str] = None, + + query_db: AsyncSession = Depends(get_db), ): + print("名称", name) + user_name = await TestService.get_user_name(query_db,) + return user_name + +@testController.post('/hello_post_json', response_model=str + ,dependencies=[Depends(LoginService.get_current_user)] + ) +@Log(title='测试Log', business_type=BusinessType.OTHER, log_type='operation') +async def hello_post_json( + request: Request, + login_info: TestUserNamePage, + query_db: AsyncSession = Depends(get_db) + +): + print(login_info.user_name) + print(login_info.page_num) + print(login_info.page_size) + # print(login_info.password) + + user_name = await TestService.get_user_name(query_db, login_info) + + logger.info("测试成功") + + return ResponseUtil.success(model_content=user_name) + +@Log(title='测试post_form_data', business_type=BusinessType.OTHER, log_type='post') +@testController.post('/hello_post_form_data', response_model=str) +async def hello_post_form_data( + request: Request, + user_name: Optional[str] = Form(), + password: Optional[str] = Form() +): + print("*"*100) + print(user_name) + print(password) + return "success" \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_admin/controller/user_controller.py b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py new file mode 100644 index 0000000..ed7cf7a --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py @@ -0,0 +1,399 @@ +import os +from datetime import datetime +from fastapi import APIRouter, Depends, File, Form, Query, Request, UploadFile +from sqlalchemy.ext.asyncio import AsyncSession +from typing import Literal, Optional, Union +from pydantic_validation_decorator import ValidateFields +from config.get_db import get_db +from config.enums import BusinessType +from config.env import UploadConfig +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.data_scope import GetDataScope +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.dept_vo import DeptModel +from module_admin.entity.vo.user_vo import ( + AddUserModel, + CrudUserRoleModel, + CurrentUserModel, + DeleteUserModel, + EditUserModel, + ResetPasswordModel, + ResetUserModel, + UserDetailModel, + UserInfoModel, + UserModel, + UserPageQueryModel, + UserProfileModel, + UserRoleQueryModel, + UserRoleResponseModel, +) +from module_admin.service.login_service import LoginService +from module_admin.service.user_service import UserService +from module_admin.service.role_service import RoleService +from module_admin.service.dept_service import DeptService +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.pwd_util import PwdUtil +from utils.response_util import ResponseUtil +from utils.upload_util import UploadUtil + + +userController = APIRouter(prefix='/system/user', dependencies=[Depends(LoginService.get_current_user)]) + + +@userController.get('/deptTree', dependencies=[Depends(CheckUserInterfaceAuth('system:user:list'))]) +async def get_system_dept_tree( + request: Request, query_db: AsyncSession = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysDept')) +): + dept_query_result = await DeptService.get_dept_tree_services(query_db, DeptModel(**{}), data_scope_sql) + logger.info('获取成功') + + return ResponseUtil.success(data=dept_query_result) + + +@userController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:user:list'))] +) +async def get_system_user_list( + request: Request, + user_page_query: UserPageQueryModel = Depends(UserPageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + # 获取分页数据 + user_page_query_result = await UserService.get_user_list_services( + query_db, user_page_query, data_scope_sql, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=user_page_query_result) + + +@userController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:user:add'))]) +@ValidateFields(validate_model='add_user') +@Log(title='用户管理', business_type=BusinessType.INSERT) +async def add_system_user( + request: Request, + add_user: AddUserModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + dept_data_scope_sql: str = Depends(GetDataScope('SysDept')), + role_data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + if not current_user.user.admin: + await DeptService.check_dept_data_scope_services(query_db, add_user.dept_id, dept_data_scope_sql) + await RoleService.check_role_data_scope_services( + query_db, ','.join([str(item) for item in add_user.role_ids]), role_data_scope_sql + ) + add_user.password = PwdUtil.get_password_hash(add_user.password) + add_user.create_by = current_user.user.user_name + add_user.create_time = datetime.now() + add_user.update_by = current_user.user.user_name + add_user.update_time = datetime.now() + add_user_result = await UserService.add_user_services(query_db, add_user) + logger.info(add_user_result.message) + + return ResponseUtil.success(msg=add_user_result.message) + + +@userController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:user:edit'))]) +@ValidateFields(validate_model='edit_user') +@Log(title='用户管理', business_type=BusinessType.UPDATE) +async def edit_system_user( + request: Request, + edit_user: EditUserModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + user_data_scope_sql: str = Depends(GetDataScope('SysUser')), + dept_data_scope_sql: str = Depends(GetDataScope('SysDept')), + role_data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + await UserService.check_user_allowed_services(edit_user) + if not current_user.user.admin: + await UserService.check_user_data_scope_services(query_db, edit_user.user_id, user_data_scope_sql) + await DeptService.check_dept_data_scope_services(query_db, edit_user.dept_id, dept_data_scope_sql) + await RoleService.check_role_data_scope_services( + query_db, ','.join([str(item) for item in edit_user.role_ids]), role_data_scope_sql + ) + edit_user.update_by = current_user.user.user_name + edit_user.update_time = datetime.now() + edit_user_result = await UserService.edit_user_services(query_db, edit_user) + logger.info(edit_user_result.message) + + return ResponseUtil.success(msg=edit_user_result.message) + + +@userController.delete('/{user_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:user:remove'))]) +@Log(title='用户管理', business_type=BusinessType.DELETE) +async def delete_system_user( + request: Request, + user_ids: str, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + user_id_list = user_ids.split(',') if user_ids else [] + if user_id_list: + if current_user.user.user_id in list(map(int, user_id_list)): + logger.warning('当前登录用户不能删除') + + return ResponseUtil.failure(msg='当前登录用户不能删除') + for user_id in user_id_list: + await UserService.check_user_allowed_services(UserModel(userId=int(user_id))) + if not current_user.user.admin: + await UserService.check_user_data_scope_services(query_db, int(user_id), data_scope_sql) + delete_user = DeleteUserModel(userIds=user_ids, updateBy=current_user.user.user_name, updateTime=datetime.now()) + delete_user_result = await UserService.delete_user_services(query_db, delete_user) + logger.info(delete_user_result.message) + + return ResponseUtil.success(msg=delete_user_result.message) + + +@userController.put('/resetPwd', dependencies=[Depends(CheckUserInterfaceAuth('system:user:resetPwd'))]) +@Log(title='用户管理', business_type=BusinessType.UPDATE) +async def reset_system_user_pwd( + request: Request, + reset_user: EditUserModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + await UserService.check_user_allowed_services(reset_user) + if not current_user.user.admin: + await UserService.check_user_data_scope_services(query_db, reset_user.user_id, data_scope_sql) + edit_user = EditUserModel( + userId=reset_user.user_id, + password=PwdUtil.get_password_hash(reset_user.password), + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + type='pwd', + ) + edit_user_result = await UserService.edit_user_services(query_db, edit_user) + logger.info(edit_user_result.message) + + return ResponseUtil.success(msg=edit_user_result.message) + + +@userController.put('/changeStatus', dependencies=[Depends(CheckUserInterfaceAuth('system:user:edit'))]) +@Log(title='用户管理', business_type=BusinessType.UPDATE) +async def change_system_user_status( + request: Request, + change_user: EditUserModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + await UserService.check_user_allowed_services(change_user) + if not current_user.user.admin: + await UserService.check_user_data_scope_services(query_db, change_user.user_id, data_scope_sql) + edit_user = EditUserModel( + userId=change_user.user_id, + status=change_user.status, + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + type='status', + ) + edit_user_result = await UserService.edit_user_services(query_db, edit_user) + logger.info(edit_user_result.message) + + return ResponseUtil.success(msg=edit_user_result.message) + + +@userController.get('/profile', response_model=UserProfileModel) +async def query_detail_system_user_profile( + request: Request, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + profile_user_result = await UserService.user_profile_services(query_db, current_user.user.user_id) + logger.info(f'获取user_id为{current_user.user.user_id}的信息成功') + + return ResponseUtil.success(model_content=profile_user_result) + + +@userController.get( + '/{user_id}', response_model=UserDetailModel, dependencies=[Depends(CheckUserInterfaceAuth('system:user:query'))] +) +@userController.get( + '/', response_model=UserDetailModel, dependencies=[Depends(CheckUserInterfaceAuth('system:user:query'))] +) +async def query_detail_system_user( + request: Request, + user_id: Optional[Union[int, Literal['']]] = '', + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + if user_id and not current_user.user.admin: + await UserService.check_user_data_scope_services(query_db, user_id, data_scope_sql) + detail_user_result = await UserService.user_detail_services(query_db, user_id) + logger.info(f'获取user_id为{user_id}的信息成功') + + return ResponseUtil.success(model_content=detail_user_result) + + +@userController.post('/profile/avatar') +@Log(title='个人信息', business_type=BusinessType.UPDATE) +async def change_system_user_profile_avatar( + request: Request, + avatarfile: bytes = File(), + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + if avatarfile: + relative_path = ( + f'avatar/{datetime.now().strftime("%Y")}/{datetime.now().strftime("%m")}/{datetime.now().strftime("%d")}' + ) + dir_path = os.path.join(UploadConfig.UPLOAD_PATH, relative_path) + try: + os.makedirs(dir_path) + except FileExistsError: + pass + avatar_name = f'avatar_{datetime.now().strftime("%Y%m%d%H%M%S")}{UploadConfig.UPLOAD_MACHINE}{UploadUtil.generate_random_number()}.png' + avatar_path = os.path.join(dir_path, avatar_name) + with open(avatar_path, 'wb') as f: + f.write(avatarfile) + edit_user = EditUserModel( + userId=current_user.user.user_id, + avatar=f'{UploadConfig.UPLOAD_PREFIX}/{relative_path}/{avatar_name}', + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + type='avatar', + ) + edit_user_result = await UserService.edit_user_services(query_db, edit_user) + logger.info(edit_user_result.message) + + return ResponseUtil.success(dict_content={'imgUrl': edit_user.avatar}, msg=edit_user_result.message) + return ResponseUtil.failure(msg='上传图片异常,请联系管理员') + + +@userController.put('/profile') +@Log(title='个人信息', business_type=BusinessType.UPDATE) +async def change_system_user_profile_info( + request: Request, + user_info: UserInfoModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_user = EditUserModel( + **user_info.model_dump(exclude_unset=True, by_alias=True, exclude={'role_ids', 'post_ids'}), + userId=current_user.user.user_id, + userName=current_user.user.user_name, + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + roleIds=current_user.user.role_ids.split(',') if current_user.user.role_ids else [], + postIds=current_user.user.post_ids.split(',') if current_user.user.post_ids else [], + role=current_user.user.role, + ) + edit_user_result = await UserService.edit_user_services(query_db, edit_user) + logger.info(edit_user_result.message) + + return ResponseUtil.success(msg=edit_user_result.message) + + +@userController.put('/profile/updatePwd') +@Log(title='个人信息', business_type=BusinessType.UPDATE) +async def reset_system_user_password( + request: Request, + reset_password: ResetPasswordModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + reset_user = ResetUserModel( + userId=current_user.user.user_id, + oldPassword=reset_password.old_password, + password=reset_password.new_password, + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + ) + reset_user_result = await UserService.reset_user_services(query_db, reset_user) + logger.info(reset_user_result.message) + + return ResponseUtil.success(msg=reset_user_result.message) + + +@userController.post('/importData', dependencies=[Depends(CheckUserInterfaceAuth('system:user:import'))]) +@Log(title='用户管理', business_type=BusinessType.IMPORT) +async def batch_import_system_user( + request: Request, + file: UploadFile = File(...), + update_support: bool = Query(alias='updateSupport'), + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + user_data_scope_sql: str = Depends(GetDataScope('SysUser')), + dept_data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + batch_import_result = await UserService.batch_import_user_services( + request, query_db, file, update_support, current_user, user_data_scope_sql, dept_data_scope_sql + ) + logger.info(batch_import_result.message) + + return ResponseUtil.success(msg=batch_import_result.message) + + +@userController.post('/importTemplate', dependencies=[Depends(CheckUserInterfaceAuth('system:user:import'))]) +async def export_system_user_template(request: Request, query_db: AsyncSession = Depends(get_db)): + user_import_template_result = await UserService.get_user_import_template_services() + logger.info('获取成功') + + return ResponseUtil.streaming(data=bytes2file_response(user_import_template_result)) + + +@userController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:user:export'))]) +@Log(title='用户管理', business_type=BusinessType.EXPORT) +async def export_system_user_list( + request: Request, + user_page_query: UserPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + # 获取全量数据 + user_query_result = await UserService.get_user_list_services( + query_db, user_page_query, data_scope_sql, is_page=False + ) + user_export_result = await UserService.export_user_list_services(user_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(user_export_result)) + + +@userController.get( + '/authRole/{user_id}', + response_model=UserRoleResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('system:user:query'))], +) +async def get_system_allocated_role_list(request: Request, user_id: int, query_db: AsyncSession = Depends(get_db)): + user_role_query = UserRoleQueryModel(userId=user_id) + user_role_allocated_query_result = await UserService.get_user_role_allocated_list_services( + query_db, user_role_query + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=user_role_allocated_query_result) + + +@userController.put( + '/authRole', + response_model=UserRoleResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('system:user:edit'))], +) +@Log(title='用户管理', business_type=BusinessType.GRANT) +async def update_system_role_user( + request: Request, + user_id: int = Query(alias='userId'), + role_ids: str = Query(alias='roleIds'), + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + user_data_scope_sql: str = Depends(GetDataScope('SysUser')), + role_data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + if not current_user.user.admin: + await UserService.check_user_data_scope_services(query_db, user_id, user_data_scope_sql) + await RoleService.check_role_data_scope_services(query_db, role_ids, role_data_scope_sql) + add_user_role_result = await UserService.add_user_role_services( + query_db, CrudUserRoleModel(userId=user_id, roleIds=role_ids) + ) + logger.info(add_user_role_result.message) + + return ResponseUtil.success(msg=add_user_role_result.message) diff --git a/ruoyi-fastapi-backend/module_admin/controller/visitor_type_controller.py b/ruoyi-fastapi-backend/module_admin/controller/visitor_type_controller.py new file mode 100644 index 0000000..d73c855 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/visitor_type_controller.py @@ -0,0 +1,105 @@ +# from datetime import datetime +# from fastapi import APIRouter, Depends, Form, Request +# from pydantic_validation_decorator import ValidateFields +# from sqlalchemy.ext.asyncio import AsyncSession +# from config.enums import BusinessType +# from config.get_db import get_db +# from module_admin.annotation.log_annotation import Log +# from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +# from module_admin.entity.vo.user_vo import CurrentUserModel +# from module_admin.service.login_service import LoginService +# from module_admin.service.visitor_type_service import Visitor_typeService +# from module_admin.entity.vo.visitor_type_vo import DeleteVisitor_typeModel, Visitor_typeModel, Visitor_typePageQueryModel +# from utils.common_util import bytes2file_response +# from utils.log_util import logger +# from utils.page_util import PageResponseModel +# from utils.response_util import ResponseUtil +# +# +# visitor_typeController = APIRouter(prefix='/system/visitor_type', dependencies=[Depends(LoginService.get_current_user)]) +# +# +# @visitor_typeController.get( +# '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:visitor_type:list'))] +# ) +# async def get_system_visitor_type_list( +# request: Request, +# visitor_type_page_query: Visitor_typePageQueryModel = Depends(Visitor_typePageQueryModel.as_query), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取分页数据 +# visitor_type_page_query_result = await Visitor_typeService.get_visitor_type_list_services(query_db, visitor_type_page_query, is_page=True) +# logger.info('获取成功') +# +# return ResponseUtil.success(model_content=visitor_type_page_query_result) +# +# +# @visitor_typeController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:visitor_type:add'))]) +# @ValidateFields(validate_model='add_visitor_type') +# @Log(title='访客类型', business_type=BusinessType.INSERT) +# async def add_system_visitor_type( +# request: Request, +# add_visitor_type: Visitor_typeModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# add_visitor_type.create_time = datetime.now() +# add_visitor_type.create_by = current_user.user.user_name +# add_visitor_type.update_time = datetime.now() +# add_visitor_type.update_by = current_user.user.user_name +# add_visitor_type_result = await Visitor_typeService.add_visitor_type_services(query_db, add_visitor_type) +# logger.info(add_visitor_type_result.message) +# +# return ResponseUtil.success(msg=add_visitor_type_result.message) +# +# +# @visitor_typeController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:visitor_type:edit'))]) +# @ValidateFields(validate_model='edit_visitor_type') +# @Log(title='访客类型', business_type=BusinessType.UPDATE) +# async def edit_system_visitor_type( +# request: Request, +# edit_visitor_type: Visitor_typeModel, +# query_db: AsyncSession = Depends(get_db), +# current_user: CurrentUserModel = Depends(LoginService.get_current_user), +# ): +# edit_visitor_type.update_by = current_user.user.user_name +# edit_visitor_type.update_time = datetime.now() +# edit_visitor_type_result = await Visitor_typeService.edit_visitor_type_services(query_db, edit_visitor_type) +# logger.info(edit_visitor_type_result.message) +# +# return ResponseUtil.success(msg=edit_visitor_type_result.message) +# +# +# @visitor_typeController.delete('/{type_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:visitor_type:remove'))]) +# @Log(title='访客类型', business_type=BusinessType.DELETE) +# async def delete_system_visitor_type(request: Request, type_ids: str, query_db: AsyncSession = Depends(get_db)): +# delete_visitor_type = DeleteVisitor_typeModel(typeIds=type_ids) +# delete_visitor_type_result = await Visitor_typeService.delete_visitor_type_services(query_db, delete_visitor_type) +# logger.info(delete_visitor_type_result.message) +# +# return ResponseUtil.success(msg=delete_visitor_type_result.message) +# +# +# @visitor_typeController.get( +# '/{type_id}', response_model=Visitor_typeModel, dependencies=[Depends(CheckUserInterfaceAuth('system:visitor_type:query'))] +# ) +# async def query_detail_system_visitor_type(request: Request, type_id: int, query_db: AsyncSession = Depends(get_db)): +# visitor_type_detail_result = await Visitor_typeService.visitor_type_detail_services(query_db, type_id) +# logger.info(f'获取type_id为{type_id}的信息成功') +# +# return ResponseUtil.success(data=visitor_type_detail_result) +# +# +# @visitor_typeController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:visitor_type:export'))]) +# @Log(title='访客类型', business_type=BusinessType.EXPORT) +# async def export_system_visitor_type_list( +# request: Request, +# visitor_type_page_query: Visitor_typePageQueryModel = Form(), +# query_db: AsyncSession = Depends(get_db), +# ): +# # 获取全量数据 +# visitor_type_query_result = await Visitor_typeService.get_visitor_type_list_services(query_db, visitor_type_page_query, is_page=False) +# visitor_type_export_result = await Visitor_typeService.export_visitor_type_list_services(visitor_type_query_result) +# logger.info('导出成功') +# +# return ResponseUtil.streaming(data=bytes2file_response(visitor_type_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/words_controller.py b/ruoyi-fastapi-backend/module_admin/controller/words_controller.py new file mode 100644 index 0000000..6d9633e --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/words_controller.py @@ -0,0 +1,173 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.visitor_type_service import Visitor_typeService +from module_admin.service.words_service import WordsService +from module_admin.entity.vo.words_vo import DeleteWordsModel, WordsModel, WordsPageQueryModel, WordsUpdateModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + + +wordsController = APIRouter(prefix='/guide/words', dependencies=[Depends(LoginService.get_current_user)]) +""" + 访客记录: visitor:record:list, dependencies=[Depends(CheckUserInterfaceAuth('guide:words:list'))] + 引导词管理: visitor:guide:list, dependencies=[Depends(CheckUserInterfaceAuth('visitor:guide:list'))] +""" + + +# 随机获取引导词 +@wordsController.get('/random_words/{type}') +async def get_random_guide_word(request: Request, type: str, query_db: AsyncSession = Depends(get_db)): + + guide_word = await WordsService.get_random_guide_word_services(query_db, type) + return ResponseUtil.success(data=guide_word) + + + +@wordsController.post( + '/list', response_model=PageResponseModel + # , dependencies=[Depends(CheckUserInterfaceAuth('guide:words:list'))] + , dependencies=[Depends(CheckUserInterfaceAuth('visitor:guide:list'))] +) +async def get_guide_words_list( + request: Request, + words_page_query: WordsPageQueryModel = Depends(WordsPageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + print(words_page_query.begin_time) + print(words_page_query.end_time) + # 获取分页数据 + words_page_query_result = await WordsService.get_words_list_services(query_db, words_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=words_page_query_result) + + +#批量启用引导词 +@wordsController.post('/enable_guide/{guide_ids}', dependencies=[Depends(CheckUserInterfaceAuth('visitor:guide:list'))]) +async def enable_guide( + request: Request, + guide_ids: str, + query_db: AsyncSession = Depends(get_db), + +): + edit_del_result = await WordsService.edit_del_words_services(query_db=query_db, guide_ids=guide_ids, del_flg='0') + return ResponseUtil.success(msg=edit_del_result.message) + +#批量禁用引导词 +@wordsController.post('/disable_guide/{guide_ids}', dependencies=[Depends(CheckUserInterfaceAuth('visitor:guide:list'))]) +async def disable_guide( + request: Request, + guide_ids: str, + query_db: AsyncSession = Depends(get_db), + +): + edit_del_result = await WordsService.edit_del_words_services(query_db=query_db, guide_ids=guide_ids, del_flg='2') + return ResponseUtil.success(msg=edit_del_result.message) + +#获取访客类型列表 +@wordsController.get('/visitor_type_list',) +async def get_system_visitor_type_list( + request: Request, + query_db: AsyncSession = Depends(get_db), +): + visitor_type_list = await Visitor_typeService.get_system_visitor_type_list(query_db=query_db) + return ResponseUtil.success(data=visitor_type_list) + + + +@wordsController.post('/add_guide' + # , dependencies=[Depends(CheckUserInterfaceAuth('guide:words:add'))] + , dependencies=[Depends(CheckUserInterfaceAuth('visitor:guide:list'))] + ) +@ValidateFields(validate_model='add_words') +@Log(title='引导词', business_type=BusinessType.INSERT) +async def add_guide_words( + request: Request, + add_words: WordsModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + if add_words.type is None: + return ResponseUtil.failure(msg='引导词类型不能为空') + if add_words.text is None: + return ResponseUtil.failure(msg='引导词内容不能为空') + add_words.create_time = datetime.now() + add_words.update_time = datetime.now() + add_words.create_by = current_user.user.user_name + add_words.del_flag = "0" + add_words_result = await WordsService.add_words_services(query_db, add_words) + logger.info(add_words_result.message) + + return ResponseUtil.success(msg=add_words_result.message) + + +@wordsController.put('/edit_guide' + # ,dependencies=[Depends(CheckUserInterfaceAuth('guide:words:edit'))] + , dependencies=[Depends(CheckUserInterfaceAuth('visitor:guide:list'))] + ) +@ValidateFields(validate_model='edit_words') +@Log(title='引导词', business_type=BusinessType.UPDATE) +async def edit_guide_words( + request: Request, + edit_words: WordsUpdateModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + + edit_words.update_by = current_user.user.user_name + edit_words.update_time = datetime.now() + edit_words_result = await WordsService.edit_words_services(query_db, edit_words) + logger.info(edit_words_result.message) + + return ResponseUtil.success(msg=edit_words_result.message) + + +@wordsController.delete('/{guide_ids}' + # , dependencies=[Depends(CheckUserInterfaceAuth('guide:words:remove'))] + , dependencies=[Depends(CheckUserInterfaceAuth('visitor:guide:list'))] + ) +@Log(title='引导词', business_type=BusinessType.DELETE) +async def delete_guide_words(request: Request, guide_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_words = DeleteWordsModel(guideIds=guide_ids) + delete_words_result = await WordsService.delete_words_services(query_db, delete_words) + logger.info(delete_words_result.message) + + return ResponseUtil.success(msg=delete_words_result.message) + + +@wordsController.get( + '/{guide_id}', response_model=WordsModel + # , dependencies=[Depends(CheckUserInterfaceAuth('guide:words:query'))] + , dependencies=[Depends(CheckUserInterfaceAuth('visitor:guide:list'))] +) +async def query_detail_guide_words(request: Request, guide_id: int, query_db: AsyncSession = Depends(get_db)): + words_detail_result = await WordsService.words_detail_services(query_db, guide_id) + logger.info(f'获取guide_id为{guide_id}的信息成功') + + return ResponseUtil.success(data=words_detail_result) + + +@wordsController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('visitor:guide:list'))]) +@Log(title='引导词', business_type=BusinessType.EXPORT) +async def export_guide_words_list( + request: Request, + words_page_query: WordsPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + words_query_result = await WordsService.get_words_list_services(query_db, words_page_query, is_page=False) + words_export_result = await WordsService.export_words_list_services(words_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(words_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/dao/__init__.py b/ruoyi-fastapi-backend/module_admin/dao/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ruoyi-fastapi-backend/module_admin/dao/config_dao.py b/ruoyi-fastapi-backend/module_admin/dao/config_dao.py new file mode 100644 index 0000000..4f34a2f --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/config_dao.py @@ -0,0 +1,116 @@ +from datetime import datetime, time +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.config_do import SysConfig +from module_admin.entity.vo.config_vo import ConfigModel, ConfigPageQueryModel +from utils.page_util import PageUtil + + +class ConfigDao: + """ + 参数配置管理模块数据库操作层 + """ + + @classmethod + async def get_config_detail_by_id(cls, db: AsyncSession, config_id: int): + """ + 根据参数配置id获取参数配置详细信息 + + :param db: orm对象 + :param config_id: 参数配置id + :return: 参数配置信息对象 + """ + config_info = (await db.execute(select(SysConfig).where(SysConfig.config_id == config_id))).scalars().first() + + return config_info + + @classmethod + async def get_config_detail_by_info(cls, db: AsyncSession, config: ConfigModel): + """ + 根据参数配置参数获取参数配置信息 + + :param db: orm对象 + :param config: 参数配置参数对象 + :return: 参数配置信息对象 + """ + config_info = ( + ( + await db.execute( + select(SysConfig).where( + SysConfig.config_key == config.config_key if config.config_key else True, + SysConfig.config_value == config.config_value if config.config_value else True, + ) + ) + ) + .scalars() + .first() + ) + + return config_info + + @classmethod + async def get_config_list(cls, db: AsyncSession, query_object: ConfigPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取参数配置列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 参数配置列表信息对象 + """ + query = ( + select(SysConfig) + .where( + SysConfig.config_name.like(f'%{query_object.config_name}%') if query_object.config_name else True, + SysConfig.config_key.like(f'%{query_object.config_key}%') if query_object.config_key else True, + SysConfig.config_type == query_object.config_type if query_object.config_type else True, + SysConfig.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .order_by(SysConfig.config_id) + .distinct() + ) + config_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return config_list + + @classmethod + async def add_config_dao(cls, db: AsyncSession, config: ConfigModel): + """ + 新增参数配置数据库操作 + + :param db: orm对象 + :param config: 参数配置对象 + :return: + """ + db_config = SysConfig(**config.model_dump()) + db.add(db_config) + await db.flush() + + return db_config + + @classmethod + async def edit_config_dao(cls, db: AsyncSession, config: dict): + """ + 编辑参数配置数据库操作 + + :param db: orm对象 + :param config: 需要更新的参数配置字典 + :return: + """ + await db.execute(update(SysConfig), [config]) + + @classmethod + async def delete_config_dao(cls, db: AsyncSession, config: ConfigModel): + """ + 删除参数配置数据库操作 + + :param db: orm对象 + :param config: 参数配置对象 + :return: + """ + await db.execute(delete(SysConfig).where(SysConfig.config_id.in_([config.config_id]))) diff --git a/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py b/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py new file mode 100644 index 0000000..f450210 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py @@ -0,0 +1,307 @@ +from sqlalchemy import bindparam, func, or_, select, update # noqa: F401 +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.util import immutabledict +from typing import List +from module_admin.entity.do.dept_do import SysDept +from module_admin.entity.do.role_do import SysRoleDept # noqa: F401 +from module_admin.entity.do.user_do import SysUser +from module_admin.entity.vo.dept_vo import DeptModel + + +class DeptDao: + """ + 部门管理模块数据库操作层 + """ + + @classmethod + async def get_dept_by_id(cls, db: AsyncSession, dept_id: int): + """ + 根据部门id获取在用部门信息 + + :param db: orm对象 + :param dept_id: 部门id + :return: 在用部门信息对象 + """ + dept_info = (await db.execute(select(SysDept).where(SysDept.dept_id == dept_id))).scalars().first() + + return dept_info + + @classmethod + async def get_dept_detail_by_id(cls, db: AsyncSession, dept_id: int): + """ + 根据部门id获取部门详细信息 + + :param db: orm对象 + :param dept_id: 部门id + :return: 部门信息对象 + """ + dept_info = ( + (await db.execute(select(SysDept).where(SysDept.dept_id == dept_id, SysDept.del_flag == '0'))) + .scalars() + .first() + ) + + return dept_info + + @classmethod + async def get_dept_detail_by_info(cls, db: AsyncSession, dept: DeptModel): + """ + 根据部门参数获取部门信息 + + :param db: orm对象 + :param dept: 部门参数对象 + :return: 部门信息对象 + """ + dept_info = ( + ( + await db.execute( + select(SysDept).where( + SysDept.parent_id == dept.parent_id if dept.parent_id else True, + SysDept.dept_name == dept.dept_name if dept.dept_name else True, + ) + ) + ) + .scalars() + .first() + ) + + return dept_info + + @classmethod + async def get_dept_info_for_edit_option(cls, db: AsyncSession, dept_info: DeptModel, data_scope_sql: str): + """ + 获取部门编辑对应的在用部门列表信息 + + :param db: orm对象 + :param dept_info: 部门对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 部门列表信息 + """ + dept_result = ( + ( + await db.execute( + select(SysDept) + .where( + SysDept.dept_id != dept_info.dept_id, + ~SysDept.dept_id.in_( + select(SysDept.dept_id).where(func.find_in_set(dept_info.dept_id, SysDept.ancestors)) + ), + SysDept.del_flag == '0', + SysDept.status == '0', + eval(data_scope_sql), + ) + .order_by(SysDept.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) + + return dept_result + + @classmethod + async def get_children_dept_dao(cls, db: AsyncSession, dept_id: int): + """ + 根据部门id查询当前部门的子部门列表信息 + + :param db: orm对象 + :param dept_id: 部门id + :return: 子部门信息列表 + """ + dept_result = ( + (await db.execute(select(SysDept).where(func.find_in_set(dept_id, SysDept.ancestors)))).scalars().all() + ) + + return dept_result + + @classmethod + async def get_dept_list_for_tree(cls, db: AsyncSession, dept_info: DeptModel, data_scope_sql: str): + """ + 获取所有在用部门列表信息 + + :param db: orm对象 + :param dept_info: 部门对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 在用部门列表信息 + """ + dept_result = ( + ( + await db.execute( + select(SysDept) + .where( + SysDept.status == '0', + SysDept.del_flag == '0', + SysDept.dept_name.like(f'%{dept_info.dept_name}%') if dept_info.dept_name else True, + eval(data_scope_sql), + ) + .order_by(SysDept.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) + + return dept_result + + @classmethod + async def get_dept_list(cls, db: AsyncSession, page_object: DeptModel, data_scope_sql: str): + """ + 根据查询参数获取部门列表信息 + + :param db: orm对象 + :param page_object: 不分页查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 部门列表信息对象 + """ + dept_result = ( + ( + await db.execute( + select(SysDept) + .where( + SysDept.del_flag == '0', + SysDept.dept_id == page_object.dept_id if page_object.dept_id is not None else True, + SysDept.status == page_object.status if page_object.status else True, + SysDept.dept_name.like(f'%{page_object.dept_name}%') if page_object.dept_name else True, + eval(data_scope_sql), + ) + .order_by(SysDept.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) + + return dept_result + + @classmethod + async def add_dept_dao(cls, db: AsyncSession, dept: DeptModel): + """ + 新增部门数据库操作 + + :param db: orm对象 + :param dept: 部门对象 + :return: 新增校验结果 + """ + db_dept = SysDept(**dept.model_dump()) + db.add(db_dept) + await db.flush() + + return db_dept + + @classmethod + async def edit_dept_dao(cls, db: AsyncSession, dept: dict): + """ + 编辑部门数据库操作 + + :param db: orm对象 + :param dept: 需要更新的部门字典 + :return: 编辑校验结果 + """ + await db.execute(update(SysDept), [dept]) + + @classmethod + async def update_dept_children_dao(cls, db: AsyncSession, update_dept: List): + """ + 更新子部门信息 + + :param db: orm对象 + :param update_dept: 需要更新的部门列表 + :return: + """ + await db.execute( + update(SysDept) + .where(SysDept.dept_id == bindparam('dept_id')) + .values( + { + 'dept_id': bindparam('dept_id'), + 'ancestors': bindparam('ancestors'), + } + ), + update_dept, + execution_options=immutabledict({'synchronize_session': None}), + ) + + @classmethod + async def update_dept_status_normal_dao(cls, db: AsyncSession, dept_id_list: List): + """ + 批量更新部门状态为正常 + + :param db: orm对象 + :param dept_id_list: 部门id列表 + :return: + """ + await db.execute(update(SysDept).where(SysDept.dept_id.in_(dept_id_list)).values(status='0')) + + @classmethod + async def delete_dept_dao(cls, db: AsyncSession, dept: DeptModel): + """ + 删除部门数据库操作 + + :param db: orm对象 + :param dept: 部门对象 + :return: + """ + await db.execute( + update(SysDept) + .where(SysDept.dept_id == dept.dept_id) + .values(del_flag='2', update_by=dept.update_by, update_time=dept.update_time) + ) + + @classmethod + async def count_normal_children_dept_dao(cls, db: AsyncSession, dept_id: int): + """ + 根据部门id查询查询所有子部门(正常状态)的数量 + + :param db: orm对象 + :param dept_id: 部门id + :return: 所有子部门(正常状态)的数量 + """ + normal_children_dept_count = ( + await db.execute( + select(func.count('*')) + .select_from(SysDept) + .where(SysDept.status == '0', SysDept.del_flag == '0', func.find_in_set(dept_id, SysDept.ancestors)) + ) + ).scalar() + + return normal_children_dept_count + + @classmethod + async def count_children_dept_dao(cls, db: AsyncSession, dept_id: int): + """ + 根据部门id查询查询所有子部门(所有状态)的数量 + + :param db: orm对象 + :param dept_id: 部门id + :return: 所有子部门(所有状态)的数量 + """ + children_dept_count = ( + await db.execute( + select(func.count('*')) + .select_from(SysDept) + .where(SysDept.del_flag == '0', SysDept.parent_id == dept_id) + .limit(1) + ) + ).scalar() + + return children_dept_count + + @classmethod + async def count_dept_user_dao(cls, db: AsyncSession, dept_id: int): + """ + 根据部门id查询查询部门下的用户数量 + + :param db: orm对象 + :param dept_id: 部门id + :return: 部门下的用户数量 + """ + dept_user_count = ( + await db.execute( + select(func.count('*')).select_from(SysUser).where(SysUser.dept_id == dept_id, SysUser.del_flag == '0') + ) + ).scalar() + + return dept_user_count diff --git a/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py b/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py new file mode 100644 index 0000000..c5a8ed5 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py @@ -0,0 +1,281 @@ +from datetime import datetime, time +from sqlalchemy import and_, delete, func, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.dict_do import SysDictType, SysDictData +from module_admin.entity.vo.dict_vo import DictDataModel, DictDataPageQueryModel, DictTypeModel, DictTypePageQueryModel +from utils.page_util import PageUtil +from utils.time_format_util import list_format_datetime + + +class DictTypeDao: + """ + 字典类型管理模块数据库操作层 + """ + + @classmethod + async def get_dict_type_detail_by_id(cls, db: AsyncSession, dict_id: int): + """ + 根据字典类型id获取字典类型详细信息 + + :param db: orm对象 + :param dict_id: 字典类型id + :return: 字典类型信息对象 + """ + dict_type_info = (await db.execute(select(SysDictType).where(SysDictType.dict_id == dict_id))).scalars().first() + + return dict_type_info + + @classmethod + async def get_dict_type_detail_by_info(cls, db: AsyncSession, dict_type: DictTypeModel): + """ + 根据字典类型参数获取字典类型信息 + + :param db: orm对象 + :param dict_type: 字典类型参数对象 + :return: 字典类型信息对象 + """ + dict_type_info = ( + ( + await db.execute( + select(SysDictType).where( + SysDictType.dict_type == dict_type.dict_type if dict_type.dict_type else True, + SysDictType.dict_name == dict_type.dict_name if dict_type.dict_name else True, + ) + ) + ) + .scalars() + .first() + ) + + return dict_type_info + + @classmethod + async def get_all_dict_type(cls, db: AsyncSession): + """ + 获取所有的字典类型信息 + + :param db: orm对象 + :return: 字典类型信息列表对象 + """ + dict_type_info = (await db.execute(select(SysDictType))).scalars().all() + + return list_format_datetime(dict_type_info) + + @classmethod + async def get_dict_type_list(cls, db: AsyncSession, query_object: DictTypePageQueryModel, is_page: bool = False): + """ + 根据查询参数获取字典类型列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 字典类型列表信息对象 + """ + query = ( + select(SysDictType) + .where( + SysDictType.dict_name.like(f'%{query_object.dict_name}%') if query_object.dict_name else True, + SysDictType.dict_type.like(f'%{query_object.dict_type}%') if query_object.dict_type else True, + SysDictType.status == query_object.status if query_object.status else True, + SysDictType.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .order_by(SysDictType.dict_id) + .distinct() + ) + dict_type_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return dict_type_list + + @classmethod + async def add_dict_type_dao(cls, db: AsyncSession, dict_type: DictTypeModel): + """ + 新增字典类型数据库操作 + + :param db: orm对象 + :param dict_type: 字典类型对象 + :return: + """ + db_dict_type = SysDictType(**dict_type.model_dump()) + db.add(db_dict_type) + await db.flush() + + return db_dict_type + + @classmethod + async def edit_dict_type_dao(cls, db: AsyncSession, dict_type: dict): + """ + 编辑字典类型数据库操作 + + :param db: orm对象 + :param dict_type: 需要更新的字典类型字典 + :return: + """ + await db.execute(update(SysDictType), [dict_type]) + + @classmethod + async def delete_dict_type_dao(cls, db: AsyncSession, dict_type: DictTypeModel): + """ + 删除字典类型数据库操作 + + :param db: orm对象 + :param dict_type: 字典类型对象 + :return: + """ + await db.execute(delete(SysDictType).where(SysDictType.dict_id.in_([dict_type.dict_id]))) + + +class DictDataDao: + """ + 字典数据管理模块数据库操作层 + """ + + @classmethod + async def get_dict_data_detail_by_id(cls, db: AsyncSession, dict_code: int): + """ + 根据字典数据id获取字典数据详细信息 + + :param db: orm对象 + :param dict_code: 字典数据id + :return: 字典数据信息对象 + """ + dict_data_info = ( + (await db.execute(select(SysDictData).where(SysDictData.dict_code == dict_code))).scalars().first() + ) + + return dict_data_info + + @classmethod + async def get_dict_data_detail_by_info(cls, db: AsyncSession, dict_data: DictDataModel): + """ + 根据字典数据参数获取字典数据信息 + + :param db: orm对象 + :param dict_data: 字典数据参数对象 + :return: 字典数据信息对象 + """ + dict_data_info = ( + ( + await db.execute( + select(SysDictData).where( + SysDictData.dict_type == dict_data.dict_type, + SysDictData.dict_label == dict_data.dict_label, + SysDictData.dict_value == dict_data.dict_value, + ) + ) + ) + .scalars() + .first() + ) + + return dict_data_info + + @classmethod + async def get_dict_data_list(cls, db: AsyncSession, query_object: DictDataPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取字典数据列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 字典数据列表信息对象 + """ + query = ( + select(SysDictData) + .where( + SysDictData.dict_type == query_object.dict_type if query_object.dict_type else True, + SysDictData.dict_label.like(f'%{query_object.dict_label}%') if query_object.dict_label else True, + SysDictData.status == query_object.status if query_object.status else True, + ) + .order_by(SysDictData.dict_sort) + .distinct() + ) + dict_data_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return dict_data_list + + @classmethod + async def query_dict_data_list(cls, db: AsyncSession, dict_type: str): + """ + 根据查询参数获取字典数据列表信息 + + :param db: orm对象 + :param dict_type: 字典类型 + :return: 字典数据列表信息对象 + """ + dict_data_list = ( + ( + await db.execute( + select(SysDictData) + .select_from(SysDictType) + .where(SysDictType.dict_type == dict_type if dict_type else True, SysDictType.status == '0') + .join( + SysDictData, + and_(SysDictType.dict_type == SysDictData.dict_type, SysDictData.status == '0'), + isouter=True, + ) + .order_by(SysDictData.dict_sort) + .distinct() + ) + ) + .scalars() + .all() + ) + + return dict_data_list + + @classmethod + async def add_dict_data_dao(cls, db: AsyncSession, dict_data: DictDataModel): + """ + 新增字典数据数据库操作 + + :param db: orm对象 + :param dict_data: 字典数据对象 + :return: + """ + db_data_type = SysDictData(**dict_data.model_dump()) + db.add(db_data_type) + await db.flush() + + return db_data_type + + @classmethod + async def edit_dict_data_dao(cls, db: AsyncSession, dict_data: dict): + """ + 编辑字典数据数据库操作 + + :param db: orm对象 + :param dict_data: 需要更新的字典数据字典 + :return: + """ + await db.execute(update(SysDictData), [dict_data]) + + @classmethod + async def delete_dict_data_dao(cls, db: AsyncSession, dict_data: DictDataModel): + """ + 删除字典数据数据库操作 + + :param db: orm对象 + :param dict_data: 字典数据对象 + :return: + """ + await db.execute(delete(SysDictData).where(SysDictData.dict_code.in_([dict_data.dict_code]))) + + @classmethod + async def count_dict_data_dao(cls, db: AsyncSession, dict_type: str): + """ + 根据字典类型查询字典类型关联的字典数据数量 + + :param db: orm对象 + :param dict_type: 字典类型 + :return: 字典类型关联的字典数据数量 + """ + dict_data_count = ( + await db.execute(select(func.count('*')).select_from(SysDictData).where(SysDictData.dict_type == dict_type)) + ).scalar() + + return dict_data_count diff --git a/ruoyi-fastapi-backend/module_admin/dao/door_dao.py b/ruoyi-fastapi-backend/module_admin/dao/door_dao.py new file mode 100644 index 0000000..0e5ae6d --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/door_dao.py @@ -0,0 +1,154 @@ +from sqlalchemy import delete, select, update, or_ +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.door_do import Door +from module_admin.entity.vo.door_vo import DoorModel, DoorPageQueryModel +from utils.page_util import PageUtil + + +class DoorDao: + """ + 门禁设备模块数据库操作层 + """ + + @classmethod + async def get_door_door_status(cls, db: AsyncSession): + """获取门禁状态 + """ + stmt = ( + select(Door.indexCode, Door.name).select_from(Door).where(Door.permission=="1") + ) + result = await db.execute(stmt) + + return result.mappings().all() + + @classmethod + async def get_door_index_code_list(cls, db: AsyncSession, permission: str = None): + """ + 获取门禁设备索引编码列表 + """ + query = select(Door.indexCode).where(Door.permission == permission if permission else True) + result = await db.execute(query) + return [row[0] for row in result.all()] + + @classmethod + async def get_door_detail_by_id(cls, db: AsyncSession, id: int): + """ + 根据主键自增获取门禁设备详细信息 + + :param db: orm对象 + :param id: 主键自增 + :return: 门禁设备信息对象 + """ + door_info = ( + ( + await db.execute( + select(Door) + .where( + Door.id == id + ) + ) + ) + .scalars() + .first() + ) + + return door_info + + @classmethod + async def get_door_detail_by_info(cls, db: AsyncSession, door: DoorModel): + """ + 根据门禁设备参数获取门禁设备信息 + + :param db: orm对象 + :param door: 门禁设备参数对象 + :return: 门禁设备信息对象 + """ + door_info = ( + ( + await db.execute( + select(Door).where( + ) + ) + ) + .scalars() + .first() + ) + + return door_info + + @classmethod + async def get_door_list(cls, db: AsyncSession, query_object: DoorPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取门禁设备列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 门禁设备列表信息对象 + """ + query = ( + select(Door) + .where( + # Door.index_code == query_object.index_code if query_object.index_code else True, + or_( + ( + Door.name.like(f"%{query_object.key_word}%") + if query_object.key_word + else True + ), + ( + Door.install_location.like(f"%{query_object.key_word}%") + if query_object.key_word + else True + ), + ), + # Door.status == query_object.status if query_object.status else True, + ( + Door.permission == query_object.permission + if query_object.permission + else True + ), + ) + .order_by(Door.id) + .distinct() + ) + door_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return door_list + + @classmethod + async def add_door_dao(cls, db: AsyncSession, door: DoorModel): + """ + 新增门禁设备数据库操作 + + :param db: orm对象 + :param door: 门禁设备对象 + :return: + """ + db_door = Door(**door.model_dump(exclude={})) + db.add(db_door) + await db.flush() + + return db_door + + @classmethod + async def edit_door_dao(cls, db: AsyncSession, door: dict): + """ + 编辑门禁设备数据库操作 + + :param db: orm对象 + :param door: 需要更新的门禁设备字典 + :return: + """ + await db.execute(update(Door), [door]) + + @classmethod + async def delete_door_dao(cls, db: AsyncSession, door: DoorModel): + """ + 删除门禁设备数据库操作 + + :param db: orm对象 + :param door: 门禁设备对象 + :return: + """ + await db.execute(delete(Door).where(Door.id.in_([door.id]))) diff --git a/ruoyi-fastapi-backend/module_admin/dao/explanation_content_dao.py b/ruoyi-fastapi-backend/module_admin/dao/explanation_content_dao.py new file mode 100644 index 0000000..085aa66 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/explanation_content_dao.py @@ -0,0 +1,135 @@ +from datetime import datetime, time +from sqlalchemy import delete, select, update, desc +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.explanation_content_do import ExplanationContent +from module_admin.entity.vo.explanation_content_vo import Explanation_contentModel, Explanation_contentPageQueryModel +from utils.page_util import PageUtil + + +class Explanation_contentDao: + """ + 讲解内容模块数据库操作层 + """ + + @classmethod + async def get_explanation_content_detail_by_id(cls, db: AsyncSession, explanation_content_id: int): + """ + 根据主键ID获取讲解内容详细信息 + + :param db: orm对象 + :param explanation_content_id: 主键ID + :return: 讲解内容信息对象 + """ + explanation_content_info = ( + ( + await db.execute( + select(ExplanationContent) + .where( + ExplanationContent.explanation_content_id == explanation_content_id + ) + ) + ) + .scalars() + .first() + ) + + return explanation_content_info + + @classmethod + async def get_explanation_content_detail_by_info(cls, db: AsyncSession, explanation_content: Explanation_contentModel): + """ + 根据讲解内容参数获取讲解内容信息 + + :param db: orm对象 + :param explanation_content: 讲解内容参数对象 + :return: 讲解内容信息对象 + """ + explanation_content_info = ( + ( + await db.execute( + select(ExplanationContent).where( + ) + ) + ) + .scalars() + .first() + ) + + return explanation_content_info + + @classmethod + async def get_explanation_content_list(cls, db: AsyncSession, query_object: Explanation_contentPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取讲解内容列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 讲解内容列表信息对象 + """ + query = ( + select(ExplanationContent) + .where( + # ExplanationContent.title == query_object.title if query_object.title else True, + # ExplanationContent.content == query_object.content if query_object.content else True, + ExplanationContent.type == query_object.type if query_object.type else True, + # ExplanationContent.spend_time == query_object.spend_time if query_object.spend_time else True, + ExplanationContent.language == query_object.language if query_object.language else True, + ExplanationContent.status == query_object.status if query_object.status else True, + ExplanationContent.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_create_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_create_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_create_time and query_object.end_create_time + else True, + ExplanationContent.update_time.between( + datetime.combine(datetime.strptime(query_object.begin_update_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_update_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_update_time and query_object.end_update_time + else True, + ) + .order_by(desc(ExplanationContent.update_time)) + .distinct() + ) + explanation_content_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return explanation_content_list + + @classmethod + async def add_explanation_content_dao(cls, db: AsyncSession, explanation_content: Explanation_contentModel): + """ + 新增讲解内容数据库操作 + + :param db: orm对象 + :param explanation_content: 讲解内容对象 + :return: + """ + db_explanation_content = ExplanationContent(**explanation_content.model_dump(exclude={})) + db.add(db_explanation_content) + await db.flush() + + return db_explanation_content + + @classmethod + async def edit_explanation_content_dao(cls, db: AsyncSession, explanation_content: dict): + """ + 编辑讲解内容数据库操作 + + :param db: orm对象 + :param explanation_content: 需要更新的讲解内容字典 + :return: + """ + await db.execute(update(ExplanationContent), [explanation_content]) + + @classmethod + async def delete_explanation_content_dao(cls, db: AsyncSession, explanation_content: Explanation_contentModel): + """ + 删除讲解内容数据库操作 + + :param db: orm对象 + :param explanation_content: 讲解内容对象 + :return: + """ + await db.execute(delete(ExplanationContent).where(ExplanationContent.explanation_content_id.in_([explanation_content.explanation_content_id]))) + diff --git a/ruoyi-fastapi-backend/module_admin/dao/explanation_content_type_dao.py b/ruoyi-fastapi-backend/module_admin/dao/explanation_content_type_dao.py new file mode 100644 index 0000000..cac3fa0 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/explanation_content_type_dao.py @@ -0,0 +1,120 @@ +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.explanation_content_type_do import ExplanationContentType +from module_admin.entity.vo.explanation_content_type_vo import Explanation_content_typeModel, Explanation_content_typePageQueryModel +from utils.page_util import PageUtil + + +class Explanation_content_typeDao: + """ + 讲解内容类型模块数据库操作层 + """ + + @classmethod + async def get_explanation_content_type_detail_by_id(cls, db: AsyncSession, content_type_id: int): + """ + 根据主键ID获取讲解内容类型详细信息 + + :param db: orm对象 + :param content_type_id: 主键ID + :return: 讲解内容类型信息对象 + """ + explanation_content_type_info = ( + ( + await db.execute( + select(ExplanationContentType) + .where( + ExplanationContentType.content_type_id == content_type_id + ) + ) + ) + .scalars() + .first() + ) + + return explanation_content_type_info + + @classmethod + async def get_explanation_content_type_detail_by_info(cls, db: AsyncSession, explanation_content_type: Explanation_content_typeModel): + """ + 根据讲解内容类型参数获取讲解内容类型信息 + + :param db: orm对象 + :param explanation_content_type: 讲解内容类型参数对象 + :return: 讲解内容类型信息对象 + """ + explanation_content_type_info = ( + ( + await db.execute( + select(ExplanationContentType).where( + ) + ) + ) + .scalars() + .first() + ) + + return explanation_content_type_info + + @classmethod + async def get_explanation_content_type_list(cls, db: AsyncSession, query_object: Explanation_content_typePageQueryModel, is_page: bool = False): + """ + 根据查询参数获取讲解内容类型列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 讲解内容类型列表信息对象 + """ + query = ( + select(ExplanationContentType) + .where( + ExplanationContentType.name.like(f'%{query_object.name}%') if query_object.name else True, + ExplanationContentType.type_value == query_object.type_value if query_object.type_value else True, + ExplanationContentType.create_time.like(f'%{query_object.create_time}%') if query_object.create_time else True, + ExplanationContentType.update_time.like(f'%{query_object.update_time}%') if query_object.update_time else True, + ) + .order_by(ExplanationContentType.content_type_id) + .distinct() + ) + # explanation_content_type_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + explanation_content_type_list = (await db.execute(query)).scalars().all() + return explanation_content_type_list + + @classmethod + async def add_explanation_content_type_dao(cls, db: AsyncSession, explanation_content_type: Explanation_content_typeModel): + """ + 新增讲解内容类型数据库操作 + + :param db: orm对象 + :param explanation_content_type: 讲解内容类型对象 + :return: + """ + db_explanation_content_type = ExplanationContentType(**explanation_content_type.model_dump(exclude={})) + db.add(db_explanation_content_type) + await db.flush() + + return db_explanation_content_type + + @classmethod + async def edit_explanation_content_type_dao(cls, db: AsyncSession, explanation_content_type: dict): + """ + 编辑讲解内容类型数据库操作 + + :param db: orm对象 + :param explanation_content_type: 需要更新的讲解内容类型字典 + :return: + """ + await db.execute(update(ExplanationContentType), [explanation_content_type]) + + @classmethod + async def delete_explanation_content_type_dao(cls, db: AsyncSession, explanation_content_type: Explanation_content_typeModel): + """ + 删除讲解内容类型数据库操作 + + :param db: orm对象 + :param explanation_content_type: 讲解内容类型对象 + :return: + """ + await db.execute(delete(ExplanationContentType).where(ExplanationContentType.content_type_id.in_([explanation_content_type.content_type_id]))) + diff --git a/ruoyi-fastapi-backend/module_admin/dao/explanation_style_dao.py b/ruoyi-fastapi-backend/module_admin/dao/explanation_style_dao.py new file mode 100644 index 0000000..cb922e6 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/explanation_style_dao.py @@ -0,0 +1,131 @@ +from datetime import datetime, time +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.explanation_style_do import ExplanationStyle +from module_admin.entity.vo.explanation_style_vo import Explanation_styleModel, Explanation_stylePageQueryModel +from utils.page_util import PageUtil + + +class Explanation_styleDao: + """ + 讲解风格模块数据库操作层 + """ + + @classmethod + async def get_explanation_style_detail_by_id(cls, db: AsyncSession, explanation_style_id: int): + """ + 根据主键ID获取讲解风格详细信息 + + :param db: orm对象 + :param explanation_style_id: 主键ID + :return: 讲解风格信息对象 + """ + explanation_style_info = ( + ( + await db.execute( + select(ExplanationStyle) + .where( + ExplanationStyle.explanation_style_id == explanation_style_id + ) + ) + ) + .scalars() + .first() + ) + + return explanation_style_info + + @classmethod + async def get_explanation_style_detail_by_info(cls, db: AsyncSession, explanation_style: Explanation_styleModel): + """ + 根据讲解风格参数获取讲解风格信息 + + :param db: orm对象 + :param explanation_style: 讲解风格参数对象 + :return: 讲解风格信息对象 + """ + explanation_style_info = ( + ( + await db.execute( + select(ExplanationStyle).where( + ) + ) + ) + .scalars() + .first() + ) + + return explanation_style_info + + @classmethod + async def get_explanation_style_list(cls, db: AsyncSession, query_object: Explanation_stylePageQueryModel, is_page: bool = False): + """ + 根据查询参数获取讲解风格列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 讲解风格列表信息对象 + """ + query = ( + select(ExplanationStyle) + .where( + ExplanationStyle.name.like(f'%{query_object.name}%') if query_object.name else True, + ExplanationStyle.detail == query_object.detail if query_object.detail else True, + ExplanationStyle.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_create_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_create_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_create_time and query_object.end_create_time + else True, + ExplanationStyle.update_time.between( + datetime.combine(datetime.strptime(query_object.begin_update_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_update_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_update_time and query_object.end_update_time + else True, + ) + .order_by(ExplanationStyle.explanation_style_id) + .distinct() + ) + explanation_style_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return explanation_style_list + + @classmethod + async def add_explanation_style_dao(cls, db: AsyncSession, explanation_style: Explanation_styleModel): + """ + 新增讲解风格数据库操作 + + :param db: orm对象 + :param explanation_style: 讲解风格对象 + :return: + """ + db_explanation_style = ExplanationStyle(**explanation_style.model_dump(exclude={})) + db.add(db_explanation_style) + await db.flush() + + return db_explanation_style + + @classmethod + async def edit_explanation_style_dao(cls, db: AsyncSession, explanation_style: dict): + """ + 编辑讲解风格数据库操作 + + :param db: orm对象 + :param explanation_style: 需要更新的讲解风格字典 + :return: + """ + await db.execute(update(ExplanationStyle), [explanation_style]) + + @classmethod + async def delete_explanation_style_dao(cls, db: AsyncSession, explanation_style: Explanation_styleModel): + """ + 删除讲解风格数据库操作 + + :param db: orm对象 + :param explanation_style: 讲解风格对象 + :return: + """ + await db.execute(delete(ExplanationStyle).where(ExplanationStyle.explanation_style_id.in_([explanation_style.explanation_style_id]))) + diff --git a/ruoyi-fastapi-backend/module_admin/dao/explanation_style_robot_pair_dao.py b/ruoyi-fastapi-backend/module_admin/dao/explanation_style_robot_pair_dao.py new file mode 100644 index 0000000..be2c316 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/explanation_style_robot_pair_dao.py @@ -0,0 +1,194 @@ +from datetime import datetime, time +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession + +from module_admin.entity.do.explanation_style_do import ExplanationStyle +from module_admin.entity.do.explanation_style_robot_pair_do import ExplanationStyleRobotPairing +from module_admin.entity.do.explanation_style_do import ExplanationStyle +from module_admin.entity.vo.explanation_style_robot_pair_vo import Explanation_style_robot_pairModel, \ + Explanation_style_robot_pairPageQueryModel, SwitchExplanationStyleModel +from utils.page_util import PageUtil + + +class Explanation_style_robot_pairDao: + """ + 讲解风格--机器人配对模块数据库操作层 + """ + + @classmethod + async def get_robot_role_style(cls, db: AsyncSession, robot_id: int, role_id: int): + """根据机器人id和角色id获取机器人角色样式 + + Args: + db (AsyncSession): 数据库会话 + robot_id (int): 机器人id + role_id (int): 角色id + + Returns: + _type_: _description_ + """ + + stmt = ( + select(ExplanationStyleRobotPairing.pairing_id, ExplanationStyleRobotPairing.explanation_style_id, ExplanationStyle.name, ExplanationStyle.detail, ExplanationStyleRobotPairing.status, ExplanationStyleRobotPairing.robot_role_id) + .select_from(ExplanationStyleRobotPairing) + .join(ExplanationStyle, ExplanationStyle.explanation_style_id == ExplanationStyleRobotPairing.explanation_style_id) + .where(ExplanationStyleRobotPairing.robot_id == robot_id, ExplanationStyleRobotPairing.robot_role_id == role_id) + ) + + result = (await db.execute(stmt)).mappings().all() + return result + + + + @classmethod + async def switch_explanation_style(cls, db: AsyncSession, robot_id: int = None, pairing_id: int = None, robot_role_id: str = None ,status:str = '0', update_by: str = None): + + condition = list() + + if robot_id: + condition.append(ExplanationStyleRobotPairing.robot_id == robot_id) + if pairing_id: + condition.append(ExplanationStyleRobotPairing.pairing_id == pairing_id) + if robot_role_id: + condition.append(ExplanationStyleRobotPairing.robot_role_id == robot_role_id) + + query = ( + update(ExplanationStyleRobotPairing) + .values(status=status) + .where(*condition) + ) + return await db.execute(query) + + @classmethod + async def get_style_robot_pair_list(cls, db: AsyncSession, robot_id: int): + query = ( + select(ExplanationStyleRobotPairing.pairing_id, ExplanationStyle.name, ExplanationStyle.detail, ExplanationStyleRobotPairing.status) + .select_from(ExplanationStyleRobotPairing) + .join(ExplanationStyle, ExplanationStyle.explanation_style_id == ExplanationStyleRobotPairing.explanation_style_id) + .where(ExplanationStyleRobotPairing.robot_id == robot_id) + ) + + result = (await db.execute(query)).mappings().all() + return result + + + @classmethod + async def get_explanation_style_robot_pair_detail_by_id(cls, db: AsyncSession, pairing_id: int): + """ + 根据主键ID获取讲解风格--机器人配对详细信息 + + :param db: orm对象 + :param pairing_id: 主键ID + :return: 讲解风格--机器人配对信息对象 + """ + explanation_style_robot_pair_info = ( + ( + await db.execute( + select(ExplanationStyleRobotPairing) + .where( + ExplanationStyleRobotPairing.pairing_id == pairing_id + ) + ) + ) + .scalars() + .first() + ) + + return explanation_style_robot_pair_info + + @classmethod + async def get_explanation_style_robot_pair_detail_by_info(cls, db: AsyncSession, explanation_style_robot_pair: Explanation_style_robot_pairModel): + """ + 根据讲解风格--机器人配对参数获取讲解风格--机器人配对信息 + + :param db: orm对象 + :param explanation_style_robot_pair: 讲解风格--机器人配对参数对象 + :return: 讲解风格--机器人配对信息对象 + """ + explanation_style_robot_pair_info = ( + ( + await db.execute( + select(ExplanationStyleRobotPairing).where( + ) + ) + ) + .scalars() + .first() + ) + + return explanation_style_robot_pair_info + + @classmethod + async def get_explanation_style_robot_pair_list(cls, db: AsyncSession, query_object: Explanation_style_robot_pairPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取讲解风格--机器人配对列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 讲解风格--机器人配对列表信息对象 + """ + query = ( + select(ExplanationStyleRobotPairing) + .where( + ExplanationStyleRobotPairing.robot_id == query_object.robot_id if query_object.robot_id else True, + ExplanationStyleRobotPairing.explanation_style_id == query_object.explanation_style_id if query_object.explanation_style_id else True, + ExplanationStyleRobotPairing.prompt == query_object.prompt if query_object.prompt else True, + ExplanationStyleRobotPairing.status == query_object.status if query_object.status else True, + ExplanationStyleRobotPairing.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_create_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_create_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_create_time and query_object.end_create_time + else True, + ExplanationStyleRobotPairing.update_time.between( + datetime.combine(datetime.strptime(query_object.begin_update_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_update_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_update_time and query_object.end_update_time + else True, + ) + .order_by(ExplanationStyleRobotPairing.pairing_id) + .distinct() + ) + explanation_style_robot_pair_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return explanation_style_robot_pair_list + + @classmethod + async def add_explanation_style_robot_pair_dao(cls, db: AsyncSession, explanation_style_robot_pair: Explanation_style_robot_pairModel): + """ + 新增讲解风格--机器人配对数据库操作 + + :param db: orm对象 + :param explanation_style_robot_pair: 讲解风格--机器人配对对象 + :return: + """ + db_explanation_style_robot_pair = ExplanationStyleRobotPairing(**explanation_style_robot_pair.model_dump(exclude={})) + db.add(db_explanation_style_robot_pair) + await db.flush() + + return db_explanation_style_robot_pair + + @classmethod + async def edit_explanation_style_robot_pair_dao(cls, db: AsyncSession, explanation_style_robot_pair: dict): + """ + 编辑讲解风格--机器人配对数据库操作 + + :param db: orm对象 + :param explanation_style_robot_pair: 需要更新的讲解风格--机器人配对字典 + :return: + """ + await db.execute(update(ExplanationStyleRobotPairing), [explanation_style_robot_pair]) + + @classmethod + async def delete_explanation_style_robot_pair_dao(cls, db: AsyncSession, explanation_style_robot_pair: Explanation_style_robot_pairModel): + """ + 删除讲解风格--机器人配对数据库操作 + + :param db: orm对象 + :param explanation_style_robot_pair: 讲解风格--机器人配对对象 + :return: + """ + await db.execute(delete(ExplanationStyleRobotPairing).where(ExplanationStyleRobotPairing.pairing_id.in_([explanation_style_robot_pair.pairing_id]))) + diff --git a/ruoyi-fastapi-backend/module_admin/dao/identification_record_dao.py b/ruoyi-fastapi-backend/module_admin/dao/identification_record_dao.py new file mode 100644 index 0000000..d2ae3b4 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/identification_record_dao.py @@ -0,0 +1,129 @@ +from datetime import datetime, time +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.identification_record_do import IdentificationRecord +from module_admin.entity.vo.identification_record_vo import Identification_recordModel, Identification_recordPageQueryModel +from utils.page_util import PageUtil + + +class Identification_recordDao: + """ + 识别记录模块数据库操作层 + """ + + @classmethod + async def get_identification_record_detail_by_id(cls, db: AsyncSession, id: int): + """ + 根据主键 自增获取识别记录详细信息 + + :param db: orm对象 + :param id: 主键 自增 + :return: 识别记录信息对象 + """ + identification_record_info = ( + ( + await db.execute( + select(IdentificationRecord) + .where( + IdentificationRecord.id == id + ) + ) + ) + .scalars() + .first() + ) + + return identification_record_info + + @classmethod + async def get_identification_record_detail_by_info(cls, db: AsyncSession, identification_record: Identification_recordModel): + """ + 根据识别记录参数获取识别记录信息 + + :param db: orm对象 + :param identification_record: 识别记录参数对象 + :return: 识别记录信息对象 + """ + identification_record_info = ( + ( + await db.execute( + select(IdentificationRecord).where( + ) + ) + ) + .scalars() + .first() + ) + + return identification_record_info + + @classmethod + async def get_identification_record_list(cls, db: AsyncSession, query_object: Identification_recordPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取识别记录列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 识别记录列表信息对象 + """ + query = ( + select(IdentificationRecord) + .where( + IdentificationRecord.person_name.like(f'%{query_object.person_name}%') if query_object.person_name else True, + IdentificationRecord.door_name.like(f'%{query_object.door_name}%') if query_object.door_name else True, + IdentificationRecord.status == query_object.status if query_object.status else True, + IdentificationRecord.source == query_object.source if query_object.source else True, + IdentificationRecord.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_create_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_create_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_create_time and query_object.end_create_time + else True, + ) + .order_by(IdentificationRecord.create_time.desc()) + .distinct() + ) + identification_record_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return identification_record_list + + @classmethod + async def add_identification_record_dao(cls, db: AsyncSession, identification_record: Identification_recordModel): + """ + 新增识别记录数据库操作 + + :param db: orm对象 + :param identification_record: 识别记录对象 + :return: + """ + # 排除 id 字段(自增主键),但保留其他所有字段 + data = identification_record.model_dump(exclude={'id'}, exclude_none=True) + db_identification_record = IdentificationRecord(**data) + db.add(db_identification_record) + await db.flush() + + return db_identification_record + + @classmethod + async def edit_identification_record_dao(cls, db: AsyncSession, identification_record: dict): + """ + 编辑识别记录数据库操作 + + :param db: orm对象 + :param identification_record: 需要更新的识别记录字典 + :return: + """ + await db.execute(update(IdentificationRecord), [identification_record]) + + @classmethod + async def delete_identification_record_dao(cls, db: AsyncSession, identification_record: Identification_recordModel): + """ + 删除识别记录数据库操作 + + :param db: orm对象 + :param identification_record: 识别记录对象 + :return: + """ + await db.execute(delete(IdentificationRecord).where(IdentificationRecord.id.in_([identification_record.id]))) + diff --git a/ruoyi-fastapi-backend/module_admin/dao/identification_statistics_dao.py b/ruoyi-fastapi-backend/module_admin/dao/identification_statistics_dao.py new file mode 100644 index 0000000..ff38f41 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/identification_statistics_dao.py @@ -0,0 +1,200 @@ +from datetime import datetime, time +from sqlalchemy import delete, select, update, func +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.identification_statistics_do import IdentificationStatistics +from module_admin.entity.vo.identification_statistics_vo import Identification_statisticsModel, Identification_statisticsPageQueryModel +from utils.page_util import PageUtil + + +class Identification_statisticsDao: + """ + 识别统计模块数据库操作层 + """ + + @classmethod + async def get_today_identification_statistics(cls, query_db: AsyncSession): + stmt = ( + select(IdentificationStatistics.employ, IdentificationStatistics.stranger, IdentificationStatistics.visitor, IdentificationStatistics.create_time) + .where(IdentificationStatistics.create_time == datetime.now().date()) + ) + + result = await query_db.execute(stmt) + return result.mappings().all() + + @classmethod + async def get_total_identification_statistics(cls, query_db: AsyncSession): + """ + 获取总的识别统计数据 - 返回各个字段的总和 + + :param query_db: orm对象 + :return: 总和统计数据 + """ + stmt = select( + func.sum(IdentificationStatistics.employ).label('employ'), + func.sum(IdentificationStatistics.visitor).label('visitor'), + func.sum(IdentificationStatistics.stranger).label('stranger') + ) + + result = await query_db.execute(stmt) + return result.mappings().first() + + @classmethod + async def get_identification_statistics_detail_by_id(cls, db: AsyncSession, id: int): + """ + 根据主键 自增获取识别统计详细信息 + + :param db: orm对象 + :param id: 主键 自增 + :return: 识别统计信息对象 + """ + identification_statistics_info = ( + ( + await db.execute( + select(IdentificationStatistics) + .where( + IdentificationStatistics.id == id + ) + ) + ) + .scalars() + .first() + ) + + return identification_statistics_info + + @classmethod + async def get_identification_statistics_detail_by_info(cls, db: AsyncSession, identification_statistics: Identification_statisticsModel): + """ + 根据识别统计参数获取识别统计信息 + + :param db: orm对象 + :param identification_statistics: 识别统计参数对象 + :return: 识别统计信息对象 + """ + identification_statistics_info = ( + ( + await db.execute( + select(IdentificationStatistics).where( + ) + ) + ) + .scalars() + .first() + ) + + return identification_statistics_info + + @classmethod + async def get_identification_statistics_list(cls, db: AsyncSession, query_object: Identification_statisticsPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取识别统计列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 识别统计列表信息对象 + """ + query = ( + select(IdentificationStatistics) + .where( + IdentificationStatistics.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_create_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_create_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_create_time and query_object.end_create_time + else True, + ) + .order_by(IdentificationStatistics.id) + .distinct() + ) + identification_statistics_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return identification_statistics_list + + @classmethod + async def add_identification_statistics_dao(cls, db: AsyncSession, identification_statistics: Identification_statisticsModel): + """ + 新增识别统计数据库操作 + + :param db: orm对象 + :param identification_statistics: 识别统计对象 + :return: + """ + db_identification_statistics = IdentificationStatistics(**identification_statistics.model_dump(exclude={})) + db.add(db_identification_statistics) + await db.flush() + + return db_identification_statistics + + @classmethod + async def edit_identification_statistics_dao(cls, db: AsyncSession, identification_statistics: dict): + """ + 编辑识别统计数据库操作 + + :param db: orm对象 + :param identification_statistics: 需要更新的识别统计字典 + :return: + """ + await db.execute(update(IdentificationStatistics), [identification_statistics]) + + @classmethod + async def delete_identification_statistics_dao(cls, db: AsyncSession, identification_statistics: Identification_statisticsModel): + """ + 删除识别统计数据库操作 + + :param db: orm对象 + :param identification_statistics: 识别统计对象 + :return: + """ + await db.execute(delete(IdentificationStatistics).where(IdentificationStatistics.id.in_([identification_statistics.id]))) + + @classmethod + async def update_identification_statistics_dao(cls, db: AsyncSession, person_status: str): + """ + 更新今日识别统计数据库操作 - 根据person_status增加相应字段的计数 + + :param db: orm对象 + :param person_status: 人员状态 (0 陌生人, 1 员工, 2 访客) + :return: + """ + today = datetime.now().date() + + # 查询今日是否已有统计记录 + stmt = select(IdentificationStatistics).where( + func.date(IdentificationStatistics.create_time) == today + ) + result = await db.execute(stmt) + today_statistics = result.scalars().first() + + # 如果没有今日记录,创建新记录 + if not today_statistics: + new_statistics = IdentificationStatistics( + employ=1 if person_status == "1" else 0, + visitor=1 if person_status == "2" else 0, + stranger=1 if person_status == "0" else 0, + create_time=datetime.now(), + create_by="system" + ) + db.add(new_statistics) + await db.flush() + else: + # 根据person_status更新相应字段 + if person_status == "0": # 陌生人 + await db.execute( + update(IdentificationStatistics) + .where(IdentificationStatistics.id == today_statistics.id) + .values(stranger=IdentificationStatistics.stranger + 1) + ) + elif person_status == "1": # 员工 + await db.execute( + update(IdentificationStatistics) + .where(IdentificationStatistics.id == today_statistics.id) + .values(employ=IdentificationStatistics.employ + 1) + ) + elif person_status == "2": # 访客 + await db.execute( + update(IdentificationStatistics) + .where(IdentificationStatistics.id == today_statistics.id) + .values(visitor=IdentificationStatistics.visitor + 1) + ) + diff --git a/ruoyi-fastapi-backend/module_admin/dao/info_dao.py b/ruoyi-fastapi-backend/module_admin/dao/info_dao.py new file mode 100644 index 0000000..0724b95 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/info_dao.py @@ -0,0 +1,173 @@ +from sqlalchemy import delete, select, update, desc +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.info_do import RobotInfo +from module_admin.entity.vo.info_vo import InfoModel, InfoPageQueryModel +from utils.page_util import PageUtil +from datetime import datetime, time + + + +class InfoDao: + """ + 机器人信息模块数据库操作层 + """ + + + @classmethod + async def get_video_uri(cls, robot_id: int, query_db: AsyncSession): + """根据机器人id获取视频流地址 + + Args: + robot_id (int): 机器人地址 + query_db (AsyncSession): _description_ + """ + stmt = ( + select(RobotInfo.video_uri) + .select_from(RobotInfo) + .where(RobotInfo.robot_id == robot_id) + ) + + result = await query_db.execute(stmt) + + return result.scalars().first() + + @classmethod + async def refresh_robot_by_ids(cls, db: AsyncSession, robot_ids: str): + id_list = map(int, robot_ids.strip(" ").split(',')) + refresh_result = ( + ( + await db.execute( + select(RobotInfo) + .where(RobotInfo.robot_id.in_(id_list)) + + ) + + ) + .scalars() + .all() + ) + return refresh_result + + @classmethod + async def get_info_detail_by_id(cls, db: AsyncSession, robot_id: int): + """ + 根据机器人ID获取机器人信息详细信息 + + :param db: orm对象 + :param robot_id: 机器人ID + :return: 机器人信息信息对象 + """ + info_info = ( + ( + await db.execute( + select(RobotInfo) + .where( + RobotInfo.robot_id == robot_id + ) + ) + ) + .scalars() + .first() + ) + + return info_info + + @classmethod + async def get_info_detail_by_info(cls, db: AsyncSession, info: InfoModel): + """ + 根据机器人信息参数获取机器人信息信息 + + :param db: orm对象 + :param info: 机器人信息参数对象 + :return: 机器人信息信息对象 + """ + info_info = ( + ( + await db.execute( + select(RobotInfo).where( + ) + ) + ) + .scalars() + .first() + ) + + return info_info + + @classmethod + async def get_info_list(cls, db: AsyncSession, query_object: InfoPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取机器人信息列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 机器人信息列表信息对象 + """ + query = ( + select(RobotInfo) + .where( + RobotInfo.name.like(f'%{query_object.name}%') if query_object.name else True, + RobotInfo.model == query_object.model if query_object.model else True, + RobotInfo.serial == query_object.serial if query_object.serial else True, + RobotInfo.online == query_object.online if query_object.online else True, + RobotInfo.power == query_object.power if query_object.power else True, + RobotInfo.run_time == query_object.run_time if query_object.run_time else True, + RobotInfo.position == query_object.position if query_object.position else True, + RobotInfo.image == query_object.image if query_object.image else True, + RobotInfo.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_create_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_create_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_create_time and query_object.end_create_time else True, + RobotInfo.update_time.between( + datetime.combine(datetime.strptime(query_object.begin_update_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_update_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_update_time and query_object.end_update_time else True + + ) + .order_by(desc(RobotInfo.create_time)) + .distinct() + ) + info_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return info_list + + @classmethod + async def add_info_dao(cls, db: AsyncSession, info: InfoModel): + """ + 新增机器人信息数据库操作 + + :param db: orm对象 + :param info: 机器人信息对象 + :return: + """ + db_info = RobotInfo(**info.model_dump(exclude={})) + db.add(db_info) + await db.flush() + + return db_info + + @classmethod + async def edit_info_dao(cls, db: AsyncSession, info: dict): + """ + 编辑机器人信息数据库操作 + + :param db: orm对象 + :param info: 需要更新的机器人信息字典 + :return: + """ + await db.execute(update(RobotInfo), [info]) + + @classmethod + async def delete_info_dao(cls, db: AsyncSession, info: InfoModel): + """ + 删除机器人信息数据库操作 + + :param db: orm对象 + :param info: 机器人信息对象 + :return: + """ + await db.execute(delete(RobotInfo).where(RobotInfo.robot_id.in_([info.robot_id]))) + diff --git a/ruoyi-fastapi-backend/module_admin/dao/job_dao.py b/ruoyi-fastapi-backend/module_admin/dao/job_dao.py new file mode 100644 index 0000000..805d460 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/job_dao.py @@ -0,0 +1,126 @@ +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.job_do import SysJob +from module_admin.entity.vo.job_vo import JobModel, JobPageQueryModel +from utils.page_util import PageUtil + + +class JobDao: + """ + 定时任务管理模块数据库操作层 + """ + + @classmethod + async def get_job_detail_by_id(cls, db: AsyncSession, job_id: int): + """ + 根据定时任务id获取定时任务详细信息 + + :param db: orm对象 + :param job_id: 定时任务id + :return: 定时任务信息对象 + """ + job_info = (await db.execute(select(SysJob).where(SysJob.job_id == job_id))).scalars().first() + + return job_info + + @classmethod + async def get_job_detail_by_info(cls, db: AsyncSession, job: JobModel): + """ + 根据定时任务参数获取定时任务信息 + + :param db: orm对象 + :param job: 定时任务参数对象 + :return: 定时任务信息对象 + """ + job_info = ( + ( + await db.execute( + select(SysJob).where( + SysJob.job_name == job.job_name, + SysJob.job_group == job.job_group, + SysJob.job_executor == job.job_executor, + SysJob.invoke_target == job.invoke_target, + SysJob.job_args == job.job_args, + SysJob.job_kwargs == job.job_kwargs, + SysJob.cron_expression == job.cron_expression, + ) + ) + ) + .scalars() + .first() + ) + + return job_info + + @classmethod + async def get_job_list(cls, db: AsyncSession, query_object: JobPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取定时任务列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 定时任务列表信息对象 + """ + query = ( + select(SysJob) + .where( + SysJob.job_name.like(f'%{query_object.job_name}%') if query_object.job_name else True, + SysJob.job_group == query_object.job_group if query_object.job_group else True, + SysJob.status == query_object.status if query_object.status else True, + ) + .order_by(SysJob.job_id) + .distinct() + ) + job_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return job_list + + @classmethod + async def get_job_list_for_scheduler(cls, db: AsyncSession): + """ + 获取定时任务列表信息 + + :param db: orm对象 + :return: 定时任务列表信息对象 + """ + job_list = (await db.execute(select(SysJob).where(SysJob.status == '0').distinct())).scalars().all() + + return job_list + + @classmethod + async def add_job_dao(cls, db: AsyncSession, job: JobModel): + """ + 新增定时任务数据库操作 + + :param db: orm对象 + :param job: 定时任务对象 + :return: + """ + db_job = SysJob(**job.model_dump()) + db.add(db_job) + await db.flush() + + return db_job + + @classmethod + async def edit_job_dao(cls, db: AsyncSession, job: dict): + """ + 编辑定时任务数据库操作 + + :param db: orm对象 + :param job: 需要更新的定时任务字典 + :return: + """ + await db.execute(update(SysJob), [job]) + + @classmethod + async def delete_job_dao(cls, db: AsyncSession, job: JobModel): + """ + 删除定时任务数据库操作 + + :param db: orm对象 + :param job: 定时任务对象 + :return: + """ + await db.execute(delete(SysJob).where(SysJob.job_id.in_([job.job_id]))) diff --git a/ruoyi-fastapi-backend/module_admin/dao/job_log_dao.py b/ruoyi-fastapi-backend/module_admin/dao/job_log_dao.py new file mode 100644 index 0000000..586a3d6 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/job_log_dao.py @@ -0,0 +1,79 @@ +from datetime import datetime, time +from sqlalchemy import delete, desc, select +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import Session +from module_admin.entity.do.job_do import SysJobLog +from module_admin.entity.vo.job_vo import JobLogModel, JobLogPageQueryModel +from utils.page_util import PageUtil + + +class JobLogDao: + """ + 定时任务日志管理模块数据库操作层 + """ + + @classmethod + async def get_job_log_list(cls, db: AsyncSession, query_object: JobLogPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取定时任务日志列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 定时任务日志列表信息对象 + """ + query = ( + select(SysJobLog) + .where( + SysJobLog.job_name.like(f'%{query_object.job_name}%') if query_object.job_name else True, + SysJobLog.job_group == query_object.job_group if query_object.job_group else True, + SysJobLog.status == query_object.status if query_object.status else True, + SysJobLog.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .order_by(desc(SysJobLog.create_time)) + .distinct() + ) + job_log_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return job_log_list + + @classmethod + def add_job_log_dao(cls, db: Session, job_log: JobLogModel): + """ + 新增定时任务日志数据库操作 + + :param db: orm对象 + :param job_log: 定时任务日志对象 + :return: + """ + db_job_log = SysJobLog(**job_log.model_dump()) + db.add(db_job_log) + db.flush() + + return db_job_log + + @classmethod + async def delete_job_log_dao(cls, db: AsyncSession, job_log: JobLogModel): + """ + 删除定时任务日志数据库操作 + + :param db: orm对象 + :param job_log: 定时任务日志对象 + :return: + """ + await db.execute(delete(SysJobLog).where(SysJobLog.job_log_id.in_([job_log.job_log_id]))) + + @classmethod + async def clear_job_log_dao(cls, db: AsyncSession): + """ + 清除定时任务日志数据库操作 + + :param db: orm对象 + :return: + """ + await db.execute(delete(SysJobLog)) diff --git a/ruoyi-fastapi-backend/module_admin/dao/log_dao.py b/ruoyi-fastapi-backend/module_admin/dao/log_dao.py new file mode 100644 index 0000000..684f3d7 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/log_dao.py @@ -0,0 +1,171 @@ +from datetime import datetime, time +from sqlalchemy import asc, delete, desc, select +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.log_do import SysLogininfor, SysOperLog +from module_admin.entity.vo.log_vo import LogininforModel, LoginLogPageQueryModel, OperLogModel, OperLogPageQueryModel +from utils.common_util import SnakeCaseUtil +from utils.page_util import PageUtil +from utils.time_format_util import TimeFormatUtil + + +class OperationLogDao: + """ + 操作日志管理模块数据库操作层 + """ + + @classmethod + async def get_operation_log_list(cls, db: AsyncSession, query_object: OperLogPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取操作日志列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 操作日志列表信息对象 + """ + if query_object.is_asc == 'ascending': + order_by_column = asc(getattr(SysOperLog, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None)) + elif query_object.is_asc == 'descending': + order_by_column = desc( + getattr(SysOperLog, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None) + ) + else: + order_by_column = desc(SysOperLog.oper_time) + query = ( + select(SysOperLog) + .where( + SysOperLog.title.like(f'%{query_object.title}%') if query_object.title else True, + SysOperLog.oper_name.like(f'%{query_object.oper_name}%') if query_object.oper_name else True, + SysOperLog.business_type == query_object.business_type if query_object.business_type else True, + SysOperLog.status == query_object.status if query_object.status else True, + SysOperLog.oper_time.between( + datetime.combine(TimeFormatUtil.parse_date(query_object.begin_time), time(00, 00, 00)), + datetime.combine(TimeFormatUtil.parse_date(query_object.end_time), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .distinct() + .order_by(order_by_column) + ) + operation_log_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return operation_log_list + + @classmethod + async def add_operation_log_dao(cls, db: AsyncSession, operation_log: OperLogModel): + """ + 新增操作日志数据库操作 + + :param db: orm对象 + :param operation_log: 操作日志对象 + :return: 新增校验结果 + """ + db_operation_log = SysOperLog(**operation_log.model_dump()) + db.add(db_operation_log) + await db.flush() + + return db_operation_log + + @classmethod + async def delete_operation_log_dao(cls, db: AsyncSession, operation_log: OperLogModel): + """ + 删除操作日志数据库操作 + + :param db: orm对象 + :param operation_log: 操作日志对象 + :return: + """ + await db.execute(delete(SysOperLog).where(SysOperLog.oper_id.in_([operation_log.oper_id]))) + + @classmethod + async def clear_operation_log_dao(cls, db: AsyncSession): + """ + 清除操作日志数据库操作 + + :param db: orm对象 + :return: + """ + await db.execute(delete(SysOperLog)) + + +class LoginLogDao: + """ + 登录日志管理模块数据库操作层 + """ + + @classmethod + async def get_login_log_list(cls, db: AsyncSession, query_object: LoginLogPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取登录日志列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 登录日志列表信息对象 + """ + if query_object.is_asc == 'ascending': + order_by_column = asc( + getattr(SysLogininfor, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None) + ) + elif query_object.is_asc == 'descending': + order_by_column = desc( + getattr(SysLogininfor, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None) + ) + else: + order_by_column = desc(SysLogininfor.login_time) + query = ( + select(SysLogininfor) + .where( + SysLogininfor.ipaddr.like(f'%{query_object.ipaddr}%') if query_object.ipaddr else True, + SysLogininfor.user_name.like(f'%{query_object.user_name}%') if query_object.user_name else True, + SysLogininfor.status == query_object.status if query_object.status else True, + SysLogininfor.login_time.between( + datetime.combine(TimeFormatUtil.parse_date(query_object.begin_time), time(00, 00, 00)), + datetime.combine(TimeFormatUtil.parse_date(query_object.end_time), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .distinct() + .order_by(order_by_column) + ) + login_log_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return login_log_list + + @classmethod + async def add_login_log_dao(cls, db: AsyncSession, login_log: LogininforModel): + """ + 新增登录日志数据库操作 + + :param db: orm对象 + :param login_log: 登录日志对象 + :return: 新增校验结果 + """ + db_login_log = SysLogininfor(**login_log.model_dump()) + db.add(db_login_log) + await db.flush() + + return db_login_log + + @classmethod + async def delete_login_log_dao(cls, db: AsyncSession, login_log: LogininforModel): + """ + 删除登录日志数据库操作 + + :param db: orm对象 + :param login_log: 登录日志对象 + :return: + """ + await db.execute(delete(SysLogininfor).where(SysLogininfor.info_id.in_([login_log.info_id]))) + + @classmethod + async def clear_login_log_dao(cls, db: AsyncSession): + """ + 清除登录日志数据库操作 + + :param db: orm对象 + :return: + """ + await db.execute(delete(SysLogininfor)) diff --git a/ruoyi-fastapi-backend/module_admin/dao/login_dao.py b/ruoyi-fastapi-backend/module_admin/dao/login_dao.py new file mode 100644 index 0000000..9764a4a --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/login_dao.py @@ -0,0 +1,28 @@ +from sqlalchemy import and_, select +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.dept_do import SysDept +from module_admin.entity.do.user_do import SysUser + + +async def login_by_account(db: AsyncSession, user_name: str): + """ + 根据用户名查询用户信息 + + :param db: orm对象 + :param user_name: 用户名 + :return: 用户对象 + """ + user = ( + await db.execute( + select(SysUser, SysDept) + .where(SysUser.user_name == user_name, SysUser.del_flag == '0') + .join( + SysDept, + and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'), + isouter=True, + ) + .distinct() + ) + ).first() + + return user diff --git a/ruoyi-fastapi-backend/module_admin/dao/menu_dao.py b/ruoyi-fastapi-backend/module_admin/dao/menu_dao.py new file mode 100644 index 0000000..976fcdf --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/menu_dao.py @@ -0,0 +1,227 @@ +from sqlalchemy import and_, delete, func, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.menu_do import SysMenu +from module_admin.entity.do.role_do import SysRole, SysRoleMenu +from module_admin.entity.do.user_do import SysUser, SysUserRole +from module_admin.entity.vo.menu_vo import MenuModel, MenuQueryModel + + +class MenuDao: + """ + 菜单管理模块数据库操作层 + """ + + @classmethod + async def get_menu_detail_by_id(cls, db: AsyncSession, menu_id: int): + """ + 根据菜单id获取菜单详细信息 + + :param db: orm对象 + :param menu_id: 菜单id + :return: 菜单信息对象 + """ + menu_info = (await db.execute(select(SysMenu).where(SysMenu.menu_id == menu_id))).scalars().first() + + return menu_info + + @classmethod + async def get_menu_detail_by_info(cls, db: AsyncSession, menu: MenuModel): + """ + 根据菜单参数获取菜单信息 + + :param db: orm对象 + :param menu: 菜单参数对象 + :return: 菜单信息对象 + """ + menu_info = ( + ( + await db.execute( + select(SysMenu).where( + SysMenu.parent_id == menu.parent_id if menu.parent_id else True, + SysMenu.menu_name == menu.menu_name if menu.menu_name else True, + SysMenu.menu_type == menu.menu_type if menu.menu_type else True, + ) + ) + ) + .scalars() + .first() + ) + + return menu_info + + @classmethod + async def get_menu_list_for_tree(cls, db: AsyncSession, user_id: int, role: list): + """ + 根据角色信息获取所有在用菜单列表信息 + + :param db: orm对象 + :param user_id: 用户id + :param role: 用户角色列表信息 + :return: 菜单列表信息 + """ + role_id_list = [item.role_id for item in role] + if 1 in role_id_list: + menu_query_all = ( + (await db.execute(select(SysMenu).where(SysMenu.status == '0' + # , SysMenu.visible == '0' + ).order_by(SysMenu.order_num).distinct())) + .scalars() + .all() + ) + else: + menu_query_all = ( + ( + await db.execute( + select(SysMenu) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_( + SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0' + ), + isouter=True, + ) + .join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True) + .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == '0' + # , SysMenu.visible == '0' + )) + .order_by(SysMenu.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) + + return menu_query_all + + @classmethod + async def get_menu_list(cls, db: AsyncSession, page_object: MenuQueryModel, user_id: int, role: list): + """ + 根据查询参数获取菜单列表信息 + + :param db: orm对象 + :param page_object: 不分页查询参数对象 + :param user_id: 用户id + :param role: 用户角色列表 + :return: 菜单列表信息对象 + """ + role_id_list = [item.role_id for item in role] + if 1 in role_id_list: + menu_query_all = ( + ( + await db.execute( + select(SysMenu) + .where( + SysMenu.status == page_object.status if page_object.status else True, + SysMenu.menu_name.like(f'%{page_object.menu_name}%') if page_object.menu_name else True, + ) + .order_by(SysMenu.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) + else: + menu_query_all = ( + ( + await db.execute( + select(SysMenu) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_( + SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0' + ), + isouter=True, + ) + .join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True) + .join( + SysMenu, + and_( + SysRoleMenu.menu_id == SysMenu.menu_id, + SysMenu.status == page_object.status if page_object.status else True, + SysMenu.menu_name.like(f'%{page_object.menu_name}%') if page_object.menu_name else True, + ), + ) + .order_by(SysMenu.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) + + return menu_query_all + + @classmethod + async def add_menu_dao(cls, db: AsyncSession, menu: MenuModel): + """ + 新增菜单数据库操作 + + :param db: orm对象 + :param menu: 菜单对象 + :return: + """ + db_menu = SysMenu(**menu.model_dump()) + db.add(db_menu) + await db.flush() + + return db_menu + + @classmethod + async def edit_menu_dao(cls, db: AsyncSession, menu: dict): + """ + 编辑菜单数据库操作 + + :param db: orm对象 + :param menu: 需要更新的菜单字典 + :return: + """ + await db.execute(update(SysMenu), [menu]) + + @classmethod + async def delete_menu_dao(cls, db: AsyncSession, menu: MenuModel): + """ + 删除菜单数据库操作 + + :param db: orm对象 + :param menu: 菜单对象 + :return: + """ + await db.execute(delete(SysMenu).where(SysMenu.menu_id.in_([menu.menu_id]))) + + @classmethod + async def has_child_by_menu_id_dao(cls, db: AsyncSession, menu_id: int): + """ + 根据菜单id查询菜单关联子菜单的数量 + + :param db: orm对象 + :param menu_id: 菜单id + :return: 菜单关联子菜单的数量 + """ + menu_count = ( + await db.execute(select(func.count('*')).select_from(SysMenu).where(SysMenu.parent_id == menu_id)) + ).scalar() + + return menu_count + + @classmethod + async def check_menu_exist_role_dao(cls, db: AsyncSession, menu_id: int): + """ + 根据菜单id查询菜单关联角色数量 + + :param db: orm对象 + :param menu_id: 菜单id + :return: 菜单关联角色数量 + """ + role_count = ( + await db.execute(select(func.count('*')).select_from(SysRoleMenu).where(SysRoleMenu.menu_id == menu_id)) + ).scalar() + + return role_count diff --git a/ruoyi-fastapi-backend/module_admin/dao/message_dao.py b/ruoyi-fastapi-backend/module_admin/dao/message_dao.py new file mode 100644 index 0000000..d2b90c0 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/message_dao.py @@ -0,0 +1,175 @@ +from sqlalchemy import delete, select, update, func +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.message_do import Message +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.message_vo import MessageModel, MessagePageQueryModel +from utils.page_util import PageUtil +from datetime import datetime, time + + +class MessageDao: + """ + 系统消息模块数据库操作层 + """ + + @classmethod + async def get_alarm_type_distribution(cls, db: AsyncSession): + stmt = ( + select(Message.specific_type, func.count(Message.specific_type).label("cnt")) + .where(Message.type == '0') + .group_by(Message.specific_type) + ) + return (await db.execute(stmt)).mappings().all() + + @classmethod + async def handle_all_message(cls, db: AsyncSession): + query = ( + update(Message) + .where(Message.status == '0', Message.type == '0') + .values(status='1') + ) + + + await db.execute(query) + + + + @classmethod + async def get_message_detail_by_id(cls, db: AsyncSession, message_id: int): + """ + 根据消息主键获取系统消息详细信息 + + :param db: orm对象 + :param message_id: 消息主键 + :return: 系统消息信息对象 + """ + message_info = ( + ( + await db.execute( + select(Message) + .where( + Message.message_id == message_id + ) + ) + ) + .scalars() + .first() + ) + + return message_info + + @classmethod + async def count_message(cls, db: AsyncSession): + query_unhandled_message_count = ( + select(func.count(Message.message_id)) + .where(Message.status=='0', Message.type=='0') + ) + + unhandled_message_count = ( + await db.execute(query_unhandled_message_count) + ).scalar() + + query_handled_message_count = ( + select(func.count(Message.message_id)) + .where(Message.status=='1', Message.type=='0') + ) + + handled_message_count = ( + await db.execute(query_handled_message_count) + ).scalar() + + return unhandled_message_count+ handled_message_count, unhandled_message_count, handled_message_count + + @classmethod + async def get_message_detail_by_info(cls, db: AsyncSession, message: MessageModel): + """ + 根据系统消息参数获取系统消息信息 + + :param db: orm对象 + :param message: 系统消息参数对象 + :return: 系统消息信息对象 + """ + message_info = ( + ( + await db.execute( + select(Message).where( + ) + ) + ) + .scalars() + .first() + ) + + return message_info + + @classmethod + async def get_message_list(cls, db: AsyncSession, query_object: MessagePageQueryModel, is_page: bool = False): + """ + 根据查询参数获取系统消息列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 系统消息列表信息对象 + """ + query = ( + select(Message) + .where( + Message.type == query_object.type if query_object.type else True, + Message.status == query_object.status if query_object.status else True, + # Message.detail == query_object.detail if query_object.detail else True, + Message.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_create_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_create_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_create_time and query_object.end_create_time else True, + Message.update_time.between( + datetime.combine(datetime.strptime(query_object.begin_update_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_update_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_update_time and query_object.end_update_time else True + ) + .order_by(Message.message_id) + .distinct() + ) + message_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return message_list + + @classmethod + async def add_message_dao(cls, db: AsyncSession, message: MessageModel): + """ + 新增系统消息数据库操作 + + :param db: orm对象 + :param message: 系统消息对象 + :return: + """ + db_message = Message(**message.model_dump(exclude={})) + db.add(db_message) + await db.flush() + + return db_message + + @classmethod + async def edit_message_dao(cls, db: AsyncSession, message: dict): + """ + 编辑系统消息数据库操作 + + :param db: orm对象 + :param message: 需要更新的系统消息字典 + :return: + """ + await db.execute(update(Message), [message]) + + @classmethod + async def delete_message_dao(cls, db: AsyncSession, message: MessageModel): + """ + 删除系统消息数据库操作 + + :param db: orm对象 + :param message: 系统消息对象 + :return: + """ + await db.execute(delete(Message).where(Message.message_id.in_([message.message_id]))) + diff --git a/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py b/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py new file mode 100644 index 0000000..9f48a14 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py @@ -0,0 +1,117 @@ +from datetime import datetime, time +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.notice_do import SysNotice +from module_admin.entity.vo.notice_vo import NoticeModel, NoticePageQueryModel +from utils.page_util import PageUtil + + +class NoticeDao: + """ + 通知公告管理模块数据库操作层 + """ + + @classmethod + async def get_notice_detail_by_id(cls, db: AsyncSession, notice_id: int): + """ + 根据通知公告id获取通知公告详细信息 + + :param db: orm对象 + :param notice_id: 通知公告id + :return: 通知公告信息对象 + """ + notice_info = (await db.execute(select(SysNotice).where(SysNotice.notice_id == notice_id))).scalars().first() + + return notice_info + + @classmethod + async def get_notice_detail_by_info(cls, db: AsyncSession, notice: NoticeModel): + """ + 根据通知公告参数获取通知公告信息 + + :param db: orm对象 + :param notice: 通知公告参数对象 + :return: 通知公告信息对象 + """ + notice_info = ( + ( + await db.execute( + select(SysNotice).where( + SysNotice.notice_title == notice.notice_title, + SysNotice.notice_type == notice.notice_type, + SysNotice.notice_content == notice.notice_content, + ) + ) + ) + .scalars() + .first() + ) + + return notice_info + + @classmethod + async def get_notice_list(cls, db: AsyncSession, query_object: NoticePageQueryModel, is_page: bool = False): + """ + 根据查询参数获取通知公告列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 通知公告列表信息对象 + """ + query = ( + select(SysNotice) + .where( + SysNotice.notice_title.like(f'%{query_object.notice_title}%') if query_object.notice_title else True, + SysNotice.create_by.like(f'%{query_object.create_by}%') if query_object.create_by else True, + SysNotice.notice_type == query_object.notice_type if query_object.notice_type else True, + SysNotice.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .order_by(SysNotice.notice_id) + .distinct() + ) + notice_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return notice_list + + @classmethod + async def add_notice_dao(cls, db: AsyncSession, notice: NoticeModel): + """ + 新增通知公告数据库操作 + + :param db: orm对象 + :param notice: 通知公告对象 + :return: + """ + db_notice = SysNotice(**notice.model_dump()) + db.add(db_notice) + await db.flush() + + return db_notice + + @classmethod + async def edit_notice_dao(cls, db: AsyncSession, notice: dict): + """ + 编辑通知公告数据库操作 + + :param db: orm对象 + :param notice: 需要更新的通知公告字典 + :return: + """ + await db.execute(update(SysNotice), [notice]) + + @classmethod + async def delete_notice_dao(cls, db: AsyncSession, notice: NoticeModel): + """ + 删除通知公告数据库操作 + + :param db: orm对象 + :param notice: 通知公告对象 + :return: + """ + await db.execute(delete(SysNotice).where(SysNotice.notice_id.in_([notice.notice_id]))) diff --git a/ruoyi-fastapi-backend/module_admin/dao/post_dao.py b/ruoyi-fastapi-backend/module_admin/dao/post_dao.py new file mode 100644 index 0000000..7d90088 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/post_dao.py @@ -0,0 +1,143 @@ +from sqlalchemy import delete, func, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.post_do import SysPost +from module_admin.entity.do.user_do import SysUserPost +from module_admin.entity.vo.post_vo import PostModel, PostPageQueryModel +from utils.page_util import PageUtil + + +class PostDao: + """ + 岗位管理模块数据库操作层 + """ + + @classmethod + async def get_post_by_id(cls, db: AsyncSession, post_id: int): + """ + 根据岗位id获取在用岗位详细信息 + + :param db: orm对象 + :param post_id: 岗位id + :return: 在用岗位信息对象 + """ + post_info = ( + (await db.execute(select(SysPost).where(SysPost.post_id == post_id, SysPost.status == '0'))) + .scalars() + .first() + ) + + return post_info + + @classmethod + async def get_post_detail_by_id(cls, db: AsyncSession, post_id: int): + """ + 根据岗位id获取岗位详细信息 + + :param db: orm对象 + :param post_id: 岗位id + :return: 岗位信息对象 + """ + post_info = (await db.execute(select(SysPost).where(SysPost.post_id == post_id))).scalars().first() + + return post_info + + @classmethod + async def get_post_detail_by_info(cls, db: AsyncSession, post: PostModel): + """ + 根据岗位参数获取岗位信息 + + :param db: orm对象 + :param post: 岗位参数对象 + :return: 岗位信息对象 + """ + post_info = ( + ( + await db.execute( + select(SysPost).where( + SysPost.post_name == post.post_name if post.post_name else True, + SysPost.post_code == post.post_code if post.post_code else True, + SysPost.post_sort == post.post_sort if post.post_sort else True, + ) + ) + ) + .scalars() + .first() + ) + + return post_info + + @classmethod + async def get_post_list(cls, db: AsyncSession, query_object: PostPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取岗位列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 岗位列表信息对象 + """ + query = ( + select(SysPost) + .where( + SysPost.post_code.like(f'%{query_object.post_code}%') if query_object.post_code else True, + SysPost.post_name.like(f'%{query_object.post_name}%') if query_object.post_name else True, + SysPost.status == query_object.status if query_object.status else True, + ) + .order_by(SysPost.post_sort) + .distinct() + ) + post_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return post_list + + @classmethod + async def add_post_dao(cls, db: AsyncSession, post: PostModel): + """ + 新增岗位数据库操作 + + :param db: orm对象 + :param post: 岗位对象 + :return: + """ + db_post = SysPost(**post.model_dump()) + db.add(db_post) + await db.flush() + + return db_post + + @classmethod + async def edit_post_dao(cls, db: AsyncSession, post: dict): + """ + 编辑岗位数据库操作 + + :param db: orm对象 + :param post: 需要更新的岗位字典 + :return: + """ + await db.execute(update(SysPost), [post]) + + @classmethod + async def delete_post_dao(cls, db: AsyncSession, post: PostModel): + """ + 删除岗位数据库操作 + + :param db: orm对象 + :param post: 岗位对象 + :return: + """ + await db.execute(delete(SysPost).where(SysPost.post_id.in_([post.post_id]))) + + @classmethod + async def count_user_post_dao(cls, db: AsyncSession, post_id: int): + """ + 根据岗位id查询岗位关联的用户数量 + + :param db: orm对象 + :param post_id: 岗位id + :return: 岗位关联的用户数量 + """ + user_post_count = ( + await db.execute(select(func.count('*')).select_from(SysUserPost).where(SysUserPost.post_id == post_id)) + ).scalar() + + return user_post_count diff --git a/ruoyi-fastapi-backend/module_admin/dao/robot_action_dao.py b/ruoyi-fastapi-backend/module_admin/dao/robot_action_dao.py new file mode 100644 index 0000000..4e8d230 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/robot_action_dao.py @@ -0,0 +1,119 @@ +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.robot_action_do import RobotAction +from module_admin.entity.vo.robot_action_vo import Robot_actionModel, Robot_actionPageQueryModel +from utils.page_util import PageUtil + + +class Robot_actionDao: + """ + 机器人动作模块数据库操作层 + """ + + @classmethod + async def get_robot_action_detail_by_id(cls, db: AsyncSession, id: int): + """ + 根据主键 自增获取机器人动作详细信息 + + :param db: orm对象 + :param id: 主键 自增 + :return: 机器人动作信息对象 + """ + robot_action_info = ( + ( + await db.execute( + select(RobotAction) + .where( + RobotAction.id == id + ) + ) + ) + .scalars() + .first() + ) + + return robot_action_info + + @classmethod + async def get_robot_action_detail_by_info(cls, db: AsyncSession, robot_action: Robot_actionModel): + """ + 根据机器人动作参数获取机器人动作信息 + + :param db: orm对象 + :param robot_action: 机器人动作参数对象 + :return: 机器人动作信息对象 + """ + robot_action_info = ( + ( + await db.execute( + select(RobotAction).where( + ) + ) + ) + .scalars() + .first() + ) + + return robot_action_info + + @classmethod + async def get_robot_action_list(cls, db: AsyncSession, query_object: Robot_actionPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取机器人动作列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 机器人动作列表信息对象 + """ + query = ( + select(RobotAction) + .where( + RobotAction.id == query_object.id if query_object.id else True, + RobotAction.name.like(f'%{query_object.name}%') if query_object.name else True, + RobotAction.status == query_object.status if query_object.status else True, + ) + .order_by(RobotAction.id) + .distinct() + ) + robot_action_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return robot_action_list + + @classmethod + async def add_robot_action_dao(cls, db: AsyncSession, robot_action: Robot_actionModel): + """ + 新增机器人动作数据库操作 + + :param db: orm对象 + :param robot_action: 机器人动作对象 + :return: + """ + db_robot_action = RobotAction(**robot_action.model_dump(exclude={})) + db.add(db_robot_action) + await db.flush() + + return db_robot_action + + @classmethod + async def edit_robot_action_dao(cls, db: AsyncSession, robot_action: dict): + """ + 编辑机器人动作数据库操作 + + :param db: orm对象 + :param robot_action: 需要更新的机器人动作字典 + :return: + """ + await db.execute(update(RobotAction), [robot_action]) + + @classmethod + async def delete_robot_action_dao(cls, db: AsyncSession, robot_action: Robot_actionModel): + """ + 删除机器人动作数据库操作 + + :param db: orm对象 + :param robot_action: 机器人动作对象 + :return: + """ + await db.execute(delete(RobotAction).where(RobotAction.id.in_([robot_action.id]))) + diff --git a/ruoyi-fastapi-backend/module_admin/dao/robot_role_dao.py b/ruoyi-fastapi-backend/module_admin/dao/robot_role_dao.py new file mode 100644 index 0000000..a9de2ab --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/robot_role_dao.py @@ -0,0 +1,131 @@ +from datetime import datetime, time +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.role_do import RobotRole +from module_admin.entity.vo.role_vo import RoleModel, RolePageQueryModel +from utils.page_util import PageUtil + + +class RoleDao: + """ + 系统角色模块数据库操作层 + """ + + @classmethod + async def get_role_detail_by_id(cls, db: AsyncSession, robot_role_id: int): + """ + 根据机器人角色ID获取系统角色详细信息 + + :param db: orm对象 + :param robot_role_id: 机器人角色ID + :return: 系统角色信息对象 + """ + role_info = ( + ( + await db.execute( + select(RobotRole) + .where( + RobotRole.robot_role_id == robot_role_id + ) + ) + ) + .scalars() + .first() + ) + + return role_info + + @classmethod + async def get_role_detail_by_info(cls, db: AsyncSession, role: RoleModel): + """ + 根据系统角色参数获取系统角色信息 + + :param db: orm对象 + :param role: 系统角色参数对象 + :return: 系统角色信息对象 + """ + role_info = ( + ( + await db.execute( + select(RobotRole).where( + ) + ) + ) + .scalars() + .first() + ) + + return role_info + + @classmethod + async def get_role_list(cls, db: AsyncSession, query_object: RolePageQueryModel, is_page: bool = False): + """ + 根据查询参数获取系统角色列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 系统角色列表信息对象 + """ + query = ( + select(RobotRole) + .where( + RobotRole.rolename.like(f'%{query_object.rolename}%') if query_object.rolename else True, + RobotRole.detail == query_object.detail if query_object.detail else True, + RobotRole.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_create_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_create_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_create_time and query_object.end_create_time + else True, + RobotRole.update_time.between( + datetime.combine(datetime.strptime(query_object.begin_update_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_update_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_update_time and query_object.end_update_time + else True, + ) + .order_by(RobotRole.robot_role_id) + .distinct() + ) + role_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return role_list + + @classmethod + async def add_role_dao(cls, db: AsyncSession, role: RoleModel): + """ + 新增系统角色数据库操作 + + :param db: orm对象 + :param role: 系统角色对象 + :return: + """ + db_role = RobotRole(**role.model_dump(exclude={})) + db.add(db_role) + await db.flush() + + return db_role + + @classmethod + async def edit_role_dao(cls, db: AsyncSession, role: dict): + """ + 编辑系统角色数据库操作 + + :param db: orm对象 + :param role: 需要更新的系统角色字典 + :return: + """ + await db.execute(update(RobotRole), [role]) + + @classmethod + async def delete_role_dao(cls, db: AsyncSession, role: RoleModel): + """ + 删除系统角色数据库操作 + + :param db: orm对象 + :param role: 系统角色对象 + :return: + """ + await db.execute(delete(RobotRole).where(RobotRole.robot_role_id.in_([role.robot_role_id]))) + diff --git a/ruoyi-fastapi-backend/module_admin/dao/robot_role_pairing_dao.py b/ruoyi-fastapi-backend/module_admin/dao/robot_role_pairing_dao.py new file mode 100644 index 0000000..a00e348 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/robot_role_pairing_dao.py @@ -0,0 +1,153 @@ +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession + +from module_admin.entity.do.robot_role_do import RobotRole +from module_admin.entity.do.robot_role_pairing_do import RobotRolePairing + +from module_admin.entity.vo.robot_role_pairing_vo import PairingModel, PairingPageQueryModel +from utils.page_util import PageUtil + + +class PairingDao: + """ + 角色-机器人-映射模块数据库操作层 + """ + + @classmethod + async def edit_pairing_status(cls, db: AsyncSession,robot_id: int = None, pairing_id: int = None, status: str = '0'): + + condition = list() + if robot_id: + condition.append(RobotRolePairing.robot_id == robot_id) + if pairing_id: + condition.append(RobotRolePairing.pairing_id == pairing_id) + query = ( + update(RobotRolePairing) + .values(status=status) + .where( + * condition + ) + ) + await db.execute(query) + + @classmethod + async def get_role_pairing_list(cls, db: AsyncSession, robot_id: int): + + query = ( + select(RobotRolePairing.pairing_id, RobotRole.robot_role_id ,RobotRole.rolename, RobotRole.detail, RobotRolePairing.status) + .select_from(RobotRolePairing) + .join(RobotRole, RobotRole.robot_role_id == RobotRolePairing.robot_role_id) + .where(RobotRolePairing.robot_id == robot_id) + ) + role_pairing_list = ( + await db.execute(query) + ).mappings().all() + return role_pairing_list + @classmethod + async def get_pairing_detail_by_id(cls, db: AsyncSession, pairing_id: int): + """ + 根据主键ID获取角色-机器人-映射详细信息 + + :param db: orm对象 + :param pairing_id: 主键ID + :return: 角色-机器人-映射信息对象 + """ + pairing_info = ( + ( + await db.execute( + select(RobotRolePairing) + .where( + RobotRolePairing.pairing_id == pairing_id + ) + ) + ) + .scalars() + .first() + ) + + return pairing_info + + @classmethod + async def get_pairing_detail_by_info(cls, db: AsyncSession, pairing: PairingModel): + """ + 根据角色-机器人-映射参数获取角色-机器人-映射信息 + + :param db: orm对象 + :param pairing: 角色-机器人-映射参数对象 + :return: 角色-机器人-映射信息对象 + """ + pairing_info = ( + ( + await db.execute( + select(RobotRolePairing).where( + ) + ) + ) + .scalars() + .first() + ) + + return pairing_info + + @classmethod + async def get_pairing_list(cls, db: AsyncSession, query_object: PairingPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取角色-机器人-映射列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 角色-机器人-映射列表信息对象 + """ + query = ( + select(RobotRolePairing) + .where( + RobotRolePairing.pairing_id == query_object.pairing_id if query_object.pairing_id else True, + RobotRolePairing.robot_role_id == query_object.robot_role_id if query_object.robot_role_id else True, + RobotRolePairing.robot_id == query_object.robot_id if query_object.robot_id else True, + RobotRolePairing.status == query_object.status if query_object.status else True, + ) + .order_by(RobotRolePairing.pairing_id) + .distinct() + ) + pairing_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return pairing_list + + @classmethod + async def add_pairing_dao(cls, db: AsyncSession, pairing: PairingModel): + """ + 新增角色-机器人-映射数据库操作 + + :param db: orm对象 + :param pairing: 角色-机器人-映射对象 + :return: + """ + db_pairing = RobotRolePairing(**pairing.model_dump(exclude={})) + db.add(db_pairing) + await db.flush() + + return db_pairing + + @classmethod + async def edit_pairing_dao(cls, db: AsyncSession, pairing: dict): + """ + 编辑角色-机器人-映射数据库操作 + + :param db: orm对象 + :param pairing: 需要更新的角色-机器人-映射字典 + :return: + """ + await db.execute(update(RobotRolePairing), [pairing]) + + @classmethod + async def delete_pairing_dao(cls, db: AsyncSession, pairing: PairingModel): + """ + 删除角色-机器人-映射数据库操作 + + :param db: orm对象 + :param pairing: 角色-机器人-映射对象 + :return: + """ + await db.execute(delete(RobotRolePairing).where(RobotRolePairing.pairing_id.in_([pairing.pairing_id]))) + diff --git a/ruoyi-fastapi-backend/module_admin/dao/role_dao.py b/ruoyi-fastapi-backend/module_admin/dao/role_dao.py new file mode 100644 index 0000000..534c7f3 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/role_dao.py @@ -0,0 +1,338 @@ +from datetime import datetime, time +from sqlalchemy import and_, delete, desc, func, or_, select, update # noqa: F401 +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.dept_do import SysDept +from module_admin.entity.do.menu_do import SysMenu +from module_admin.entity.do.role_do import SysRole, SysRoleMenu, SysRoleDept +from module_admin.entity.do.user_do import SysUser, SysUserRole +from module_admin.entity.vo.role_vo import RoleDeptModel, RoleMenuModel, RoleModel, RolePageQueryModel +from utils.page_util import PageUtil + + +class RoleDao: + """ + 角色管理模块数据库操作层 + """ + + @classmethod + async def get_role_by_name(cls, db: AsyncSession, role_name: str): + """ + 根据角色名获取在用角色信息 + + :param db: orm对象 + :param role_name: 角色名 + :return: 当前角色名的角色信息对象 + """ + query_role_info = ( + ( + await db.execute( + select(SysRole) + .where(SysRole.status == '0', SysRole.del_flag == '0', SysRole.role_name == role_name) + .order_by(desc(SysRole.create_time)) + .distinct() + ) + ) + .scalars() + .first() + ) + + return query_role_info + + @classmethod + async def get_role_by_info(cls, db: AsyncSession, role: RoleModel): + """ + 根据角色参数获取角色信息 + + :param db: orm对象 + :param role: 角色参数 + :return: 当前角色参数的角色信息对象 + """ + query_role_info = ( + ( + await db.execute( + select(SysRole) + .where( + SysRole.del_flag == '0', + SysRole.role_name == role.role_name if role.role_name else True, + SysRole.role_key == role.role_key if role.role_key else True, + ) + .order_by(desc(SysRole.create_time)) + .distinct() + ) + ) + .scalars() + .first() + ) + + return query_role_info + + @classmethod + async def get_role_by_id(cls, db: AsyncSession, role_id: int): + """ + 根据角色id获取在用角色信息 + + :param db: orm对象 + :param role_id: 角色id + :return: 当前角色id的角色信息对象 + """ + role_info = ( + ( + await db.execute( + select(SysRole).where(SysRole.role_id == role_id, SysRole.status == '0', SysRole.del_flag == '0') + ) + ) + .scalars() + .first() + ) + + return role_info + + @classmethod + async def get_role_detail_by_id(cls, db: AsyncSession, role_id: int): + """ + 根据role_id获取角色详细信息 + + :param db: orm对象 + :param role_id: 角色id + :return: 当前role_id的角色信息对象 + """ + query_role_info = ( + (await db.execute(select(SysRole).where(SysRole.del_flag == '0', SysRole.role_id == role_id).distinct())) + .scalars() + .first() + ) + + return query_role_info + + @classmethod + async def get_role_select_option_dao(cls, db: AsyncSession): + """ + 获取编辑页面对应的在用角色列表信息 + + :param db: orm对象 + :return: 角色列表信息 + """ + role_info = ( + ( + await db.execute( + select(SysRole).where(SysRole.role_id != 1, SysRole.status == '0', SysRole.del_flag == '0') + ) + ) + .scalars() + .all() + ) + + return role_info + + @classmethod + async def get_role_list( + cls, db: AsyncSession, query_object: RolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): + """ + 根据查询参数获取角色列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :param is_page: 是否开启分页 + :return: 角色列表信息对象 + """ + query = ( + select(SysRole) + .join(SysUserRole, SysUserRole.role_id == SysRole.role_id, isouter=True) + .join(SysUser, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join(SysDept, SysDept.dept_id == SysUser.dept_id, isouter=True) + .where( + SysRole.del_flag == '0', + SysRole.role_id == query_object.role_id if query_object.role_id is not None else True, + SysRole.role_name.like(f'%{query_object.role_name}%') if query_object.role_name else True, + SysRole.role_key.like(f'%{query_object.role_key}%') if query_object.role_key else True, + SysRole.status == query_object.status if query_object.status else True, + SysRole.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + eval(data_scope_sql), + ) + .order_by(SysRole.role_sort) + .distinct() + ) + role_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return role_list + + @classmethod + async def add_role_dao(cls, db: AsyncSession, role: RoleModel): + """ + 新增角色数据库操作 + + :param db: orm对象 + :param role: 角色对象 + :return: + """ + db_role = SysRole(**role.model_dump(exclude={'admin'})) + db.add(db_role) + await db.flush() + + return db_role + + @classmethod + async def edit_role_dao(cls, db: AsyncSession, role: dict): + """ + 编辑角色数据库操作 + + :param db: orm对象 + :param role: 需要更新的角色字典 + :return: + """ + await db.execute(update(SysRole), [role]) + + @classmethod + async def delete_role_dao(cls, db: AsyncSession, role: RoleModel): + """ + 删除角色数据库操作 + + :param db: orm对象 + :param role: 角色对象 + :return: + """ + await db.execute( + update(SysRole) + .where(SysRole.role_id == role.role_id) + .values(del_flag='2', update_by=role.update_by, update_time=role.update_time) + ) + + @classmethod + async def get_role_menu_dao(cls, db: AsyncSession, role: RoleModel): + """ + 根据角色id获取角色菜单关联列表信息 + + :param db: orm对象 + :param role: 角色对象 + :return: 角色菜单关联列表信息 + """ + role_menu_query_all = ( + ( + await db.execute( + select(SysMenu) + .join(SysRoleMenu, SysRoleMenu.menu_id == SysMenu.menu_id) + .where( + SysRoleMenu.role_id == role.role_id, + ~SysMenu.menu_id.in_( + select(SysMenu.parent_id) + .select_from(SysMenu) + .join( + SysRoleMenu, + and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysRoleMenu.role_id == role.role_id), + ) + ) + if role.menu_check_strictly + else True, + ) + .order_by(SysMenu.parent_id, SysMenu.order_num) + ) + ) + .scalars() + .all() + ) + + return role_menu_query_all + + @classmethod + async def add_role_menu_dao(cls, db: AsyncSession, role_menu: RoleMenuModel): + """ + 新增角色菜单关联信息数据库操作 + + :param db: orm对象 + :param role_menu: 用户角色菜单关联对象 + :return: + """ + db_role_menu = SysRoleMenu(**role_menu.model_dump()) + db.add(db_role_menu) + + @classmethod + async def delete_role_menu_dao(cls, db: AsyncSession, role_menu: RoleMenuModel): + """ + 删除角色菜单关联信息数据库操作 + + :param db: orm对象 + :param role_menu: 角色菜单关联对象 + :return: + """ + await db.execute(delete(SysRoleMenu).where(SysRoleMenu.role_id.in_([role_menu.role_id]))) + + @classmethod + async def get_role_dept_dao(cls, db: AsyncSession, role: RoleModel): + """ + 根据角色id获取角色部门关联列表信息 + + :param db: orm对象 + :param role: 角色对象 + :return: 角色部门关联列表信息 + """ + role_dept_query_all = ( + ( + await db.execute( + select(SysDept) + .join(SysRoleDept, SysRoleDept.dept_id == SysDept.dept_id) + .where( + SysRoleDept.role_id == role.role_id, + ~SysDept.dept_id.in_( + select(SysDept.parent_id) + .select_from(SysDept) + .join( + SysRoleDept, + and_(SysRoleDept.dept_id == SysDept.dept_id, SysRoleDept.role_id == role.role_id), + ) + ) + if role.dept_check_strictly + else True, + ) + .order_by(SysDept.parent_id, SysDept.order_num) + ) + ) + .scalars() + .all() + ) + + return role_dept_query_all + + @classmethod + async def add_role_dept_dao(cls, db: AsyncSession, role_dept: RoleDeptModel): + """ + 新增角色部门关联信息数据库操作 + + :param db: orm对象 + :param role_dept: 用户角色部门关联对象 + :return: + """ + db_role_dept = SysRoleDept(**role_dept.dict()) + db.add(db_role_dept) + + @classmethod + async def delete_role_dept_dao(cls, db: AsyncSession, role_dept: RoleDeptModel): + """ + 删除角色部门关联信息数据库操作 + + :param db: orm对象 + :param role_dept: 角色部门关联对象 + :return: + """ + await db.execute(delete(SysRoleDept).where(SysRoleDept.role_id.in_([role_dept.role_id]))) + + @classmethod + async def count_user_role_dao(cls, db: AsyncSession, role_id: int): + """ + 根据角色id查询角色关联用户数量 + + :param db: orm对象 + :param role_id: 角色id + :return: 角色关联用户数量 + """ + user_count = ( + await db.execute(select(func.count('*')).select_from(SysUserRole).where(SysUserRole.role_id == role_id)) + ).scalar() + + return user_count diff --git a/ruoyi-fastapi-backend/module_admin/dao/sys_alert_dao.py b/ruoyi-fastapi-backend/module_admin/dao/sys_alert_dao.py new file mode 100644 index 0000000..c6ecb0e --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/sys_alert_dao.py @@ -0,0 +1,132 @@ +from datetime import datetime, time +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.sys_alert_do import SysAlert +from module_admin.entity.vo.sys_alert_vo import Sys_alertModel, Sys_alertPageQueryModel +from utils.page_util import PageUtil + + +class Sys_alertDao: + """ + 系统告警模块数据库操作层 + """ + + @classmethod + async def get_sys_alert_detail_by_id(cls, db: AsyncSession, id: int): + """ + 根据主键 自增获取系统告警详细信息 + + :param db: orm对象 + :param id: 主键 自增 + :return: 系统告警信息对象 + """ + sys_alert_info = ( + ( + await db.execute( + select(SysAlert) + .where( + SysAlert.id == id + ) + ) + ) + .scalars() + .first() + ) + + return sys_alert_info + + @classmethod + async def get_sys_alert_detail_by_info(cls, db: AsyncSession, sys_alert: Sys_alertModel): + """ + 根据系统告警参数获取系统告警信息 + + :param db: orm对象 + :param sys_alert: 系统告警参数对象 + :return: 系统告警信息对象 + """ + sys_alert_info = ( + ( + await db.execute( + select(SysAlert).where( + ) + ) + ) + .scalars() + .first() + ) + + return sys_alert_info + + @classmethod + async def get_sys_alert_list(cls, db: AsyncSession, query_object: Sys_alertPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取系统告警列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 系统告警列表信息对象 + """ + query = ( + select(SysAlert) + .where( + SysAlert.detail.like(f'%{query_object.detail}%') if query_object.detail else True, + SysAlert.type == query_object.type if query_object.type else True, + SysAlert.status == query_object.status if query_object.status else True, + SysAlert.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_create_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_create_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_create_time and query_object.end_create_time + else True, + SysAlert.update_time.between( + datetime.combine(datetime.strptime(query_object.begin_update_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_update_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_update_time and query_object.end_update_time + else True, + ) + .order_by(SysAlert.id) + .distinct() + ) + sys_alert_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return sys_alert_list + + @classmethod + async def add_sys_alert_dao(cls, db: AsyncSession, sys_alert: Sys_alertModel): + """ + 新增系统告警数据库操作 + + :param db: orm对象 + :param sys_alert: 系统告警对象 + :return: + """ + db_sys_alert = SysAlert(**sys_alert.model_dump(exclude={})) + db.add(db_sys_alert) + await db.flush() + + return db_sys_alert + + @classmethod + async def edit_sys_alert_dao(cls, db: AsyncSession, sys_alert: dict): + """ + 编辑系统告警数据库操作 + + :param db: orm对象 + :param sys_alert: 需要更新的系统告警字典 + :return: + """ + await db.execute(update(SysAlert), [sys_alert]) + + @classmethod + async def delete_sys_alert_dao(cls, db: AsyncSession, sys_alert: Sys_alertModel): + """ + 删除系统告警数据库操作 + + :param db: orm对象 + :param sys_alert: 系统告警对象 + :return: + """ + await db.execute(delete(SysAlert).where(SysAlert.id.in_([sys_alert.id]))) + diff --git a/ruoyi-fastapi-backend/module_admin/dao/sys_statistics_dao.py b/ruoyi-fastapi-backend/module_admin/dao/sys_statistics_dao.py new file mode 100644 index 0000000..4f63829 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/sys_statistics_dao.py @@ -0,0 +1,266 @@ +from sqlalchemy import delete, select, update, and_, desc, insert, func +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.sys_statistics_do import SysStatistics +from module_admin.entity.vo.sys_statistics_vo import Sys_statisticsModel, Sys_statisticsPageQueryModel +from utils.page_util import PageUtil +from datetime import datetime, date + + +class Sys_statisticsDao: + """ + 系统统计数据模块数据库操作层 + """ + + @classmethod + async def get_total_statistics_data(cls, db: AsyncSession): + """ + 添加系统统计数据 + :param query_db: + :param page_object: + :return: + """ + stmt = ( + select( + func.sum(SysStatistics.llm_call).label('llm_call'), + func.sum(SysStatistics.access_control_count).label('access_control_count'), + func.sum(SysStatistics.visitor_guide).label('visitor_guide'), + func.sum(SysStatistics.exhibition_explanation).label('exhibition_explanation'), + ) + ) + + result = await db.execute(stmt) + return result.mappings().first() + + @classmethod + async def add_count(cls, db: AsyncSession, field: str, user_name:str , count: int = 1): + stmt = ( + select(SysStatistics) + .where( + SysStatistics.stat_date == datetime.now().date() + ) + ) + result = (await db.execute(stmt)).scalars().first() + if result: + update_stmt = ( + update(SysStatistics) + .where( + SysStatistics.statistic_id == result.statistic_id + ) + .values( + { + field: getattr(result, field) + count, + "update_time": datetime.now(), + "update_by": user_name, + } + ) + ) + await db.execute(update_stmt) + else: + insert_stmt = ( + insert(SysStatistics) + .values( + { + "stat_date": datetime.now().date(), + field: 1, + "create_time": datetime.now(), + "update_time": datetime.now(), + "create_by": user_name, + } + ) + ) + await db.execute(insert_stmt) + + @classmethod + async def add_model_call_count(cls, db: AsyncSession, user_name: str): + stmt = ( + select(SysStatistics) + .where( + SysStatistics.stat_date == datetime.now().date() + ) + ) + result = (await db.execute(stmt)).scalars().first() + if result: + update_stmt = ( + update(SysStatistics) + .where( + SysStatistics.statistic_id == result.statistic_id + ) + .values( + { + "llm_call": result.llm_call + 1, + "update_time": datetime.now(), + "update_by": user_name, + } + ) + ) + await db.execute(update_stmt) + else: + insert_stmt = ( + insert(SysStatistics) + .values( + { + "stat_date": datetime.now().date(), + "llm_call": 1, + "create_time": datetime.now(), + "update_time": datetime.now(), + "create_by": user_name, + } + ) + ) + await db.execute(insert_stmt) + + + + + + @classmethod + async def get_access_control_success_rate(cls, db: AsyncSession, start_time: datetime, end_time: datetime): + stmt = ( + select(SysStatistics.access_control_success_rate, SysStatistics.stat_date) + .where( + and_( + SysStatistics.stat_date >= start_time, + SysStatistics.stat_date <= end_time + ) + ) + .order_by(desc(SysStatistics.stat_date)) + ) + + return (await db.execute(stmt)).mappings().all() + + @classmethod + async def get_visitor_count(cls, db: AsyncSession, start_time: date, end_time: date): + stmt = ( + select(SysStatistics.visitor_guide, SysStatistics.stat_date) + .where( + and_( + SysStatistics.stat_date >= start_time, + SysStatistics.stat_date <= end_time, + ) + ) + .order_by(desc(SysStatistics.stat_date)) + ) + + return (await db.execute(stmt)).mappings().all() + + @classmethod + async def get_statistics_data(cls, db: AsyncSession, dd: date): + # 使用 func.date() 确保日期格式正确比较 + stmt = ( + select( + SysStatistics.llm_call, + SysStatistics.access_control_count, + SysStatistics.visitor_guide, + SysStatistics.exhibition_explanation + ) + .where(func.date(SysStatistics.stat_date) == dd) + ) + result = (await db.execute(stmt)).mappings().first() + return result + + + @classmethod + async def get_sys_statistics_detail_by_id(cls, db: AsyncSession, statistic_id: int): + """ + 根据统计项目ID获取系统统计数据详细信息 + + :param db: orm对象 + :param statistic_id: 统计项目ID + :return: 系统统计数据信息对象 + """ + sys_statistics_info = ( + ( + await db.execute( + select(SysStatistics) + .where( + SysStatistics.statistic_id == statistic_id + ) + ) + ) + .scalars() + .first() + ) + + return sys_statistics_info + + @classmethod + async def get_sys_statistics_detail_by_info(cls, db: AsyncSession, sys_statistics: Sys_statisticsModel): + """ + 根据系统统计数据参数获取系统统计数据信息 + + :param db: orm对象 + :param sys_statistics: 系统统计数据参数对象 + :return: 系统统计数据信息对象 + """ + sys_statistics_info = ( + ( + await db.execute( + select(SysStatistics).where( + ) + ) + ) + .scalars() + .first() + ) + + return sys_statistics_info + + @classmethod + async def get_sys_statistics_list(cls, db: AsyncSession, query_object: Sys_statisticsPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取系统统计数据列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 系统统计数据列表信息对象 + """ + query = ( + select(SysStatistics) + .where( + SysStatistics.stat_date == query_object.stat_date if query_object.stat_date else True, + ) + .order_by(SysStatistics.statistic_id) + .distinct() + ) + sys_statistics_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return sys_statistics_list + + @classmethod + async def add_sys_statistics_dao(cls, db: AsyncSession, sys_statistics: Sys_statisticsModel): + """ + 新增系统统计数据数据库操作 + + :param db: orm对象 + :param sys_statistics: 系统统计数据对象 + :return: + """ + db_sys_statistics = SysStatistics(**sys_statistics.model_dump(exclude={})) + db.add(db_sys_statistics) + await db.flush() + + return db_sys_statistics + + @classmethod + async def edit_sys_statistics_dao(cls, db: AsyncSession, sys_statistics: dict): + """ + 编辑系统统计数据数据库操作 + + :param db: orm对象 + :param sys_statistics: 需要更新的系统统计数据字典 + :return: + """ + await db.execute(update(SysStatistics), [sys_statistics]) + + @classmethod + async def delete_sys_statistics_dao(cls, db: AsyncSession, sys_statistics: Sys_statisticsModel): + """ + 删除系统统计数据数据库操作 + + :param db: orm对象 + :param sys_statistics: 系统统计数据对象 + :return: + """ + await db.execute(delete(SysStatistics).where(SysStatistics.statistic_id.in_([sys_statistics.statistic_id]))) + diff --git a/ruoyi-fastapi-backend/module_admin/dao/test_dao.py b/ruoyi-fastapi-backend/module_admin/dao/test_dao.py new file mode 100644 index 0000000..f1651fc --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/test_dao.py @@ -0,0 +1,16 @@ +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.user_do import SysUser +from utils.page_util import PageUtil +from module_admin.entity.vo.test_vo import TestUserNamePage + + +class TestDao: + @classmethod + async def get_user_name(cls, db: AsyncSession, query_paramter: TestUserNamePage): + query = ( + select(SysUser.user_name) + ) + # user_name = await db.execute(select(SysUser.user_name)) + user_name = await PageUtil.paginate(db, query, query_paramter.page_num, query_paramter.page_size, True) + return user_name diff --git a/ruoyi-fastapi-backend/module_admin/dao/user_dao.py b/ruoyi-fastapi-backend/module_admin/dao/user_dao.py new file mode 100644 index 0000000..cc53566 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/user_dao.py @@ -0,0 +1,568 @@ +from datetime import datetime, time +from sqlalchemy import and_, delete, desc, func, or_, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.dept_do import SysDept +from module_admin.entity.do.menu_do import SysMenu +from module_admin.entity.do.post_do import SysPost +from module_admin.entity.do.role_do import SysRole, SysRoleDept, SysRoleMenu # noqa: F401 +from module_admin.entity.do.user_do import SysUser, SysUserPost, SysUserRole +from module_admin.entity.vo.user_vo import ( + UserModel, + UserPageQueryModel, + UserPostModel, + UserRoleModel, + UserRolePageQueryModel, + UserRoleQueryModel, +) +from utils.page_util import PageUtil + + +class UserDao: + """ + 用户管理模块数据库操作层 + """ + + @classmethod + async def get_user_by_name(cls, db: AsyncSession, user_name: str): + """ + 根据用户名获取用户信息 + + :param db: orm对象 + :param user_name: 用户名 + :return: 当前用户名的用户信息对象 + """ + query_user_info = ( + ( + await db.execute( + select(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_name == user_name) + .order_by(desc(SysUser.create_time)) + .distinct() + ) + ) + .scalars() + .first() + ) + + return query_user_info + + @classmethod + async def get_user_by_info(cls, db: AsyncSession, user: UserModel): + """ + 根据用户参数获取用户信息 + + :param db: orm对象 + :param user: 用户参数 + :return: 当前用户参数的用户信息对象 + """ + query_user_info = ( + ( + await db.execute( + select(SysUser) + .where( + SysUser.del_flag == '0', + SysUser.user_name == user.user_name if user.user_name else True, + SysUser.phonenumber == user.phonenumber if user.phonenumber else True, + SysUser.email == user.email if user.email else True, + ) + .order_by(desc(SysUser.create_time)) + .distinct() + ) + ) + .scalars() + .first() + ) + + return query_user_info + + @classmethod + async def get_user_by_id(cls, db: AsyncSession, user_id: int): + """ + 根据user_id获取用户信息 + + :param db: orm对象 + :param user_id: 用户id + :return: 当前user_id的用户信息对象 + """ + query_user_basic_info = ( + ( + await db.execute( + select(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .distinct() + ) + ) + .scalars() + .first() + ) + query_user_dept_info = ( + ( + await db.execute( + select(SysDept) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join( + SysDept, + and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'), + ) + .distinct() + ) + ) + .scalars() + .first() + ) + query_user_role_info = ( + ( + await db.execute( + select(SysRole) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0'), + ) + .distinct() + ) + ) + .scalars() + .all() + ) + query_user_post_info = ( + ( + await db.execute( + select(SysPost) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserPost, SysUser.user_id == SysUserPost.user_id, isouter=True) + .join(SysPost, and_(SysUserPost.post_id == SysPost.post_id, SysPost.status == '0')) + .distinct() + ) + ) + .scalars() + .all() + ) + role_id_list = [item.role_id for item in query_user_role_info] + if 1 in role_id_list: + query_user_menu_info = ( + (await db.execute(select(SysMenu).where(SysMenu.status == '0').distinct())).scalars().all() + ) + else: + query_user_menu_info = ( + ( + await db.execute( + select(SysMenu) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_( + SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0' + ), + isouter=True, + ) + .join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True) + .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == '0')) + .order_by(SysMenu.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) + + results = dict( + user_basic_info=query_user_basic_info, + user_dept_info=query_user_dept_info, + user_role_info=query_user_role_info, + user_post_info=query_user_post_info, + user_menu_info=query_user_menu_info, + ) + + return results + + @classmethod + async def get_user_detail_by_id(cls, db: AsyncSession, user_id: int): + """ + 根据user_id获取用户详细信息 + + :param db: orm对象 + :param user_id: 用户id + :return: 当前user_id的用户信息对象 + """ + query_user_basic_info = ( + (await db.execute(select(SysUser).where(SysUser.del_flag == '0', SysUser.user_id == user_id).distinct())) + .scalars() + .first() + ) + query_user_dept_info = ( + ( + await db.execute( + select(SysDept) + .select_from(SysUser) + .where(SysUser.del_flag == '0', SysUser.user_id == user_id) + .join( + SysDept, + and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'), + ) + .distinct() + ) + ) + .scalars() + .first() + ) + query_user_role_info = ( + ( + await db.execute( + select(SysRole) + .select_from(SysUser) + .where(SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0'), + ) + .distinct() + ) + ) + .scalars() + .all() + ) + query_user_post_info = ( + ( + await db.execute( + select(SysPost) + .select_from(SysUser) + .where(SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserPost, SysUser.user_id == SysUserPost.user_id, isouter=True) + .join(SysPost, and_(SysUserPost.post_id == SysPost.post_id, SysPost.status == '0')) + .distinct() + ) + ) + .scalars() + .all() + ) + query_user_menu_info = ( + ( + await db.execute( + select(SysMenu) + .select_from(SysUser) + .where(SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0'), + isouter=True, + ) + .join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True) + .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == '0')) + .distinct() + ) + ) + .scalars() + .all() + ) + results = dict( + user_basic_info=query_user_basic_info, + user_dept_info=query_user_dept_info, + user_role_info=query_user_role_info, + user_post_info=query_user_post_info, + user_menu_info=query_user_menu_info, + ) + + return results + + @classmethod + async def get_user_list( + cls, db: AsyncSession, query_object: UserPageQueryModel, data_scope_sql: str, is_page: bool = False + ): + """ + 根据查询参数获取用户列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :param is_page: 是否开启分页 + :return: 用户列表信息对象 + """ + query = ( + select(SysUser, SysDept) + .where( + SysUser.del_flag == '0', + or_( + SysUser.dept_id == query_object.dept_id, + SysUser.dept_id.in_( + select(SysDept.dept_id).where(func.find_in_set(query_object.dept_id, SysDept.ancestors)) + ), + ) + if query_object.dept_id + else True, + SysUser.user_id == query_object.user_id if query_object.user_id is not None else True, + SysUser.user_name.like(f'%{query_object.user_name}%') if query_object.user_name else True, + SysUser.nick_name.like(f'%{query_object.nick_name}%') if query_object.nick_name else True, + SysUser.email.like(f'%{query_object.email}%') if query_object.email else True, + SysUser.phonenumber.like(f'%{query_object.phonenumber}%') if query_object.phonenumber else True, + SysUser.status == query_object.status if query_object.status else True, + SysUser.sex == query_object.sex if query_object.sex else True, + SysUser.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + eval(data_scope_sql), + ) + .join( + SysDept, + and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'), + isouter=True, + ) + .order_by(SysUser.user_id) + .distinct() + ) + user_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return user_list + + @classmethod + async def add_user_dao(cls, db: AsyncSession, user: UserModel): + """ + 新增用户数据库操作 + + :param db: orm对象 + :param user: 用户对象 + :return: 新增校验结果 + """ + db_user = SysUser(**user.model_dump(exclude={'admin'})) + db.add(db_user) + await db.flush() + + return db_user + + @classmethod + async def edit_user_dao(cls, db: AsyncSession, user: dict): + """ + 编辑用户数据库操作 + + :param db: orm对象 + :param user: 需要更新的用户字典 + :return: 编辑校验结果 + """ + await db.execute(update(SysUser), [user]) + + @classmethod + async def delete_user_dao(cls, db: AsyncSession, user: UserModel): + """ + 删除用户数据库操作 + + :param db: orm对象 + :param user: 用户对象 + :return: + """ + await db.execute( + update(SysUser) + .where(SysUser.user_id == user.user_id) + .values(del_flag='2', update_by=user.update_by, update_time=user.update_time) + ) + + @classmethod + async def get_user_role_allocated_list_by_user_id(cls, db: AsyncSession, query_object: UserRoleQueryModel): + """ + 根据用户id获取用户已分配的角色列表信息数据库操作 + + :param db: orm对象 + :param query_object: 用户角色查询对象 + :return: 用户已分配的角色列表信息 + """ + allocated_role_list = ( + ( + await db.execute( + select(SysRole) + .where( + SysRole.del_flag == '0', + SysRole.role_id != 1, + SysRole.role_name == query_object.role_name if query_object.role_name else True, + SysRole.role_key == query_object.role_key if query_object.role_key else True, + SysRole.role_id.in_( + select(SysUserRole.role_id).where(SysUserRole.user_id == query_object.user_id) + ), + ) + .distinct() + ) + ) + .scalars() + .all() + ) + + return allocated_role_list + + @classmethod + async def get_user_role_allocated_list_by_role_id( + cls, db: AsyncSession, query_object: UserRolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): + """ + 根据角色id获取已分配的用户列表信息 + + :param db: orm对象 + :param query_object: 用户角色查询对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :param is_page: 是否开启分页 + :return: 角色已分配的用户列表信息 + """ + query = ( + select(SysUser) + .join(SysDept, SysDept.dept_id == SysUser.dept_id, isouter=True) + .join(SysUserRole, SysUserRole.user_id == SysUser.user_id, isouter=True) + .join(SysRole, SysRole.role_id == SysUserRole.role_id, isouter=True) + .where( + SysUser.del_flag == '0', + SysUser.user_name == query_object.user_name if query_object.user_name else True, + SysUser.phonenumber == query_object.phonenumber if query_object.phonenumber else True, + SysRole.role_id == query_object.role_id, + eval(data_scope_sql), + ) + .distinct() + ) + allocated_user_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return allocated_user_list + + @classmethod + async def get_user_role_unallocated_list_by_role_id( + cls, db: AsyncSession, query_object: UserRolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): + """ + 根据角色id获取未分配的用户列表信息 + + :param db: orm对象 + :param query_object: 用户角色查询对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :param is_page: 是否开启分页 + :return: 角色未分配的用户列表信息 + """ + query = ( + select(SysUser) + .join(SysDept, SysDept.dept_id == SysUser.dept_id, isouter=True) + .join(SysUserRole, SysUserRole.user_id == SysUser.user_id, isouter=True) + .join(SysRole, SysRole.role_id == SysUserRole.role_id, isouter=True) + .where( + SysUser.del_flag == '0', + SysUser.user_name == query_object.user_name if query_object.user_name else True, + SysUser.phonenumber == query_object.phonenumber if query_object.phonenumber else True, + or_(SysRole.role_id != query_object.role_id, SysRole.role_id.is_(None)), + ~SysUser.user_id.in_( + select(SysUser.user_id) + .select_from(SysUser) + .join( + SysUserRole, + and_(SysUserRole.user_id == SysUser.user_id, SysUserRole.role_id == query_object.role_id), + ) + ), + eval(data_scope_sql), + ) + .distinct() + ) + unallocated_user_list = await PageUtil.paginate( + db, query, query_object.page_num, query_object.page_size, is_page + ) + + return unallocated_user_list + + @classmethod + async def add_user_role_dao(cls, db: AsyncSession, user_role: UserRoleModel): + """ + 新增用户角色关联信息数据库操作 + + :param db: orm对象 + :param user_role: 用户角色关联对象 + :return: + """ + db_user_role = SysUserRole(**user_role.model_dump()) + db.add(db_user_role) + + @classmethod + async def delete_user_role_dao(cls, db: AsyncSession, user_role: UserRoleModel): + """ + 删除用户角色关联信息数据库操作 + + :param db: orm对象 + :param user_role: 用户角色关联对象 + :return: + """ + await db.execute(delete(SysUserRole).where(SysUserRole.user_id.in_([user_role.user_id]))) + + @classmethod + async def delete_user_role_by_user_and_role_dao(cls, db: AsyncSession, user_role: UserRoleModel): + """ + 根据用户id及角色id删除用户角色关联信息数据库操作 + + :param db: orm对象 + :param user_role: 用户角色关联对象 + :return: + """ + await db.execute( + delete(SysUserRole).where( + SysUserRole.user_id == user_role.user_id if user_role.user_id else True, + SysUserRole.role_id == user_role.role_id if user_role.role_id else True, + ) + ) + + @classmethod + async def get_user_role_detail(cls, db: AsyncSession, user_role: UserRoleModel): + """ + 根据用户角色关联获取用户角色关联详细信息 + + :param db: orm对象 + :param user_role: 用户角色关联对象 + :return: 用户角色关联信息 + """ + user_role_info = ( + ( + await db.execute( + select(SysUserRole) + .where(SysUserRole.user_id == user_role.user_id, SysUserRole.role_id == user_role.role_id) + .distinct() + ) + ) + .scalars() + .first() + ) + + return user_role_info + + @classmethod + async def add_user_post_dao(cls, db: AsyncSession, user_post: UserPostModel): + """ + 新增用户岗位关联信息数据库操作 + + :param db: orm对象 + :param user_post: 用户岗位关联对象 + :return: + """ + db_user_post = SysUserPost(**user_post.model_dump()) + db.add(db_user_post) + + @classmethod + async def delete_user_post_dao(cls, db: AsyncSession, user_post: UserPostModel): + """ + 删除用户岗位关联信息数据库操作 + + :param db: orm对象 + :param user_post: 用户岗位关联对象 + :return: + """ + await db.execute(delete(SysUserPost).where(SysUserPost.user_id.in_([user_post.user_id]))) + + @classmethod + async def get_user_dept_info(cls, db: AsyncSession, dept_id: int): + dept_basic_info = ( + ( + await db.execute( + select(SysDept).where(SysDept.dept_id == dept_id, SysDept.status == '0', SysDept.del_flag == '0') + ) + ) + .scalars() + .first() + ) + return dept_basic_info diff --git a/ruoyi-fastapi-backend/module_admin/dao/visitor_type_dao.py b/ruoyi-fastapi-backend/module_admin/dao/visitor_type_dao.py new file mode 100644 index 0000000..cdf2bd3 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/visitor_type_dao.py @@ -0,0 +1,125 @@ +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.visitor_type_do import VisitorType +from module_admin.entity.vo.visitor_type_vo import Visitor_typeModel, Visitor_typePageQueryModel +from utils.page_util import PageUtil + + +class Visitor_typeDao: + """ + 访客类型模块数据库操作层 + """ + + @classmethod + async def get_system_visitor_type_list(cls, db: AsyncSession): + stmt = ( + select(VisitorType.type_id, VisitorType.name, VisitorType.value) + ) + return (await db.execute(stmt)).mappings().all() + + @classmethod + async def get_visitor_type_detail_by_id(cls, db: AsyncSession, type_id: int): + """ + 根据访客类型ID获取访客类型详细信息 + + :param db: orm对象 + :param type_id: 访客类型ID + :return: 访客类型信息对象 + """ + visitor_type_info = ( + ( + await db.execute( + select(VisitorType) + .where( + VisitorType.type_id == type_id + ) + ) + ) + .scalars() + .first() + ) + + return visitor_type_info + + @classmethod + async def get_visitor_type_detail_by_info(cls, db: AsyncSession, visitor_type: Visitor_typeModel): + """ + 根据访客类型参数获取访客类型信息 + + :param db: orm对象 + :param visitor_type: 访客类型参数对象 + :return: 访客类型信息对象 + """ + visitor_type_info = ( + ( + await db.execute( + select(VisitorType).where( + ) + ) + ) + .scalars() + .first() + ) + + return visitor_type_info + + @classmethod + async def get_visitor_type_list(cls, db: AsyncSession, query_object: Visitor_typePageQueryModel, is_page: bool = False): + """ + 根据查询参数获取访客类型列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 访客类型列表信息对象 + """ + query = ( + select(VisitorType) + .where( + VisitorType.name.like(f'%{query_object.name}%') if query_object.name else True, + VisitorType.value == query_object.value if query_object.value else True, + ) + .order_by(VisitorType.type_id) + .distinct() + ) + visitor_type_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return visitor_type_list + + @classmethod + async def add_visitor_type_dao(cls, db: AsyncSession, visitor_type: Visitor_typeModel): + """ + 新增访客类型数据库操作 + + :param db: orm对象 + :param visitor_type: 访客类型对象 + :return: + """ + db_visitor_type = VisitorType(**visitor_type.model_dump(exclude={})) + db.add(db_visitor_type) + await db.flush() + + return db_visitor_type + + @classmethod + async def edit_visitor_type_dao(cls, db: AsyncSession, visitor_type: dict): + """ + 编辑访客类型数据库操作 + + :param db: orm对象 + :param visitor_type: 需要更新的访客类型字典 + :return: + """ + await db.execute(update(VisitorType), [visitor_type]) + + @classmethod + async def delete_visitor_type_dao(cls, db: AsyncSession, visitor_type: Visitor_typeModel): + """ + 删除访客类型数据库操作 + + :param db: orm对象 + :param visitor_type: 访客类型对象 + :return: + """ + await db.execute(delete(VisitorType).where(VisitorType.type_id.in_([visitor_type.type_id]))) + diff --git a/ruoyi-fastapi-backend/module_admin/dao/words_dao.py b/ruoyi-fastapi-backend/module_admin/dao/words_dao.py new file mode 100644 index 0000000..08a6de1 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/words_dao.py @@ -0,0 +1,161 @@ +from datetime import datetime, time +from sqlalchemy import delete, select, update, desc +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.words_do import GuideWords +from module_admin.entity.vo.words_vo import WordsModel, WordsPageQueryModel +from utils.page_util import PageUtil + + +class WordsDao: + """ + 引导词模块数据库操作层 + """ + + @classmethod + async def get_words_by_type(cls, db: AsyncSession, type: str): + """ + 获取随机引导词 + + :param db: orm对象 + :param type: 引导词类型 + :return: 引导词对象 + """ + guide_word = ( + ( + await db.execute( + select(GuideWords.text) + .where( + GuideWords.type == type, + GuideWords.del_flag == '0' + ) + ) + ) + ) + return guide_word.scalars().all() + + + @classmethod + async def edit_del_words(cls, db: AsyncSession, guide_ids: str, del_flag:str): + + guide_id_list = guide_ids.strip(' ').split(',') + + stmt = ( + update(GuideWords) + .values(del_flag = del_flag) + .where(GuideWords.guide_id.in_(guide_id_list)) + ) + + return await db.execute(stmt) + + @classmethod + async def get_words_detail_by_id(cls, db: AsyncSession, guide_id: int): + """ + 根据引导词id获取引导词详细信息 + + :param db: orm对象 + :param guide_id: 引导词id + :return: 引导词信息对象 + """ + words_info = ( + ( + await db.execute( + select(GuideWords) + .where( + GuideWords.guide_id == guide_id + ) + ) + ) + .scalars() + .first() + ) + + return words_info + + @classmethod + async def get_words_detail_by_info(cls, db: AsyncSession, words: WordsModel): + """ + 根据引导词参数获取引导词信息 + + :param db: orm对象 + :param words: 引导词参数对象 + :return: 引导词信息对象 + """ + words_info = ( + ( + await db.execute( + select(GuideWords).where( + ) + ) + ) + .scalars() + .first() + ) + + return words_info + + @classmethod + async def get_words_list(cls, db: AsyncSession, query_object: WordsPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取引导词列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 引导词列表信息对象 + """ + query = ( + select(GuideWords) + .where( + GuideWords.type == query_object.type if query_object.type else True, + GuideWords.text.like(f'%{query_object.text}%') if query_object.text else True, + GuideWords.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .order_by(desc(GuideWords.create_time)) + .distinct() + ) + words_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return words_list + + @classmethod + async def add_words_dao(cls, db: AsyncSession, words: WordsModel): + """ + 新增引导词数据库操作 + + :param db: orm对象 + :param words: 引导词对象 + :return: + """ + db_words = GuideWords(**words.model_dump(exclude={})) + db.add(db_words) + await db.flush() + + return db_words + + @classmethod + async def edit_words_dao(cls, db: AsyncSession, words: dict): + """ + 编辑引导词数据库操作 + + :param db: orm对象 + :param words: 需要更新的引导词字典 + :return: + """ + await db.execute(update(GuideWords), [words]) + + @classmethod + async def delete_words_dao(cls, db: AsyncSession, words: WordsModel): + """ + 删除引导词数据库操作 + + :param db: orm对象 + :param words: 引导词对象 + :return: + """ + await db.execute(delete(GuideWords).where(GuideWords.guide_id.in_([words.guide_id]))) + diff --git a/ruoyi-fastapi-backend/module_admin/entity/__init__.py b/ruoyi-fastapi-backend/module_admin/entity/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/__init__.py b/ruoyi-fastapi-backend/module_admin/entity/do/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py new file mode 100644 index 0000000..012d2be --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py @@ -0,0 +1,22 @@ +from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base + + +class SysConfig(Base): + """ + 参数配置表 + """ + + __tablename__ = 'sys_config' + + config_id = Column(Integer, primary_key=True, autoincrement=True, comment='参数主键') + config_name = Column(String(100), nullable=True, default='', comment='参数名称') + config_key = Column(String(100), nullable=True, default='', comment='参数键名') + config_value = Column(String(500), nullable=True, default='', comment='参数键值') + config_type = Column(String(1), nullable=True, default='N', comment='系统内置(Y是 N否)') + create_by = Column(String(64), nullable=True, default='', comment='创建者') + create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') + update_by = Column(String(64), nullable=True, default='', comment='更新者') + update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') + remark = Column(String(500), nullable=True, default=None, comment='备注') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py new file mode 100644 index 0000000..44e2f02 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py @@ -0,0 +1,26 @@ +from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base + + +class SysDept(Base): + """ + 部门表 + """ + + __tablename__ = 'sys_dept' + + dept_id = Column(Integer, primary_key=True, autoincrement=True, comment='部门id') + parent_id = Column(Integer, default=0, comment='父部门id') + ancestors = Column(String(50), nullable=True, default='', comment='祖级列表') + dept_name = Column(String(30), nullable=True, default='', comment='部门名称') + order_num = Column(Integer, default=0, comment='显示顺序') + leader = Column(String(20), nullable=True, default=None, comment='负责人') + phone = Column(String(11), nullable=True, default=None, comment='联系电话') + email = Column(String(50), nullable=True, default=None, comment='邮箱') + status = Column(String(1), nullable=True, default='0', comment='部门状态(0正常 1停用)') + del_flag = Column(String(1), nullable=True, default='0', comment='删除标志(0代表存在 2代表删除)') + create_by = Column(String(64), nullable=True, default='', comment='创建者') + create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') + update_by = Column(String(64), nullable=True, default='', comment='更新者') + update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py new file mode 100644 index 0000000..7a155ea --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py @@ -0,0 +1,46 @@ +from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String, UniqueConstraint +from config.database import Base + + +class SysDictType(Base): + """ + 字典类型表 + """ + + __tablename__ = 'sys_dict_type' + + dict_id = Column(Integer, primary_key=True, autoincrement=True, comment='字典主键') + dict_name = Column(String(100), nullable=True, default='', comment='字典名称') + dict_type = Column(String(100), nullable=True, default='', comment='字典类型') + status = Column(String(1), nullable=True, default='0', comment='状态(0正常 1停用)') + create_by = Column(String(64), nullable=True, default='', comment='创建者') + create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') + update_by = Column(String(64), nullable=True, default='', comment='更新者') + update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') + remark = Column(String(500), nullable=True, default=None, comment='备注') + + __table_args__ = (UniqueConstraint('dict_type', name='uq_sys_dict_type_dict_type'),) + + +class SysDictData(Base): + """ + 字典数据表 + """ + + __tablename__ = 'sys_dict_data' + + dict_code = Column(Integer, primary_key=True, autoincrement=True, comment='字典编码') + dict_sort = Column(Integer, nullable=True, default=0, comment='字典排序') + dict_label = Column(String(100), nullable=True, default='', comment='字典标签') + dict_value = Column(String(100), nullable=True, default='', comment='字典键值') + dict_type = Column(String(100), nullable=True, default='', comment='字典类型') + css_class = Column(String(100), nullable=True, default=None, comment='样式属性(其他样式扩展)') + list_class = Column(String(100), nullable=True, default=None, comment='表格回显样式') + is_default = Column(String(1), nullable=True, default='N', comment='是否默认(Y是 N否)') + status = Column(String(1), nullable=True, default='0', comment='状态(0正常 1停用)') + create_by = Column(String(64), nullable=True, default='', comment='创建者') + create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') + update_by = Column(String(64), nullable=True, default='', comment='更新者') + update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') + remark = Column(String(500), nullable=True, default=None, comment='备注') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/door_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/door_do.py new file mode 100644 index 0000000..fe4d4fd --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/door_do.py @@ -0,0 +1,24 @@ +from sqlalchemy import Column, BigInteger, String, DateTime +from config.database import Base + + +class Door(Base): + """ + 门禁设备表 + """ + + __tablename__ = 'door' + + id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='主键自增') + indexCode = Column(String(64), nullable=True, comment='设备索引编码') + name = Column(String(32), nullable=True, comment='门禁名称') + install_location = Column(String(32), nullable=True, comment='位置名称') + status = Column(String(2), nullable=True, comment='设备状态, 0 离线, 1 正常在线') + permission = Column(String(2), nullable=True, comment='权限, 0 禁用, 1 正常调用') + create_time = Column(DateTime, nullable=True, comment='创建时间') + create_by = Column(String(64), nullable=True, comment='创建者') + update_time = Column(DateTime, nullable=True, comment='更新时间') + update_by = Column(String(64), nullable=True, comment='更新者') + + + diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/explanation_content_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/explanation_content_do.py new file mode 100644 index 0000000..6eb29dc --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/explanation_content_do.py @@ -0,0 +1,25 @@ +from sqlalchemy import Column, Text, DateTime, String, BigInteger, Integer +from config.database import Base + + +class ExplanationContent(Base): + """ + 讲解内容表 + """ + + __tablename__ = 'explanation_content' + + explanation_content_id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='主键ID') + title = Column(String(64), nullable=False, comment='标题') + content = Column(Text, nullable=True, comment='讲解内容') + type = Column(String(2), nullable=True, comment='类型(0科技类)') + spend_time = Column(Integer, nullable=True, comment='预计时长(分钟)') + language = Column(String(2), nullable=True, comment='语言(0中文,1英文)') + status = Column(String(2), nullable=True, comment='状态(0未开始,1已就绪,2播放中,3已完成)') + create_time = Column(DateTime, nullable=True, comment='创建时间') + create_by = Column(String(64), nullable=True, comment='创建者') + update_time = Column(DateTime, nullable=True, comment='修改时间') + update_by = Column(String(64), nullable=True, comment='修改者') + + + diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/explanation_content_type_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/explanation_content_type_do.py new file mode 100644 index 0000000..49fe731 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/explanation_content_type_do.py @@ -0,0 +1,21 @@ +from sqlalchemy import BigInteger, DateTime, String, Column +from config.database import Base + + +class ExplanationContentType(Base): + """ + 讲解内容类型表 + """ + + __tablename__ = 'explanation_content_type' + + content_type_id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='主键ID') + name = Column(String(32), nullable=True, comment='类型名称') + type_value = Column(String(2), nullable=True, comment='类型值(与讲解内容表中的type对应)') + create_time = Column(DateTime, nullable=True, comment='创建时间') + create_by = Column(String(64), nullable=True, comment='创建者') + update_time = Column(DateTime, nullable=True, comment='修改时间') + update_by = Column(String(64), nullable=True, comment='修改者') + + + diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/explanation_style_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/explanation_style_do.py new file mode 100644 index 0000000..df7a137 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/explanation_style_do.py @@ -0,0 +1,21 @@ +from sqlalchemy import BigInteger, DateTime, String, Column +from config.database import Base + + +class ExplanationStyle(Base): + """ + 讲解风格表 + """ + + __tablename__ = 'explanation_style' + + explanation_style_id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='主键ID') + name = Column(String(32), nullable=True, comment='风格名称') + detail = Column(String(64), nullable=True, comment='风格简介') + create_time = Column(DateTime, nullable=True, comment='创建时间') + create_by = Column(String(64), nullable=True, comment='创建者') + update_time = Column(DateTime, nullable=True, comment='修改时间') + update_by = Column(String(64), nullable=True, comment='修改者') + + + diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/explanation_style_robot_pair_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/explanation_style_robot_pair_do.py new file mode 100644 index 0000000..3ee4b24 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/explanation_style_robot_pair_do.py @@ -0,0 +1,24 @@ +from sqlalchemy import BigInteger, DateTime, String, Column +from config.database import Base + + +class ExplanationStyleRobotPairing(Base): + """ + 讲解风格--机器人配对表 + """ + + __tablename__ = 'explanation_style_robot_pairing' + + pairing_id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='主键ID') + robot_id = Column(BigInteger, nullable=False, comment='机器人ID(关联robot_info表)') + robot_role_id = Column(BigInteger, nullable=False, comment='机器人角色ID(关联robot_role表)') + explanation_style_id = Column(BigInteger, nullable=False, comment='讲解风格ID(关联explanation_style表)') + prompt = Column(String(128), nullable=True, comment='提示词') + status = Column(String(2), nullable=True, comment='状态(0停用,1使用)') + create_time = Column(DateTime, nullable=True, comment='创建时间') + create_by = Column(String(64), nullable=True, comment='创建者') + update_time = Column(DateTime, nullable=True, comment='修改时间') + update_by = Column(String(64), nullable=True, comment='修改者') + + + diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/identification_record_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/identification_record_do.py new file mode 100644 index 0000000..7145067 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/identification_record_do.py @@ -0,0 +1,24 @@ +from sqlalchemy import DateTime, String, BigInteger, Column +from config.database import Base + + +class IdentificationRecord(Base): + """ + 识别记录表 + """ + + __tablename__ = 'identification_record' + + id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='主键 自增') + person_name = Column(String(20), nullable=True, comment='识别人姓名') + door_name = Column(String(20), nullable=True, comment='门禁点') + status = Column(String(2), nullable=True, comment='识别结果, 0 拒绝, 1 通过') + source = Column(String(20), nullable=True, comment='识别来源') + pic_uri = Column(String(256), nullable=True, comment='图像url') + create_time = Column(DateTime, nullable=True, comment='创建时间') + create_by = Column(String(64), nullable=True, comment='创建者') + conf = Column(BigInteger, default=0, comment='识别置信度') + person_status = Column(String(2), nullable=True, comment=' 0 陌生人, 1 员工, 2 访客') + + + diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/identification_statistics_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/identification_statistics_do.py new file mode 100644 index 0000000..d43c5a9 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/identification_statistics_do.py @@ -0,0 +1,20 @@ +from sqlalchemy import String, Column, BigInteger, DateTime +from config.database import Base + + +class IdentificationStatistics(Base): + """ + 识别统计表 + """ + + __tablename__ = 'identification_statistics' + + id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='主键 自增') + employ = Column(BigInteger, nullable=True, comment='员工识别成功数') + visitor = Column(BigInteger, nullable=True, comment='访客识别成功数') + stranger = Column(BigInteger, nullable=True, comment='位置人员识别失败数') + create_time = Column(DateTime, nullable=True, comment='创建时间') + create_by = Column(String(64), nullable=True, comment='创建者') + + + diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/info_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/info_do.py new file mode 100644 index 0000000..2eeed5f --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/info_do.py @@ -0,0 +1,33 @@ +from sqlalchemy import DateTime, String, BigInteger, CHAR, Column +from config.database import Base + + +class RobotInfo(Base): + """ + 机器人信息表 + """ + + __tablename__ = 'robot_info' + + robot_id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='机器人ID') + name = Column(String(32), nullable=False, comment='机器人名称') + model = Column(String(32), nullable=True, comment='机器人型号') + serial = Column(String(32), nullable=True, comment='序列号') + online = Column(String(2), nullable=True, comment='机器人在线状态(0在线, 1离线)') + power = Column(String(3), nullable=True, comment='电量') + run_time = Column(BigInteger, nullable=True, comment='总运行时长(单位:分钟)') + position = Column(String(32), nullable=True, comment='位置') + image = Column(String(512), nullable=True, comment='机器人外观图地址') + create_time = Column(DateTime, nullable=True, comment='创建时间') + create_by = Column(String(64), nullable=True, comment='创建者') + update_time = Column(DateTime, nullable=True, comment='修改时间') + update_by = Column(String(64), nullable=True, comment='修改者') + del_flag = Column(CHAR(1), nullable=False, comment='删除标志(0存在, 2删除)') + remark = Column(String(512), nullable=True, comment='备注') + last_charge = Column(DateTime, nullable=True, comment='上一次充电时间') + ip = Column(String(16), nullable=True, comment='机器人ip') + status = Column(String(2), nullable=False, comment='使用情况(0待机,1正在运行)') + video_uri = Column(String(512), nullable=True, comment='视频uri') + + + diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py new file mode 100644 index 0000000..b2cbfb8 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py @@ -0,0 +1,54 @@ +from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base + + +class SysJob(Base): + """ + 定时任务调度表 + """ + + __tablename__ = 'sys_job' + + job_id = Column(Integer, primary_key=True, autoincrement=True, comment='任务ID') + job_name = Column(String(64), nullable=True, default='', comment='任务名称') + job_group = Column(String(64), nullable=True, default='default', comment='任务组名') + job_executor = Column(String(64), nullable=True, default='default', comment='任务执行器') + invoke_target = Column(String(500), nullable=False, comment='调用目标字符串') + job_args = Column(String(255), nullable=True, default='', comment='位置参数') + job_kwargs = Column(String(255), nullable=True, default='', comment='关键字参数') + cron_expression = Column(String(255), nullable=True, default='', comment='cron执行表达式') + misfire_policy = Column( + String(20), + nullable=True, + default='3', + comment='计划执行错误策略(1立即执行 2执行一次 3放弃执行)', + ) + concurrent = Column(String(1), nullable=True, default='1', comment='是否并发执行(0允许 1禁止)') + status = Column(String(1), nullable=True, default='0', comment='状态(0正常 1暂停)') + create_by = Column(String(64), nullable=True, default='', comment='创建者') + create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') + update_by = Column(String(64), nullable=True, default='', comment='更新者') + update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') + remark = Column(String(500), nullable=True, default='', comment='备注信息') + + +class SysJobLog(Base): + """ + 定时任务调度日志表 + """ + + __tablename__ = 'sys_job_log' + + job_log_id = Column(Integer, primary_key=True, autoincrement=True, comment='任务日志ID') + job_name = Column(String(64), nullable=False, comment='任务名称') + job_group = Column(String(64), nullable=False, comment='任务组名') + job_executor = Column(String(64), nullable=False, comment='任务执行器') + invoke_target = Column(String(500), nullable=False, comment='调用目标字符串') + job_args = Column(String(255), nullable=True, default='', comment='位置参数') + job_kwargs = Column(String(255), nullable=True, default='', comment='关键字参数') + job_trigger = Column(String(255), nullable=True, default='', comment='任务触发器') + job_message = Column(String(500), nullable=True, default='', comment='日志信息') + status = Column(String(1), nullable=True, default='0', comment='执行状态(0正常 1失败)') + exception_info = Column(String(2000), nullable=True, default='', comment='异常信息') + create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py new file mode 100644 index 0000000..f9e14ab --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py @@ -0,0 +1,54 @@ +from datetime import datetime +from sqlalchemy import BigInteger, Column, DateTime, Index, Integer, String +from config.database import Base + + +class SysLogininfor(Base): + """ + 系统访问记录 + """ + + __tablename__ = 'sys_logininfor' + + info_id = Column(Integer, primary_key=True, autoincrement=True, comment='访问ID') + user_name = Column(String(50), nullable=True, default='', comment='用户账号') + ipaddr = Column(String(128), nullable=True, default='', comment='登录IP地址') + login_location = Column(String(255), nullable=True, default='', comment='登录地点') + browser = Column(String(50), nullable=True, default='', comment='浏览器类型') + os = Column(String(50), nullable=True, default='', comment='操作系统') + status = Column(String(1), nullable=True, default='0', comment='登录状态(0成功 1失败)') + msg = Column(String(255), nullable=True, default='', comment='提示消息') + login_time = Column(DateTime, nullable=True, default=datetime.now(), comment='访问时间') + + idx_sys_logininfor_s = Index('idx_sys_logininfor_s', status) + idx_sys_logininfor_lt = Index('idx_sys_logininfor_lt', login_time) + + +class SysOperLog(Base): + """ + 操作日志记录 + """ + + __tablename__ = 'sys_oper_log' + + oper_id = Column(BigInteger, primary_key=True, autoincrement=True, comment='日志主键') + title = Column(String(50), nullable=True, default='', comment='模块标题') + business_type = Column(Integer, default=0, comment='业务类型(0其它 1新增 2修改 3删除)') + method = Column(String(100), nullable=True, default='', comment='方法名称') + request_method = Column(String(10), nullable=True, default='', comment='请求方式') + operator_type = Column(Integer, default=0, comment='操作类别(0其它 1后台用户 2手机端用户)') + oper_name = Column(String(50), nullable=True, default='', comment='操作人员') + dept_name = Column(String(50), nullable=True, default='', comment='部门名称') + oper_url = Column(String(255), nullable=True, default='', comment='请求URL') + oper_ip = Column(String(128), nullable=True, default='', comment='主机地址') + oper_location = Column(String(255), nullable=True, default='', comment='操作地点') + oper_param = Column(String(2000), nullable=True, default='', comment='请求参数') + json_result = Column(String(2000), nullable=True, default='', comment='返回参数') + status = Column(Integer, default=0, comment='操作状态(0正常 1异常)') + error_msg = Column(String(2000), nullable=True, default='', comment='错误消息') + oper_time = Column(DateTime, nullable=True, default=datetime.now(), comment='操作时间') + cost_time = Column(BigInteger, default=0, comment='消耗时间') + + idx_sys_oper_log_bt = Index('idx_sys_oper_log_bt', business_type) + idx_sys_oper_log_s = Index('idx_sys_oper_log_s', status) + idx_sys_oper_log_ot = Index('idx_sys_oper_log_ot', oper_time) diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py new file mode 100644 index 0000000..d3ccffa --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py @@ -0,0 +1,32 @@ +from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base + + +class SysMenu(Base): + """ + 菜单权限表 + """ + + __tablename__ = 'sys_menu' + + menu_id = Column(Integer, primary_key=True, autoincrement=True, comment='菜单ID') + menu_name = Column(String(50), nullable=False, default='', comment='菜单名称') + parent_id = Column(Integer, default=0, comment='父菜单ID') + order_num = Column(Integer, default=0, comment='显示顺序') + path = Column(String(200), nullable=True, default='', comment='路由地址') + component = Column(String(255), nullable=True, default=None, comment='组件路径') + query = Column(String(255), nullable=True, default=None, comment='路由参数') + route_name = Column(String(50), nullable=True, default='', comment='路由名称') + is_frame = Column(Integer, default=1, comment='是否为外链(0是 1否)') + is_cache = Column(Integer, default=0, comment='是否缓存(0缓存 1不缓存)') + menu_type = Column(String(1), nullable=True, default='', comment='菜单类型(M目录 C菜单 F按钮)') + visible = Column(String(1), nullable=True, default='0', comment='菜单状态(0显示 1隐藏)') + status = Column(String(1), nullable=True, default='0', comment='菜单状态(0正常 1停用)') + perms = Column(String(100), nullable=True, default=None, comment='权限标识') + icon = Column(String(100), nullable=True, default='#', comment='菜单图标') + create_by = Column(String(64), nullable=True, default='', comment='创建者') + create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') + update_by = Column(String(64), nullable=True, default='', comment='更新者') + update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') + remark = Column(String(500), nullable=True, default='', comment='备注') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/message_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/message_do.py new file mode 100644 index 0000000..a423f60 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/message_do.py @@ -0,0 +1,24 @@ +from sqlalchemy import DateTime, String, BigInteger, Column +from config.database import Base + + +class Message(Base): + """ + 系统消息表 + """ + + __tablename__ = 'message' + + message_id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='消息主键') + type = Column(String(2), nullable=False, comment='类型(0告警消息)') + status = Column(String(2), nullable=False, comment='状态(0未处理,1已处理)') + detail = Column(String(512), nullable=True, comment='消息描述') + create_time = Column(DateTime, nullable=False, comment='创建时间') + create_by = Column(String(64), nullable=True, comment='创建者') + update_time = Column(DateTime, nullable=False, comment='更新时间') + update_by = Column(String(64), nullable=False, comment='更新者') + remark = Column(String(512), nullable=True, comment='备注') + specific_type = Column(String(2), nullable=True, comment='具体消息类型(0 设备故障, 1 系统异常, 2安全告警, 3 网络异常)') + + + diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py new file mode 100644 index 0000000..9d1eb98 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py @@ -0,0 +1,22 @@ +from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, LargeBinary, String +from config.database import Base + + +class SysNotice(Base): + """ + 通知公告表 + """ + + __tablename__ = 'sys_notice' + + notice_id = Column(Integer, primary_key=True, autoincrement=True, comment='公告ID') + notice_title = Column(String(50), nullable=False, comment='公告标题') + notice_type = Column(String(1), nullable=False, comment='公告类型(1通知 2公告)') + notice_content = Column(LargeBinary, comment='公告内容') + status = Column(String(1), default='0', comment='公告状态(0正常 1关闭)') + create_by = Column(String(64), default='', comment='创建者') + create_time = Column(DateTime, comment='创建时间', default=datetime.now()) + update_by = Column(String(64), default='', comment='更新者') + update_time = Column(DateTime, comment='更新时间', default=datetime.now()) + remark = Column(String(255), comment='备注') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py new file mode 100644 index 0000000..f231f72 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py @@ -0,0 +1,22 @@ +from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base + + +class SysPost(Base): + """ + 岗位信息表 + """ + + __tablename__ = 'sys_post' + + post_id = Column(Integer, primary_key=True, autoincrement=True, comment='岗位ID') + post_code = Column(String(64), nullable=False, comment='岗位编码') + post_name = Column(String(50), nullable=False, comment='岗位名称') + post_sort = Column(Integer, nullable=False, comment='显示顺序') + status = Column(String(1), nullable=False, default='0', comment='状态(0正常 1停用)') + create_by = Column(String(64), default='', comment='创建者') + create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') + update_by = Column(String(64), default='', comment='更新者') + update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') + remark = Column(String(500), nullable=True, default=None, comment='备注') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/robot_action_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/robot_action_do.py new file mode 100644 index 0000000..e4acfdd --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/robot_action_do.py @@ -0,0 +1,21 @@ +from sqlalchemy import Integer, String, BigInteger, Column, DateTime +from config.database import Base + + +class RobotAction(Base): + """ + 机器人动作表 + """ + + __tablename__ = 'robot_action' + + id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='主键 自增') + name = Column(String(50), nullable=True, comment='动作名') + description = Column(String(50), nullable=True, comment='描述') + interval_min = Column(Integer, nullable=True, comment='最小时间间隔 单位s') + interval_max = Column(Integer, nullable=True, comment='最大时间间隔 单位s') + status = Column(String(2), nullable=True, comment='动作状态 0 未启用, 1已启用') + update_time = Column(DateTime, nullable=True, comment='修改时间') + + + diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/robot_role_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/robot_role_do.py new file mode 100644 index 0000000..eac1af0 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/robot_role_do.py @@ -0,0 +1,22 @@ +from sqlalchemy import BigInteger, String, Column, DateTime +from config.database import Base + + +class RobotRole(Base): + """ + 系统角色表 + """ + + __tablename__ = 'robot_role' + + robot_role_id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='机器人角色ID') + rolename = Column(String(32), nullable=False, comment='角色名称') + detail = Column(String(512), nullable=True, comment='角色介绍') + create_time = Column(DateTime, nullable=False, comment='创建时间') + create_by = Column(String(64), nullable=False, comment='创建者') + update_time = Column(DateTime, nullable=False, comment='修改时间') + update_by = Column(String(64), nullable=False, comment='修改者') + remark = Column(String(512), nullable=True, comment='备注') + + + diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/robot_role_pairing_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/robot_role_pairing_do.py new file mode 100644 index 0000000..ee56a05 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/robot_role_pairing_do.py @@ -0,0 +1,32 @@ +from sqlalchemy import DateTime, Column, BigInteger, String +from config.database import Base + + +class RobotRolePairing(Base): + """ + 角色-机器人-映射表 + """ + + __tablename__ = 'robot_role_pairing' + + pairing_id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='主键ID') + robot_role_id = Column(BigInteger, nullable=False, comment='机器人角色ID') + robot_id = Column(BigInteger, nullable=False, comment='机器人ID') + status = Column(String(2), nullable=False, comment='使用情况(0停用,1使用)') + update_time = Column(DateTime, nullable=False, comment='更新时间') + update_by = Column(String(64), nullable=False, comment='修改者') + + +# +# class RolePairing(Base): +# """ +# 角色-机器人-映射表对应pydantic模型, 查询角色-机器人结果的返回模型 +# """ +# +# +# pairing_id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='主键ID') +# rolename = Column(String(32), nullable=False, comment='角色名称') +# detail = Column(String(512), nullable=True, comment='角色介绍') +# status = Column(String(2), nullable=False, comment='使用情况(0停用,1使用)') + + diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py new file mode 100644 index 0000000..58d4de1 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py @@ -0,0 +1,52 @@ +from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base + + +class SysRole(Base): + """ + 角色信息表 + """ + + __tablename__ = 'sys_role' + + role_id = Column(Integer, primary_key=True, autoincrement=True, comment='角色ID') + role_name = Column(String(30), nullable=False, comment='角色名称') + role_key = Column(String(100), nullable=False, comment='角色权限字符串') + role_sort = Column(Integer, nullable=False, comment='显示顺序') + data_scope = Column( + String(1), + default='1', + comment='数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + ) + menu_check_strictly = Column(Integer, default=1, comment='菜单树选择项是否关联显示') + dept_check_strictly = Column(Integer, default=1, comment='部门树选择项是否关联显示') + status = Column(String(1), nullable=False, default='0', comment='角色状态(0正常 1停用)') + del_flag = Column(String(1), default='0', comment='删除标志(0代表存在 2代表删除)') + create_by = Column(String(64), default='', comment='创建者') + create_time = Column(DateTime, default=datetime.now(), comment='创建时间') + update_by = Column(String(64), default='', comment='更新者') + update_time = Column(DateTime, default=datetime.now(), comment='更新时间') + remark = Column(String(500), default=None, comment='备注') + + +class SysRoleDept(Base): + """ + 角色和部门关联表 + """ + + __tablename__ = 'sys_role_dept' + + role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID') + dept_id = Column(Integer, primary_key=True, nullable=False, comment='部门ID') + + +class SysRoleMenu(Base): + """ + 角色和菜单关联表 + """ + + __tablename__ = 'sys_role_menu' + + role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID') + menu_id = Column(Integer, primary_key=True, nullable=False, comment='菜单ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/sys_alert_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/sys_alert_do.py new file mode 100644 index 0000000..425942a --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/sys_alert_do.py @@ -0,0 +1,22 @@ +from sqlalchemy import String, Column, BigInteger, DateTime +from config.database import Base + + +class SysAlert(Base): + """ + 系统告警表 + """ + + __tablename__ = 'sys_alert' + + id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='主键 自增') + detail = Column(String(256), nullable=True, comment='告警描述') + type = Column(String(2), nullable=True, comment='告警类型') + status = Column(String(2), nullable=True, comment='告警状态, 0 未处理, 1 已处理') + create_time = Column(DateTime, nullable=True, comment='创建时间') + create_by = Column(String(64), nullable=True, comment='创建者') + update_time = Column(DateTime, nullable=True, comment='更新时间') + update_by = Column(String(64), nullable=True, comment='更新者') + + + diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/sys_statistics_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/sys_statistics_do.py new file mode 100644 index 0000000..38d0918 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/sys_statistics_do.py @@ -0,0 +1,25 @@ +from sqlalchemy import Date, DateTime, String, BigInteger, Column, Integer +from config.database import Base + + +class SysStatistics(Base): + """ + 系统统计数据表 + """ + + __tablename__ = 'sys_statistics' + + statistic_id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='统计项目ID') + stat_date = Column(Date, nullable=False, comment='统计日期') + llm_call = Column(BigInteger, nullable=True, comment='大模型调用量') + access_control_count = Column(BigInteger, nullable=True, comment='门禁通行量') + visitor_guide = Column(BigInteger, nullable=True, comment='访客引导次数') + exhibition_explanation = Column(BigInteger, nullable=True, comment='展厅讲解次数') + access_control_success_rate = Column(Integer, nullable=True, comment='门禁识别成功率(%)') + create_time = Column(DateTime, nullable=True, comment='创建时间') + create_by = Column(String(64), nullable=True, comment='创建者') + update_time = Column(DateTime, nullable=False, comment='修改时间') + update_by = Column(String(64), nullable=True, comment='修改者') + + + diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py new file mode 100644 index 0000000..2dd0ba0 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py @@ -0,0 +1,53 @@ +from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base + + +class SysUser(Base): + """ + 用户信息表 + """ + + __tablename__ = 'sys_user' + + user_id = Column(Integer, primary_key=True, autoincrement=True, comment='用户ID') + dept_id = Column(Integer, default=None, comment='部门ID') + user_name = Column(String(30), nullable=False, comment='用户账号') + nick_name = Column(String(30), nullable=False, comment='用户昵称') + user_type = Column(String(2), default='00', comment='用户类型(00系统用户)') + email = Column(String(50), default='', comment='用户邮箱') + phonenumber = Column(String(11), default='', comment='手机号码') + sex = Column(String(1), default='0', comment='用户性别(0男 1女 2未知)') + avatar = Column(String(100), default='', comment='头像地址') + password = Column(String(100), default='', comment='密码') + status = Column(String(1), default='0', comment='帐号状态(0正常 1停用)') + del_flag = Column(String(1), default='0', comment='删除标志(0代表存在 2代表删除)') + login_ip = Column(String(128), default='', comment='最后登录IP') + login_date = Column(DateTime, comment='最后登录时间') + create_by = Column(String(64), default='', comment='创建者') + create_time = Column(DateTime, comment='创建时间', default=datetime.now()) + update_by = Column(String(64), default='', comment='更新者') + update_time = Column(DateTime, comment='更新时间', default=datetime.now()) + remark = Column(String(500), default=None, comment='备注') + + +class SysUserRole(Base): + """ + 用户和角色关联表 + """ + + __tablename__ = 'sys_user_role' + + user_id = Column(Integer, primary_key=True, nullable=False, comment='用户ID') + role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID') + + +class SysUserPost(Base): + """ + 用户与岗位关联表 + """ + + __tablename__ = 'sys_user_post' + + user_id = Column(Integer, primary_key=True, nullable=False, comment='用户ID') + post_id = Column(Integer, primary_key=True, nullable=False, comment='岗位ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/visitor_type_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/visitor_type_do.py new file mode 100644 index 0000000..8f698af --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/visitor_type_do.py @@ -0,0 +1,21 @@ +from sqlalchemy import BigInteger, DateTime, String, Column +from config.database import Base + + +class VisitorType(Base): + """ + 访客类型表 + """ + + __tablename__ = 'visitor_type' + + type_id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='访客类型ID') + name = Column(String(64), nullable=True, comment='类型名称') + value = Column(String(2), nullable=False, comment='类型值') + create_time = Column(DateTime, nullable=True, comment='创建时间') + create_by = Column(String(64), nullable=True, comment='创建者') + update_time = Column(DateTime, nullable=True, comment='修改时间') + update_by = Column(String(64), nullable=True, comment='修改者') + + + diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/words_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/words_do.py new file mode 100644 index 0000000..d253fee --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/words_do.py @@ -0,0 +1,26 @@ +from sqlalchemy import CHAR, Column, DateTime, String, BigInteger +from config.database import Base + + +class GuideWords(Base): + """ + 引导词表 + """ + + __tablename__ = 'guide_words' + + guide_id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='引导词id') + type = Column(String(2), nullable=True, comment='引导词类型') + text = Column(String(1024), nullable=True, comment='文本内容') + create_time = Column(DateTime, nullable=True, comment='创建时间') + update_time = Column(DateTime, nullable=True, comment='修改时间') + create_by = Column(String(64), nullable=True, comment='创建者') + remark = Column(String(500), nullable=True, comment='备注') + del_flag = Column(CHAR(1), nullable=False, comment='删除标志(0代表存在 2代表删除)') + update_by = Column(String(64), nullable=True, comment='修改者') + title = Column(String(64), nullable=True, comment='引导词名称') + label = Column(String(2), nullable=True, comment='引导词标签') + trigger_type = Column(String(2), nullable=True, comment='触发方式') + + + diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/__init__.py b/ruoyi-fastapi-backend/module_admin/entity/vo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/cache_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/cache_vo.py new file mode 100644 index 0000000..79b49fb --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/cache_vo.py @@ -0,0 +1,28 @@ +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from typing import Any, List, Optional + + +class CacheMonitorModel(BaseModel): + """ + 缓存监控信息对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + command_stats: Optional[List] = Field(default=[], description='命令统计') + db_size: Optional[int] = Field(default=None, description='Key数量') + info: Optional[dict] = Field(default={}, description='Redis信息') + + +class CacheInfoModel(BaseModel): + """ + 缓存监控对象对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + cache_key: Optional[str] = Field(default=None, description='缓存键名') + cache_name: Optional[str] = Field(default=None, description='缓存名称') + cache_value: Optional[Any] = Field(default=None, description='缓存内容') + remark: Optional[str] = Field(default=None, description='备注') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/common_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/common_vo.py new file mode 100644 index 0000000..258be5d --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/common_vo.py @@ -0,0 +1,26 @@ +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from typing import Any, Optional + + +class CrudResponseModel(BaseModel): + """ + 操作响应模型 + """ + + is_success: bool = Field(description='操作是否成功') + message: str = Field(description='响应信息') + result: Optional[Any] = Field(default=None, description='响应结果') + + +class UploadResponseModel(BaseModel): + """ + 上传响应模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + file_name: Optional[str] = Field(default=None, description='新文件映射路径') + new_file_name: Optional[str] = Field(default=None, description='新文件名称') + original_filename: Optional[str] = Field(default=None, description='原文件名称') + url: Optional[str] = Field(default=None, description='新文件url') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/config_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/config_vo.py new file mode 100644 index 0000000..917d7d6 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/config_vo.py @@ -0,0 +1,74 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank, Size +from typing import Literal, Optional +from module_admin.annotation.pydantic_annotation import as_query + + +class ConfigModel(BaseModel): + """ + 参数配置表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + config_id: Optional[int] = Field(default=None, description='参数主键') + config_name: Optional[str] = Field(default=None, description='参数名称') + config_key: Optional[str] = Field(default=None, description='参数键名') + config_value: Optional[str] = Field(default=None, description='参数键值') + config_type: Optional[Literal['Y', 'N']] = Field(default=None, description='系统内置(Y是 N否)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='config_key', message='参数名称不能为空') + @Size(field_name='config_key', min_length=0, max_length=100, message='参数名称长度不能超过100个字符') + def get_config_key(self): + return self.config_key + + @NotBlank(field_name='config_name', message='参数键名不能为空') + @Size(field_name='config_name', min_length=0, max_length=100, message='参数键名长度不能超过100个字符') + def get_config_name(self): + return self.config_name + + @NotBlank(field_name='config_value', message='参数键值不能为空') + @Size(field_name='config_value', min_length=0, max_length=500, message='参数键值长度不能超过500个字符') + def get_config_value(self): + return self.config_value + + def validate_fields(self): + self.get_config_key() + self.get_config_name() + self.get_config_value() + + +class ConfigQueryModel(ConfigModel): + """ + 参数配置管理不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class ConfigPageQueryModel(ConfigQueryModel): + """ + 参数配置管理分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteConfigModel(BaseModel): + """ + 删除参数配置模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + config_ids: str = Field(description='需要删除的参数主键') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/dept_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/dept_vo.py new file mode 100644 index 0000000..dcad117 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/dept_vo.py @@ -0,0 +1,75 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import Network, NotBlank, Size +from typing import Literal, Optional +from module_admin.annotation.pydantic_annotation import as_query + + +class DeptModel(BaseModel): + """ + 部门表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + dept_id: Optional[int] = Field(default=None, description='部门id') + parent_id: Optional[int] = Field(default=None, description='父部门id') + ancestors: Optional[str] = Field(default=None, description='祖级列表') + dept_name: Optional[str] = Field(default=None, description='部门名称') + order_num: Optional[int] = Field(default=None, description='显示顺序') + leader: Optional[str] = Field(default=None, description='负责人') + phone: Optional[str] = Field(default=None, description='联系电话') + email: Optional[str] = Field(default=None, description='邮箱') + status: Optional[Literal['0', '1']] = Field(default=None, description='部门状态(0正常 1停用)') + del_flag: Optional[Literal['0', '2']] = Field(default=None, description='删除标志(0代表存在 2代表删除)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + + @NotBlank(field_name='dept_name', message='部门名称不能为空') + @Size(field_name='dept_name', min_length=0, max_length=30, message='部门名称长度不能超过30个字符') + def get_dept_name(self): + return self.dept_name + + @NotBlank(field_name='order_num', message='显示顺序不能为空') + def get_order_num(self): + return self.order_num + + @Size(field_name='phone', min_length=0, max_length=11, message='联系电话长度不能超过11个字符') + def get_phone(self): + return self.phone + + @Network(field_name='email', field_type='EmailStr', message='邮箱格式不正确') + @Size(field_name='email', min_length=0, max_length=50, message='邮箱长度不能超过50个字符') + def get_email(self): + return self.email + + def validate_fields(self): + self.get_dept_name() + self.get_order_num() + self.get_phone() + self.get_email() + + +@as_query +class DeptQueryModel(DeptModel): + """ + 部门管理不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +class DeleteDeptModel(BaseModel): + """ + 删除部门模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + dept_ids: str = Field(default=None, description='需要删除的部门id') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[str] = Field(default=None, description='更新时间') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/dict_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/dict_vo.py new file mode 100644 index 0000000..3251f08 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/dict_vo.py @@ -0,0 +1,149 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank, Pattern, Size +from typing import Literal, Optional +from module_admin.annotation.pydantic_annotation import as_query + + +class DictTypeModel(BaseModel): + """ + 字典类型表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + dict_id: Optional[int] = Field(default=None, description='字典主键') + dict_name: Optional[str] = Field(default=None, description='字典名称') + dict_type: Optional[str] = Field(default=None, description='字典类型') + status: Optional[Literal['0', '1']] = Field(default=None, description='状态(0正常 1停用)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='dict_name', message='字典名称不能为空') + @Size(field_name='dict_name', min_length=0, max_length=100, message='字典类型名称长度不能超过100个字符') + def get_dict_name(self): + return self.dict_name + + @NotBlank(field_name='dict_type', message='字典类型不能为空') + @Size(field_name='dict_type', min_length=0, max_length=100, message='字典类型类型长度不能超过100个字符') + @Pattern( + field_name='dict_type', + regexp='^[a-z][a-z0-9_]*$', + message='字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)', + ) + def get_dict_type(self): + return self.dict_type + + def validate_fields(self): + self.get_dict_name() + self.get_dict_type() + + +class DictDataModel(BaseModel): + """ + 字典数据表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + dict_code: Optional[int] = Field(default=None, description='字典编码') + dict_sort: Optional[int] = Field(default=None, description='字典排序') + dict_label: Optional[str] = Field(default=None, description='字典标签') + dict_value: Optional[str] = Field(default=None, description='字典键值') + dict_type: Optional[str] = Field(default=None, description='字典类型') + css_class: Optional[str] = Field(default=None, description='样式属性(其他样式扩展)') + list_class: Optional[str] = Field(default=None, description='表格回显样式') + is_default: Optional[Literal['Y', 'N']] = Field(default=None, description='是否默认(Y是 N否)') + status: Optional[Literal['0', '1']] = Field(default=None, description='状态(0正常 1停用)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='dict_label', message='字典标签不能为空') + @Size(field_name='dict_label', min_length=0, max_length=100, message='字典标签长度不能超过100个字符') + def get_dict_label(self): + return self.dict_label + + @NotBlank(field_name='dict_value', message='字典键值不能为空') + @Size(field_name='dict_value', min_length=0, max_length=100, message='字典键值长度不能超过100个字符') + def get_dict_value(self): + return self.dict_value + + @NotBlank(field_name='dict_type', message='字典类型不能为空') + @Size(field_name='dict_type', min_length=0, max_length=100, message='字典类型长度不能超过100个字符') + def get_dict_type(self): + return self.dict_type + + @Size(field_name='css_class', min_length=0, max_length=100, message='样式属性长度不能超过100个字符') + def get_css_class(self): + return self.css_class + + def validate_fields(self): + self.get_dict_label() + self.get_dict_value() + self.get_dict_type() + self.get_css_class() + + +class DictTypeQueryModel(DictTypeModel): + """ + 字典类型管理不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class DictTypePageQueryModel(DictTypeQueryModel): + """ + 字典类型管理分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteDictTypeModel(BaseModel): + """ + 删除字典类型模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + dict_ids: str = Field(description='需要删除的字典主键') + + +class DictDataQueryModel(DictDataModel): + """ + 字典数据管理不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class DictDataPageQueryModel(DictDataQueryModel): + """ + 字典数据管理分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteDictDataModel(BaseModel): + """ + 删除字典数据模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + dict_codes: str = Field(description='需要删除的字典编码') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/door_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/door_vo.py new file mode 100644 index 0000000..7c3bd09 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/door_vo.py @@ -0,0 +1,57 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + + + +class DoorModel(BaseModel): + """ + 门禁设备表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + id: Optional[int] = Field(default=None, description='主键自增') + indexCode: Optional[str] = Field(default=None, description='设备索引编码') + name: Optional[str] = Field(default=None, description='门禁名称') + install_location: Optional[str] = Field(default=None, description='位置名称') + status: Optional[str] = Field(default=None, description='设备状态, 0 离线, 1 正常在线') + permission: Optional[str] = Field(default=None, description='权限, 0 禁用, 1 正常调用') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + create_by: Optional[str] = Field(default=None, description='创建者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + update_by: Optional[str] = Field(default=None, description='更新者') + + + + + + +class DoorQueryModel(DoorModel): + """ + 门禁设备不分页查询模型 + """ + key_word : Optional[str] = Field(default=None, description='模糊查询字段') + + + +@as_query +class DoorPageQueryModel(DoorQueryModel): + """ + 门禁设备分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteDoorModel(BaseModel): + """ + 删除门禁设备模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + ids: str = Field(description='需要删除的主键自增') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/explanation_content_type_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/explanation_content_type_vo.py new file mode 100644 index 0000000..fbcfb6b --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/explanation_content_type_vo.py @@ -0,0 +1,53 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + + + +class Explanation_content_typeModel(BaseModel): + """ + 讲解内容类型表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + content_type_id: Optional[int] = Field(default=None, description='主键ID') + name: Optional[str] = Field(default=None, description='类型名称') + type_value: Optional[str] = Field(default=None, description='类型值(与讲解内容表中的type对应)') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + create_by: Optional[str] = Field(default=None, description='创建者') + update_time: Optional[datetime] = Field(default=None, description='修改时间') + update_by: Optional[str] = Field(default=None, description='修改者') + + + + + + +class Explanation_content_typeQueryModel(Explanation_content_typeModel): + """ + 讲解内容类型不分页查询模型 + """ + pass + + +@as_query +class Explanation_content_typePageQueryModel(Explanation_content_typeQueryModel): + """ + 讲解内容类型分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteExplanation_content_typeModel(BaseModel): + """ + 删除讲解内容类型模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + content_type_ids: str = Field(description='需要删除的主键ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/explanation_content_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/explanation_content_vo.py new file mode 100644 index 0000000..d552d35 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/explanation_content_vo.py @@ -0,0 +1,67 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + + + +class Explanation_contentModel(BaseModel): + """ + 讲解内容表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + explanation_content_id: Optional[int] = Field(default=None, description='主键ID') + title: Optional[str] = Field(default=None, description='标题') + content: Optional[str] = Field(default=None, description='讲解内容') + type: Optional[str] = Field(default=None, description='类型(0科技类)') + spend_time: Optional[int] = Field(default=None, description='预计时长(分钟)') + language: Optional[str] = Field(default=None, description='语言(0中文,1英文)') + status: Optional[str] = Field(default=None, description='状态(0未开始,1已就绪,2播放中,3已完成)') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + create_by: Optional[str] = Field(default=None, description='创建者') + update_time: Optional[datetime] = Field(default=None, description='修改时间') + update_by: Optional[str] = Field(default=None, description='修改者') + + @NotBlank(field_name='title', message='标题不能为空') + def get_title(self): + return self.title + + + def validate_fields(self): + self.get_title() + + + + +class Explanation_contentQueryModel(Explanation_contentModel): + """ + 讲解内容不分页查询模型 + """ + begin_create_time: Optional[str] = Field(default=None, description='开始创建时间') + end_create_time: Optional[str] = Field(default=None, description='结束创建时间') + begin_update_time: Optional[str] = Field(default=None, description='开始修改时间') + end_update_time: Optional[str] = Field(default=None, description='结束修改时间') + + +@as_query +class Explanation_contentPageQueryModel(Explanation_contentQueryModel): + """ + 讲解内容分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteExplanation_contentModel(BaseModel): + """ + 删除讲解内容模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + explanation_content_ids: str = Field(description='需要删除的主键ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/explanation_style_robot_pair_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/explanation_style_robot_pair_vo.py new file mode 100644 index 0000000..2c74bd2 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/explanation_style_robot_pair_vo.py @@ -0,0 +1,83 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + +class SwitchExplanationStyleModel(BaseModel): + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + pairing_id: int = Field(default=None, description='主键ID') + robot_id: int = Field(default=None, description='机器人ID(关联robot_info表)') + robot_role_id: int = Field(default=None, description='机器人角色ID(关联robot_role表)') + + update_by: Optional[str] = Field(default=None, description='修改者') + + + + + +class Explanation_style_robot_pairModel(BaseModel): + """ + 讲解风格--机器人配对表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + pairing_id: Optional[int] = Field(default=None, description='主键ID') + robot_id: Optional[int] = Field(default=None, description='机器人ID(关联robot_info表)') + robot_role_id : Optional[int] = Field(default=None, description='机器人角色ID(关联robot_role表)') + explanation_style_id: Optional[int] = Field(default=None, description='讲解风格ID(关联explanation_style表)') + prompt: Optional[str] = Field(default=None, description='提示词') + status: Optional[str] = Field(default=None, description='状态(0停用,1使用)') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + create_by: Optional[str] = Field(default=None, description='创建者') + update_time: Optional[datetime] = Field(default=None, description='修改时间') + update_by: Optional[str] = Field(default=None, description='修改者') + + @NotBlank(field_name='robot_id', message='机器人ID不能为空') + def get_robot_id(self): + return self.robot_id + + @NotBlank(field_name='explanation_style_id', message='讲解风格ID不能为空') + def get_explanation_style_id(self): + return self.explanation_style_id + + + def validate_fields(self): + self.get_robot_id() + self.get_explanation_style_id() + + + + +class Explanation_style_robot_pairQueryModel(Explanation_style_robot_pairModel): + """ + 讲解风格--机器人配对不分页查询模型 + """ + begin_create_time: Optional[str] = Field(default=None, description='开始创建时间') + end_create_time: Optional[str] = Field(default=None, description='结束创建时间') + begin_update_time: Optional[str] = Field(default=None, description='开始修改时间') + end_update_time: Optional[str] = Field(default=None, description='结束修改时间') + + +@as_query +class Explanation_style_robot_pairPageQueryModel(Explanation_style_robot_pairQueryModel): + """ + 讲解风格--机器人配对分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteExplanation_style_robot_pairModel(BaseModel): + """ + 删除讲解风格--机器人配对模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + pairing_ids: str = Field(description='需要删除的主键ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/explanation_style_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/explanation_style_vo.py new file mode 100644 index 0000000..6920c07 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/explanation_style_vo.py @@ -0,0 +1,56 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + + + +class Explanation_styleModel(BaseModel): + """ + 讲解风格表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + explanation_style_id: Optional[int] = Field(default=None, description='主键ID') + name: Optional[str] = Field(default=None, description='风格名称') + detail: Optional[str] = Field(default=None, description='风格简介') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + create_by: Optional[str] = Field(default=None, description='创建者') + update_time: Optional[datetime] = Field(default=None, description='修改时间') + update_by: Optional[str] = Field(default=None, description='修改者') + + + + + + +class Explanation_styleQueryModel(Explanation_styleModel): + """ + 讲解风格不分页查询模型 + """ + begin_create_time: Optional[str] = Field(default=None, description='开始创建时间') + end_create_time: Optional[str] = Field(default=None, description='结束创建时间') + begin_update_time: Optional[str] = Field(default=None, description='开始修改时间') + end_update_time: Optional[str] = Field(default=None, description='结束修改时间') + + +@as_query +class Explanation_stylePageQueryModel(Explanation_styleQueryModel): + """ + 讲解风格分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteExplanation_styleModel(BaseModel): + """ + 删除讲解风格模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + explanation_style_ids: str = Field(description='需要删除的主键ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/haikang_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/haikang_vo.py new file mode 100644 index 0000000..d8c5158 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/haikang_vo.py @@ -0,0 +1,24 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + +class VisitorReservationQueryModel(BaseModel): + + + receptionistId: Optional[str] = Field(default=None, description='接待ID') + visitorName: Optional[str] = Field(default=None, description='访客姓名,支持模糊搜索') + phoneNo: Optional[str] = Field(default=None, description='访客手机号') + visitStartTimeBegin: Optional[str] = Field(default=None, description='开始时间, 满足ISO8601格式') + visitStartTimeEnd: Optional[str] = Field(default=None, description='结束时间, 满足ISO8601格式') + visitEndTimeBegin: Optional[str] = Field(default=None, description='开始时间, 满足ISO8601格式') + visitEndTimeEnd: Optional[str] = Field(default=None, description='结束时间, 满足ISO8601格式') + + pageNo: int = Field(default=1, description='页码') + pageSize: int = Field(default=20, description='每页数量') + + visitorStatus: Optional[int] = Field(default=None, description='访客状态') + orderId: Optional[str] = Field(default=None, description='订单ID') + \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/identification_record_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/identification_record_vo.py new file mode 100644 index 0000000..684bb6f --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/identification_record_vo.py @@ -0,0 +1,57 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + + + +class Identification_recordModel(BaseModel): + """ + 识别记录表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + id: Optional[int] = Field(default=None, description='主键 自增') + person_name: Optional[str] = Field(default=None, description='识别人姓名') + door_name: Optional[str] = Field(default=None, description='门禁点') + status: Optional[str] = Field(default=None, description='识别结果, 0 拒绝, 1 通过') + source: Optional[str] = Field(default=None, description='识别来源') + pic_uri: Optional[str] = Field(default=None, description='图像url') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + create_by: Optional[str] = Field(default=None, description='创建者') + conf: Optional[int] = Field(default=None, description='识别置信度') + person_status: Optional[str] = Field(default=None, description=' 0 陌生人, 1 员工, 2 访客') + + + + + + +class Identification_recordQueryModel(Identification_recordModel): + """ + 识别记录不分页查询模型 + """ + begin_create_time: Optional[str] = Field(default=None, description='开始创建时间') + end_create_time: Optional[str] = Field(default=None, description='结束创建时间') + + +@as_query +class Identification_recordPageQueryModel(Identification_recordQueryModel): + """ + 识别记录分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteIdentification_recordModel(BaseModel): + """ + 删除识别记录模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + ids: str = Field(description='需要删除的主键 自增') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/identification_statistics_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/identification_statistics_vo.py new file mode 100644 index 0000000..c9d6992 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/identification_statistics_vo.py @@ -0,0 +1,53 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + + + +class Identification_statisticsModel(BaseModel): + """ + 识别统计表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + id: Optional[int] = Field(default=None, description='主键 自增') + employ: Optional[int] = Field(default=None, description='员工识别成功数') + visitor: Optional[int] = Field(default=None, description='访客识别成功数') + stranger: Optional[int] = Field(default=None, description='位置人员识别失败数') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + create_by: Optional[str] = Field(default=None, description='创建者') + + + + + + +class Identification_statisticsQueryModel(Identification_statisticsModel): + """ + 识别统计不分页查询模型 + """ + begin_create_time: Optional[str] = Field(default=None, description='开始创建时间') + end_create_time: Optional[str] = Field(default=None, description='结束创建时间') + + +@as_query +class Identification_statisticsPageQueryModel(Identification_statisticsQueryModel): + """ + 识别统计分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteIdentification_statisticsModel(BaseModel): + """ + 删除识别统计模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + ids: str = Field(description='需要删除的主键 自增') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/info_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/info_vo.py new file mode 100644 index 0000000..a1b5406 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/info_vo.py @@ -0,0 +1,100 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + + + +class InfoModel(BaseModel): + """ + 机器人信息表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + robot_id: Optional[int] = Field(default=None, description='机器人ID') + name: Optional[str] = Field(default=None, description='机器人名称') + model: Optional[str] = Field(default=None, description='机器人型号') + serial: Optional[str] = Field(default=None, description='序列号') + online: Optional[str] = Field(default=None, description='机器人在线状态(0在线, 1离线)') + power: Optional[str] = Field(default=None, description='电量') + run_time: Optional[int] = Field(default=None, description='总运行时长(单位:分钟)') + position: Optional[str] = Field(default=None, description='位置') + image: Optional[str] = Field(default=None, description='机器人外观图地址') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + create_by: Optional[str] = Field(default=None, description='创建者') + update_time: Optional[datetime] = Field(default=None, description='修改时间') + update_by: Optional[str] = Field(default=None, description='修改者') + del_flag: Optional[str] = Field(default=None, description='删除标志(0存在, 2删除)') + remark: Optional[str] = Field(default=None, description='备注') + last_charge: Optional[datetime] = Field(default=None, description='上一次充电时间') + ip: Optional[str] = Field(default=None, description='机器人ip') + status: Optional[str] = Field(default=None, description='使用情况(0待机,1正在运行)') + video_uri: Optional[str] = Field(default=None, description='视频uri') + + + @NotBlank(field_name='name', message='机器人名称不能为空') + def get_name(self): + return self.name + + # @NotBlank(field_name='del_flag', message='删除标志(0存在, 2删除)不能为空') + # def get_del_flag(self): + # return self.del_flag + + + def validate_fields(self): + self.get_name() + # self.get_del_flag() + + + + +class InfoQueryModel(InfoModel): + """ + 机器人信息不分页查询模型 + """ + begin_create_time: Optional[str] = Field(default=None, description='开始创建时间') + end_create_time: Optional[str] = Field(default=None, description='结束创建时间') + begin_update_time: Optional[str] = Field(default=None, description='开始修改时间') + end_update_time: Optional[str] = Field(default=None, description='结束修改时间') + + +@as_query +class InfoPageQueryModel(InfoQueryModel): + """ + 机器人信息分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class InfoUpdateModel(BaseModel): + """ + 更新机器人信息模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + robot_id: Optional[int] = Field(default=None, description='机器人ID') + + name: Optional[str] = Field(default=None, description='机器人名称') + model: Optional[str] = Field(default=None, description='机器人型号') + serial: Optional[str] = Field(default=None, description='序列号') + position: Optional[str] = Field(default=None, description='位置') + image: Optional[str] = Field(default=None, description='机器人外观图地址') + remark: Optional[str] = Field(default=None, description='备注') + update_time: Optional[datetime] = Field(default=None, description='修改时间') + update_by: Optional[str] = Field(default=None, description='修改者') + + +class DeleteInfoModel(BaseModel): + """ + 删除机器人信息模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + robot_ids: str = Field(description='需要删除的机器人ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/job_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/job_vo.py new file mode 100644 index 0000000..960a837 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/job_vo.py @@ -0,0 +1,134 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank, Size +from typing import Literal, Optional +from module_admin.annotation.pydantic_annotation import as_query + + +class JobModel(BaseModel): + """ + 定时任务调度表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + job_id: Optional[int] = Field(default=None, description='任务ID') + job_name: Optional[str] = Field(default=None, description='任务名称') + job_group: Optional[str] = Field(default=None, description='任务组名') + job_executor: Optional[str] = Field(default=None, description='任务执行器') + invoke_target: Optional[str] = Field(default=None, description='调用目标字符串') + job_args: Optional[str] = Field(default=None, description='位置参数') + job_kwargs: Optional[str] = Field(default=None, description='关键字参数') + cron_expression: Optional[str] = Field(default=None, description='cron执行表达式') + misfire_policy: Optional[Literal['1', '2', '3']] = Field( + default=None, description='计划执行错误策略(1立即执行 2执行一次 3放弃执行)' + ) + concurrent: Optional[Literal['0', '1']] = Field(default=None, description='是否并发执行(0允许 1禁止)') + status: Optional[Literal['0', '1']] = Field(default=None, description='状态(0正常 1暂停)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注信息') + + @NotBlank(field_name='invoke_target', message='调用目标字符串不能为空') + @Size(field_name='invoke_target', min_length=0, max_length=500, message='调用目标字符串长度不能超过500个字符') + def get_invoke_target(self): + return self.invoke_target + + @NotBlank(field_name='cron_expression', message='Cron执行表达式不能为空') + @Size(field_name='cron_expression', min_length=0, max_length=255, message='Cron执行表达式不能超过255个字符') + def get_cron_expression(self): + return self.cron_expression + + def validate_fields(self): + self.get_invoke_target() + self.get_cron_expression() + + +class JobLogModel(BaseModel): + """ + 定时任务调度日志表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + job_log_id: Optional[int] = Field(default=None, description='任务日志ID') + job_name: Optional[str] = Field(default=None, description='任务名称') + job_group: Optional[str] = Field(default=None, description='任务组名') + job_executor: Optional[str] = Field(default=None, description='任务执行器') + invoke_target: Optional[str] = Field(default=None, description='调用目标字符串') + job_args: Optional[str] = Field(default=None, description='位置参数') + job_kwargs: Optional[str] = Field(default=None, description='关键字参数') + job_trigger: Optional[str] = Field(default=None, description='任务触发器') + job_message: Optional[str] = Field(default=None, description='日志信息') + status: Optional[Literal['0', '1']] = Field(default=None, description='执行状态(0正常 1失败)') + exception_info: Optional[str] = Field(default=None, description='异常信息') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + + +class JobQueryModel(JobModel): + """ + 定时任务管理不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class JobPageQueryModel(JobQueryModel): + """ + 定时任务管理分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class EditJobModel(JobModel): + """ + 编辑定时任务模型 + """ + + type: Optional[str] = Field(default=None, description='操作类型') + + +class DeleteJobModel(BaseModel): + """ + 删除定时任务模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + job_ids: str = Field(description='需要删除的定时任务ID') + + +class JobLogQueryModel(JobLogModel): + """ + 定时任务日志不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class JobLogPageQueryModel(JobLogQueryModel): + """ + 定时任务日志管理分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteJobLogModel(BaseModel): + """ + 删除定时任务日志模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + job_log_ids: str = Field(description='需要删除的定时任务日志ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/log_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/log_vo.py new file mode 100644 index 0000000..739ad6c --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/log_vo.py @@ -0,0 +1,131 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from typing import Literal, Optional +from module_admin.annotation.pydantic_annotation import as_query + + +class OperLogModel(BaseModel): + """ + 操作日志表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + oper_id: Optional[int] = Field(default=None, description='日志主键') + title: Optional[str] = Field(default=None, description='模块标题') + business_type: Optional[Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']] = ( + Field( + default=None, description='业务类型(0其它 1新增 2修改 3删除 4授权 5导出 6导入 7强退 8生成代码 9清空数据)' + ) + ) + method: Optional[str] = Field(default=None, description='方法名称') + request_method: Optional[str] = Field(default=None, description='请求方式') + operator_type: Optional[Literal[0, 1, 2]] = Field( + default=None, description='操作类别(0其它 1后台用户 2手机端用户)' + ) + oper_name: Optional[str] = Field(default=None, description='操作人员') + dept_name: Optional[str] = Field(default=None, description='部门名称') + oper_url: Optional[str] = Field(default=None, description='请求URL') + oper_ip: Optional[str] = Field(default=None, description='主机地址') + oper_location: Optional[str] = Field(default=None, description='操作地点') + oper_param: Optional[str] = Field(default=None, description='请求参数') + json_result: Optional[str] = Field(default=None, description='返回参数') + status: Optional[Literal[0, 1, '0', '1']] = Field(default=None, description='操作状态(0正常 1异常)') + error_msg: Optional[str] = Field(default=None, description='错误消息') + oper_time: Optional[datetime] = Field(default=None, description='操作时间') + cost_time: Optional[int] = Field(default=None, description='消耗时间') + + +class LogininforModel(BaseModel): + """ + 登录日志表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + info_id: Optional[int] = Field(default=None, description='访问ID') + user_name: Optional[str] = Field(default=None, description='用户账号') + ipaddr: Optional[str] = Field(default=None, description='登录IP地址') + login_location: Optional[str] = Field(default=None, description='登录地点') + browser: Optional[str] = Field(default=None, description='浏览器类型') + os: Optional[str] = Field(default=None, description='操作系统') + status: Optional[Literal['0', '1']] = Field(default=None, description='登录状态(0成功 1失败)') + msg: Optional[str] = Field(default=None, description='提示消息') + login_time: Optional[datetime] = Field(default=None, description='访问时间') + + +class OperLogQueryModel(OperLogModel): + """ + 操作日志管理不分页查询模型 + """ + + order_by_column: Optional[str] = Field(default=None, description='排序的字段名称') + is_asc: Optional[Literal['ascending', 'descending']] = Field( + default=None, description='排序方式(ascending升序 descending降序)' + ) + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class OperLogPageQueryModel(OperLogQueryModel): + """ + 操作日志管理分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteOperLogModel(BaseModel): + """ + 删除操作日志模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + oper_ids: str = Field(description='需要删除的日志主键') + + +class LoginLogQueryModel(LogininforModel): + """ + 登录日志管理不分页查询模型 + """ + + order_by_column: Optional[str] = Field(default=None, description='排序的字段名称') + is_asc: Optional[Literal['ascending', 'descending']] = Field( + default=None, description='排序方式(ascending升序 descending降序)' + ) + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class LoginLogPageQueryModel(LoginLogQueryModel): + """ + 登录日志管理分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteLoginLogModel(BaseModel): + """ + 删除登录日志模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + info_ids: str = Field(description='需要删除的访问ID') + + +class UnlockUser(BaseModel): + """ + 解锁用户模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + user_name: str = Field(description='用户名称') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py new file mode 100644 index 0000000..fbd6e07 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py @@ -0,0 +1,87 @@ +import re +from pydantic import BaseModel, ConfigDict, Field, model_validator +from pydantic.alias_generators import to_camel +from typing import List, Optional, Union +from exceptions.exception import ModelValidatorException +from module_admin.entity.vo.menu_vo import MenuModel + + +class UserLogin(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + user_name: str = Field(description='用户名称') + password: str = Field(description='用户密码') + code: Optional[str] = Field(default=None, description='验证码') + uuid: Optional[str] = Field(default=None, description='会话编号') + login_info: Optional[dict] = Field(default=None, description='登录信息,前端无需传递') + captcha_enabled: Optional[bool] = Field(default=None, description='是否启用验证码,前端无需传递') + + +class UserRegister(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + username: str = Field(description='用户名称') + password: str = Field(description='用户密码') + confirm_password: str = Field(description='用户二次确认密码') + code: Optional[str] = Field(default=None, description='验证码') + uuid: Optional[str] = Field(default=None, description='会话编号') + + @model_validator(mode='after') + def check_password(self) -> 'UserRegister': + pattern = r"""^[^<>"'|\\]+$""" + if self.password is None or re.match(pattern, self.password): + return self + else: + raise ModelValidatorException(message='密码不能包含非法字符:< > " \' \\ |') + + +class Token(BaseModel): + access_token: str = Field(description='token信息') + token_type: str = Field(description='token类型') + + +class CaptchaCode(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + captcha_enabled: bool = Field(description='是否启用验证码') + register_enabled: bool = Field(description='是否启用注册') + img: str = Field(description='验证码图片') + uuid: str = Field(description='会话编号') + + +class SmsCode(BaseModel): + is_success: Optional[bool] = Field(default=None, description='操作是否成功') + sms_code: str = Field(description='短信验证码') + session_id: str = Field(description='会话编号') + message: Optional[str] = Field(default=None, description='响应信息') + + +class MenuTreeModel(MenuModel): + children: Optional[Union[List['MenuTreeModel'], None]] = Field(default=None, description='子菜单') + + +class MetaModel(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + title: Optional[str] = Field(default=None, description='设置路由在侧边栏和面包屑中展示的名字') + icon: Optional[str] = Field(default=None, description='设置路由的图标') + no_cache: Optional[bool] = Field(default=None, description='设置为true,则不会被 缓存') + link: Optional[str] = Field(default=None, description='内链地址(http(s)://开头)') + + +class RouterModel(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + name: Optional[str] = Field(default=None, description='路由名称') + path: Optional[str] = Field(default=None, description='路由地址') + hidden: Optional[bool] = Field(default=None, description='是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现') + redirect: Optional[str] = Field( + default=None, description='重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击' + ) + component: Optional[str] = Field(default=None, description='组件地址') + query: Optional[str] = Field(default=None, description='路由参数:如 {"id": 1, "name": "ry"}') + always_show: Optional[bool] = Field( + default=None, description='当你一个路由下面的children声明的路由大于1个时,自动会变成嵌套的模式--如组件页面' + ) + meta: Optional[MetaModel] = Field(default=None, description='其他元素') + children: Optional[Union[List['RouterModel'], None]] = Field(default=None, description='子路由') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/menu_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/menu_vo.py new file mode 100644 index 0000000..9dc8d75 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/menu_vo.py @@ -0,0 +1,88 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank, Size +from typing import Literal, Optional +from module_admin.annotation.pydantic_annotation import as_query + + +class MenuModel(BaseModel): + """ + 菜单表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + menu_id: Optional[int] = Field(default=None, description='菜单ID') + menu_name: Optional[str] = Field(default=None, description='菜单名称') + parent_id: Optional[int] = Field(default=None, description='父菜单ID') + order_num: Optional[int] = Field(default=None, description='显示顺序') + path: Optional[str] = Field(default=None, description='路由地址') + component: Optional[str] = Field(default=None, description='组件路径') + query: Optional[str] = Field(default=None, description='路由参数') + route_name: Optional[str] = Field(default=None, description='路由名称') + is_frame: Optional[Literal[0, 1]] = Field(default=None, description='是否为外链(0是 1否)') + is_cache: Optional[Literal[0, 1]] = Field(default=None, description='是否缓存(0缓存 1不缓存)') + menu_type: Optional[Literal['M', 'C', 'F']] = Field(default=None, description='菜单类型(M目录 C菜单 F按钮)') + visible: Optional[Literal['0', '1']] = Field(default=None, description='菜单状态(0显示 1隐藏)') + status: Optional[Literal['0', '1']] = Field(default=None, description='菜单状态(0正常 1停用)') + perms: Optional[str] = Field(default=None, description='权限标识') + icon: Optional[str] = Field(default=None, description='菜单图标') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='menu_name', message='菜单名称不能为空') + @Size(field_name='menu_name', min_length=0, max_length=50, message='菜单名称长度不能超过50个字符') + def get_menu_name(self): + return self.menu_name + + @NotBlank(field_name='order_num', message='显示顺序不能为空') + def get_order_num(self): + return self.order_num + + @Size(field_name='path', min_length=0, max_length=200, message='路由地址长度不能超过200个字符') + def get_path(self): + return self.path + + @Size(field_name='component', min_length=0, max_length=255, message='组件路径长度不能超过255个字符') + def get_component(self): + return self.component + + @NotBlank(field_name='menu_type', message='菜单类型不能为空') + def get_menu_type(self): + return self.menu_type + + @Size(field_name='perms', min_length=0, max_length=100, message='权限标识长度不能超过100个字符') + def get_perms(self): + return self.perms + + def validate_fields(self): + self.get_menu_name() + self.get_order_num() + self.get_path() + self.get_component() + self.get_menu_type() + self.get_perms() + + +@as_query +class MenuQueryModel(MenuModel): + """ + 菜单管理不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +class DeleteMenuModel(BaseModel): + """ + 删除菜单模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + menu_ids: str = Field(description='需要删除的菜单ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/message_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/message_vo.py new file mode 100644 index 0000000..0e513ae --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/message_vo.py @@ -0,0 +1,104 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + + + +class MessageModel(BaseModel): + """ + 系统消息表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + message_id: Optional[int] = Field(default=None, description='消息主键') + type: Optional[str] = Field(default=None, description='类型(0告警消息)') + status: Optional[str] = Field(default=None, description='状态(0未处理,1已处理)') + detail: Optional[str] = Field(default=None, description='消息描述') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + create_by: Optional[str] = Field(default=None, description='创建者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + update_by: Optional[str] = Field(default=None, description='更新者') + remark : Optional[str] = Field(default=None, description='备注') + specific_type: Optional[str] = Field(default=None, description='具体类型') + + # @NotBlank(field_name='type', message='类型不能为空') + # def get_type(self): + # return self.type + # + # @NotBlank(field_name='status', message='状态不能为空') + # def get_status(self): + # return self.status + # + # @NotBlank(field_name='create_time', message='创建时间不能为空') + # def get_create_time(self): + # return self.create_time + # + # @NotBlank(field_name='update_time', message='更新时间不能为空') + # def get_update_time(self): + # return self.update_time + # + # @NotBlank(field_name='update_by', message='更新者不能为空') + # def get_update_by(self): + # return self.update_by + # + # def validate_fields(self): + # self.get_type() + # self.get_status() + # self.get_create_time() + # self.get_update_time() + # self.get_update_by() + + + + +class MessageQueryModel(BaseModel): + """ + 系统消息不分页查询模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + type: Optional[str] = Field(default=None, description='类型(0告警消息)') + status: Optional[str] = Field(default=None, description='状态(0未处理,1已处理)') + + begin_create_time: Optional[str] = Field(default=None, description='开始创建时间') + end_create_time: Optional[str] = Field(default=None, description='结束创建时间') + begin_update_time: Optional[str] = Field(default=None, description='开始更新时间') + end_update_time: Optional[str] = Field(default=None, description='结束更新时间') + +# 编辑消息类 +class EditMessageModel(BaseModel): + """ + 系统消息编辑模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + message_id: int = Field(default=None, description='消息主键') + status: Optional[str] = Field(default=None, description='状态(0未处理,1已处理)') + remark: Optional[str] = Field(default=None, description='备注') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + update_by: Optional[str] = Field(default=None, description='更新者') + + + +@as_query +class MessagePageQueryModel(MessageQueryModel): + """ + 系统消息分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteMessageModel(BaseModel): + """ + 删除系统消息模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + message_ids: str = Field(description='需要删除的消息主键') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/notice_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/notice_vo.py new file mode 100644 index 0000000..0b5d70c --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/notice_vo.py @@ -0,0 +1,63 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank, Size, Xss +from typing import Literal, Optional +from module_admin.annotation.pydantic_annotation import as_query + + +class NoticeModel(BaseModel): + """ + 通知公告表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + notice_id: Optional[int] = Field(default=None, description='公告ID') + notice_title: Optional[str] = Field(default=None, description='公告标题') + notice_type: Optional[Literal['1', '2']] = Field(default=None, description='公告类型(1通知 2公告)') + notice_content: Optional[bytes] = Field(default=None, description='公告内容') + status: Optional[Literal['0', '1']] = Field(default=None, description='公告状态(0正常 1关闭)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @Xss(field_name='notice_title', message='公告标题不能包含脚本字符') + @NotBlank(field_name='notice_title', message='公告标题不能为空') + @Size(field_name='notice_title', min_length=0, max_length=50, message='公告标题不能超过50个字符') + def get_notice_title(self): + return self.notice_title + + def validate_fields(self): + self.get_notice_title() + + +class NoticeQueryModel(NoticeModel): + """ + 通知公告管理不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class NoticePageQueryModel(NoticeQueryModel): + """ + 通知公告管理分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteNoticeModel(BaseModel): + """ + 删除通知公告模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + notice_ids: str = Field(description='需要删除的公告ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/online_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/online_vo.py new file mode 100644 index 0000000..11bb475 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/online_vo.py @@ -0,0 +1,42 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + +class OnlineModel(BaseModel): + """ + 在线用户对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + token_id: Optional[str] = Field(default=None, description='会话编号') + user_name: Optional[str] = Field(default=None, description='登录名称') + dept_name: Optional[str] = Field(default=None, description='所属部门') + ipaddr: Optional[str] = Field(default=None, description='主机') + login_location: Optional[str] = Field(default=None, description='登录地点') + browser: Optional[str] = Field(default=None, description='浏览器类型') + os: Optional[str] = Field(default=None, description='操作系统') + login_time: Optional[datetime] = Field(default=None, description='登录时间') + + +@as_query +class OnlineQueryModel(OnlineModel): + """ + 岗位管理不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +class DeleteOnlineModel(BaseModel): + """ + 强退在线用户模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + token_ids: str = Field(description='需要强退的会话编号') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/post_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/post_vo.py new file mode 100644 index 0000000..79390c3 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/post_vo.py @@ -0,0 +1,73 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank, Size +from typing import Literal, Optional +from module_admin.annotation.pydantic_annotation import as_query + + +class PostModel(BaseModel): + """ + 岗位信息表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + post_id: Optional[int] = Field(default=None, description='岗位ID') + post_code: Optional[str] = Field(default=None, description='岗位编码') + post_name: Optional[str] = Field(default=None, description='岗位名称') + post_sort: Optional[int] = Field(default=None, description='显示顺序') + status: Optional[Literal['0', '1']] = Field(default=None, description='状态(0正常 1停用)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='post_code', message='岗位编码不能为空') + @Size(field_name='post_code', min_length=0, max_length=64, message='岗位编码长度不能超过64个字符') + def get_post_code(self): + return self.post_code + + @NotBlank(field_name='post_name', message='岗位名称不能为空') + @Size(field_name='post_name', min_length=0, max_length=50, message='岗位名称长度不能超过50个字符') + def get_post_name(self): + return self.post_name + + @NotBlank(field_name='post_sort', message='显示顺序不能为空') + def get_post_sort(self): + return self.post_sort + + def validate_fields(self): + self.get_post_code() + self.get_post_name() + self.get_post_sort() + + +class PostQueryModel(PostModel): + """ + 岗位管理不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class PostPageQueryModel(PostQueryModel): + """ + 岗位管理分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeletePostModel(BaseModel): + """ + 删除岗位模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + post_ids: str = Field(description='需要删除的岗位ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/ragflow_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/ragflow_vo.py new file mode 100644 index 0000000..7150481 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/ragflow_vo.py @@ -0,0 +1,153 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from typing import Optional, List +from module_admin.annotation.pydantic_annotation import as_query + + + +class RagflowListQueryModel(BaseModel): + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + page: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页数量') + orderby: Optional[str] = Field(default='create_time', description='排序字段') + desc: Optional[str] = Field(default='true', description='排序方式') + name: Optional[str] = Field(default=None, description='名称') + # dataset_id: Optional[str] = Field(default=None, description='数据集ID') + # chat_id: Optional[str] = Field(default=None, description='聊天ID') + +@as_query +class ListDocumentsQueryModel(BaseModel): + """ + 查询文档列表参数模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + page: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页数量') + orderby: Optional[str] = Field(default='create_time', description='排序字段') + desc: Optional[str] = Field(default='true', description='排序方式') + keywords: Optional[str] = Field(default=None, description='关键字') + document_id: Optional[str] = Field(default=None, description='文档ID') + document_name: Optional[str] = Field(default=None, description='文档名称') + + +class UpdateFileModel(BaseModel): + """ + 更新文件模型 + """ + # name 要带文件后缀名 + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + name: Optional[str] = Field(default=None, description='文件名称') + meta_fields: Optional[dict] = Field(default=None, description='文件元数据') + + # naive, manual qa table paper book laws presentation picture one email + chunk_method: Optional[str] = Field(default=None, description='分块方法') + + # 不同的分块方法有不同的参数. + parser_config: Optional[dict] = Field(default=None, description='解析器配置') + + # status: Optional[str] = Field(default=None, description='状态') + + +class DocumentIdsModel(BaseModel): + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + documnet_ids: list[str] = Field(default=None, description='文件ID列表') + +class DeleteFileModel(BaseModel): + + + ids: List[str] = Field(description='文档ID列表') + + +class CreateDatasetModel(BaseModel): + """ + 创建数据集参数模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + name:str = Field(description='数据集名称') + avatar: Optional[str] = Field(default=None, description='数据集头像, base64编码') + description: Optional[str] = Field(default=None, description='数据集描述') + embedding_model: Optional[str] = Field(default=None, description='数据集的embedding模型') + permission: Optional[str] = Field(default = "me", description='数据集权限') + chunk_method: Optional[str] = Field(default = "naive", description='数据集分块方法') + parser_config: Optional[dict] = Field(default = None, description='数据集解析配置') + +class LLM(BaseModel): + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + model_name: Optional[str] = Field(default=None, description='模型名称') + temperature: Optional[float] = Field(default=0.1, description='模型温度') + top_p: Optional[float] = Field(default=0.3, description='模型top_p') + presence_penalty: Optional[float] = Field(default=0.2, description='模型presence_penalty') + frequency_penalty: Optional[float] = Field(default=0.7, description='模型frequency_penalty') + +class Prompt(BaseModel): + """ + 聊天助手提示词参数 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + similarity_threshold: Optional[float] = Field(default=0.2, description='相似度阈值') + keywords_similarity_weight: Optional[float] = Field(default=0.7, description='关键词相似度权重') + top_n: Optional[int] = Field(default=8, description='返回结果数量') + variables: Optional[List[dict]] = Field(default=[{"key": "knowledge", "optional": "true"}], description='变量列表') + # 默认余弦相似度 + rerank_model: Optional[str] = Field(default=None, description='rerank模型') + empty_response: Optional[str] = Field(default=None, description='空结果回复') + opener: Optional[str] = Field(default=None, description='开启者') + show_quote: Optional[bool] = Field(default=True, description='是否显示引用') + prompt: Optional[str] = Field(default=None, description='提示语') + + + +class UpdateChatAssistantModel(BaseModel): + """ 修改聊天助手参数 + + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + chat_id: str = Field(default = None, description='聊天助手ID') + name: Optional[str] = Field(default = None, description='聊天助手名称') + # base64编码的图像 + avatar: Optional[str] = Field(default = None, description='聊天助手头像') + # 启用了哪些数据集? + dataset_ids: Optional[List[str]] = Field(default = None, description='数据集ID列表') + + # 模型配置参数 + llm: Optional[LLM] = Field(default = None, description='LLM模型') + + prompt: Optional[Prompt] = Field(default = None, description='LLM模型') + + +class CreateSessionWithChatModel(BaseModel): + """ + 创建会话及会话内容模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + chat_id: str = Field(default = None, description='会话ID') + name: str = Field(default = None, description='会话名称') + user_id: Optional[str] = Field(default = None, description='用户ID') + +class ConverseWithChatAssistantModel(BaseModel): + """ + 会话聊天模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + chat_id: str = Field(default = None, description='会话ID') + question: str = Field(default = None, description='问题') + stream: Optional[bool] = Field(default = True, description='是否流式返回') + session_id: Optional[str] = Field(default = None, description='会话ID') + user_id: Optional[str] = Field(default = None, description='用户ID') + + + + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/robot_action_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/robot_action_vo.py new file mode 100644 index 0000000..38ecde2 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/robot_action_vo.py @@ -0,0 +1,54 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + + + +class Robot_actionModel(BaseModel): + """ + 机器人动作表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + id: Optional[int] = Field(default=None, description='主键 自增') + name: Optional[str] = Field(default=None, description='动作名') + description: Optional[str] = Field(default=None, description='描述') + interval_min: Optional[int] = Field(default=None, description='最小时间间隔 单位s') + interval_max: Optional[int] = Field(default=None, description='最大时间间隔 单位s') + status: Optional[str] = Field(default=None, description='动作状态 0 未启用, 1已启用') + update_time: Optional[datetime] = Field(default=None, description='修改时间') + + + + + + +class Robot_actionQueryModel(Robot_actionModel): + """ + 机器人动作不分页查询模型 + """ + begin_update_time: Optional[str] = Field(default=None, description='开始修改时间') + end_update_time: Optional[str] = Field(default=None, description='结束修改时间') + + +@as_query +class Robot_actionPageQueryModel(Robot_actionQueryModel): + """ + 机器人动作分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=20, description='每页记录数') + + +class DeleteRobot_actionModel(BaseModel): + """ + 删除机器人动作模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + ids: str = Field(description='需要删除的主键 自增') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/robot_role_pairing_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/robot_role_pairing_vo.py new file mode 100644 index 0000000..d5329fd --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/robot_role_pairing_vo.py @@ -0,0 +1,94 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + + + +# class RolePairing(BaseModel): +# """ +# 角色-机器人-映射表对应pydantic模型, 查询角色-机器人结果的返回模型 +# """ +# model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) +# +# pairing_id: Optional[int] = Field(default=None, description='主键ID') +# rolename: Optional[str] = Field(default=None, description='角色名称') +# detail: Optional[str] = Field(default=None, description='角色介绍') +# status: Optional[str] = Field(default=None, description='使用情况(0停用,1使用)') + +class ChangeRobotRoleModel(BaseModel): + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + robot_id: Optional[int] = Field(default=None, description='机器人ID') + pairing_id: Optional[int] = Field(default=None, description='主键ID') + +class PairingModel(BaseModel): + """ + 角色-机器人-映射表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + pairing_id: Optional[int] = Field(default=None, description='主键ID') + robot_role_id: Optional[int] = Field(default=None, description='机器人角色ID') + robot_id: Optional[int] = Field(default=None, description='机器人ID') + status: Optional[str] = Field(default=None, description='使用情况(0停用,1使用)') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + update_by: Optional[str] = Field(default=None, description='修改者') + + @NotBlank(field_name='robot_role_id', message='机器人角色ID不能为空') + def get_robot_role_id(self): + return self.robot_role_id + + @NotBlank(field_name='robot_id', message='机器人ID不能为空') + def get_robot_id(self): + return self.robot_id + + @NotBlank(field_name='status', message='使用情况(0停用,1使用)不能为空') + def get_status(self): + return self.status + + @NotBlank(field_name='update_time', message='更新时间不能为空') + def get_update_time(self): + return self.update_time + + @NotBlank(field_name='update_by', message='修改者不能为空') + def get_update_by(self): + return self.update_by + + def validate_fields(self): + self.get_robot_role_id() + self.get_robot_id() + self.get_status() + self.get_update_time() + self.get_update_by() + + + + +class PairingQueryModel(PairingModel): + """ + 角色-机器人-映射不分页查询模型 + """ + pass + + +@as_query +class PairingPageQueryModel(PairingQueryModel): + """ + 角色-机器人-映射分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeletePairingModel(BaseModel): + """ + 删除角色-机器人-映射模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + pairing_ids: str = Field(description='需要删除的主键ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/robot_role_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/robot_role_vo.py new file mode 100644 index 0000000..30317e1 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/robot_role_vo.py @@ -0,0 +1,84 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + + + +class RoleModel(BaseModel): + """ + 系统角色表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + robot_role_id: Optional[int] = Field(default=None, description='机器人角色ID') + rolename: Optional[str] = Field(default=None, description='角色名称') + detail: Optional[str] = Field(default=None, description='角色介绍') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + create_by: Optional[str] = Field(default=None, description='创建者') + update_time: Optional[datetime] = Field(default=None, description='修改时间') + update_by: Optional[str] = Field(default=None, description='修改者') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='rolename', message='角色名称不能为空') + def get_rolename(self): + return self.rolename + + @NotBlank(field_name='create_time', message='创建时间不能为空') + def get_create_time(self): + return self.create_time + + @NotBlank(field_name='create_by', message='创建者不能为空') + def get_create_by(self): + return self.create_by + + @NotBlank(field_name='update_time', message='修改时间不能为空') + def get_update_time(self): + return self.update_time + + @NotBlank(field_name='update_by', message='修改者不能为空') + def get_update_by(self): + return self.update_by + + + def validate_fields(self): + self.get_rolename() + self.get_create_time() + self.get_create_by() + self.get_update_time() + self.get_update_by() + + + + +class RoleQueryModel(RoleModel): + """ + 系统角色不分页查询模型 + """ + begin_create_time: Optional[str] = Field(default=None, description='开始创建时间') + end_create_time: Optional[str] = Field(default=None, description='结束创建时间') + begin_update_time: Optional[str] = Field(default=None, description='开始修改时间') + end_update_time: Optional[str] = Field(default=None, description='结束修改时间') + + +@as_query +class RolePageQueryModel(RoleQueryModel): + """ + 系统角色分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteRoleModel(BaseModel): + """ + 删除系统角色模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + robot_role_ids: str = Field(description='需要删除的机器人角色ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/role_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/role_vo.py new file mode 100644 index 0000000..a81f8d5 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/role_vo.py @@ -0,0 +1,158 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank, Size +from typing import List, Literal, Optional, Union +from module_admin.annotation.pydantic_annotation import as_query + + +class RoleModel(BaseModel): + """ + 角色表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + role_id: Optional[int] = Field(default=None, description='角色ID') + role_name: Optional[str] = Field(default=None, description='角色名称') + role_key: Optional[str] = Field(default=None, description='角色权限字符串') + role_sort: Optional[int] = Field(default=None, description='显示顺序') + data_scope: Optional[Literal['1', '2', '3', '4', '5']] = Field( + default=None, + description='数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限)', + ) + menu_check_strictly: Optional[Union[int, bool]] = Field(default=None, description='菜单树选择项是否关联显示') + dept_check_strictly: Optional[Union[int, bool]] = Field(default=None, description='部门树选择项是否关联显示') + status: Optional[Literal['0', '1']] = Field(default=None, description='角色状态(0正常 1停用)') + del_flag: Optional[Literal['0', '2']] = Field(default=None, description='删除标志(0代表存在 2代表删除)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + admin: Optional[bool] = Field(default=False, description='是否为admin') + + @field_validator('menu_check_strictly', 'dept_check_strictly') + @classmethod + def check_filed_mapping(cls, v: Union[int, bool]) -> Union[int, bool]: + if v == 1: + v = True + elif v == 0: + v = False + elif v is True: + v = 1 + elif v is False: + v = 0 + return v + + @model_validator(mode='after') + def check_admin(self) -> 'RoleModel': + if self.role_id == 1: + self.admin = True + else: + self.admin = False + return self + + @NotBlank(field_name='role_name', message='角色名称不能为空') + @Size(field_name='role_name', min_length=0, max_length=30, message='角色名称长度不能超过30个字符') + def get_role_name(self): + return self.role_name + + @NotBlank(field_name='role_key', message='权限字符不能为空') + @Size(field_name='role_key', min_length=0, max_length=100, message='权限字符长度不能超过100个字符') + def get_role_key(self): + return self.role_key + + @NotBlank(field_name='role_sort', message='显示顺序不能为空') + def get_role_sort(self): + return self.role_sort + + def validate_fields(self): + self.get_role_name() + self.get_role_key() + self.get_role_sort() + + +class RoleMenuModel(BaseModel): + """ + 角色和菜单关联表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + role_id: Optional[int] = Field(default=None, description='角色ID') + menu_id: Optional[int] = Field(default=None, description='菜单ID') + + +class RoleDeptModel(BaseModel): + """ + 角色和部门关联表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + role_id: Optional[int] = Field(default=None, description='角色ID') + dept_id: Optional[int] = Field(default=None, description='部门ID') + + +class RoleQueryModel(RoleModel): + """ + 角色管理不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class RolePageQueryModel(RoleQueryModel): + """ + 角色管理分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class RoleMenuQueryModel(BaseModel): + """ + 角色菜单查询模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + menus: List = Field(default=[], description='菜单信息') + checked_keys: List[int] = Field(default=[], description='已选择的菜单ID信息') + + +class RoleDeptQueryModel(BaseModel): + """ + 角色部门查询模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + depts: List = Field(default=[], description='部门信息') + checked_keys: List[int] = Field(default=[], description='已选择的部门ID信息') + + +class AddRoleModel(RoleModel): + """ + 新增角色模型 + """ + + dept_ids: List = Field(default=[], description='部门ID信息') + menu_ids: List = Field(default=[], description='菜单ID信息') + type: Optional[str] = Field(default=None, description='操作类型') + + +class DeleteRoleModel(BaseModel): + """ + 删除角色模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + role_ids: str = Field(description='需要删除的菜单ID') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/server_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/server_vo.py new file mode 100644 index 0000000..810ecac --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/server_vo.py @@ -0,0 +1,67 @@ +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from typing import List, Optional + + +class CpuInfo(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + cpu_num: Optional[int] = Field(default=None, description='核心数') + used: Optional[float] = Field(default=None, description='CPU用户使用率') + sys: Optional[float] = Field(default=None, description='CPU系统使用率') + free: Optional[float] = Field(default=None, description='CPU当前空闲率') + + +class MemoryInfo(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + total: Optional[str] = Field(default=None, description='内存总量') + used: Optional[str] = Field(default=None, description='已用内存') + free: Optional[str] = Field(default=None, description='剩余内存') + usage: Optional[float] = Field(default=None, description='使用率') + + +class SysInfo(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + computer_ip: Optional[str] = Field(default=None, description='服务器IP') + computer_name: Optional[str] = Field(default=None, description='服务器名称') + os_arch: Optional[str] = Field(default=None, description='系统架构') + os_name: Optional[str] = Field(default=None, description='操作系统') + user_dir: Optional[str] = Field(default=None, description='项目路径') + + +class PyInfo(MemoryInfo): + model_config = ConfigDict(alias_generator=to_camel) + + name: Optional[str] = Field(default=None, description='Python名称') + version: Optional[str] = Field(default=None, description='Python版本') + start_time: Optional[str] = Field(default=None, description='启动时间') + run_time: Optional[str] = Field(default=None, description='运行时长') + home: Optional[str] = Field(default=None, description='安装路径') + + +class SysFiles(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + dir_name: Optional[str] = Field(default=None, description='盘符路径') + sys_type_name: Optional[str] = Field(default=None, description='盘符类型') + type_name: Optional[str] = Field(default=None, description='文件类型') + total: Optional[str] = Field(default=None, description='总大小') + used: Optional[str] = Field(default=None, description='已经使用量') + free: Optional[str] = Field(default=None, description='剩余大小') + usage: Optional[str] = Field(default=None, description='资源的使用率') + + +class ServerMonitorModel(BaseModel): + """ + 服务监控对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + cpu: Optional[CpuInfo] = Field(description='CPU相关信息') + py: Optional[PyInfo] = Field(description='Python相关信息') + mem: Optional[MemoryInfo] = Field(description='內存相关信息') + sys: Optional[SysInfo] = Field(description='服务器相关信息') + sys_files: Optional[List[SysFiles]] = Field(description='磁盘相关信息') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/sys_alert_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/sys_alert_vo.py new file mode 100644 index 0000000..cfb949e --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/sys_alert_vo.py @@ -0,0 +1,57 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + + + +class Sys_alertModel(BaseModel): + """ + 系统告警表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + id: Optional[int] = Field(default=None, description='主键 自增') + detail: Optional[str] = Field(default=None, description='告警描述') + type: Optional[str] = Field(default=None, description='告警类型') + status: Optional[str] = Field(default=None, description='告警状态, 0 未处理, 1 已处理') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + create_by: Optional[str] = Field(default=None, description='创建者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + update_by: Optional[str] = Field(default=None, description='更新者') + + + + + + +class Sys_alertQueryModel(Sys_alertModel): + """ + 系统告警不分页查询模型 + """ + begin_create_time: Optional[str] = Field(default=None, description='开始创建时间') + end_create_time: Optional[str] = Field(default=None, description='结束创建时间') + begin_update_time: Optional[str] = Field(default=None, description='开始更新时间') + end_update_time: Optional[str] = Field(default=None, description='结束更新时间') + + +@as_query +class Sys_alertPageQueryModel(Sys_alertQueryModel): + """ + 系统告警分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteSys_alertModel(BaseModel): + """ + 删除系统告警模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + ids: str = Field(description='需要删除的主键 自增') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/sys_statistics_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/sys_statistics_vo.py new file mode 100644 index 0000000..92a263c --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/sys_statistics_vo.py @@ -0,0 +1,69 @@ +from datetime import datetime, date +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + + + +class Sys_statisticsModel(BaseModel): + """ + 系统统计数据表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + statistic_id: Optional[int] = Field(default=None, description='统计项目ID') + stat_date: Optional[date] = Field(default=None, description='统计日期') + llm_call: Optional[int] = Field(default=None, description='大模型调用量') + access_control_count: Optional[int] = Field(default=None, description='门禁通行量') + visitor_guide: Optional[int] = Field(default=None, description='访客引导次数') + exhibition_explanation: Optional[int] = Field(default=None, description='展厅讲解次数') + access_control_success_rate: Optional[int] = Field(default=None, description='门禁识别成功率(%)') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + create_by: Optional[str] = Field(default=None, description='创建者') + update_time: Optional[datetime] = Field(default=None, description='修改时间') + update_by: Optional[str] = Field(default=None, description='修改者') + + @NotBlank(field_name='stat_date', message='统计日期不能为空') + def get_stat_date(self): + return self.stat_date + + @NotBlank(field_name='update_time', message='修改时间不能为空') + def get_update_time(self): + return self.update_time + + + def validate_fields(self): + self.get_stat_date() + self.get_update_time() + + + + +class Sys_statisticsQueryModel(Sys_statisticsModel): + """ + 系统统计数据不分页查询模型 + """ + pass + + +@as_query +class Sys_statisticsPageQueryModel(Sys_statisticsQueryModel): + """ + 系统统计数据分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteSys_statisticsModel(BaseModel): + """ + 删除系统统计数据模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + statistic_ids: str = Field(description='需要删除的统计项目ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/test_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/test_vo.py new file mode 100644 index 0000000..e2ad1d6 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/test_vo.py @@ -0,0 +1,21 @@ +from typing import Optional +from pydantic import BaseModel, Field, ConfigDict +from module_admin.annotation.pydantic_annotation import as_query +from pydantic.alias_generators import to_camel + + + +class TestUserName(BaseModel): + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + user_name: Optional[str] = Field(default=None, description='用户名') + + +class TestPostForm(BaseModel): + user_name: Optional[str] = None + password: Optional[str] = None + +@as_query +class TestUserNamePage(TestUserName): + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py new file mode 100644 index 0000000..4eac953 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py @@ -0,0 +1,282 @@ +import re +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field, model_validator +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import Network, NotBlank, Size, Xss +from typing import List, Literal, Optional, Union +from exceptions.exception import ModelValidatorException +from module_admin.annotation.pydantic_annotation import as_query +from module_admin.entity.vo.dept_vo import DeptModel +from module_admin.entity.vo.post_vo import PostModel +from module_admin.entity.vo.role_vo import RoleModel + + +class TokenData(BaseModel): + """ + token解析结果 + """ + + user_id: Union[int, None] = Field(default=None, description='用户ID') + + +class UserModel(BaseModel): + """ + 用户表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + user_id: Optional[int] = Field(default=None, description='用户ID') + dept_id: Optional[int] = Field(default=None, description='部门ID') + user_name: Optional[str] = Field(default=None, description='用户账号') + nick_name: Optional[str] = Field(default=None, description='用户昵称') + user_type: Optional[str] = Field(default=None, description='用户类型(00系统用户)') + email: Optional[str] = Field(default=None, description='用户邮箱') + phonenumber: Optional[str] = Field(default=None, description='手机号码') + sex: Optional[Literal['0', '1', '2']] = Field(default=None, description='用户性别(0男 1女 2未知)') + avatar: Optional[str] = Field(default=None, description='头像地址') + password: Optional[str] = Field(default=None, description='密码') + status: Optional[Literal['0', '1']] = Field(default=None, description='帐号状态(0正常 1停用)') + del_flag: Optional[Literal['0', '2']] = Field(default=None, description='删除标志(0代表存在 2代表删除)') + login_ip: Optional[str] = Field(default=None, description='最后登录IP') + login_date: Optional[datetime] = Field(default=None, description='最后登录时间') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + admin: Optional[bool] = Field(default=False, description='是否为admin') + + @model_validator(mode='after') + def check_password(self) -> 'UserModel': + pattern = r"""^[^<>"'|\\]+$""" + if self.password is None or re.match(pattern, self.password): + return self + else: + raise ModelValidatorException(message='密码不能包含非法字符:< > " \' \\ |') + + @model_validator(mode='after') + def check_admin(self) -> 'UserModel': + if self.user_id == 1: + self.admin = True + else: + self.admin = False + return self + + @Xss(field_name='user_name', message='用户账号不能包含脚本字符') + @NotBlank(field_name='user_name', message='用户账号不能为空') + @Size(field_name='user_name', min_length=0, max_length=30, message='用户账号长度不能超过30个字符') + def get_user_name(self): + return self.user_name + + @Xss(field_name='nick_name', message='用户昵称不能包含脚本字符') + @Size(field_name='nick_name', min_length=0, max_length=30, message='用户昵称长度不能超过30个字符') + def get_nick_name(self): + return self.nick_name + + @Network(field_name='email', field_type='EmailStr', message='邮箱格式不正确') + @Size(field_name='email', min_length=0, max_length=50, message='邮箱长度不能超过50个字符') + def get_email(self): + return self.email + + @Size(field_name='phonenumber', min_length=0, max_length=11, message='手机号码长度不能超过11个字符') + def get_phonenumber(self): + return self.phonenumber + + def validate_fields(self): + self.get_user_name() + self.get_nick_name() + self.get_email() + self.get_phonenumber() + + +class UserRoleModel(BaseModel): + """ + 用户和角色关联表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + user_id: Optional[int] = Field(default=None, description='用户ID') + role_id: Optional[int] = Field(default=None, description='角色ID') + + +class UserPostModel(BaseModel): + """ + 用户与岗位关联表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + user_id: Optional[int] = Field(default=None, description='用户ID') + post_id: Optional[int] = Field(default=None, description='岗位ID') + + +class UserInfoModel(UserModel): + post_ids: Optional[Union[str, None]] = Field(default=None, description='岗位ID信息') + role_ids: Optional[Union[str, None]] = Field(default=None, description='角色ID信息') + dept: Optional[Union[DeptModel, None]] = Field(default=None, description='部门信息') + role: Optional[List[Union[RoleModel, None]]] = Field(default=[], description='角色信息') + + +class CurrentUserModel(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + permissions: List = Field(description='权限信息') + roles: List = Field(description='角色信息') + user: Union[UserInfoModel, None] = Field(description='用户信息') + + +class UserDetailModel(BaseModel): + """ + 获取用户详情信息响应模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + data: Optional[Union[UserInfoModel, None]] = Field(default=None, description='用户信息') + post_ids: Optional[List] = Field(default=None, description='岗位ID信息') + posts: List[Union[PostModel, None]] = Field(description='岗位信息') + role_ids: Optional[List] = Field(default=None, description='角色ID信息') + roles: List[Union[RoleModel, None]] = Field(description='角色信息') + + +class UserProfileModel(BaseModel): + """ + 获取个人信息响应模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + data: Union[UserInfoModel, None] = Field(description='用户信息') + post_group: Union[str, None] = Field(description='岗位信息') + role_group: Union[str, None] = Field(description='角色信息') + + +class UserQueryModel(UserModel): + """ + 用户管理不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class UserPageQueryModel(UserQueryModel): + """ + 用户管理分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class AddUserModel(UserModel): + """ + 新增用户模型 + """ + + role_ids: Optional[List] = Field(default=[], description='角色ID信息') + post_ids: Optional[List] = Field(default=[], description='岗位ID信息') + type: Optional[str] = Field(default=None, description='操作类型') + + +class EditUserModel(AddUserModel): + """ + 编辑用户模型 + """ + + role: Optional[List] = Field(default=[], description='角色信息') + + +class ResetPasswordModel(BaseModel): + """ + 重置密码模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + old_password: Optional[str] = Field(default=None, description='旧密码') + new_password: Optional[str] = Field(default=None, description='新密码') + + @model_validator(mode='after') + def check_new_password(self) -> 'ResetPasswordModel': + pattern = r"""^[^<>"'|\\]+$""" + if self.new_password is None or re.match(pattern, self.new_password): + return self + else: + raise ModelValidatorException(message='密码不能包含非法字符:< > " \' \\ |') + + +class ResetUserModel(UserModel): + """ + 重置用户密码模型 + """ + + old_password: Optional[str] = Field(default=None, description='旧密码') + sms_code: Optional[str] = Field(default=None, description='验证码') + session_id: Optional[str] = Field(default=None, description='会话id') + + +class DeleteUserModel(BaseModel): + """ + 删除用户模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + user_ids: str = Field(description='需要删除的用户ID') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + + +class UserRoleQueryModel(UserModel): + """ + 用户角色关联管理不分页查询模型 + """ + + role_id: Optional[int] = Field(default=None, description='角色ID') + + +@as_query +class UserRolePageQueryModel(UserRoleQueryModel): + """ + 用户角色关联管理分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class SelectedRoleModel(RoleModel): + """ + 是否选择角色模型 + """ + + flag: Optional[bool] = Field(default=False, description='选择标识') + + +class UserRoleResponseModel(BaseModel): + """ + 用户角色关联管理列表返回模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + roles: List[Union[SelectedRoleModel, None]] = Field(default=[], description='角色信息') + user: UserInfoModel = Field(description='用户信息') + + +@as_query +class CrudUserRoleModel(BaseModel): + """ + 新增、删除用户关联角色及角色关联用户模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + user_id: Optional[int] = Field(default=None, description='用户ID') + user_ids: Optional[str] = Field(default=None, description='用户ID信息') + role_id: Optional[int] = Field(default=None, description='角色ID') + role_ids: Optional[str] = Field(default=None, description='角色ID信息') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/visitor_type_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/visitor_type_vo.py new file mode 100644 index 0000000..f7f53ff --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/visitor_type_vo.py @@ -0,0 +1,60 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + + + +class Visitor_typeModel(BaseModel): + """ + 访客类型表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + type_id: Optional[int] = Field(default=None, description='访客类型ID') + name: Optional[str] = Field(default=None, description='类型名称') + value: Optional[str] = Field(default=None, description='类型值') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + create_by: Optional[str] = Field(default=None, description='创建者') + update_time: Optional[datetime] = Field(default=None, description='修改时间') + update_by: Optional[str] = Field(default=None, description='修改者') + + @NotBlank(field_name='value', message='类型值不能为空') + def get_value(self): + return self.value + + + def validate_fields(self): + self.get_value() + + + + +class Visitor_typeQueryModel(Visitor_typeModel): + """ + 访客类型不分页查询模型 + """ + pass + + +@as_query +class Visitor_typePageQueryModel(Visitor_typeQueryModel): + """ + 访客类型分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteVisitor_typeModel(BaseModel): + """ + 删除访客类型模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + type_ids: str = Field(description='需要删除的访客类型ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/words_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/words_vo.py new file mode 100644 index 0000000..c6139ff --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/words_vo.py @@ -0,0 +1,80 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank +from typing import Optional +from module_admin.annotation.pydantic_annotation import as_query + + + + +class WordsModel(BaseModel): + """ + 引导词表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + guide_id: Optional[int] = Field(default=None, description='引导词id') + type: Optional[str] = Field(default=None, description='引导词类型') + text: Optional[str] = Field(default=None, description='文本内容') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_time: Optional[datetime] = Field(default=None, description='修改时间') + create_by: Optional[str] = Field(default=None, description='创建者') + remark: Optional[str] = Field(default=None, description='备注') + del_flag: Optional[str] = Field(default=None, description='删除标志(0代表存在 2代表删除)') + update_by : Optional[str] = Field(default=None, description='修改者') + title: Optional[str] = Field(default=None, description='引导词名称') + label: Optional[str] = Field(default=None, description='引导词标签') + trigger_type: Optional[str] = Field(default=None, description='触发方式') + + + + + # @NotBlank(field_name='del_flag', message='删除标志不能为空') + def get_del_flag(self): + return self.del_flag + + def validate_fields(self): + self.get_del_flag() + +class WordsUpdateModel(WordsModel): + """ + 引导词更新模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + guide_id: int = Field(description='引导词id') + text: str = Field(default=None, description='文本内容') + type: Optional[str] = Field(default=None, description='引导词类型') + remark : Optional[str] = Field(default=None, description='备注') + update_time: Optional[datetime] = Field(default=None, description='修改时间') + update_by : Optional[str] = Field(default=None, description='修改者') + + +class WordsQueryModel(WordsModel): + """ + 引导词不分页查询模型 + """ + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class WordsPageQueryModel(WordsQueryModel): + """ + 引导词分页查询模型 + """ + + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteWordsModel(BaseModel): + """ + 删除引导词模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + guide_ids: str = Field(description='需要删除的引导词id') diff --git a/ruoyi-fastapi-backend/module_admin/service/__init__.py b/ruoyi-fastapi-backend/module_admin/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ruoyi-fastapi-backend/module_admin/service/cache_service.py b/ruoyi-fastapi-backend/module_admin/service/cache_service.py new file mode 100644 index 0000000..72212cb --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/cache_service.py @@ -0,0 +1,124 @@ +from fastapi import Request +from config.enums import RedisInitKeyConfig +from config.get_redis import RedisUtil +from module_admin.entity.vo.cache_vo import CacheInfoModel, CacheMonitorModel +from module_admin.entity.vo.common_vo import CrudResponseModel + + +class CacheService: + """ + 缓存监控模块服务层 + """ + + @classmethod + async def get_cache_monitor_statistical_info_services(cls, request: Request): + """ + 获取缓存监控信息service + + :param request: Request对象 + :return: 缓存监控信息 + """ + info = await request.app.state.redis.info() + db_size = await request.app.state.redis.dbsize() + command_stats_dict = await request.app.state.redis.info('commandstats') + command_stats = [ + dict(name=key.split('_')[1], value=str(value.get('calls'))) for key, value in command_stats_dict.items() + ] + result = CacheMonitorModel(commandStats=command_stats, dbSize=db_size, info=info) + + return result + + @classmethod + async def get_cache_monitor_cache_name_services(cls): + """ + 获取缓存名称列表信息service + + :return: 缓存名称列表信息 + """ + name_list = [] + for key_config in RedisInitKeyConfig: + name_list.append( + CacheInfoModel( + cacheKey='', + cacheName=key_config.key, + cacheValue='', + remark=key_config.remark, + ) + ) + + return name_list + + @classmethod + async def get_cache_monitor_cache_key_services(cls, request: Request, cache_name: str): + """ + 获取缓存键名列表信息service + + :param request: Request对象 + :param cache_name: 缓存名称 + :return: 缓存键名列表信息 + """ + cache_keys = await request.app.state.redis.keys(f'{cache_name}*') + cache_key_list = [key.split(':', 1)[1] for key in cache_keys if key.startswith(f'{cache_name}:')] + + return cache_key_list + + @classmethod + async def get_cache_monitor_cache_value_services(cls, request: Request, cache_name: str, cache_key: str): + """ + 获取缓存内容信息service + + :param request: Request对象 + :param cache_name: 缓存名称 + :param cache_key: 缓存键名 + :return: 缓存内容信息 + """ + cache_value = await request.app.state.redis.get(f'{cache_name}:{cache_key}') + + return CacheInfoModel(cacheKey=cache_key, cacheName=cache_name, cacheValue=cache_value, remark='') + + @classmethod + async def clear_cache_monitor_cache_name_services(cls, request: Request, cache_name: str): + """ + 清除缓存名称对应所有键值service + + :param request: Request对象 + :param cache_name: 缓存名称 + :return: 操作缓存响应信息 + """ + cache_keys = await request.app.state.redis.keys(f'{cache_name}*') + if cache_keys: + await request.app.state.redis.delete(*cache_keys) + + return CrudResponseModel(is_success=True, message=f'{cache_name}对应键值清除成功') + + @classmethod + async def clear_cache_monitor_cache_key_services(cls, request: Request, cache_key: str): + """ + 清除缓存名称对应所有键值service + + :param request: Request对象 + :param cache_key: 缓存键名 + :return: 操作缓存响应信息 + """ + cache_keys = await request.app.state.redis.keys(f'*{cache_key}') + if cache_keys: + await request.app.state.redis.delete(*cache_keys) + + return CrudResponseModel(is_success=True, message=f'{cache_key}清除成功') + + @classmethod + async def clear_cache_monitor_all_services(cls, request: Request): + """ + 清除所有缓存service + + :param request: Request对象 + :return: 操作缓存响应信息 + """ + cache_keys = await request.app.state.redis.keys() + if cache_keys: + await request.app.state.redis.delete(*cache_keys) + + await RedisUtil.init_sys_dict(request.app.state.redis) + await RedisUtil.init_sys_config(request.app.state.redis) + + return CrudResponseModel(is_success=True, message='所有缓存清除成功') diff --git a/ruoyi-fastapi-backend/module_admin/service/captcha_service.py b/ruoyi-fastapi-backend/module_admin/service/captcha_service.py new file mode 100644 index 0000000..1be8ffb --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/captcha_service.py @@ -0,0 +1,48 @@ +import base64 +import io +import os +import random +from PIL import Image, ImageDraw, ImageFont + + +class CaptchaService: + """ + 验证码模块服务层 + """ + + @classmethod + async def create_captcha_image_service(cls): + # 创建空白图像 + image = Image.new('RGB', (160, 60), color='#EAEAEA') + + # 创建绘图对象 + draw = ImageDraw.Draw(image) + + # 设置字体 + font = ImageFont.truetype(os.path.join(os.path.abspath(os.getcwd()), 'assets', 'font', 'Arial.ttf'), size=30) + + # 生成两个0-9之间的随机整数 + num1 = random.randint(0, 9) + num2 = random.randint(0, 9) + # 从运算符列表中随机选择一个 + operational_character_list = ['+', '-', '*'] + operational_character = random.choice(operational_character_list) + # 根据选择的运算符进行计算 + if operational_character == '+': + result = num1 + num2 + elif operational_character == '-': + result = num1 - num2 + else: + result = num1 * num2 + # 绘制文本 + text = f'{num1} {operational_character} {num2} = ?' + draw.text((25, 15), text, fill='blue', font=font) + + # 将图像数据保存到内存中 + buffer = io.BytesIO() + image.save(buffer, format='PNG') + + # 将图像数据转换为base64字符串 + base64_string = base64.b64encode(buffer.getvalue()).decode() + + return [base64_string, result] diff --git a/ruoyi-fastapi-backend/module_admin/service/common_service.py b/ruoyi-fastapi-backend/module_admin/service/common_service.py new file mode 100644 index 0000000..20eb868 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/common_service.py @@ -0,0 +1,91 @@ +import os +from datetime import datetime +from fastapi import BackgroundTasks, Request, UploadFile +from config.env import UploadConfig +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel, UploadResponseModel +from utils.upload_util import UploadUtil + + +class CommonService: + """ + 通用模块服务层 + """ + + @classmethod + async def upload_service(cls, request: Request, file: UploadFile): + """ + 通用上传service + + :param request: Request对象 + :param file: 上传文件对象 + :return: 上传结果 + """ + if not UploadUtil.check_file_extension(file): + raise ServiceException(message='文件类型不合法') + else: + relative_path = f'upload/{datetime.now().strftime("%Y")}/{datetime.now().strftime("%m")}/{datetime.now().strftime("%d")}' + dir_path = os.path.join(UploadConfig.UPLOAD_PATH, relative_path) + try: + os.makedirs(dir_path) + except FileExistsError: + pass + filename = f'{file.filename.rsplit(".", 1)[0]}_{datetime.now().strftime("%Y%m%d%H%M%S")}{UploadConfig.UPLOAD_MACHINE}{UploadUtil.generate_random_number()}.{file.filename.rsplit(".")[-1]}' + filepath = os.path.join(dir_path, filename) + with open(filepath, 'wb') as f: + # 流式写出大型文件,这里的10代表10MB + for chunk in iter(lambda: file.file.read(1024 * 1024 * 10), b''): + f.write(chunk) + + return CrudResponseModel( + is_success=True, + result=UploadResponseModel( + fileName=f'{UploadConfig.UPLOAD_PREFIX}/{relative_path}/{filename}', + newFileName=filename, + originalFilename=file.filename, + url=f'{request.base_url}{UploadConfig.UPLOAD_PREFIX[1:]}/{relative_path}/{filename}', + ), + message='上传成功', + ) + + @classmethod + async def download_services(cls, background_tasks: BackgroundTasks, file_name, delete: bool): + """ + 下载下载目录文件service + + :param background_tasks: 后台任务对象 + :param file_name: 下载的文件名称 + :param delete: 是否在下载完成后删除文件 + :return: 上传结果 + """ + filepath = os.path.join(UploadConfig.DOWNLOAD_PATH, file_name) + if '..' in file_name: + raise ServiceException(message='文件名称不合法') + elif not UploadUtil.check_file_exists(filepath): + raise ServiceException(message='文件不存在') + else: + if delete: + background_tasks.add_task(UploadUtil.delete_file, filepath) + return CrudResponseModel(is_success=True, result=UploadUtil.generate_file(filepath), message='下载成功') + + @classmethod + async def download_resource_services(cls, resource: str): + """ + 下载上传目录文件service + + :param resource: 下载的文件名称 + :return: 上传结果 + """ + filepath = os.path.join(resource.replace(UploadConfig.UPLOAD_PREFIX, UploadConfig.UPLOAD_PATH)) + filename = resource.rsplit('/', 1)[-1] + if ( + '..' in filename + or not UploadUtil.check_file_timestamp(filename) + or not UploadUtil.check_file_machine(filename) + or not UploadUtil.check_file_random_code(filename) + ): + raise ServiceException(message='文件名称不合法') + elif not UploadUtil.check_file_exists(filepath): + raise ServiceException(message='文件不存在') + else: + return CrudResponseModel(is_success=True, result=UploadUtil.generate_file(filepath), message='下载成功') diff --git a/ruoyi-fastapi-backend/module_admin/service/compreface_service.py b/ruoyi-fastapi-backend/module_admin/service/compreface_service.py new file mode 100644 index 0000000..7764e98 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/compreface_service.py @@ -0,0 +1,185 @@ +from typing import Dict, List, Optional + +from utils.compreface_util import ComprefaceUtil +from config.env import ComprefaceConfig + +POSE_MAX_ANGLE = getattr(ComprefaceConfig, "COMPREFACE_POSE_MAX_ANGLE", 10) +MIN_FACE_WIDTH = getattr(ComprefaceConfig, "COMPREFACE_MIN_FACE_WIDTH", 200) +MIN_FACE_HEIGHT = getattr(ComprefaceConfig, "COMPREFACE_MIN_FACE_HEIGHT", 200) +MIN_DETECTION_PROBABILITY = getattr(ComprefaceConfig, "COMPREFACE_MIN_DETECTION_PROBABILITY", 0.9) + +class ComprefaceService(ComprefaceUtil): + + """compreface服务类""" + + @classmethod + def _is_pose_front(cls, pose: Dict | None) -> bool: + if not pose: + return False + return all(abs(float(pose.get(axis, 0))) <= POSE_MAX_ANGLE for axis in ("pitch", "roll", "yaw")) + + @classmethod + def _is_face_close(cls, box: Dict | None) -> bool: + if not box: + return False + width = float(box.get("x_max", 0)) - float(box.get("x_min", 0)) + height = float(box.get("y_max", 0)) - float(box.get("y_min", 0)) + probability = float(box.get("probability", 0)) + return ( + width >= MIN_FACE_WIDTH + and height >= MIN_FACE_HEIGHT + and probability >= MIN_DETECTION_PROBABILITY + ) + + @classmethod + def _select_valid_detection(cls, detections: List[Dict]) -> Optional[Dict]: + for detection in detections: + pose = detection.get("pose") + box = detection.get("box") + if cls._is_pose_front(pose) and cls._is_face_close(box): + return detection + return None + + @staticmethod + def _pending_response() -> Dict[str, str]: + return { + "name": "未识别", + "role": "等待正脸" + } + + # 人脸检测 + @classmethod + async def face_detection_service(cls, image, options: dict = {}): + """ 人脸检测 + + Args: + image_path (str): 图片路径 + options (dict, optional): 检测参数. Defaults to {}. + + Returns: + [type]: [description] + """ + options={ + "limit": 0, + "det_prob_threshold": 0.8, + "prediction_count": 1, + # 可选参数 age,gender,landmarks,calculator + "face_plugins": "pose", + "status": "false", + } + result = await ComprefaceUtil.face_detection(image, options) + return result.get("result", []) + + # 人脸识别 + @classmethod + async def face_recognition_service(cls, image, options: dict = {}): + """ 人脸识别 + + Args: + image_path (str): 图片路径 + options (dict, optional): 参数. Defaults to {}. + + Returns: + [type]: [description] + """ + detections = await cls.face_detection_service(image) + detection = cls._select_valid_detection(detections) + if not detection: + return cls._pending_response() + + result = await ComprefaceUtil.face_recognition(image, options) + recognition_list = result.get('result') or [] + if not recognition_list: + return cls._pending_response() + + subjects = recognition_list[0].get('subjects') or [] + if not subjects: + return cls._pending_response() + + subject = subjects[0] + name = subject.get('subject', '') + similarity = subject.get('similarity', 0) + if similarity < ComprefaceConfig.COMPREFACE_SIMILARITY_THRESHOLD: + return { + "name": "未知", + "role": "陌生人" + } + else: + t = name.split("_") + return { + "name": t[0], + "role": t[1] if len(t) > 1 else "员工" + } + + # 添加图像到人脸库 + @classmethod + async def face_addition_service(cls, image, name): + result = await ComprefaceUtil.face_addition(image, name) + return result + + # 批量添加图像到人脸库 + @classmethod + async def face_addition_batch_service(cls, t): + result_list = [] + for image, name in t: + result = await ComprefaceUtil.face_addition(image, name) + result_list.append(result) + return result_list + + # 删除过期的访客人脸 + @classmethod + async def delete_expired_visitor_faces_service(cls, active_visitor_names: list): + """删除CompreFace中已过期的访客人脸数据 + + Args: + active_visitor_names (list): 当前有效的访客名字列表 + + Returns: + dict: 包含删除成功和失败的subject列表 + """ + try: + # 获取CompreFace中所有的subjects + all_subjects_result = await ComprefaceUtil.get_all_subjects() + all_subjects = all_subjects_result.get('subjects', []) + + deleted_subjects = [] + failed_subjects = [] + + # 遍历所有subjects,删除不在active_visitor_names中的访客 + for subject in all_subjects: + # 假设访客的subject格式为 "姓名_访客" 或 "姓名" + # 只删除访客类型的人脸,员工人脸不删除 + if subject not in active_visitor_names: + # 如果subject不在活跃访客列表中,且不是员工(员工一般包含"_员工"等标识) + # 这里假设访客没有特殊后缀,或者可以根据实际情况调整判断逻辑 + # 如果subject包含"_员工"等标识,则跳过 + if "_员工" in subject or "_职员" in subject or "_工作人员" in subject: + continue + + try: + # 删除该subject + await ComprefaceUtil.delete_subject(subject) + deleted_subjects.append(subject) + except Exception as e: + failed_subjects.append({"subject": subject, "error": str(e)}) + + return { + "deleted_count": len(deleted_subjects), + "deleted_subjects": deleted_subjects, + "failed_count": len(failed_subjects), + "failed_subjects": failed_subjects + } + except Exception as e: + return { + "error": f"删除过期访客失败: {str(e)}", + "deleted_count": 0, + "deleted_subjects": [], + "failed_count": 0, + "failed_subjects": [] + } + + + + + + diff --git a/ruoyi-fastapi-backend/module_admin/service/config_service.py b/ruoyi-fastapi-backend/module_admin/service/config_service.py new file mode 100644 index 0000000..312006d --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/config_service.py @@ -0,0 +1,231 @@ +from fastapi import Request +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from config.enums import RedisInitKeyConfig +from exceptions.exception import ServiceException +from module_admin.dao.config_dao import ConfigDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.config_vo import ConfigModel, ConfigPageQueryModel, DeleteConfigModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class ConfigService: + """ + 参数配置管理模块服务层 + """ + + @classmethod + async def get_config_list_services( + cls, query_db: AsyncSession, query_object: ConfigPageQueryModel, is_page: bool = False + ): + """ + 获取参数配置列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 参数配置列表信息对象 + """ + config_list_result = await ConfigDao.get_config_list(query_db, query_object, is_page) + + return config_list_result + + @classmethod + async def init_cache_sys_config_services(cls, query_db: AsyncSession, redis): + """ + 应用初始化:获取所有参数配置对应的键值对信息并缓存service + + :param query_db: orm对象 + :param redis: redis对象 + :return: + """ + # 获取以sys_config:开头的键列表 + keys = await redis.keys(f'{RedisInitKeyConfig.SYS_CONFIG.key}:*') + # 删除匹配的键 + if keys: + await redis.delete(*keys) + config_all = await ConfigDao.get_config_list(query_db, ConfigPageQueryModel(**dict()), is_page=False) + for config_obj in config_all: + await redis.set( + f"{RedisInitKeyConfig.SYS_CONFIG.key}:{config_obj.get('configKey')}", + config_obj.get('configValue'), + ) + + @classmethod + async def query_config_list_from_cache_services(cls, redis, config_key: str): + """ + 从缓存获取参数键名对应值service + + :param redis: redis对象 + :param config_key: 参数键名 + :return: 参数键名对应值 + """ + result = await redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:{config_key}') + + return result + + @classmethod + async def check_config_key_unique_services(cls, query_db: AsyncSession, page_object: ConfigModel): + """ + 校验参数键名是否唯一service + + :param query_db: orm对象 + :param page_object: 参数配置对象 + :return: 校验结果 + """ + config_id = -1 if page_object.config_id is None else page_object.config_id + config = await ConfigDao.get_config_detail_by_info(query_db, ConfigModel(configKey=page_object.config_key)) + if config and config.config_id != config_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_config_services(cls, request: Request, query_db: AsyncSession, page_object: ConfigModel): + """ + 新增参数配置信息service + + :param request: Request对象 + :param query_db: orm对象 + :param page_object: 新增参数配置对象 + :return: 新增参数配置校验结果 + """ + if not await cls.check_config_key_unique_services(query_db, page_object): + raise ServiceException(message=f'新增参数{page_object.config_name}失败,参数键名已存在') + else: + try: + await ConfigDao.add_config_dao(query_db, page_object) + await query_db.commit() + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_CONFIG.key}:{page_object.config_key}', page_object.config_value + ) + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_config_services(cls, request: Request, query_db: AsyncSession, page_object: ConfigModel): + """ + 编辑参数配置信息service + + :param request: Request对象 + :param query_db: orm对象 + :param page_object: 编辑参数配置对象 + :return: 编辑参数配置校验结果 + """ + edit_config = page_object.model_dump(exclude_unset=True) + config_info = await cls.config_detail_services(query_db, page_object.config_id) + if config_info.config_id: + if not await cls.check_config_key_unique_services(query_db, page_object): + raise ServiceException(message=f'修改参数{page_object.config_name}失败,参数键名已存在') + else: + try: + await ConfigDao.edit_config_dao(query_db, edit_config) + await query_db.commit() + if config_info.config_key != page_object.config_key: + await request.app.state.redis.delete( + f'{RedisInitKeyConfig.SYS_CONFIG.key}:{config_info.config_key}' + ) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_CONFIG.key}:{page_object.config_key}', page_object.config_value + ) + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='参数配置不存在') + + @classmethod + async def delete_config_services(cls, request: Request, query_db: AsyncSession, page_object: DeleteConfigModel): + """ + 删除参数配置信息service + + :param request: Request对象 + :param query_db: orm对象 + :param page_object: 删除参数配置对象 + :return: 删除参数配置校验结果 + """ + if page_object.config_ids: + config_id_list = page_object.config_ids.split(',') + try: + delete_config_key_list = [] + for config_id in config_id_list: + config_info = await cls.config_detail_services(query_db, int(config_id)) + if config_info.config_type == CommonConstant.YES: + raise ServiceException(message=f'内置参数{config_info.config_key}不能删除') + else: + await ConfigDao.delete_config_dao(query_db, ConfigModel(configId=int(config_id))) + delete_config_key_list.append(f'{RedisInitKeyConfig.SYS_CONFIG.key}:{config_info.config_key}') + await query_db.commit() + if delete_config_key_list: + await request.app.state.redis.delete(*delete_config_key_list) + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入参数配置id为空') + + @classmethod + async def config_detail_services(cls, query_db: AsyncSession, config_id: int): + """ + 获取参数配置详细信息service + + :param query_db: orm对象 + :param config_id: 参数配置id + :return: 参数配置id对应的信息 + """ + config = await ConfigDao.get_config_detail_by_id(query_db, config_id=config_id) + if config: + result = ConfigModel(**CamelCaseUtil.transform_result(config)) + else: + result = ConfigModel(**dict()) + + return result + + @staticmethod + async def export_config_list_services(config_list: List): + """ + 导出参数配置信息service + + :param config_list: 参数配置信息列表 + :return: 参数配置信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'configId': '参数主键', + 'configName': '参数名称', + 'configKey': '参数键名', + 'configValue': '参数键值', + 'configType': '系统内置', + 'createBy': '创建者', + 'createTime': '创建时间', + 'updateBy': '更新者', + 'updateTime': '更新时间', + 'remark': '备注', + } + + for item in config_list: + if item.get('configType') == 'Y': + item['configType'] = '是' + else: + item['configType'] = '否' + binary_data = ExcelUtil.export_list2excel(config_list, mapping_dict) + + return binary_data + + @classmethod + async def refresh_sys_config_services(cls, request: Request, query_db: AsyncSession): + """ + 刷新字典缓存信息service + + :param request: Request对象 + :param query_db: orm对象 + :return: 刷新字典缓存校验结果 + """ + await cls.init_cache_sys_config_services(query_db, request.app.state.redis) + + return CrudResponseModel(is_success=True, message='刷新成功') diff --git a/ruoyi-fastapi-backend/module_admin/service/dept_service.py b/ruoyi-fastapi-backend/module_admin/service/dept_service.py new file mode 100644 index 0000000..50594c8 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/dept_service.py @@ -0,0 +1,275 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from config.constant import CommonConstant +from exceptions.exception import ServiceException, ServiceWarning +from module_admin.dao.dept_dao import DeptDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.dept_vo import DeleteDeptModel, DeptModel +from utils.common_util import CamelCaseUtil + + +class DeptService: + """ + 部门管理模块服务层 + """ + + @classmethod + async def get_dept_tree_services(cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: str): + """ + 获取部门树信息service + + :param query_db: orm对象 + :param page_object: 查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 部门树信息对象 + """ + dept_list_result = await DeptDao.get_dept_list_for_tree(query_db, page_object, data_scope_sql) + dept_tree_result = cls.list_to_tree(dept_list_result) + + return dept_tree_result + + @classmethod + async def get_dept_for_edit_option_services( + cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: str + ): + """ + 获取部门编辑部门树信息service + + :param query_db: orm对象 + :param page_object: 查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 部门树信息对象 + """ + dept_list_result = await DeptDao.get_dept_info_for_edit_option(query_db, page_object, data_scope_sql) + + return CamelCaseUtil.transform_result(dept_list_result) + + @classmethod + async def get_dept_list_services(cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: str): + """ + 获取部门列表信息service + + :param query_db: orm对象 + :param page_object: 分页查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 部门列表信息对象 + """ + dept_list_result = await DeptDao.get_dept_list(query_db, page_object, data_scope_sql) + + return CamelCaseUtil.transform_result(dept_list_result) + + @classmethod + async def check_dept_data_scope_services(cls, query_db: AsyncSession, dept_id: int, data_scope_sql: str): + """ + 校验部门是否有数据权限service + + :param query_db: orm对象 + :param dept_id: 部门id + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 校验结果 + """ + depts = await DeptDao.get_dept_list(query_db, DeptModel(deptId=dept_id), data_scope_sql) + if depts: + return CrudResponseModel(is_success=True, message='校验通过') + else: + raise ServiceException(message='没有权限访问部门数据') + + @classmethod + async def check_dept_name_unique_services(cls, query_db: AsyncSession, page_object: DeptModel): + """ + 校验部门名称是否唯一service + + :param query_db: orm对象 + :param page_object: 部门对象 + :return: 校验结果 + """ + dept_id = -1 if page_object.dept_id is None else page_object.dept_id + dept = await DeptDao.get_dept_detail_by_info( + query_db, DeptModel(deptName=page_object.dept_name, parentId=page_object.parent_id) + ) + if dept and dept.dept_id != dept_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_dept_services(cls, query_db: AsyncSession, page_object: DeptModel): + """ + 新增部门信息service + + :param query_db: orm对象 + :param page_object: 新增部门对象 + :return: 新增部门校验结果 + """ + if not await cls.check_dept_name_unique_services(query_db, page_object): + raise ServiceException(message=f'新增部门{page_object.dept_name}失败,部门名称已存在') + parent_info = await DeptDao.get_dept_by_id(query_db, page_object.parent_id) + if parent_info.status != CommonConstant.DEPT_NORMAL: + raise ServiceException(message=f'部门{parent_info.dept_name}停用,不允许新增') + page_object.ancestors = f'{parent_info.ancestors},{page_object.parent_id}' + try: + await DeptDao.add_dept_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_dept_services(cls, query_db: AsyncSession, page_object: DeptModel): + """ + 编辑部门信息service + + :param query_db: orm对象 + :param page_object: 编辑部门对象 + :return: 编辑部门校验结果 + """ + if not await cls.check_dept_name_unique_services(query_db, page_object): + raise ServiceException(message=f'修改部门{page_object.dept_name}失败,部门名称已存在') + elif page_object.dept_id == page_object.parent_id: + raise ServiceException(message=f'修改部门{page_object.dept_name}失败,上级部门不能是自己') + elif ( + page_object.status == CommonConstant.DEPT_DISABLE + and (await DeptDao.count_normal_children_dept_dao(query_db, page_object.dept_id)) > 0 + ): + raise ServiceException(message=f'修改部门{page_object.dept_name}失败,该部门包含未停用的子部门') + new_parent_dept = await DeptDao.get_dept_by_id(query_db, page_object.parent_id) + old_dept = await DeptDao.get_dept_by_id(query_db, page_object.dept_id) + try: + if new_parent_dept and old_dept: + new_ancestors = f'{new_parent_dept.ancestors},{new_parent_dept.dept_id}' + old_ancestors = old_dept.ancestors + page_object.ancestors = new_ancestors + await cls.update_dept_children(query_db, page_object.dept_id, new_ancestors, old_ancestors) + edit_dept = page_object.model_dump(exclude_unset=True) + await DeptDao.edit_dept_dao(query_db, edit_dept) + if ( + page_object.status == CommonConstant.DEPT_NORMAL + and page_object.ancestors + and page_object.ancestors != 0 + ): + await cls.update_parent_dept_status_normal(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def delete_dept_services(cls, query_db: AsyncSession, page_object: DeleteDeptModel): + """ + 删除部门信息service + + :param query_db: orm对象 + :param page_object: 删除部门对象 + :return: 删除部门校验结果 + """ + if page_object.dept_ids: + dept_id_list = page_object.dept_ids.split(',') + try: + for dept_id in dept_id_list: + if (await DeptDao.count_children_dept_dao(query_db, int(dept_id))) > 0: + raise ServiceWarning(message='存在下级部门,不允许删除') + elif (await DeptDao.count_dept_user_dao(query_db, int(dept_id))) > 0: + raise ServiceWarning(message='部门存在用户,不允许删除') + + await DeptDao.delete_dept_dao(query_db, DeptModel(deptId=dept_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入部门id为空') + + @classmethod + async def dept_detail_services(cls, query_db: AsyncSession, dept_id: int): + """ + 获取部门详细信息service + + :param query_db: orm对象 + :param dept_id: 部门id + :return: 部门id对应的信息 + """ + dept = await DeptDao.get_dept_detail_by_id(query_db, dept_id=dept_id) + if dept: + result = DeptModel(**CamelCaseUtil.transform_result(dept)) + else: + result = DeptModel(**dict()) + + return result + + @classmethod + def list_to_tree(cls, permission_list: list) -> list: + """ + 工具方法:根据部门列表信息生成树形嵌套数据 + + :param permission_list: 部门列表信息 + :return: 部门树形嵌套数据 + """ + permission_list = [ + dict(id=item.dept_id, label=item.dept_name, parentId=item.parent_id) for item in permission_list + ] + # 转成id为key的字典 + mapping: dict = dict(zip([i['id'] for i in permission_list], permission_list)) + + # 树容器 + container: list = [] + + for d in permission_list: + # 如果找不到父级项,则是根节点 + parent: dict = mapping.get(d['parentId']) + if parent is None: + container.append(d) + else: + children: list = parent.get('children') + if not children: + children = [] + children.append(d) + parent.update({'children': children}) + + return container + + @classmethod + async def replace_first(cls, original_str: str, old_str: str, new_str: str): + """ + 工具方法:替换字符串 + + :param original_str: 需要替换的原始字符串 + :param old_str: 用于匹配的字符串 + :param new_str: 替换的字符串 + :return: 替换后的字符串 + """ + if original_str.startswith(old_str): + return original_str.replace(old_str, new_str, 1) + else: + return original_str + + @classmethod + async def update_parent_dept_status_normal(cls, query_db: AsyncSession, dept: DeptModel): + """ + 更新父部门状态为正常 + + :param query_db: orm对象 + :param dept: 部门对象 + :return: + """ + dept_id_list = dept.ancestors.split(',') + await DeptDao.update_dept_status_normal_dao(query_db, list(map(int, dept_id_list))) + + @classmethod + async def update_dept_children(cls, query_db: AsyncSession, dept_id: int, new_ancestors: str, old_ancestors: str): + """ + 更新子部门信息 + + :param query_db: orm对象 + :param dept_id: 部门id + :param new_ancestors: 新的祖先 + :param old_ancestors: 旧的祖先 + :return: + """ + children = await DeptDao.get_children_dept_dao(query_db, dept_id) + update_children = [] + for child in children: + child_ancestors = await cls.replace_first(child.ancestors, old_ancestors, new_ancestors) + update_children.append({'dept_id': child.dept_id, 'ancestors': child_ancestors}) + if children: + await DeptDao.update_dept_children_dao(query_db, update_children) diff --git a/ruoyi-fastapi-backend/module_admin/service/dict_service.py b/ruoyi-fastapi-backend/module_admin/service/dict_service.py new file mode 100644 index 0000000..0acfd73 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/dict_service.py @@ -0,0 +1,459 @@ +import json +from fastapi import Request +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from config.enums import RedisInitKeyConfig +from exceptions.exception import ServiceException +from module_admin.dao.dict_dao import DictDataDao, DictTypeDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.dict_vo import ( + DeleteDictDataModel, + DeleteDictTypeModel, + DictDataModel, + DictDataPageQueryModel, + DictTypeModel, + DictTypePageQueryModel, +) +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class DictTypeService: + """ + 字典类型管理模块服务层 + """ + + @classmethod + async def get_dict_type_list_services( + cls, query_db: AsyncSession, query_object: DictTypePageQueryModel, is_page: bool = False + ): + """ + 获取字典类型列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 字典类型列表信息对象 + """ + dict_type_list_result = await DictTypeDao.get_dict_type_list(query_db, query_object, is_page) + + return dict_type_list_result + + @classmethod + async def check_dict_type_unique_services(cls, query_db: AsyncSession, page_object: DictTypeModel): + """ + 校验字典类型称是否唯一service + + :param query_db: orm对象 + :param page_object: 字典类型对象 + :return: 校验结果 + """ + dict_id = -1 if page_object.dict_id is None else page_object.dict_id + dict_type = await DictTypeDao.get_dict_type_detail_by_info( + query_db, DictTypeModel(dictType=page_object.dict_type) + ) + if dict_type and dict_type.dict_id != dict_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_dict_type_services(cls, request: Request, query_db: AsyncSession, page_object: DictTypeModel): + """ + 新增字典类型信息service + + :param request: Request对象 + :param query_db: orm对象 + :param page_object: 新增岗位对象 + :return: 新增字典类型校验结果 + """ + if not await cls.check_dict_type_unique_services(query_db, page_object): + raise ServiceException(message=f'新增字典{page_object.dict_name}失败,字典类型已存在') + else: + try: + await DictTypeDao.add_dict_type_dao(query_db, page_object) + await query_db.commit() + await request.app.state.redis.set(f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', '') + result = dict(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + return CrudResponseModel(**result) + + @classmethod + async def edit_dict_type_services(cls, request: Request, query_db: AsyncSession, page_object: DictTypeModel): + """ + 编辑字典类型信息service + + :param request: Request对象 + :param query_db: orm对象 + :param page_object: 编辑字典类型对象 + :return: 编辑字典类型校验结果 + """ + edit_dict_type = page_object.model_dump(exclude_unset=True) + dict_type_info = await cls.dict_type_detail_services(query_db, page_object.dict_id) + if dict_type_info.dict_id: + if not await cls.check_dict_type_unique_services(query_db, page_object): + raise ServiceException(message=f'修改字典{page_object.dict_name}失败,字典类型已存在') + else: + try: + query_dict_data = DictDataPageQueryModel(dictType=dict_type_info.dict_type) + dict_data_list = await DictDataDao.get_dict_data_list(query_db, query_dict_data, is_page=False) + if dict_type_info.dict_type != page_object.dict_type: + for dict_data in dict_data_list: + edit_dict_data = DictDataModel( + dictCode=dict_data.get('dict_code'), + dictType=page_object.dict_type, + updateBy=page_object.update_by, + updateTime=page_object.update_time, + ).model_dump(exclude_unset=True) + await DictDataDao.edit_dict_data_dao(query_db, edit_dict_data) + await DictTypeDao.edit_dict_type_dao(query_db, edit_dict_type) + await query_db.commit() + if dict_type_info.dict_type != page_object.dict_type: + dict_data = [CamelCaseUtil.transform_result(row) for row in dict_data_list if row] + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', + json.dumps(dict_data, ensure_ascii=False, default=str), + ) + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='字典类型不存在') + + @classmethod + async def delete_dict_type_services( + cls, request: Request, query_db: AsyncSession, page_object: DeleteDictTypeModel + ): + """ + 删除字典类型信息service + + :param request: Request对象 + :param query_db: orm对象 + :param page_object: 删除字典类型对象 + :return: 删除字典类型校验结果 + """ + if page_object.dict_ids: + dict_id_list = page_object.dict_ids.split(',') + try: + delete_dict_type_list = [] + for dict_id in dict_id_list: + dict_type_into = await cls.dict_type_detail_services(query_db, int(dict_id)) + if (await DictDataDao.count_dict_data_dao(query_db, dict_type_into.dict_type)) > 0: + raise ServiceException(message=f'{dict_type_into.dict_name}已分配,不能删除') + await DictTypeDao.delete_dict_type_dao(query_db, DictTypeModel(dictId=int(dict_id))) + delete_dict_type_list.append(f'{RedisInitKeyConfig.SYS_DICT.key}:{dict_type_into.dict_type}') + await query_db.commit() + if delete_dict_type_list: + await request.app.state.redis.delete(*delete_dict_type_list) + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入字典类型id为空') + + @classmethod + async def dict_type_detail_services(cls, query_db: AsyncSession, dict_id: int): + """ + 获取字典类型详细信息service + + :param query_db: orm对象 + :param dict_id: 字典类型id + :return: 字典类型id对应的信息 + """ + dict_type = await DictTypeDao.get_dict_type_detail_by_id(query_db, dict_id=dict_id) + if dict_type: + result = DictTypeModel(**CamelCaseUtil.transform_result(dict_type)) + else: + result = DictTypeModel(**dict()) + + return result + + @staticmethod + async def export_dict_type_list_services(dict_type_list: List): + """ + 导出字典类型信息service + + :param dict_type_list: 字典信息列表 + :return: 字典信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'dictId': '字典编号', + 'dictName': '字典名称', + 'dictType': '字典类型', + 'status': '状态', + 'createBy': '创建者', + 'createTime': '创建时间', + 'updateBy': '更新者', + 'updateTime': '更新时间', + 'remark': '备注', + } + + for item in dict_type_list: + if item.get('status') == '0': + item['status'] = '正常' + else: + item['status'] = '停用' + binary_data = ExcelUtil.export_list2excel(dict_type_list, mapping_dict) + + return binary_data + + @classmethod + async def refresh_sys_dict_services(cls, request: Request, query_db: AsyncSession): + """ + 刷新字典缓存信息service + + :param request: Request对象 + :param query_db: orm对象 + :return: 刷新字典缓存校验结果 + """ + await DictDataService.init_cache_sys_dict_services(query_db, request.app.state.redis) + result = dict(is_success=True, message='刷新成功') + + return CrudResponseModel(**result) + + +class DictDataService: + """ + 字典数据管理模块服务层 + """ + + @classmethod + async def get_dict_data_list_services( + cls, query_db: AsyncSession, query_object: DictDataPageQueryModel, is_page: bool = False + ): + """ + 获取字典数据列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 字典数据列表信息对象 + """ + dict_data_list_result = await DictDataDao.get_dict_data_list(query_db, query_object, is_page) + + return dict_data_list_result + + @classmethod + async def query_dict_data_list_services(cls, query_db: AsyncSession, dict_type: str): + """ + 获取字典数据列表信息service + + :param query_db: orm对象 + :param dict_type: 字典类型 + :return: 字典数据列表信息对象 + """ + dict_data_list_result = await DictDataDao.query_dict_data_list(query_db, dict_type) + + return dict_data_list_result + + @classmethod + async def init_cache_sys_dict_services(cls, query_db: AsyncSession, redis): + """ + 应用初始化:获取所有字典类型对应的字典数据信息并缓存service + + :param query_db: orm对象 + :param redis: redis对象 + :return: + """ + # 获取以sys_dict:开头的键列表 + keys = await redis.keys(f'{RedisInitKeyConfig.SYS_DICT.key}:*') + # 删除匹配的键 + if keys: + await redis.delete(*keys) + dict_type_all = await DictTypeDao.get_all_dict_type(query_db) + for dict_type_obj in [item for item in dict_type_all if item.status == '0']: + dict_type = dict_type_obj.dict_type + dict_data_list = await DictDataDao.query_dict_data_list(query_db, dict_type) + dict_data = [CamelCaseUtil.transform_result(row) for row in dict_data_list if row] + await redis.set( + f'{RedisInitKeyConfig.SYS_DICT.key}:{dict_type}', + json.dumps(dict_data, ensure_ascii=False, default=str), + ) + + @classmethod + async def query_dict_data_list_from_cache_services(cls, redis, dict_type: str): + """ + 从缓存获取字典数据列表信息service + + :param redis: redis对象 + :param dict_type: 字典类型 + :return: 字典数据列表信息对象 + """ + result = [] + dict_data_list_result = await redis.get(f'{RedisInitKeyConfig.SYS_DICT.key}:{dict_type}') + if dict_data_list_result: + result = json.loads(dict_data_list_result) + + return CamelCaseUtil.transform_result(result) + + @classmethod + async def check_dict_data_unique_services(cls, query_db: AsyncSession, page_object: DictDataModel): + """ + 校验字典数据是否唯一service + + :param query_db: orm对象 + :param page_object: 字典数据对象 + :return: 校验结果 + """ + dict_code = -1 if page_object.dict_code is None else page_object.dict_code + dict_data = await DictDataDao.get_dict_data_detail_by_info(query_db, page_object) + if dict_data and dict_data.dict_code != dict_code: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_dict_data_services(cls, request: Request, query_db: AsyncSession, page_object: DictDataModel): + """ + 新增字典数据信息service + + :param request: Request对象 + :param query_db: orm对象 + :param page_object: 新增岗位对象 + :return: 新增字典数据校验结果 + """ + if not await cls.check_dict_data_unique_services(query_db, page_object): + raise ServiceException( + message=f'新增字典数据{page_object.dict_label}失败,{page_object.dict_type}下已存在该字典数据' + ) + else: + try: + await DictDataDao.add_dict_data_dao(query_db, page_object) + await query_db.commit() + dict_data_list = await cls.query_dict_data_list_services(query_db, page_object.dict_type) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', + json.dumps(CamelCaseUtil.transform_result(dict_data_list), ensure_ascii=False, default=str), + ) + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_dict_data_services(cls, request: Request, query_db: AsyncSession, page_object: DictDataModel): + """ + 编辑字典数据信息service + + :param request: Request对象 + :param query_db: orm对象 + :param page_object: 编辑字典数据对象 + :return: 编辑字典数据校验结果 + """ + edit_data_type = page_object.model_dump(exclude_unset=True) + dict_data_info = await cls.dict_data_detail_services(query_db, page_object.dict_code) + if dict_data_info.dict_code: + if not await cls.check_dict_data_unique_services(query_db, page_object): + raise ServiceException( + message=f'新增字典数据{page_object.dict_label}失败,{page_object.dict_type}下已存在该字典数据' + ) + else: + try: + await DictDataDao.edit_dict_data_dao(query_db, edit_data_type) + await query_db.commit() + dict_data_list = await cls.query_dict_data_list_services(query_db, page_object.dict_type) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', + json.dumps(CamelCaseUtil.transform_result(dict_data_list), ensure_ascii=False, default=str), + ) + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='字典数据不存在') + + @classmethod + async def delete_dict_data_services( + cls, request: Request, query_db: AsyncSession, page_object: DeleteDictDataModel + ): + """ + 删除字典数据信息service + + :param request: Request对象 + :param query_db: orm对象 + :param page_object: 删除字典数据对象 + :return: 删除字典数据校验结果 + """ + if page_object.dict_codes: + dict_code_list = page_object.dict_codes.split(',') + try: + delete_dict_type_list = [] + for dict_code in dict_code_list: + dict_data = await cls.dict_data_detail_services(query_db, int(dict_code)) + await DictDataDao.delete_dict_data_dao(query_db, DictDataModel(dictCode=dict_code)) + delete_dict_type_list.append(dict_data.dict_type) + await query_db.commit() + for dict_type in list(set(delete_dict_type_list)): + dict_data_list = await cls.query_dict_data_list_services(query_db, dict_type) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_DICT.key}:{dict_type}', + json.dumps(CamelCaseUtil.transform_result(dict_data_list), ensure_ascii=False, default=str), + ) + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入字典数据id为空') + + @classmethod + async def dict_data_detail_services(cls, query_db: AsyncSession, dict_code: int): + """ + 获取字典数据详细信息service + + :param query_db: orm对象 + :param dict_code: 字典数据id + :return: 字典数据id对应的信息 + """ + dict_data = await DictDataDao.get_dict_data_detail_by_id(query_db, dict_code=dict_code) + if dict_data: + result = DictDataModel(**CamelCaseUtil.transform_result(dict_data)) + else: + result = DictDataModel(**dict()) + + return result + + @staticmethod + async def export_dict_data_list_services(dict_data_list: List): + """ + 导出字典数据信息service + + :param dict_data_list: 字典数据信息列表 + :return: 字典数据信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'dictCode': '字典编码', + 'dictSort': '字典标签', + 'dictLabel': '字典键值', + 'dictValue': '字典排序', + 'dictType': '字典类型', + 'cssClass': '样式属性', + 'listClass': '表格回显样式', + 'isDefault': '是否默认', + 'status': '状态', + 'createBy': '创建者', + 'createTime': '创建时间', + 'updateBy': '更新者', + 'updateTime': '更新时间', + 'remark': '备注', + } + + for item in dict_data_list: + if item.get('status') == '0': + item['status'] = '正常' + else: + item['status'] = '停用' + if item.get('isDefault') == 'Y': + item['isDefault'] = '是' + else: + item['isDefault'] = '否' + binary_data = ExcelUtil.export_list2excel(dict_data_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/door_service.py b/ruoyi-fastapi-backend/module_admin/service/door_service.py new file mode 100644 index 0000000..70537ba --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/door_service.py @@ -0,0 +1,178 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.door_dao import DoorDao +from module_admin.dao.info_dao import InfoDao +from module_admin.entity.vo.door_vo import DeleteDoorModel, DoorModel, DoorPageQueryModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil +from utils.haikang_util import HaikangUtil + + +class DoorService: + """ + 门禁设备模块服务层 + """ + + @classmethod + async def get_video_uri_service(cls, robot_id: int , query_db: AsyncSession): + """获取机器人视频流 + """ + video_uri = await InfoDao.get_video_uri(robot_id, query_db) + return video_uri + + + @classmethod + async def get_door_status_service(cls, query_db: AsyncSession): + """获取门禁状态 + + Args: + query_db (AsyncSession): orm对象 + Returns: + _type_: _description_ + """ + + door_indexcode_name = await DoorDao.get_door_door_status(query_db) + + t_d = dict() + index_code_list = list() + for t in door_indexcode_name: + t_d[t["indexCode"]] = t["name"] + index_code_list.append(t["indexCode"]) + + door_online_status_result = await HaikangUtil.door_online_status(index_code_list) + + if door_online_status_result[0]: + back = list() + for door_online_status in door_online_status_result[1]['list']: + back.append({ + "name": t_d.get(door_online_status['indexCode'], None), + "online": door_online_status['online'] + }) + + return True, back + return door_online_status_result + + @classmethod + async def get_door_list_services( + cls, query_db: AsyncSession, query_object: DoorPageQueryModel, is_page: bool = False + ): + """ + 获取门禁设备列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 门禁设备列表信息对象 + """ + door_list_result = await DoorDao.get_door_list(query_db, query_object, is_page) + + # 获取门禁在线状态 + + return door_list_result + + + @classmethod + async def add_door_services(cls, query_db: AsyncSession, page_object: DoorModel): + """ + 新增门禁设备信息service + + :param query_db: orm对象 + :param page_object: 新增门禁设备对象 + :return: 新增门禁设备校验结果 + """ + try: + await DoorDao.add_door_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_door_services(cls, query_db: AsyncSession, page_object: DoorModel): + """ + 编辑门禁设备信息service + + :param query_db: orm对象 + :param page_object: 编辑门禁设备对象 + :return: 编辑门禁设备校验结果 + """ + edit_door = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'create_by', }) + door_info = await cls.door_detail_services(query_db, page_object.id) + if door_info.id: + try: + await DoorDao.edit_door_dao(query_db, edit_door) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='门禁设备不存在') + + @classmethod + async def delete_door_services(cls, query_db: AsyncSession, page_object: DeleteDoorModel): + """ + 删除门禁设备信息service + + :param query_db: orm对象 + :param page_object: 删除门禁设备对象 + :return: 删除门禁设备校验结果 + """ + if page_object.ids: + id_list = page_object.ids.split(',') + try: + for id in id_list: + await DoorDao.delete_door_dao(query_db, DoorModel(id=id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入主键自增为空') + + @classmethod + async def door_detail_services(cls, query_db: AsyncSession, id: int): + """ + 获取门禁设备详细信息service + + :param query_db: orm对象 + :param id: 主键自增 + :return: 主键自增对应的信息 + """ + door = await DoorDao.get_door_detail_by_id(query_db, id=id) + if door: + result = DoorModel(**CamelCaseUtil.transform_result(door)) + else: + result = DoorModel(**dict()) + + return result + + @staticmethod + async def export_door_list_services(door_list: List): + """ + 导出门禁设备信息service + + :param door_list: 门禁设备信息列表 + :return: 门禁设备信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'id': '主键自增', + 'indexCode': '设备索引编码', + 'name': '门禁名称', + 'installLocation': '位置名称', + 'status': '设备状态, 0 离线, 1 正常在线', + 'permission': '权限, 0 禁用, 1 正常调用', + 'createTime': '创建时间', + 'createBy': '创建者', + 'updateTime': '更新时间', + 'updateBy': '更新者', + } + binary_data = ExcelUtil.export_list2excel(door_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/explanation_content_service.py b/ruoyi-fastapi-backend/module_admin/service/explanation_content_service.py new file mode 100644 index 0000000..70c250d --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/explanation_content_service.py @@ -0,0 +1,136 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.explanation_content_dao import Explanation_contentDao +from module_admin.entity.vo.explanation_content_vo import DeleteExplanation_contentModel, Explanation_contentModel, Explanation_contentPageQueryModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class Explanation_contentService: + """ + 讲解内容模块服务层 + """ + + @classmethod + async def get_explanation_content_list_services( + cls, query_db: AsyncSession, query_object: Explanation_contentPageQueryModel, is_page: bool = False + ): + """ + 获取讲解内容列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 讲解内容列表信息对象 + """ + explanation_content_list_result = await Explanation_contentDao.get_explanation_content_list(query_db, query_object, is_page) + + return explanation_content_list_result + + + @classmethod + async def add_explanation_content_services(cls, query_db: AsyncSession, page_object: Explanation_contentModel): + """ + 新增讲解内容信息service + + :param query_db: orm对象 + :param page_object: 新增讲解内容对象 + :return: 新增讲解内容校验结果 + """ + try: + await Explanation_contentDao.add_explanation_content_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_explanation_content_services(cls, query_db: AsyncSession, page_object: Explanation_contentModel): + """ + 编辑讲解内容信息service + + :param query_db: orm对象 + :param page_object: 编辑讲解内容对象 + :return: 编辑讲解内容校验结果 + """ + edit_explanation_content = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'create_by', }) + explanation_content_info = await cls.explanation_content_detail_services(query_db, page_object.explanation_content_id) + if explanation_content_info.explanation_content_id: + try: + await Explanation_contentDao.edit_explanation_content_dao(query_db, edit_explanation_content) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='讲解内容不存在') + + @classmethod + async def delete_explanation_content_services(cls, query_db: AsyncSession, page_object: DeleteExplanation_contentModel): + """ + 删除讲解内容信息service + + :param query_db: orm对象 + :param page_object: 删除讲解内容对象 + :return: 删除讲解内容校验结果 + """ + if page_object.explanation_content_ids: + explanation_content_id_list = page_object.explanation_content_ids.split(',') + try: + for explanation_content_id in explanation_content_id_list: + await Explanation_contentDao.delete_explanation_content_dao(query_db, Explanation_contentModel(explanationContentId=explanation_content_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入主键ID为空') + + @classmethod + async def explanation_content_detail_services(cls, query_db: AsyncSession, explanation_content_id: int): + """ + 获取讲解内容详细信息service + + :param query_db: orm对象 + :param explanation_content_id: 主键ID + :return: 主键ID对应的信息 + """ + explanation_content = await Explanation_contentDao.get_explanation_content_detail_by_id(query_db, explanation_content_id=explanation_content_id) + if explanation_content: + result = Explanation_contentModel(**CamelCaseUtil.transform_result(explanation_content)) + else: + result = Explanation_contentModel(**dict()) + + return result + + @staticmethod + async def export_explanation_content_list_services(explanation_content_list: List): + """ + 导出讲解内容信息service + + :param explanation_content_list: 讲解内容信息列表 + :return: 讲解内容信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'explanationContentId': '主键ID', + 'title': '标题', + 'content': '讲解内容', + 'type': '类型(0科技类)', + 'spendTime': '预计时长(分钟)', + 'language': '语言(0中文,1英文)', + 'status': '状态(0未开始,1已就绪,2播放中,3已完成)', + 'createTime': '创建时间', + 'createBy': '创建者', + 'updateTime': '修改时间', + 'updateBy': '修改者', + } + binary_data = ExcelUtil.export_list2excel(explanation_content_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/explanation_content_type_service.py b/ruoyi-fastapi-backend/module_admin/service/explanation_content_type_service.py new file mode 100644 index 0000000..ff7a1e1 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/explanation_content_type_service.py @@ -0,0 +1,132 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.explanation_content_type_dao import Explanation_content_typeDao +from module_admin.entity.vo.explanation_content_type_vo import DeleteExplanation_content_typeModel, Explanation_content_typeModel, Explanation_content_typePageQueryModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class Explanation_content_typeService: + """ + 讲解内容类型模块服务层 + """ + + @classmethod + async def get_explanation_content_type_list_services( + cls, query_db: AsyncSession, query_object: Explanation_content_typePageQueryModel, is_page: bool = False + ): + """ + 获取讲解内容类型列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 讲解内容类型列表信息对象 + """ + explanation_content_type_list_result = await Explanation_content_typeDao.get_explanation_content_type_list(query_db, query_object, is_page) + + return explanation_content_type_list_result + + + @classmethod + async def add_explanation_content_type_services(cls, query_db: AsyncSession, page_object: Explanation_content_typeModel): + """ + 新增讲解内容类型信息service + + :param query_db: orm对象 + :param page_object: 新增讲解内容类型对象 + :return: 新增讲解内容类型校验结果 + """ + try: + await Explanation_content_typeDao.add_explanation_content_type_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_explanation_content_type_services(cls, query_db: AsyncSession, page_object: Explanation_content_typeModel): + """ + 编辑讲解内容类型信息service + + :param query_db: orm对象 + :param page_object: 编辑讲解内容类型对象 + :return: 编辑讲解内容类型校验结果 + """ + edit_explanation_content_type = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'create_by', }) + explanation_content_type_info = await cls.explanation_content_type_detail_services(query_db, page_object.content_type_id) + if explanation_content_type_info.content_type_id: + try: + await Explanation_content_typeDao.edit_explanation_content_type_dao(query_db, edit_explanation_content_type) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='讲解内容类型不存在') + + @classmethod + async def delete_explanation_content_type_services(cls, query_db: AsyncSession, page_object: DeleteExplanation_content_typeModel): + """ + 删除讲解内容类型信息service + + :param query_db: orm对象 + :param page_object: 删除讲解内容类型对象 + :return: 删除讲解内容类型校验结果 + """ + if page_object.content_type_ids: + content_type_id_list = page_object.content_type_ids.split(',') + try: + for content_type_id in content_type_id_list: + await Explanation_content_typeDao.delete_explanation_content_type_dao(query_db, Explanation_content_typeModel(contentTypeId=content_type_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入主键ID为空') + + @classmethod + async def explanation_content_type_detail_services(cls, query_db: AsyncSession, content_type_id: int): + """ + 获取讲解内容类型详细信息service + + :param query_db: orm对象 + :param content_type_id: 主键ID + :return: 主键ID对应的信息 + """ + explanation_content_type = await Explanation_content_typeDao.get_explanation_content_type_detail_by_id(query_db, content_type_id=content_type_id) + if explanation_content_type: + result = Explanation_content_typeModel(**CamelCaseUtil.transform_result(explanation_content_type)) + else: + result = Explanation_content_typeModel(**dict()) + + return result + + @staticmethod + async def export_explanation_content_type_list_services(explanation_content_type_list: List): + """ + 导出讲解内容类型信息service + + :param explanation_content_type_list: 讲解内容类型信息列表 + :return: 讲解内容类型信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'contentTypeId': '主键ID', + 'name': '类型名称', + 'typeValue': '类型值', + 'createTime': '创建时间', + 'createBy': '创建者', + 'updateTime': '修改时间', + 'updateBy': '修改者', + } + binary_data = ExcelUtil.export_list2excel(explanation_content_type_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/explanation_style_robot_pair_service.py b/ruoyi-fastapi-backend/module_admin/service/explanation_style_robot_pair_service.py new file mode 100644 index 0000000..aefc13e --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/explanation_style_robot_pair_service.py @@ -0,0 +1,159 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.explanation_style_robot_pair_dao import Explanation_style_robot_pairDao +from module_admin.entity.vo.explanation_style_robot_pair_vo import DeleteExplanation_style_robot_pairModel, \ + Explanation_style_robot_pairModel, Explanation_style_robot_pairPageQueryModel, SwitchExplanationStyleModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class Explanation_style_robot_pairService: + """ + 讲解风格--机器人配对模块服务层 + """ + + @classmethod + async def switch_explanation_style_services(cls, query_db: AsyncSession, switch_explanation_style: SwitchExplanationStyleModel): + try: + # 现将所有风格都置为0 + result1 = await Explanation_style_robot_pairDao.switch_explanation_style(query_db, switch_explanation_style.robot_id, None, switch_explanation_style.robot_role_id ,"0", switch_explanation_style.update_by) + # 再将选择的风格置为1 + result2 = await Explanation_style_robot_pairDao.switch_explanation_style(query_db, None, switch_explanation_style.pairing_id,None ,"1", switch_explanation_style.update_by) + count = result2.rowcount + + await query_db.commit() + if count == 0: + await query_db.rollback() + return CrudResponseModel(is_success=False, message='修改失败') + + return CrudResponseModel(is_success=True, message='修改成功') + except Exception as e: + await query_db.rollback() + raise e + + + @classmethod + async def get_style_robot_pair_list_services(cls, query_db: AsyncSession, robot_id: int): + return await Explanation_style_robot_pairDao.get_style_robot_pair_list(query_db, robot_id) + + @classmethod + async def get_explanation_style_robot_pair_list_services( + cls, query_db: AsyncSession, query_object: Explanation_style_robot_pairPageQueryModel, is_page: bool = False + ): + """ + 获取讲解风格--机器人配对列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 讲解风格--机器人配对列表信息对象 + """ + explanation_style_robot_pair_list_result = await Explanation_style_robot_pairDao.get_explanation_style_robot_pair_list(query_db, query_object, is_page) + + return explanation_style_robot_pair_list_result + + + @classmethod + async def add_explanation_style_robot_pair_services(cls, query_db: AsyncSession, page_object: Explanation_style_robot_pairModel): + """ + 新增讲解风格--机器人配对信息service + + :param query_db: orm对象 + :param page_object: 新增讲解风格--机器人配对对象 + :return: 新增讲解风格--机器人配对校验结果 + """ + try: + await Explanation_style_robot_pairDao.add_explanation_style_robot_pair_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_explanation_style_robot_pair_services(cls, query_db: AsyncSession, page_object: Explanation_style_robot_pairModel): + """ + 编辑讲解风格--机器人配对信息service + + :param query_db: orm对象 + :param page_object: 编辑讲解风格--机器人配对对象 + :return: 编辑讲解风格--机器人配对校验结果 + """ + edit_explanation_style_robot_pair = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'create_by', }) + explanation_style_robot_pair_info = await cls.explanation_style_robot_pair_detail_services(query_db, page_object.pairing_id) + if explanation_style_robot_pair_info.pairing_id: + try: + await Explanation_style_robot_pairDao.edit_explanation_style_robot_pair_dao(query_db, edit_explanation_style_robot_pair) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='讲解风格--机器人配对不存在') + + @classmethod + async def delete_explanation_style_robot_pair_services(cls, query_db: AsyncSession, page_object: DeleteExplanation_style_robot_pairModel): + """ + 删除讲解风格--机器人配对信息service + + :param query_db: orm对象 + :param page_object: 删除讲解风格--机器人配对对象 + :return: 删除讲解风格--机器人配对校验结果 + """ + if page_object.pairing_ids: + pairing_id_list = page_object.pairing_ids.split(',') + try: + for pairing_id in pairing_id_list: + await Explanation_style_robot_pairDao.delete_explanation_style_robot_pair_dao(query_db, Explanation_style_robot_pairModel(pairingId=pairing_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入主键ID为空') + + @classmethod + async def explanation_style_robot_pair_detail_services(cls, query_db: AsyncSession, pairing_id: int): + """ + 获取讲解风格--机器人配对详细信息service + + :param query_db: orm对象 + :param pairing_id: 主键ID + :return: 主键ID对应的信息 + """ + explanation_style_robot_pair = await Explanation_style_robot_pairDao.get_explanation_style_robot_pair_detail_by_id(query_db, pairing_id=pairing_id) + if explanation_style_robot_pair: + result = Explanation_style_robot_pairModel(**CamelCaseUtil.transform_result(explanation_style_robot_pair)) + else: + result = Explanation_style_robot_pairModel(**dict()) + + return result + + @staticmethod + async def export_explanation_style_robot_pair_list_services(explanation_style_robot_pair_list: List): + """ + 导出讲解风格--机器人配对信息service + + :param explanation_style_robot_pair_list: 讲解风格--机器人配对信息列表 + :return: 讲解风格--机器人配对信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'pairingId': '主键ID', + 'robotId': '机器人ID', + 'explanationStyleId': '讲解风格ID', + 'prompt': '提示词', + 'status': '状态(0停用,1使用)', + 'createTime': '创建时间', + 'createBy': '创建者', + 'updateTime': '修改时间', + 'updateBy': '修改者', + } + binary_data = ExcelUtil.export_list2excel(explanation_style_robot_pair_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/explanation_style_service.py b/ruoyi-fastapi-backend/module_admin/service/explanation_style_service.py new file mode 100644 index 0000000..631c29b --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/explanation_style_service.py @@ -0,0 +1,132 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.explanation_style_dao import Explanation_styleDao +from module_admin.entity.vo.explanation_style_vo import DeleteExplanation_styleModel, Explanation_styleModel, Explanation_stylePageQueryModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class Explanation_styleService: + """ + 讲解风格模块服务层 + """ + + @classmethod + async def get_explanation_style_list_services( + cls, query_db: AsyncSession, query_object: Explanation_stylePageQueryModel, is_page: bool = False + ): + """ + 获取讲解风格列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 讲解风格列表信息对象 + """ + explanation_style_list_result = await Explanation_styleDao.get_explanation_style_list(query_db, query_object, is_page) + + return explanation_style_list_result + + + @classmethod + async def add_explanation_style_services(cls, query_db: AsyncSession, page_object: Explanation_styleModel): + """ + 新增讲解风格信息service + + :param query_db: orm对象 + :param page_object: 新增讲解风格对象 + :return: 新增讲解风格校验结果 + """ + try: + await Explanation_styleDao.add_explanation_style_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_explanation_style_services(cls, query_db: AsyncSession, page_object: Explanation_styleModel): + """ + 编辑讲解风格信息service + + :param query_db: orm对象 + :param page_object: 编辑讲解风格对象 + :return: 编辑讲解风格校验结果 + """ + edit_explanation_style = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'create_by', }) + explanation_style_info = await cls.explanation_style_detail_services(query_db, page_object.explanation_style_id) + if explanation_style_info.explanation_style_id: + try: + await Explanation_styleDao.edit_explanation_style_dao(query_db, edit_explanation_style) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='讲解风格不存在') + + @classmethod + async def delete_explanation_style_services(cls, query_db: AsyncSession, page_object: DeleteExplanation_styleModel): + """ + 删除讲解风格信息service + + :param query_db: orm对象 + :param page_object: 删除讲解风格对象 + :return: 删除讲解风格校验结果 + """ + if page_object.explanation_style_ids: + explanation_style_id_list = page_object.explanation_style_ids.split(',') + try: + for explanation_style_id in explanation_style_id_list: + await Explanation_styleDao.delete_explanation_style_dao(query_db, Explanation_styleModel(explanationStyleId=explanation_style_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入主键ID为空') + + @classmethod + async def explanation_style_detail_services(cls, query_db: AsyncSession, explanation_style_id: int): + """ + 获取讲解风格详细信息service + + :param query_db: orm对象 + :param explanation_style_id: 主键ID + :return: 主键ID对应的信息 + """ + explanation_style = await Explanation_styleDao.get_explanation_style_detail_by_id(query_db, explanation_style_id=explanation_style_id) + if explanation_style: + result = Explanation_styleModel(**CamelCaseUtil.transform_result(explanation_style)) + else: + result = Explanation_styleModel(**dict()) + + return result + + @staticmethod + async def export_explanation_style_list_services(explanation_style_list: List): + """ + 导出讲解风格信息service + + :param explanation_style_list: 讲解风格信息列表 + :return: 讲解风格信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'explanationStyleId': '主键ID', + 'name': '风格名称', + 'detail': '风格简介', + 'createTime': '创建时间', + 'createBy': '创建者', + 'updateTime': '修改时间', + 'updateBy': '修改者', + } + binary_data = ExcelUtil.export_list2excel(explanation_style_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/haikang_service.py b/ruoyi-fastapi-backend/module_admin/service/haikang_service.py new file mode 100644 index 0000000..09cf3cb --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/haikang_service.py @@ -0,0 +1,313 @@ +from utils.haikang_util import HaikangUtil +from module_admin.entity.vo.haikang_vo import VisitorReservationQueryModel +from config.env import HaiKangConfig +import json +import base64 +import asyncio +import os + +class HaiKangService: + """ + 海康服务 + """ + # 获取门禁列表 + @classmethod + async def get_door_list_service(cls, pageNo: int = 1, pageSize: int = 10): + result = await HaikangUtil.get_door_list_v2(pageNo, pageSize) + print(result) + with open("get_door_list_service.json", "w", encoding="utf-8") as f: + f.write(json.dumps(result[1])) + + # 查询门禁状态 + @classmethod + async def get_door_status_service(cls, door_index_codes): + result = await HaikangUtil.get_door_status(door_index_codes) + print(result) + with open("get_door_status_service.json", "w", encoding="utf-8") as f: + f.write(json.dumps(result[1])) + + # 门禁控制 + @classmethod + async def door_do_control_service(cls, door_index_codes, control_type): + try: + result = await cls.door_do_control(door_index_codes, control_type) + return result + except Exception as e: + return [False,"请求失败"] + + + # print(result) + # with open("door_do_control_service.json", "w", encoding="utf-8") as f: + # f.write(json.dumps(result[1])) + + # 查询门禁点事件 + @classmethod + async def query_door_events_service(cls, door_index_code,pageNo, pageSize, startTime, endTime): + result = await HaikangUtil.query_door_events_v2(door_index_code, pageNo=pageNo, pageSize=pageSize ,startTime=startTime, endTime=endTime) + print(result) + with open("query_door_events_service.json", "w", encoding="utf-8") as f: + f.write(json.dumps(result[1])) + + # 查看门禁点在线状态 + @classmethod + async def door_online_status_service(cls, door_index_codes): + result = await HaikangUtil.door_online_status(door_index_codes) + print(result) + with open("door_online_status_service.json", "w", encoding="utf-8") as f: + f.write(json.dumps(result[1])) + + # 按条件查询人脸分组, 很重要 + @classmethod + async def get_face_group_service(cls): + result = await HaikangUtil.get_face_group() + print(result) + with open("get_face_group_service.json", "w", encoding="utf-8") as f: + f.write(json.dumps(result[1])) + + # 人脸分组1vN搜索 + @classmethod + async def face_group_1vN_search_service(cls, image_path,faceGroupIndexCodes:list, pageNo=1, pageSize=5, searchNum:int=5 , minSimilarity=50): + + with open(image_path, 'rb') as f: + image_data = f.read() + + encoded_image = base64.b64encode(image_data).decode('utf-8') + + # with open("image_base64.txt", "w", encoding="utf-8") as f: + # f.write(encoded_image) + + result = await HaikangUtil.face_group_1vN_search( + facePicBinaryData=encoded_image, + pageNo=pageNo, + pageSize=pageSize, + searchNum=searchNum, + minSimilarity=minSimilarity, + faceGroupIndexCodes=faceGroupIndexCodes + ) + print(result) + with open("face_group_1vN_search_service.json", "w", encoding="utf-8") as f: + f.write(json.dumps(result[1])) + + # 人脸评分 + @classmethod + async def face_picture_check(cls, image_path): + + with open(image_path, 'rb') as f: + image_data = f.read() + + encoded_image = base64.b64encode(image_data).decode('utf-8') + + # print(encoded_image) + result = await HaikangUtil.face_picture_check( + facePicBinaryData=encoded_image + ) + print(result) + with open("face_picture_check.json", "w", encoding="utf-8") as f: + f.write(json.dumps(result[1])) + + # 查询访客预约记录 + @classmethod + async def query_visitor_record(cls): + result = await HaikangUtil.query_visitor_record(pageNo=1, pageSize=10, visitorStatus=1) + print(result) + with open("query_visitor_record.json", "w", encoding="utf-8") as f: + f.write(json.dumps(result[1])) + + + # 查询访客预约记录图片 + @classmethod + async def query_visitor_record_pictures_service(cls, svrIndexCode, picUri): + await HaikangUtil.query_visitor_record_pictures(svrIndexCode, picUri) + + + # 获取所有访客图片 + @classmethod + async def get_all_visitor_pictures(cls, pageNo=1, pageSize=10, visitorStatus=1): + + + result = await HaikangUtil.query_visitor_record(pageNo=pageNo, pageSize=pageSize, visitorStatus=visitorStatus) + + visitor_list = list() + if result[0]: + total = result[1]['total'] + visitor_list += result[1]['list'] + while pageNo*pageSize < total: + pageNo += 1 + result = await HaikangUtil.query_visitor_record(pageNo=pageNo+1, pageSize=pageSize, visitorStatus=visitorStatus) + if result[0]: + visitor_list += result[1]['list'] + else: + break + # else: + # return [] + # + # print(visitor_list) + + os.makedirs(os.path.dirname(HaiKangConfig.HAIKANG_VISITOR_PICTURES_SAVE_PATH), exist_ok=True) + + with open(HaiKangConfig.HAIKANG_VISITOR_RECORD_TXT, "r", encoding="utf-8") as f: + visitor_ids = [line.rstrip('\n') for line in f] + # print(visitor_ids) + + + visitor_save_path_name_list = list() + visitor_ids_add = list() + for visitor in visitor_list: + if visitor["visitorId"] not in visitor_ids: + visitor_ids_add.append(visitor["visitorId"]) + # 获取图片 + try: + result = await HaikangUtil.query_visitor_record_pictures(visitor["svrIndexCode"], visitor["picUri"], f"{HaiKangConfig.HAIKANG_VISITOR_PICTURES_SAVE_PATH}{visitor['visitorName']}") + await asyncio.sleep(1) + visitor_save_path_name_list.append(result) + except Exception as e: + print(e, visitor["visitorId"]) + + with open(HaiKangConfig.HAIKANG_VISITOR_RECORD_TXT, "a", encoding="utf-8") as f: + for visitor_id in visitor_ids_add: + f.write(visitor_id + "\n") + + return visitor_save_path_name_list + + + + # 获取所有员工图片 + @classmethod + async def get_all_employee_pictures(cls, pageNo=1, pageSize=500): + + result = await HaikangUtil.get_person_list(pageNo=pageNo, pageSize=pageSize) + + employee_list = list() + if result[0]: + total = result[1]['total'] + employee_list += result[1]['list'] + while pageNo * pageSize < total: + pageNo += 1 + result = await HaikangUtil.get_person_list(pageNo=pageNo + 1, pageSize=pageSize, ) + if result[0]: + employee_list += result[1]['list'] + else: + break + + + with open("./employee/personIds.txt", "r", encoding="utf-8") as f: + person_ids = [line.rstrip('\n') for line in f] + # print(visitor_ids) + person_ids_add = list() + for employee in employee_list: + # print(employee) + if employee["personId"] not in person_ids: + person_ids_add.append(employee["personId"]) + # 获取图片 + try: + await HaikangUtil.get_person_picture(employee.get("personPhoto")[0]["serverIndexCode"], employee.get("personPhoto")[0]["picUri"], f"./employee/face_images/{employee['personName']}") + await asyncio.sleep(0.5) + except Exception as e: + print(f"error: {e}, personId: {employee['personId']}") + + + with open("./employee/personIds.txt", "a", encoding="utf-8") as f: + for person_id in person_ids_add: + f.write(person_id + "\n") + + + # 添加单个人脸分组 + @classmethod + async def face_group_addition_service(cls, name, description): + result = await HaikangUtil.face_group_addition(name, description) + print(result) + with open("face_group_addition_service.json", "a", encoding="utf-8") as f: + f.write(json.dumps(result[1])) + + # 向分组中添加单个人脸 + @classmethod + async def face_single_addition_service(cls, faceGroupIndexCode, image_path, name): + + with open(image_path, 'rb') as f: + image_data = f.read() + + encoded_image = base64.b64encode(image_data).decode('utf-8') + + faceInfo = { + "name": name, + } + facePic = { + "facePicBinaryData": encoded_image, + "facePicUrl":"" + } + + + result = await HaikangUtil.face_single_addition(faceGroupIndexCode, faceInfo, facePic) + print(result) + with open("face_single_addition_service.json", "a", encoding="utf-8") as f: + f.write(json.dumps(result[1])) + + # 批量删除人脸 + @classmethod + async def face_delete_service(cls, faceGroupIndexCode: str, indexCodes: list): + result = await HaikangUtil.face_delete(faceGroupIndexCode, indexCodes) + print(result) + with open("face_delete_service.json", "a", encoding="utf-8") as f: + f.write(json.dumps(result[1])) + + + + + # # 查询访客列表 + @classmethod + async def get_visitor_list_service(cls, visitor_query: VisitorReservationQueryModel): + """查询访客列表 + """ + + result = await HaikangUtil.query_visitor_record(**cls.parse_dict(visitor_query.model_dump())) + + return result + + + # # 查询门禁点列表v2 + # @classmethod + # async def get_door_list_service(cls, pageNo: int = 1, pageSize: int = 10): + # """查询门禁点列表v2 + # """ + + # result = await HaikangUtil.get_door_list_v2(pageNo=pageNo, pageSize=pageSize) + + # return result + + # # 查询门禁状态 + # @classmethod + # async def get_door_status_service(cls, door_index_codes): + # """查询门禁状态 + + # Args: + # door_index_codes (list): 门禁点唯一标识 + # """ + + # result = await HaikangUtil.get_door_status(door_index_codes) + + # if result[0]: + # noAuthDoorIndexCodeList = result[1] + # if door_index_codes in noAuthDoorIndexCodeList: + # return False, 500 ,'无门禁权限' + # else: + # return True, result[1] + # else: + # return result + + # @classmethod + # def parse_dict(cls, d): + # t_d = dict() + # for k, v in d.items(): + # if v: + # t_d[k] = v + # return t_d + + # 门禁点反控 + # @classmethod + # async def door_do_control_service(cls, door_index_code: list, control_type: int): + # """门禁点反控服务 + + # Args: + # door_index_code (list): _description_ + # control_type (int): _description_ + # """ \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_admin/service/identification_record_service.py b/ruoyi-fastapi-backend/module_admin/service/identification_record_service.py new file mode 100644 index 0000000..53e592a --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/identification_record_service.py @@ -0,0 +1,187 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.do.identification_record_do import IdentificationRecord +from module_admin.dao.identification_record_dao import Identification_recordDao +from module_admin.dao.door_dao import DoorDao +from module_admin.dao.identification_statistics_dao import Identification_statisticsDao +from module_admin.entity.vo.identification_record_vo import DeleteIdentification_recordModel, Identification_recordModel, Identification_recordPageQueryModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil +from utils.haikang_util import HaikangUtil + +from datetime import datetime +from dateutil import tz + + +class Identification_recordService: + """ + 识别记录模块服务层 + """ + + @classmethod + async def get_identification_record_list_services( + cls, query_db: AsyncSession, query_object: Identification_recordPageQueryModel, is_page: bool = False + ): + """ + 获取识别记录列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 识别记录列表信息对象 + """ + t = query_object.page_size + query_object.page_size = query_object.page_size//2 + identification_record_list_result = await Identification_recordDao.get_identification_record_list(query_db, query_object, is_page) + + # 这里也要获取海康的识别记录, 查询时间需要满足ISO8601标准 + start_time = cls.time_2_iso8601(query_object.begin_create_time) if query_object.begin_create_time else None + end_time = cls.time_2_iso8601(query_object.end_create_time) if query_object.end_create_time else None + + # 获取门禁点列表, 获取机器人添加的门禁设备列表,有权限的. + door_index_code_list = await DoorDao.get_door_index_code_list(query_db, permission='1') + try: + haikang_record_list = await HaikangUtil.query_door_events_v2(door_index_code_list + ,pageNo=query_object.page_num + , pageSize=max(t-query_object.page_size, t-len(identification_record_list_result.rows)) + ,startTime=start_time, endTime=end_time + ,personName=query_object.person_name) + return identification_record_list_result.rows + cls.parse_haikang_record_list(haikang_record_list[1]["list"]) + except Exception as e: + print(e) + + # print(identification_record_list_result.rows) + # print("*"*100) + # print(haikang_record_list[1]) + return identification_record_list_result.rows + # return None + + + @classmethod + async def add_identification_record_services(cls, query_db: AsyncSession, page_object: Identification_recordModel): + """ + 新增识别记录信息service + + :param query_db: orm对象 + :param page_object: 新增识别记录对象 + :return: 新增识别记录校验结果 + """ + try: + await Identification_recordDao.add_identification_record_dao(query_db, page_object) + + # 更新识别统计数据 + # 更新识别统计数据 - 根据person_status更新相应字段 + if page_object.person_status: + await Identification_statisticsDao.update_identification_statistics_dao(query_db, page_object.person_status) + + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_identification_record_services(cls, query_db: AsyncSession, page_object: Identification_recordModel): + """ + 编辑识别记录信息service + + :param query_db: orm对象 + :param page_object: 编辑识别记录对象 + :return: 编辑识别记录校验结果 + """ + edit_identification_record = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'create_by'}) + identification_record_info = await cls.identification_record_detail_services(query_db, page_object.id) + if identification_record_info.id: + try: + await Identification_recordDao.edit_identification_record_dao(query_db, edit_identification_record) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='识别记录不存在') + + @classmethod + async def delete_identification_record_services(cls, query_db: AsyncSession, page_object: DeleteIdentification_recordModel): + """ + 删除识别记录信息service + + :param query_db: orm对象 + :param page_object: 删除识别记录对象 + :return: 删除识别记录校验结果 + """ + if page_object.ids: + id_list = page_object.ids.split(',') + try: + for id in id_list: + await Identification_recordDao.delete_identification_record_dao(query_db, Identification_recordModel(id=id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入主键 自增为空') + + @classmethod + async def identification_record_detail_services(cls, query_db: AsyncSession, id: int): + """ + 获取识别记录详细信息service + + :param query_db: orm对象 + :param id: 主键 自增 + :return: 主键 自增对应的信息 + """ + identification_record = await Identification_recordDao.get_identification_record_detail_by_id(query_db, id=id) + if identification_record: + result = Identification_recordModel(**CamelCaseUtil.transform_result(identification_record)) + else: + result = Identification_recordModel(**dict()) + + return result + + @staticmethod + async def export_identification_record_list_services(identification_record_list: List): + """ + 导出识别记录信息service + + :param identification_record_list: 识别记录信息列表 + :return: 识别记录信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'id': '主键 自增', + 'personName': '识别人姓名', + 'doorName': '门禁点', + 'status': '识别结果, 0 拒绝, 1 通过', + 'source': '识别来源', + 'picUri': '图像url', + 'createTime': '创建时间', + 'createBy': '创建者', + } + binary_data = ExcelUtil.export_list2excel(identification_record_list, mapping_dict) + + return binary_data + + @classmethod + def time_2_iso8601(cls, time_str): + # 解析日期并设置时区为东八区 + dt = datetime.strptime(time_str, "%Y-%m-%d").replace(tzinfo=tz.gettz("Asia/Shanghai")) + + # 转换为 ISO 8601 格式 + iso_format = dt.isoformat() + + return iso_format + + @classmethod + def parse_haikang_record_list(cls, record_list: list): + back = list() + for record in record_list: + back.append(IdentificationRecord(person_name=record['personName'], person_status="1", door_name=record['doorName'], pic_uri=record['picUri'] + , status="1", source="haikang", conf=100, create_time=record['eventTime'])) + + return back diff --git a/ruoyi-fastapi-backend/module_admin/service/identification_statistics_service.py b/ruoyi-fastapi-backend/module_admin/service/identification_statistics_service.py new file mode 100644 index 0000000..d19869e --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/identification_statistics_service.py @@ -0,0 +1,152 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.identification_statistics_dao import Identification_statisticsDao +from module_admin.entity.vo.identification_statistics_vo import DeleteIdentification_statisticsModel, Identification_statisticsModel, Identification_statisticsPageQueryModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class Identification_statisticsService: + """ + 识别统计模块服务层 + """ + + @classmethod + async def get_today_identification_statistics_services(cls, query_db: AsyncSession ): + """ + 获取识别统计列表 + :param query_object: + :param is_page: + :return: + """ + result = await Identification_statisticsDao.get_today_identification_statistics(query_db) + return result + @classmethod + async def get_total_identification_statistics_services(cls, query_db: AsyncSession): + """ + 获取总的识别统计数据 - 返回各个字段的总和 + + :param query_db: orm对象 + :return: 总和统计数据 + """ + result = await Identification_statisticsDao.get_total_identification_statistics(query_db) + return result + + @classmethod + async def get_identification_statistics_list_services( + cls, query_db: AsyncSession, query_object: Identification_statisticsPageQueryModel, is_page: bool = False + ): + """ + 获取识别统计列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 识别统计列表信息对象 + """ + identification_statistics_list_result = await Identification_statisticsDao.get_identification_statistics_list(query_db, query_object, is_page) + + return identification_statistics_list_result + + + @classmethod + async def add_identification_statistics_services(cls, query_db: AsyncSession, page_object: Identification_statisticsModel): + """ + 新增识别统计信息service + + :param query_db: orm对象 + :param page_object: 新增识别统计对象 + :return: 新增识别统计校验结果 + """ + try: + await Identification_statisticsDao.add_identification_statistics_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_identification_statistics_services(cls, query_db: AsyncSession, page_object: Identification_statisticsModel): + """ + 编辑识别统计信息service + + :param query_db: orm对象 + :param page_object: 编辑识别统计对象 + :return: 编辑识别统计校验结果 + """ + edit_identification_statistics = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'create_by'}) + identification_statistics_info = await cls.identification_statistics_detail_services(query_db, page_object.id) + if identification_statistics_info.id: + try: + await Identification_statisticsDao.edit_identification_statistics_dao(query_db, edit_identification_statistics) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='识别统计不存在') + + @classmethod + async def delete_identification_statistics_services(cls, query_db: AsyncSession, page_object: DeleteIdentification_statisticsModel): + """ + 删除识别统计信息service + + :param query_db: orm对象 + :param page_object: 删除识别统计对象 + :return: 删除识别统计校验结果 + """ + if page_object.ids: + id_list = page_object.ids.split(',') + try: + for id in id_list: + await Identification_statisticsDao.delete_identification_statistics_dao(query_db, Identification_statisticsModel(id=id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入主键 自增为空') + + @classmethod + async def identification_statistics_detail_services(cls, query_db: AsyncSession, id: int): + """ + 获取识别统计详细信息service + + :param query_db: orm对象 + :param id: 主键 自增 + :return: 主键 自增对应的信息 + """ + identification_statistics = await Identification_statisticsDao.get_identification_statistics_detail_by_id(query_db, id=id) + if identification_statistics: + result = Identification_statisticsModel(**CamelCaseUtil.transform_result(identification_statistics)) + else: + result = Identification_statisticsModel(**dict()) + + return result + + @staticmethod + async def export_identification_statistics_list_services(identification_statistics_list: List): + """ + 导出识别统计信息service + + :param identification_statistics_list: 识别统计信息列表 + :return: 识别统计信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'id': '主键 自增', + 'employ': '员工识别成功数', + 'visitor': '访客识别成功数', + 'stranger': '位置人员识别失败数', + 'createTime': '创建时间', + 'createBy': '创建者', + } + binary_data = ExcelUtil.export_list2excel(identification_statistics_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/info_service.py b/ruoyi-fastapi-backend/module_admin/service/info_service.py new file mode 100644 index 0000000..7bc8a30 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/info_service.py @@ -0,0 +1,145 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.info_dao import InfoDao +from module_admin.entity.vo.info_vo import DeleteInfoModel, InfoModel, InfoPageQueryModel, InfoUpdateModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class InfoService: + """ + 机器人信息模块服务层 + """ + + + @classmethod + async def refresh_robot_services(cls, query_db: AsyncSession, robot_ids: str): + return await InfoDao.refresh_robot_by_ids(query_db, robot_ids) + + @classmethod + async def get_info_list_services( + cls, query_db: AsyncSession, query_object: InfoPageQueryModel, is_page: bool = False + ): + """ + 获取机器人信息列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 机器人信息列表信息对象 + """ + info_list_result = await InfoDao.get_info_list(query_db, query_object, is_page) + + return info_list_result + + + @classmethod + async def add_info_services(cls, query_db: AsyncSession, page_object: InfoModel): + """ + 新增机器人信息信息service + + :param query_db: orm对象 + :param page_object: 新增机器人信息对象 + :return: 新增机器人信息校验结果 + """ + try: + await InfoDao.add_info_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_info_services(cls, query_db: AsyncSession, page_object: InfoUpdateModel): + """ + 编辑机器人信息信息service + + :param query_db: orm对象 + :param page_object: 编辑机器人信息对象 + :return: 编辑机器人信息校验结果 + """ + edit_info = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'create_by', 'del_flag', }) + info_info = await cls.info_detail_services(query_db, page_object.robot_id) + if info_info.robot_id: + try: + await InfoDao.edit_info_dao(query_db, edit_info) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='机器人信息不存在') + + @classmethod + async def delete_info_services(cls, query_db: AsyncSession, page_object: DeleteInfoModel): + """ + 删除机器人信息信息service + + :param query_db: orm对象 + :param page_object: 删除机器人信息对象 + :return: 删除机器人信息校验结果 + """ + if page_object.robot_ids: + robot_id_list = page_object.robot_ids.split(',') + try: + for robot_id in robot_id_list: + await InfoDao.delete_info_dao(query_db, InfoModel(robotId=robot_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入机器人ID为空') + + @classmethod + async def info_detail_services(cls, query_db: AsyncSession, robot_id: int): + """ + 获取机器人信息详细信息service + + :param query_db: orm对象 + :param robot_id: 机器人ID + :return: 机器人ID对应的信息 + """ + info = await InfoDao.get_info_detail_by_id(query_db, robot_id=robot_id) + if info: + result = InfoModel(**CamelCaseUtil.transform_result(info)) + else: + result = InfoModel(**dict()) + + return result + + @staticmethod + async def export_info_list_services(info_list: List): + """ + 导出机器人信息信息service + + :param info_list: 机器人信息信息列表 + :return: 机器人信息信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'robotId': '机器人ID', + 'name': '机器人名称', + 'model': '机器人型号', + 'serial': '序列号', + 'online': '机器人在线状态(0在线, 1离线)', + 'power': '电量', + 'runTime': '总运行时长', + 'position': '位置', + 'image': '机器人外观图地址', + 'createTime': '创建时间', + 'createBy': '创建者', + 'updateTime': '修改时间', + 'updateBy': '修改者', + 'delFlag': '删除标志(0存在, 2删除)', + 'remark': '备注', + } + binary_data = ExcelUtil.export_list2excel(info_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/job_log_service.py b/ruoyi-fastapi-backend/module_admin/service/job_log_service.py new file mode 100644 index 0000000..596abe7 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/job_log_service.py @@ -0,0 +1,142 @@ +from fastapi import Request +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import Session +from typing import List +from module_admin.dao.job_log_dao import JobLogDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.job_vo import DeleteJobLogModel, JobLogModel, JobLogPageQueryModel +from module_admin.service.dict_service import DictDataService +from utils.excel_util import ExcelUtil + + +class JobLogService: + """ + 定时任务日志管理模块服务层 + """ + + @classmethod + async def get_job_log_list_services( + cls, query_db: AsyncSession, query_object: JobLogPageQueryModel, is_page: bool = False + ): + """ + 获取定时任务日志列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 定时任务日志列表信息对象 + """ + job_log_list_result = await JobLogDao.get_job_log_list(query_db, query_object, is_page) + + return job_log_list_result + + @classmethod + def add_job_log_services(cls, query_db: Session, page_object: JobLogModel): + """ + 新增定时任务日志信息service + + :param query_db: orm对象 + :param page_object: 新增定时任务日志对象 + :return: 新增定时任务日志校验结果 + """ + try: + JobLogDao.add_job_log_dao(query_db, page_object) + query_db.commit() + result = dict(is_success=True, message='新增成功') + except Exception as e: + query_db.rollback() + result = dict(is_success=False, message=str(e)) + + return CrudResponseModel(**result) + + @classmethod + async def delete_job_log_services(cls, query_db: AsyncSession, page_object: DeleteJobLogModel): + """ + 删除定时任务日志信息service + + :param query_db: orm对象 + :param page_object: 删除定时任务日志对象 + :return: 删除定时任务日志校验结果 + """ + if page_object.job_log_ids: + job_log_id_list = page_object.job_log_ids.split(',') + try: + for job_log_id in job_log_id_list: + await JobLogDao.delete_job_log_dao(query_db, JobLogModel(jobLogId=job_log_id)) + await query_db.commit() + result = dict(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + result = dict(is_success=False, message='传入定时任务日志id为空') + return CrudResponseModel(**result) + + @classmethod + async def clear_job_log_services(cls, query_db: AsyncSession): + """ + 清除定时任务日志信息service + + :param query_db: orm对象 + :return: 清除定时任务日志校验结果 + """ + try: + await JobLogDao.clear_job_log_dao(query_db) + await query_db.commit() + result = dict(is_success=True, message='清除成功') + except Exception as e: + await query_db.rollback() + raise e + + return CrudResponseModel(**result) + + @staticmethod + async def export_job_log_list_services(request: Request, job_log_list: List): + """ + 导出定时任务日志信息service + + :param request: Request对象 + :param job_log_list: 定时任务日志信息列表 + :return: 定时任务日志信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'jobLogId': '任务日志编码', + 'jobName': '任务名称', + 'jobGroup': '任务组名', + 'jobExecutor': '任务执行器', + 'invokeTarget': '调用目标字符串', + 'jobArgs': '位置参数', + 'jobKwargs': '关键字参数', + 'jobTrigger': '任务触发器', + 'jobMessage': '日志信息', + 'status': '执行状态', + 'exceptionInfo': '异常信息', + 'createTime': '创建时间', + } + + job_group_list = await DictDataService.query_dict_data_list_from_cache_services( + request.app.state.redis, dict_type='sys_job_group' + ) + job_group_option = [dict(label=item.get('dictLabel'), value=item.get('dictValue')) for item in job_group_list] + job_group_option_dict = {item.get('value'): item for item in job_group_option} + job_executor_list = await DictDataService.query_dict_data_list_from_cache_services( + request.app.state.redis, dict_type='sys_job_executor' + ) + job_executor_option = [ + dict(label=item.get('dictLabel'), value=item.get('dictValue')) for item in job_executor_list + ] + job_executor_option_dict = {item.get('value'): item for item in job_executor_option} + + for item in job_log_list: + if item.get('status') == '0': + item['status'] = '正常' + else: + item['status'] = '暂停' + if str(item.get('jobGroup')) in job_group_option_dict.keys(): + item['jobGroup'] = job_group_option_dict.get(str(item.get('jobGroup'))).get('label') + if str(item.get('jobExecutor')) in job_executor_option_dict.keys(): + item['jobExecutor'] = job_executor_option_dict.get(str(item.get('jobExecutor'))).get('label') + binary_data = ExcelUtil.export_list2excel(job_log_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/job_service.py b/ruoyi-fastapi-backend/module_admin/service/job_service.py new file mode 100644 index 0000000..55263c1 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/job_service.py @@ -0,0 +1,265 @@ +from fastapi import Request +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant, JobConstant +from config.get_scheduler import SchedulerUtil +from exceptions.exception import ServiceException +from module_admin.dao.job_dao import JobDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.job_vo import DeleteJobModel, EditJobModel, JobModel, JobPageQueryModel +from module_admin.service.dict_service import DictDataService +from utils.common_util import CamelCaseUtil +from utils.cron_util import CronUtil +from utils.excel_util import ExcelUtil +from utils.string_util import StringUtil + + +class JobService: + """ + 定时任务管理模块服务层 + """ + + @classmethod + async def get_job_list_services( + cls, query_db: AsyncSession, query_object: JobPageQueryModel, is_page: bool = False + ): + """ + 获取定时任务列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 定时任务列表信息对象 + """ + job_list_result = await JobDao.get_job_list(query_db, query_object, is_page) + + return job_list_result + + @classmethod + async def check_job_unique_services(cls, query_db: AsyncSession, page_object: JobModel): + """ + 校验定时任务是否存在service + + :param query_db: orm对象 + :param page_object: 定时任务对象 + :return: 校验结果 + """ + job_id = -1 if page_object.job_id is None else page_object.job_id + job = await JobDao.get_job_detail_by_info(query_db, page_object) + if job and job.job_id != job_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_job_services(cls, query_db: AsyncSession, page_object: JobModel): + """ + 新增定时任务信息service + + :param query_db: orm对象 + :param page_object: 新增定时任务对象 + :return: 新增定时任务校验结果 + """ + if not CronUtil.validate_cron_expression(page_object.cron_expression): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,Cron表达式不正确') + elif StringUtil.contains_ignore_case(page_object.invoke_target, CommonConstant.LOOKUP_RMI): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许rmi调用') + elif StringUtil.contains_any_ignore_case( + page_object.invoke_target, [CommonConstant.LOOKUP_LDAP, CommonConstant.LOOKUP_LDAPS] + ): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许ldap(s)调用') + elif StringUtil.contains_any_ignore_case( + page_object.invoke_target, [CommonConstant.HTTP, CommonConstant.HTTPS] + ): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许http(s)调用') + elif StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_ERROR_LIST): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串存在违规') + elif not StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_WHITE_LIST): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不在白名单内') + elif not await cls.check_job_unique_services(query_db, page_object): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,定时任务已存在') + else: + try: + add_job = await JobDao.add_job_dao(query_db, page_object) + job_info = await cls.job_detail_services(query_db, add_job.job_id) + if job_info.status == '0': + SchedulerUtil.add_scheduler_job(job_info=job_info) + await query_db.commit() + result = dict(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + return CrudResponseModel(**result) + + @classmethod + async def edit_job_services(cls, query_db: AsyncSession, page_object: EditJobModel): + """ + 编辑定时任务信息service + + :param query_db: orm对象 + :param page_object: 编辑定时任务对象 + :return: 编辑定时任务校验结果 + """ + edit_job = page_object.model_dump(exclude_unset=True) + if page_object.type == 'status': + del edit_job['type'] + job_info = await cls.job_detail_services(query_db, page_object.job_id) + if job_info: + if page_object.type != 'status': + if not CronUtil.validate_cron_expression(page_object.cron_expression): + raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,Cron表达式不正确') + elif StringUtil.contains_ignore_case(page_object.invoke_target, CommonConstant.LOOKUP_RMI): + raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许rmi调用') + elif StringUtil.contains_any_ignore_case( + page_object.invoke_target, [CommonConstant.LOOKUP_LDAP, CommonConstant.LOOKUP_LDAPS] + ): + raise ServiceException( + message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许ldap(s)调用' + ) + elif StringUtil.contains_any_ignore_case( + page_object.invoke_target, [CommonConstant.HTTP, CommonConstant.HTTPS] + ): + raise ServiceException( + message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许http(s)调用' + ) + elif StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_ERROR_LIST): + raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串存在违规') + elif not StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_WHITE_LIST): + raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串不在白名单内') + elif not await cls.check_job_unique_services(query_db, page_object): + raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,定时任务已存在') + try: + await JobDao.edit_job_dao(query_db, edit_job) + SchedulerUtil.remove_scheduler_job(job_id=edit_job.get('job_id')) + if edit_job.get('status') == '0': + job_info = await cls.job_detail_services(query_db, edit_job.get('job_id')) + SchedulerUtil.add_scheduler_job(job_info=job_info) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='定时任务不存在') + + @classmethod + async def execute_job_once_services(cls, query_db: AsyncSession, page_object: JobModel): + """ + 执行一次定时任务service + + :param query_db: orm对象 + :param page_object: 定时任务对象 + :return: 执行一次定时任务结果 + """ + SchedulerUtil.remove_scheduler_job(job_id=page_object.job_id) + job_info = await cls.job_detail_services(query_db, page_object.job_id) + if job_info: + SchedulerUtil.execute_scheduler_job_once(job_info=job_info) + return CrudResponseModel(is_success=True, message='执行成功') + else: + raise ServiceException(message='定时任务不存在') + + @classmethod + async def delete_job_services(cls, query_db: AsyncSession, page_object: DeleteJobModel): + """ + 删除定时任务信息service + + :param query_db: orm对象 + :param page_object: 删除定时任务对象 + :return: 删除定时任务校验结果 + """ + if page_object.job_ids: + job_id_list = page_object.job_ids.split(',') + try: + for job_id in job_id_list: + await JobDao.delete_job_dao(query_db, JobModel(jobId=job_id)) + SchedulerUtil.remove_scheduler_job(job_id=job_id) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入定时任务id为空') + + @classmethod + async def job_detail_services(cls, query_db: AsyncSession, job_id: int): + """ + 获取定时任务详细信息service + + :param query_db: orm对象 + :param job_id: 定时任务id + :return: 定时任务id对应的信息 + """ + job = await JobDao.get_job_detail_by_id(query_db, job_id=job_id) + if job: + result = JobModel(**CamelCaseUtil.transform_result(job)) + else: + result = JobModel(**dict()) + + return result + + @staticmethod + async def export_job_list_services(request: Request, job_list: List): + """ + 导出定时任务信息service + + :param request: Request对象 + :param job_list: 定时任务信息列表 + :return: 定时任务信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'jobId': '任务编码', + 'jobName': '任务名称', + 'jobGroup': '任务组名', + 'jobExecutor': '任务执行器', + 'invokeTarget': '调用目标字符串', + 'jobArgs': '位置参数', + 'jobKwargs': '关键字参数', + 'cronExpression': 'cron执行表达式', + 'misfirePolicy': '计划执行错误策略', + 'concurrent': '是否并发执行', + 'status': '状态', + 'createBy': '创建者', + 'createTime': '创建时间', + 'updateBy': '更新者', + 'updateTime': '更新时间', + 'remark': '备注', + } + + job_group_list = await DictDataService.query_dict_data_list_from_cache_services( + request.app.state.redis, dict_type='sys_job_group' + ) + job_group_option = [dict(label=item.get('dictLabel'), value=item.get('dictValue')) for item in job_group_list] + job_group_option_dict = {item.get('value'): item for item in job_group_option} + job_executor_list = await DictDataService.query_dict_data_list_from_cache_services( + request.app.state.redis, dict_type='sys_job_executor' + ) + job_executor_option = [ + dict(label=item.get('dictLabel'), value=item.get('dictValue')) for item in job_executor_list + ] + job_executor_option_dict = {item.get('value'): item for item in job_executor_option} + + for item in job_list: + if item.get('status') == '0': + item['status'] = '正常' + else: + item['status'] = '暂停' + if str(item.get('jobGroup')) in job_group_option_dict.keys(): + item['jobGroup'] = job_group_option_dict.get(str(item.get('jobGroup'))).get('label') + if str(item.get('jobExecutor')) in job_executor_option_dict.keys(): + item['jobExecutor'] = job_executor_option_dict.get(str(item.get('jobExecutor'))).get('label') + if item.get('misfirePolicy') == '1': + item['misfirePolicy'] = '立即执行' + elif item.get('misfirePolicy') == '2': + item['misfirePolicy'] = '执行一次' + else: + item['misfirePolicy'] = '放弃执行' + if item.get('concurrent') == '0': + item['concurrent'] = '允许' + else: + item['concurrent'] = '禁止' + binary_data = ExcelUtil.export_list2excel(job_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/log_service.py b/ruoyi-fastapi-backend/module_admin/service/log_service.py new file mode 100644 index 0000000..0983b1a --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/log_service.py @@ -0,0 +1,258 @@ +from fastapi import Request +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from exceptions.exception import ServiceException +from module_admin.dao.log_dao import LoginLogDao, OperationLogDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.log_vo import ( + DeleteLoginLogModel, + DeleteOperLogModel, + LogininforModel, + LoginLogPageQueryModel, + OperLogModel, + OperLogPageQueryModel, + UnlockUser, +) +from module_admin.service.dict_service import DictDataService +from utils.excel_util import ExcelUtil + + +class OperationLogService: + """ + 操作日志管理模块服务层 + """ + + @classmethod + async def get_operation_log_list_services( + cls, query_db: AsyncSession, query_object: OperLogPageQueryModel, is_page: bool = False + ): + """ + 获取操作日志列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 操作日志列表信息对象 + """ + operation_log_list_result = await OperationLogDao.get_operation_log_list(query_db, query_object, is_page) + + return operation_log_list_result + + @classmethod + async def add_operation_log_services(cls, query_db: AsyncSession, page_object: OperLogModel): + """ + 新增操作日志service + + :param query_db: orm对象 + :param page_object: 新增操作日志对象 + :return: 新增操作日志校验结果 + """ + try: + await OperationLogDao.add_operation_log_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def delete_operation_log_services(cls, query_db: AsyncSession, page_object: DeleteOperLogModel): + """ + 删除操作日志信息service + + :param query_db: orm对象 + :param page_object: 删除操作日志对象 + :return: 删除操作日志校验结果 + """ + if page_object.oper_ids: + oper_id_list = page_object.oper_ids.split(',') + try: + for oper_id in oper_id_list: + await OperationLogDao.delete_operation_log_dao(query_db, OperLogModel(operId=oper_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入操作日志id为空') + + @classmethod + async def clear_operation_log_services(cls, query_db: AsyncSession): + """ + 清除操作日志信息service + + :param query_db: orm对象 + :return: 清除操作日志校验结果 + """ + try: + await OperationLogDao.clear_operation_log_dao(query_db) + await query_db.commit() + return CrudResponseModel(is_success=True, message='清除成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def export_operation_log_list_services(cls, request: Request, operation_log_list: List): + """ + 导出操作日志信息service + + :param request: Request对象 + :param operation_log_list: 操作日志信息列表 + :return: 操作日志信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'operId': '日志编号', + 'title': '系统模块', + 'businessType': '操作类型', + 'method': '方法名称', + 'requestMethod': '请求方式', + 'operName': '操作人员', + 'deptName': '部门名称', + 'operUrl': '请求URL', + 'operIp': '操作地址', + 'operLocation': '操作地点', + 'operParam': '请求参数', + 'jsonResult': '返回参数', + 'status': '操作状态', + 'error_msg': '错误消息', + 'operTime': '操作日期', + 'costTime': '消耗时间(毫秒)', + } + + operation_type_list = await DictDataService.query_dict_data_list_from_cache_services( + request.app.state.redis, dict_type='sys_oper_type' + ) + operation_type_option = [ + dict(label=item.get('dictLabel'), value=item.get('dictValue')) for item in operation_type_list + ] + operation_type_option_dict = {item.get('value'): item for item in operation_type_option} + + for item in operation_log_list: + if item.get('status') == 0: + item['status'] = '成功' + else: + item['status'] = '失败' + if str(item.get('businessType')) in operation_type_option_dict.keys(): + item['businessType'] = operation_type_option_dict.get(str(item.get('businessType'))).get('label') + binary_data = ExcelUtil.export_list2excel(operation_log_list, mapping_dict) + + return binary_data + + +class LoginLogService: + """ + 登录日志管理模块服务层 + """ + + @classmethod + async def get_login_log_list_services( + cls, query_db: AsyncSession, query_object: LoginLogPageQueryModel, is_page: bool = False + ): + """ + 获取登录日志列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 登录日志列表信息对象 + """ + operation_log_list_result = await LoginLogDao.get_login_log_list(query_db, query_object, is_page) + + return operation_log_list_result + + @classmethod + async def add_login_log_services(cls, query_db: AsyncSession, page_object: LogininforModel): + """ + 新增登录日志service + + :param query_db: orm对象 + :param page_object: 新增登录日志对象 + :return: 新增登录日志校验结果 + """ + try: + await LoginLogDao.add_login_log_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def delete_login_log_services(cls, query_db: AsyncSession, page_object: DeleteLoginLogModel): + """ + 删除操作日志信息service + + :param query_db: orm对象 + :param page_object: 删除操作日志对象 + :return: 删除操作日志校验结果 + """ + if page_object.info_ids: + info_id_list = page_object.info_ids.split(',') + try: + for info_id in info_id_list: + await LoginLogDao.delete_login_log_dao(query_db, LogininforModel(infoId=info_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入登录日志id为空') + + @classmethod + async def clear_login_log_services(cls, query_db: AsyncSession): + """ + 清除操作日志信息service + + :param query_db: orm对象 + :return: 清除操作日志校验结果 + """ + try: + await LoginLogDao.clear_login_log_dao(query_db) + await query_db.commit() + return CrudResponseModel(is_success=True, message='清除成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def unlock_user_services(cls, request: Request, unlock_user: UnlockUser): + locked_user = await request.app.state.redis.get(f'account_lock:{unlock_user.user_name}') + if locked_user: + await request.app.state.redis.delete(f'account_lock:{unlock_user.user_name}') + return CrudResponseModel(is_success=True, message='解锁成功') + else: + raise ServiceException(message='该用户未锁定') + + @staticmethod + async def export_login_log_list_services(login_log_list: List): + """ + 导出登录日志信息service + + :param login_log_list: 登录日志信息列表 + :return: 登录日志信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'infoId': '访问编号', + 'userName': '用户名称', + 'ipaddr': '登录地址', + 'loginLocation': '登录地点', + 'browser': '浏览器', + 'os': '操作系统', + 'status': '登录状态', + 'msg': '操作信息', + 'loginTime': '登录日期', + } + + for item in login_log_list: + if item.get('status') == '0': + item['status'] = '成功' + else: + item['status'] = '失败' + binary_data = ExcelUtil.export_list2excel(login_log_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/login_service.py b/ruoyi-fastapi-backend/module_admin/service/login_service.py new file mode 100644 index 0000000..5579f0d --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/login_service.py @@ -0,0 +1,606 @@ +import jwt +import random +import uuid +from datetime import datetime, timedelta, timezone +from fastapi import Depends, Form, Request +from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm +from jwt.exceptions import InvalidTokenError +from sqlalchemy.ext.asyncio import AsyncSession +from typing import Dict, List, Optional, Union +from config.constant import CommonConstant, MenuConstant +from config.enums import RedisInitKeyConfig +from config.env import AppConfig, JwtConfig +from config.get_db import get_db +from exceptions.exception import LoginException, AuthException, ServiceException +from module_admin.dao.login_dao import login_by_account +from module_admin.dao.user_dao import UserDao +from module_admin.entity.do.menu_do import SysMenu +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.login_vo import MenuTreeModel, MetaModel, RouterModel, SmsCode, UserLogin, UserRegister +from module_admin.entity.vo.user_vo import AddUserModel, CurrentUserModel, ResetUserModel, TokenData, UserInfoModel +from module_admin.service.user_service import UserService +from utils.common_util import CamelCaseUtil +from utils.log_util import logger +from utils.message_util import message_service +from utils.pwd_util import PwdUtil + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl='login') + + +class CustomOAuth2PasswordRequestForm(OAuth2PasswordRequestForm): + """ + 自定义OAuth2PasswordRequestForm类,增加验证码及会话编号参数 + """ + + def __init__( + self, + grant_type: str = Form(default=None, regex='password'), + username: str = Form(), + password: str = Form(), + scope: str = Form(default=''), + client_id: Optional[str] = Form(default=None), + client_secret: Optional[str] = Form(default=None), + code: Optional[str] = Form(default=''), + uuid: Optional[str] = Form(default=''), + login_info: Optional[Dict[str, str]] = Form(default=None), + ): + super().__init__( + grant_type=grant_type, + username=username, + password=password, + scope=scope, + client_id=client_id, + client_secret=client_secret, + ) + self.code = code + self.uuid = uuid + self.login_info = login_info + + +class LoginService: + """ + 登录模块服务层 + """ + + @classmethod + async def authenticate_user(cls, request: Request, query_db: AsyncSession, login_user: UserLogin): + """ + 根据用户名密码校验用户登录 + + :param request: Request对象 + :param query_db: orm对象 + :param login_user: 登录用户对象 + :return: 校验结果 + """ + await cls.__check_login_ip(request) + account_lock = await request.app.state.redis.get( + f'{RedisInitKeyConfig.ACCOUNT_LOCK.key}:{login_user.user_name}' + ) + if login_user.user_name == account_lock: + logger.warning('账号已锁定,请稍后再试') + raise LoginException(data='', message='账号已锁定,请稍后再试') + # 判断请求是否来自于api文档,如果是返回指定格式的结果,用于修复api文档认证成功后token显示undefined的bug + request_from_swagger = ( + request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False + ) + request_from_redoc = ( + request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False + ) + # 判断是否开启验证码,开启则验证,否则不验证(dev模式下来自API文档的登录请求不检验) + if not login_user.captcha_enabled or ( + (request_from_swagger or request_from_redoc) and AppConfig.app_env == 'dev' + ): + pass + else: + await cls.__check_login_captcha(request, login_user) + user = await login_by_account(query_db, login_user.user_name) + if not user: + logger.warning('用户不存在') + raise LoginException(data='', message='用户不存在') + if not PwdUtil.verify_password(login_user.password, user[0].password): + cache_password_error_count = await request.app.state.redis.get( + f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}' + ) + password_error_counted = 0 + if cache_password_error_count: + password_error_counted = cache_password_error_count + password_error_count = int(password_error_counted) + 1 + await request.app.state.redis.set( + f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}', + password_error_count, + ex=timedelta(minutes=10), + ) + if password_error_count > 5: + await request.app.state.redis.delete( + f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}' + ) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.ACCOUNT_LOCK.key}:{login_user.user_name}', + login_user.user_name, + ex=timedelta(minutes=10), + ) + logger.warning('10分钟内密码已输错超过5次,账号已锁定,请10分钟后再试') + raise LoginException(data='', message='10分钟内密码已输错超过5次,账号已锁定,请10分钟后再试') + logger.warning('密码错误') + raise LoginException(data='', message='密码错误') + if user[0].status == '1': + logger.warning('用户已停用') + raise LoginException(data='', message='用户已停用') + await request.app.state.redis.delete(f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}') + return user + + @classmethod + async def __check_login_ip(cls, request: Request): + """ + 校验用户登录ip是否在黑名单内 + + :param request: Request对象 + :return: 校验结果 + """ + black_ip_value = await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.login.blackIPList') + black_ip_list = black_ip_value.split(',') if black_ip_value else [] + if request.headers.get('X-Forwarded-For') in black_ip_list: + logger.warning('当前IP禁止登录') + raise LoginException(data='', message='当前IP禁止登录') + return True + + @classmethod + async def __check_login_captcha(cls, request: Request, login_user: UserLogin): + """ + 校验用户登录验证码 + + :param request: Request对象 + :param login_user: 登录用户对象 + :return: 校验结果 + """ + captcha_value = await request.app.state.redis.get(f'{RedisInitKeyConfig.CAPTCHA_CODES.key}:{login_user.uuid}') + if not captcha_value: + logger.warning('验证码已失效') + raise LoginException(data='', message='验证码已失效') + if login_user.code != str(captcha_value): + logger.warning('验证码错误') + raise LoginException(data='', message='验证码错误') + return True + + @classmethod + async def create_access_token(cls, data: dict, expires_delta: Union[timedelta, None] = None): + """ + 根据登录信息创建当前用户token + + :param data: 登录信息 + :param expires_delta: token有效期 + :return: token + """ + to_encode = data.copy() + if expires_delta: + expire = datetime.now(timezone.utc) + expires_delta + else: + expire = datetime.now(timezone.utc) + timedelta(minutes=30) + to_encode.update({'exp': expire}) + encoded_jwt = jwt.encode(to_encode, JwtConfig.jwt_secret_key, algorithm=JwtConfig.jwt_algorithm) + return encoded_jwt + + @classmethod + async def get_current_user( + cls, request: Request = Request, token: str = Depends(oauth2_scheme), query_db: AsyncSession = Depends(get_db) + ): + """ + 根据token获取当前用户信息 + + :param request: Request对象 + :param token: 用户token + :param query_db: orm对象 + :return: 当前用户信息对象 + :raise: 令牌异常AuthException + """ + # if token[:6] != 'Bearer': + # logger.warning("用户token不合法") + # raise AuthException(data="", message="用户token不合法") + try: + if token.startswith('Bearer'): + token = token.split(' ')[1] + payload = jwt.decode(token, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm]) + user_id: str = payload.get('user_id') + session_id: str = payload.get('session_id') + if not user_id: + logger.warning('用户token不合法') + raise AuthException(data='', message='用户token不合法') + token_data = TokenData(user_id=int(user_id)) + except InvalidTokenError: + logger.warning('用户token已失效,请重新登录') + raise AuthException(data='', message='用户token已失效,请重新登录') + query_user = await UserDao.get_user_by_id(query_db, user_id=token_data.user_id) + if query_user.get('user_basic_info') is None: + logger.warning('用户token不合法') + raise AuthException(data='', message='用户token不合法') + if AppConfig.app_same_time_login: + redis_token = await request.app.state.redis.get(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}') + else: + # 此方法可实现同一账号同一时间只能登录一次 + redis_token = await request.app.state.redis.get( + f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{query_user.get('user_basic_info').user_id}" + ) + if token == redis_token: + if AppConfig.app_same_time_login: + await request.app.state.redis.set( + f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}', + redis_token, + ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes), + ) + else: + await request.app.state.redis.set( + f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{query_user.get('user_basic_info').user_id}", + redis_token, + ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes), + ) + + role_id_list = [item.role_id for item in query_user.get('user_role_info')] + if 1 in role_id_list: + permissions = ['*:*:*'] + else: + permissions = [row.perms for row in query_user.get('user_menu_info')] + post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')]) + role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')]) + roles = [row.role_key for row in query_user.get('user_role_info')] + + current_user = CurrentUserModel( + permissions=permissions, + roles=roles, + user=UserInfoModel( + **CamelCaseUtil.transform_result(query_user.get('user_basic_info')), + postIds=post_ids, + roleIds=role_ids, + dept=CamelCaseUtil.transform_result(query_user.get('user_dept_info')), + role=CamelCaseUtil.transform_result(query_user.get('user_role_info')), + ), + ) + return current_user + else: + logger.warning('用户token已失效,请重新登录') + raise AuthException(data='', message='用户token已失效,请重新登录') + + @classmethod + async def get_current_user_routers(cls, user_id: int, query_db: AsyncSession): + """ + 根据用户id获取当前用户路由信息 + + :param user_id: 用户id + :param query_db: orm对象 + :return: 当前用户路由信息对象 + """ + query_user = await UserDao.get_user_by_id(query_db, user_id=user_id) + user_router_menu = sorted( + [ + row + for row in query_user.get('user_menu_info') + if row.menu_type in [MenuConstant.TYPE_DIR, MenuConstant.TYPE_MENU] + ], + key=lambda x: x.order_num, + ) + menus = cls.__generate_menus(0, user_router_menu) + user_router = cls.__generate_user_router_menu(menus) + return [router.model_dump(exclude_unset=True, by_alias=True) for router in user_router] + + @classmethod + def __generate_menus(cls, pid: int, permission_list: List[SysMenu]): + """ + 工具方法:根据菜单信息生成菜单信息树形嵌套数据 + + :param pid: 菜单id + :param permission_list: 菜单列表信息 + :return: 菜单信息树形嵌套数据 + """ + menu_list: List[MenuTreeModel] = [] + for permission in permission_list: + if permission.parent_id == pid: + children = cls.__generate_menus(permission.menu_id, permission_list) + menu_list_data = MenuTreeModel(**CamelCaseUtil.transform_result(permission)) + if children: + menu_list_data.children = children + menu_list.append(menu_list_data) + + return menu_list + + @classmethod + def __generate_user_router_menu(cls, permission_list: List[MenuTreeModel]): + """ + 工具方法:根据菜单树信息生成路由信息树形嵌套数据 + + :param permission_list: 菜单树列表信息 + :return: 路由信息树形嵌套数据 + """ + router_list: List[RouterModel] = [] + for permission in permission_list: + router = RouterModel( + hidden=True if permission.visible == '1' else False, + name=RouterUtil.get_router_name(permission), + path=RouterUtil.get_router_path(permission), + component=RouterUtil.get_component(permission), + query=permission.query, + meta=MetaModel( + title=permission.menu_name, + icon=permission.icon, + noCache=True if permission.is_cache == 1 else False, + link=permission.path if RouterUtil.is_http(permission.path) else None, + ), + ) + c_menus = permission.children + if c_menus and permission.menu_type == MenuConstant.TYPE_DIR: + router.always_show = True + router.redirect = 'noRedirect' + router.children = cls.__generate_user_router_menu(c_menus) + elif RouterUtil.is_menu_frame(permission): + router.meta = None + children_list: List[RouterModel] = [] + children = RouterModel( + path=permission.path, + component=permission.component, + name=RouterUtil.get_route_name(permission.route_name, permission.path), + meta=MetaModel( + title=permission.menu_name, + icon=permission.icon, + noCache=True if permission.is_cache == 1 else False, + link=permission.path if RouterUtil.is_http(permission.path) else None, + ), + query=permission.query, + ) + children_list.append(children) + router.children = children_list + elif permission.parent_id == 0 and RouterUtil.is_inner_link(permission): + router.meta = MetaModel(title=permission.menu_name, icon=permission.icon) + router.path = '/' + children_list: List[RouterModel] = [] + router_path = RouterUtil.inner_link_replace_each(permission.path) + children = RouterModel( + path=router_path, + component=MenuConstant.INNER_LINK, + name=RouterUtil.get_route_name(permission.route_name, permission.path), + meta=MetaModel( + title=permission.menu_name, + icon=permission.icon, + link=permission.path if RouterUtil.is_http(permission.path) else None, + ), + ) + children_list.append(children) + router.children = children_list + + router_list.append(router) + + return router_list + + @classmethod + async def register_user_services(cls, request: Request, query_db: AsyncSession, user_register: UserRegister): + """ + 用户注册services + + :param request: Request对象 + :param query_db: orm对象 + :param user_register: 注册用户对象 + :return: 注册结果 + """ + register_enabled = ( + True + if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.registerUser') + == 'true' + else False + ) + captcha_enabled = ( + True + if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled') + == 'true' + else False + ) + if user_register.password == user_register.confirm_password: + if register_enabled: + if captcha_enabled: + captcha_value = await request.app.state.redis.get( + f'{RedisInitKeyConfig.CAPTCHA_CODES.key}:{user_register.uuid}' + ) + if not captcha_value: + raise ServiceException(message='验证码已失效') + elif user_register.code != str(captcha_value): + raise ServiceException(message='验证码错误') + add_user = AddUserModel( + userName=user_register.username, + nickName=user_register.username, + password=PwdUtil.get_password_hash(user_register.password), + ) + result = await UserService.add_user_services(query_db, add_user) + return result + else: + raise ServiceException(message='注册程序已关闭,禁止注册') + else: + raise ServiceException(message='两次输入的密码不一致') + + @classmethod + async def get_sms_code_services(cls, request: Request, query_db: AsyncSession, user: ResetUserModel): + """ + 获取短信验证码service + + :param request: Request对象 + :param query_db: orm对象 + :param user: 用户对象 + :return: 短信验证码对象 + """ + redis_sms_result = await request.app.state.redis.get(f'{RedisInitKeyConfig.SMS_CODE.key}:{user.session_id}') + if redis_sms_result: + return SmsCode(**dict(is_success=False, sms_code='', session_id='', message='短信验证码仍在有效期内')) + is_user = await UserDao.get_user_by_name(query_db, user.user_name) + if is_user: + sms_code = str(random.randint(100000, 999999)) + session_id = str(uuid.uuid4()) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SMS_CODE.key}:{session_id}', sms_code, ex=timedelta(minutes=2) + ) + # 此处模拟调用短信服务 + message_service(sms_code) + + return SmsCode(**dict(is_success=True, sms_code=sms_code, session_id=session_id, message='获取成功')) + + return SmsCode(**dict(is_success=False, sms_code='', session_id='', message='用户不存在')) + + @classmethod + async def forget_user_services(cls, request: Request, query_db: AsyncSession, forget_user: ResetUserModel): + """ + 用户忘记密码services + + :param request: Request对象 + :param query_db: orm对象 + :param forget_user: 重置用户对象 + :return: 重置结果 + """ + redis_sms_result = await request.app.state.redis.get( + f'{RedisInitKeyConfig.SMS_CODE.key}:{forget_user.session_id}' + ) + if forget_user.sms_code == redis_sms_result: + forget_user.password = PwdUtil.get_password_hash(forget_user.password) + forget_user.user_id = (await UserDao.get_user_by_name(query_db, forget_user.user_name)).user_id + edit_result = await UserService.reset_user_services(query_db, forget_user) + result = edit_result.dict() + elif not redis_sms_result: + result = dict(is_success=False, message='短信验证码已过期') + else: + await request.app.state.redis.delete(f'{RedisInitKeyConfig.SMS_CODE.key}:{forget_user.session_id}') + result = dict(is_success=False, message='短信验证码不正确') + + return CrudResponseModel(**result) + + @classmethod + async def logout_services(cls, request: Request, session_id: str): + """ + 退出登录services + + :param request: Request对象 + :param session_id: 会话编号 + :return: 退出登录结果 + """ + await request.app.state.redis.delete(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}') + # await request.app.state.redis.delete(f'{current_user.user.user_id}_access_token') + # await request.app.state.redis.delete(f'{current_user.user.user_id}_session_id') + + return True + + +class RouterUtil: + """ + 路由处理工具类 + """ + + @classmethod + def get_router_name(cls, menu: MenuTreeModel): + """ + 获取路由名称 + + :param menu: 菜单数对象 + :return: 路由名称 + """ + # 非外链并且是一级目录(类型为目录) + if cls.is_menu_frame(menu): + return '' + + return cls.get_route_name(menu.route_name, menu.path) + + @classmethod + def get_route_name(cls, name: str, path: str): + """ + 获取路由名称,如没有配置路由名称则取路由地址 + + :param name: 路由名称 + :param path: 路由地址 + :return: 路由名称(驼峰格式) + """ + router_name = name if name else path + return router_name.capitalize() + + @classmethod + def get_router_path(cls, menu: MenuTreeModel): + """ + 获取路由地址 + + :param menu: 菜单数对象 + :return: 路由地址 + """ + # 内链打开外网方式 + router_path = menu.path + if menu.parent_id != 0 and cls.is_inner_link(menu): + router_path = cls.inner_link_replace_each(router_path) + # 非外链并且是一级目录(类型为目录) + if menu.parent_id == 0 and menu.menu_type == MenuConstant.TYPE_DIR and menu.is_frame == MenuConstant.NO_FRAME: + router_path = f'/{menu.path}' + # 非外链并且是一级目录(类型为菜单) + elif cls.is_menu_frame(menu): + router_path = '/' + return router_path + + @classmethod + def get_component(cls, menu: MenuTreeModel): + """ + 获取组件信息 + + :param menu: 菜单数对象 + :return: 组件信息 + """ + component = MenuConstant.LAYOUT + if menu.component and not cls.is_menu_frame(menu): + component = menu.component + elif (menu.component is None or menu.component == '') and menu.parent_id != 0 and cls.is_inner_link(menu): + component = MenuConstant.INNER_LINK + elif (menu.component is None or menu.component == '') and cls.is_parent_view(menu): + component = MenuConstant.PARENT_VIEW + return component + + @classmethod + def is_menu_frame(cls, menu: MenuTreeModel): + """ + 判断是否为菜单内部跳转 + + :param menu: 菜单数对象 + :return: 是否为菜单内部跳转 + """ + return ( + menu.parent_id == 0 and menu.menu_type == MenuConstant.TYPE_MENU and menu.is_frame == MenuConstant.NO_FRAME + ) + + @classmethod + def is_inner_link(cls, menu: MenuTreeModel): + """ + 判断是否为内链组件 + + :param menu: 菜单数对象 + :return: 是否为内链组件 + """ + return menu.is_frame == MenuConstant.NO_FRAME and cls.is_http(menu.path) + + @classmethod + def is_parent_view(cls, menu: MenuTreeModel): + """ + 判断是否为parent_view组件 + + :param menu: 菜单数对象 + :return: 是否为parent_view组件 + """ + return menu.parent_id != 0 and menu.menu_type == MenuConstant.TYPE_DIR + + @classmethod + def is_http(cls, link: str): + """ + 判断是否为http(s)://开头 + + :param link: 链接 + :return: 是否为http(s)://开头 + """ + return link.startswith(CommonConstant.HTTP) or link.startswith(CommonConstant.HTTPS) + + @classmethod + def inner_link_replace_each(cls, path: str): + """ + 内链域名特殊字符替换 + + :param path: 内链域名 + :return: 替换后的内链域名 + """ + old_values = [CommonConstant.HTTP, CommonConstant.HTTPS, CommonConstant.WWW, '.', ':'] + new_values = ['', '', '', '/', '/'] + for old, new in zip(old_values, new_values): + path = path.replace(old, new) + return path diff --git a/ruoyi-fastapi-backend/module_admin/service/menu_service.py b/ruoyi-fastapi-backend/module_admin/service/menu_service.py new file mode 100644 index 0000000..cb6368a --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/menu_service.py @@ -0,0 +1,215 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import Optional +from config.constant import CommonConstant, MenuConstant +from exceptions.exception import ServiceException, ServiceWarning +from module_admin.dao.menu_dao import MenuDao +from module_admin.dao.role_dao import RoleDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.menu_vo import DeleteMenuModel, MenuQueryModel, MenuModel +from module_admin.entity.vo.role_vo import RoleMenuQueryModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from utils.common_util import CamelCaseUtil +from utils.string_util import StringUtil + + +class MenuService: + """ + 菜单管理模块服务层 + """ + + @classmethod + async def get_menu_tree_services(cls, query_db: AsyncSession, current_user: Optional[CurrentUserModel] = None): + """ + 获取菜单树信息service + + :param query_db: orm对象 + :param current_user: 当前用户对象 + :return: 菜单树信息对象 + """ + menu_list_result = await MenuDao.get_menu_list_for_tree( + query_db, current_user.user.user_id, current_user.user.role + ) + menu_tree_result = cls.list_to_tree(menu_list_result) + + return menu_tree_result + + @classmethod + async def get_role_menu_tree_services( + cls, query_db: AsyncSession, role_id: int, current_user: Optional[CurrentUserModel] = None + ): + """ + 根据角色id获取菜单树信息service + + :param query_db: orm对象 + :param role_id: 角色id + :param current_user: 当前用户对象 + :return: 当前角色id的菜单树信息对象 + """ + menu_list_result = await MenuDao.get_menu_list_for_tree( + query_db, current_user.user.user_id, current_user.user.role + ) + menu_tree_result = cls.list_to_tree(menu_list_result) + role = await RoleDao.get_role_detail_by_id(query_db, role_id) + role_menu_list = await RoleDao.get_role_menu_dao(query_db, role) + checked_keys = [row.menu_id for row in role_menu_list] + result = RoleMenuQueryModel(menus=menu_tree_result, checkedKeys=checked_keys) + + return result + + @classmethod + async def get_menu_list_services( + cls, query_db: AsyncSession, page_object: MenuQueryModel, current_user: Optional[CurrentUserModel] = None + ): + """ + 获取菜单列表信息service + + :param query_db: orm对象 + :param page_object: 分页查询参数对象 + :param current_user: 当前用户对象 + :return: 菜单列表信息对象 + """ + menu_list_result = await MenuDao.get_menu_list( + query_db, page_object, current_user.user.user_id, current_user.user.role + ) + + return CamelCaseUtil.transform_result(menu_list_result) + + @classmethod + async def check_menu_name_unique_services(cls, query_db: AsyncSession, page_object: MenuModel): + """ + 校验菜单名称是否唯一service + + :param query_db: orm对象 + :param page_object: 菜单对象 + :return: 校验结果 + """ + menu_id = -1 if page_object.menu_id is None else page_object.menu_id + menu = await MenuDao.get_menu_detail_by_info(query_db, MenuModel(menuName=page_object.menu_name)) + if menu and menu.menu_id != menu_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_menu_services(cls, query_db: AsyncSession, page_object: MenuModel): + """ + 新增菜单信息service + + :param query_db: orm对象 + :param page_object: 新增菜单对象 + :return: 新增菜单校验结果 + """ + if not await cls.check_menu_name_unique_services(query_db, page_object): + raise ServiceException(message=f'新增菜单{page_object.menu_name}失败,菜单名称已存在') + elif page_object.is_frame == MenuConstant.YES_FRAME and not StringUtil.is_http(page_object.path): + raise ServiceException(message=f'新增菜单{page_object.menu_name}失败,地址必须以http(s)://开头') + else: + try: + await MenuDao.add_menu_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_menu_services(cls, query_db: AsyncSession, page_object: MenuModel): + """ + 编辑菜单信息service + + :param query_db: orm对象 + :param page_object: 编辑部门对象 + :return: 编辑菜单校验结果 + """ + edit_menu = page_object.model_dump(exclude_unset=True) + menu_info = await cls.menu_detail_services(query_db, page_object.menu_id) + if menu_info.menu_id: + if not await cls.check_menu_name_unique_services(query_db, page_object): + raise ServiceException(message=f'修改菜单{page_object.menu_name}失败,菜单名称已存在') + elif page_object.is_frame == MenuConstant.YES_FRAME and not StringUtil.is_http(page_object.path): + raise ServiceException(message=f'修改菜单{page_object.menu_name}失败,地址必须以http(s)://开头') + elif page_object.menu_id == page_object.parent_id: + raise ServiceException(message=f'修改菜单{page_object.menu_name}失败,上级菜单不能选择自己') + else: + try: + await MenuDao.edit_menu_dao(query_db, edit_menu) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='菜单不存在') + + @classmethod + async def delete_menu_services(cls, query_db: AsyncSession, page_object: DeleteMenuModel): + """ + 删除菜单信息service + + :param query_db: orm对象 + :param page_object: 删除菜单对象 + :return: 删除菜单校验结果 + """ + if page_object.menu_ids: + menu_id_list = page_object.menu_ids.split(',') + try: + for menu_id in menu_id_list: + if (await MenuDao.has_child_by_menu_id_dao(query_db, int(menu_id))) > 0: + raise ServiceWarning(message='存在子菜单,不允许删除') + elif (await MenuDao.check_menu_exist_role_dao(query_db, int(menu_id))) > 0: + raise ServiceWarning(message='菜单已分配,不允许删除') + await MenuDao.delete_menu_dao(query_db, MenuModel(menuId=menu_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入菜单id为空') + + @classmethod + async def menu_detail_services(cls, query_db: AsyncSession, menu_id: int): + """ + 获取菜单详细信息service + + :param query_db: orm对象 + :param menu_id: 菜单id + :return: 菜单id对应的信息 + """ + menu = await MenuDao.get_menu_detail_by_id(query_db, menu_id=menu_id) + if menu: + result = MenuModel(**CamelCaseUtil.transform_result(menu)) + else: + result = MenuModel(**dict()) + + return result + + @classmethod + def list_to_tree(cls, permission_list: list) -> list: + """ + 工具方法:根据菜单列表信息生成树形嵌套数据 + + :param permission_list: 菜单列表信息 + :return: 菜单树形嵌套数据 + """ + permission_list = [ + dict(id=item.menu_id, label=item.menu_name, parentId=item.parent_id) for item in permission_list + ] + # 转成id为key的字典 + mapping: dict = dict(zip([i['id'] for i in permission_list], permission_list)) + + # 树容器 + container: list = [] + + for d in permission_list: + # 如果找不到父级项,则是根节点 + parent: dict = mapping.get(d['parentId']) + if parent is None: + container.append(d) + else: + children: list = parent.get('children') + if not children: + children = [] + children.append(d) + parent.update({'children': children}) + + return container diff --git a/ruoyi-fastapi-backend/module_admin/service/message_service.py b/ruoyi-fastapi-backend/module_admin/service/message_service.py new file mode 100644 index 0000000..023d698 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/message_service.py @@ -0,0 +1,181 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.message_dao import MessageDao +from module_admin.entity.vo.message_vo import DeleteMessageModel, MessageModel, MessagePageQueryModel, EditMessageModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class MessageService: + """ + 系统消息模块服务层 + """ + # 获取告警消息列表 + @classmethod + async def get_alert_message_services(cls, query_db: AsyncSession, pageNum, pageSize): + t = MessagePageQueryModel() + t.type = '0' + t.page_num = pageNum + t.page_size = pageSize + return await MessageDao.get_message_list(query_db, t, True) + + + # 获取告警消息分布 + @classmethod + async def get_alarm_type_distribution_services(cls, query_db: AsyncSession): + result = await MessageDao.get_alarm_type_distribution(query_db) + # d = dict() + # t = 0 + # for k, v in result.items(): + # d[k] = v + # t += v + # for k, v in d.items(): + # result[k] = round(v/t, 2) + return result + + @classmethod + async def handle_all_message_services(cls,query_db: AsyncSession): + + try: + await MessageDao.handle_all_message(query_db) + await query_db.commit() + return CrudResponseModel(is_success=True, message='处理成功') + except Exception as e: + await query_db.rollback() + raise e + + + + @classmethod + async def get_message_list_services( + cls, query_db: AsyncSession, query_object: MessagePageQueryModel, is_page: bool = False + ): + """ + 获取系统消息列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 系统消息列表信息对象 + """ + message_list_result = await MessageDao.get_message_list(query_db, query_object, is_page) + + return message_list_result + + @classmethod + async def count_message(cls, query_db: AsyncSession,): + """ + 获取系统消息数量service + + :param query_db: orm对象 + :return: + total 总消息数 + unhandled 未处理消息数 + handled 已处理消息数 + """ + total, unhandled, handled = await MessageDao.count_message(query_db) + return {'total': total, 'unhandled': unhandled, 'handled': handled} + + @classmethod + async def add_message_services(cls, query_db: AsyncSession, page_object: MessageModel): + """ + 新增系统消息信息service + + :param query_db: orm对象 + :param page_object: 新增系统消息对象 + :return: 新增系统消息校验结果 + """ + try: + await MessageDao.add_message_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_message_services(cls, query_db: AsyncSession, page_object: EditMessageModel): + """ + 编辑系统消息信息service + + :param query_db: orm对象 + :param page_object: 编辑系统消息对象 + :return: 编辑系统消息校验结果 + """ + edit_message = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'create_by', }) + message_info = await cls.message_detail_services(query_db, page_object.message_id) + if message_info.message_id: + try: + await MessageDao.edit_message_dao(query_db, edit_message) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='系统消息不存在') + + @classmethod + async def delete_message_services(cls, query_db: AsyncSession, page_object: DeleteMessageModel): + """ + 删除系统消息信息service + + :param query_db: orm对象 + :param page_object: 删除系统消息对象 + :return: 删除系统消息校验结果 + """ + if page_object.message_ids: + message_id_list = page_object.message_ids.split(',') + try: + for message_id in message_id_list: + await MessageDao.delete_message_dao(query_db, MessageModel(messageId=message_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入消息主键为空') + + @classmethod + async def message_detail_services(cls, query_db: AsyncSession, message_id: int): + """ + 获取系统消息详细信息service + + :param query_db: orm对象 + :param message_id: 消息主键 + :return: 消息主键对应的信息 + """ + message = await MessageDao.get_message_detail_by_id(query_db, message_id=message_id) + if message: + result = MessageModel(**CamelCaseUtil.transform_result(message)) + else: + result = MessageModel(**dict()) + + return result + + @staticmethod + async def export_message_list_services(message_list: List): + """ + 导出系统消息信息service + + :param message_list: 系统消息信息列表 + :return: 系统消息信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'messageId': '消息主键', + 'type': '类型', + 'status': '状态', + 'detail': '消息描述', + 'createTime': '创建时间', + 'createBy': '创建者', + 'updateTime': '更新时间', + 'updateBy': '更新者', + } + binary_data = ExcelUtil.export_list2excel(message_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/notice_service.py b/ruoyi-fastapi-backend/module_admin/service/notice_service.py new file mode 100644 index 0000000..4671703 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/notice_service.py @@ -0,0 +1,128 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.dao.notice_dao import NoticeDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.notice_vo import DeleteNoticeModel, NoticeModel, NoticePageQueryModel +from utils.common_util import CamelCaseUtil + + +class NoticeService: + """ + 通知公告管理模块服务层 + """ + + @classmethod + async def get_notice_list_services( + cls, query_db: AsyncSession, query_object: NoticePageQueryModel, is_page: bool = True + ): + """ + 获取通知公告列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 通知公告列表信息对象 + """ + notice_list_result = await NoticeDao.get_notice_list(query_db, query_object, is_page) + + return notice_list_result + + @classmethod + async def check_notice_unique_services(cls, query_db: AsyncSession, page_object: NoticeModel): + """ + 校验通知公告是否存在service + + :param query_db: orm对象 + :param page_object: 通知公告对象 + :return: 校验结果 + """ + notice_id = -1 if page_object.notice_id is None else page_object.notice_id + notice = await NoticeDao.get_notice_detail_by_info(query_db, page_object) + if notice and notice.notice_id != notice_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_notice_services(cls, query_db: AsyncSession, page_object: NoticeModel): + """ + 新增通知公告信息service + + :param query_db: orm对象 + :param page_object: 新增通知公告对象 + :return: 新增通知公告校验结果 + """ + if not await cls.check_notice_unique_services(query_db, page_object): + raise ServiceException(message=f'新增通知公告{page_object.notice_title}失败,通知公告已存在') + else: + try: + await NoticeDao.add_notice_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_notice_services(cls, query_db: AsyncSession, page_object: NoticeModel): + """ + 编辑通知公告信息service + + :param query_db: orm对象 + :param page_object: 编辑通知公告对象 + :return: 编辑通知公告校验结果 + """ + edit_notice = page_object.model_dump(exclude_unset=True) + notice_info = await cls.notice_detail_services(query_db, page_object.notice_id) + if notice_info.notice_id: + if not await cls.check_notice_unique_services(query_db, page_object): + raise ServiceException(message=f'修改通知公告{page_object.notice_title}失败,通知公告已存在') + else: + try: + await NoticeDao.edit_notice_dao(query_db, edit_notice) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='通知公告不存在') + + @classmethod + async def delete_notice_services(cls, query_db: AsyncSession, page_object: DeleteNoticeModel): + """ + 删除通知公告信息service + + :param query_db: orm对象 + :param page_object: 删除通知公告对象 + :return: 删除通知公告校验结果 + """ + if page_object.notice_ids: + notice_id_list = page_object.notice_ids.split(',') + try: + for notice_id in notice_id_list: + await NoticeDao.delete_notice_dao(query_db, NoticeModel(noticeId=notice_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入通知公告id为空') + + @classmethod + async def notice_detail_services(cls, query_db: AsyncSession, notice_id: int): + """ + 获取通知公告详细信息service + + :param query_db: orm对象 + :param notice_id: 通知公告id + :return: 通知公告id对应的信息 + """ + notice = await NoticeDao.get_notice_detail_by_id(query_db, notice_id=notice_id) + if notice: + result = NoticeModel(**CamelCaseUtil.transform_result(notice)) + else: + result = NoticeModel(**dict()) + + return result diff --git a/ruoyi-fastapi-backend/module_admin/service/online_service.py b/ruoyi-fastapi-backend/module_admin/service/online_service.py new file mode 100644 index 0000000..56742b0 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/online_service.py @@ -0,0 +1,76 @@ +import jwt +from fastapi import Request +from config.enums import RedisInitKeyConfig +from config.env import JwtConfig +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.online_vo import DeleteOnlineModel, OnlineQueryModel +from utils.common_util import CamelCaseUtil + + +class OnlineService: + """ + 在线用户管理模块服务层 + """ + + @classmethod + async def get_online_list_services(cls, request: Request, query_object: OnlineQueryModel): + """ + 获取在线用户表信息service + + :param request: Request对象 + :param query_object: 查询参数对象 + :return: 在线用户列表信息 + """ + access_token_keys = await request.app.state.redis.keys(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}*') + if not access_token_keys: + access_token_keys = [] + access_token_values_list = [await request.app.state.redis.get(key) for key in access_token_keys] + online_info_list = [] + for item in access_token_values_list: + payload = jwt.decode(item, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm]) + online_dict = dict( + token_id=payload.get('session_id'), + user_name=payload.get('user_name'), + dept_name=payload.get('dept_name'), + ipaddr=payload.get('login_info').get('ipaddr'), + login_location=payload.get('login_info').get('loginLocation'), + browser=payload.get('login_info').get('browser'), + os=payload.get('login_info').get('os'), + login_time=payload.get('login_info').get('loginTime'), + ) + if query_object.user_name and not query_object.ipaddr: + if query_object.user_name == payload.get('user_name'): + online_info_list = [online_dict] + break + elif not query_object.user_name and query_object.ipaddr: + if query_object.ipaddr == payload.get('login_info').get('ipaddr'): + online_info_list = [online_dict] + break + elif query_object.user_name and query_object.ipaddr: + if query_object.user_name == payload.get('user_name') and query_object.ipaddr == payload.get( + 'login_info' + ).get('ipaddr'): + online_info_list = [online_dict] + break + else: + online_info_list.append(online_dict) + + return CamelCaseUtil.transform_result(online_info_list) + + @classmethod + async def delete_online_services(cls, request: Request, page_object: DeleteOnlineModel): + """ + 强退在线用户信息service + + :param request: Request对象 + :param page_object: 强退在线用户对象 + :return: 强退在线用户校验结果 + """ + if page_object.token_ids: + token_id_list = page_object.token_ids.split(',') + for token_id in token_id_list: + await request.app.state.redis.delete(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{token_id}') + return CrudResponseModel(is_success=True, message='强退成功') + else: + raise ServiceException(message='传入session_id为空') diff --git a/ruoyi-fastapi-backend/module_admin/service/post_service.py b/ruoyi-fastapi-backend/module_admin/service/post_service.py new file mode 100644 index 0000000..9338a9f --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/post_service.py @@ -0,0 +1,183 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.dao.post_dao import PostDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.post_vo import DeletePostModel, PostModel, PostPageQueryModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class PostService: + """ + 岗位管理模块服务层 + """ + + @classmethod + async def get_post_list_services( + cls, query_db: AsyncSession, query_object: PostPageQueryModel, is_page: bool = False + ): + """ + 获取岗位列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 岗位列表信息对象 + """ + post_list_result = await PostDao.get_post_list(query_db, query_object, is_page) + + return post_list_result + + @classmethod + async def check_post_name_unique_services(cls, query_db: AsyncSession, page_object: PostModel): + """ + 检查岗位名称是否唯一service + + :param query_db: orm对象 + :param page_object: 岗位对象 + :return: 校验结果 + """ + post_id = -1 if page_object.post_id is None else page_object.post_id + post = await PostDao.get_post_detail_by_info(query_db, PostModel(postName=page_object.post_name)) + if post and post.post_id != post_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def check_post_code_unique_services(cls, query_db: AsyncSession, page_object: PostModel): + """ + 检查岗位编码是否唯一service + + :param query_db: orm对象 + :param page_object: 岗位对象 + :return: 校验结果 + """ + post_id = -1 if page_object.post_id is None else page_object.post_id + post = await PostDao.get_post_detail_by_info(query_db, PostModel(postCode=page_object.post_code)) + if post and post.post_id != post_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_post_services(cls, query_db: AsyncSession, page_object: PostModel): + """ + 新增岗位信息service + + :param query_db: orm对象 + :param page_object: 新增岗位对象 + :return: 新增岗位校验结果 + """ + if not await cls.check_post_name_unique_services(query_db, page_object): + raise ServiceException(message=f'新增岗位{page_object.post_name}失败,岗位名称已存在') + elif not await cls.check_post_code_unique_services(query_db, page_object): + raise ServiceException(message=f'新增岗位{page_object.post_name}失败,岗位编码已存在') + else: + try: + await PostDao.add_post_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_post_services(cls, query_db: AsyncSession, page_object: PostModel): + """ + 编辑岗位信息service + + :param query_db: orm对象 + :param page_object: 编辑岗位对象 + :return: 编辑岗位校验结果 + """ + edit_post = page_object.model_dump(exclude_unset=True) + post_info = await cls.post_detail_services(query_db, page_object.post_id) + if post_info.post_id: + if not await cls.check_post_name_unique_services(query_db, page_object): + raise ServiceException(message=f'修改岗位{page_object.post_name}失败,岗位名称已存在') + elif not await cls.check_post_code_unique_services(query_db, page_object): + raise ServiceException(message=f'修改岗位{page_object.post_name}失败,岗位编码已存在') + else: + try: + await PostDao.edit_post_dao(query_db, edit_post) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='岗位不存在') + + @classmethod + async def delete_post_services(cls, query_db: AsyncSession, page_object: DeletePostModel): + """ + 删除岗位信息service + + :param query_db: orm对象 + :param page_object: 删除岗位对象 + :return: 删除岗位校验结果 + """ + if page_object.post_ids: + post_id_list = page_object.post_ids.split(',') + try: + for post_id in post_id_list: + post = await cls.post_detail_services(query_db, int(post_id)) + if (await PostDao.count_user_post_dao(query_db, int(post_id))) > 0: + raise ServiceException(message=f'{post.post_name}已分配,不能删除') + await PostDao.delete_post_dao(query_db, PostModel(postId=post_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入岗位id为空') + + @classmethod + async def post_detail_services(cls, query_db: AsyncSession, post_id: int): + """ + 获取岗位详细信息service + + :param query_db: orm对象 + :param post_id: 岗位id + :return: 岗位id对应的信息 + """ + post = await PostDao.get_post_detail_by_id(query_db, post_id=post_id) + if post: + result = PostModel(**CamelCaseUtil.transform_result(post)) + else: + result = PostModel(**dict()) + + return result + + @staticmethod + async def export_post_list_services(post_list: List): + """ + 导出岗位信息service + + :param post_list: 岗位信息列表 + :return: 岗位信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'postId': '岗位编号', + 'postCode': '岗位编码', + 'postName': '岗位名称', + 'postSort': '显示顺序', + 'status': '状态', + 'createBy': '创建者', + 'createTime': '创建时间', + 'updateBy': '更新者', + 'updateTime': '更新时间', + 'remark': '备注', + } + + for item in post_list: + if item.get('status') == '0': + item['status'] = '正常' + else: + item['status'] = '停用' + binary_data = ExcelUtil.export_list2excel(post_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/ragflow_service.py b/ruoyi-fastapi-backend/module_admin/service/ragflow_service.py new file mode 100644 index 0000000..1f24152 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/ragflow_service.py @@ -0,0 +1,181 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.vo.ragflow_vo import RagflowListQueryModel, ListDocumentsQueryModel, UpdateFileModel, DeleteFileModel, CreateDatasetModel, DocumentIdsModel, UpdateChatAssistantModel \ + ,CreateSessionWithChatModel, ConverseWithChatAssistantModel +from utils.ragflow_client_manager import get_ragflow_client + +class RAGFlowService: + """ + RAGFlow服务 + """ + + # 获取数据集列表 + @classmethod + async def get_ragflow_dataset_list_services(cls, query_db: AsyncSession, rage_flow_query: RagflowListQueryModel): + """ + 获取数据集列表 + """ + + client = await get_ragflow_client() + result = await client.list_datasets(**(rage_flow_query.model_dump())) + + # 获取分页数据 + return result + # 创建数据集 + @classmethod + async def create_dataset_services(cls, create_dataset_params: CreateDatasetModel): + """创建数据集 + + Args: + create_dataset_params (CreateDatasetModel): 创建参数 + + Returns: + _type_: _description_ + """ + client = await get_ragflow_client() + result = await client.create_dataset( + **(create_dataset_params.model_dump()) + ) + + return result + + # 更新数据集 + @classmethod + async def update_dataset_services( + cls, + dataset_id: str, + update_dataset_params: CreateDatasetModel, + ): + """更新数据集信息 + + Args: + dataset_id (str): 数据集id + update_dataset_params (CreateDatasetModel): 更新参数 + + Returns: + _type_: _description_ + """ + client = await get_ragflow_client() + result = await client.update_dataset( + dataset_id=dataset_id, **(update_dataset_params.model_dump()) + ) + + return result + + + # 获取数据集中文档列表 + @classmethod + async def list_documents_services( + cls, + query_db: AsyncSession, + dataset_id: str, + list_documents_query: ListDocumentsQueryModel, + ): + client = await get_ragflow_client() + result = await client.list_documents(dataset_id=dataset_id, **(list_documents_query.model_dump())) + + return result + + # 上传文档到数据集 + @classmethod + async def upload_file_dataset_services( + cls, + dataset_id: str, + files, + ): + client = await get_ragflow_client() + result = await client.upload_documents_bytes(dataset_id=dataset_id, file_bytes=files) + + return result + # 开始解析文档 + @classmethod + async def parse_documents_services( + cls, + dataset_id: str, + parse_params: DocumentIdsModel, + ): + client = await get_ragflow_client() + result = await client.parse_documents(dataset_id=dataset_id, document_ids=parse_params.documnet_ids) + + return result + + # 停止解析文档 + @classmethod + async def stop_parse_documents_services( + cls, + dataset_id: str, + parse_params: DocumentIdsModel, + ): + client = await get_ragflow_client() + result = await client.stop_parsing_documents(dataset_id=dataset_id, document_ids=parse_params.documnet_ids) + + return result + + + # 更新文档内容 + @classmethod + async def update_file_dataset_services( + cls, + dataset_id: str, + document_id: str, + update_params: UpdateFileModel, + ): + client = await get_ragflow_client() + result = await client.update_document(dataset_id=dataset_id, document_id=document_id, **(update_params.model_dump())) + + return result + + # 删除文档 + @classmethod + async def delete_file_services( + cls, + dataset_id: str, + delete_params: DeleteFileModel, + ): + client = await get_ragflow_client() + result = await client.delete_documents(dataset_id=dataset_id, **(delete_params.model_dump())) + return result + + # 删除数据集 + @classmethod + async def delete_datasets_services( + cls, + delete_params: DeleteFileModel, + ): + client = await get_ragflow_client() + result = await client.delete_datasets(**(delete_params.model_dump())) + return result + + + # 查看聊天助手列表 + @classmethod + async def get_chat_assistant_list_services( + cls, + query_params: RagflowListQueryModel, + ): + client = await get_ragflow_client() + result = await client.list_chat_assistants(**(query_params.model_dump())) + return result + + # 修改聊天助手 + @classmethod + async def update_chat_assistant_services(cls, update_params: UpdateChatAssistantModel): + client = await get_ragflow_client() + result = await client.update_chat_assistant(**(update_params.model_dump())) + return result + + # 创建助手会话 + @classmethod + async def create_session_with_chat_services(cls, create_params: CreateSessionWithChatModel): + client = await get_ragflow_client() + result = await client.create_session_with_chat(**(create_params.model_dump())) + return result + + + # 与助手聊天 + @classmethod + async def converse_with_chat_assistant_services(cls, converse_params: ConverseWithChatAssistantModel): + client = await get_ragflow_client() + return await client.converse_with_chat_assistant(**(converse_params.model_dump())) + + + diff --git a/ruoyi-fastapi-backend/module_admin/service/robot_action_service.py b/ruoyi-fastapi-backend/module_admin/service/robot_action_service.py new file mode 100644 index 0000000..254b974 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/robot_action_service.py @@ -0,0 +1,132 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.robot_action_dao import Robot_actionDao +from module_admin.entity.vo.robot_action_vo import DeleteRobot_actionModel, Robot_actionModel, Robot_actionPageQueryModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class Robot_actionService: + """ + 机器人动作模块服务层 + """ + + @classmethod + async def get_robot_action_list_services( + cls, query_db: AsyncSession, query_object: Robot_actionPageQueryModel, is_page: bool = False + ): + """ + 获取机器人动作列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 机器人动作列表信息对象 + """ + robot_action_list_result = await Robot_actionDao.get_robot_action_list(query_db, query_object, is_page) + + return robot_action_list_result + + + @classmethod + async def add_robot_action_services(cls, query_db: AsyncSession, page_object: Robot_actionModel): + """ + 新增机器人动作信息service + + :param query_db: orm对象 + :param page_object: 新增机器人动作对象 + :return: 新增机器人动作校验结果 + """ + try: + await Robot_actionDao.add_robot_action_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_robot_action_services(cls, query_db: AsyncSession, page_object: Robot_actionModel): + """ + 编辑机器人动作信息service + + :param query_db: orm对象 + :param page_object: 编辑机器人动作对象 + :return: 编辑机器人动作校验结果 + """ + edit_robot_action = page_object.model_dump(exclude_unset=True, exclude={}) + robot_action_info = await cls.robot_action_detail_services(query_db, page_object.id) + if robot_action_info.id: + try: + await Robot_actionDao.edit_robot_action_dao(query_db, edit_robot_action) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='机器人动作不存在') + + @classmethod + async def delete_robot_action_services(cls, query_db: AsyncSession, page_object: DeleteRobot_actionModel): + """ + 删除机器人动作信息service + + :param query_db: orm对象 + :param page_object: 删除机器人动作对象 + :return: 删除机器人动作校验结果 + """ + if page_object.ids: + id_list = page_object.ids.split(',') + try: + for id in id_list: + await Robot_actionDao.delete_robot_action_dao(query_db, Robot_actionModel(id=id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入主键 自增为空') + + @classmethod + async def robot_action_detail_services(cls, query_db: AsyncSession, id: int): + """ + 获取机器人动作详细信息service + + :param query_db: orm对象 + :param id: 主键 自增 + :return: 主键 自增对应的信息 + """ + robot_action = await Robot_actionDao.get_robot_action_detail_by_id(query_db, id=id) + if robot_action: + result = Robot_actionModel(**CamelCaseUtil.transform_result(robot_action)) + else: + result = Robot_actionModel(**dict()) + + return result + + @staticmethod + async def export_robot_action_list_services(robot_action_list: List): + """ + 导出机器人动作信息service + + :param robot_action_list: 机器人动作信息列表 + :return: 机器人动作信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'id': '主键 自增', + 'name': '动作名', + 'description': '描述', + 'intervalMin': '最小时间间隔 单位s', + 'intervalMax': '最大时间间隔 单位s', + 'status': '动作状态 0 未启用, 1已启用', + 'updateTime': '修改时间', + } + binary_data = ExcelUtil.export_list2excel(robot_action_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/robot_role_pairing_service.py b/ruoyi-fastapi-backend/module_admin/service/robot_role_pairing_service.py new file mode 100644 index 0000000..428f26d --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/robot_role_pairing_service.py @@ -0,0 +1,187 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.robot_role_pairing_dao import PairingDao +from module_admin.dao.explanation_style_robot_pair_dao import Explanation_style_robot_pairDao +from module_admin.entity.vo.robot_role_pairing_vo import DeletePairingModel, PairingModel, PairingPageQueryModel, \ + ChangeRobotRoleModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class PairingService: + """ + 角色-机器人-映射模块服务层 + """ + + @classmethod + async def change_robot_role_services(cls, query_db: AsyncSession,change_pairing: ChangeRobotRoleModel): + try: + # 现修改原本角色status为0 + print("xxxxxxxxxxxxxxx", change_pairing.robot_id) + await PairingDao.edit_pairing_status(query_db, change_pairing.robot_id, None, '0') + # 再修改新的角色status为1 + print("xxxxxxxxxxxxxxx", change_pairing.pairing_id) + await PairingDao.edit_pairing_status(query_db, None, change_pairing.pairing_id, '1') + + await query_db.commit() + + except Exception as e: + await query_db.rollback() + raise e + @classmethod + async def get_role_pairing_list_services(cls, query_db: AsyncSession, robot_id: int): + pairing_list = await PairingDao.get_role_pairing_list(query_db, robot_id) + + # style_list = list() + style = None + # print(pairing_list) + for pairing in pairing_list: + + if pairing["status"] == '1': + style = await Explanation_style_robot_pairDao.get_robot_role_style(query_db, robot_id, pairing["robot_role_id"]) + # style_list.append(style) + + return pairing_list, style + + @classmethod + async def get_role_pairing_list_services_v1(cls, query_db: AsyncSession, robot_id: int) -> list: + """ 获取机器人角色对齐风格列表 + + Args: + query_db (AsyncSession): db + robot_id (int): 机器人id + + Returns: + list: 机器人角色风格对齐列表 + """ + + pairing_list = await PairingDao.get_role_pairing_list(query_db, robot_id) + + style_list = list() + + for pairing in pairing_list: + + style = await Explanation_style_robot_pairDao.get_robot_role_style( + query_db, robot_id, pairing["robot_role_id"] + ) + style_list.append(style) + + return pairing_list, style_list + + @classmethod + async def get_pairing_list_services( + cls, query_db: AsyncSession, query_object: PairingPageQueryModel, is_page: bool = False + ): + """ + 获取角色-机器人-映射列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 角色-机器人-映射列表信息对象 + """ + pairing_list_result = await PairingDao.get_pairing_list(query_db, query_object, is_page) + + return pairing_list_result + + @classmethod + async def add_pairing_services(cls, query_db: AsyncSession, page_object: PairingModel): + """ + 新增角色-机器人-映射信息service + + :param query_db: orm对象 + :param page_object: 新增角色-机器人-映射对象 + :return: 新增角色-机器人-映射校验结果 + """ + try: + await PairingDao.add_pairing_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_pairing_services(cls, query_db: AsyncSession, page_object: PairingModel): + """ + 编辑角色-机器人-映射信息service + + :param query_db: orm对象 + :param page_object: 编辑角色-机器人-映射对象 + :return: 编辑角色-机器人-映射校验结果 + """ + edit_pairing = page_object.model_dump(exclude_unset=True, exclude={}) + pairing_info = await cls.pairing_detail_services(query_db, page_object.pairing_id) + if pairing_info.pairing_id: + try: + await PairingDao.edit_pairing_dao(query_db, edit_pairing) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='角色-机器人-映射不存在') + + @classmethod + async def delete_pairing_services(cls, query_db: AsyncSession, page_object: DeletePairingModel): + """ + 删除角色-机器人-映射信息service + + :param query_db: orm对象 + :param page_object: 删除角色-机器人-映射对象 + :return: 删除角色-机器人-映射校验结果 + """ + if page_object.pairing_ids: + pairing_id_list = page_object.pairing_ids.split(',') + try: + for pairing_id in pairing_id_list: + await PairingDao.delete_pairing_dao(query_db, PairingModel(pairingId=pairing_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入主键ID为空') + + @classmethod + async def pairing_detail_services(cls, query_db: AsyncSession, pairing_id: int): + """ + 获取角色-机器人-映射详细信息service + + :param query_db: orm对象 + :param pairing_id: 主键ID + :return: 主键ID对应的信息 + """ + pairing = await PairingDao.get_pairing_detail_by_id(query_db, pairing_id=pairing_id) + if pairing: + result = PairingModel(**CamelCaseUtil.transform_result(pairing)) + else: + result = PairingModel(**dict()) + + return result + + @staticmethod + async def export_pairing_list_services(pairing_list: List): + """ + 导出角色-机器人-映射信息service + + :param pairing_list: 角色-机器人-映射信息列表 + :return: 角色-机器人-映射信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'pairingId': '主键ID', + 'robotRoleId': '机器人角色ID', + 'robotId': '机器人ID', + 'status': '使用情况(0停用,1使用)', + 'updateTime': '更新时间', + 'updateBy': '修改者', + } + binary_data = ExcelUtil.export_list2excel(pairing_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/robot_role_service.py b/ruoyi-fastapi-backend/module_admin/service/robot_role_service.py new file mode 100644 index 0000000..3f64aa6 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/robot_role_service.py @@ -0,0 +1,133 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.role_dao import RoleDao +from module_admin.entity.vo.role_vo import DeleteRoleModel, RoleModel, RolePageQueryModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class RoleService: + """ + 系统角色模块服务层 + """ + + @classmethod + async def get_role_list_services( + cls, query_db: AsyncSession, query_object: RolePageQueryModel, is_page: bool = False + ): + """ + 获取系统角色列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 系统角色列表信息对象 + """ + role_list_result = await RoleDao.get_role_list(query_db, query_object, is_page) + + return role_list_result + + + @classmethod + async def add_role_services(cls, query_db: AsyncSession, page_object: RoleModel): + """ + 新增系统角色信息service + + :param query_db: orm对象 + :param page_object: 新增系统角色对象 + :return: 新增系统角色校验结果 + """ + try: + await RoleDao.add_role_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_role_services(cls, query_db: AsyncSession, page_object: RoleModel): + """ + 编辑系统角色信息service + + :param query_db: orm对象 + :param page_object: 编辑系统角色对象 + :return: 编辑系统角色校验结果 + """ + edit_role = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'create_by', }) + role_info = await cls.role_detail_services(query_db, page_object.robot_role_id) + if role_info.robot_role_id: + try: + await RoleDao.edit_role_dao(query_db, edit_role) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='系统角色不存在') + + @classmethod + async def delete_role_services(cls, query_db: AsyncSession, page_object: DeleteRoleModel): + """ + 删除系统角色信息service + + :param query_db: orm对象 + :param page_object: 删除系统角色对象 + :return: 删除系统角色校验结果 + """ + if page_object.robot_role_ids: + robot_role_id_list = page_object.robot_role_ids.split(',') + try: + for robot_role_id in robot_role_id_list: + await RoleDao.delete_role_dao(query_db, RoleModel(robotRoleId=robot_role_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入机器人角色ID为空') + + @classmethod + async def role_detail_services(cls, query_db: AsyncSession, robot_role_id: int): + """ + 获取系统角色详细信息service + + :param query_db: orm对象 + :param robot_role_id: 机器人角色ID + :return: 机器人角色ID对应的信息 + """ + role = await RoleDao.get_role_detail_by_id(query_db, robot_role_id=robot_role_id) + if role: + result = RoleModel(**CamelCaseUtil.transform_result(role)) + else: + result = RoleModel(**dict()) + + return result + + @staticmethod + async def export_role_list_services(role_list: List): + """ + 导出系统角色信息service + + :param role_list: 系统角色信息列表 + :return: 系统角色信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'robotRoleId': '机器人角色ID', + 'rolename': '角色名称', + 'detail': '角色介绍', + 'createTime': '创建时间', + 'createBy': '创建者', + 'updateTime': '修改时间', + 'updateBy': '修改者', + 'remark': '备注', + } + binary_data = ExcelUtil.export_list2excel(role_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/role_service.py b/ruoyi-fastapi-backend/module_admin/service/role_service.py new file mode 100644 index 0000000..4b633de --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/role_service.py @@ -0,0 +1,356 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.role_vo import ( + AddRoleModel, + DeleteRoleModel, + RoleDeptModel, + RoleDeptQueryModel, + RoleMenuModel, + RoleModel, + RolePageQueryModel, +) +from module_admin.entity.vo.user_vo import UserInfoModel, UserRolePageQueryModel +from module_admin.dao.role_dao import RoleDao +from module_admin.dao.user_dao import UserDao +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil +from utils.page_util import PageResponseModel + + +class RoleService: + """ + 角色管理模块服务层 + """ + + @classmethod + async def get_role_select_option_services(cls, query_db: AsyncSession): + """ + 获取角色列表不分页信息service + + :param query_db: orm对象 + :return: 角色列表不分页信息对象 + """ + role_list_result = await RoleDao.get_role_select_option_dao(query_db) + + return CamelCaseUtil.transform_result(role_list_result) + + @classmethod + async def get_role_dept_tree_services(cls, query_db: AsyncSession, role_id: int): + """ + 根据角色id获取部门树信息service + + :param query_db: orm对象 + :param role_id: 角色id + :return: 当前角色id的部门树信息对象 + """ + role = await cls.role_detail_services(query_db, role_id) + role_dept_list = await RoleDao.get_role_dept_dao(query_db, role) + checked_keys = [row.dept_id for row in role_dept_list] + result = RoleDeptQueryModel(checkedKeys=checked_keys) + + return result + + @classmethod + async def get_role_list_services( + cls, query_db: AsyncSession, query_object: RolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): + """ + 获取角色列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :param is_page: 是否开启分页 + :return: 角色列表信息对象 + """ + role_list_result = await RoleDao.get_role_list(query_db, query_object, data_scope_sql, is_page) + + return role_list_result + + @classmethod + async def check_role_allowed_services(cls, check_role: RoleModel): + """ + 校验角色是否允许操作service + + :param check_role: 角色信息 + :return: 校验结果 + """ + if check_role.admin: + raise ServiceException(message='不允许操作超级管理员角色') + else: + return CrudResponseModel(is_success=True, message='校验通过') + + @classmethod + async def check_role_data_scope_services(cls, query_db: AsyncSession, role_ids: str, data_scope_sql: str): + """ + 校验角色是否有数据权限service + + :param query_db: orm对象 + :param role_ids: 角色id + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 校验结果 + """ + role_id_list = role_ids.split(',') if role_ids else [] + if role_id_list: + for role_id in role_id_list: + roles = await RoleDao.get_role_list( + query_db, RolePageQueryModel(roleId=int(role_id)), data_scope_sql, is_page=False + ) + if roles: + continue + else: + raise ServiceException(message='没有权限访问角色数据') + + @classmethod + async def check_role_name_unique_services(cls, query_db: AsyncSession, page_object: RoleModel): + """ + 校验角色名称是否唯一service + + :param query_db: orm对象 + :param page_object: 角色对象 + :return: 校验结果 + """ + role_id = -1 if page_object.role_id is None else page_object.role_id + role = await RoleDao.get_role_by_info(query_db, RoleModel(roleName=page_object.role_name)) + if role and role.role_id != role_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def check_role_key_unique_services(cls, query_db: AsyncSession, page_object: RoleModel): + """ + 校验角色权限字符是否唯一service + + :param query_db: orm对象 + :param page_object: 角色对象 + :return: 校验结果 + """ + role_id = -1 if page_object.role_id is None else page_object.role_id + role = await RoleDao.get_role_by_info(query_db, RoleModel(roleKey=page_object.role_key)) + if role and role.role_id != role_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_role_services(cls, query_db: AsyncSession, page_object: AddRoleModel): + """ + 新增角色信息service + + :param query_db: orm对象 + :param page_object: 新增角色对象 + :return: 新增角色校验结果 + """ + add_role = RoleModel(**page_object.model_dump(by_alias=True)) + if not await cls.check_role_name_unique_services(query_db, page_object): + raise ServiceException(message=f'新增角色{page_object.role_name}失败,角色名称已存在') + elif not await cls.check_role_key_unique_services(query_db, page_object): + raise ServiceException(message=f'新增角色{page_object.role_name}失败,角色权限已存在') + else: + try: + add_result = await RoleDao.add_role_dao(query_db, add_role) + role_id = add_result.role_id + if page_object.menu_ids: + for menu in page_object.menu_ids: + await RoleDao.add_role_menu_dao(query_db, RoleMenuModel(roleId=role_id, menuId=menu)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_role_services(cls, query_db: AsyncSession, page_object: AddRoleModel): + """ + 编辑角色信息service + + :param query_db: orm对象 + :param page_object: 编辑角色对象 + :return: 编辑角色校验结果 + """ + edit_role = page_object.model_dump(exclude_unset=True, exclude={'admin'}) + if page_object.type != 'status': + del edit_role['menu_ids'] + if page_object.type == 'status': + del edit_role['type'] + role_info = await cls.role_detail_services(query_db, edit_role.get('role_id')) + if role_info: + if page_object.type != 'status': + if not await cls.check_role_name_unique_services(query_db, page_object): + raise ServiceException(message=f'修改角色{page_object.role_name}失败,角色名称已存在') + elif not await cls.check_role_key_unique_services(query_db, page_object): + raise ServiceException(message=f'修改角色{page_object.role_name}失败,角色权限已存在') + try: + await RoleDao.edit_role_dao(query_db, edit_role) + if page_object.type != 'status': + await RoleDao.delete_role_menu_dao(query_db, RoleMenuModel(roleId=page_object.role_id)) + if page_object.menu_ids: + for menu in page_object.menu_ids: + await RoleDao.add_role_menu_dao( + query_db, RoleMenuModel(roleId=page_object.role_id, menuId=menu) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='角色不存在') + + @classmethod + async def role_datascope_services(cls, query_db: AsyncSession, page_object: AddRoleModel): + """ + 分配角色数据权限service + + :param query_db: orm对象 + :param page_object: 角色数据权限对象 + :return: 分配角色数据权限结果 + """ + edit_role = page_object.model_dump(exclude_unset=True, exclude={'admin', 'dept_ids'}) + role_info = await cls.role_detail_services(query_db, page_object.role_id) + if role_info.role_id: + try: + await RoleDao.edit_role_dao(query_db, edit_role) + await RoleDao.delete_role_dept_dao(query_db, RoleDeptModel(roleId=page_object.role_id)) + if page_object.dept_ids and page_object.data_scope == '2': + for dept in page_object.dept_ids: + await RoleDao.add_role_dept_dao( + query_db, RoleDeptModel(roleId=page_object.role_id, deptId=dept) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='分配成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='角色不存在') + + @classmethod + async def delete_role_services(cls, query_db: AsyncSession, page_object: DeleteRoleModel): + """ + 删除角色信息service + + :param query_db: orm对象 + :param page_object: 删除角色对象 + :return: 删除角色校验结果 + """ + if page_object.role_ids: + role_id_list = page_object.role_ids.split(',') + try: + for role_id in role_id_list: + role = await cls.role_detail_services(query_db, int(role_id)) + if (await RoleDao.count_user_role_dao(query_db, int(role_id))) > 0: + raise ServiceException(message=f'角色{role.role_name}已分配,不能删除') + role_id_dict = dict( + roleId=role_id, updateBy=page_object.update_by, updateTime=page_object.update_time + ) + await RoleDao.delete_role_menu_dao(query_db, RoleMenuModel(**role_id_dict)) + await RoleDao.delete_role_dept_dao(query_db, RoleDeptModel(**role_id_dict)) + await RoleDao.delete_role_dao(query_db, RoleModel(**role_id_dict)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入角色id为空') + + @classmethod + async def role_detail_services(cls, query_db: AsyncSession, role_id: int): + """ + 获取角色详细信息service + + :param query_db: orm对象 + :param role_id: 角色id + :return: 角色id对应的信息 + """ + role = await RoleDao.get_role_detail_by_id(query_db, role_id=role_id) + if role: + result = RoleModel(**CamelCaseUtil.transform_result(role)) + else: + result = RoleModel(**dict()) + + return result + + @staticmethod + async def export_role_list_services(role_list: List): + """ + 导出角色列表信息service + + :param role_list: 角色信息列表 + :return: 角色列表信息对象 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'roleId': '角色编号', + 'roleName': '角色名称', + 'roleKey': '权限字符', + 'roleSort': '显示顺序', + 'status': '状态', + 'createBy': '创建者', + 'createTime': '创建时间', + 'updateBy': '更新者', + 'updateTime': '更新时间', + 'remark': '备注', + } + + for item in role_list: + if item.get('status') == '0': + item['status'] = '正常' + else: + item['status'] = '停用' + binary_data = ExcelUtil.export_list2excel(role_list, mapping_dict) + + return binary_data + + @classmethod + async def get_role_user_allocated_list_services( + cls, query_db: AsyncSession, page_object: UserRolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): + """ + 根据角色id获取已分配用户列表 + + :param query_db: orm对象 + :param page_object: 用户关联角色对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :param is_page: 是否开启分页 + :return: 已分配用户列表 + """ + query_user_list = await UserDao.get_user_role_allocated_list_by_role_id( + query_db, page_object, data_scope_sql, is_page + ) + allocated_list = PageResponseModel( + **{ + **query_user_list.model_dump(by_alias=True), + 'rows': [UserInfoModel(**row) for row in query_user_list.rows], + } + ) + + return allocated_list + + @classmethod + async def get_role_user_unallocated_list_services( + cls, query_db: AsyncSession, page_object: UserRolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): + """ + 根据角色id获取未分配用户列表 + + :param query_db: orm对象 + :param page_object: 用户关联角色对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :param is_page: 是否开启分页 + :return: 未分配用户列表 + """ + query_user_list = await UserDao.get_user_role_unallocated_list_by_role_id( + query_db, page_object, data_scope_sql, is_page + ) + unallocated_list = PageResponseModel( + **{ + **query_user_list.model_dump(by_alias=True), + 'rows': [UserInfoModel(**row) for row in query_user_list.rows], + } + ) + + return unallocated_list diff --git a/ruoyi-fastapi-backend/module_admin/service/server_service.py b/ruoyi-fastapi-backend/module_admin/service/server_service.py new file mode 100644 index 0000000..2f9a53f --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/server_service.py @@ -0,0 +1,96 @@ +import os +import platform +import psutil +import socket +import time +from module_admin.entity.vo.server_vo import CpuInfo, MemoryInfo, PyInfo, ServerMonitorModel, SysFiles, SysInfo +from utils.common_util import bytes2human + + +class ServerService: + """ + 服务监控模块服务层 + """ + + @staticmethod + async def get_server_monitor_info(): + # CPU信息 + # 获取CPU总核心数 + cpu_num = psutil.cpu_count(logical=True) + cpu_usage_percent = psutil.cpu_times_percent() + cpu_used = cpu_usage_percent.user + cpu_sys = cpu_usage_percent.system + cpu_free = cpu_usage_percent.idle + cpu = CpuInfo(cpuNum=cpu_num, used=cpu_used, sys=cpu_sys, free=cpu_free) + + # 内存信息 + memory_info = psutil.virtual_memory() + memory_total = bytes2human(memory_info.total) + memory_used = bytes2human(memory_info.used) + memory_free = bytes2human(memory_info.free) + memory_usage = memory_info.percent + mem = MemoryInfo(total=memory_total, used=memory_used, free=memory_free, usage=memory_usage) + + # 主机信息 + # 获取主机名 + hostname = socket.gethostname() + # 获取IP + computer_ip = socket.gethostbyname(hostname) + os_name = platform.platform() + computer_name = platform.node() + os_arch = platform.machine() + user_dir = os.path.abspath(os.getcwd()) + sys = SysInfo( + computerIp=computer_ip, computerName=computer_name, osArch=os_arch, osName=os_name, userDir=user_dir + ) + + # python解释器信息 + current_pid = os.getpid() + current_process = psutil.Process(current_pid) + python_name = current_process.name() + python_version = platform.python_version() + python_home = current_process.exe() + start_time_stamp = current_process.create_time() + start_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(start_time_stamp)) + current_time_stamp = time.time() + difference = current_time_stamp - start_time_stamp + # 将时间差转换为天、小时和分钟数 + days = int(difference // (24 * 60 * 60)) # 每天的秒数 + hours = int((difference % (24 * 60 * 60)) // (60 * 60)) # 每小时的秒数 + minutes = int((difference % (60 * 60)) // 60) # 每分钟的秒数 + run_time = f'{days}天{hours}小时{minutes}分钟' + # 获取当前Python程序的pid + pid = os.getpid() + # 获取该进程的内存信息 + current_process_memory_info = psutil.Process(pid).memory_info() + py = PyInfo( + name=python_name, + version=python_version, + startTime=start_time, + runTime=run_time, + home=python_home, + total=bytes2human(memory_info.available), + used=bytes2human(current_process_memory_info.rss), + free=bytes2human(memory_info.available - current_process_memory_info.rss), + usage=round((current_process_memory_info.rss / memory_info.available) * 100, 2), + ) + + # 磁盘信息 + io = psutil.disk_partitions() + sys_files = [] + for i in io: + o = psutil.disk_usage(i.device) + disk_data = SysFiles( + dirName=i.device, + sysTypeName=i.fstype, + typeName='本地固定磁盘(' + i.mountpoint.replace('\\', '') + ')', + total=bytes2human(o.total), + used=bytes2human(o.used), + free=bytes2human(o.free), + usage=f'{psutil.disk_usage(i.device).percent}%', + ) + sys_files.append(disk_data) + + result = ServerMonitorModel(cpu=cpu, mem=mem, sys=sys, py=py, sysFiles=sys_files) + + return result diff --git a/ruoyi-fastapi-backend/module_admin/service/sys_alert_service.py b/ruoyi-fastapi-backend/module_admin/service/sys_alert_service.py new file mode 100644 index 0000000..aa4e6f0 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/sys_alert_service.py @@ -0,0 +1,133 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.sys_alert_dao import Sys_alertDao +from module_admin.entity.vo.sys_alert_vo import DeleteSys_alertModel, Sys_alertModel, Sys_alertPageQueryModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class Sys_alertService: + """ + 系统告警模块服务层 + """ + + @classmethod + async def get_sys_alert_list_services( + cls, query_db: AsyncSession, query_object: Sys_alertPageQueryModel, is_page: bool = False + ): + """ + 获取系统告警列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 系统告警列表信息对象 + """ + sys_alert_list_result = await Sys_alertDao.get_sys_alert_list(query_db, query_object, is_page) + + return sys_alert_list_result + + + @classmethod + async def add_sys_alert_services(cls, query_db: AsyncSession, page_object: Sys_alertModel): + """ + 新增系统告警信息service + + :param query_db: orm对象 + :param page_object: 新增系统告警对象 + :return: 新增系统告警校验结果 + """ + try: + await Sys_alertDao.add_sys_alert_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_sys_alert_services(cls, query_db: AsyncSession, page_object: Sys_alertModel): + """ + 编辑系统告警信息service + + :param query_db: orm对象 + :param page_object: 编辑系统告警对象 + :return: 编辑系统告警校验结果 + """ + edit_sys_alert = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'create_by', }) + sys_alert_info = await cls.sys_alert_detail_services(query_db, page_object.id) + if sys_alert_info.id: + try: + await Sys_alertDao.edit_sys_alert_dao(query_db, edit_sys_alert) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='系统告警不存在') + + @classmethod + async def delete_sys_alert_services(cls, query_db: AsyncSession, page_object: DeleteSys_alertModel): + """ + 删除系统告警信息service + + :param query_db: orm对象 + :param page_object: 删除系统告警对象 + :return: 删除系统告警校验结果 + """ + if page_object.ids: + id_list = page_object.ids.split(',') + try: + for id in id_list: + await Sys_alertDao.delete_sys_alert_dao(query_db, Sys_alertModel(id=id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入主键 自增为空') + + @classmethod + async def sys_alert_detail_services(cls, query_db: AsyncSession, id: int): + """ + 获取系统告警详细信息service + + :param query_db: orm对象 + :param id: 主键 自增 + :return: 主键 自增对应的信息 + """ + sys_alert = await Sys_alertDao.get_sys_alert_detail_by_id(query_db, id=id) + if sys_alert: + result = Sys_alertModel(**CamelCaseUtil.transform_result(sys_alert)) + else: + result = Sys_alertModel(**dict()) + + return result + + @staticmethod + async def export_sys_alert_list_services(sys_alert_list: List): + """ + 导出系统告警信息service + + :param sys_alert_list: 系统告警信息列表 + :return: 系统告警信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'id': '主键 自增', + 'detail': '告警描述', + 'type': '告警类型', + 'status': '告警状态, 0 未处理, 1 已处理', + 'createTime': '创建时间', + 'createBy': '创建者', + 'updateTime': '更新时间', + 'updateBy': '更新者', + } + binary_data = ExcelUtil.export_list2excel(sys_alert_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/sys_statistics_service.py b/ruoyi-fastapi-backend/module_admin/service/sys_statistics_service.py new file mode 100644 index 0000000..8b6ef7f --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/sys_statistics_service.py @@ -0,0 +1,248 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.sys_statistics_dao import Sys_statisticsDao +from module_admin.entity.vo.sys_statistics_vo import DeleteSys_statisticsModel, Sys_statisticsModel, Sys_statisticsPageQueryModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil +from datetime import datetime, timedelta, time + + +class Sys_statisticsService: + """ + 系统统计数据模块服务层 + """ + # 展厅讲解次数加1 + @classmethod + async def add_explain_count_services(cls, query_db: AsyncSession, user_name: str): + """ + 新增展厅讲解次数 + :param query_db: orm对象 + :return: + """ + await Sys_statisticsDao.add_count(query_db, "exhibition_explanation", user_name, 1) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + # 访客引导量加1 + @classmethod + async def add_visitor_guide_count_services(cls, query_db: AsyncSession, user_name: str): + """ + 新增访客引导量 + :param query_db: orm对象 + :return: + """ + await Sys_statisticsDao.add_count(query_db, "visitor_guide", user_name, 1) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + # 门禁通行量加1 + @classmethod + async def add_door_pass_count_services(cls, query_db: AsyncSession, user_name: str): + """ + 新增门禁通行量 + :param query_db: orm对象 + :return: + """ + await Sys_statisticsDao.add_count(query_db, "access_control_count", user_name, 1) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + # 大模型调用量加1 + @classmethod + async def add_model_call_count_services(cls, query_db: AsyncSession, user_name: str): + """ + 新增模型调用次数 + :param query_db: orm对象 + :return: + """ + # await Sys_statisticsDao.add_model_call_count(query_db, user_name) + await Sys_statisticsDao.add_count(query_db, "llm_call", user_name, 1) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + + # 根据时间区间获取门禁识别成功率 + @classmethod + async def get_access_control_success_rate_services(cls, query_db: AsyncSession, day: int): + end_date = datetime.now().date() - timedelta(days=1) + start_date = end_date - timedelta(days=day) + result = await Sys_statisticsDao.get_access_control_success_rate(query_db, start_date, end_date) + return result + + + + # 根据时间区间获取访客数量 + @classmethod + async def get_visitor_count_services(cls, query_db: AsyncSession, day: int): + end_date = datetime.now().date() - timedelta(days=1) + start_date = end_date - timedelta(days=day) + result = await Sys_statisticsDao.get_visitor_count(query_db, start_date, end_date) + # if len(result) < day: + + return result + + + + # 获取系统统计数据 + @classmethod + async def get_statistics_data_services(cls, query_db: AsyncSession): + + # 当前日期 + today = datetime.now().date() + # yesterday = today - timedelta(days=1) + # data_1 = await Sys_statisticsDao.get_statistics_data(query_db, yesterday) + data_2 = await Sys_statisticsDao.get_statistics_data(query_db, today) + + total = await Sys_statisticsDao.get_total_statistics_data(query_db) + + if total is None: + total = { + "llm_call": 1, + "access_control_count": 1, + "visitor_guide": 1, + "exhibition_explanation": 1 + } + else: + total = dict(total) + + if data_2 is None: + data_2 = { + "llm_call": 0, + "access_control_count": 0, + "visitor_guide": 0, + "exhibition_explanation": 0 + } + else: + data_2 =dict(data_2) + # 计算今日数据占总数据的百分比 + for k, v in data_2.items(): + if total.get(k) and total[k] > 0: + # 计算百分比,保留2位小数 + data_2[k] = round((v / total[k]) * 100, 2) + else: + data_2[k] = 0.0 + + return { + 'change': data_2, + 'total': total + } + + @classmethod + async def get_sys_statistics_list_services( + cls, query_db: AsyncSession, query_object: Sys_statisticsPageQueryModel, is_page: bool = False + ): + """ + 获取系统统计数据列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 系统统计数据列表信息对象 + """ + sys_statistics_list_result = await Sys_statisticsDao.get_sys_statistics_list(query_db, query_object, is_page) + + return sys_statistics_list_result + + + @classmethod + async def add_sys_statistics_services(cls, query_db: AsyncSession, page_object: Sys_statisticsModel): + """ + 新增系统统计数据信息service + + :param query_db: orm对象 + :param page_object: 新增系统统计数据对象 + :return: 新增系统统计数据校验结果 + """ + try: + await Sys_statisticsDao.add_sys_statistics_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_sys_statistics_services(cls, query_db: AsyncSession, page_object: Sys_statisticsModel): + """ + 编辑系统统计数据信息service + + :param query_db: orm对象 + :param page_object: 编辑系统统计数据对象 + :return: 编辑系统统计数据校验结果 + """ + edit_sys_statistics = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'create_by', }) + sys_statistics_info = await cls.sys_statistics_detail_services(query_db, page_object.statistic_id) + if sys_statistics_info.statistic_id: + try: + await Sys_statisticsDao.edit_sys_statistics_dao(query_db, edit_sys_statistics) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='系统统计数据不存在') + + @classmethod + async def delete_sys_statistics_services(cls, query_db: AsyncSession, page_object: DeleteSys_statisticsModel): + """ + 删除系统统计数据信息service + + :param query_db: orm对象 + :param page_object: 删除系统统计数据对象 + :return: 删除系统统计数据校验结果 + """ + if page_object.statistic_ids: + statistic_id_list = page_object.statistic_ids.split(',') + try: + for statistic_id in statistic_id_list: + await Sys_statisticsDao.delete_sys_statistics_dao(query_db, Sys_statisticsModel(statisticId=statistic_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入统计项目ID为空') + + @classmethod + async def sys_statistics_detail_services(cls, query_db: AsyncSession, statistic_id: int): + """ + 获取系统统计数据详细信息service + + :param query_db: orm对象 + :param statistic_id: 统计项目ID + :return: 统计项目ID对应的信息 + """ + sys_statistics = await Sys_statisticsDao.get_sys_statistics_detail_by_id(query_db, statistic_id=statistic_id) + if sys_statistics: + result = Sys_statisticsModel(**CamelCaseUtil.transform_result(sys_statistics)) + else: + result = Sys_statisticsModel(**dict()) + + return result + + @staticmethod + async def export_sys_statistics_list_services(sys_statistics_list: List): + """ + 导出系统统计数据信息service + + :param sys_statistics_list: 系统统计数据信息列表 + :return: 系统统计数据信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'statisticId': '统计项目ID', + 'statDate': '统计日期', + 'llmCall': '大模型调用量', + 'accessControlCount': '门禁通行量', + 'visitorGuide': '访客引导次数', + 'exhibitionExplanation': '展厅讲解次数', + 'accessControlSuccessRate': '门禁识别成功率(%)', + 'createTime': '创建时间', + 'createBy': '创建者', + 'updateTime': '修改时间', + 'updateBy': '修改者', + } + binary_data = ExcelUtil.export_list2excel(sys_statistics_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/test_service.py b/ruoyi-fastapi-backend/module_admin/service/test_service.py new file mode 100644 index 0000000..a14fc73 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/test_service.py @@ -0,0 +1,10 @@ +from module_admin.dao.test_dao import TestDao +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.vo.test_vo import TestUserNamePage + + +class TestService: + @staticmethod + async def get_user_name(db: AsyncSession, query_paramter:TestUserNamePage): + user_name = await TestDao.get_user_name(db, query_paramter) + return user_name diff --git a/ruoyi-fastapi-backend/module_admin/service/user_service.py b/ruoyi-fastapi-backend/module_admin/service/user_service.py new file mode 100644 index 0000000..f269d3f --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/user_service.py @@ -0,0 +1,639 @@ +import io +import pandas as pd +from datetime import datetime +from fastapi import Request, UploadFile +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List, Union +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.dao.user_dao import UserDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.post_vo import PostPageQueryModel +from module_admin.entity.vo.user_vo import ( + AddUserModel, + CrudUserRoleModel, + CurrentUserModel, + DeleteUserModel, + EditUserModel, + ResetUserModel, + SelectedRoleModel, + UserDetailModel, + UserInfoModel, + UserModel, + UserPageQueryModel, + UserPostModel, + UserProfileModel, + UserRoleModel, + UserRoleQueryModel, + UserRoleResponseModel, +) +from module_admin.service.config_service import ConfigService +from module_admin.service.dept_service import DeptService +from module_admin.service.post_service import PostService +from module_admin.service.role_service import RoleService +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil +from utils.page_util import PageResponseModel +from utils.pwd_util import PwdUtil + + +class UserService: + """ + 用户管理模块服务层 + """ + + @classmethod + async def get_user_list_services( + cls, query_db: AsyncSession, query_object: UserPageQueryModel, data_scope_sql: str, is_page: bool = False + ): + """ + 获取用户列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :param is_page: 是否开启分页 + :return: 用户列表信息对象 + """ + query_result = await UserDao.get_user_list(query_db, query_object, data_scope_sql, is_page) + if is_page: + user_list_result = PageResponseModel( + **{ + **query_result.model_dump(by_alias=True), + 'rows': [{**row[0], 'dept': row[1]} for row in query_result.rows], + } + ) + else: + user_list_result = [] + if query_result: + user_list_result = [{**row[0], 'dept': row[1]} for row in query_result] + + return user_list_result + + @classmethod + async def check_user_allowed_services(cls, check_user: UserModel): + """ + 校验用户是否允许操作service + + :param check_user: 用户信息 + :return: 校验结果 + """ + if check_user.admin: + raise ServiceException(message='不允许操作超级管理员用户') + else: + return CrudResponseModel(is_success=True, message='校验通过') + + @classmethod + async def check_user_data_scope_services(cls, query_db: AsyncSession, user_id: int, data_scope_sql: str): + """ + 校验用户数据权限service + + :param query_db: orm对象 + :param user_id: 用户id + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 校验结果 + """ + users = await UserDao.get_user_list(query_db, UserPageQueryModel(userId=user_id), data_scope_sql, is_page=False) + if users: + return CrudResponseModel(is_success=True, message='校验通过') + else: + raise ServiceException(message='没有权限访问用户数据') + + @classmethod + async def check_user_name_unique_services(cls, query_db: AsyncSession, page_object: UserModel): + """ + 校验用户名是否唯一service + + :param query_db: orm对象 + :param page_object: 用户对象 + :return: 校验结果 + """ + user_id = -1 if page_object.user_id is None else page_object.user_id + user = await UserDao.get_user_by_info(query_db, UserModel(userName=page_object.user_name)) + if user and user.user_id != user_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def check_phonenumber_unique_services(cls, query_db: AsyncSession, page_object: UserModel): + """ + 校验用户手机号是否唯一service + + :param query_db: orm对象 + :param page_object: 用户对象 + :return: 校验结果 + """ + user_id = -1 if page_object.user_id is None else page_object.user_id + user = await UserDao.get_user_by_info(query_db, UserModel(phonenumber=page_object.phonenumber)) + if user and user.user_id != user_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def check_email_unique_services(cls, query_db: AsyncSession, page_object: UserModel): + """ + 校验用户邮箱是否唯一service + + :param query_db: orm对象 + :param page_object: 用户对象 + :return: 校验结果 + """ + user_id = -1 if page_object.user_id is None else page_object.user_id + user = await UserDao.get_user_by_info(query_db, UserModel(email=page_object.email)) + if user and user.user_id != user_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_user_services(cls, query_db: AsyncSession, page_object: AddUserModel): + """ + 新增用户信息service + + :param query_db: orm对象 + :param page_object: 新增用户对象 + :return: 新增用户校验结果 + """ + add_user = UserModel(**page_object.model_dump(by_alias=True)) + if not await cls.check_user_name_unique_services(query_db, page_object): + raise ServiceException(message=f'新增用户{page_object.user_name}失败,登录账号已存在') + elif page_object.phonenumber and not await cls.check_phonenumber_unique_services(query_db, page_object): + raise ServiceException(message=f'新增用户{page_object.user_name}失败,手机号码已存在') + elif page_object.email and not await cls.check_email_unique_services(query_db, page_object): + raise ServiceException(message=f'新增用户{page_object.user_name}失败,邮箱账号已存在') + else: + try: + add_result = await UserDao.add_user_dao(query_db, add_user) + user_id = add_result.user_id + if page_object.role_ids: + for role in page_object.role_ids: + await UserDao.add_user_role_dao(query_db, UserRoleModel(userId=user_id, roleId=role)) + if page_object.post_ids: + for post in page_object.post_ids: + await UserDao.add_user_post_dao(query_db, UserPostModel(userId=user_id, postId=post)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_user_services(cls, query_db: AsyncSession, page_object: EditUserModel): + """ + 编辑用户信息service + + :param query_db: orm对象 + :param page_object: 编辑用户对象 + :return: 编辑用户校验结果 + """ + edit_user = page_object.model_dump(exclude_unset=True, exclude={'admin'}) + if page_object.type != 'status' and page_object.type != 'avatar' and page_object.type != 'pwd': + del edit_user['role_ids'] + del edit_user['post_ids'] + del edit_user['role'] + if page_object.type == 'status' or page_object.type == 'avatar' or page_object.type == 'pwd': + del edit_user['type'] + user_info = await cls.user_detail_services(query_db, edit_user.get('user_id')) + if user_info.data and user_info.data.user_id: + if page_object.type != 'status' and page_object.type != 'avatar' and page_object.type != 'pwd': + if not await cls.check_user_name_unique_services(query_db, page_object): + raise ServiceException(message=f'修改用户{page_object.user_name}失败,登录账号已存在') + elif page_object.phonenumber and not await cls.check_phonenumber_unique_services(query_db, page_object): + raise ServiceException(message=f'修改用户{page_object.user_name}失败,手机号码已存在') + elif page_object.email and not await cls.check_email_unique_services(query_db, page_object): + raise ServiceException(message=f'修改用户{page_object.user_name}失败,邮箱账号已存在') + try: + await UserDao.edit_user_dao(query_db, edit_user) + if page_object.type != 'status' and page_object.type != 'avatar' and page_object.type != 'pwd': + await UserDao.delete_user_role_dao(query_db, UserRoleModel(userId=page_object.user_id)) + await UserDao.delete_user_post_dao(query_db, UserPostModel(userId=page_object.user_id)) + if page_object.role_ids: + for role in page_object.role_ids: + await UserDao.add_user_role_dao( + query_db, UserRoleModel(userId=page_object.user_id, roleId=role) + ) + if page_object.post_ids: + for post in page_object.post_ids: + await UserDao.add_user_post_dao( + query_db, UserPostModel(userId=page_object.user_id, postId=post) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='用户不存在') + + @classmethod + async def delete_user_services(cls, query_db: AsyncSession, page_object: DeleteUserModel): + """ + 删除用户信息service + + :param query_db: orm对象 + :param page_object: 删除用户对象 + :return: 删除用户校验结果 + """ + if page_object.user_ids: + user_id_list = page_object.user_ids.split(',') + try: + for user_id in user_id_list: + user_id_dict = dict( + userId=user_id, updateBy=page_object.update_by, updateTime=page_object.update_time + ) + await UserDao.delete_user_role_dao(query_db, UserRoleModel(**user_id_dict)) + await UserDao.delete_user_post_dao(query_db, UserPostModel(**user_id_dict)) + await UserDao.delete_user_dao(query_db, UserModel(**user_id_dict)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入用户id为空') + + @classmethod + async def user_detail_services(cls, query_db: AsyncSession, user_id: Union[int, str]): + """ + 获取用户详细信息service + + :param query_db: orm对象 + :param user_id: 用户id + :return: 用户id对应的信息 + """ + posts = await PostService.get_post_list_services(query_db, PostPageQueryModel(**{}), is_page=False) + roles = await RoleService.get_role_select_option_services(query_db) + if user_id != '': + query_user = await UserDao.get_user_detail_by_id(query_db, user_id=user_id) + post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')]) + post_ids_list = [row.post_id for row in query_user.get('user_post_info')] + role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')]) + role_ids_list = [row.role_id for row in query_user.get('user_role_info')] + + return UserDetailModel( + data=UserInfoModel( + **CamelCaseUtil.transform_result(query_user.get('user_basic_info')), + postIds=post_ids, + roleIds=role_ids, + dept=CamelCaseUtil.transform_result(query_user.get('user_dept_info')), + role=CamelCaseUtil.transform_result(query_user.get('user_role_info')), + ), + postIds=post_ids_list, + posts=posts, + roleIds=role_ids_list, + roles=roles, + ) + + return UserDetailModel(posts=posts, roles=roles) + + @classmethod + async def user_profile_services(cls, query_db: AsyncSession, user_id: int): + """ + 获取用户个人详细信息service + + :param query_db: orm对象 + :param user_id: 用户id + :return: 用户id对应的信息 + """ + query_user = await UserDao.get_user_detail_by_id(query_db, user_id=user_id) + post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')]) + post_group = ','.join([row.post_name for row in query_user.get('user_post_info')]) + role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')]) + role_group = ','.join([row.role_name for row in query_user.get('user_role_info')]) + + return UserProfileModel( + data=UserInfoModel( + **CamelCaseUtil.transform_result(query_user.get('user_basic_info')), + postIds=post_ids, + roleIds=role_ids, + dept=CamelCaseUtil.transform_result(query_user.get('user_dept_info')), + role=CamelCaseUtil.transform_result(query_user.get('user_role_info')), + ), + postGroup=post_group, + roleGroup=role_group, + ) + + @classmethod + async def reset_user_services(cls, query_db: AsyncSession, page_object: ResetUserModel): + """ + 重置用户密码service + + :param query_db: orm对象 + :param page_object: 重置用户对象 + :return: 重置用户校验结果 + """ + reset_user = page_object.model_dump(exclude_unset=True, exclude={'admin'}) + if page_object.old_password: + user = (await UserDao.get_user_detail_by_id(query_db, user_id=page_object.user_id)).get('user_basic_info') + if not PwdUtil.verify_password(page_object.old_password, user.password): + raise ServiceException(message='修改密码失败,旧密码错误') + elif PwdUtil.verify_password(page_object.password, user.password): + raise ServiceException(message='新密码不能与旧密码相同') + else: + del reset_user['old_password'] + if page_object.sms_code and page_object.session_id: + del reset_user['sms_code'] + del reset_user['session_id'] + try: + reset_user['password'] = PwdUtil.get_password_hash(page_object.password) + await UserDao.edit_user_dao(query_db, reset_user) + await query_db.commit() + return CrudResponseModel(is_success=True, message='重置成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def batch_import_user_services( + cls, + request: Request, + query_db: AsyncSession, + file: UploadFile, + update_support: bool, + current_user: CurrentUserModel, + user_data_scope_sql: str, + dept_data_scope_sql: str, + ): + """ + 批量导入用户service + + :param request: Request对象 + :param query_db: orm对象 + :param file: 用户导入文件对象 + :param update_support: 用户存在时是否更新 + :param current_user: 当前用户对象 + :param user_data_scope_sql: 用户数据权限sql + :param dept_data_scope_sql: 部门数据权限sql + :return: 批量导入用户结果 + """ + header_dict = { + '部门编号': 'dept_id', + '登录名称': 'user_name', + '用户名称': 'nick_name', + '用户邮箱': 'email', + '手机号码': 'phonenumber', + '用户性别': 'sex', + '帐号状态': 'status', + } + contents = await file.read() + df = pd.read_excel(io.BytesIO(contents)) + await file.close() + df.rename(columns=header_dict, inplace=True) + add_error_result = [] + count = 0 + try: + for index, row in df.iterrows(): + count = count + 1 + if row['sex'] == '男': + row['sex'] = '0' + if row['sex'] == '女': + row['sex'] = '1' + if row['sex'] == '未知': + row['sex'] = '2' + if row['status'] == '正常': + row['status'] = '0' + if row['status'] == '停用': + row['status'] = '1' + add_user = UserModel( + deptId=row['dept_id'], + userName=row['user_name'], + password=PwdUtil.get_password_hash( + await ConfigService.query_config_list_from_cache_services( + request.app.state.redis, 'sys.user.initPassword' + ) + ), + nickName=row['nick_name'], + email=row['email'], + phonenumber=str(row['phonenumber']), + sex=row['sex'], + status=row['status'], + createBy=current_user.user.user_name, + createTime=datetime.now(), + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + ) + user_info = await UserDao.get_user_by_info(query_db, UserModel(userName=row['user_name'])) + if user_info: + if update_support: + edit_user_model = UserModel( + userId=user_info.user_id, + deptId=row['dept_id'], + userName=row['user_name'], + nickName=row['nick_name'], + email=row['email'], + phonenumber=str(row['phonenumber']), + sex=row['sex'], + status=row['status'], + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + ) + edit_user_model.validate_fields() + await cls.check_user_allowed_services(edit_user_model) + if not current_user.user.admin: + await cls.check_user_data_scope_services( + query_db, edit_user_model.user_id, user_data_scope_sql + ) + await DeptService.check_dept_data_scope_services( + query_db, edit_user_model.dept_id, dept_data_scope_sql + ) + edit_user = edit_user_model.model_dump(exclude_unset=True) + await UserDao.edit_user_dao(query_db, edit_user) + else: + add_error_result.append(f"{count}.用户账号{row['user_name']}已存在") + else: + add_user.validate_fields() + if not current_user.user.admin: + await DeptService.check_dept_data_scope_services( + query_db, add_user.dept_id, dept_data_scope_sql + ) + await UserDao.add_user_dao(query_db, add_user) + await query_db.commit() + return CrudResponseModel(is_success=True, message='\n'.join(add_error_result)) + except Exception as e: + await query_db.rollback() + raise e + + @staticmethod + async def get_user_import_template_services(): + """ + 获取用户导入模板service + + :return: 用户导入模板excel的二进制数据 + """ + header_list = ['部门编号', '登录名称', '用户名称', '用户邮箱', '手机号码', '用户性别', '帐号状态'] + selector_header_list = ['用户性别', '帐号状态'] + option_list = [{'用户性别': ['男', '女', '未知']}, {'帐号状态': ['正常', '停用']}] + binary_data = ExcelUtil.get_excel_template( + header_list=header_list, selector_header_list=selector_header_list, option_list=option_list + ) + + return binary_data + + @staticmethod + async def export_user_list_services(user_list: List): + """ + 导出用户信息service + + :param user_list: 用户信息列表 + :return: 用户信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'userId': '用户编号', + 'userName': '用户名称', + 'nickName': '用户昵称', + 'deptName': '部门', + 'email': '邮箱地址', + 'phonenumber': '手机号码', + 'sex': '性别', + 'status': '状态', + 'createBy': '创建者', + 'createTime': '创建时间', + 'updateBy': '更新者', + 'updateTime': '更新时间', + 'remark': '备注', + } + + for item in user_list: + # item['deptName'] = item.get('dept').get('deptName') + if item.get('status') == '0': + item['status'] = '正常' + else: + item['status'] = '停用' + if item.get('sex') == '0': + item['sex'] = '男' + elif item.get('sex') == '1': + item['sex'] = '女' + else: + item['sex'] = '未知' + binary_data = ExcelUtil.export_list2excel(user_list, mapping_dict) + + return binary_data + + @classmethod + async def get_user_role_allocated_list_services(cls, query_db: AsyncSession, page_object: UserRoleQueryModel): + """ + 根据用户id获取已分配角色列表 + + :param query_db: orm对象 + :param page_object: 用户关联角色对象 + :return: 已分配角色列表 + """ + query_user = await UserDao.get_user_detail_by_id(query_db, page_object.user_id) + post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')]) + role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')]) + user = UserInfoModel( + **CamelCaseUtil.transform_result(query_user.get('user_basic_info')), + postIds=post_ids, + roleIds=role_ids, + dept=CamelCaseUtil.transform_result(query_user.get('user_dept_info')), + role=CamelCaseUtil.transform_result(query_user.get('user_role_info')), + ) + query_role_list = [ + SelectedRoleModel(**row) for row in await RoleService.get_role_select_option_services(query_db) + ] + for model_a in query_role_list: + for model_b in user.role: + if model_a.role_id == model_b.role_id: + model_a.flag = True + result = UserRoleResponseModel(roles=query_role_list, user=user) + + return result + + @classmethod + async def add_user_role_services(cls, query_db: AsyncSession, page_object: CrudUserRoleModel): + """ + 新增用户关联角色信息service + + :param query_db: orm对象 + :param page_object: 新增用户关联角色对象 + :return: 新增用户关联角色校验结果 + """ + if page_object.user_id and page_object.role_ids: + role_id_list = page_object.role_ids.split(',') + try: + await UserDao.delete_user_role_by_user_and_role_dao(query_db, UserRoleModel(userId=page_object.user_id)) + for role_id in role_id_list: + await UserDao.add_user_role_dao(query_db, UserRoleModel(userId=page_object.user_id, roleId=role_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='分配成功') + except Exception as e: + await query_db.rollback() + raise e + elif page_object.user_id and not page_object.role_ids: + try: + await UserDao.delete_user_role_by_user_and_role_dao(query_db, UserRoleModel(userId=page_object.user_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='分配成功') + except Exception as e: + await query_db.rollback() + raise e + elif page_object.user_ids and page_object.role_id: + user_id_list = page_object.user_ids.split(',') + try: + for user_id in user_id_list: + user_role = await cls.detail_user_role_services( + query_db, UserRoleModel(userId=user_id, roleId=page_object.role_id) + ) + if user_role: + continue + else: + await UserDao.add_user_role_dao( + query_db, UserRoleModel(userId=user_id, roleId=page_object.role_id) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='不满足新增条件') + + @classmethod + async def delete_user_role_services(cls, query_db: AsyncSession, page_object: CrudUserRoleModel): + """ + 删除用户关联角色信息service + + :param query_db: orm对象 + :param page_object: 删除用户关联角色对象 + :return: 删除用户关联角色校验结果 + """ + if (page_object.user_id and page_object.role_id) or (page_object.user_ids and page_object.role_id): + if page_object.user_id and page_object.role_id: + try: + await UserDao.delete_user_role_by_user_and_role_dao( + query_db, UserRoleModel(userId=page_object.user_id, roleId=page_object.role_id) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + elif page_object.user_ids and page_object.role_id: + user_id_list = page_object.user_ids.split(',') + try: + for user_id in user_id_list: + await UserDao.delete_user_role_by_user_and_role_dao( + query_db, UserRoleModel(userId=user_id, roleId=page_object.role_id) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='不满足删除条件') + else: + raise ServiceException(message='传入用户角色关联信息为空') + + @classmethod + async def detail_user_role_services(cls, query_db: AsyncSession, page_object: UserRoleModel): + """ + 获取用户关联角色详细信息service + + :param query_db: orm对象 + :param page_object: 用户关联角色对象 + :return: 用户关联角色详细信息 + """ + user_role = await UserDao.get_user_role_detail(query_db, page_object) + + return user_role diff --git a/ruoyi-fastapi-backend/module_admin/service/visitor_type_service.py b/ruoyi-fastapi-backend/module_admin/service/visitor_type_service.py new file mode 100644 index 0000000..27636c6 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/visitor_type_service.py @@ -0,0 +1,136 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.visitor_type_dao import Visitor_typeDao +from module_admin.entity.vo.visitor_type_vo import DeleteVisitor_typeModel, Visitor_typeModel, Visitor_typePageQueryModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class Visitor_typeService: + """ + 访客类型模块服务层 + """ + + @classmethod + async def get_system_visitor_type_list(cls, query_db: AsyncSession): + return await Visitor_typeDao.get_system_visitor_type_list(query_db) + + @classmethod + async def get_visitor_type_list_services( + cls, query_db: AsyncSession, query_object: Visitor_typePageQueryModel, is_page: bool = False + ): + """ + 获取访客类型列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 访客类型列表信息对象 + """ + visitor_type_list_result = await Visitor_typeDao.get_visitor_type_list(query_db, query_object, is_page) + + return visitor_type_list_result + + + @classmethod + async def add_visitor_type_services(cls, query_db: AsyncSession, page_object: Visitor_typeModel): + """ + 新增访客类型信息service + + :param query_db: orm对象 + :param page_object: 新增访客类型对象 + :return: 新增访客类型校验结果 + """ + try: + await Visitor_typeDao.add_visitor_type_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_visitor_type_services(cls, query_db: AsyncSession, page_object: Visitor_typeModel): + """ + 编辑访客类型信息service + + :param query_db: orm对象 + :param page_object: 编辑访客类型对象 + :return: 编辑访客类型校验结果 + """ + edit_visitor_type = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'create_by', }) + visitor_type_info = await cls.visitor_type_detail_services(query_db, page_object.type_id) + if visitor_type_info.type_id: + try: + await Visitor_typeDao.edit_visitor_type_dao(query_db, edit_visitor_type) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='访客类型不存在') + + @classmethod + async def delete_visitor_type_services(cls, query_db: AsyncSession, page_object: DeleteVisitor_typeModel): + """ + 删除访客类型信息service + + :param query_db: orm对象 + :param page_object: 删除访客类型对象 + :return: 删除访客类型校验结果 + """ + if page_object.type_ids: + type_id_list = page_object.type_ids.split(',') + try: + for type_id in type_id_list: + await Visitor_typeDao.delete_visitor_type_dao(query_db, Visitor_typeModel(typeId=type_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入访客类型ID为空') + + @classmethod + async def visitor_type_detail_services(cls, query_db: AsyncSession, type_id: int): + """ + 获取访客类型详细信息service + + :param query_db: orm对象 + :param type_id: 访客类型ID + :return: 访客类型ID对应的信息 + """ + visitor_type = await Visitor_typeDao.get_visitor_type_detail_by_id(query_db, type_id=type_id) + if visitor_type: + result = Visitor_typeModel(**CamelCaseUtil.transform_result(visitor_type)) + else: + result = Visitor_typeModel(**dict()) + + return result + + @staticmethod + async def export_visitor_type_list_services(visitor_type_list: List): + """ + 导出访客类型信息service + + :param visitor_type_list: 访客类型信息列表 + :return: 访客类型信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'typeId': '访客类型ID', + 'name': '类型名称', + 'value': '类型值', + 'createTime': '创建时间', + 'createBy': '创建者', + 'updateTime': '修改时间', + 'updateBy': '修改者', + } + binary_data = ExcelUtil.export_list2excel(visitor_type_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/weather_service.py b/ruoyi-fastapi-backend/module_admin/service/weather_service.py new file mode 100644 index 0000000..a8ec119 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/weather_service.py @@ -0,0 +1,139 @@ +import json +import re +from datetime import datetime +from typing import Any, Dict, Optional + +from config.env import WeatherConfig +from utils.log_util import logger +from utils.weather_client import WeatherAPIClient, WeatherAPIError + + +class WeatherServiceError(Exception): + """天气问答业务异常""" + + +class WeatherService: + WEATHER_KEYWORDS = ( + '天气', '温度', '气温', '下雨', '晴', '多云', 'weather', 'temperature', 'rain', 'sunny', 'cloudy' + ) + CITY_PATTERN = re.compile(r'(?:在|到|去)?([\u4e00-\u9fa5]{2,8})(?:市|区|县|州|城)?(?:的)?(?:天气|气温)') + CITY_EN_PATTERN = re.compile(r'(?:in|at)\s+([A-Za-z\s]+?)\s*(?:weather|temperature)', re.IGNORECASE) + + _client = WeatherAPIClient( + base_url=WeatherConfig.WEATHER_API_BASE, + api_key=WeatherConfig.WEATHER_API_KEY, + ) + + @classmethod + def should_handle(cls, question: Optional[str]) -> bool: + if not question: + return False + normalized = question.strip().lower() + return any(k in question for k in cls.WEATHER_KEYWORDS) or any(k in normalized for k in cls.WEATHER_KEYWORDS) + + @classmethod + def extract_city(cls, question: str) -> Optional[str]: + if not question: + return None + match = cls.CITY_PATTERN.search(question) + if match: + return match.group(1) + match = cls.CITY_EN_PATTERN.search(question) + if match: + return match.group(1).strip().title() + return None + + @classmethod + async def get_weather_answer(cls, question: str, redis=None) -> Dict[str, Any]: + if not WeatherConfig.WEATHER_API_KEY: + raise WeatherServiceError('天气服务未配置 API Key,请先在环境变量中完成配置') + + city = cls.extract_city(question) or WeatherConfig.WEATHER_DEFAULT_CITY + cache_key = None + if redis: + cache_key = cls._build_cache_key(city) + cached = await redis.get(cache_key) + if cached: + try: + return json.loads(cached) + except (TypeError, json.JSONDecodeError): + pass + + try: + payload = await cls._client.get_current_weather( + city, + units=WeatherConfig.WEATHER_UNITS, + lang=WeatherConfig.WEATHER_LANG, + ) + except WeatherAPIError as exc: + logger.warning('获取天气失败: %s', exc) + raise WeatherServiceError(exc.message) from exc + + formatted = cls._format_response(city, payload) + + if redis and cache_key: + try: + await redis.set(cache_key, json.dumps(formatted, ensure_ascii=False), ex=WeatherConfig.WEATHER_CACHE_TTL) + except Exception as exc: # noqa: BLE001 + logger.debug('缓存天气数据失败: %s', exc) + + return formatted + + @staticmethod + def _build_cache_key(city: str) -> str: + today = datetime.utcnow().strftime('%Y%m%d') + return f'weather:{city}:{today}' + + @classmethod + def _format_response(cls, city: str, payload: Dict[str, Any]) -> Dict[str, Any]: + main = payload.get('main', {}) + weather = (payload.get('weather') or [{}])[0] + wind = payload.get('wind', {}) + + temp = cls._safe_number(main.get('temp')) + feels_like = cls._safe_number(main.get('feels_like')) + humidity = cls._safe_number(main.get('humidity')) + wind_speed = cls._safe_number(wind.get('speed')) + wind_deg = cls._safe_number(wind.get('deg')) + description = weather.get('description') or weather.get('main') or '' + + unit = cls._unit_symbol() + parts = [f"{city}当前{description}" if description else f"{city}当前天气"] + if temp is not None: + parts.append(f"气温 {temp}{unit}" if unit else f"气温 {temp}") + if feels_like is not None: + parts.append(f"体感 {feels_like}{unit}" if unit else f"体感 {feels_like}") + if humidity is not None: + parts.append(f"湿度 {humidity}%") + if wind_speed is not None: + parts.append(f"风速 {wind_speed}m/s") + + return { + 'type': 'weather', + 'city': city, + 'description': description, + 'temperature': temp, + 'feels_like': feels_like, + 'humidity': humidity, + 'wind_speed': wind_speed, + 'wind_direction': wind_deg, + 'units': unit, + 'message': ','.join(parts), + 'raw': payload, + } + + @staticmethod + def _unit_symbol() -> str: + units = (WeatherConfig.WEATHER_UNITS or '').lower() + if units == 'metric': + return '°C' + if units == 'imperial': + return '°F' + return '' + + @staticmethod + def _safe_number(value: Any) -> Optional[float]: + try: + return round(float(value), 1) + except (TypeError, ValueError): + return None diff --git a/ruoyi-fastapi-backend/module_admin/service/words_service.py b/ruoyi-fastapi-backend/module_admin/service/words_service.py new file mode 100644 index 0000000..2026749 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/words_service.py @@ -0,0 +1,163 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.words_dao import WordsDao +from module_admin.entity.vo.words_vo import DeleteWordsModel, WordsModel, WordsPageQueryModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil +import random + + +class WordsService: + """ + 引导词模块服务层 + """ + + # 随机获取引导词 + @classmethod + async def get_random_guide_word_services(cls, query_db: AsyncSession, type: str): + """ + 随机获取引导词 + + :param query_db: orm对象 + :param type: 引导词类型 + :return: 引导词信息 + """ + guide_word = await WordsDao.get_words_by_type(query_db, type) + if guide_word: + return guide_word[random.randint(0, len(guide_word) - 1)] + + return guide_word + + # 批量禁用/启用引导词 + @classmethod + async def edit_del_words_services(cls, query_db: AsyncSession, guide_ids: str, del_flg:str): + + try: + await WordsDao.edit_del_words(query_db, guide_ids, del_flg) + await query_db.commit() + return CrudResponseModel(is_success=True, message='批量修改') + except Exception as e: + query_db.rollback() + raise e + + + @classmethod + async def get_words_list_services( + cls, query_db: AsyncSession, query_object: WordsPageQueryModel, is_page: bool = False + ): + """ + 获取引导词列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 引导词列表信息对象 + """ + words_list_result = await WordsDao.get_words_list(query_db, query_object, is_page) + + return words_list_result + + + @classmethod + async def add_words_services(cls, query_db: AsyncSession, page_object: WordsModel): + """ + 新增引导词信息service + + :param query_db: orm对象 + :param page_object: 新增引导词对象 + :return: 新增引导词校验结果 + """ + try: + await WordsDao.add_words_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_words_services(cls, query_db: AsyncSession, page_object: WordsModel): + """ + 编辑引导词信息service + + :param query_db: orm对象 + :param page_object: 编辑引导词对象 + :return: 编辑引导词校验结果 + """ + edit_words = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'create_by', }) + words_info = await cls.words_detail_services(query_db, page_object.guide_id) + if words_info.guide_id: + try: + await WordsDao.edit_words_dao(query_db, edit_words) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='引导词不存在') + + @classmethod + async def delete_words_services(cls, query_db: AsyncSession, page_object: DeleteWordsModel): + """ + 删除引导词信息service + + :param query_db: orm对象 + :param page_object: 删除引导词对象 + :return: 删除引导词校验结果 + """ + if page_object.guide_ids: + guide_id_list = page_object.guide_ids.split(',') + try: + for guide_id in guide_id_list: + await WordsDao.delete_words_dao(query_db, WordsModel(guideId=guide_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入引导词id为空') + + @classmethod + async def words_detail_services(cls, query_db: AsyncSession, guide_id: int): + """ + 获取引导词详细信息service + + :param query_db: orm对象 + :param guide_id: 引导词id + :return: 引导词id对应的信息 + """ + words = await WordsDao.get_words_detail_by_id(query_db, guide_id=guide_id) + if words: + result = WordsModel(**CamelCaseUtil.transform_result(words)) + else: + result = WordsModel(**dict()) + + return result + + @staticmethod + async def export_words_list_services(words_list: List): + """ + 导出引导词信息service + + :param words_list: 引导词信息列表 + :return: 引导词信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + 'guideId': '引导词id', + 'type': '引导词类型', + 'text': '文本内容', + 'createTime': '创建时间', + 'updateTime': '修改时间', + 'createBy': '创建者', + 'remark': '备注', + 'delFlag': '删除标志', + } + binary_data = ExcelUtil.export_list2excel(words_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_generator/controller/gen_controller.py b/ruoyi-fastapi-backend/module_generator/controller/gen_controller.py new file mode 100644 index 0000000..4e227c1 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/controller/gen_controller.py @@ -0,0 +1,158 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Query, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.env import GenConfig +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckRoleInterfaceAuth, CheckUserInterfaceAuth +from module_admin.service.login_service import LoginService +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_generator.entity.vo.gen_vo import DeleteGenTableModel, EditGenTableModel, GenTablePageQueryModel +from module_generator.service.gen_service import GenTableColumnService, GenTableService +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +genController = APIRouter(prefix='/tool/gen', dependencies=[Depends(LoginService.get_current_user)]) + + +@genController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:list'))] +) +async def get_gen_table_list( + request: Request, + gen_page_query: GenTablePageQueryModel = Depends(GenTablePageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + gen_page_query_result = await GenTableService.get_gen_table_list_services(query_db, gen_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=gen_page_query_result) + + +@genController.get( + '/db/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:list'))] +) +async def get_gen_db_table_list( + request: Request, + gen_page_query: GenTablePageQueryModel = Depends(GenTablePageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + gen_page_query_result = await GenTableService.get_gen_db_table_list_services(query_db, gen_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=gen_page_query_result) + + +@genController.post('/importTable', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:import'))]) +@Log(title='代码生成', business_type=BusinessType.IMPORT) +async def import_gen_table( + request: Request, + tables: str = Query(), + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + table_names = tables.split(',') if tables else [] + add_gen_table_list = await GenTableService.get_gen_db_table_list_by_name_services(query_db, table_names) + add_gen_table_result = await GenTableService.import_gen_table_services(query_db, add_gen_table_list, current_user) + logger.info(add_gen_table_result.message) + + return ResponseUtil.success(msg=add_gen_table_result.message) + + +@genController.put('', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:edit'))]) +@ValidateFields(validate_model='edit_gen_table') +@Log(title='代码生成', business_type=BusinessType.UPDATE) +async def edit_gen_table( + request: Request, + edit_gen_table: EditGenTableModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_gen_table.update_by = current_user.user.user_name + edit_gen_table.update_time = datetime.now() + await GenTableService.validate_edit(edit_gen_table) + edit_gen_result = await GenTableService.edit_gen_table_services(query_db, edit_gen_table) + logger.info(edit_gen_result.message) + + return ResponseUtil.success(msg=edit_gen_result.message) + + +@genController.delete('/{table_ids}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:remove'))]) +@Log(title='代码生成', business_type=BusinessType.DELETE) +async def delete_gen_table(request: Request, table_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_gen_table = DeleteGenTableModel(tableIds=table_ids) + delete_gen_table_result = await GenTableService.delete_gen_table_services(query_db, delete_gen_table) + logger.info(delete_gen_table_result.message) + + return ResponseUtil.success(msg=delete_gen_table_result.message) + + +@genController.post('/createTable', dependencies=[Depends(CheckRoleInterfaceAuth('admin'))]) +@Log(title='创建表', business_type=BusinessType.OTHER) +async def create_table( + request: Request, + sql: str = Query(), + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + create_table_result = await GenTableService.create_table_services(query_db, sql, current_user) + logger.info(create_table_result.message) + + return ResponseUtil.success(msg=create_table_result.message) + + +@genController.get('/batchGenCode', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:code'))]) +@Log(title='代码生成', business_type=BusinessType.GENCODE) +async def batch_gen_code(request: Request, tables: str = Query(), query_db: AsyncSession = Depends(get_db)): + table_names = tables.split(',') if tables else [] + batch_gen_code_result = await GenTableService.batch_gen_code_services(query_db, table_names) + logger.info('生成代码成功') + + return ResponseUtil.streaming(data=bytes2file_response(batch_gen_code_result)) + + +@genController.get('/genCode/{table_name}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:code'))]) +@Log(title='代码生成', business_type=BusinessType.GENCODE) +async def gen_code_local(request: Request, table_name: str, query_db: AsyncSession = Depends(get_db)): + if not GenConfig.allow_overwrite: + logger.error('【系统预设】不允许生成文件覆盖到本地') + return ResponseUtil.error('【系统预设】不允许生成文件覆盖到本地') + gen_code_local_result = await GenTableService.generate_code_services(query_db, table_name) + logger.info(gen_code_local_result.message) + + return ResponseUtil.success(msg=gen_code_local_result.message) + + +@genController.get('/{table_id}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:query'))]) +async def query_detail_gen_table(request: Request, table_id: int, query_db: AsyncSession = Depends(get_db)): + gen_table = await GenTableService.get_gen_table_by_id_services(query_db, table_id) + gen_tables = await GenTableService.get_gen_table_all_services(query_db) + gen_columns = await GenTableColumnService.get_gen_table_column_list_by_table_id_services(query_db, table_id) + gen_table_detail_result = dict(info=gen_table, rows=gen_columns, tables=gen_tables) + logger.info(f'获取table_id为{table_id}的信息成功') + + return ResponseUtil.success(data=gen_table_detail_result) + + +@genController.get('/preview/{table_id}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:preview'))]) +async def preview_code(request: Request, table_id: int, query_db: AsyncSession = Depends(get_db)): + preview_code_result = await GenTableService.preview_code_services(query_db, table_id) + logger.info('获取预览代码成功') + + return ResponseUtil.success(data=preview_code_result) + + +@genController.get('/synchDb/{table_name}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:edit'))]) +@Log(title='代码生成', business_type=BusinessType.UPDATE) +async def sync_db(request: Request, table_name: str, query_db: AsyncSession = Depends(get_db)): + sync_db_result = await GenTableService.sync_db_services(query_db, table_name) + logger.info(sync_db_result.message) + + return ResponseUtil.success(data=sync_db_result.message) diff --git a/ruoyi-fastapi-backend/module_generator/dao/gen_dao.py b/ruoyi-fastapi-backend/module_generator/dao/gen_dao.py new file mode 100644 index 0000000..937668a --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/dao/gen_dao.py @@ -0,0 +1,393 @@ +from datetime import datetime, time +from sqlalchemy import delete, func, select, text, update +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import selectinload +from sqlglot.expressions import Expression +from typing import List +from config.env import DataBaseConfig +from module_generator.entity.do.gen_do import GenTable, GenTableColumn +from module_generator.entity.vo.gen_vo import ( + GenTableBaseModel, + GenTableColumnBaseModel, + GenTableColumnModel, + GenTableModel, + GenTablePageQueryModel, +) +from utils.page_util import PageUtil + + +class GenTableDao: + """ + 代码生成业务表模块数据库操作层 + """ + + @classmethod + async def get_gen_table_by_id(cls, db: AsyncSession, table_id: int): + """ + 根据业务表id获取需要生成的业务表信息 + + :param db: orm对象 + :param table_id: 业务表id + :return: 需要生成的业务表信息对象 + """ + gen_table_info = ( + ( + await db.execute( + select(GenTable).options(selectinload(GenTable.columns)).where(GenTable.table_id == table_id) + ) + ) + .scalars() + .first() + ) + + return gen_table_info + + @classmethod + async def get_gen_table_by_name(cls, db: AsyncSession, table_name: str): + """ + 根据业务表名称获取需要生成的业务表信息 + + :param db: orm对象 + :param table_name: 业务表名称 + :return: 需要生成的业务表信息对象 + """ + gen_table_info = ( + ( + await db.execute( + select(GenTable).options(selectinload(GenTable.columns)).where(GenTable.table_name == table_name) + ) + ) + .scalars() + .first() + ) + + return gen_table_info + + @classmethod + async def get_gen_table_all(cls, db: AsyncSession): + """ + 获取所有业务表信息 + + :param db: orm对象 + :return: 所有业务表信息 + """ + gen_table_all = (await db.execute(select(GenTable).options(selectinload(GenTable.columns)))).scalars().all() + + return gen_table_all + + @classmethod + async def create_table_by_sql_dao(cls, db: AsyncSession, sql_statements: List[Expression]): + """ + 根据sql语句创建表结构 + + :param db: orm对象 + :param sql_statements: sql语句的ast列表 + :return: + """ + for sql_statement in sql_statements: + sql = sql_statement.sql(dialect=DataBaseConfig.sqlglot_parse_dialect) + await db.execute(text(sql)) + + @classmethod + async def get_gen_table_list(cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False): + """ + 根据查询参数获取代码生成业务表列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 代码生成业务表列表信息对象 + """ + query = ( + select(GenTable) + .options(selectinload(GenTable.columns)) + .where( + func.lower(GenTable.table_name).like(f'%{query_object.table_name.lower()}%') + if query_object.table_name + else True, + func.lower(GenTable.table_comment).like(f'%{query_object.table_comment.lower()}%') + if query_object.table_comment + else True, + GenTable.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .distinct() + ) + gen_table_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return gen_table_list + + @classmethod + async def get_gen_db_table_list(cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False): + """ + 根据查询参数获取数据库列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 数据库列表信息对象 + """ + if DataBaseConfig.db_type == 'postgresql': + query_sql = """ + table_name as table_name, + table_comment as table_comment, + create_time as create_time, + update_time as update_time + from + list_table + where + table_name not like 'apscheduler_%' + and table_name not like 'gen_%' + and table_name not in (select table_name from gen_table) + """ + else: + query_sql = """ + table_name as table_name, + table_comment as table_comment, + create_time as create_time, + update_time as update_time + from + information_schema.tables + where + table_schema = (select database()) + and table_name not like 'apscheduler\_%' + and table_name not like 'gen\_%' + and table_name not in (select table_name from gen_table) + """ + if query_object.table_name: + query_sql += """and lower(table_name) like lower(concat('%', :table_name, '%'))""" + if query_object.table_comment: + query_sql += """and lower(table_comment) like lower(concat('%', :table_comment, '%'))""" + if query_object.begin_time: + if DataBaseConfig.db_type == 'postgresql': + query_sql += """and create_time::date >= to_date(:begin_time, 'yyyy-MM-dd')""" + else: + query_sql += """and date_format(create_time, '%Y%m%d') >= date_format(:begin_time, '%Y%m%d')""" + if query_object.end_time: + if DataBaseConfig.db_type == 'postgresql': + query_sql += """and create_time::date <= to_date(:end_time, 'yyyy-MM-dd')""" + else: + query_sql += """and date_format(create_time, '%Y%m%d') >= date_format(:end_time, '%Y%m%d')""" + query_sql += """order by create_time desc""" + query = select( + text(query_sql).bindparams( + **{ + k: v + for k, v in query_object.model_dump(exclude_none=True, exclude={'page_num', 'page_size'}).items() + } + ) + ) + gen_db_table_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return gen_db_table_list + + @classmethod + async def get_gen_db_table_list_by_names(cls, db: AsyncSession, table_names: List[str]): + """ + 根据业务表名称组获取数据库列表信息 + + :param db: orm对象 + :param table_names: 业务表名称组 + :return: 数据库列表信息对象 + """ + if DataBaseConfig.db_type == 'postgresql': + query_sql = """ + select + table_name as table_name, + table_comment as table_comment, + create_time as create_time, + update_time as update_time + from + list_table + where + table_name not like 'qrtz_%' + and table_name not like 'gen_%' + and table_name = any(:table_names) + """ + else: + query_sql = """ + select + table_name as table_name, + table_comment as table_comment, + create_time as create_time, + update_time as update_time + from + information_schema.tables + where + table_name not like 'qrtz\_%' + and table_name not like 'gen\_%' + and table_schema = (select database()) + and table_name in :table_names + """ + query = text(query_sql).bindparams(table_names=tuple(table_names)) + gen_db_table_list = (await db.execute(query)).fetchall() + + return gen_db_table_list + + @classmethod + async def add_gen_table_dao(cls, db: AsyncSession, gen_table: GenTableModel): + """ + 新增业务表数据库操作 + + :param db: orm对象 + :param gen_table: 业务表对象 + :return: + """ + db_gen_table = GenTable(**GenTableBaseModel(**gen_table.model_dump(by_alias=True)).model_dump()) + db.add(db_gen_table) + await db.flush() + + return db_gen_table + + @classmethod + async def edit_gen_table_dao(cls, db: AsyncSession, gen_table: dict): + """ + 编辑业务表数据库操作 + + :param db: orm对象 + :param gen_table: 需要更新的业务表字典 + :return: + """ + await db.execute(update(GenTable), [GenTableBaseModel(**gen_table).model_dump()]) + + @classmethod + async def delete_gen_table_dao(cls, db: AsyncSession, gen_table: GenTableModel): + """ + 删除业务表数据库操作 + + :param db: orm对象 + :param gen_table: 业务表对象 + :return: + """ + await db.execute(delete(GenTable).where(GenTable.table_id.in_([gen_table.table_id]))) + + +class GenTableColumnDao: + """ + 代码生成业务表字段模块数据库操作层 + """ + + @classmethod + async def get_gen_table_column_list_by_table_id(cls, db: AsyncSession, table_id: int): + """ + 根据业务表id获取需要生成的业务表字段列表信息 + + :param db: orm对象 + :param table_id: 业务表id + :return: 需要生成的业务表字段列表信息对象 + """ + gen_table_column_list = ( + ( + await db.execute( + select(GenTableColumn).where(GenTableColumn.table_id == table_id).order_by(GenTableColumn.sort) + ) + ) + .scalars() + .all() + ) + + return gen_table_column_list + + @classmethod + async def get_gen_db_table_columns_by_name(cls, db: AsyncSession, table_name: str): + """ + 根据业务表名称获取业务表字段列表信息 + + :param db: orm对象 + :param table_name: 业务表名称 + :return: 业务表字段列表信息对象 + """ + if DataBaseConfig.db_type == 'postgresql': + query_sql = """ + select + column_name, is_required, is_pk, sort, column_comment, is_increment, column_type + from + list_column + where + table_name = :table_name + """ + else: + query_sql = """ + select + column_name as column_name, + case + when is_nullable = 'no' and column_key != 'PRI' then '1' + else '0' + end as is_required, + case + when column_key = 'PRI' then '1' + else '0' + end as is_pk, + ordinal_position as sort, + column_comment as column_comment, + case + when extra = 'auto_increment' then '1' + else '0' + end as is_increment, + column_type as column_type + from + information_schema.columns + where + table_schema = (select database()) + and table_name = :table_name + order by + ordinal_position + """ + query = text(query_sql).bindparams(table_name=table_name) + gen_db_table_columns = (await db.execute(query)).fetchall() + + return gen_db_table_columns + + @classmethod + async def add_gen_table_column_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel): + """ + 新增业务表字段数据库操作 + + :param db: orm对象 + :param gen_table_column: 岗位对象 + :return: + """ + db_gen_table_column = GenTableColumn( + **GenTableColumnBaseModel(**gen_table_column.model_dump(by_alias=True)).model_dump() + ) + db.add(db_gen_table_column) + await db.flush() + + return db_gen_table_column + + @classmethod + async def edit_gen_table_column_dao(cls, db: AsyncSession, gen_table_column: dict): + """ + 编辑业务表字段数据库操作 + + :param db: orm对象 + :param gen_table_column: 需要更新的业务表字段字典 + :return: + """ + await db.execute(update(GenTableColumn), [GenTableColumnBaseModel(**gen_table_column).model_dump()]) + + @classmethod + async def delete_gen_table_column_by_table_id_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel): + """ + 通过业务表id删除业务表字段数据库操作 + + :param db: orm对象 + :param gen_table_column: 业务表字段对象 + :return: + """ + await db.execute(delete(GenTableColumn).where(GenTableColumn.table_id.in_([gen_table_column.table_id]))) + + @classmethod + async def delete_gen_table_column_by_column_id_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel): + """ + 通过业务字段id删除业务表字段数据库操作 + + :param db: orm对象 + :param post: 业务表字段对象 + :return: + """ + await db.execute(delete(GenTableColumn).where(GenTableColumn.column_id.in_([gen_table_column.column_id]))) diff --git a/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py b/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py new file mode 100644 index 0000000..e64d0bf --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py @@ -0,0 +1,74 @@ +from datetime import datetime +from sqlalchemy import Column, DateTime, ForeignKey, Integer, String +from sqlalchemy.orm import relationship +from config.database import Base + + +class GenTable(Base): + """ + 代码生成业务表 + """ + + __tablename__ = 'gen_table' + + table_id = Column(Integer, primary_key=True, autoincrement=True, comment='编号') + table_name = Column(String(200), nullable=True, default='', comment='表名称') + table_comment = Column(String(500), nullable=True, default='', comment='表描述') + sub_table_name = Column(String(64), nullable=True, comment='关联子表的表名') + sub_table_fk_name = Column(String(64), nullable=True, comment='子表关联的外键名') + class_name = Column(String(100), nullable=True, default='', comment='实体类名称') + tpl_category = Column(String(200), nullable=True, default='crud', comment='使用的模板(crud单表操作 tree树表操作)') + tpl_web_type = Column( + String(30), nullable=True, default='', comment='前端模板类型(element-ui模版 element-plus模版)' + ) + package_name = Column(String(100), nullable=True, comment='生成包路径') + module_name = Column(String(30), nullable=True, comment='生成模块名') + business_name = Column(String(30), nullable=True, comment='生成业务名') + function_name = Column(String(100), nullable=True, comment='生成功能名') + function_author = Column(String(100), nullable=True, comment='生成功能作者') + gen_type = Column(String(1), nullable=True, default='0', comment='生成代码方式(0zip压缩包 1自定义路径)') + gen_path = Column(String(200), nullable=True, default='/', comment='生成路径(不填默认项目路径)') + options = Column(String(1000), nullable=True, comment='其它生成选项') + create_by = Column(String(64), default='', comment='创建者') + create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') + update_by = Column(String(64), default='', comment='更新者') + update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') + remark = Column(String(500), nullable=True, default=None, comment='备注') + + columns = relationship('GenTableColumn', order_by='GenTableColumn.sort', back_populates='tables') + + +class GenTableColumn(Base): + """ + 代码生成业务表字段 + """ + + __tablename__ = 'gen_table_column' + + column_id = Column(Integer, primary_key=True, autoincrement=True, comment='编号') + table_id = Column(Integer, ForeignKey('gen_table.table_id'), nullable=True, comment='归属表编号') + column_name = Column(String(200), nullable=True, comment='列名称') + column_comment = Column(String(500), nullable=True, comment='列描述') + column_type = Column(String(100), nullable=True, comment='列类型') + python_type = Column(String(500), nullable=True, comment='PYTHON类型') + python_field = Column(String(200), nullable=True, comment='PYTHON字段名') + is_pk = Column(String(1), nullable=True, comment='是否主键(1是)') + is_increment = Column(String(1), nullable=True, comment='是否自增(1是)') + is_required = Column(String(1), nullable=True, comment='是否必填(1是)') + is_unique = Column(String(1), nullable=True, comment='是否唯一(1是)') + is_insert = Column(String(1), nullable=True, comment='是否为插入字段(1是)') + is_edit = Column(String(1), nullable=True, comment='是否编辑字段(1是)') + is_list = Column(String(1), nullable=True, comment='是否列表字段(1是)') + is_query = Column(String(1), nullable=True, comment='是否查询字段(1是)') + query_type = Column(String(200), nullable=True, default='EQ', comment='查询方式(等于、不等于、大于、小于、范围)') + html_type = Column( + String(200), nullable=True, comment='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)' + ) + dict_type = Column(String(200), nullable=True, default='', comment='字典类型') + sort = Column(Integer, nullable=True, comment='排序') + create_by = Column(String(64), default='', comment='创建者') + create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') + update_by = Column(String(64), default='', comment='更新者') + update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') + + tables = relationship('GenTable', back_populates='columns') diff --git a/ruoyi-fastapi-backend/module_generator/entity/vo/gen_vo.py b/ruoyi-fastapi-backend/module_generator/entity/vo/gen_vo.py new file mode 100644 index 0000000..e5d7917 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/entity/vo/gen_vo.py @@ -0,0 +1,264 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field, model_validator +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank +from typing import List, Literal, Optional +from config.constant import GenConstant +from module_admin.annotation.pydantic_annotation import as_query +from utils.string_util import StringUtil + + +class GenTableBaseModel(BaseModel): + """ + 代码生成业务表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + table_id: Optional[int] = Field(default=None, description='编号') + table_name: Optional[str] = Field(default=None, description='表名称') + table_comment: Optional[str] = Field(default=None, description='表描述') + sub_table_name: Optional[str] = Field(default=None, description='关联子表的表名') + sub_table_fk_name: Optional[str] = Field(default=None, description='子表关联的外键名') + class_name: Optional[str] = Field(default=None, description='实体类名称') + tpl_category: Optional[str] = Field(default=None, description='使用的模板(crud单表操作 tree树表操作)') + tpl_web_type: Optional[str] = Field(default=None, description='前端模板类型(element-ui模版 element-plus模版)') + package_name: Optional[str] = Field(default=None, description='生成包路径') + module_name: Optional[str] = Field(default=None, description='生成模块名') + business_name: Optional[str] = Field(default=None, description='生成业务名') + function_name: Optional[str] = Field(default=None, description='生成功能名') + function_author: Optional[str] = Field(default=None, description='生成功能作者') + gen_type: Optional[Literal['0', '1']] = Field(default=None, description='生成代码方式(0zip压缩包 1自定义路径)') + gen_path: Optional[str] = Field(default=None, description='生成路径(不填默认项目路径)') + options: Optional[str] = Field(default=None, description='其它生成选项') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='table_name', message='表名称不能为空') + def get_table_name(self): + return self.table_name + + @NotBlank(field_name='table_comment', message='表描述不能为空') + def get_table_comment(self): + return self.table_comment + + @NotBlank(field_name='class_name', message='实体类名称不能为空') + def get_class_name(self): + return self.class_name + + @NotBlank(field_name='package_name', message='生成包路径不能为空') + def get_package_name(self): + return self.package_name + + @NotBlank(field_name='module_name', message='生成模块名不能为空') + def get_module_name(self): + return self.module_name + + @NotBlank(field_name='business_name', message='生成业务名不能为空') + def get_business_name(self): + return self.business_name + + @NotBlank(field_name='function_name', message='生成功能名不能为空') + def get_function_name(self): + return self.function_name + + @NotBlank(field_name='function_author', message='生成功能作者不能为空') + def get_function_author(self): + return self.function_author + + def validate_fields(self): + self.get_table_name() + self.get_table_comment() + self.get_class_name() + self.get_package_name() + self.get_module_name() + self.get_business_name() + self.get_function_name() + self.get_function_author() + + +class GenTableModel(GenTableBaseModel): + """ + 代码生成业务表模型 + """ + + pk_column: Optional['GenTableColumnModel'] = Field(default=None, description='主键信息') + sub_table: Optional['GenTableModel'] = Field(default=None, description='子表信息') + columns: Optional[List['GenTableColumnModel']] = Field(default=None, description='表列信息') + tree_code: Optional[str] = Field(default=None, description='树编码字段') + tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段') + tree_name: Optional[str] = Field(default=None, description='树名称字段') + parent_menu_id: Optional[int] = Field(default=None, description='上级菜单ID字段') + parent_menu_name: Optional[str] = Field(default=None, description='上级菜单名称字段') + sub: Optional[bool] = Field(default=None, description='是否为子表') + tree: Optional[bool] = Field(default=None, description='是否为树表') + crud: Optional[bool] = Field(default=None, description='是否为单表') + + @model_validator(mode='after') + def check_some_is(self) -> 'GenTableModel': + self.sub = True if self.tpl_category and self.tpl_category == GenConstant.TPL_SUB else False + self.tree = True if self.tpl_category and self.tpl_category == GenConstant.TPL_TREE else False + self.crud = True if self.tpl_category and self.tpl_category == GenConstant.TPL_CRUD else False + return self + + +class EditGenTableModel(GenTableModel): + """ + 修改代码生成业务表模型 + """ + + params: Optional['GenTableParamsModel'] = Field(default=None, description='业务表参数') + + +class GenTableParamsModel(BaseModel): + """ + 代码生成业务表参数模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + tree_code: Optional[str] = Field(default=None, description='树编码字段') + tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段') + tree_name: Optional[str] = Field(default=None, description='树名称字段') + parent_menu_id: Optional[int] = Field(default=None, description='上级菜单ID字段') + + +class GenTableQueryModel(GenTableBaseModel): + """ + 代码生成业务表不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class GenTablePageQueryModel(GenTableQueryModel): + """ + 代码生成业务表分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteGenTableModel(BaseModel): + """ + 删除代码生成业务表模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + table_ids: str = Field(description='需要删除的代码生成业务表ID') + + +class GenTableColumnBaseModel(BaseModel): + """ + 代码生成业务表字段对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + column_id: Optional[int] = Field(default=None, description='编号') + table_id: Optional[int] = Field(default=None, description='归属表编号') + column_name: Optional[str] = Field(default=None, description='列名称') + column_comment: Optional[str] = Field(default=None, description='列描述') + column_type: Optional[str] = Field(default=None, description='列类型') + python_type: Optional[str] = Field(default=None, description='PYTHON类型') + python_field: Optional[str] = Field(default=None, description='PYTHON字段名') + is_pk: Optional[str] = Field(default=None, description='是否主键(1是)') + is_increment: Optional[str] = Field(default=None, description='是否自增(1是)') + is_required: Optional[str] = Field(default=None, description='是否必填(1是)') + is_unique: Optional[str] = Field(default=None, description='是否唯一(1是)') + is_insert: Optional[str] = Field(default=None, description='是否为插入字段(1是)') + is_edit: Optional[str] = Field(default=None, description='是否编辑字段(1是)') + is_list: Optional[str] = Field(default=None, description='是否列表字段(1是)') + is_query: Optional[str] = Field(default=None, description='是否查询字段(1是)') + query_type: Optional[str] = Field(default=None, description='查询方式(等于、不等于、大于、小于、范围)') + html_type: Optional[str] = Field( + default=None, description='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)' + ) + dict_type: Optional[str] = Field(default=None, description='字典类型') + sort: Optional[int] = Field(default=None, description='排序') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + + @NotBlank(field_name='python_field', message='Python属性不能为空') + def get_python_field(self): + return self.python_field + + def validate_fields(self): + self.get_python_field() + + +class GenTableColumnModel(GenTableColumnBaseModel): + """ + 代码生成业务表字段模型 + """ + + cap_python_field: Optional[str] = Field(default=None, description='字段大写形式') + pk: Optional[bool] = Field(default=None, description='是否主键') + increment: Optional[bool] = Field(default=None, description='是否自增') + required: Optional[bool] = Field(default=None, description='是否必填') + unique: Optional[bool] = Field(default=None, description='是否唯一') + insert: Optional[bool] = Field(default=None, description='是否为插入字段') + edit: Optional[bool] = Field(default=None, description='是否编辑字段') + list: Optional[bool] = Field(default=None, description='是否列表字段') + query: Optional[bool] = Field(default=None, description='是否查询字段') + super_column: Optional[bool] = Field(default=None, description='是否为基类字段') + usable_column: Optional[bool] = Field(default=None, description='是否为基类字段白名单') + + @model_validator(mode='after') + def check_some_is(self) -> 'GenTableModel': + self.cap_python_field = self.python_field[0].upper() + self.python_field[1:] if self.python_field else None + self.pk = True if self.is_pk and self.is_pk == '1' else False + self.increment = True if self.is_increment and self.is_increment == '1' else False + self.required = True if self.is_required and self.is_required == '1' else False + self.unique = True if self.is_unique and self.is_unique == '1' else False + self.insert = True if self.is_insert and self.is_insert == '1' else False + self.edit = True if self.is_edit and self.is_edit == '1' else False + self.list = True if self.is_list and self.is_list == '1' else False + self.query = True if self.is_query and self.is_query == '1' else False + self.super_column = ( + True + if StringUtil.equals_any_ignore_case(self.python_field, GenConstant.TREE_ENTITY + GenConstant.BASE_ENTITY) + else False + ) + self.usable_column = ( + True if StringUtil.equals_any_ignore_case(self.python_field, ['parentId', 'orderNum', 'remark']) else False + ) + return self + + +class GenTableColumnQueryModel(GenTableColumnBaseModel): + """ + 代码生成业务表字段不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class GenTableColumnPageQueryModel(GenTableColumnQueryModel): + """ + 代码生成业务表字段分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteGenTableColumnModel(BaseModel): + """ + 删除代码生成业务表字段模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + column_ids: str = Field(description='需要删除的代码生成业务表字段ID') diff --git a/ruoyi-fastapi-backend/module_generator/service/gen_service.py b/ruoyi-fastapi-backend/module_generator/service/gen_service.py new file mode 100644 index 0000000..aec0bbf --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/service/gen_service.py @@ -0,0 +1,499 @@ +import io +import json +import os +import zipfile +from datetime import datetime +from sqlalchemy.ext.asyncio import AsyncSession +from sqlglot import parse as sqlglot_parse +from sqlglot.expressions import Add, Alter, Create, Delete, Drop, Expression, Insert, Table, TruncateTable, Update +from typing import List +from config.constant import GenConstant +from config.env import DataBaseConfig, GenConfig +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_generator.entity.vo.gen_vo import ( + DeleteGenTableModel, + EditGenTableModel, + GenTableColumnModel, + GenTableModel, + GenTablePageQueryModel, +) +from module_generator.dao.gen_dao import GenTableColumnDao, GenTableDao +from utils.common_util import CamelCaseUtil +from utils.gen_util import GenUtils +from utils.template_util import TemplateInitializer, TemplateUtils + + +class GenTableService: + """ + 代码生成业务表服务层 + """ + + @classmethod + async def get_gen_table_list_services( + cls, query_db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False + ): + """ + 获取代码生成业务表列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 代码生成业务列表信息对象 + """ + gen_table_list_result = await GenTableDao.get_gen_table_list(query_db, query_object, is_page) + + return gen_table_list_result + + @classmethod + async def get_gen_db_table_list_services( + cls, query_db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False + ): + """ + 获取数据库列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 数据库列表信息对象 + """ + gen_db_table_list_result = await GenTableDao.get_gen_db_table_list(query_db, query_object, is_page) + + return gen_db_table_list_result + + @classmethod + async def get_gen_db_table_list_by_name_services(cls, query_db: AsyncSession, table_names: List[str]): + """ + 根据表名称组获取数据库列表信息service + + :param query_db: orm对象 + :param table_names: 表名称组 + :return: 数据库列表信息对象 + """ + gen_db_table_list_result = await GenTableDao.get_gen_db_table_list_by_names(query_db, table_names) + + return [GenTableModel(**gen_table) for gen_table in CamelCaseUtil.transform_result(gen_db_table_list_result)] + + @classmethod + async def import_gen_table_services( + cls, query_db: AsyncSession, gen_table_list: List[GenTableModel], current_user: CurrentUserModel + ): + """ + 导入表结构service + + :param query_db: orm对象 + :param gen_table_list: 导入表列表 + :param current_user: 当前用户信息对象 + :return: 导入结果 + """ + try: + for table in gen_table_list: + table_name = table.table_name + GenUtils.init_table(table, current_user.user.user_name) + add_gen_table = await GenTableDao.add_gen_table_dao(query_db, table) + if add_gen_table: + table.table_id = add_gen_table.table_id + gen_table_columns = await GenTableColumnDao.get_gen_db_table_columns_by_name(query_db, table_name) + for column in [ + GenTableColumnModel(**gen_table_column) + for gen_table_column in CamelCaseUtil.transform_result(gen_table_columns) + ]: + GenUtils.init_column_field(column, table) + await GenTableColumnDao.add_gen_table_column_dao(query_db, column) + await query_db.commit() + return CrudResponseModel(is_success=True, message='导入成功') + except Exception as e: + await query_db.rollback() + raise ServiceException(message=f'导入失败, {str(e)}') + + @classmethod + async def edit_gen_table_services(cls, query_db: AsyncSession, page_object: EditGenTableModel): + """ + 编辑业务表信息service + + :param query_db: orm对象 + :param page_object: 编辑业务表对象 + :return: 编辑业务表校验结果 + """ + edit_gen_table = page_object.model_dump(exclude_unset=True, by_alias=True) + gen_table_info = await cls.get_gen_table_by_id_services(query_db, page_object.table_id) + if gen_table_info.table_id: + try: + edit_gen_table['options'] = json.dumps(edit_gen_table.get('params')) + await GenTableDao.edit_gen_table_dao(query_db, edit_gen_table) + for gen_table_column in page_object.columns: + gen_table_column.update_by = page_object.update_by + gen_table_column.update_time = datetime.now() + await GenTableColumnDao.edit_gen_table_column_dao( + query_db, gen_table_column.model_dump(by_alias=True) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='业务表不存在') + + @classmethod + async def delete_gen_table_services(cls, query_db: AsyncSession, page_object: DeleteGenTableModel): + """ + 删除业务表信息service + + :param query_db: orm对象 + :param page_object: 删除业务表对象 + :return: 删除业务表校验结果 + """ + if page_object.table_ids: + table_id_list = page_object.table_ids.split(',') + try: + for table_id in table_id_list: + await GenTableDao.delete_gen_table_dao(query_db, GenTableModel(tableId=table_id)) + await GenTableColumnDao.delete_gen_table_column_by_table_id_dao( + query_db, GenTableColumnModel(tableId=table_id) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入业务表id为空') + + @classmethod + async def get_gen_table_by_id_services(cls, query_db: AsyncSession, table_id: int): + """ + 获取需要生成的业务表详细信息service + + :param query_db: orm对象 + :param table_id: 需要生成的业务表id + :return: 需要生成的业务表id对应的信息 + """ + gen_table = await GenTableDao.get_gen_table_by_id(query_db, table_id) + result = await cls.set_table_from_options(GenTableModel(**CamelCaseUtil.transform_result(gen_table))) + + return result + + @classmethod + async def get_gen_table_all_services(cls, query_db: AsyncSession): + """ + 获取所有业务表信息service + + :param query_db: orm对象 + :return: 所有业务表信息 + """ + gen_table_all = await GenTableDao.get_gen_table_all(query_db) + result = [GenTableModel(**gen_table) for gen_table in CamelCaseUtil.transform_result(gen_table_all)] + + return result + + @classmethod + async def create_table_services(cls, query_db: AsyncSession, sql: str, current_user: CurrentUserModel): + """ + 创建表结构service + + :param query_db: orm对象 + :param sql: 建表语句 + :param current_user: 当前用户信息对象 + :return: 创建表结构结果 + """ + sql_statements = sqlglot_parse(sql, dialect=DataBaseConfig.sqlglot_parse_dialect) + if cls.__is_valid_create_table(sql_statements): + try: + table_names = cls.__get_table_names(sql_statements) + await GenTableDao.create_table_by_sql_dao(query_db, sql_statements) + gen_table_list = await cls.get_gen_db_table_list_by_name_services(query_db, table_names) + await cls.import_gen_table_services(query_db, gen_table_list, current_user) + + return CrudResponseModel(is_success=True, message='创建表结构成功') + except Exception as e: + raise ServiceException(message=f'创建表结构异常,详细错误信息:{str(e)}') + else: + raise ServiceException(message='建表语句不合法') + + @classmethod + def __is_valid_create_table(cls, sql_statements: List[Expression]): + """ + 校验sql语句是否为合法的建表语句 + + :param sql_statements: sql语句的ast列表 + :return: 校验结果 + """ + validate_create = [isinstance(sql_statement, Create) for sql_statement in sql_statements] + validate_forbidden_keywords = [ + isinstance( + sql_statement, + (Add, Alter, Delete, Drop, Insert, TruncateTable, Update), + ) + for sql_statement in sql_statements + ] + if not any(validate_create) or any(validate_forbidden_keywords): + return False + return True + + @classmethod + def __get_table_names(cls, sql_statements: List[Expression]): + """ + 获取sql语句中所有的建表表名 + + :param sql_statements: sql语句的ast列表 + :return: 建表表名列表 + """ + table_names = [] + for sql_statement in sql_statements: + if isinstance(sql_statement, Create): + table_names.append(sql_statement.find(Table).name) + return table_names + + @classmethod + async def preview_code_services(cls, query_db: AsyncSession, table_id: int): + """ + 预览代码service + + :param query_db: orm对象 + :param table_id: 业务表id + :return: 预览数据列表 + """ + gen_table = GenTableModel( + **CamelCaseUtil.transform_result(await GenTableDao.get_gen_table_by_id(query_db, table_id)) + ) + await cls.set_sub_table(query_db, gen_table) + await cls.set_pk_column(gen_table) + env = TemplateInitializer.init_jinja2() + context = TemplateUtils.prepare_context(gen_table) + template_list = TemplateUtils.get_template_list(gen_table.tpl_category, gen_table.tpl_web_type) + preview_code_result = {} + for template in template_list: + render_content = env.get_template(template).render(**context) + preview_code_result[template] = render_content + return preview_code_result + + @classmethod + async def generate_code_services(cls, query_db: AsyncSession, table_name: str): + """ + 生成代码至指定路径service + + :param query_db: orm对象 + :param table_name: 业务表名称 + :return: 生成代码结果 + """ + env = TemplateInitializer.init_jinja2() + render_info = await cls.__get_gen_render_info(query_db, table_name) + for template in render_info[0]: + try: + render_content = env.get_template(template).render(**render_info[2]) + gen_path = cls.__get_gen_path(render_info[3], template) + os.makedirs(os.path.dirname(gen_path), exist_ok=True) + with open(gen_path, 'w', encoding='utf-8') as f: + f.write(render_content) + except Exception as e: + raise ServiceException( + message=f'渲染模板失败,表名:{render_info[3].table_name},详细错误信息:{str(e)}' + ) + + return CrudResponseModel(is_success=True, message='生成代码成功') + + @classmethod + async def batch_gen_code_services(cls, query_db: AsyncSession, table_names: List[str]): + """ + 批量生成代码service + + :param query_db: orm对象 + :param table_names: 业务表名称组 + :return: 下载代码结果 + """ + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: + for table_name in table_names: + env = TemplateInitializer.init_jinja2() + render_info = await cls.__get_gen_render_info(query_db, table_name) + for template_file, output_file in zip(render_info[0], render_info[1]): + render_content = env.get_template(template_file).render(**render_info[2]) + zip_file.writestr(output_file, render_content) + + zip_data = zip_buffer.getvalue() + zip_buffer.close() + return zip_data + + @classmethod + async def __get_gen_render_info(cls, query_db: AsyncSession, table_name: str): + """ + 获取生成代码渲染模板相关信息 + + :param query_db: orm对象 + :param table_name: 业务表名称 + :return: 生成代码渲染模板相关信息 + """ + gen_table = GenTableModel( + **CamelCaseUtil.transform_result(await GenTableDao.get_gen_table_by_name(query_db, table_name)) + ) + await cls.set_sub_table(query_db, gen_table) + await cls.set_pk_column(gen_table) + context = TemplateUtils.prepare_context(gen_table) + template_list = TemplateUtils.get_template_list(gen_table.tpl_category, gen_table.tpl_web_type) + output_files = [TemplateUtils.get_file_name(template, gen_table) for template in template_list] + + return [template_list, output_files, context, gen_table] + + @classmethod + def __get_gen_path(cls, gen_table: GenTableModel, template: str): + """ + 根据GenTableModel对象和模板名称生成路径 + + :param gen_table: GenTableModel对象 + :param template: 模板名称 + :return: 生成的路径 + """ + gen_path = gen_table.gen_path + if gen_path == '/': + return os.path.join(os.getcwd(), GenConfig.GEN_PATH, TemplateUtils.get_file_name(template, gen_table)) + else: + return os.path.join(gen_path, TemplateUtils.get_file_name(template, gen_table)) + + @classmethod + async def sync_db_services(cls, query_db: AsyncSession, table_name: str): + """ + 同步数据库service + + :param query_db: orm对象 + :param table_name: 业务表名称 + :return: 同步数据库结果 + """ + gen_table = await GenTableDao.get_gen_table_by_name(query_db, table_name) + table = GenTableModel(**CamelCaseUtil.transform_result(gen_table)) + table_columns = table.columns + table_column_map = {column.column_name: column for column in table_columns} + query_db_table_columns = await GenTableColumnDao.get_gen_db_table_columns_by_name(query_db, table_name) + db_table_columns = [ + GenTableColumnModel(**column) for column in CamelCaseUtil.transform_result(query_db_table_columns) + ] + if not db_table_columns: + raise ServiceException('同步数据失败,原表结构不存在') + db_table_column_names = [column.column_name for column in db_table_columns] + try: + for column in db_table_columns: + GenUtils.init_column_field(column, table) + if column.column_name in table_column_map: + prev_column = table_column_map[column.column_name] + column.column_id = prev_column.column_id + if column.list: + column.dict_type = prev_column.dict_type + column.query_type = prev_column.query_type + if ( + prev_column.is_required != '' + and not column.pk + and (column.insert or column.edit) + and (column.usable_column or column.super_column) + ): + column.is_required = prev_column.is_required + column.html_type = prev_column.html_type + await GenTableColumnDao.edit_gen_table_column_dao(query_db, column.model_dump(by_alias=True)) + else: + await GenTableColumnDao.add_gen_table_column_dao(query_db, column) + del_columns = [column for column in table_columns if column.column_name not in db_table_column_names] + if del_columns: + for column in del_columns: + await GenTableColumnDao.delete_gen_table_column_by_column_id_dao(query_db, column) + await query_db.commit() + return CrudResponseModel(is_success=True, message='同步成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def set_sub_table(cls, query_db: AsyncSession, gen_table: GenTableModel): + """ + 设置主子表信息 + + :param query_db: orm对象 + :param gen_table: 业务表信息 + :return: + """ + if gen_table.sub_table_name: + sub_table = await GenTableDao.get_gen_table_by_name(query_db, gen_table.sub_table_name) + gen_table.sub_table = GenTableModel(**CamelCaseUtil.transform_result(sub_table)) + + @classmethod + async def set_pk_column(cls, gen_table: GenTableModel): + """ + 设置主键列信息 + + :param gen_table: 业务表信息 + :return: + """ + for column in gen_table.columns: + if column.pk: + gen_table.pk_column = column + break + if gen_table.pk_column is None: + gen_table.pk_column = gen_table.columns[0] + if gen_table.tpl_category == GenConstant.TPL_SUB: + for column in gen_table.sub_table.columns: + if column.pk: + gen_table.sub_table.pk_column = column + break + if gen_table.sub_table.columns is None: + gen_table.sub_table.pk_column = gen_table.sub_table.columns[0] + + @classmethod + async def set_table_from_options(cls, gen_table: GenTableModel): + """ + 设置代码生成其他选项值 + + :param gen_table: 生成对象 + :return: 设置后的生成对象 + """ + params_obj = json.loads(gen_table.options) if gen_table.options else None + if params_obj: + gen_table.tree_code = params_obj.get(GenConstant.TREE_CODE) + gen_table.tree_parent_code = params_obj.get(GenConstant.TREE_PARENT_CODE) + gen_table.tree_name = params_obj.get(GenConstant.TREE_NAME) + gen_table.parent_menu_id = params_obj.get(GenConstant.PARENT_MENU_ID) + gen_table.parent_menu_name = params_obj.get(GenConstant.PARENT_MENU_NAME) + + return gen_table + + @classmethod + async def validate_edit(cls, edit_gen_table: EditGenTableModel): + """ + 编辑保存参数校验 + + :param edit_gen_table: 编辑业务表对象 + """ + if edit_gen_table.tpl_category == GenConstant.TPL_TREE: + params_obj = edit_gen_table.params.model_dump(by_alias=True) + + if GenConstant.TREE_CODE not in params_obj: + raise ServiceException(message='树编码字段不能为空') + elif GenConstant.TREE_PARENT_CODE not in params_obj: + raise ServiceException(message='树父编码字段不能为空') + elif GenConstant.TREE_NAME not in params_obj: + raise ServiceException(message='树名称字段不能为空') + elif edit_gen_table.tpl_category == GenConstant.TPL_SUB: + if not edit_gen_table.sub_table_name: + raise ServiceException(message='关联子表的表名不能为空') + elif not edit_gen_table.sub_table_fk_name: + raise ServiceException(message='子表关联的外键名不能为空') + + +class GenTableColumnService: + """ + 代码生成业务表字段服务层 + """ + + @classmethod + async def get_gen_table_column_list_by_table_id_services(cls, query_db: AsyncSession, table_id: int): + """ + 获取业务表字段列表信息service + + :param query_db: orm对象 + :param table_id: 业务表格id + :return: 业务表字段列表信息对象 + """ + gen_table_column_list_result = await GenTableColumnDao.get_gen_table_column_list_by_table_id(query_db, table_id) + + return [ + GenTableColumnModel(**gen_table_column) + for gen_table_column in CamelCaseUtil.transform_result(gen_table_column_list_result) + ] diff --git a/ruoyi-fastapi-backend/module_generator/templates/js/api.js.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/js/api.js.jinja2 new file mode 100644 index 0000000..3a2a5a9 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/js/api.js.jinja2 @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询{{ functionName }}列表 +export function list{{ BusinessName }}(query) { + return request({ + url: '/{{ moduleName }}/{{ businessName }}/list', + method: 'get', + params: query + }) +} + +// 查询{{ functionName }}详细 +export function get{{ BusinessName }}({{ pkColumn.python_field }}) { + return request({ + url: '/{{ moduleName }}/{{ businessName }}/' + {{ pkColumn.python_field }}, + method: 'get' + }) +} + +// 新增{{ functionName }} +export function add{{ BusinessName }}(data) { + return request({ + url: '/{{ moduleName }}/{{ businessName }}', + method: 'post', + data: data + }) +} + +// 修改{{ functionName }} +export function update{{ BusinessName }}(data) { + return request({ + url: '/{{ moduleName }}/{{ businessName }}', + method: 'put', + data: data + }) +} + +// 删除{{ functionName }} +export function del{{ BusinessName }}({{ pkColumn.python_field }}) { + return request({ + url: '/{{ moduleName }}/{{ businessName }}/' + {{ pkColumn.python_field }}, + method: 'delete' + }) +} diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/controller.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/controller.py.jinja2 new file mode 100644 index 0000000..4d5a12e --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/python/controller.py.jinja2 @@ -0,0 +1,125 @@ +{% set pkField = pkColumn.python_field %} +{% set pk_field = pkColumn.python_field | camel_to_snake %} +{% for column in columns %} +{% if column.python_field == "createTime" %} +from datetime import datetime +{% endif %} +{% endfor %} +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from {{ packageName }}.service.{{ businessName }}_service import {{ BusinessName }}Service +from {{ packageName }}.entity.vo.{{ businessName }}_vo import Delete{{ BusinessName }}Model, {{ BusinessName }}Model, {{ BusinessName }}PageQueryModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +{{ businessName }}Controller = APIRouter(prefix='/{{ moduleName }}/{{ businessName }}', dependencies=[Depends(LoginService.get_current_user)]) + + +@{{ businessName }}Controller.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:list'))] +) +async def get_{{ moduleName }}_{{ businessName }}_list( + request: Request, + {% if table.crud or table.sub %}{{ businessName }}_page_query{% elif table.tree %}{{ businessName }}_query{% endif %}: {{ BusinessName }}PageQueryModel = Depends({{ BusinessName }}PageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + {% if table.crud or table.sub %} + # 获取分页数据 + {{ businessName }}_page_query_result = await {{ BusinessName }}Service.get_{{ businessName }}_list_services(query_db, {{ businessName }}_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content={{ businessName }}_page_query_result) + {% elif table.tree %} + {{ businessName }}_query_result = await {{ BusinessName }}Service.get_{{ businessName }}_list_services(query_db, {{ businessName }}_query) + logger.info('获取成功') + + return ResponseUtil.success(data={{ businessName }}_query_result) + {% endif %} + + +@{{ businessName }}Controller.post('', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:add'))]) +@ValidateFields(validate_model='add_{{ businessName }}') +@Log(title='{{ functionName }}', business_type=BusinessType.INSERT) +async def add_{{ moduleName }}_{{ businessName }}( + request: Request, + add_{{ businessName }}: {{ BusinessName }}Model, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + {% for column in columns %} + {% if column.python_field == "createBy" %} + add_{{ businessName }}.create_by = current_user.user.user_name + {% elif column.python_field == "createTime" %} + add_{{ businessName }}.create_time = datetime.now() + {% elif column.python_field == "updateBy" %} + add_{{ businessName }}.update_by = current_user.user.user_name + {% elif column.python_field == "updateTime" %} + add_{{ businessName }}.update_time = datetime.now() + {% endif %} + {% endfor %} + add_{{ businessName }}_result = await {{ BusinessName }}Service.add_{{ businessName }}_services(query_db, add_{{ businessName }}) + logger.info(add_{{ businessName }}_result.message) + + return ResponseUtil.success(msg=add_{{ businessName }}_result.message) + + +@{{ businessName }}Controller.put('', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:edit'))]) +@ValidateFields(validate_model='edit_{{ businessName }}') +@Log(title='{{ functionName }}', business_type=BusinessType.UPDATE) +async def edit_{{ moduleName }}_{{ businessName }}( + request: Request, + edit_{{ businessName }}: {{ BusinessName }}Model, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_{{ businessName }}.update_by = current_user.user.user_name + edit_{{ businessName }}.update_time = datetime.now() + edit_{{ businessName }}_result = await {{ BusinessName }}Service.edit_{{ businessName }}_services(query_db, edit_{{ businessName }}) + logger.info(edit_{{ businessName }}_result.message) + + return ResponseUtil.success(msg=edit_{{ businessName }}_result.message) + + +@{{ businessName }}Controller.delete('/{% raw %}{{% endraw %}{{ pk_field }}s{% raw %}}{% endraw %}', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:remove'))]) +@Log(title='{{ functionName }}', business_type=BusinessType.DELETE) +async def delete_{{ moduleName }}_{{ businessName }}(request: Request, {{ pk_field }}s: str, query_db: AsyncSession = Depends(get_db)): + delete_{{ businessName }} = Delete{{ BusinessName }}Model({{ pkField }}s={{ pk_field }}s) + delete_{{ businessName }}_result = await {{ BusinessName }}Service.delete_{{ businessName }}_services(query_db, delete_{{ businessName }}) + logger.info(delete_{{ businessName }}_result.message) + + return ResponseUtil.success(msg=delete_{{ businessName }}_result.message) + + +@{{ businessName }}Controller.get( + '/{% raw %}{{% endraw %}{{ pk_field }}{% raw %}}{% endraw %}', response_model={{ BusinessName }}Model, dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:query'))] +) +async def query_detail_{{ moduleName }}_{{ businessName }}(request: Request, {{ pk_field }}: int, query_db: AsyncSession = Depends(get_db)): + {{ businessName }}_detail_result = await {{ BusinessName }}Service.{{ businessName }}_detail_services(query_db, {{ pk_field }}) + logger.info(f'获取{{ pk_field }}为{% raw %}{{% endraw %}{{ pk_field }}{% raw %}}{% endraw %}的信息成功') + + return ResponseUtil.success(data={{ businessName }}_detail_result) + + +@{{ businessName }}Controller.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:export'))]) +@Log(title='{{ functionName }}', business_type=BusinessType.EXPORT) +async def export_{{ moduleName }}_{{ businessName }}_list( + request: Request, + {{ businessName }}_page_query: {{ BusinessName }}PageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + {{ businessName }}_query_result = await {{ BusinessName }}Service.get_{{ businessName }}_list_services(query_db, {{ businessName }}_page_query, is_page=False) + {{ businessName }}_export_result = await {{ BusinessName }}Service.export_{{ businessName }}_list_services({% if dicts %}request, {% endif %}{{ businessName }}_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response({{ businessName }}_export_result)) diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/dao.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/dao.py.jinja2 new file mode 100644 index 0000000..3b186c5 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/python/dao.py.jinja2 @@ -0,0 +1,213 @@ +{% set pkField = pkColumn.python_field %} +{% set pk_field = pkColumn.python_field | camel_to_snake %} +{% set pkParentheseIndex = pkColumn.column_comment.find("(") %} +{% set pk_field_comment = pkColumn.column_comment[:pkParentheseIndex] if pkParentheseIndex != -1 else pkColumn.column_comment %} +{% for column in columns %} +{% if column.query and column.query_type == 'BETWEEN' and column.python_field == "createTime" %} +from datetime import datetime, time +{% endif %} +{% endfor %} +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession +{% if table.sub %} +from sqlalchemy.orm import selectinload +{% endif %} +{% if table.sub %} +from {{ packageName }}.entity.do.{{ businessName }}_do import {{ ClassName }}, {{ subClassName }} +from {{ packageName }}.entity.vo.{{ businessName }}_vo import {{ BusinessName }}Model, {{ BusinessName }}PageQueryModel, {{ subTable.business_name | capitalize }}Model +{% else %} +from {{ packageName }}.entity.do.{{ businessName }}_do import {{ ClassName }} +from {{ packageName }}.entity.vo.{{ businessName }}_vo import {{ BusinessName }}Model, {{ BusinessName }}PageQueryModel +{% endif %} +from utils.page_util import PageUtil + + +class {{ BusinessName }}Dao: + """ + {{ functionName }}模块数据库操作层 + """ + + @classmethod + async def get_{{ businessName }}_detail_by_id(cls, db: AsyncSession, {{ pk_field }}: int): + """ + 根据{{ pk_field_comment }}获取{{ functionName }}详细信息 + + :param db: orm对象 + :param {{ pk_field }}: {{ pk_field_comment }} + :return: {{ functionName }}信息对象 + """ + {{ businessName }}_info = ( + ( + await db.execute( + {% if table.sub %} + select({{ ClassName }}) + .options(selectinload({{ ClassName }}.{{ subclassName }}_list)) + {% else %} + select({{ ClassName }}) + {% endif %} + .where( + {{ ClassName }}.{{ pk_field }} == {{ pk_field }} + ) + ) + ) + .scalars() + .first() + ) + + return {{ businessName }}_info + + @classmethod + async def get_{{ businessName }}_detail_by_info(cls, db: AsyncSession, {{ businessName }}: {{ BusinessName }}Model): + """ + 根据{{ functionName }}参数获取{{ functionName }}信息 + + :param db: orm对象 + :param {{ businessName }}: {{ functionName }}参数对象 + :return: {{ functionName }}信息对象 + """ + {{ businessName }}_info = ( + ( + await db.execute( + select({{ ClassName }}).where( + {% for column in columns %} + {% if column.unique %} + {{ ClassName }}.{{ column.python_field | camel_to_snake }} == {{ businessName }}.{{ column.python_field | camel_to_snake }} if {{ businessName }}.{{ column.python_field | camel_to_snake }} else True, + {% endif %} + {% endfor %} + ) + ) + ) + .scalars() + .first() + ) + + return {{ businessName }}_info + + @classmethod + async def get_{{ businessName }}_list(cls, db: AsyncSession, query_object: {{ BusinessName }}PageQueryModel, is_page: bool = False): + """ + 根据查询参数获取{{ functionName }}列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: {{ functionName }}列表信息对象 + """ + query = ( + {% if table.sub %} + select({{ ClassName }}) + .options(selectinload({{ ClassName }}.{{ subclassName }}_list)) + {% else %} + select({{ ClassName }}) + {% endif %} + .where( + {% for column in columns %} + {% set field = column.python_field | camel_to_snake %} + {% if column.query %} + {% if column.query_type == "EQ" %} + {{ ClassName }}.{{ field }} == query_object.{{ field }} if query_object.{{ field }} else True, + {% elif column.query_type == "NE" %} + {{ ClassName }}.{{ field }} != query_object.{{ field }} if query_object.{{ field }} else True, + {% elif column.query_type == "GT" %} + {{ ClassName }}.{{ field }} > query_object.{{ field }} if query_object.{{ field }} else True, + {% elif column.query_type == "GTE" %} + {{ ClassName }}.{{ field }} >= query_object.{{ field }} if query_object.{{ field }} else True, + {% elif column.query_type == "LT" %} + {{ ClassName }}.{{ field }} < query_object.{{ field }} if query_object.{{ field }} else True, + {% elif column.query_type == "LTE" %} + {{ ClassName }}.{{ field }} <= query_object.{{ field }} if query_object.{{ field }} else True, + {% elif column.query_type == "LIKE" %} + {{ ClassName }}.{{ field }}.like(f'%{% raw %}{{% endraw %}query_object.{{ field }}{% raw %}}{% endraw %}%') if query_object.{{ field }} else True, + {% elif column.html_type == "datetime" and column.query_type == "BETWEEN" %} + {{ ClassName }}.{{ field }}.between( + datetime.combine(datetime.strptime(query_object.begin_{{ column.column_name }}, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_{{ column.column_name }}, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_{{ column.column_name }} and query_object.end_{{ column.column_name }} + else True, + {% endif %} + {% endif %} + {% endfor %} + ) + .order_by({{ ClassName }}.{{ pk_field }}) + .distinct() + ) + {{ businessName }}_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return {{ businessName }}_list + + @classmethod + async def add_{{ businessName }}_dao(cls, db: AsyncSession, {{ businessName }}: {{ BusinessName }}Model): + """ + 新增{{ functionName }}数据库操作 + + :param db: orm对象 + :param {{ businessName }}: {{ functionName }}对象 + :return: + """ + db_{{ businessName }} = {{ ClassName }}(**{{ businessName }}.model_dump(exclude={% raw %}{{% endraw %}{% if table.sub %}'{{ subclassName }}_list', {% endif %}{% for column in columns %}{% if not column.insert and column.column_name not in column_not_add_show + column_not_edit_show %}'{{ column.python_field | camel_to_snake }}'{% if not loop.last %}, {% endif %}{% endif %}{% endfor %}{% raw %}}{% endraw %})) + db.add(db_{{ businessName }}) + await db.flush() + + return db_{{ businessName }} + + @classmethod + async def edit_{{ businessName }}_dao(cls, db: AsyncSession, {{ businessName }}: dict): + """ + 编辑{{ functionName }}数据库操作 + + :param db: orm对象 + :param {{ businessName }}: 需要更新的{{ functionName }}字典 + :return: + """ + await db.execute(update({{ ClassName }}), [{{ businessName }}]) + + @classmethod + async def delete_{{ businessName }}_dao(cls, db: AsyncSession, {{ businessName }}: {{ BusinessName }}Model): + """ + 删除{{ functionName }}数据库操作 + + :param db: orm对象 + :param {{ businessName }}: {{ functionName }}对象 + :return: + """ + await db.execute(delete({{ ClassName }}).where({{ ClassName }}.{{ pk_field }}.in_([{{ businessName }}.{{ pk_field }}]))) + + {% if table.sub %} + @classmethod + async def add_{{ subTable.business_name }}_dao(cls, db: AsyncSession, {{ subTable.business_name }}: {{ subTable.business_name | capitalize }}Model): + """ + 新增{{ subTable.function_name }}数据库操作 + + :param db: orm对象 + :param {{ subTable.business_name }}: {{ subTable.function_name }}对象 + :return: + """ + db_{{ subTable.business_name }} = {{ subClassName }}(**{{ subTable.business_name }}.model_dump()) + db.add(db_{{ subTable.business_name }}) + await db.flush() + + return db_{{ subTable.business_name }} + + @classmethod + async def edit_{{ subTable.business_name }}_dao(cls, db: AsyncSession, {{ subTable.business_name }}: dict): + """ + 编辑{{ subTable.function_name }}数据库操作 + + :param db: orm对象 + :param {{ subTable.business_name }}: 需要更新的{{ subTable.function_name }}字典 + :return: + """ + await db.execute(update({{ subClassName }}), [{{ subTable.business_name }}]) + + @classmethod + async def delete_{{ subTable.business_name }}_dao(cls, db: AsyncSession, {{ subTable.business_name }}: {{ subTable.business_name | capitalize }}Model): + """ + 删除{{ subTable.function_name }}数据库操作 + + :param db: orm对象 + :param {{ subTable.business_name }}: {{ subTable.function_name }}对象 + :return: + """ + await db.execute(delete({{ subClassName }}).where({{ subClassName }}.{{ subTable.pk_column.python_field | camel_to_snake }}.in_([{{ subTable.business_name }}.{{ subTable.pk_column.python_field | camel_to_snake }}]))) + {% endif %} diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/do.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/do.py.jinja2 new file mode 100644 index 0000000..22c23c4 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/python/do.py.jinja2 @@ -0,0 +1,41 @@ +{% for do_import in doImportList %} +{{ do_import }} +{% endfor %} +{% if table.sub %} +from sqlalchemy.orm import relationship +{% endif %} +from config.database import Base + + +class {{ ClassName }}(Base): + """ + {{ functionName }}表 + """ + + __tablename__ = '{{ tableName }}' + + {% for column in columns %} + {{ column.column_name }} = Column({{ column.column_type | get_sqlalchemy_type }}, {% if column.pk %}primary_key=True, {% endif %}{% if column.increment %}autoincrement=True, {% endif %}{% if column.required or column.pk %}nullable=False{% else %}nullable=True{% endif %}, comment='{{ column.column_comment }}') + {% endfor %} + + {% if table.sub %} + {{ subclassName }}_list = relationship('{{ subClassName }}', back_populates='{{ businessName }}') + {% endif %} + + +{% if table.sub %} +class {{ subClassName }}(Base): + """ + {{ subTable.function_name }}表 + """ + + __tablename__ = '{{ subTableName }}' + + {% for column in subTable.columns %} + {{ column.column_name }} = Column({{ column.column_type | get_sqlalchemy_type }}, {% if column.column_name == subTableFkName %}ForeignKey('{{ tableName }}.{{ subTableFkName }}'), {% endif %}{% if column.pk %}primary_key=True, {% endif %}{% if column.increment %}autoincrement=True, {% endif %}{% if column.required %}nullable=True{% else %}nullable=False{% endif %}, comment='{{ column.column_comment }}') + {% endfor %} + + {% if table.sub %} + {{ businessName }} = relationship('{{ ClassName }}', back_populates='{{ subclassName }}_list') + {% endif %} +{% endif %} diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/service.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/service.py.jinja2 new file mode 100644 index 0000000..5726c5d --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/python/service.py.jinja2 @@ -0,0 +1,211 @@ +{% set pkField = pkColumn.python_field %} +{% set pk_field = pkColumn.python_field | camel_to_snake %} +{% set pkParentheseIndex = pkColumn.column_comment.find("(") %} +{% set pk_field_comment = pkColumn.column_comment[:pkParentheseIndex] if pkParentheseIndex != -1 else pkColumn.column_comment %} +{% if dicts %} +from fastapi import Request +{% endif %} +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +{% if dicts %} +from module_admin.service.dict_service import DictDataService +{% endif %} +from {{ packageName }}.dao.{{ businessName }}_dao import {{ BusinessName }}Dao +from {{ packageName }}.entity.vo.{{ businessName }}_vo import Delete{{ BusinessName }}Model, {{ BusinessName }}Model, {{ BusinessName }}PageQueryModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class {{ BusinessName }}Service: + """ + {{ functionName }}模块服务层 + """ + + @classmethod + async def get_{{ businessName }}_list_services( + cls, query_db: AsyncSession, query_object: {{ BusinessName }}PageQueryModel, is_page: bool = False + ): + """ + 获取{{ functionName }}列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: {{ functionName }}列表信息对象 + """ + {{ businessName }}_list_result = await {{ BusinessName }}Dao.get_{{ businessName }}_list(query_db, query_object, is_page) + + return {{ businessName }}_list_result + + {% for column in columns %} + {% set parentheseIndex = column.column_comment.find("(") %} + {% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %} + {% if column.unique %} + @classmethod + async def check_{{ column.python_field | camel_to_snake }}_unique_services(cls, query_db: AsyncSession, page_object: {{ BusinessName }}Model): + """ + 检查{{ comment }}是否唯一service + + :param query_db: orm对象 + :param page_object: {{ functionName }}对象 + :return: 校验结果 + """ + {{ pk_field }} = -1 if page_object.{{ pk_field }} is None else page_object.{{ pk_field }} + {{ businessName }} = await {{ BusinessName }}Dao.get_{{ businessName }}_detail_by_info(query_db, {{ BusinessName }}Model({{ column.python_field }}=page_object.{{ column.python_field | camel_to_snake }})) + if {{ businessName }} and {{ businessName }}.{{ pk_field }} != {{ pk_field }}: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + {% if not loop.last %}{{ "\n" }}{% endif %} + {% endif %} + {% endfor %} + + @classmethod + async def add_{{ businessName }}_services(cls, query_db: AsyncSession, page_object: {{ BusinessName }}Model): + """ + 新增{{ functionName }}信息service + + :param query_db: orm对象 + :param page_object: 新增{{ functionName }}对象 + :return: 新增{{ functionName }}校验结果 + """ + {% for column in columns %} + {% set parentheseIndex = column.column_comment.find("(") %} + {% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %} + {% if column.unique %} + if not await cls.check_{{ column.python_field | camel_to_snake }}_unique_services(query_db, page_object): + raise ServiceException(message=f'新增{{ functionName }}{page_object.{{ column.python_field | camel_to_snake }}}失败,{{ comment }}已存在') + {% endif %} + {% endfor %} + try: + {% if table.sub %} + add_{{ businessName }} = await {{ BusinessName }}Dao.add_{{ businessName }}_dao(query_db, page_object) + if add_{{ businessName }}: + for sub_table in page_object.{{ subclassName }}_list: + await {{ BusinessName }}Dao.add_{{ subTable.business_name }}_dao(query_db, sub_table) + {% else %} + await {{ BusinessName }}Dao.add_{{ businessName }}_dao(query_db, page_object) + {% endif %} + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_{{ businessName }}_services(cls, query_db: AsyncSession, page_object: {{ BusinessName }}Model): + """ + 编辑{{ functionName }}信息service + + :param query_db: orm对象 + :param page_object: 编辑{{ functionName }}对象 + :return: 编辑{{ functionName }}校验结果 + """ + edit_{{ businessName }} = page_object.model_dump(exclude_unset=True, exclude={% raw %}{{% endraw %}{% if table.sub %}'{{ subclassName }}_list', {% endif %}{% for column in columns %}{% if not column.edit and not column.pk and column.column_name not in column_not_edit_show %}'{{ column.python_field | camel_to_snake }}'{% if not loop.last %}, {% endif %}{% endif %}{% endfor %}{% raw %}}{% endraw %}) + {{ businessName }}_info = await cls.{{ businessName }}_detail_services(query_db, page_object.{{ pk_field }}) + if {{ businessName }}_info.{{ pk_field }}: + {% for column in columns %} + {% set parentheseIndex = column.column_comment.find("(") %} + {% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %} + {% if column.unique %} + if not await cls.check_{{ column.python_field | camel_to_snake }}_unique_services(query_db, page_object): + raise ServiceException(message=f'修改{{ functionName }}{page_object.{{ column.python_field | camel_to_snake }}}失败,{{ comment }}已存在') + {% endif %} + {% endfor %} + try: + await {{ BusinessName }}Dao.edit_{{ businessName }}_dao(query_db, edit_{{ businessName }}) + {% if table.sub %} + for sub_table in {{ businessName }}_info.{{ subclassName }}_list: + await {{ BusinessName }}Dao.delete_{{ subTable.business_name }}_dao(query_db, sub_table) + for sub_table in page_object.{{ subclassName }}_list: + await {{ BusinessName }}Dao.add_{{ subTable.business_name }}_dao(query_db, sub_table) + {% endif %} + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='{{ functionName }}不存在') + + @classmethod + async def delete_{{ businessName }}_services(cls, query_db: AsyncSession, page_object: Delete{{ BusinessName }}Model): + """ + 删除{{ functionName }}信息service + + :param query_db: orm对象 + :param page_object: 删除{{ functionName }}对象 + :return: 删除{{ functionName }}校验结果 + """ + if page_object.{{ pk_field }}s: + {{ pk_field }}_list = page_object.{{ pk_field }}s.split(',') + try: + for {{ pk_field }} in {{ pk_field }}_list: + {% if table.sub %} + {{ businessName }} = await cls.{{ businessName }}_detail_services(query_db, int({{ pk_field }})) + for sub_table in {{ businessName }}.{{ subclassName }}_list: + await {{ BusinessName }}Dao.delete_{{ subTable.business_name }}_dao(query_db, sub_table) + {% endif %} + await {{ BusinessName }}Dao.delete_{{ businessName }}_dao(query_db, {{ BusinessName }}Model({{ pkField }}={{ pk_field }})) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入{{ pk_field_comment }}为空') + + @classmethod + async def {{ businessName }}_detail_services(cls, query_db: AsyncSession, {{ pk_field }}: int): + """ + 获取{{ functionName }}详细信息service + + :param query_db: orm对象 + :param {{ pk_field }}: {{ pk_field_comment }} + :return: {{ pk_field_comment }}对应的信息 + """ + {{ businessName }} = await {{ BusinessName }}Dao.get_{{ businessName }}_detail_by_id(query_db, {{ pk_field }}={{ pk_field }}) + if {{ businessName }}: + result = {{ BusinessName }}Model(**CamelCaseUtil.transform_result({{ businessName }})) + else: + result = {{ BusinessName }}Model(**dict()) + + return result + + @staticmethod + async def export_{{ businessName }}_list_services({% if dicts %}request: Request, {% endif %}{{ businessName }}_list: List): + """ + 导出{{ functionName }}信息service + + :param {{ businessName }}_list: {{ functionName }}信息列表 + :return: {{ functionName }}信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + {% for column in columns %} + {% set parentheseIndex = column.column_comment.find("(") %} + {% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %} + '{{ column.python_field }}': '{{ comment }}', + {% endfor %} + } + {% if dicts %} + {% for dict_type in dicts.split(", ") %} + {{ dict_type[1:-1] }}_list = await DictDataService.query_dict_data_list_from_cache_services( + request.app.state.redis, dict_type={{ dict_type }} + ) + {{ dict_type[1:-1] }}_option = [dict(label=item.get('dictLabel'), value=item.get('dictValue')) for item in {{ dict_type[1:-1] }}_list] + {{ dict_type[1:-1] }}_option_dict = {item.get('value'): item for item in {{ dict_type[1:-1] }}_option} + {% endfor %} + for item in {{ businessName }}_list: + {% for column in columns %} + {% if column.dict_type %} + if str(item.get('{{ column.python_field }}')) in {{ column.dict_type }}_option_dict.keys(): + item['{{ column.python_field }}'] = {{ column.dict_type }}_option_dict.get(str(item.get('{{ column.python_field }}'))).get('label') + {% endif %} + {% endfor %} + {% endif %} + binary_data = ExcelUtil.export_list2excel({{ businessName }}_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/vo.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/vo.py.jinja2 new file mode 100644 index 0000000..47abbf5 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/python/vo.py.jinja2 @@ -0,0 +1,178 @@ +{% set pkField = pkColumn.python_field %} +{% set pk_field = pkColumn.python_field | camel_to_snake %} +{% set pkParentheseIndex = pkColumn.column_comment.find("(") %} +{% set pk_field_comment = pkColumn.column_comment[:pkParentheseIndex] if pkParentheseIndex != -1 else pkColumn.column_comment %} +{% set vo_field_required = namespace(has_required=False) %} +{% set vo_field_daterange = namespace(has_daterange=False) %} +{% for column in columns %} +{% if column.required %} + {% set vo_field_required.has_required = True %} +{% endif %} +{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %} + {% set vo_field_daterange.has_daterange = True %} +{% endif %} +{% endfor %} +{% set sub_vo_field_required = namespace(has_required=False) %} +{% if table.sub %} +{% for sub_column in subTable.columns %} +{% if sub_column.required %} + {% set sub_vo_field_required.has_required = True %} +{% endif %} +{% endfor %} +{% endif %} +{% for vo_import in voImportList %} +{{ vo_import }} +{% endfor %} +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +{% if vo_field_required.has_required or sub_vo_field_required.has_required %} +from pydantic_validation_decorator import NotBlank +{% endif %} +{% if table.sub %} +from typing import List, Optional +{% else %} +from typing import Optional +{% endif %} +from module_admin.annotation.pydantic_annotation import as_query + + +{% if table.sub %} +class {{ BusinessName }}BaseModel(BaseModel): + """ + {{ functionName }}表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + {% for column in columns %} + {{ column.column_name }}: Optional[{{ column.python_type }}] = Field(default=None, description='{{ column.column_comment }}') + {% endfor %} + + {% for column in columns %} + {% if column.required %} + {% set parentheseIndex = column.column_comment.find("(") %} + {% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %} + @NotBlank(field_name='{{ column.column_name }}', message='{{ comment }}不能为空') + def get_{{ column.column_name }}(self): + return self.{{ column.column_name }} + {% if not loop.last %}{{ "\n" }}{% endif %} + {% endif %} + {% endfor %} + + {% if vo_field_required.has_required %} + def validate_fields(self): + {% for column in columns %} + {% if column.required %} + self.get_{{ column.column_name }}() + {% endif %} + {% endfor %} + {% endif %} +{% endif %} + + +class {{ BusinessName }}Model({% if table.sub %}{{ BusinessName }}BaseModel{% else %}BaseModel{% endif %}): + """ + {{ functionName }}表对应pydantic模型 + """ + {% if not table.sub %} + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + {% for column in columns %} + {{ column.column_name }}: Optional[{{ column.python_type }}] = Field(default=None, description='{{ column.column_comment }}') + {% endfor %} + {% endif %} + {% if table.sub %} + {{ subclassName }}_list: Optional[List['{{ subTable.business_name | capitalize }}Model']] = Field(default=None, description='子表列信息') + {% endif %} + + {% if not table.sub %} + {% for column in columns %} + {% if column.required %} + {% set parentheseIndex = column.column_comment.find("(") %} + {% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %} + @NotBlank(field_name='{{ column.column_name }}', message='{{ comment }}不能为空') + def get_{{ column.column_name }}(self): + return self.{{ column.column_name }} + {% if not loop.last %}{{ "\n" }}{% endif %} + {% endif %} + {% endfor %} + + {% if vo_field_required.has_required %} + def validate_fields(self): + {% for column in columns %} + {% if column.required %} + self.get_{{ column.column_name }}() + {% endif %} + {% endfor %} + {% endif %} + {% endif %} + + +{% if table.sub %} +class {{ subTable.business_name | capitalize }}Model(BaseModel): + """ + {{ subTable.function_name }}表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + {% for sub_column in subTable.columns %} + {{ sub_column.column_name }}: Optional[{{ sub_column.python_type }}] = Field(default=None, description='{{ sub_column.column_comment}}') + {% endfor %} + + {% for sub_column in subTable.columns %} + {% if sub_column.required %} + {% set parentheseIndex = sub_column.column_comment.find("(") %} + {% set comment = sub_column.column_comment[:parentheseIndex] if parentheseIndex != -1 else sub_column.column_comment %} + @NotBlank(field_name='{{ sub_column.column_name }}', message='{{ comment }}不能为空') + def get_{{ sub_column.column_name }}(self): + return self.{{ sub_column.column_name }} + {% if not loop.last %}{{ "\n" }}{% endif %} + {% endif %} + {% endfor %} + + {% if sub_vo_field_required.has_required %} + def validate_fields(self): + {% for sub_column in subTable.columns %} + {% if sub_column.required %} + self.get_{{ sub_column.column_name }}() + {% endif %} + {% endfor %} + {% endif %} +{% endif %} + + +class {{ BusinessName }}QueryModel({% if table.sub %}{{ BusinessName }}BaseModel{% else %}{{ BusinessName }}Model{% endif %}): + """ + {{ functionName }}不分页查询模型 + """ + {% if vo_field_daterange.has_daterange %} + {% for column in columns %} + {% if column.html_type == "datetime" and column.query_type == "BETWEEN" %} + begin_{{ column.column_name }}: Optional[str] = Field(default=None, description='开始{{ column.column_comment }}') + end_{{ column.column_name }}: Optional[str] = Field(default=None, description='结束{{ column.column_comment }}') + {% endif %} + {% endfor %} + {% else %} + pass + {% endif %} + + +@as_query +class {{ BusinessName }}PageQueryModel({{ BusinessName }}QueryModel): + """ + {{ functionName }}分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class Delete{{ BusinessName }}Model(BaseModel): + """ + 删除{{ functionName }}模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + {{ pk_field }}s: str = Field(description='需要删除的{{ pk_field_comment }}') diff --git a/ruoyi-fastapi-backend/module_generator/templates/sql/sql.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/sql/sql.jinja2 new file mode 100644 index 0000000..dd4099f --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/sql/sql.jinja2 @@ -0,0 +1,47 @@ +{% if dbType == 'postgresql' %} +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}', '{{ parentMenuId }}', '1', '{{ businessName }}', '{{ moduleName }}/{{ businessName }}/index', 1, 0, 'C', '0', '0', '{{ permissionPrefix }}:list', '#', 'admin', current_timestamp, '', null, '{{ functionName }}菜单'); + +-- 按钮父菜单ID +select max(menu_id) from sys_menu; + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}查询', max(menu_id), '1', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:query', '#', 'admin', current_timestamp, '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}新增', max(menu_id), '2', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:add', '#', 'admin', current_timestamp, '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}修改', max(menu_id), '3', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:edit', '#', 'admin', current_timestamp, '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}删除', max(menu_id), '4', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:remove', '#', 'admin', current_timestamp, '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}导出', max(menu_id), '5', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:export', '#', 'admin', current_timestamp, '', null, ''); +{% else %} +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}', '{{ parentMenuId }}', '1', '{{ businessName }}', '{{ moduleName }}/{{ businessName }}/index', 1, 0, 'C', '0', '0', '{{ permissionPrefix }}:list', '#', 'admin', sysdate(), '', null, '{{ functionName }}菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:export', '#', 'admin', sysdate(), '', null, ''); +{% endif %} \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_generator/templates/vue/index-tree.vue.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/vue/index-tree.vue.jinja2 new file mode 100644 index 0000000..eb8ceaf --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/vue/index-tree.vue.jinja2 @@ -0,0 +1,496 @@ + + + \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_generator/templates/vue/index.vue.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/vue/index.vue.jinja2 new file mode 100644 index 0000000..39f861d --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/vue/index.vue.jinja2 @@ -0,0 +1,591 @@ + + + \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index-tree.vue.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index-tree.vue.jinja2 new file mode 100644 index 0000000..52eeda9 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index-tree.vue.jinja2 @@ -0,0 +1,463 @@ + + + \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index.vue.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index.vue.jinja2 new file mode 100644 index 0000000..73dae7d --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index.vue.jinja2 @@ -0,0 +1,580 @@ + + + \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_task/__init__.py b/ruoyi-fastapi-backend/module_task/__init__.py new file mode 100644 index 0000000..1f4b412 --- /dev/null +++ b/ruoyi-fastapi-backend/module_task/__init__.py @@ -0,0 +1 @@ +from . import scheduler_test # noqa: F401 diff --git a/ruoyi-fastapi-backend/module_task/scheduler_test.py b/ruoyi-fastapi-backend/module_task/scheduler_test.py new file mode 100644 index 0000000..b282732 --- /dev/null +++ b/ruoyi-fastapi-backend/module_task/scheduler_test.py @@ -0,0 +1,19 @@ +from datetime import datetime + + +def job(*args, **kwargs): + """ + 定时任务执行同步函数示例 + """ + print(args) + print(kwargs) + print(f'{datetime.now()}同步函数执行了') + + +async def async_job(*args, **kwargs): + """ + 定时任务执行异步函数示例 + """ + print(args) + print(kwargs) + print(f'{datetime.now()}异步函数执行了') diff --git a/ruoyi-fastapi-backend/output.mp4 b/ruoyi-fastapi-backend/output.mp4 new file mode 100644 index 0000000..9283c30 Binary files /dev/null and b/ruoyi-fastapi-backend/output.mp4 differ diff --git a/ruoyi-fastapi-backend/requirements-pg.txt b/ruoyi-fastapi-backend/requirements-pg.txt new file mode 100644 index 0000000..095197f --- /dev/null +++ b/ruoyi-fastapi-backend/requirements-pg.txt @@ -0,0 +1,18 @@ +APScheduler==3.11.0 +asyncpg==0.30.0 +DateTime==5.5 +fastapi[all]==0.115.8 +loguru==0.7.3 +openpyxl==3.1.5 +pandas==2.2.3 +passlib[bcrypt]==1.7.4 +Pillow==11.1.0 +psutil==7.0.0 +pydantic-validation-decorator==0.1.4 +PyJWT[crypto]==2.10.1 +psycopg2==2.9.10 +redis==5.2.1 +requests==2.32.3 +SQLAlchemy[asyncio]==2.0.38 +sqlglot[rs]==26.6.0 +user-agents==2.2.0 diff --git a/ruoyi-fastapi-backend/requirements.txt b/ruoyi-fastapi-backend/requirements.txt new file mode 100644 index 0000000..2ac675b --- /dev/null +++ b/ruoyi-fastapi-backend/requirements.txt @@ -0,0 +1,19 @@ +APScheduler==3.11.0 +asyncmy==0.2.10 +DateTime==5.5 +fastapi[all]==0.115.8 +httpx==0.27.2 +loguru==0.7.3 +openpyxl==3.1.5 +pandas==2.2.3 +passlib[bcrypt]==1.7.4 +Pillow==11.1.0 +psutil==7.0.0 +pydantic-validation-decorator==0.1.4 +PyJWT[crypto]==2.10.1 +PyMySQL==1.1.1 +redis==5.2.1 +requests==2.32.3 +SQLAlchemy[asyncio]==2.0.38 +sqlglot[rs]==26.6.0 +user-agents==2.2.0 diff --git a/ruoyi-fastapi-backend/ruff.toml b/ruoyi-fastapi-backend/ruff.toml new file mode 100644 index 0000000..ea95368 --- /dev/null +++ b/ruoyi-fastapi-backend/ruff.toml @@ -0,0 +1,4 @@ +line-length = 120 + +[format] +quote-style = "single" \ No newline at end of file diff --git a/ruoyi-fastapi-backend/server.py b/ruoyi-fastapi-backend/server.py new file mode 100644 index 0000000..37bb7de --- /dev/null +++ b/ruoyi-fastapi-backend/server.py @@ -0,0 +1,131 @@ +from contextlib import asynccontextmanager +from fastapi import FastAPI +from config.env import AppConfig +from config.get_db import init_create_table +from config.get_redis import RedisUtil +from config.get_scheduler import SchedulerUtil +from exceptions.handle import handle_exception +from middlewares.handle import handle_middleware +from module_admin.controller.cache_controller import cacheController # noqa: F401 +from module_admin.controller.captcha_controller import captchaController +from module_admin.controller.common_controller import commonController +from module_admin.controller.config_controller import configController # noqa: F401 +from module_admin.controller.dept_controller import deptController # noqa: F401 +from module_admin.controller.dict_controller import dictController +from module_admin.controller.log_controller import logController +from module_admin.controller.login_controller import loginController +from module_admin.controller.job_controller import jobController +from module_admin.controller.menu_controller import menuController +from module_admin.controller.notice_controller import noticeController # noqa: F401 +from module_admin.controller.online_controller import onlineController +from module_admin.controller.post_controler import postController # noqa: F401 +from module_admin.controller.role_controller import roleController +from module_admin.controller.server_controller import serverController +from module_admin.controller.user_controller import userController +from module_admin.controller.test_controller import testController # noqa: F401 +from module_admin.controller.words_controller import wordsController +from module_admin.controller.info_controller import infoController +from module_generator.controller.gen_controller import genController +from module_admin.controller.message_controller import messageController +from module_admin.controller.robot_role_pairing_controller import pairingController +from module_admin.controller.explanation_content_controller import explanationContentController +from module_admin.controller.explanation_style_robot_pair_controller import explanation_style_robot_pairController +from module_admin.controller.sys_statistics_controller import sys_statisticsController +from module_admin.controller.identification_statistics_controller import identification_statisticsController +# from module_admin.controller.identify_record_controller import identify_recordController +from module_admin.controller.ragflow_controller import ragflowController +from module_admin.controller.robot_action_controller import robot_actionController +from module_admin.controller.identification_record_controller import identification_recordController +from module_admin.controller.door_controller import doorController +from module_admin.controller.haikang_controller import haikang_controller +from module_admin.controller.sys_alert_controller import sys_alertController # noqa: F401 +from module_admin.controller.compreface_controller import comprefaceController + +from sub_applications.handle import handle_sub_applications +from utils.common_util import worship +from utils.log_util import logger +from module_admin.controller.scheduler_controller import init_scheduled_tasks # noqa: F401 +from utils.ragflow_client_manager import init_ragflow_client, shutdown_ragflow_client + + +# 生命周期事件 +@asynccontextmanager +async def lifespan(app: FastAPI): + logger.info(f'{AppConfig.app_name}开始启动') + worship() + await init_create_table() + app.state.redis = await RedisUtil.create_redis_pool() + await RedisUtil.init_sys_dict(app.state.redis) + await RedisUtil.init_sys_config(app.state.redis) + await SchedulerUtil.init_system_scheduler() + await init_ragflow_client() + # 初始化自定义定时任务 + # init_scheduled_tasks() + logger.info(f'{AppConfig.app_name}启动成功') + yield + await RedisUtil.close_redis_pool(app) + await SchedulerUtil.close_system_scheduler() + await shutdown_ragflow_client() + + +# 初始化FastAPI对象 +app = FastAPI( + title=AppConfig.app_name, + description=f'{AppConfig.app_name}接口文档', + version=AppConfig.app_version, + lifespan=lifespan, + docs_url=AppConfig.app_docs_url, + redoc_url=AppConfig.app_redoc_url, + openapi_url=AppConfig.app_openapi_url, +) + + +# 挂载子应用 +handle_sub_applications(app) +# 加载中间件处理方法 +handle_middleware(app) +# 加载全局异常处理方法 +handle_exception(app) + + +# 加载路由列表 +controller_list = [ + {'router': loginController, 'tags': ['登录模块']}, + {'router': captchaController, 'tags': ['验证码模块']}, + {'router': userController, 'tags': ['系统管理-用户管理']}, + {'router': roleController, 'tags': ['系统管理-角色管理']}, + {'router': menuController, 'tags': ['系统管理-菜单管理']}, + # {'router': deptController, 'tags': ['系统管理-部门管理']}, + # {'router': postController, 'tags': ['系统管理-岗位管理']}, + {'router': dictController, 'tags': ['系统管理-字典管理']}, + # {'router': configController, 'tags': ['系统管理-参数管理']}, + # {'router': noticeController, 'tags': ['系统管理-通知公告管理']}, + {'router': logController, 'tags': ['系统管理-日志管理']}, + {'router': onlineController, 'tags': ['系统监控-在线用户']}, + {'router': jobController, 'tags': ['系统监控-定时任务']}, + {'router': serverController, 'tags': ['系统监控-菜单管理']}, + # {'router': cacheController, 'tags': ['系统监控-缓存监控']}, + {'router': commonController, 'tags': ['通用模块']}, + {'router': genController, 'tags': ['代码生成']}, + # {'router': testController, 'tags': ['测试模块']}, + {'router': wordsController, 'tags': ['引导词管理']}, + {'router': infoController, 'tage':['机器人信息管理']}, + {'router': messageController, 'tags': ['消息管理']}, + {'router': pairingController, 'tags': ['机器人角色']}, + {'router': explanationContentController, 'tags': ['讲解管理']}, + {'router': explanation_style_robot_pairController, 'tags': ['讲解风格管理']}, + {'router': sys_statisticsController, 'tags': ['系统统计数据管理']}, + # {'router': identify_recordController, 'tags': ['识别记录管理']}, + {'router': ragflowController, 'tags': ['ragflow管理']}, + {'router': robot_actionController, 'tags': ['机器人动作管理']}, + {'router': identification_recordController, 'tags': ['识别记录管理']}, + {'router': doorController, 'tags': ['门禁设备管理']}, + {'router': identification_statisticsController, 'tags': ['识别统计管理']}, + {'router': haikang_controller, 'tags': ['海康']}, + {'router': comprefaceController, 'tags': ['人脸识别管理']}, + # {'router': sys_alertController, 'tags': ['系统告警消息']} + +] + +for controller in controller_list: + app.include_router(router=controller.get('router'), tags=controller.get('tags')) diff --git a/ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql b/ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql new file mode 100644 index 0000000..b3731aa --- /dev/null +++ b/ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql @@ -0,0 +1,1047 @@ +-- ---------------------------- +-- 1、部门表 +-- ---------------------------- +drop table if exists sys_dept; +create table sys_dept ( + dept_id bigserial, + parent_id bigint default 0, + ancestors varchar(50) default '', + dept_name varchar(30) default '', + order_num int4 default 0, + leader varchar(20) default null, + phone varchar(11) default null, + email varchar(50) default null, + status char(1) default '0', + del_flag char(1) default '0', + create_by varchar(64) default '', + create_time timestamp(0), + update_by varchar(64) default '', + update_time timestamp(0), + primary key (dept_id) +); +alter sequence sys_dept_dept_id_seq restart 200; +comment on column sys_dept.dept_id is '部门id'; +comment on column sys_dept.parent_id is '父部门id'; +comment on column sys_dept.ancestors is '祖级列表'; +comment on column sys_dept.dept_name is '部门名称'; +comment on column sys_dept.order_num is '显示顺序'; +comment on column sys_dept.leader is '负责人'; +comment on column sys_dept.phone is '联系电话'; +comment on column sys_dept.email is '邮箱'; +comment on column sys_dept.status is '部门状态(0正常 1停用)'; +comment on column sys_dept.del_flag is '删除标志(0代表存在 2代表删除)'; +comment on column sys_dept.create_by is '创建者'; +comment on column sys_dept.create_time is '创建时间'; +comment on column sys_dept.update_by is '更新者'; +comment on column sys_dept.update_time is '更新时间'; +comment on table sys_dept is '部门表'; + +-- ---------------------------- +-- 初始化-部门表数据 +-- ---------------------------- +insert into sys_dept values(100, 0, '0', '集团总公司', 0, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept values(101, 100, '0,100', '深圳分公司', 1, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept values(102, 100, '0,100', '长沙分公司', 2, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept values(103, 101, '0,100,101', '研发部门', 1, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept values(104, 101, '0,100,101', '市场部门', 2, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept values(105, 101, '0,100,101', '测试部门', 3, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept values(106, 101, '0,100,101', '财务部门', 4, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept values(107, 101, '0,100,101', '运维部门', 5, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept values(108, 102, '0,100,102', '市场部门', 1, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept values(109, 102, '0,100,102', '财务部门', 2, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', current_timestamp, '', null); + +-- ---------------------------- +-- 2、用户信息表 +-- ---------------------------- +drop table if exists sys_user; +create table sys_user ( + user_id bigserial not null, + dept_id bigint default null, + user_name varchar(30) not null, + nick_name varchar(30) not null, + user_type varchar(2) default '00', + email varchar(50) default '', + phonenumber varchar(11) default '', + sex char(1) default '0', + avatar varchar(100) default '', + password varchar(100) default '', + status char(1) default '0', + del_flag char(1) default '0', + login_ip varchar(128) default '', + login_date timestamp(0), + create_by varchar(64) default '', + create_time timestamp(0), + update_by varchar(64) default '', + update_time timestamp(0), + remark varchar(500) default null, + primary key (user_id) +); +alter sequence sys_user_user_id_seq restart 100; +comment on column sys_user.user_id is '用户ID'; +comment on column sys_user.dept_id is '部门ID'; +comment on column sys_user.user_name is '用户账号'; +comment on column sys_user.nick_name is '用户昵称'; +comment on column sys_user.user_type is '用户类型(00系统用户)'; +comment on column sys_user.email is '用户邮箱'; +comment on column sys_user.phonenumber is '手机号码'; +comment on column sys_user.sex is '用户性别(0男 1女 2未知)'; +comment on column sys_user.avatar is '头像地址'; +comment on column sys_user.password is '密码'; +comment on column sys_user.status is '帐号状态(0正常 1停用)'; +comment on column sys_user.del_flag is '删除标志(0代表存在 2代表删除)'; +comment on column sys_user.login_ip is '最后登录IP'; +comment on column sys_user.login_date is '最后登录时间'; +comment on column sys_user.create_by is '创建者'; +comment on column sys_user.create_time is '创建时间'; +comment on column sys_user.update_by is '更新者'; +comment on column sys_user.update_time is '更新时间'; +comment on column sys_user.remark is '备注'; +comment on table sys_user is '用户信息表'; + +-- ---------------------------- +-- 3、岗位信息表 +-- ---------------------------- +drop table if exists sys_post; +create table sys_post ( + post_id bigserial not null, + post_code varchar(64) not null, + post_name varchar(50) not null, + post_sort int4 not null, + status char(1) not null, + create_by varchar(64) default '', + create_time timestamp(0), + update_by varchar(64) default '', + update_time timestamp(0), + remark varchar(500) default null, + primary key (post_id) +); +alter sequence sys_post_post_id_seq restart 5; +comment on column sys_post.post_id is '岗位ID'; +comment on column sys_post.post_code is '岗位编码'; +comment on column sys_post.post_name is '岗位名称'; +comment on column sys_post.post_sort is '显示顺序'; +comment on column sys_post.status is '状态(0正常 1停用)'; +comment on column sys_post.create_by is '创建者'; +comment on column sys_post.create_time is '创建时间'; +comment on column sys_post.update_by is '更新者'; +comment on column sys_post.update_time is '更新时间'; +comment on column sys_post.remark is '备注'; +comment on table sys_post is '岗位信息表'; + +-- ---------------------------- +-- 初始化-岗位信息表数据 +-- ---------------------------- +insert into sys_post values(1, 'ceo', '董事长', 1, '0', 'admin', current_timestamp, '', null, ''); +insert into sys_post values(2, 'se', '项目经理', 2, '0', 'admin', current_timestamp, '', null, ''); +insert into sys_post values(3, 'hr', '人力资源', 3, '0', 'admin', current_timestamp, '', null, ''); +insert into sys_post values(4, 'user', '普通员工', 4, '0', 'admin', current_timestamp, '', null, ''); + +-- ---------------------------- +-- 初始化-用户信息表数据 +-- ---------------------------- +insert into sys_user values(1, 103, 'admin', '超级管理员', '00', 'niangao@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', current_timestamp, 'admin', current_timestamp, '', null, '管理员'); +insert into sys_user values(2, 105, 'niangao', '年糕', '00', 'niangao@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', current_timestamp, 'admin', current_timestamp, '', null, '测试员'); + +-- ---------------------------- +-- 4、角色信息表 +-- ---------------------------- +drop table if exists sys_role; +create table sys_role ( + role_id bigserial not null, + role_name varchar(30) not null, + role_key varchar(100) not null, + role_sort int4 not null, + data_scope char(1) default '1', + menu_check_strictly smallint default 1, + dept_check_strictly smallint default 1, + status char(1) not null, + del_flag char(1) default '0', + create_by varchar(64) default '', + create_time timestamp(0), + update_by varchar(64) default '', + update_time timestamp(0), + remark varchar(500) default null, + primary key (role_id) +); +alter sequence sys_role_role_id_seq restart 3; +comment on column sys_role.role_id is '角色ID'; +comment on column sys_role.role_name is '角色名称'; +comment on column sys_role.role_key is '角色权限字符串'; +comment on column sys_role.role_sort is '显示顺序'; +comment on column sys_role.data_scope is '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)'; +comment on column sys_role.menu_check_strictly is '菜单树选择项是否关联显示'; +comment on column sys_role.dept_check_strictly is '部门树选择项是否关联显示'; +comment on column sys_role.status is '角色状态(0正常 1停用)'; +comment on column sys_role.del_flag is '删除标志(0代表存在 2代表删除)'; +comment on column sys_role.create_by is '创建者'; +comment on column sys_role.create_time is '创建时间'; +comment on column sys_role.update_by is '更新者'; +comment on column sys_role.update_time is '更新时间'; +comment on column sys_role.remark is '备注'; +comment on table sys_role is '角色信息表'; + +-- ---------------------------- +-- 初始化-角色信息表数据 +-- ---------------------------- +insert into sys_role values(1, '超级管理员', 'admin', 1, 1, 1, 1, '0', '0', 'admin', current_timestamp, '', null, '超级管理员'); +insert into sys_role values(2, '普通角色', 'common', 2, 2, 1, 1, '0', '0', 'admin', current_timestamp, '', null, '普通角色'); + +-- ---------------------------- +-- 5、菜单权限表 +-- ---------------------------- +drop table if exists sys_menu; +create table sys_menu ( + menu_id bigserial not null, + menu_name varchar(50) not null, + parent_id bigint default 0, + order_num int4 default 0, + path varchar(200) default '', + component varchar(255) default null, + query varchar(255) default null, + route_name varchar(50) default '', + is_frame int4 default 1, + is_cache int4 default 0, + menu_type char(1) default '', + visible char(1) default '0', + status char(1) default '0', + perms varchar(100) default null, + icon varchar(100) default '#', + create_by varchar(64) default '', + create_time timestamp(0), + update_by varchar(64) default '', + update_time timestamp(0), + remark varchar(500) default '', + primary key (menu_id) +); +alter sequence sys_menu_menu_id_seq restart 2000; +comment on column sys_menu.menu_id is '菜单ID'; +comment on column sys_menu.menu_name is '菜单名称'; +comment on column sys_menu.parent_id is '父菜单ID'; +comment on column sys_menu.order_num is '显示顺序'; +comment on column sys_menu.path is '路由地址'; +comment on column sys_menu.component is '组件路径'; +comment on column sys_menu.query is '路由参数'; +comment on column sys_menu.route_name is '路由名称'; +comment on column sys_menu.is_frame is '是否为外链(0是 1否)'; +comment on column sys_menu.is_cache is '是否缓存(0缓存 1不缓存)'; +comment on column sys_menu.menu_type is '菜单类型(M目录 C菜单 F按钮)'; +comment on column sys_menu.visible is '菜单状态(0显示 1隐藏)'; +comment on column sys_menu.status is '菜单状态(0正常 1停用)'; +comment on column sys_menu.perms is '权限标识'; +comment on column sys_menu.icon is '菜单图标'; +comment on column sys_menu.create_by is '创建者'; +comment on column sys_menu.create_time is '创建时间'; +comment on column sys_menu.update_by is '更新者'; +comment on column sys_menu.update_time is '更新时间'; +comment on column sys_menu.remark is '备注'; +comment on table sys_menu is '菜单权限表'; + +-- ---------------------------- +-- 初始化-菜单信息表数据 +-- ---------------------------- +-- 一级菜单 +insert into sys_menu values(1, '系统管理', 0, '1', 'system', null, '', '', 1, 0, 'M', '0', '0', '', 'system', 'admin', current_timestamp, '', null, '系统管理目录'); +insert into sys_menu values(2, '系统监控', 0, '2', 'monitor', null, '', '', 1, 0, 'M', '0', '0', '', 'monitor', 'admin', current_timestamp, '', null, '系统监控目录'); +insert into sys_menu values(3, '系统工具', 0, '3', 'tool', null, '', '', 1, 0, 'M', '0', '0', '', 'tool', 'admin', current_timestamp, '', null, '系统工具目录'); +insert into sys_menu values(4, '若依官网', 0, '4', 'http://ruoyi.vip', null, '', '', 0, 0, 'M', '0', '0', '', 'guide', 'admin', current_timestamp, '', null, '若依官网地址'); +-- 二级菜单 +insert into sys_menu values(100, '用户管理', 1, '1', 'user', 'system/user/index', '', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', current_timestamp, '', null, '用户管理菜单'); +insert into sys_menu values(101, '角色管理', 1, '2', 'role', 'system/role/index', '', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', current_timestamp, '', null, '角色管理菜单'); +insert into sys_menu values(102, '菜单管理', 1, '3', 'menu', 'system/menu/index', '', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', current_timestamp, '', null, '菜单管理菜单'); +insert into sys_menu values(103, '部门管理', 1, '4', 'dept', 'system/dept/index', '', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', current_timestamp, '', null, '部门管理菜单'); +insert into sys_menu values(104, '岗位管理', 1, '5', 'post', 'system/post/index', '', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', current_timestamp, '', null, '岗位管理菜单'); +insert into sys_menu values(105, '字典管理', 1, '6', 'dict', 'system/dict/index', '', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', current_timestamp, '', null, '字典管理菜单'); +insert into sys_menu values(106, '参数设置', 1, '7', 'config', 'system/config/index', '', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', current_timestamp, '', null, '参数设置菜单'); +insert into sys_menu values(107, '通知公告', 1, '8', 'notice', 'system/notice/index', '', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', current_timestamp, '', null, '通知公告菜单'); +insert into sys_menu values(108, '日志管理', 1, '9', 'log', '', '', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', current_timestamp, '', null, '日志管理菜单'); +insert into sys_menu values(109, '在线用户', 2, '1', 'online', 'monitor/online/index', '', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', current_timestamp, '', null, '在线用户菜单'); +insert into sys_menu values(110, '定时任务', 2, '2', 'job', 'monitor/job/index', '', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', current_timestamp, '', null, '定时任务菜单'); +insert into sys_menu values(111, '数据监控', 2, '3', 'druid', 'monitor/druid/index', '', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', current_timestamp, '', null, '数据监控菜单'); +insert into sys_menu values(112, '服务监控', 2, '4', 'server', 'monitor/server/index', '', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', current_timestamp, '', null, '服务监控菜单'); +insert into sys_menu values(113, '缓存监控', 2, '5', 'cache', 'monitor/cache/index', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', current_timestamp, '', null, '缓存监控菜单'); +insert into sys_menu values(114, '缓存列表', 2, '6', 'cacheList', 'monitor/cache/list', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis-list', 'admin', current_timestamp, '', null, '缓存列表菜单'); +insert into sys_menu values(115, '表单构建', 3, '1', 'build', 'tool/build/index', '', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', current_timestamp, '', null, '表单构建菜单'); +insert into sys_menu values(116, '代码生成', 3, '2', 'gen', 'tool/gen/index', '', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', current_timestamp, '', null, '代码生成菜单'); +insert into sys_menu values(117, '系统接口', 3, '3', 'swagger', 'tool/swagger/index', '', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', 'admin', current_timestamp, '', null, '系统接口菜单'); +-- 三级菜单 +insert into sys_menu values(500, '操作日志', 108, '1', 'operlog', 'monitor/operlog/index', '', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', current_timestamp, '', null, '操作日志菜单'); +insert into sys_menu values(501, '登录日志', 108, '2', 'logininfor', 'monitor/logininfor/index', '', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 'admin', current_timestamp, '', null, '登录日志菜单'); +-- 用户管理按钮 +insert into sys_menu values(1000, '用户查询', 100, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1001, '用户新增', 100, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1002, '用户修改', 100, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1003, '用户删除', 100, '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1004, '用户导出', 100, '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1005, '用户导入', 100, '6', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1006, '重置密码', 100, '7', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', current_timestamp, '', null, ''); +-- 角色管理按钮 +insert into sys_menu values(1007, '角色查询', 101, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1008, '角色新增', 101, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1009, '角色修改', 101, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1010, '角色删除', 101, '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1011, '角色导出', 101, '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', current_timestamp, '', null, ''); +-- 菜单管理按钮 +insert into sys_menu values(1012, '菜单查询', 102, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1013, '菜单新增', 102, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1014, '菜单修改', 102, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1015, '菜单删除', 102, '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', current_timestamp, '', null, ''); +-- 部门管理按钮 +insert into sys_menu values(1016, '部门查询', 103, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1017, '部门新增', 103, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1018, '部门修改', 103, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1019, '部门删除', 103, '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', current_timestamp, '', null, ''); +-- 岗位管理按钮 +insert into sys_menu values(1020, '岗位查询', 104, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1021, '岗位新增', 104, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1022, '岗位修改', 104, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1023, '岗位删除', 104, '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1024, '岗位导出', 104, '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', current_timestamp, '', null, ''); +-- 字典管理按钮 +insert into sys_menu values(1025, '字典查询', 105, '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1026, '字典新增', 105, '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1027, '字典修改', 105, '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1028, '字典删除', 105, '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1029, '字典导出', 105, '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', current_timestamp, '', null, ''); +-- 参数设置按钮 +insert into sys_menu values(1030, '参数查询', 106, '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1031, '参数新增', 106, '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1032, '参数修改', 106, '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1033, '参数删除', 106, '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1034, '参数导出', 106, '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', current_timestamp, '', null, ''); +-- 通知公告按钮 +insert into sys_menu values(1035, '公告查询', 107, '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1036, '公告新增', 107, '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1037, '公告修改', 107, '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1038, '公告删除', 107, '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', current_timestamp, '', null, ''); +-- 操作日志按钮 +insert into sys_menu values(1039, '操作查询', 500, '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1040, '操作删除', 500, '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1041, '日志导出', 500, '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', current_timestamp, '', null, ''); +-- 登录日志按钮 +insert into sys_menu values(1042, '登录查询', 501, '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1043, '登录删除', 501, '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1044, '日志导出', 501, '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1045, '账户解锁', 501, '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 'admin', current_timestamp, '', null, ''); +-- 在线用户按钮 +insert into sys_menu values(1046, '在线查询', 109, '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1047, '批量强退', 109, '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1048, '单条强退', 109, '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', current_timestamp, '', null, ''); +-- 定时任务按钮 +insert into sys_menu values(1049, '任务查询', 110, '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1050, '任务新增', 110, '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1051, '任务修改', 110, '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1052, '任务删除', 110, '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1053, '状态修改', 110, '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1054, '任务导出', 110, '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', current_timestamp, '', null, ''); +-- 代码生成按钮 +insert into sys_menu values(1055, '生成查询', 116, '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1056, '生成修改', 116, '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1057, '生成删除', 116, '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1058, '导入代码', 116, '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1059, '预览代码', 116, '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu values(1060, '生成代码', 116, '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', current_timestamp, '', null, ''); + +-- ---------------------------- +-- 6、用户和角色关联表 用户N-1角色 +-- ---------------------------- +drop table if exists sys_user_role; +create table sys_user_role ( + user_id bigint not null, + role_id bigint not null, + primary key (user_id, role_id) +); +comment on column sys_user_role.user_id is '用户ID'; +comment on column sys_user_role.role_id is '角色ID'; +comment on table sys_user_role is '用户和角色关联表'; + +-- ---------------------------- +-- 初始化-用户和角色关联表数据 +-- ---------------------------- +insert into sys_user_role values (1, 1); +insert into sys_user_role values (2, 2); + +-- ---------------------------- +-- 7、角色和菜单关联表 角色1-N菜单 +-- ---------------------------- +drop table if exists sys_role_menu; +create table sys_role_menu ( + role_id bigint not null, + menu_id bigint not null, + primary key (role_id, menu_id) +); +comment on column sys_role_menu.role_id is '角色ID'; +comment on column sys_role_menu.menu_id is '菜单ID'; +comment on table sys_role_menu is '角色和菜单关联表'; + +-- ---------------------------- +-- 初始化-角色和菜单关联表数据 +-- ---------------------------- +insert into sys_role_menu values (2, 1); +insert into sys_role_menu values (2, 2); +insert into sys_role_menu values (2, 3); +insert into sys_role_menu values (2, 4); +insert into sys_role_menu values (2, 100); +insert into sys_role_menu values (2, 101); +insert into sys_role_menu values (2, 102); +insert into sys_role_menu values (2, 103); +insert into sys_role_menu values (2, 104); +insert into sys_role_menu values (2, 105); +insert into sys_role_menu values (2, 106); +insert into sys_role_menu values (2, 107); +insert into sys_role_menu values (2, 108); +insert into sys_role_menu values (2, 109); +insert into sys_role_menu values (2, 110); +insert into sys_role_menu values (2, 111); +insert into sys_role_menu values (2, 112); +insert into sys_role_menu values (2, 113); +insert into sys_role_menu values (2, 114); +insert into sys_role_menu values (2, 115); +insert into sys_role_menu values (2, 116); +insert into sys_role_menu values (2, 117); +insert into sys_role_menu values (2, 500); +insert into sys_role_menu values (2, 501); +insert into sys_role_menu values (2, 1000); +insert into sys_role_menu values (2, 1001); +insert into sys_role_menu values (2, 1002); +insert into sys_role_menu values (2, 1003); +insert into sys_role_menu values (2, 1004); +insert into sys_role_menu values (2, 1005); +insert into sys_role_menu values (2, 1006); +insert into sys_role_menu values (2, 1007); +insert into sys_role_menu values (2, 1008); +insert into sys_role_menu values (2, 1009); +insert into sys_role_menu values (2, 1010); +insert into sys_role_menu values (2, 1011); +insert into sys_role_menu values (2, 1012); +insert into sys_role_menu values (2, 1013); +insert into sys_role_menu values (2, 1014); +insert into sys_role_menu values (2, 1015); +insert into sys_role_menu values (2, 1016); +insert into sys_role_menu values (2, 1017); +insert into sys_role_menu values (2, 1018); +insert into sys_role_menu values (2, 1019); +insert into sys_role_menu values (2, 1020); +insert into sys_role_menu values (2, 1021); +insert into sys_role_menu values (2, 1022); +insert into sys_role_menu values (2, 1023); +insert into sys_role_menu values (2, 1024); +insert into sys_role_menu values (2, 1025); +insert into sys_role_menu values (2, 1026); +insert into sys_role_menu values (2, 1027); +insert into sys_role_menu values (2, 1028); +insert into sys_role_menu values (2, 1029); +insert into sys_role_menu values (2, 1030); +insert into sys_role_menu values (2, 1031); +insert into sys_role_menu values (2, 1032); +insert into sys_role_menu values (2, 1033); +insert into sys_role_menu values (2, 1034); +insert into sys_role_menu values (2, 1035); +insert into sys_role_menu values (2, 1036); +insert into sys_role_menu values (2, 1037); +insert into sys_role_menu values (2, 1038); +insert into sys_role_menu values (2, 1039); +insert into sys_role_menu values (2, 1040); +insert into sys_role_menu values (2, 1041); +insert into sys_role_menu values (2, 1042); +insert into sys_role_menu values (2, 1043); +insert into sys_role_menu values (2, 1044); +insert into sys_role_menu values (2, 1045); +insert into sys_role_menu values (2, 1046); +insert into sys_role_menu values (2, 1047); +insert into sys_role_menu values (2, 1048); +insert into sys_role_menu values (2, 1049); +insert into sys_role_menu values (2, 1050); +insert into sys_role_menu values (2, 1051); +insert into sys_role_menu values (2, 1052); +insert into sys_role_menu values (2, 1053); +insert into sys_role_menu values (2, 1054); +insert into sys_role_menu values (2, 1055); +insert into sys_role_menu values (2, 1056); +insert into sys_role_menu values (2, 1057); +insert into sys_role_menu values (2, 1058); +insert into sys_role_menu values (2, 1059); +insert into sys_role_menu values (2, 1060); + +-- ---------------------------- +-- 8、角色和部门关联表 角色1-N部门 +-- ---------------------------- +drop table if exists sys_role_dept; +create table sys_role_dept ( + role_id bigint not null, + dept_id bigint not null, + primary key (role_id, dept_id) +); +comment on column sys_role_dept.role_id is '角色ID'; +comment on column sys_role_dept.dept_id is '部门ID'; +comment on table sys_role_dept is '角色和部门关联表'; + +-- ---------------------------- +-- 初始化-角色和部门关联表数据 +-- ---------------------------- +insert into sys_role_dept values (2, 100); +insert into sys_role_dept values (2, 101); +insert into sys_role_dept values (2, 105); + +-- ---------------------------- +-- 9、用户与岗位关联表 用户1-N岗位 +-- ---------------------------- +drop table if exists sys_user_post; +create table sys_user_post ( + user_id bigint not null, + post_id bigint not null, + primary key (user_id, post_id) +); +comment on column sys_user_post.user_id is '用户ID'; +comment on column sys_user_post.post_id is '岗位ID'; +comment on table sys_user_post is '用户与岗位关联表'; + +-- ---------------------------- +-- 初始化-用户与岗位关联表数据 +-- ---------------------------- +insert into sys_user_post values (1, 1); +insert into sys_user_post values (2, 2); + +-- ---------------------------- +-- 10、操作日志记录 +-- ---------------------------- +drop table if exists sys_oper_log; +create table sys_oper_log ( + oper_id bigserial not null, + title varchar(50) default '', + business_type int4 default 0, + method varchar(100) default '', + request_method varchar(10) default '', + operator_type int4 default 0, + oper_name varchar(50) default '', + dept_name varchar(50) default '', + oper_url varchar(255) default '', + oper_ip varchar(128) default '', + oper_location varchar(255) default '', + oper_param varchar(2000) default '', + json_result varchar(2000) default '', + status int4 default 0, + error_msg varchar(2000) default '', + oper_time timestamp(0), + cost_time int8 default 0, + primary key (oper_id) +); +alter sequence sys_oper_log_oper_id_seq restart 100; +create index idx_sys_oper_log_bt on sys_oper_log(business_type); +create index idx_sys_oper_log_s on sys_oper_log(status); +create index idx_sys_oper_log_ot on sys_oper_log(oper_time); +comment on column sys_oper_log.oper_id is '日志主键'; +comment on column sys_oper_log.title is '模块标题'; +comment on column sys_oper_log.business_type is '业务类型(0其它 1新增 2修改 3删除)'; +comment on column sys_oper_log.method is '方法名称'; +comment on column sys_oper_log.request_method is '请求方式'; +comment on column sys_oper_log.operator_type is '操作类别(0其它 1后台用户 2手机端用户)'; +comment on column sys_oper_log.oper_name is '操作人员'; +comment on column sys_oper_log.dept_name is '部门名称'; +comment on column sys_oper_log.oper_url is '请求URL'; +comment on column sys_oper_log.oper_ip is '主机地址'; +comment on column sys_oper_log.oper_location is '操作地点'; +comment on column sys_oper_log.oper_param is '请求参数'; +comment on column sys_oper_log.json_result is '返回参数'; +comment on column sys_oper_log.status is '操作状态(0正常 1异常)'; +comment on column sys_oper_log.error_msg is '错误消息'; +comment on column sys_oper_log.oper_time is '操作时间'; +comment on table sys_oper_log is '操作日志记录'; + +-- ---------------------------- +-- 11、字典类型表 +-- ---------------------------- +drop table if exists sys_dict_type; +create table sys_dict_type ( + dict_id bigserial not null, + dict_name varchar(100) default '', + dict_type varchar(100) unique default '', + status char(1) default '0', + create_by varchar(64) default '', + create_time timestamp(0), + update_by varchar(64) default '', + update_time timestamp(0), + remark varchar(500) default null, + primary key (dict_id) +); +alter sequence sys_dict_type_dict_id_seq restart 100; +comment on column sys_dict_type.dict_id is '字典主键'; +comment on column sys_dict_type.dict_name is '字典名称'; +comment on column sys_dict_type.dict_type is '字典类型'; +comment on column sys_dict_type.status is '状态(0正常 1停用)'; +comment on column sys_dict_type.create_by is '创建者'; +comment on column sys_dict_type.create_time is '创建时间'; +comment on column sys_dict_type.update_by is '更新者'; +comment on column sys_dict_type.update_time is '更新时间'; +comment on column sys_dict_type.remark is '备注'; +comment on table sys_dict_type is '字典类型表'; + +-- ---------------------------- +-- 初始化-字典类型表数据 +-- ---------------------------- +insert into sys_dict_type values(1, '用户性别', 'sys_user_sex', '0', 'admin', current_timestamp, '', null, '用户性别列表'); +insert into sys_dict_type values(2, '菜单状态', 'sys_show_hide', '0', 'admin', current_timestamp, '', null, '菜单状态列表'); +insert into sys_dict_type values(3, '系统开关', 'sys_normal_disable', '0', 'admin', current_timestamp, '', null, '系统开关列表'); +insert into sys_dict_type values(4, '任务状态', 'sys_job_status', '0', 'admin', current_timestamp, '', null, '任务状态列表'); +insert into sys_dict_type values(5, '任务分组', 'sys_job_group', '0', 'admin', current_timestamp, '', null, '任务分组列表'); +insert into sys_dict_type values(6, '任务执行器', 'sys_job_executor', '0', 'admin', current_timestamp, '', null, '任务执行器列表'); +insert into sys_dict_type values(7, '系统是否', 'sys_yes_no', '0', 'admin', current_timestamp, '', null, '系统是否列表'); +insert into sys_dict_type values(8, '通知类型', 'sys_notice_type', '0', 'admin', current_timestamp, '', null, '通知类型列表'); +insert into sys_dict_type values(9, '通知状态', 'sys_notice_status', '0', 'admin', current_timestamp, '', null, '通知状态列表'); +insert into sys_dict_type values(10, '操作类型', 'sys_oper_type', '0', 'admin', current_timestamp, '', null, '操作类型列表'); +insert into sys_dict_type values(11, '系统状态', 'sys_common_status', '0', 'admin', current_timestamp, '', null, '登录状态列表'); + +-- ---------------------------- +-- 12、字典数据表 +-- ---------------------------- +drop table if exists sys_dict_data; +create table sys_dict_data ( + dict_code bigserial not null, + dict_sort int4 default 0, + dict_label varchar(100) default '', + dict_value varchar(100) default '', + dict_type varchar(100) default '', + css_class varchar(100) default null, + list_class varchar(100) default null, + is_default char(1) default 'N', + status char(1) default '0', + create_by varchar(64) default '', + create_time timestamp(0), + update_by varchar(64) default '', + update_time timestamp(0), + remark varchar(500) default null, + primary key (dict_code) +); +alter sequence sys_dict_data_dict_code_seq restart 100; +comment on column sys_dict_data.dict_code is '字典编码'; +comment on column sys_dict_data.dict_sort is '字典排序'; +comment on column sys_dict_data.dict_label is '字典标签'; +comment on column sys_dict_data.dict_value is '字典键值'; +comment on column sys_dict_data.dict_type is '字典类型'; +comment on column sys_dict_data.css_class is '样式属性(其他样式扩展)'; +comment on column sys_dict_data.list_class is '表格回显样式'; +comment on column sys_dict_data.is_default is '是否默认(Y是 N否)'; +comment on column sys_dict_data.status is '状态(0正常 1停用)'; +comment on column sys_dict_data.create_by is '创建者'; +comment on column sys_dict_data.create_time is '创建时间'; +comment on column sys_dict_data.update_by is '更新者'; +comment on column sys_dict_data.update_time is '更新时间'; +comment on column sys_dict_data.remark is '备注'; +comment on table sys_dict_data is '字典数据表'; + +-- ---------------------------- +-- 初始化-字典数据表数据 +-- ---------------------------- +insert into sys_dict_data values(1, 1, '男', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', current_timestamp, '', null, '性别男'); +insert into sys_dict_data values(2, 2, '女', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', current_timestamp, '', null, '性别女'); +insert into sys_dict_data values(3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', current_timestamp, '', null, '性别未知'); +insert into sys_dict_data values(4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', current_timestamp, '', null, '显示菜单'); +insert into sys_dict_data values(5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '隐藏菜单'); +insert into sys_dict_data values(6, 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', current_timestamp, '', null, '正常状态'); +insert into sys_dict_data values(7, 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '停用状态'); +insert into sys_dict_data values(8, 1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', current_timestamp, '', null, '正常状态'); +insert into sys_dict_data values(9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '停用状态'); +insert into sys_dict_data values(10, 1, '默认', 'default', 'sys_job_group', '', '', 'Y', '0', 'admin', current_timestamp, '', null, '默认分组'); +insert into sys_dict_data values(11, 2, '数据库', 'sqlalchemy', 'sys_job_group', '', '', 'N', '0', 'admin', current_timestamp, '', null, '数据库分组'); +insert into sys_dict_data values(12, 3, 'redis', 'redis', 'sys_job_group', '', '', 'N', '0', 'admin', current_timestamp, '', null, 'reids分组'); +insert into sys_dict_data values(13, 1, '默认', 'default', 'sys_job_executor', '', '', 'N', '0', 'admin', current_timestamp, '', null, '线程池'); +insert into sys_dict_data values(14, 2, '进程池', 'processpool', 'sys_job_executor', '', '', 'N', '0', 'admin', current_timestamp, '', null, '进程池'); +insert into sys_dict_data values(15, 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', current_timestamp, '', null, '系统默认是'); +insert into sys_dict_data values(16, 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '系统默认否'); +insert into sys_dict_data values(17, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', current_timestamp, '', null, '通知'); +insert into sys_dict_data values(18, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', current_timestamp, '', null, '公告'); +insert into sys_dict_data values(19, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', current_timestamp, '', null, '正常状态'); +insert into sys_dict_data values(20, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '关闭状态'); +insert into sys_dict_data values(21, 99, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 'admin', current_timestamp, '', null, '其他操作'); +insert into sys_dict_data values(22, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', current_timestamp, '', null, '新增操作'); +insert into sys_dict_data values(23, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', current_timestamp, '', null, '修改操作'); +insert into sys_dict_data values(24, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '删除操作'); +insert into sys_dict_data values(25, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', current_timestamp, '', null, '授权操作'); +insert into sys_dict_data values(26, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', current_timestamp, '', null, '导出操作'); +insert into sys_dict_data values(27, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', current_timestamp, '', null, '导入操作'); +insert into sys_dict_data values(28, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '强退操作'); +insert into sys_dict_data values(29, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', current_timestamp, '', null, '生成操作'); +insert into sys_dict_data values(30, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '清空操作'); +insert into sys_dict_data values(31, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', current_timestamp, '', null, '正常状态'); +insert into sys_dict_data values(32, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '停用状态'); + +-- ---------------------------- +-- 13、参数配置表 +-- ---------------------------- +drop table if exists sys_config; +create table sys_config ( + config_id serial not null, + config_name varchar(100) default '', + config_key varchar(100) default '', + config_value varchar(500) default '', + config_type char(1) default 'N', + create_by varchar(64) default '', + create_time timestamp(0), + update_by varchar(64) default '', + update_time timestamp(0), + remark varchar(500) default null, + primary key (config_id) +); +alter sequence sys_config_config_id_seq restart 100; +comment on column sys_config.config_id is '参数主键'; +comment on column sys_config.config_name is '参数名称'; +comment on column sys_config.config_key is '参数键名'; +comment on column sys_config.config_value is '参数键值'; +comment on column sys_config.config_type is '系统内置(Y是 N否)'; +comment on column sys_config.create_by is '创建者'; +comment on column sys_config.create_time is '创建时间'; +comment on column sys_config.update_by is '更新者'; +comment on column sys_config.update_time is '更新时间'; +comment on column sys_config.remark is '备注'; +comment on table sys_config is '参数配置表'; + +-- ---------------------------- +-- 初始化-参数配置表数据 +-- ---------------------------- +insert into sys_config values(1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'admin', current_timestamp, '', null, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow' ); +insert into sys_config values(2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', current_timestamp, '', null, '初始化密码 123456' ); +insert into sys_config values(3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', current_timestamp, '', null, '深色主题theme-dark,浅色主题theme-light' ); +insert into sys_config values(4, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', current_timestamp, '', null, '是否开启验证码功能(true开启,false关闭)'); +insert into sys_config values(5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', current_timestamp, '', null, '是否开启注册用户功能(true开启,false关闭)'); +insert into sys_config values(6, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', current_timestamp, '', null, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)'); + +-- ---------------------------- +-- 14、系统访问记录 +-- ---------------------------- +drop table if exists sys_logininfor; +create table sys_logininfor ( + info_id bigserial not null, + user_name varchar(50) default '', + ipaddr varchar(128) default '', + login_location varchar(255) default '', + browser varchar(50) default '', + os varchar(50) default '', + status char(1) default '0', + msg varchar(255) default '', + login_time timestamp(0), + primary key (info_id) +); +alter sequence sys_logininfor_info_id_seq restart 100; +create index idx_sys_logininfor_s on sys_logininfor(status); +create index idx_sys_logininfor_lt on sys_logininfor(login_time); +comment on column sys_logininfor.info_id is '访问ID'; +comment on column sys_logininfor.user_name is '用户账号'; +comment on column sys_logininfor.ipaddr is '登录IP地址'; +comment on column sys_logininfor.login_location is '登录地点'; +comment on column sys_logininfor.browser is '浏览器类型'; +comment on column sys_logininfor.os is '操作系统'; +comment on column sys_logininfor.status is '登录状态(0成功 1失败)'; +comment on column sys_logininfor.msg is '提示消息'; +comment on column sys_logininfor.login_time is '访问时间'; +comment on table sys_logininfor is '系统访问记录'; + +-- ---------------------------- +-- 15、定时任务调度表 +-- ---------------------------- +drop table if exists sys_job; +create table sys_job ( + job_id bigserial not null, + job_name varchar(64) default '', + job_group varchar(64) default 'default', + job_executor varchar(64) default 'default', + invoke_target varchar(500) not null, + job_args varchar(255) default '', + job_kwargs varchar(255) default '', + cron_expression varchar(255) default '', + misfire_policy varchar(20) default '3', + concurrent char(1) default '1', + status char(1) default '0', + create_by varchar(64) default '', + create_time timestamp(0), + update_by varchar(64) default '', + update_time timestamp(0), + remark varchar(500) default '', + primary key (job_id, job_name, job_group) +); +alter sequence sys_job_job_id_seq restart 100; +comment on column sys_job.job_id is '任务ID'; +comment on column sys_job.job_name is '任务名称'; +comment on column sys_job.job_group is '任务组名'; +comment on column sys_job.job_executor is '任务执行器'; +comment on column sys_job.invoke_target is '调用目标字符串'; +comment on column sys_job.job_args is '位置参数'; +comment on column sys_job.job_kwargs is '关键字参数'; +comment on column sys_job.cron_expression is 'cron执行表达式'; +comment on column sys_job.misfire_policy is '计划执行错误策略(1立即执行 2执行一次 3放弃执行)'; +comment on column sys_job.concurrent is '是否并发执行(0允许 1禁止)'; +comment on column sys_job.status is '状态(0正常 1暂停)'; +comment on column sys_job.create_by is '创建者'; +comment on column sys_job.create_time is '创建时间'; +comment on column sys_job.update_by is '更新者'; +comment on column sys_job.update_time is '更新时间'; +comment on column sys_job.remark is '备注信息'; +comment on table sys_job is '定时任务调度表'; + +-- ---------------------------- +-- 初始化-定时任务调度表数据 +-- ---------------------------- +insert into sys_job values(1, '系统默认(无参)', 'default', 'default', 'module_task.scheduler_test.job', null, null, '0/10 * * * * ?', '3', '1', '1', 'admin', current_timestamp, '', null, ''); +insert into sys_job values(2, '系统默认(有参)', 'default', 'default', 'module_task.scheduler_test.job', 'test', null, '0/15 * * * * ?', '3', '1', '1', 'admin', current_timestamp, '', null, ''); +insert into sys_job values(3, '系统默认(多参)', 'default', 'default', 'module_task.scheduler_test.job', 'new', '{test: 111}', '0/20 * * * * ?', '3', '1', '1', 'admin', current_timestamp, '', null, ''); + +-- ---------------------------- +-- 16、定时任务调度日志表 +-- ---------------------------- +drop table if exists sys_job_log; +create table sys_job_log ( + job_log_id bigserial not null, + job_name varchar(64) not null, + job_group varchar(64) not null, + job_executor varchar(64) not null, + invoke_target varchar(500) not null, + job_args varchar(255) default '', + job_kwargs varchar(255) default '', + job_trigger varchar(255) default '', + job_message varchar(500), + status char(1) default '0', + exception_info varchar(2000) default '', + create_time timestamp(0), + primary key (job_log_id) +); +comment on column sys_job_log.job_log_id is '任务日志ID'; +comment on column sys_job_log.job_name is '任务名称'; +comment on column sys_job_log.job_group is '任务组名'; +comment on column sys_job_log.job_executor is '任务执行器'; +comment on column sys_job_log.invoke_target is '调用目标字符串'; +comment on column sys_job_log.job_args is '位置参数'; +comment on column sys_job_log.job_kwargs is '关键字参数'; +comment on column sys_job_log.job_trigger is '任务触发器'; +comment on column sys_job_log.job_message is '日志信息'; +comment on column sys_job_log.status is '执行状态(0正常 1失败)'; +comment on column sys_job_log.exception_info is '异常信息'; +comment on column sys_job_log.create_time is '创建时间'; +comment on table sys_job_log is '定时任务调度日志表'; + +-- ---------------------------- +-- 17、通知公告表 +-- ---------------------------- +drop table if exists sys_notice; +create table sys_notice ( + notice_id serial not null, + notice_title varchar(50) not null, + notice_type char(1) not null, + notice_content bytea default null, + status char(1) default '0', + create_by varchar(64) default '', + create_time timestamp(0), + update_by varchar(64) default '', + update_time timestamp(0), + remark varchar(255) default null, + primary key (notice_id) +); +alter sequence sys_notice_notice_id_seq restart 10; +comment on column sys_notice.notice_id is '公告ID'; +comment on column sys_notice.notice_title is '公告标题'; +comment on column sys_notice.notice_type is '公告类型(1通知 2公告)'; +comment on column sys_notice.notice_content is '公告内容'; +comment on column sys_notice.status is '公告状态(0正常 1关闭)'; +comment on column sys_notice.create_by is '创建者'; +comment on column sys_notice.create_time is '创建时间'; +comment on column sys_notice.update_by is '更新者'; +comment on column sys_notice.update_time is '更新时间'; +comment on column sys_notice.remark is '备注'; +comment on table sys_notice is '通知公告表'; + +-- ---------------------------- +-- 初始化-公告信息表数据 +-- ---------------------------- +insert into sys_notice values(1, '温馨提醒:2018-07-01 vfadmin新版本发布啦', '2', '新版本内容', '0', 'admin', current_timestamp, '', null, '管理员'); +insert into sys_notice values(2, '维护通知:2018-07-01 vfadmin系统凌晨维护', '1', '维护内容', '0', 'admin', current_timestamp, '', null, '管理员'); + +-- ---------------------------- +-- 18、代码生成业务表 +-- ---------------------------- +drop table if exists gen_table; +create table gen_table ( + table_id bigserial not null, + table_name varchar(200) default '', + table_comment varchar(500) default '', + sub_table_name varchar(64) default null, + sub_table_fk_name varchar(64) default null, + class_name varchar(100) default '', + tpl_category varchar(200) default 'crud', + tpl_web_type varchar(30) default '', + package_name varchar(100), + module_name varchar(30), + business_name varchar(30), + function_name varchar(50), + function_author varchar(50), + gen_type char(1) default '0', + gen_path varchar(200) default '/', + options varchar(1000), + create_by varchar(64) default '', + create_time timestamp(0), + update_by varchar(64) default '', + update_time timestamp(0), + remark varchar(500) default null, + primary key (table_id) +); +comment on column gen_table.table_id is '编号'; +comment on column gen_table.table_name is '表名称'; +comment on column gen_table.table_comment is '表描述'; +comment on column gen_table.sub_table_name is '关联子表的表名'; +comment on column gen_table.sub_table_fk_name is '子表关联的外键名'; +comment on column gen_table.class_name is '实体类名称'; +comment on column gen_table.tpl_category is '使用的模板(crud单表操作 tree树表操作)'; +comment on column gen_table.tpl_web_type is '前端模板类型(element-ui模版 element-plus模版)'; +comment on column gen_table.package_name is '生成包路径'; +comment on column gen_table.module_name is '生成模块名'; +comment on column gen_table.business_name is '生成业务名'; +comment on column gen_table.function_name is '生成功能名'; +comment on column gen_table.function_author is '生成功能作者'; +comment on column gen_table.gen_type is '生成代码方式(0zip压缩包 1自定义路径)'; +comment on column gen_table.gen_path is '生成路径(不填默认项目路径)'; +comment on column gen_table.options is '其它生成选项'; +comment on column gen_table.create_by is '创建者'; +comment on column gen_table.create_time is '创建时间'; +comment on column gen_table.update_by is '更新者'; +comment on column gen_table.update_time is '更新时间'; +comment on column gen_table.remark is '备注'; +comment on table gen_table is '代码生成业务表'; + +-- ---------------------------- +-- 19、代码生成业务表字段 +-- ---------------------------- +drop table if exists gen_table_column; +create table gen_table_column ( + column_id bigserial not null, + table_id bigint, + column_name varchar(200), + column_comment varchar(500), + column_type varchar(100), + python_type varchar(500), + python_field varchar(200), + is_pk char(1), + is_increment char(1), + is_required char(1), + is_unique char(1), + is_insert char(1), + is_edit char(1), + is_list char(1), + is_query char(1), + query_type varchar(200) default 'EQ', + html_type varchar(200), + dict_type varchar(200) default '', + sort int4, + create_by varchar(64) default '', + create_time timestamp(0), + update_by varchar(64) default '', + update_time timestamp(0), + primary key (column_id) +); +comment on column gen_table_column.column_id is '编号'; +comment on column gen_table_column.table_id is '归属表编号'; +comment on column gen_table_column.column_name is '列名称'; +comment on column gen_table_column.column_comment is '列描述'; +comment on column gen_table_column.column_type is '列类型'; +comment on column gen_table_column.python_type is 'PYTHON类型'; +comment on column gen_table_column.python_field is 'PYTHON字段名'; +comment on column gen_table_column.is_pk is '是否主键(1是)'; +comment on column gen_table_column.is_increment is '是否自增(1是)'; +comment on column gen_table_column.is_required is '是否必填(1是)'; +comment on column gen_table_column.is_unique is '是否唯一(1是)'; +comment on column gen_table_column.is_insert is '是否为插入字段(1是)'; +comment on column gen_table_column.is_edit is '是否编辑字段(1是)'; +comment on column gen_table_column.is_list is '是否列表字段(1是)'; +comment on column gen_table_column.is_query is '是否查询字段(1是)'; +comment on column gen_table_column.query_type is '查询方式(等于、不等于、大于、小于、范围)'; +comment on column gen_table_column.html_type is '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)'; +comment on column gen_table_column.dict_type is '字典类型'; +comment on column gen_table_column.sort is '排序'; +comment on column gen_table_column.create_by is '创建者'; +comment on column gen_table_column.create_time is '创建时间'; +comment on column gen_table_column.update_by is '更新者'; +comment on column gen_table_column.update_time is '更新时间'; +comment on table gen_table_column is '代码生成业务表字段'; + +CREATE OR REPLACE FUNCTION "find_in_set"(int8, varchar) + RETURNS "pg_catalog"."bool" AS $BODY$ +DECLARE + STR ALIAS FOR $1; + STRS ALIAS FOR $2; + POS INTEGER; + STATUS BOOLEAN; +BEGIN + SELECT POSITION( ','||STR||',' IN ','||STRS||',') INTO POS; + IF POS > 0 THEN + STATUS = TRUE; + ELSE + STATUS = FALSE; + END IF; + RETURN STATUS; +END; +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100; + +create or replace view list_column as +SELECT c.relname AS table_name, + a.attname AS column_name, + d.description AS column_comment, + CASE + WHEN a.attnotnull AND con.conname IS NULL THEN '1' + ELSE '0' + END AS is_required, + CASE + WHEN con.conname IS NOT NULL THEN '1' + ELSE '0' + END AS is_pk, + a.attnum AS sort, + CASE + WHEN "position"(pg_get_expr(ad.adbin, ad.adrelid), ((c.relname::text || '_'::text) || a.attname + ::text) || '_seq'::text) > 0 THEN '1' + ELSE '0' + END AS is_increment, + btrim( + CASE + WHEN t.typelem <> 0::oid AND t.typlen = '-1'::integer THEN 'ARRAY'::text + ELSE + CASE + WHEN t.typtype = 'd'::"char" THEN format_type(t.typbasetype, NULL::integer) + ELSE format_type(a.atttypid, NULL::integer) + END + END, '"'::text) AS column_type +FROM pg_attribute a + JOIN (pg_class c + JOIN pg_namespace n ON c.relnamespace = n.oid) ON a.attrelid = c.oid + LEFT JOIN pg_description d ON d.objoid = c.oid AND a.attnum = d.objsubid + LEFT JOIN pg_constraint con ON con.conrelid = c.oid AND (a.attnum = ANY (con.conkey)) + LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum + LEFT JOIN pg_type t ON a.atttypid = t.oid +WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) + AND a.attnum > 0 + AND n.nspname = 'public'::name + AND not a.attisdropped + ORDER BY c.relname, a.attnum; + +create or replace view list_table as +SELECT c.relname AS table_name, + obj_description(c.oid) AS table_comment, + CURRENT_TIMESTAMP AS create_time, + CURRENT_TIMESTAMP AS update_time +FROM pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace +WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) + AND c.relname !~~ 'spatial_%'::text AND n.nspname = 'public'::name AND n.nspname <> ''::name; + +CREATE OR REPLACE FUNCTION substring_index(varchar, varchar, integer) +RETURNS varchar AS $$ +DECLARE +tokens varchar[]; +length integer ; +indexnum integer; +BEGIN +tokens := pg_catalog.string_to_array($1, $2); +length := pg_catalog.array_upper(tokens, 1); +indexnum := length - ($3 * -1) + 1; +IF $3 >= 0 THEN +RETURN pg_catalog.array_to_string(tokens[1:$3], $2); +ELSE +RETURN pg_catalog.array_to_string(tokens[indexnum:length], $2); +END IF; +END; +$$ IMMUTABLE STRICT LANGUAGE PLPGSQL; diff --git a/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql b/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql new file mode 100644 index 0000000..d0ce11a --- /dev/null +++ b/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql @@ -0,0 +1,713 @@ +-- ---------------------------- +-- 1、部门表 +-- ---------------------------- +drop table if exists sys_dept; +create table sys_dept ( + dept_id bigint(20) not null auto_increment comment '部门id', + parent_id bigint(20) default 0 comment '父部门id', + ancestors varchar(50) default '' comment '祖级列表', + dept_name varchar(30) default '' comment '部门名称', + order_num int(4) default 0 comment '显示顺序', + leader varchar(20) default null comment '负责人', + phone varchar(11) default null comment '联系电话', + email varchar(50) default null comment '邮箱', + status char(1) default '0' comment '部门状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + primary key (dept_id) +) engine=innodb auto_increment=200 comment = '部门表'; + +-- ---------------------------- +-- 初始化-部门表数据 +-- ---------------------------- +insert into sys_dept values(100, 0, '0', '集团总公司', 0, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(101, 100, '0,100', '深圳分公司', 1, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(102, 100, '0,100', '长沙分公司', 2, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(103, 101, '0,100,101', '研发部门', 1, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(104, 101, '0,100,101', '市场部门', 2, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(105, 101, '0,100,101', '测试部门', 3, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(106, 101, '0,100,101', '财务部门', 4, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(107, 101, '0,100,101', '运维部门', 5, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(108, 102, '0,100,102', '市场部门', 1, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(109, 102, '0,100,102', '财务部门', 2, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); + + +-- ---------------------------- +-- 2、用户信息表 +-- ---------------------------- +drop table if exists sys_user; +create table sys_user ( + user_id bigint(20) not null auto_increment comment '用户ID', + dept_id bigint(20) default null comment '部门ID', + user_name varchar(30) not null comment '用户账号', + nick_name varchar(30) not null comment '用户昵称', + user_type varchar(2) default '00' comment '用户类型(00系统用户)', + email varchar(50) default '' comment '用户邮箱', + phonenumber varchar(11) default '' comment '手机号码', + sex char(1) default '0' comment '用户性别(0男 1女 2未知)', + avatar varchar(100) default '' comment '头像地址', + password varchar(100) default '' comment '密码', + status char(1) default '0' comment '帐号状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + login_ip varchar(128) default '' comment '最后登录IP', + login_date datetime comment '最后登录时间', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (user_id) +) engine=innodb auto_increment=100 comment = '用户信息表'; + +-- ---------------------------- +-- 初始化-用户信息表数据 +-- ---------------------------- +insert into sys_user values(1, 103, 'admin', '超级管理员', '00', 'niangao@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '管理员'); +insert into sys_user values(2, 105, 'niangao', '年糕', '00', 'niangao@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '测试员'); + + +-- ---------------------------- +-- 3、岗位信息表 +-- ---------------------------- +drop table if exists sys_post; +create table sys_post +( + post_id bigint(20) not null auto_increment comment '岗位ID', + post_code varchar(64) not null comment '岗位编码', + post_name varchar(50) not null comment '岗位名称', + post_sort int(4) not null comment '显示顺序', + status char(1) not null comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (post_id) +) engine=innodb comment = '岗位信息表'; + +-- ---------------------------- +-- 初始化-岗位信息表数据 +-- ---------------------------- +insert into sys_post values(1, 'ceo', '董事长', 1, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(2, 'se', '项目经理', 2, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(3, 'hr', '人力资源', 3, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(4, 'user', '普通员工', 4, '0', 'admin', sysdate(), '', null, ''); + + +-- ---------------------------- +-- 4、角色信息表 +-- ---------------------------- +drop table if exists sys_role; +create table sys_role ( + role_id bigint(20) not null auto_increment comment '角色ID', + role_name varchar(30) not null comment '角色名称', + role_key varchar(100) not null comment '角色权限字符串', + role_sort int(4) not null comment '显示顺序', + data_scope char(1) default '1' comment '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + menu_check_strictly tinyint(1) default 1 comment '菜单树选择项是否关联显示', + dept_check_strictly tinyint(1) default 1 comment '部门树选择项是否关联显示', + status char(1) not null comment '角色状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (role_id) +) engine=innodb auto_increment=100 comment = '角色信息表'; + +-- ---------------------------- +-- 初始化-角色信息表数据 +-- ---------------------------- +insert into sys_role values('1', '超级管理员', 'admin', 1, 1, 1, 1, '0', '0', 'admin', sysdate(), '', null, '超级管理员'); +insert into sys_role values('2', '普通角色', 'common', 2, 2, 1, 1, '0', '0', 'admin', sysdate(), '', null, '普通角色'); + + +-- ---------------------------- +-- 5、菜单权限表 +-- ---------------------------- +drop table if exists sys_menu; +create table sys_menu ( + menu_id bigint(20) not null auto_increment comment '菜单ID', + menu_name varchar(50) not null comment '菜单名称', + parent_id bigint(20) default 0 comment '父菜单ID', + order_num int(4) default 0 comment '显示顺序', + path varchar(200) default '' comment '路由地址', + component varchar(255) default null comment '组件路径', + query varchar(255) default null comment '路由参数', + route_name varchar(50) default '' comment '路由名称', + is_frame int(1) default 1 comment '是否为外链(0是 1否)', + is_cache int(1) default 0 comment '是否缓存(0缓存 1不缓存)', + menu_type char(1) default '' comment '菜单类型(M目录 C菜单 F按钮)', + visible char(1) default 0 comment '菜单状态(0显示 1隐藏)', + status char(1) default 0 comment '菜单状态(0正常 1停用)', + perms varchar(100) default null comment '权限标识', + icon varchar(100) default '#' comment '菜单图标', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default '' comment '备注', + primary key (menu_id) +) engine=innodb auto_increment=2000 comment = '菜单权限表'; + +-- ---------------------------- +-- 初始化-菜单信息表数据 +-- ---------------------------- +-- 一级菜单 +insert into sys_menu values('1', '系统管理', '0', '1', 'system', null, '', '', 1, 0, 'M', '0', '0', '', 'system', 'admin', sysdate(), '', null, '系统管理目录'); +insert into sys_menu values('2', '系统监控', '0', '2', 'monitor', null, '', '', 1, 0, 'M', '0', '0', '', 'monitor', 'admin', sysdate(), '', null, '系统监控目录'); +insert into sys_menu values('3', '系统工具', '0', '3', 'tool', null, '', '', 1, 0, 'M', '0', '0', '', 'tool', 'admin', sysdate(), '', null, '系统工具目录'); +insert into sys_menu values('4', '若依官网', '0', '4', 'http://ruoyi.vip', null, '', '', 0, 0, 'M', '0', '0', '', 'guide', 'admin', sysdate(), '', null, '若依官网地址'); +-- 二级菜单 +insert into sys_menu values('100', '用户管理', '1', '1', 'user', 'system/user/index', '', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', sysdate(), '', null, '用户管理菜单'); +insert into sys_menu values('101', '角色管理', '1', '2', 'role', 'system/role/index', '', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', sysdate(), '', null, '角色管理菜单'); +insert into sys_menu values('102', '菜单管理', '1', '3', 'menu', 'system/menu/index', '', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', sysdate(), '', null, '菜单管理菜单'); +insert into sys_menu values('103', '部门管理', '1', '4', 'dept', 'system/dept/index', '', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', sysdate(), '', null, '部门管理菜单'); +insert into sys_menu values('104', '岗位管理', '1', '5', 'post', 'system/post/index', '', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', sysdate(), '', null, '岗位管理菜单'); +insert into sys_menu values('105', '字典管理', '1', '6', 'dict', 'system/dict/index', '', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', sysdate(), '', null, '字典管理菜单'); +insert into sys_menu values('106', '参数设置', '1', '7', 'config', 'system/config/index', '', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', sysdate(), '', null, '参数设置菜单'); +insert into sys_menu values('107', '通知公告', '1', '8', 'notice', 'system/notice/index', '', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', sysdate(), '', null, '通知公告菜单'); +insert into sys_menu values('108', '日志管理', '1', '9', 'log', '', '', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', sysdate(), '', null, '日志管理菜单'); +insert into sys_menu values('109', '在线用户', '2', '1', 'online', 'monitor/online/index', '', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', sysdate(), '', null, '在线用户菜单'); +insert into sys_menu values('110', '定时任务', '2', '2', 'job', 'monitor/job/index', '', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', sysdate(), '', null, '定时任务菜单'); +insert into sys_menu values('111', '数据监控', '2', '3', 'druid', 'monitor/druid/index', '', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', sysdate(), '', null, '数据监控菜单'); +insert into sys_menu values('112', '服务监控', '2', '4', 'server', 'monitor/server/index', '', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', sysdate(), '', null, '服务监控菜单'); +insert into sys_menu values('113', '缓存监控', '2', '5', 'cache', 'monitor/cache/index', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', sysdate(), '', null, '缓存监控菜单'); +insert into sys_menu values('114', '缓存列表', '2', '6', 'cacheList', 'monitor/cache/list', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis-list', 'admin', sysdate(), '', null, '缓存列表菜单'); +insert into sys_menu values('115', '表单构建', '3', '1', 'build', 'tool/build/index', '', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', sysdate(), '', null, '表单构建菜单'); +insert into sys_menu values('116', '代码生成', '3', '2', 'gen', 'tool/gen/index', '', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', sysdate(), '', null, '代码生成菜单'); +insert into sys_menu values('117', '系统接口', '3', '3', 'swagger', 'tool/swagger/index', '', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', 'admin', sysdate(), '', null, '系统接口菜单'); +-- 三级菜单 +insert into sys_menu values('500', '操作日志', '108', '1', 'operlog', 'monitor/operlog/index', '', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', sysdate(), '', null, '操作日志菜单'); +insert into sys_menu values('501', '登录日志', '108', '2', 'logininfor', 'monitor/logininfor/index', '', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 'admin', sysdate(), '', null, '登录日志菜单'); +-- 用户管理按钮 +insert into sys_menu values('1000', '用户查询', '100', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1001', '用户新增', '100', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1002', '用户修改', '100', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1003', '用户删除', '100', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1004', '用户导出', '100', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1005', '用户导入', '100', '6', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1006', '重置密码', '100', '7', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', sysdate(), '', null, ''); +-- 角色管理按钮 +insert into sys_menu values('1007', '角色查询', '101', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1008', '角色新增', '101', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1009', '角色修改', '101', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1010', '角色删除', '101', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1011', '角色导出', '101', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', sysdate(), '', null, ''); +-- 菜单管理按钮 +insert into sys_menu values('1012', '菜单查询', '102', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1013', '菜单新增', '102', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1014', '菜单修改', '102', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1015', '菜单删除', '102', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', sysdate(), '', null, ''); +-- 部门管理按钮 +insert into sys_menu values('1016', '部门查询', '103', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1017', '部门新增', '103', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1018', '部门修改', '103', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1019', '部门删除', '103', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', sysdate(), '', null, ''); +-- 岗位管理按钮 +insert into sys_menu values('1020', '岗位查询', '104', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1021', '岗位新增', '104', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1022', '岗位修改', '104', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1023', '岗位删除', '104', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1024', '岗位导出', '104', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', sysdate(), '', null, ''); +-- 字典管理按钮 +insert into sys_menu values('1025', '字典查询', '105', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1026', '字典新增', '105', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1027', '字典修改', '105', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1028', '字典删除', '105', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1029', '字典导出', '105', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', sysdate(), '', null, ''); +-- 参数设置按钮 +insert into sys_menu values('1030', '参数查询', '106', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1031', '参数新增', '106', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1032', '参数修改', '106', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1033', '参数删除', '106', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1034', '参数导出', '106', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', sysdate(), '', null, ''); +-- 通知公告按钮 +insert into sys_menu values('1035', '公告查询', '107', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1036', '公告新增', '107', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1037', '公告修改', '107', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1038', '公告删除', '107', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', sysdate(), '', null, ''); +-- 操作日志按钮 +insert into sys_menu values('1039', '操作查询', '500', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1040', '操作删除', '500', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1041', '日志导出', '500', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', sysdate(), '', null, ''); +-- 登录日志按钮 +insert into sys_menu values('1042', '登录查询', '501', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1043', '登录删除', '501', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1044', '日志导出', '501', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1045', '账户解锁', '501', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 'admin', sysdate(), '', null, ''); +-- 在线用户按钮 +insert into sys_menu values('1046', '在线查询', '109', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1047', '批量强退', '109', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1048', '单条强退', '109', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', sysdate(), '', null, ''); +-- 定时任务按钮 +insert into sys_menu values('1049', '任务查询', '110', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1050', '任务新增', '110', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1051', '任务修改', '110', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1052', '任务删除', '110', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1053', '状态修改', '110', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1054', '任务导出', '110', '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', sysdate(), '', null, ''); +-- 代码生成按钮 +insert into sys_menu values('1055', '生成查询', '116', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1056', '生成修改', '116', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1057', '生成删除', '116', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1058', '导入代码', '116', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1059', '预览代码', '116', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1060', '生成代码', '116', '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', sysdate(), '', null, ''); + + +-- ---------------------------- +-- 6、用户和角色关联表 用户N-1角色 +-- ---------------------------- +drop table if exists sys_user_role; +create table sys_user_role ( + user_id bigint(20) not null comment '用户ID', + role_id bigint(20) not null comment '角色ID', + primary key(user_id, role_id) +) engine=innodb comment = '用户和角色关联表'; + +-- ---------------------------- +-- 初始化-用户和角色关联表数据 +-- ---------------------------- +insert into sys_user_role values ('1', '1'); +insert into sys_user_role values ('2', '2'); + + +-- ---------------------------- +-- 7、角色和菜单关联表 角色1-N菜单 +-- ---------------------------- +drop table if exists sys_role_menu; +create table sys_role_menu ( + role_id bigint(20) not null comment '角色ID', + menu_id bigint(20) not null comment '菜单ID', + primary key(role_id, menu_id) +) engine=innodb comment = '角色和菜单关联表'; + +-- ---------------------------- +-- 初始化-角色和菜单关联表数据 +-- ---------------------------- +insert into sys_role_menu values ('2', '1'); +insert into sys_role_menu values ('2', '2'); +insert into sys_role_menu values ('2', '3'); +insert into sys_role_menu values ('2', '4'); +insert into sys_role_menu values ('2', '100'); +insert into sys_role_menu values ('2', '101'); +insert into sys_role_menu values ('2', '102'); +insert into sys_role_menu values ('2', '103'); +insert into sys_role_menu values ('2', '104'); +insert into sys_role_menu values ('2', '105'); +insert into sys_role_menu values ('2', '106'); +insert into sys_role_menu values ('2', '107'); +insert into sys_role_menu values ('2', '108'); +insert into sys_role_menu values ('2', '109'); +insert into sys_role_menu values ('2', '110'); +insert into sys_role_menu values ('2', '111'); +insert into sys_role_menu values ('2', '112'); +insert into sys_role_menu values ('2', '113'); +insert into sys_role_menu values ('2', '114'); +insert into sys_role_menu values ('2', '115'); +insert into sys_role_menu values ('2', '116'); +insert into sys_role_menu values ('2', '117'); +insert into sys_role_menu values ('2', '500'); +insert into sys_role_menu values ('2', '501'); +insert into sys_role_menu values ('2', '1000'); +insert into sys_role_menu values ('2', '1001'); +insert into sys_role_menu values ('2', '1002'); +insert into sys_role_menu values ('2', '1003'); +insert into sys_role_menu values ('2', '1004'); +insert into sys_role_menu values ('2', '1005'); +insert into sys_role_menu values ('2', '1006'); +insert into sys_role_menu values ('2', '1007'); +insert into sys_role_menu values ('2', '1008'); +insert into sys_role_menu values ('2', '1009'); +insert into sys_role_menu values ('2', '1010'); +insert into sys_role_menu values ('2', '1011'); +insert into sys_role_menu values ('2', '1012'); +insert into sys_role_menu values ('2', '1013'); +insert into sys_role_menu values ('2', '1014'); +insert into sys_role_menu values ('2', '1015'); +insert into sys_role_menu values ('2', '1016'); +insert into sys_role_menu values ('2', '1017'); +insert into sys_role_menu values ('2', '1018'); +insert into sys_role_menu values ('2', '1019'); +insert into sys_role_menu values ('2', '1020'); +insert into sys_role_menu values ('2', '1021'); +insert into sys_role_menu values ('2', '1022'); +insert into sys_role_menu values ('2', '1023'); +insert into sys_role_menu values ('2', '1024'); +insert into sys_role_menu values ('2', '1025'); +insert into sys_role_menu values ('2', '1026'); +insert into sys_role_menu values ('2', '1027'); +insert into sys_role_menu values ('2', '1028'); +insert into sys_role_menu values ('2', '1029'); +insert into sys_role_menu values ('2', '1030'); +insert into sys_role_menu values ('2', '1031'); +insert into sys_role_menu values ('2', '1032'); +insert into sys_role_menu values ('2', '1033'); +insert into sys_role_menu values ('2', '1034'); +insert into sys_role_menu values ('2', '1035'); +insert into sys_role_menu values ('2', '1036'); +insert into sys_role_menu values ('2', '1037'); +insert into sys_role_menu values ('2', '1038'); +insert into sys_role_menu values ('2', '1039'); +insert into sys_role_menu values ('2', '1040'); +insert into sys_role_menu values ('2', '1041'); +insert into sys_role_menu values ('2', '1042'); +insert into sys_role_menu values ('2', '1043'); +insert into sys_role_menu values ('2', '1044'); +insert into sys_role_menu values ('2', '1045'); +insert into sys_role_menu values ('2', '1046'); +insert into sys_role_menu values ('2', '1047'); +insert into sys_role_menu values ('2', '1048'); +insert into sys_role_menu values ('2', '1049'); +insert into sys_role_menu values ('2', '1050'); +insert into sys_role_menu values ('2', '1051'); +insert into sys_role_menu values ('2', '1052'); +insert into sys_role_menu values ('2', '1053'); +insert into sys_role_menu values ('2', '1054'); +insert into sys_role_menu values ('2', '1055'); +insert into sys_role_menu values ('2', '1056'); +insert into sys_role_menu values ('2', '1057'); +insert into sys_role_menu values ('2', '1058'); +insert into sys_role_menu values ('2', '1059'); +insert into sys_role_menu values ('2', '1060'); + +-- ---------------------------- +-- 8、角色和部门关联表 角色1-N部门 +-- ---------------------------- +drop table if exists sys_role_dept; +create table sys_role_dept ( + role_id bigint(20) not null comment '角色ID', + dept_id bigint(20) not null comment '部门ID', + primary key(role_id, dept_id) +) engine=innodb comment = '角色和部门关联表'; + +-- ---------------------------- +-- 初始化-角色和部门关联表数据 +-- ---------------------------- +insert into sys_role_dept values ('2', '100'); +insert into sys_role_dept values ('2', '101'); +insert into sys_role_dept values ('2', '105'); + + +-- ---------------------------- +-- 9、用户与岗位关联表 用户1-N岗位 +-- ---------------------------- +drop table if exists sys_user_post; +create table sys_user_post +( + user_id bigint(20) not null comment '用户ID', + post_id bigint(20) not null comment '岗位ID', + primary key (user_id, post_id) +) engine=innodb comment = '用户与岗位关联表'; + +-- ---------------------------- +-- 初始化-用户与岗位关联表数据 +-- ---------------------------- +insert into sys_user_post values ('1', '1'); +insert into sys_user_post values ('2', '2'); + + +-- ---------------------------- +-- 10、操作日志记录 +-- ---------------------------- +drop table if exists sys_oper_log; +create table sys_oper_log ( + oper_id bigint(20) not null auto_increment comment '日志主键', + title varchar(50) default '' comment '模块标题', + business_type int(2) default 0 comment '业务类型(0其它 1新增 2修改 3删除)', + method varchar(100) default '' comment '方法名称', + request_method varchar(10) default '' comment '请求方式', + operator_type int(1) default 0 comment '操作类别(0其它 1后台用户 2手机端用户)', + oper_name varchar(50) default '' comment '操作人员', + dept_name varchar(50) default '' comment '部门名称', + oper_url varchar(255) default '' comment '请求URL', + oper_ip varchar(128) default '' comment '主机地址', + oper_location varchar(255) default '' comment '操作地点', + oper_param varchar(2000) default '' comment '请求参数', + json_result varchar(2000) default '' comment '返回参数', + status int(1) default 0 comment '操作状态(0正常 1异常)', + error_msg varchar(2000) default '' comment '错误消息', + oper_time datetime comment '操作时间', + cost_time bigint(20) default 0 comment '消耗时间', + primary key (oper_id), + key idx_sys_oper_log_bt (business_type), + key idx_sys_oper_log_s (status), + key idx_sys_oper_log_ot (oper_time) +) engine=innodb auto_increment=100 comment = '操作日志记录'; + + +-- ---------------------------- +-- 11、字典类型表 +-- ---------------------------- +drop table if exists sys_dict_type; +create table sys_dict_type +( + dict_id bigint(20) not null auto_increment comment '字典主键', + dict_name varchar(100) default '' comment '字典名称', + dict_type varchar(100) default '' comment '字典类型', + status char(1) default '0' comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (dict_id), + unique (dict_type) +) engine=innodb auto_increment=100 comment = '字典类型表'; + +insert into sys_dict_type values(1, '用户性别', 'sys_user_sex', '0', 'admin', sysdate(), '', null, '用户性别列表'); +insert into sys_dict_type values(2, '菜单状态', 'sys_show_hide', '0', 'admin', sysdate(), '', null, '菜单状态列表'); +insert into sys_dict_type values(3, '系统开关', 'sys_normal_disable', '0', 'admin', sysdate(), '', null, '系统开关列表'); +insert into sys_dict_type values(4, '任务状态', 'sys_job_status', '0', 'admin', sysdate(), '', null, '任务状态列表'); +insert into sys_dict_type values(5, '任务分组', 'sys_job_group', '0', 'admin', sysdate(), '', null, '任务分组列表'); +insert into sys_dict_type values(6, '任务执行器', 'sys_job_executor', '0', 'admin', sysdate(), '', null, '任务执行器列表'); +insert into sys_dict_type values(7, '系统是否', 'sys_yes_no', '0', 'admin', sysdate(), '', null, '系统是否列表'); +insert into sys_dict_type values(8, '通知类型', 'sys_notice_type', '0', 'admin', sysdate(), '', null, '通知类型列表'); +insert into sys_dict_type values(9, '通知状态', 'sys_notice_status', '0', 'admin', sysdate(), '', null, '通知状态列表'); +insert into sys_dict_type values(10, '操作类型', 'sys_oper_type', '0', 'admin', sysdate(), '', null, '操作类型列表'); +insert into sys_dict_type values(11, '系统状态', 'sys_common_status', '0', 'admin', sysdate(), '', null, '登录状态列表'); + + +-- ---------------------------- +-- 12、字典数据表 +-- ---------------------------- +drop table if exists sys_dict_data; +create table sys_dict_data +( + dict_code bigint(20) not null auto_increment comment '字典编码', + dict_sort int(4) default 0 comment '字典排序', + dict_label varchar(100) default '' comment '字典标签', + dict_value varchar(100) default '' comment '字典键值', + dict_type varchar(100) default '' comment '字典类型', + css_class varchar(100) default null comment '样式属性(其他样式扩展)', + list_class varchar(100) default null comment '表格回显样式', + is_default char(1) default 'N' comment '是否默认(Y是 N否)', + status char(1) default '0' comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (dict_code) +) engine=innodb auto_increment=100 comment = '字典数据表'; + +insert into sys_dict_data values(1, 1, '男', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', sysdate(), '', null, '性别男'); +insert into sys_dict_data values(2, 2, '女', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别女'); +insert into sys_dict_data values(3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别未知'); +insert into sys_dict_data values(4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '显示菜单'); +insert into sys_dict_data values(5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '隐藏菜单'); +insert into sys_dict_data values(6, 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(7, 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); +insert into sys_dict_data values(8, 1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); +insert into sys_dict_data values(10, 1, '默认', 'default', 'sys_job_group', '', '', 'Y', '0', 'admin', sysdate(), '', null, '默认分组'); +insert into sys_dict_data values(11, 2, '数据库', 'sqlalchemy', 'sys_job_group', '', '', 'N', '0', 'admin', sysdate(), '', null, '数据库分组'); +insert into sys_dict_data values(12, 3, 'redis', 'redis', 'sys_job_group', '', '', 'N', '0', 'admin', sysdate(), '', null, 'reids分组'); +insert into sys_dict_data values(13, 1, '默认', 'default', 'sys_job_executor', '', '', 'N', '0', 'admin', sysdate(), '', null, '线程池'); +insert into sys_dict_data values(14, 2, '进程池', 'processpool', 'sys_job_executor', '', '', 'N', '0', 'admin', sysdate(), '', null, '进程池'); +insert into sys_dict_data values(15, 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '系统默认是'); +insert into sys_dict_data values(16, 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '系统默认否'); +insert into sys_dict_data values(17, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', sysdate(), '', null, '通知'); +insert into sys_dict_data values(18, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '公告'); +insert into sys_dict_data values(19, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(20, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '关闭状态'); +insert into sys_dict_data values(21, 99, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '其他操作'); +insert into sys_dict_data values(22, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '新增操作'); +insert into sys_dict_data values(23, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '修改操作'); +insert into sys_dict_data values(24, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '删除操作'); +insert into sys_dict_data values(25, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '授权操作'); +insert into sys_dict_data values(26, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导出操作'); +insert into sys_dict_data values(27, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导入操作'); +insert into sys_dict_data values(28, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '强退操作'); +insert into sys_dict_data values(29, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '生成操作'); +insert into sys_dict_data values(30, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '清空操作'); +insert into sys_dict_data values(31, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(32, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); + + +-- ---------------------------- +-- 13、参数配置表 +-- ---------------------------- +drop table if exists sys_config; +create table sys_config ( + config_id int(5) not null auto_increment comment '参数主键', + config_name varchar(100) default '' comment '参数名称', + config_key varchar(100) default '' comment '参数键名', + config_value varchar(500) default '' comment '参数键值', + config_type char(1) default 'N' comment '系统内置(Y是 N否)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (config_id) +) engine=innodb auto_increment=100 comment = '参数配置表'; + +insert into sys_config values(1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'admin', sysdate(), '', null, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow' ); +insert into sys_config values(2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', sysdate(), '', null, '初始化密码 123456' ); +insert into sys_config values(3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', sysdate(), '', null, '深色主题theme-dark,浅色主题theme-light' ); +insert into sys_config values(4, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', sysdate(), '', null, '是否开启验证码功能(true开启,false关闭)'); +insert into sys_config values(5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', sysdate(), '', null, '是否开启注册用户功能(true开启,false关闭)'); +insert into sys_config values(6, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', sysdate(), '', null, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)'); + + +-- ---------------------------- +-- 14、系统访问记录 +-- ---------------------------- +drop table if exists sys_logininfor; +create table sys_logininfor ( + info_id bigint(20) not null auto_increment comment '访问ID', + user_name varchar(50) default '' comment '用户账号', + ipaddr varchar(128) default '' comment '登录IP地址', + login_location varchar(255) default '' comment '登录地点', + browser varchar(50) default '' comment '浏览器类型', + os varchar(50) default '' comment '操作系统', + status char(1) default '0' comment '登录状态(0成功 1失败)', + msg varchar(255) default '' comment '提示消息', + login_time datetime comment '访问时间', + primary key (info_id), + key idx_sys_logininfor_s (status), + key idx_sys_logininfor_lt (login_time) +) engine=innodb auto_increment=100 comment = '系统访问记录'; + + +-- ---------------------------- +-- 15、定时任务调度表 +-- ---------------------------- +drop table if exists sys_job; +create table sys_job ( + job_id bigint(20) not null auto_increment comment '任务ID', + job_name varchar(64) default '' comment '任务名称', + job_group varchar(64) default 'default' comment '任务组名', + job_executor varchar(64) default 'default' comment '任务执行器', + invoke_target varchar(500) not null comment '调用目标字符串', + job_args varchar(255) default '' comment '位置参数', + job_kwargs varchar(255) default '' comment '关键字参数', + cron_expression varchar(255) default '' comment 'cron执行表达式', + misfire_policy varchar(20) default '3' comment '计划执行错误策略(1立即执行 2执行一次 3放弃执行)', + concurrent char(1) default '1' comment '是否并发执行(0允许 1禁止)', + status char(1) default '0' comment '状态(0正常 1暂停)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default '' comment '备注信息', + primary key (job_id, job_name, job_group) +) engine=innodb auto_increment=100 comment = '定时任务调度表'; + +insert into sys_job values(1, '系统默认(无参)', 'default', 'default', 'module_task.scheduler_test.job', NULL, NULL, '0/10 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); +insert into sys_job values(2, '系统默认(有参)', 'default', 'default', 'module_task.scheduler_test.job', 'test', NULL, '0/15 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); +insert into sys_job values(3, '系统默认(多参)', 'default', 'default', 'module_task.scheduler_test.job', 'new', '{\"test\": 111}', '0/20 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); + + +-- ---------------------------- +-- 16、定时任务调度日志表 +-- ---------------------------- +drop table if exists sys_job_log; +create table sys_job_log ( + job_log_id bigint(20) not null auto_increment comment '任务日志ID', + job_name varchar(64) not null comment '任务名称', + job_group varchar(64) not null comment '任务组名', + job_executor varchar(64) not null comment '任务执行器', + invoke_target varchar(500) not null comment '调用目标字符串', + job_args varchar(255) default '' comment '位置参数', + job_kwargs varchar(255) default '' comment '关键字参数', + job_trigger varchar(255) default '' comment '任务触发器', + job_message varchar(500) comment '日志信息', + status char(1) default '0' comment '执行状态(0正常 1失败)', + exception_info varchar(2000) default '' comment '异常信息', + create_time datetime comment '创建时间', + primary key (job_log_id) +) engine=innodb comment = '定时任务调度日志表'; + + +-- ---------------------------- +-- 17、通知公告表 +-- ---------------------------- +drop table if exists sys_notice; +create table sys_notice ( + notice_id int(4) not null auto_increment comment '公告ID', + notice_title varchar(50) not null comment '公告标题', + notice_type char(1) not null comment '公告类型(1通知 2公告)', + notice_content longblob default null comment '公告内容', + status char(1) default '0' comment '公告状态(0正常 1关闭)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(255) default null comment '备注', + primary key (notice_id) +) engine=innodb auto_increment=10 comment = '通知公告表'; + +-- ---------------------------- +-- 初始化-公告信息表数据 +-- ---------------------------- +insert into sys_notice values('1', '温馨提醒:2018-07-01 vfadmin新版本发布啦', '2', '新版本内容', '0', 'admin', sysdate(), '', null, '管理员'); +insert into sys_notice values('2', '维护通知:2018-07-01 vfadmin系统凌晨维护', '1', '维护内容', '0', 'admin', sysdate(), '', null, '管理员'); + + +-- ---------------------------- +-- 18、代码生成业务表 +-- ---------------------------- +drop table if exists gen_table; +create table gen_table ( + table_id bigint(20) not null auto_increment comment '编号', + table_name varchar(200) default '' comment '表名称', + table_comment varchar(500) default '' comment '表描述', + sub_table_name varchar(64) default null comment '关联子表的表名', + sub_table_fk_name varchar(64) default null comment '子表关联的外键名', + class_name varchar(100) default '' comment '实体类名称', + tpl_category varchar(200) default 'crud' comment '使用的模板(crud单表操作 tree树表操作)', + tpl_web_type varchar(30) default '' comment '前端模板类型(element-ui模版 element-plus模版)', + package_name varchar(100) comment '生成包路径', + module_name varchar(30) comment '生成模块名', + business_name varchar(30) comment '生成业务名', + function_name varchar(50) comment '生成功能名', + function_author varchar(50) comment '生成功能作者', + gen_type char(1) default '0' comment '生成代码方式(0zip压缩包 1自定义路径)', + gen_path varchar(200) default '/' comment '生成路径(不填默认项目路径)', + options varchar(1000) comment '其它生成选项', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (table_id) +) engine=innodb auto_increment=1 comment = '代码生成业务表'; + + +-- ---------------------------- +-- 19、代码生成业务表字段 +-- ---------------------------- +drop table if exists gen_table_column; +create table gen_table_column ( + column_id bigint(20) not null auto_increment comment '编号', + table_id bigint(20) comment '归属表编号', + column_name varchar(200) comment '列名称', + column_comment varchar(500) comment '列描述', + column_type varchar(100) comment '列类型', + python_type varchar(500) comment 'PYTHON类型', + python_field varchar(200) comment 'PYTHON字段名', + is_pk char(1) comment '是否主键(1是)', + is_increment char(1) comment '是否自增(1是)', + is_required char(1) comment '是否必填(1是)', + is_unique char(1) comment '是否唯一(1是)', + is_insert char(1) comment '是否为插入字段(1是)', + is_edit char(1) comment '是否编辑字段(1是)', + is_list char(1) comment '是否列表字段(1是)', + is_query char(1) comment '是否查询字段(1是)', + query_type varchar(200) default 'EQ' comment '查询方式(等于、不等于、大于、小于、范围)', + html_type varchar(200) comment '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)', + dict_type varchar(200) default '' comment '字典类型', + sort int comment '排序', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + primary key (column_id) +) engine=innodb auto_increment=1 comment = '代码生成业务表字段'; \ No newline at end of file diff --git a/ruoyi-fastapi-backend/sub_applications/handle.py b/ruoyi-fastapi-backend/sub_applications/handle.py new file mode 100644 index 0000000..df2a5f4 --- /dev/null +++ b/ruoyi-fastapi-backend/sub_applications/handle.py @@ -0,0 +1,10 @@ +from fastapi import FastAPI +from sub_applications.staticfiles import mount_staticfiles + + +def handle_sub_applications(app: FastAPI): + """ + 全局处理子应用挂载 + """ + # 挂载静态文件 + mount_staticfiles(app) diff --git a/ruoyi-fastapi-backend/sub_applications/staticfiles.py b/ruoyi-fastapi-backend/sub_applications/staticfiles.py new file mode 100644 index 0000000..c481d72 --- /dev/null +++ b/ruoyi-fastapi-backend/sub_applications/staticfiles.py @@ -0,0 +1,10 @@ +from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles +from config.env import UploadConfig + + +def mount_staticfiles(app: FastAPI): + """ + 挂载静态文件 + """ + app.mount(f'{UploadConfig.UPLOAD_PREFIX}', StaticFiles(directory=f'{UploadConfig.UPLOAD_PATH}'), name='profile') diff --git a/ruoyi-fastapi-backend/test/001测试access_token.py b/ruoyi-fastapi-backend/test/001测试access_token.py new file mode 100644 index 0000000..1b34f9d --- /dev/null +++ b/ruoyi-fastapi-backend/test/001测试access_token.py @@ -0,0 +1,5 @@ +from utils.haikang_util import HaikangUtil +import asyncio + +access_token = asyncio.run(HaikangUtil.get_access_token()) +print("access_token:", access_token) \ No newline at end of file diff --git a/ruoyi-fastapi-backend/test/002测试查询门禁点列表.py b/ruoyi-fastapi-backend/test/002测试查询门禁点列表.py new file mode 100644 index 0000000..0f4d04d --- /dev/null +++ b/ruoyi-fastapi-backend/test/002测试查询门禁点列表.py @@ -0,0 +1,16 @@ +from utils.haikang_util import HaikangUtil +import asyncio + + +result = asyncio.run(HaikangUtil.get_door_list_v2()) +print(result) +""" +{ + "log-driver": "json-file", + "log-opts": { + "max-size": "10m", + "max-file": "3", + "compress": "true" + } +} +""" \ No newline at end of file diff --git a/ruoyi-fastapi-backend/test/002测试查询门禁状态.py b/ruoyi-fastapi-backend/test/002测试查询门禁状态.py new file mode 100644 index 0000000..7d14395 --- /dev/null +++ b/ruoyi-fastapi-backend/test/002测试查询门禁状态.py @@ -0,0 +1,8 @@ +from utils.haikang_util import HaikangUtil +import asyncio + + + +door_index_codes = ['D01'] +data = asyncio.run( HaikangUtil.get_door_status(door_index_codes)) +print("门禁状态", data) diff --git a/ruoyi-fastapi-backend/test/003测试控制门禁.py b/ruoyi-fastapi-backend/test/003测试控制门禁.py new file mode 100644 index 0000000..cf5a487 --- /dev/null +++ b/ruoyi-fastapi-backend/test/003测试控制门禁.py @@ -0,0 +1,8 @@ +from utils.haikang_util import HaikangUtil +import asyncio + +door_index_code = ['D01'] +control_type = 2 # 0-常开, 1-门闭, 2-门开, 3-常闭 + +result = asyncio.run(HaikangUtil.door_do_control(door_index_code, control_type)) +print("测试控制门禁", result) \ No newline at end of file diff --git a/ruoyi-fastapi-backend/test/004查询门禁点事件.py b/ruoyi-fastapi-backend/test/004查询门禁点事件.py new file mode 100644 index 0000000..9697654 --- /dev/null +++ b/ruoyi-fastapi-backend/test/004查询门禁点事件.py @@ -0,0 +1,7 @@ +from utils.haikang_util import HaikangUtil +import asyncio + + +door_index_code = ['D01'] +result = asyncio.run( HaikangUtil.query_door_events_v2(door_index_code)) +print("查询门禁点事件", result) \ No newline at end of file diff --git a/ruoyi-fastapi-backend/test/005测试按条件查询人脸分组.py b/ruoyi-fastapi-backend/test/005测试按条件查询人脸分组.py new file mode 100644 index 0000000..114a7d9 --- /dev/null +++ b/ruoyi-fastapi-backend/test/005测试按条件查询人脸分组.py @@ -0,0 +1,8 @@ +from utils.haikang_util import HaikangUtil +import asyncio + + + +result = asyncio.run( HaikangUtil.get_face_group()) + +print("测试查询人脸分组", result) \ No newline at end of file diff --git a/ruoyi-fastapi-backend/test/006测试人脸分组1vN检索.py b/ruoyi-fastapi-backend/test/006测试人脸分组1vN检索.py new file mode 100644 index 0000000..de3865d --- /dev/null +++ b/ruoyi-fastapi-backend/test/006测试人脸分组1vN检索.py @@ -0,0 +1,17 @@ +import base64 + +from utils.haikang_util import HaikangUtil +import asyncio + + +with open("75c03e462769c81b6a8513d90ff2a27d.jpg", "rb") as image_file: + encoded_bytes = base64.b64encode(image_file.read()) + # 解码为字符串(默认为bytes),方便打印或传输 + encoded_str = encoded_bytes.decode('utf-8') + # print(encoded_str) + faceGroupIndexCodes = [] + +result = asyncio.run( HaikangUtil.face_group_1vN_search(encoded_str, + faceGroupIndexCodes=faceGroupIndexCodes)) + +print("测试人脸1vN检索", result) \ No newline at end of file diff --git a/ruoyi-fastapi-backend/utils/__init__.py b/ruoyi-fastapi-backend/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ruoyi-fastapi-backend/utils/common_util.py b/ruoyi-fastapi-backend/utils/common_util.py new file mode 100644 index 0000000..b56000e --- /dev/null +++ b/ruoyi-fastapi-backend/utils/common_util.py @@ -0,0 +1,274 @@ +import io +import os +import pandas as pd +import re +from openpyxl import Workbook +from openpyxl.styles import Alignment, PatternFill +from openpyxl.utils import get_column_letter +from openpyxl.worksheet.datavalidation import DataValidation +from sqlalchemy.engine.row import Row +from sqlalchemy.orm.collections import InstrumentedList +from typing import Any, Dict, List, Literal, Union +from config.database import Base +from config.env import CachePathConfig + + +def worship(): + print(""" +//////////////////////////////////////////////////////////////////// +// _ooOoo_ // +// o8888888o // +// 88" . "88 // +// (| ^_^ |) // +// O\ = /O // +// ____/`---'\____ // +// .' \\| |// `. // +// / \\||| : |||// \ // +// / _||||| -:- |||||- \ // +// | | \\\ - /// | | // +// | \_| ''\---/'' | | // +// \ .-\__ `-` ___/-. / // +// ___`. .' /--.--\ `. . ___ // +// ."" '< `.___\_<|>_/___.' >'"". // +// | | : `- \`.;`\ _ /`;.`/ - ` : | | // +// \ \ `-. \_ __\ /__ _/ .-` / / // +// ========`-.____`-.___\_____/___.-`____.-'======== // +// `=---=' // +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // +// 佛祖保佑 永不宕机 永无BUG // +//////////////////////////////////////////////////////////////////// + """) + + +class SqlalchemyUtil: + """ + sqlalchemy工具类 + """ + + @classmethod + def base_to_dict( + cls, obj: Union[Base, Dict], transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case' + ): + """ + 将sqlalchemy模型对象转换为字典 + + :param obj: sqlalchemy模型对象或普通字典 + :param transform_case: 转换得到的结果形式,可选的有'no_case'(不转换)、'snake_to_camel'(下划线转小驼峰)、'camel_to_snake'(小驼峰转下划线),默认为'no_case' + :return: 字典结果 + """ + if isinstance(obj, Base): + base_dict = obj.__dict__.copy() + base_dict.pop('_sa_instance_state', None) + for name, value in base_dict.items(): + if isinstance(value, InstrumentedList): + base_dict[name] = cls.serialize_result(value, 'snake_to_camel') + elif isinstance(obj, dict): + base_dict = obj.copy() + if transform_case == 'snake_to_camel': + return {CamelCaseUtil.snake_to_camel(k): v for k, v in base_dict.items()} + elif transform_case == 'camel_to_snake': + return {SnakeCaseUtil.camel_to_snake(k): v for k, v in base_dict.items()} + + return base_dict + + @classmethod + def serialize_result( + cls, result: Any, transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case' + ): + """ + 将sqlalchemy查询结果序列化 + + :param result: sqlalchemy查询结果 + :param transform_case: 转换得到的结果形式,可选的有'no_case'(不转换)、'snake_to_camel'(下划线转小驼峰)、'camel_to_snake'(小驼峰转下划线),默认为'no_case' + :return: 序列化结果 + """ + if isinstance(result, (Base, dict)): + return cls.base_to_dict(result, transform_case) + elif isinstance(result, list): + return [cls.serialize_result(row, transform_case) for row in result] + elif isinstance(result, Row): + if all([isinstance(row, Base) for row in result]): + return [cls.base_to_dict(row, transform_case) for row in result] + elif any([isinstance(row, Base) for row in result]): + return [cls.serialize_result(row, transform_case) for row in result] + else: + result_dict = result._asdict() + if transform_case == 'snake_to_camel': + return {CamelCaseUtil.snake_to_camel(k): v for k, v in result_dict.items()} + elif transform_case == 'camel_to_snake': + return {SnakeCaseUtil.camel_to_snake(k): v for k, v in result_dict.items()} + return result_dict + return result + + +class CamelCaseUtil: + """ + 下划线形式(snake_case)转小驼峰形式(camelCase)工具方法 + """ + + @classmethod + def snake_to_camel(cls, snake_str: str): + """ + 下划线形式字符串(snake_case)转换为小驼峰形式字符串(camelCase) + + :param snake_str: 下划线形式字符串 + :return: 小驼峰形式字符串 + """ + # 分割字符串 + words = snake_str.split('_') + # 小驼峰命名,第一个词首字母小写,其余词首字母大写 + return words[0] + ''.join(word.capitalize() for word in words[1:]) + + @classmethod + def transform_result(cls, result: Any): + """ + 针对不同类型将下划线形式(snake_case)批量转换为小驼峰形式(camelCase)方法 + + :param result: 输入数据 + :return: 小驼峰形式结果 + """ + return SqlalchemyUtil.serialize_result(result=result, transform_case='snake_to_camel') + + +class SnakeCaseUtil: + """ + 小驼峰形式(camelCase)转下划线形式(snake_case)工具方法 + """ + + @classmethod + def camel_to_snake(cls, camel_str: str): + """ + 小驼峰形式字符串(camelCase)转换为下划线形式字符串(snake_case) + + :param camel_str: 小驼峰形式字符串 + :return: 下划线形式字符串 + """ + # 在大写字母前添加一个下划线,然后将整个字符串转为小写 + words = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camel_str) + return re.sub('([a-z0-9])([A-Z])', r'\1_\2', words).lower() + + @classmethod + def transform_result(cls, result: Any): + """ + 针对不同类型将下划线形式(snake_case)批量转换为小驼峰形式(camelCase)方法 + + :param result: 输入数据 + :return: 小驼峰形式结果 + """ + return SqlalchemyUtil.serialize_result(result=result, transform_case='camel_to_snake') + + +def bytes2human(n, format_str='%(value).1f%(symbol)s'): + """Used by various scripts. See: + http://goo.gl/zeJZl + + >>> bytes2human(10000) + '9.8K' + >>> bytes2human(100001221) + '95.4M' + """ + symbols = ('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB') + prefix = {} + for i, s in enumerate(symbols[1:]): + prefix[s] = 1 << (i + 1) * 10 + for symbol in reversed(symbols[1:]): + if n >= prefix[symbol]: + value = float(n) / prefix[symbol] + return format_str % locals() + return format_str % dict(symbol=symbols[0], value=n) + + +def bytes2file_response(bytes_info): + yield bytes_info + + +def export_list2excel(list_data: List): + """ + 工具方法:将需要导出的list数据转化为对应excel的二进制数据 + + :param list_data: 数据列表 + :return: 字典信息对应excel的二进制数据 + """ + df = pd.DataFrame(list_data) + binary_data = io.BytesIO() + df.to_excel(binary_data, index=False, engine='openpyxl') + binary_data = binary_data.getvalue() + + return binary_data + + +def get_excel_template(header_list: List, selector_header_list: List, option_list: List[dict]): + """ + 工具方法:将需要导出的list数据转化为对应excel的二进制数据 + + :param header_list: 表头数据列表 + :param selector_header_list: 需要设置为选择器格式的表头数据列表 + :param option_list: 选择器格式的表头预设的选项列表 + :return: 模板excel的二进制数据 + """ + # 创建Excel工作簿 + wb = Workbook() + # 选择默认的活动工作表 + ws = wb.active + + # 设置表头文字 + headers = header_list + + # 设置表头背景样式为灰色,前景色为白色 + header_fill = PatternFill(start_color='ababab', end_color='ababab', fill_type='solid') + + # 将表头写入第一行 + for col_num, header in enumerate(headers, 1): + cell = ws.cell(row=1, column=col_num) + cell.value = header + cell.fill = header_fill + # 设置列宽度为16 + ws.column_dimensions[chr(64 + col_num)].width = 12 + # 设置水平居中对齐 + cell.alignment = Alignment(horizontal='center') + + # 设置选择器的预设选项 + options = option_list + + # 获取selector_header的字母索引 + for selector_header in selector_header_list: + column_selector_header_index = headers.index(selector_header) + 1 + + # 创建数据有效性规则 + header_option = [] + for option in options: + if option.get(selector_header): + header_option = option.get(selector_header) + dv = DataValidation(type='list', formula1=f'"{",".join(header_option)}"') + # 设置数据有效性规则的起始单元格和结束单元格 + dv.add( + f'{get_column_letter(column_selector_header_index)}2:{get_column_letter(column_selector_header_index)}1048576' + ) + # 添加数据有效性规则到工作表 + ws.add_data_validation(dv) + + # 保存Excel文件为字节类型的数据 + file = io.BytesIO() + wb.save(file) + file.seek(0) + + # 读取字节数据 + excel_data = file.getvalue() + + return excel_data + + +def get_filepath_from_url(url: str): + """ + 工具方法:根据请求参数获取文件路径 + + :param url: 请求参数中的url参数 + :return: 文件路径 + """ + file_info = url.split('?')[1].split('&') + task_id = file_info[0].split('=')[1] + file_name = file_info[1].split('=')[1] + task_path = file_info[2].split('=')[1] + filepath = os.path.join(CachePathConfig.PATH, task_path, task_id, file_name) + + return filepath diff --git a/ruoyi-fastapi-backend/utils/compreface_util.py b/ruoyi-fastapi-backend/utils/compreface_util.py new file mode 100644 index 0000000..0dbd1c2 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/compreface_util.py @@ -0,0 +1,160 @@ +from config.env import ComprefaceConfig +from compreface import CompreFace +from compreface.service import RecognitionService +from compreface.collections import FaceCollection +from compreface.common.typed_dict import AllOptionsDict, check_fields_by_name + + +import httpx +import json +import os + + + +RECOGNITION_ROOT_API: str = '/api/v1/recognition' + +RECOGNIZE_API: str = RECOGNITION_ROOT_API + '/recognize' +RECOGNIZE_CRUD_API: str = RECOGNITION_ROOT_API + '/faces' +SUBJECTS_CRUD_API: str = RECOGNITION_ROOT_API + '/subjects' + +DETECTION_API: str = '/api/v1/detection/detect' + +VERIFICATION_API: str = '/api/v1/verification' + + + + +compre_face: CompreFace = CompreFace(ComprefaceConfig.COMPREFACE_BASE_URL, ComprefaceConfig.COMPERFACE_BASE_PORT) +# recognition: RecognitionService = compre_face.init_face_recognition(ComprefaceConfig.COMPREFACE_API_KEY) +# face_collection: FaceCollection = recognition.get_face_collection() + + + +class ComprefaceUtil: + + api_key: str = ComprefaceConfig.COMPREFACE_API_KEY + api_key_detection: str = ComprefaceConfig.COMPREFACE_API_KEY_DETECTION + @classmethod + async def send_request(cls, method: str, url: str, headers: dict, body: str | None=None, data=None, files=None ,json=True): + + async with httpx.AsyncClient(verify=False) as client: + response = await client.request( + method, url, headers=headers, content=body, data=data, files=files, timeout=5 # timeout 单位秒 + ) + if response.status_code in (301, 302, 303, 307, 308): + location = response.headers.get("Location") + # 可能需要处理成 GET 请求或保留原方法 + response = await client.get(location, headers=headers) + response.raise_for_status() + if json: + return response.json() + return response + + # 添加人脸到指定分组下, subject 为分组名称 + @classmethod + async def face_addition(cls, image: str = '' or bytes, subject: str = '') -> dict: + client_url: str = RECOGNIZE_CRUD_API + url: str = ComprefaceConfig.COMPREFACE_BASE_URL + ':' + ComprefaceConfig.COMPERFACE_BASE_PORT + client_url + '?subject=' + subject + # Validation loop and adding fields to the url. + m = await multipart_constructor(image) + result = await cls.send_request("post" + , url + , headers={'x-api-key': cls.api_key} + , files=m) + return result + # 人脸识别 + @classmethod + async def face_recognition(cls, image: str = '' or bytes, options: AllOptionsDict = {}) -> dict: + + client_url: str = RECOGNIZE_API + url: str = ComprefaceConfig.COMPREFACE_BASE_URL + ':' + ComprefaceConfig.COMPERFACE_BASE_PORT + client_url+"?" + # url: str = "http://10.0.0.202:8000/api/v1/recognition/recognize?face_plugins=landmarks&face_plugins=gender&face_plugins=age&face_plugins=pose" + + for key in options.keys(): + # Checks fields with necessary rules. + # key - key field by options. + check_fields_by_name(key, options[key]) + url += '&' + key + "=" + str(options[key]) + + m = await multipart_constructor(image) + result = await cls.send_request("post" + , url + , headers={'x-api-key': cls.api_key} + , files=m) + return result + + # 人脸检测 + @classmethod + async def face_detection(cls, image: str = '' or bytes, options: AllOptionsDict = {}) -> dict: + client_url: str = DETECTION_API + url: str = ComprefaceConfig.COMPREFACE_BASE_URL + ':' + ComprefaceConfig.COMPERFACE_BASE_PORT + client_url+"?" + for key in options.keys(): + # Checks fields with necessary rules. + # key - key field by options. + # check_fields_by_name(key, options[key]) + url += '&' + key + "=" + str(options[key]) + m = await multipart_constructor(image) + result = await cls.send_request("post" + , url + , headers={'x-api-key': cls.api_key_detection} + , files=m) + return result + + # 获取所有subjects + @classmethod + async def get_all_subjects(cls) -> dict: + """获取所有已注册的人脸subjects + + Returns: + dict: 包含subjects列表的字典 + """ + client_url: str = SUBJECTS_CRUD_API + url: str = ComprefaceConfig.COMPREFACE_BASE_URL + ':' + ComprefaceConfig.COMPERFACE_BASE_PORT + client_url + result = await cls.send_request("get" + , url + , headers={'x-api-key': cls.api_key}) + return result + + # 删除指定的subject + @classmethod + async def delete_subject(cls, subject: str) -> dict: + """删除指定的subject及其所有人脸数据 + + Args: + subject (str): 要删除的subject名称 + + Returns: + dict: 删除操作的结果 + """ + client_url: str = SUBJECTS_CRUD_API + '/' + subject + url: str = ComprefaceConfig.COMPREFACE_BASE_URL + ':' + ComprefaceConfig.COMPERFACE_BASE_PORT + client_url + result = await cls.send_request("delete" + , url + , headers={'x-api-key': cls.api_key}) + return result + +async def get_file(image: str = '' or bytes): + if not os.path.isfile(image): + if type(image) != bytes: + async with httpx.AsyncClient() as client: + response = await client.get(image, timeout=5) + file = response.content + else: + file = image + file = ('image.jpg', file) + else: + name_img: str = os.path.basename(image) + file = (name_img, open(image, 'rb')) + return file + + +async def multipart_constructor(image: str = '' or bytes): + + return {"file": await get_file(image)} + + +# def multipart_constructor_with_two_images(source_image: str = '' or bytes, target_image: str = '' or bytes): +# return MultipartEncoder( +# fields={'source_image': get_file( +# source_image), 'target_image': get_file(target_image)} +# ) \ No newline at end of file diff --git a/ruoyi-fastapi-backend/utils/cron_util.py b/ruoyi-fastapi-backend/utils/cron_util.py new file mode 100644 index 0000000..6232962 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/cron_util.py @@ -0,0 +1,172 @@ +import re +from datetime import datetime + + +class CronUtil: + """ + Cron表达式工具类 + """ + + @classmethod + def __valid_range(cls, search_str: str, start_range: int, end_range: int): + match = re.match(r'^(\d+)-(\d+)$', search_str) + if match: + start, end = int(match.group(1)), int(match.group(2)) + return start_range <= start < end <= end_range + return False + + @classmethod + def __valid_sum( + cls, search_str: str, start_range_a: int, start_range_b: int, end_range_a: int, end_range_b: int, sum_range: int + ): + match = re.match(r'^(\d+)/(\d+)$', search_str) + if match: + start, end = int(match.group(1)), int(match.group(2)) + return ( + start_range_a <= start <= start_range_b + and end_range_a <= end <= end_range_b + and start + end <= sum_range + ) + return False + + @classmethod + def validate_second_or_minute(cls, second_or_minute: str): + """ + 校验秒或分钟值是否正确 + + :param second_or_minute: 秒或分钟值 + :return: 校验结果 + """ + if ( + second_or_minute == '*' + or ('-' in second_or_minute and cls.__valid_range(second_or_minute, 0, 59)) + or ('/' in second_or_minute and cls.__valid_sum(second_or_minute, 0, 58, 1, 59, 59)) + or re.match(r'^(?:[0-5]?\d|59)(?:,[0-5]?\d|59)*$', second_or_minute) + ): + return True + return False + + @classmethod + def validate_hour(cls, hour: str): + """ + 校验小时值是否正确 + + :param hour: 小时值 + :return: 校验结果 + """ + if ( + hour == '*' + or ('-' in hour and cls.__valid_range(hour, 0, 23)) + or ('/' in hour and cls.__valid_sum(hour, 0, 22, 1, 23, 23)) + or re.match(r'^(?:0|[1-9]|1\d|2[0-3])(?:,(?:0|[1-9]|1\d|2[0-3]))*$', hour) + ): + return True + return False + + @classmethod + def validate_day(cls, day: str): + """ + 校验日值是否正确 + + :param day: 日值 + :return: 校验结果 + """ + if ( + day in ['*', '?', 'L'] + or ('-' in day and cls.__valid_range(day, 1, 31)) + or ('/' in day and cls.__valid_sum(day, 1, 30, 1, 30, 31)) + or ('W' in day and re.match(r'^(?:[1-9]|1\d|2\d|3[01])W$', day)) + or re.match(r'^(?:0|[1-9]|1\d|2[0-9]|3[0-1])(?:,(?:0|[1-9]|1\d|2[0-9]|3[0-1]))*$', day) + ): + return True + return False + + @classmethod + def validate_month(cls, month: str): + """ + 校验月值是否正确 + + :param month: 月值 + :return: 校验结果 + """ + if ( + month == '*' + or ('-' in month and cls.__valid_range(month, 1, 12)) + or ('/' in month and cls.__valid_sum(month, 1, 11, 1, 11, 12)) + or re.match(r'^(?:0|[1-9]|1[0-2])(?:,(?:0|[1-9]|1[0-2]))*$', month) + ): + return True + return False + + @classmethod + def validate_week(cls, week: str): + """ + 校验周值是否正确 + + :param week: 周值 + :return: 校验结果 + """ + if ( + week in ['*', '?'] + or ('-' in week and cls.__valid_range(week, 1, 7)) + or ('#' in week and re.match(r'^[1-7]#[1-4]$', week)) + or ('L' in week and re.match(r'^[1-7]L$', week)) + or re.match(r'^[1-7](?:(,[1-7]))*$', week) + ): + return True + return False + + @classmethod + def validate_year(cls, year: str): + """ + 校验年值是否正确 + + :param year: 年值 + :return: 校验结果 + """ + current_year = int(datetime.now().year) + future_years = [current_year + i for i in range(9)] + if ( + year == '*' + or ('-' in year and cls.__valid_range(year, current_year, 2099)) + or ('/' in year and cls.__valid_sum(year, current_year, 2098, 1, 2099 - current_year, 2099)) + or ('#' in year and re.match(r'^[1-7]#[1-4]$', year)) + or ('L' in year and re.match(r'^[1-7]L$', year)) + or ( + (len(year) == 4 or ',' in year) + and all(int(item) in future_years and current_year <= int(item) <= 2099 for item in year.split(',')) + ) + ): + return True + return False + + @classmethod + def validate_cron_expression(cls, cron_expression: str): + """ + 校验Cron表达式是否正确 + + :param cron_expression: Cron表达式 + :return: 校验结果 + """ + values = cron_expression.split() + if len(values) != 6 and len(values) != 7: + return False + second_validation = cls.validate_second_or_minute(values[0]) + minute_validation = cls.validate_second_or_minute(values[1]) + hour_validation = cls.validate_hour(values[2]) + day_validation = cls.validate_day(values[3]) + month_validation = cls.validate_month(values[4]) + week_validation = cls.validate_week(values[5]) + validation = ( + second_validation + and minute_validation + and hour_validation + and day_validation + and month_validation + and week_validation + ) + if len(values) == 6: + return validation + if len(values) == 7: + year_validation = cls.validate_year(values[6]) + return validation and year_validation diff --git a/ruoyi-fastapi-backend/utils/excel_util.py b/ruoyi-fastapi-backend/utils/excel_util.py new file mode 100644 index 0000000..cf19119 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/excel_util.py @@ -0,0 +1,118 @@ +import io +import pandas as pd +from openpyxl import Workbook +from openpyxl.styles import Alignment, PatternFill +from openpyxl.utils import get_column_letter +from openpyxl.worksheet.datavalidation import DataValidation +from typing import Dict, List + + +class ExcelUtil: + """ + Excel操作类 + """ + + @classmethod + def __mapping_list(cls, list_data: List, mapping_dict: Dict): + """ + 工具方法:将list数据中的字段名映射为对应的中文字段名 + + :param list_data: 数据列表 + :param mapping_dict: 映射字典 + :return: 映射后的数据列表 + """ + mapping_data = [{mapping_dict.get(key): item.get(key) for key in mapping_dict} for item in list_data] + + return mapping_data + + @classmethod + def export_list2excel(cls, list_data: List, mapping_dict: Dict): + """ + 工具方法:将需要导出的list数据转化为对应excel的二进制数据 + + :param list_data: 数据列表 + :param mapping_dict: 映射字典 + :return: list数据对应excel的二进制数据 + """ + print("-" * 100, '\n', list_data) + + new_t = list() + # list_data = dict(list_data) + for item in list_data: + t = dict(item) + new_t.append(t) + print(t) + # for k, v in item.items(): + # print(k, v) + # print(item.get('recordId')) + # print(item.get()) + # mapping_data = cls.__mapping_list(list_data, mapping_dict) + mapping_data = cls.__mapping_list(new_t, mapping_dict) + df = pd.DataFrame(mapping_data) + print("-"*100,'\n', df) + binary_data = io.BytesIO() + df.to_excel(binary_data, index=False, engine='openpyxl') + binary_data = binary_data.getvalue() + + return binary_data + + @classmethod + def get_excel_template(cls, header_list: List, selector_header_list: List, option_list: List[Dict]): + """ + 工具方法:将需要导出的list数据转化为对应excel的二进制数据 + + :param header_list: 表头数据列表 + :param selector_header_list: 需要设置为选择器格式的表头数据列表 + :param option_list: 选择器格式的表头预设的选项列表 + :return: 模板excel的二进制数据 + """ + # 创建Excel工作簿 + wb = Workbook() + # 选择默认的活动工作表 + ws = wb.active + + # 设置表头文字 + headers = header_list + + # 设置表头背景样式为灰色,前景色为白色 + header_fill = PatternFill(start_color='ababab', end_color='ababab', fill_type='solid') + + # 将表头写入第一行 + for col_num, header in enumerate(headers, 1): + cell = ws.cell(row=1, column=col_num) + cell.value = header + cell.fill = header_fill + # 设置列宽度为16 + ws.column_dimensions[chr(64 + col_num)].width = 12 + # 设置水平居中对齐 + cell.alignment = Alignment(horizontal='center') + + # 设置选择器的预设选项 + options = option_list + + # 获取selector_header的字母索引 + for selector_header in selector_header_list: + column_selector_header_index = headers.index(selector_header) + 1 + + # 创建数据有效性规则 + header_option = [] + for option in options: + if option.get(selector_header): + header_option = option.get(selector_header) + dv = DataValidation(type='list', formula1=f'"{",".join(header_option)}"') + # 设置数据有效性规则的起始单元格和结束单元格 + dv.add( + f'{get_column_letter(column_selector_header_index)}2:{get_column_letter(column_selector_header_index)}1048576' + ) + # 添加数据有效性规则到工作表 + ws.add_data_validation(dv) + + # 保存Excel文件为字节类型的数据 + file = io.BytesIO() + wb.save(file) + file.seek(0) + + # 读取字节数据 + excel_data = file.getvalue() + + return excel_data diff --git a/ruoyi-fastapi-backend/utils/gen_util.py b/ruoyi-fastapi-backend/utils/gen_util.py new file mode 100644 index 0000000..355e5d0 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/gen_util.py @@ -0,0 +1,223 @@ +import re +from datetime import datetime +from typing import List +from config.constant import GenConstant +from config.env import GenConfig +from module_generator.entity.vo.gen_vo import GenTableColumnModel, GenTableModel +from utils.string_util import StringUtil + + +class GenUtils: + """代码生成器工具类""" + + @classmethod + def init_table(cls, gen_table: GenTableModel, oper_name: str) -> None: + """ + 初始化表信息 + + param gen_table: 业务表对象 + param oper_name: 操作人 + :return: + """ + gen_table.class_name = cls.convert_class_name(gen_table.table_name) + gen_table.package_name = GenConfig.package_name + gen_table.module_name = cls.get_module_name(GenConfig.package_name) + gen_table.business_name = cls.get_business_name(gen_table.table_name) + gen_table.function_name = cls.replace_text(gen_table.table_comment) + gen_table.function_author = GenConfig.author + gen_table.create_by = oper_name + gen_table.create_time = datetime.now() + gen_table.update_by = oper_name + gen_table.update_time = datetime.now() + + @classmethod + def init_column_field(cls, column: GenTableColumnModel, table: GenTableModel) -> None: + """ + 初始化列属性字段 + + param column: 业务表字段对象 + param table: 业务表对象 + :return: + """ + data_type = cls.get_db_type(column.column_type) + column_name = column.column_name + column.table_id = table.table_id + column.create_by = table.create_by + # 设置Python字段名 + column.python_field = cls.to_camel_case(column_name) + # 设置默认类型 + column.python_type = StringUtil.get_mapping_value_by_key_ignore_case( + GenConstant.DB_TO_PYTHON_TYPE_MAPPING, data_type + ) + column.query_type = GenConstant.QUERY_EQ + + if cls.arrays_contains(GenConstant.COLUMNTYPE_STR, data_type) or cls.arrays_contains( + GenConstant.COLUMNTYPE_TEXT, data_type + ): + # 字符串长度超过500设置为文本域 + column_length = cls.get_column_length(column.column_type) + html_type = ( + GenConstant.HTML_TEXTAREA + if column_length >= 500 or cls.arrays_contains(GenConstant.COLUMNTYPE_TEXT, data_type) + else GenConstant.HTML_INPUT + ) + column.html_type = html_type + elif cls.arrays_contains(GenConstant.COLUMNTYPE_TIME, data_type): + column.html_type = GenConstant.HTML_DATETIME + elif cls.arrays_contains(GenConstant.COLUMNTYPE_NUMBER, data_type): + column.html_type = GenConstant.HTML_INPUT + + # 插入字段(默认所有字段都需要插入) + column.is_insert = GenConstant.REQUIRE + + # 编辑字段 + if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_EDIT, column_name) and not column.pk: + column.is_edit = GenConstant.REQUIRE + # 列表字段 + if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_LIST, column_name) and not column.pk: + column.is_list = GenConstant.REQUIRE + # 查询字段 + if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_QUERY, column_name) and not column.pk: + column.is_query = GenConstant.REQUIRE + + # 查询字段类型 + if column_name.lower().endswith('name'): + column.query_type = GenConstant.QUERY_LIKE + # 状态字段设置单选框 + if column_name.lower().endswith('status'): + column.html_type = GenConstant.HTML_RADIO + # 类型&性别字段设置下拉框 + elif column_name.lower().endswith('type') or column_name.lower().endswith('sex'): + column.html_type = GenConstant.HTML_SELECT + # 图片字段设置图片上传控件 + elif column_name.lower().endswith('image'): + column.html_type = GenConstant.HTML_IMAGE_UPLOAD + # 文件字段设置文件上传控件 + elif column_name.lower().endswith('file'): + column.html_type = GenConstant.HTML_FILE_UPLOAD + # 内容字段设置富文本控件 + elif column_name.lower().endswith('content'): + column.html_type = GenConstant.HTML_EDITOR + + column.create_by = table.create_by + column.create_time = datetime.now() + column.update_by = table.update_by + column.update_time = datetime.now() + + @classmethod + def arrays_contains(cls, arr: List[str], target_value: str) -> bool: + """ + 校验数组是否包含指定值 + + param arr: 数组 + param target_value: 需要校验的值 + :return: 校验结果 + """ + return target_value in arr + + @classmethod + def get_module_name(cls, package_name: str) -> str: + """ + 获取模块名 + + param package_name: 包名 + :return: 模块名 + """ + return package_name.split('.')[-1] + + @classmethod + def get_business_name(cls, table_name: str) -> str: + """ + 获取业务名 + + param table_name: 业务表名 + :return: 业务名 + """ + return table_name.split('_')[-1] + + @classmethod + def convert_class_name(cls, table_name: str) -> str: + """ + 表名转换成Python类名 + + param table_name: 业务表名 + :return: Python类名 + """ + auto_remove_pre = GenConfig.auto_remove_pre + table_prefix = GenConfig.table_prefix + if auto_remove_pre and table_prefix: + search_list = table_prefix.split(',') + table_name = cls.replace_first(table_name, search_list) + return StringUtil.convert_to_camel_case(table_name) + + @classmethod + def replace_first(cls, replacement: str, search_list: List[str]) -> str: + """ + 批量替换前缀 + + param replacement: 需要被替换的字符串 + param search_list: 可替换的字符串列表 + :return: 替换后的字符串 + """ + for search_string in search_list: + if replacement.startswith(search_string): + return replacement.replace(search_string, '', 1) + return replacement + + @classmethod + def replace_text(cls, text: str) -> str: + """ + 关键字替换 + + param text: 需要被替换的字符串 + :return: 替换后的字符串 + """ + return re.sub(r'(?:表|若依)', '', text) + + @classmethod + def get_db_type(cls, column_type: str) -> str: + """ + 获取数据库类型字段 + + param column_type: 字段类型 + :return: 数据库类型 + """ + if '(' in column_type: + return column_type.split('(')[0] + return column_type + + @classmethod + def get_column_length(cls, column_type: str) -> int: + """ + 获取字段长度 + + param column_type: 字段类型 + :return: 字段长度 + """ + if '(' in column_type: + length = len(column_type.split('(')[1].split(')')[0]) + return length + return 0 + + @classmethod + def split_column_type(cls, column_type: str) -> List[str]: + """ + 拆分列类型 + + param column_type: 字段类型 + :return: 拆分结果 + """ + if '(' in column_type and ')' in column_type: + return column_type.split('(')[1].split(')')[0].split(',') + return [] + + @classmethod + def to_camel_case(cls, text: str) -> str: + """ + 将字符串转换为驼峰命名 + + param text: 需要转换的字符串 + :return: 驼峰命名 + """ + parts = text.split('_') + return parts[0] + ''.join(word.capitalize() for word in parts[1:]) diff --git a/ruoyi-fastapi-backend/utils/haikang_util.py b/ruoyi-fastapi-backend/utils/haikang_util.py new file mode 100644 index 0000000..3b332cb --- /dev/null +++ b/ruoyi-fastapi-backend/utils/haikang_util.py @@ -0,0 +1,1056 @@ +import hashlib +import hmac +import json +import base64 +import requests +import httpx +import os +from fastapi import HTTPException +from datetime import datetime, timezone +from email.utils import format_datetime +from urllib.parse import urlparse +from dateutil.relativedelta import relativedelta + +from config.env import HaiKangConfig +from utils.log_util import logger + + +class HaikangUtil: + """ + 海康平台工具类 + """ + + # def __init__(self): + # self.HAIKANG_URL = HaiKangConfig.HAIKANG_URL + # self.HAIKANG_PORT = HaiKangConfig.HAIKANG_PORT + # self.HAIKANG_AK = HaiKangConfig.HAIKANG_AK + # self.HAIKANG_SK = HaiKangConfig.HAIKANG_SK + # 通用请求头 + @classmethod + def build_signed_headers(cls, method, url, body, app_key, app_secret): + """ + 返回一个 dict,包含所有签名请求所需的 header。 + + 参数: + - method: HTTP 方法,例如 "POST" + - url: URL完整url(代码中提取相对位置, 保留 path 和 query) + - body: 请求主体字符串(如 JSON),可为空。 + - app_key: AK + - app_secret: SK + """ + # 1. 基本 headers + accept = "*/*" + content_type = "application/json" + + # 2. 计算 MD5(可选,如果 body 存在) + content_md5 = "" + if body: + + # .digest()返回原始二进制md5 + md5_digest = hashlib.md5(body.encode("utf-8")).digest() + content_md5 = base64.b64encode(md5_digest).decode("utf-8") + + # 3. 生成 Date header(HTTP 规范格式) + now = datetime.now(timezone.utc) + date = format_datetime(now, usegmt=True) + + # 4. 构造 httpHeaders 部分 + http_headers_str = "\n".join( + [method.upper(), accept, content_md5, content_type, date, ""] + ) + + # 5. 自定义 headers 部分 + custom_headers_str = f"x-ca-key:{app_key}\n" + + # 6. 拼接 path + query + parsed = urlparse(url) + path_and_query = parsed.path + if parsed.query: + path_and_query += "?" + parsed.query + + # 7. 构造签名字符串 + string_to_sign = http_headers_str + custom_headers_str + path_and_query + + # 8. 使用 HmacSHA256 + Base64 签名 + h = hmac.new( + app_secret.encode("utf-8"), string_to_sign.encode("utf-8"), hashlib.sha256 + ) + signature = base64.b64encode(h.digest()).decode("utf-8") + + # 9. 返回完整 headers + headers = { + "Accept": accept, + "Content-MD5": content_md5, + "Content-Type": content_type, + "Date": date, + "X-Ca-Key": app_key, + "X-Ca-Signature": signature, + "X-Ca-Signature-Headers": "x-ca-key", + } + return headers + + # 发送请求 + @classmethod + async def send_request(cls, method: str, url: str, headers: dict, body: str | None, json=True): + + + + async with httpx.AsyncClient(verify=False) as client: + response = await client.request( + method, url, headers=headers, content=body, timeout=5 # timeout 单位秒 + ) + if response.status_code in (301, 302, 303, 307, 308): + location = response.headers.get("Location") + # 可能需要处理成 GET 请求或保留原方法 + response = await client.get(location, headers=headers) + response.raise_for_status() + # if response.status_code >= 400: + # logger.error(f"发送请求失败: {url} , {response.status_code} {response.text}") + # raise HTTPException(status_code=response.status_code, detail=response.text) + if json: + return response.json() + return response + + # 获取access_token + @classmethod + async def get_access_token(cls): + """获取access_token + + Returns: + _type_: json + """ + + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_ACCESS_TOKEN_URL}" + headers = cls.build_signed_headers( + "POST", url, None, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + back = await cls.send_request("POST", url, headers, None) + + if back["code"] == "0": + # logger.info("获取access_token成功") + return [True, back] + # return back["data"]["access_token"],back["data"]["token_type"] ,back["data"]["expires_in"] + else: + # logger.error("获取access_token失败", back["code"], back["msg"]) + return [False, back] + + # 查询门禁点列表v2 + @classmethod + async def get_door_list_v2(cls, pageNo: int = 1, pageSize: int = 10): + """获取门禁点列表v2 + + Args: + pageNo (int, optional): 页码. Defaults to 1. + pageSize (int, optional): 每页个数. Defaults to 10. + + Returns: + _type_: _description_ + """ + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_DOOR_SEARCH}" + + body_dict = { + "pageNo": max(pageNo, 1), + "pageSize": min(pageSize, 999), + } + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + if back["code"] == "0": + # logger.info("查询门禁点列表成功") + return [True, back["data"]] + else: + # logger.error("查询门禁点列表失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + # 查询门禁状态 + @classmethod + async def get_door_status(cls, door_index_codes: list): + """查询门禁状态 + + Args: + door_index_codes (list): 门禁点唯一标识 + """ + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_DOOR_STATES_URL}" + + body_dict = {"doorIndexCodes": door_index_codes} + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + if back["code"] == "0": + # logger.info("查询门禁状态成功") + return [ + True, + back["data"]["authDoorList"], + back["data"]["noAuthDoorIndexCodeList"], + ] + else: + # logger.error("查询门禁状态失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + # 门禁点反控 + @classmethod + async def door_do_control(cls, door_index_code: list, control_type: int): + """门禁点反控 + + Args: + door_index_code (list): 门禁点唯一标识 + control_type (int): 操作类型 0-常开, 1-门闭, 2-门开, 3-常闭, 不允许长闭 + """ + if control_type not in [0, 1, 2]: + # logger.error("control_type参数错误 ", control_type) + return [False, 400, "control_type参数错误"] + + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_DOOR_DOCONTROL_URL}" + body_dict = {"doorIndexCodes": door_index_code, "controlType": control_type} + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + """ + { + "code": "0", + "msg": "success", + "data": [ + { + "doorIndexCode": "2c95c028a809448f962a969e3ab34f", + "controlResultCode": 0, # 0表示反控成功, 其他表示失败, 门禁控制是否成功主要看这个值 + "controlResultDesc": "success", + } + ], + }, + """ + + if back["code"] == "0": + # logger.info( + # f"执行门禁控制接口成功 door_index_code:{door_index_code} control_type:{control_type} " + # ) + return [True, back["data"]] + else: + # logger.error(f"执行门禁控制失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + @classmethod + # 查询门禁点事件v2 + async def query_door_events_v2(cls, door_index_code: list, **args): + """查询门禁点事件v2 + + Args: + door_index_code (str): 门禁唯一标识 + """ + + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_DOOR_ENVENTS_URL}" + + # current_time = datetime.now(timezone.utc) + # three_months_ago = current_time - relativedelta(months=3) + + # # ISO8601 时间格式 + # current_time_iso = current_time.isoformat() + # three_months_ago_iso = three_months_ago.isoformat() + + body_dict = { + "pageNo": max(args.get("pageNo", 1), 1), + "pageSize": min(args.get("pageSize", 10), 999), + "doorIndexCode": door_index_code, + # 排序字段 + "sort": "eventTime", + # 倒序返回 + "order": "desc", + } + + if args.get("startTime") and args.get('startTime') is not None: + body_dict["startTime"] = args.get("startTime") + if args.get("endTime") and args.get('endTime') is not None: + body_dict["endTime"] = args.get("endTime") + if args.get("eventType") and args.get('eventType') is not None: + body_dict["eventType"] = args.get("eventType") + if args.get("personName") and args.get('personName') is not None: + body_dict["personName"] = args.get("personName") + + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + if back["code"] == "0": + # logger.info(f"获取门禁事件成功") + return [True, back["data"]] + else: + # logger.error(f"获取门禁事件失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + # 查看门禁在线状态 + @classmethod + async def door_online_status(cls, indexCodes: list): + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_DOOR_ONLINE_STATUS}" + + body_dict = { + "indexCodes": indexCodes + } + + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + if back["code"] == "0": + # logger.info(f"获取门禁在线状态成功") + return [True, back["data"]] + else: + # logger.error(f"获取门禁在线状态失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + # 人脸分组1vN检索 + @classmethod + async def face_group_1vN_search( + cls, + facePicBinaryData: str, # base64编码后的字符串 + pageNo: int = 1, + pageSize: int = 20, + searchNum: int = 99, + minSimilarity: int = 50, + faceGroupIndexCodes: list[str] = None, + ): + """人脸分组1vN检索 + + Args: + facePicBinaryData (str): 图片二值化后,base64编码的字符串 + pageSize (int, optional): 每页个数 Defaults to 20. + searchNum (int, optional): 最大搜索返回数. Defaults to 99. + minSimilarity (int, optional): 最小相似度. Defaults to 50. + faceGroupIndexCodes (list[str], optional): 查询人脸分组. Defaults to None. + + Returns: + _type_: _description_ + """ + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_APPLICATION_ONETOMANY_URL}" + + body_dict = { + "facePicBinaryData": facePicBinaryData, + "pageNo": pageNo, + "pageSize": pageSize, + "searchNum": searchNum, + "minSimilarity": minSimilarity, + "faceGroupIndexCodes": faceGroupIndexCodes, + } + + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + if back["code"] == "0": + # logger.info(f"获取人脸分组检索成功") + return [True, back["data"]] + else: + # logger.error(f"获取人脸分组检索失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + # 人脸评分 + @classmethod + async def face_picture_check( + cls, + facePicBinaryData: str, # + ): + """人脸评分 + + Args: + facePicBinaryData (str): 人脸图的二进制数据经过Base64编码后的字符串 + + Returns: + _type_: _description_ + """ + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_PICTURE_CHECK_URL}" + + body_dict = {"facePicBinaryData": facePicBinaryData} + + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + if back["code"] == "0": + # logger.info(f"获取人脸评分成功") + return [True, back["data"]] + else: + # logger.error(f"获取人脸评分失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + # 按条件查询人员识别事件 + @classmethod + async def get_face_capture_list_event(cls, startTime, endTime): + """按条件查询人员识别事件, 暂无实现. + + Args: + startTime (_type_): _description_ + endTime (_type_): _description_ + """ + pass + + # 按条件查询人脸分组 + @classmethod + async def get_face_group(cls): + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_FACE_GROUP_URL}" + + headers = cls.build_signed_headers( + "POST", url, json.dumps({}), HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, json.dumps({})) + + if back["code"] == "0": + # logger.info(f"按条件查询人脸成功") + return [True, back["data"]] + else: + # logger.error(f"按条件查询人脸失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + # 查询访客预约记录, visitorStatus: 0 待审核, 1 正常, 2 迟到, 3 失效, 4 审核退回, 9 审核失效, 10 邀约中, 11 邀约失效 + @classmethod + async def query_visitor_record( + cls, **args + ): + """查询访客预约记录 + + Args: + pageNo (int, optional): 页码. Defaults to 1. + pageSize (int, optional): 每页个数. Defaults to 10. + visitorStatus (int, optional): 访客状态. Defaults to 1. + + Returns: + _type_: _description_ + """ + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_VISITOR_RECORD_SEARCH}" + body_dict = args + + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + if back["code"] == "0": + logger.info(f"查询访客预约记录成功") + return [True, back["data"]] + else: + logger.error(f"查询访客预约记录失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + # 获取访客记录中的图片 + @classmethod + async def query_visitor_record_pictures(cls, svrIndexCode: str, picUri: str, save_path:str): + """获取访客记录中的图片 + + Args: + visitorRecordIndexCode (str): 访客记录索引码 + + Returns: + _type_: _description_ + """ + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_VISITOR_RECORD_PICTURES}" + + body_dict = { + "svrIndexCode": svrIndexCode, + "picUri": picUri, + } + + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json, False) + + os.makedirs(save_path, exist_ok=True) + + name = save_path.split('/')[-1] + save_path = os.path.join(save_path, name+"_访客"+".jpg") + + with open(save_path, 'wb') as f: + for chunk in back.iter_bytes(chunk_size=8192): + if chunk: # 忽略 keep-alive 的空 chunk + f.write(chunk) + return [save_path, name] + + + + + # 获取人员列表 + @classmethod + async def get_person_list(cls, pageNo: int = 1, pageSize: int = 10): + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_PERSON_LIST}" + body_dict = { + "pageNo": pageNo, + "pageSize": pageSize, + } + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + if back["code"] == "0": + return [True, back["data"]] + else: + return [False, back["code"], back["msg"]] + + # 提取人员图片 + @classmethod + async def get_person_picture(cls, serverIndexCode, picUri, save_path): + + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_GET_PERSON_PICTURE}" + body_dict = { + "serverIndexCode": serverIndexCode, + "picUri": picUri, + } + + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json, False) + + os.makedirs(save_path, exist_ok=True) + save_path = os.path.join(save_path, picUri.split('/')[-1]+".jpg") + + with open(save_path, 'wb') as f: + for chunk in back.iter_bytes(chunk_size=8192): + if chunk: # 忽略 keep-alive 的空 chunk + f.write(chunk) + + + # 单个添加人脸分组 + @classmethod + async def face_group_addition(cls, name, description): + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_FACE_GROUP_ADDITION}" + body_dict = { + "name": name, + "description": description, + } + body_json = json.dumps(body_dict, separators=(",", ":")) + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + if back["code"] == "0": + return [True, back["data"]] + else: + return [False, back["code"], back["msg"]] + + # 向分组中单个添加人脸图片 + @classmethod + async def face_single_addition(cls, faceGroupIndexCode: str, faceInfo: dict, facePic: dict): + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_FACE_SIGLE_ADDITION}" + + + body_dict = { + "faceGroupIndexCode": faceGroupIndexCode, + "faceInfo": { + "name": "测1" + }, + "facePic": { + #"facePicBinaryData": facePic["facePicBinaryData"] + "facePicUrl":"https://pic-image.yesky.com/uploadImages/newPic/2023/271/38/A3A130JG06X2.png" + } + } + body_json = json.dumps(body_dict, separators=(",", ":")) + + # print(body_json) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + back = await cls.send_request("POST", url, headers, body_json) + if back["code"] == "0": + return [True, back["data"]] + else: + return [False, back["code"], back["msg"]] + + # 批量删除人脸 + @classmethod + async def face_delete(cls, faceGroupIndexCode: str, indexCodes: list): + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_FACE_DELETE}" + + body_dict = { + "faceGroupIndexCode": faceGroupIndexCode, + "indexCodes": indexCodes, + } + body_json = json.dumps(body_dict, separators=(",", ":")) + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + back = await cls.send_request("POST", url, headers, body_json) + if back["code"] == "0": + return [True, back["data"]] + else: + return [False, back["code"], back["msg"]] + """ + 海康平台工具类 + """ + + # def __init__(self): + # self.HAIKANG_URL = HaiKangConfig.HAIKANG_URL + # self.HAIKANG_PORT = HaiKangConfig.HAIKANG_PORT + # self.HAIKANG_AK = HaiKangConfig.HAIKANG_AK + # self.HAIKANG_SK = HaiKangConfig.HAIKANG_SK + # 通用请求头 + @classmethod + def build_signed_headers(cls, method, url, body, app_key, app_secret): + """ + 返回一个 dict,包含所有签名请求所需的 header。 + + 参数: + - method: HTTP 方法,例如 "POST" + - url: URL完整url(代码中提取相对位置, 保留 path 和 query) + - body: 请求主体字符串(如 JSON),可为空。 + - app_key: AK + - app_secret: SK + """ + # 1. 基本 headers + accept = "*/*" + content_type = "application/json" + + # 2. 计算 MD5(可选,如果 body 存在) + content_md5 = "" + if body: + + # .digest()返回原始二进制md5 + md5_digest = hashlib.md5(body.encode("utf-8")).digest() + content_md5 = base64.b64encode(md5_digest).decode("utf-8") + + # 3. 生成 Date header(HTTP 规范格式) + now = datetime.now(timezone.utc) + date = format_datetime(now, usegmt=True) + + # 4. 构造 httpHeaders 部分 + http_headers_str = "\n".join( + [method.upper(), accept, content_md5, content_type, date, ""] + ) + + # 5. 自定义 headers 部分 + custom_headers_str = f"x-ca-key:{app_key}\n" + + # 6. 拼接 path + query + parsed = urlparse(url) + path_and_query = parsed.path + if parsed.query: + path_and_query += "?" + parsed.query + + # 7. 构造签名字符串 + string_to_sign = http_headers_str + custom_headers_str + path_and_query + + # 8. 使用 HmacSHA256 + Base64 签名 + h = hmac.new( + app_secret.encode("utf-8"), string_to_sign.encode("utf-8"), hashlib.sha256 + ) + signature = base64.b64encode(h.digest()).decode("utf-8") + + # 9. 返回完整 headers + headers = { + "Accept": accept, + "Content-MD5": content_md5, + "Content-Type": content_type, + "Date": date, + "X-Ca-Key": app_key, + "X-Ca-Signature": signature, + "X-Ca-Signature-Headers": "x-ca-key", + } + return headers + + # 发送请求 + # @classmethod + # async def send_request(cls, method: str, url: str, headers: dict, body: str | None): + # async with httpx.AsyncClient() as client: + # response = await client.request( + # method, url, headers=headers, content=body, timeout=5 + # ) + # response.raise_for_status() + # # if response.status_code >= 400: + # # logger.error(f"发送请求失败: {url} , {response.status_code} {response.text}") + # # raise HTTPException(status_code=response.status_code, detail=response.text) + # return response.json() + + # 获取access_token + @classmethod + async def get_access_token(cls): + """获取access_token + + Returns: + _type_: json + """ + + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_ACCESS_TOKEN_URL}" + headers = cls.build_signed_headers( + "POST", url, None, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + back = await cls.send_request("POST", url, headers, None) + + if back["code"] == "0": + logger.info("获取access_token成功") + return [True, back] + # return back["data"]["access_token"],back["data"]["token_type"] ,back["data"]["expires_in"] + else: + logger.error("获取access_token失败", back["code"], back["msg"]) + return [False, back] + + # 查询门禁点列表v2 + @classmethod + async def get_door_list_v2(cls, pageNo: int = 1, pageSize: int = 10): + """获取门禁点列表v2 + + Args: + pageNo (int, optional): 页码. Defaults to 1. + pageSize (int, optional): 每页个数. Defaults to 10. + + Returns: + _type_: _description_ + """ + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_DOOR_SEARCH}" + body_dict = { + "pageNo": max(pageNo, 1), + "pageSize": min(pageSize, 999), + } + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + if back["code"] == "0": + logger.info("查询门禁点列表成功") + return [True, back["data"]] + else: + logger.error("查询门禁点列表失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + # 查询门禁状态 + @classmethod + async def get_door_status(cls, door_index_codes: list): + """查询门禁状态 + + Args: + door_index_codes (list): 门禁点唯一标识 + """ + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_DOOR_STATES_URL}" + + body_dict = {"doorIndexCodes": door_index_codes} + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + if back["code"] == "0": + logger.info("查询门禁状态成功") + return [ + True, + back["data"]["authDoorList"], + back["data"]["noAuthDoorIndexCodeList"], + ] + else: + logger.error("查询门禁状态失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + # 门禁点反控 + @classmethod + async def door_do_control(cls, door_index_code: list, control_type: int): + """门禁点反控 + + Args: + door_index_code (list): 门禁点唯一标识 + control_type (int): 操作类型 0-常开, 1-门闭, 2-门开, 3-常闭, 不允许长闭 + """ + if control_type not in [0, 1, 2]: + logger.error("control_type参数错误 ", control_type) + return [False, 400, "control_type参数错误"] + + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_DOOR_DOCONTROL_URL}" + body_dict = {"doorIndexCodes": door_index_code, "controlType": control_type} + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + """ + { + "code": "0", + "msg": "success", + "data": [ + { + "doorIndexCode": "2c95c028a809448f962a969e3ab34f", + "controlResultCode": 0, # 0表示反控成功, 其他表示失败, 门禁控制是否成功主要看这个值 + "controlResultDesc": "success", + } + ], + }, + """ + + if back["code"] == "0": + logger.info( + f"执行门禁控制接口成功 door_index_code:{door_index_code} control_type:{control_type} " + ) + return [True, back["data"]] + else: + logger.error(f"执行门禁控制失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + @classmethod + # 查询门禁点事件v2 + async def query_door_events_v2(cls, door_index_code: list, **args): + """查询门禁点事件v2 + + Args: + door_index_code (str): 门禁唯一标识 + """ + + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_DOOR_ENVENTS_URL}" + + # current_time = datetime.now(timezone.utc) + # three_months_ago = current_time - relativedelta(months=3) + + # # ISO8601 时间格式 + # current_time_iso = current_time.isoformat() + # three_months_ago_iso = three_months_ago.isoformat() + + body_dict = { + "pageNo": max(args.get("pageNo", 1), 1), + "pageSize": min(args.get("pageSize", 10), 999), + "doorIndexCode": door_index_code, + # 排序字段 + "sort": "eventTime", + # 倒序返回 + "order": "desc", + } + + if args.get("startTime") and args.get('startTime') is not None: + body_dict["startTime"] = args.get("startTime") + if args.get("endTime") and args.get('endTime') is not None: + body_dict["endTime"] = args.get("endTime") + if args.get("eventType") and args.get('eventType') is not None: + body_dict["eventType"] = args.get("eventType") + if args.get("personName") and args.get('personName') is not None: + body_dict["personName"] = args.get("personName") + + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + if back["code"] == "0": + logger.info(f"获取门禁事件成功") + return [True, back["data"]] + else: + logger.error(f"获取门禁事件失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + # 查看门禁在线状态 + @classmethod + async def door_online_status(cls, indexCodes: list): + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_DOOR_ONLINE_STATUS}" + + body_dict = { + "indexCodes": indexCodes + } + + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + if back["code"] == "0": + logger.info(f"获取门禁在线状态成功") + return [True, back["data"]] + else: + logger.error(f"获取门禁在线状态失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + # 人脸分组1vN检索 + @classmethod + async def face_group_1vN_search( + cls, + facePicBinaryData: str, # base64编码后的字符串 + pageNo: int = 1, + pageSize: int = 20, + searchNum: int = 99, + minSimilarity: int = 50, + faceGroupIndexCodes: list[str] = None, + ): + """人脸分组1vN检索 + + Args: + facePicBinaryData (str): 图片二值化后,base64编码的字符串 + pageSize (int, optional): 每页个数 Defaults to 20. + searchNum (int, optional): 最大搜索返回数. Defaults to 99. + minSimilarity (int, optional): 最小相似度. Defaults to 50. + faceGroupIndexCodes (list[str], optional): 查询人脸分组. Defaults to None. + + Returns: + _type_: _description_ + """ + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_APPLICATION_ONETOMANY_URL}" + + body_dict = { + "facePicBinaryData": facePicBinaryData, + "pageNo": pageNo, + "pageSize": pageSize, + "searchNum": searchNum, + "minSimilarity": minSimilarity, + "faceGroupIndexCodes": faceGroupIndexCodes, + } + + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + if back["code"] == "0": + logger.info(f"获取人脸分组检索成功") + return [True, back["data"]] + else: + logger.error(f"获取人脸分组检索失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + # 人脸评分 + @classmethod + async def face_picture_check( + cls, + facePicBinaryData: str, # + ): + """人脸评分 + + Args: + facePicBinaryData (str): 人脸图的二进制数据经过Base64编码后的字符串 + + Returns: + _type_: _description_ + """ + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_PICTURE_CHECK_URL}" + + body_dict = {"facePicBinaryData": facePicBinaryData} + + body_json = json.dumps(body_dict, separators=(",", ":")) + + headers = cls.build_signed_headers( + "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, body_json) + + if back["code"] == "0": + logger.info(f"获取人脸评分成功") + return [True, back["data"]] + else: + logger.error(f"获取人脸评分失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + # 按条件查询人员识别事件 + @classmethod + async def get_face_capture_list_event(cls, startTime, endTime): + """按条件查询人员识别事件, 暂无实现. + + Args: + startTime (_type_): _description_ + endTime (_type_): _description_ + """ + pass + + # 按条件查询人脸分组 + @classmethod + async def get_face_group(cls): + url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_FACE_GROUP_URL}" + + headers = cls.build_signed_headers( + "POST", url, None, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + ) + + back = await cls.send_request("POST", url, headers, None) + + if back["code"] == "0": + logger.info(f"按条件查询人脸成功") + return [True, back["data"]] + else: + logger.error(f"按条件查询人脸失败 ", back["code"], back["msg"]) + return [False, back["code"], back["msg"]] + + # 查询访客预约记录, visitorStatus: 0 待审核, 1 正常, 2 迟到, 3 失效, 4 审核退回, 9 审核失效, 10 邀约中, 11 邀约失效 + # @classmethod + # async def query_visitor_record( + # cls, **args + # ): + # """查询访客预约记录 + + # Args: + # pageNo (int, optional): 页码. Defaults to 1. + # pageSize (int, optional): 每页个数. Defaults to 10. + # visitorStatus (int, optional): 访客状态. Defaults to 1. + + # Returns: + # _type_: _description_ + # """ + # url = f"{HaiKangConfig.HAIKANG_URL}:{HaiKangConfig.HAIKANG_PORT}/artemis{HaiKangConfig.HAIKANG_VISITOR_RECORD_SEARCH}" + # body_dict = args + + # body_json = json.dumps(body_dict, separators=(",", ":")) + + # headers = cls.build_signed_headers( + # "POST", url, body_json, HaiKangConfig.HAIKANG_AK, HaiKangConfig.HAIKANG_SK + # ) + + # back = await cls.send_request("POST", url, headers, body_json) + + # if back["code"] == "0": + # logger.info(f"查询访客预约记录成功") + # return [True, back["data"]] + # else: + # logger.error(f"查询访客预约记录失败 ", back["code"], back["msg"]) + # return [False, back["code"], back["msg"]] diff --git a/ruoyi-fastapi-backend/utils/lang_graph_util.py b/ruoyi-fastapi-backend/utils/lang_graph_util.py new file mode 100644 index 0000000..b915d17 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/lang_graph_util.py @@ -0,0 +1,2 @@ +# pip install -qU "langchain[anthropic]" to call the model + diff --git a/ruoyi-fastapi-backend/utils/log_util.py b/ruoyi-fastapi-backend/utils/log_util.py new file mode 100644 index 0000000..f953f55 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/log_util.py @@ -0,0 +1,60 @@ +import os +import sys +import time +from loguru import logger as _logger +from typing import Dict +from middlewares.trace_middleware import TraceCtx + + +class LoggerInitializer: + def __init__(self): + self.log_path = os.path.join(os.getcwd(), 'logs') + self.__ensure_log_directory_exists() + self.log_path_error = os.path.join(self.log_path, f'{time.strftime("%Y-%m-%d")}_error.log') + + def __ensure_log_directory_exists(self): + """ + 确保日志目录存在,如果不存在则创建 + """ + if not os.path.exists(self.log_path): + os.mkdir(self.log_path) + + @staticmethod + def __filter(log: Dict): + """ + 自定义日志过滤器,添加trace_id + """ + log['trace_id'] = TraceCtx.get_id() + return log + + def init_log(self): + """ + 初始化日志配置 + """ + # 自定义日志格式 + format_str = ( + '{time:YYYY-MM-DD HH:mm:ss.SSS} | ' + '{trace_id} | ' + '{level: <8} | ' + '{name}:{function}:{line} - ' + '{message}' + ) + _logger.remove() + # 移除后重新添加sys.stderr, 目的: 控制台输出与文件日志内容和结构一致 + _logger.add(sys.stderr, filter=self.__filter, format=format_str, enqueue=True) + _logger.add( + self.log_path_error, + filter=self.__filter, + format=format_str, + rotation='50MB', + encoding='utf-8', + enqueue=True, + compression='zip', + ) + + return _logger + + +# 初始化日志处理器 +log_initializer = LoggerInitializer() +logger = log_initializer.init_log() diff --git a/ruoyi-fastapi-backend/utils/message_util.py b/ruoyi-fastapi-backend/utils/message_util.py new file mode 100644 index 0000000..3d3eb51 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/message_util.py @@ -0,0 +1,5 @@ +from utils.log_util import logger + + +def message_service(sms_code: str): + logger.info(f'短信验证码为{sms_code}') diff --git a/ruoyi-fastapi-backend/utils/page_util.py b/ruoyi-fastapi-backend/utils/page_util.py new file mode 100644 index 0000000..dfe8a31 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/page_util.py @@ -0,0 +1,116 @@ +import math +from pydantic import BaseModel, ConfigDict +from pydantic.alias_generators import to_camel +from sqlalchemy import func, select, Select +from sqlalchemy.ext.asyncio import AsyncSession +from typing import Optional, List +from utils.common_util import CamelCaseUtil + + +class PageResponseModel(BaseModel): + """ + 列表分页查询返回模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + rows: List = [] + page_num: Optional[int] = None + page_size: Optional[int] = None + total: int + has_next: Optional[bool] = None + + +class PageUtil: + """ + 分页工具类 + """ + + @classmethod + def get_page_obj(cls, data_list: List, page_num: int, page_size: int): + """ + 输入数据列表data_list和分页信息,返回分页数据列表结果 + + :param data_list: 原始数据列表 + :param page_num: 当前页码 + :param page_size: 当前页面数据量 + :return: 分页数据对象 + """ + # 计算起始索引和结束索引 + start = (page_num - 1) * page_size + end = page_num * page_size + + # 根据计算得到的起始索引和结束索引对数据列表进行切片 + paginated_data = data_list[start:end] + has_next = True if math.ceil(len(data_list) / page_size) > page_num else False + + result = PageResponseModel( + rows=paginated_data, pageNum=page_num, pageSize=page_size, total=len(data_list), hasNext=has_next + ) + + return result + + @classmethod + async def paginate(cls, db: AsyncSession, query: Select, page_num: int, page_size: int, is_page: bool = False): + """ + 输入查询语句和分页信息,返回分页数据列表结果 + + :param db: orm对象 + :param query: sqlalchemy查询语句 + :param page_num: 当前页码 + :param page_size: 当前页面数据量 + :param is_page: 是否开启分页 + :return: 分页数据对象 + """ + if is_page: + total = (await db.execute(select(func.count('*')).select_from(query.subquery()))).scalar() + query_result = await db.execute(query.offset((page_num - 1) * page_size).limit(page_size)) + paginated_data = [] + for row in query_result: + if row and len(row) == 1: + paginated_data.append(row[0]) + else: + paginated_data.append(row) + has_next = math.ceil(total / page_size) > page_num + result = PageResponseModel( + rows=CamelCaseUtil.transform_result(paginated_data), + pageNum=page_num, + pageSize=page_size, + total=total, + hasNext=has_next, + ) + else: + query_result = await db.execute(query) + no_paginated_data = [] + for row in query_result: + if row and len(row) == 1: + no_paginated_data.append(row[0]) + else: + no_paginated_data.append(row) + result = CamelCaseUtil.transform_result(no_paginated_data) + + return result + + +def get_page_obj(data_list: List, page_num: int, page_size: int): + """ + 输入数据列表data_list和分页信息,返回分页数据列表结果 + + :param data_list: 原始数据列表 + :param page_num: 当前页码 + :param page_size: 当前页面数据量 + :return: 分页数据对象 + """ + # 计算起始索引和结束索引 + start = (page_num - 1) * page_size + end = page_num * page_size + + # 根据计算得到的起始索引和结束索引对数据列表进行切片 + paginated_data = data_list[start:end] + has_next = True if math.ceil(len(data_list) / page_size) > page_num else False + + result = PageResponseModel( + rows=paginated_data, pageNum=page_num, pageSize=page_size, total=len(data_list), hasNext=has_next + ) + + return result diff --git a/ruoyi-fastapi-backend/utils/pwd_util.py b/ruoyi-fastapi-backend/utils/pwd_util.py new file mode 100644 index 0000000..86e9c27 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/pwd_util.py @@ -0,0 +1,30 @@ +from passlib.context import CryptContext + +pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto') + + +class PwdUtil: + """ + 密码工具类 + """ + + @classmethod + def verify_password(cls, plain_password, hashed_password): + """ + 工具方法:校验当前输入的密码与数据库存储的密码是否一致 + + :param plain_password: 当前输入的密码 + :param hashed_password: 数据库存储的密码 + :return: 校验结果 + """ + return pwd_context.verify(plain_password, hashed_password) + + @classmethod + def get_password_hash(cls, input_password): + """ + 工具方法:对当前输入的密码进行加密 + + :param input_password: 输入的密码 + :return: 加密成功的密码 + """ + return pwd_context.hash(input_password) diff --git a/ruoyi-fastapi-backend/utils/ragflow_asy_util.py b/ruoyi-fastapi-backend/utils/ragflow_asy_util.py new file mode 100644 index 0000000..89037ef --- /dev/null +++ b/ruoyi-fastapi-backend/utils/ragflow_asy_util.py @@ -0,0 +1,1069 @@ +import aiohttp +import asyncio +import inspect +import json +from typing import Optional, List, Dict, Any, Union, AsyncGenerator +import os +from pathlib import Path + + +class RAGFlowError(Exception): + """RAGFlow API错误异常""" + def __init__(self, code: int, message: str): + self.code = code + self.message = message + super().__init__(f"Error {code}: {message}") + + +class AsyncRAGFlowClient: + """异步RAGFlow API客户端""" + + def __init__(self, base_url: str, api_key: str, timeout: int = 30, connector_kwargs: Optional[Dict[str, Any]] = None): + """ + 初始化RAGFlow客户端 + + Args: + base_url: RAGFlow服务器地址 + api_key: API密钥 + timeout: 请求超时时间(秒) + """ + try: + self.base_url = base_url.rstrip('/') + except Exception: + self.base_url = base_url + self.api_key = api_key + self.timeout = timeout + self.connector_kwargs = connector_kwargs or { + "limit": 100, + "ssl": False, + "keepalive_timeout": 30, + } + self.headers = { + 'Authorization': f'Bearer {api_key}', + 'Content-Type': 'application/json' + } + self._session = None + + async def __aenter__(self): + """异步上下文管理器入口""" + await self.create_session() + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + """异步上下文管理器出口""" + await self.close_session() + + async def create_session(self): + """创建aiohttp会话""" + if self._session is None: + timeout = aiohttp.ClientTimeout(total=self.timeout) + connector = aiohttp.TCPConnector(**self.connector_kwargs) + self._session = aiohttp.ClientSession(timeout=timeout, connector=connector) + + async def close_session(self): + """关闭aiohttp会话""" + if self._session: + await self._session.close() + self._session = None + + async def _request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]: + """发送HTTP请求""" + if not self._session: + await self.create_session() + + url = f"{self.base_url}{endpoint}" + + # 处理headers + headers = kwargs.pop('headers', self.headers.copy()) + + async with self._session.request(method, url, headers=headers, **kwargs) as response: + try: + result = await response.json() + except (aiohttp.ContentTypeError, json.JSONDecodeError): + if response.status == 200: + content = await response.read() + return {'code': 0, 'data': content} + else: + text = await response.text() + raise RAGFlowError(response.status, text) + + if result.get('code', 0) != 0: + raise RAGFlowError(result.get('code'), result.get('message', 'Unknown error')) + + return result + + async def _stream_request(self, method: str, endpoint: str, **kwargs) -> AsyncGenerator[Dict[str, Any], None]: + """发送流式HTTP请求""" + if not self._session: + await self.create_session() + + url = f"{self.base_url}{endpoint}" + headers = kwargs.pop('headers', self.headers.copy()) + + async with self._session.request(method, url, headers=headers, **kwargs) as response: + async for line in response.content: + if line: + line_str = line.decode('utf-8').strip() + if line_str.startswith('data:'): + try: + data = json.loads(line_str[5:].strip()) + yield data + except json.JSONDecodeError: + continue + + # ==================== + # OpenAI兼容API + # ==================== + + async def create_chat_completion(self, chat_id: str, model: str, messages: List[Dict[str, str]], + stream: bool = False) -> Union[Dict[str, Any], AsyncGenerator[Dict[str, Any], None]]: + """ + 创建聊天完成 + + Args: + chat_id: 聊天ID + model: 模型名称 + messages: 消息列表 + stream: 是否流式返回 + """ + endpoint = f"/api/v1/chats_openai/{chat_id}/chat/completions" + data = { + "model": model, + "messages": messages, + "stream": stream + } + + if stream: + return self._stream_request('POST', endpoint, json=data) + else: + return await self._request('POST', endpoint, json=data) + + async def create_agent_completion(self, agent_id: str, model: str, messages: List[Dict[str, str]], + stream: bool = False) -> Union[Dict[str, Any], AsyncGenerator[Dict[str, Any], None]]: + """ + 创建代理完成 + + Args: + agent_id: 代理ID + model: 模型名称 + messages: 消息列表 + stream: 是否流式返回 + """ + endpoint = f"/api/v1/agents_openai/{agent_id}/chat/completions" + data = { + "model": model, + "messages": messages, + "stream": stream + } + + if stream: + return self._stream_request('POST', endpoint, json=data) + else: + return await self._request('POST', endpoint, json=data) + + # ==================== + # 数据集管理 + # ==================== + + async def create_dataset(self, name: str, avatar: Optional[str] = None, description: Optional[str] = None, + embedding_model: Optional[str] = None, permission: str = "me", + chunk_method: str = "naive", + parser_config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """ + 创建数据集 + + Args: + name: 数据集名称 + avatar: Base64编码的头像 + description: 描述 + embedding_model: 嵌入模型 + permission: 权限设置 ("me" 或 "team") + chunk_method: 分块方法 + parser_config: 解析器配置 + """ + endpoint = "/api/v1/datasets" + data = { + "name": name, + "permission": permission, + "chunk_method": chunk_method, + } + + if avatar: + data["avatar"] = avatar + if description: + data["description"] = description + if embedding_model: + data["embedding_model"] = embedding_model + if parser_config: + data["parser_config"] = parser_config + + return await self._request('POST', endpoint, json=data) + + async def delete_datasets(self, ids: Optional[List[str]] = None) -> Dict[str, Any]: + """ + 删除数据集 + + Args: + ids: 要删除的数据集ID列表,None表示删除所有 + """ + endpoint = "/api/v1/datasets" + data = {"ids": ids} + return await self._request('DELETE', endpoint, json=data) + + async def update_dataset(self, dataset_id: str, name: Optional[str] = None, + avatar: Optional[str] = None, description: Optional[str] = None, + embedding_model: Optional[str] = None, permission: Optional[str] = None, + chunk_method: Optional[str] = None, pagerank: Optional[int] = None, + parser_config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """ + 更新数据集 + """ + endpoint = f"/api/v1/datasets/{dataset_id}" + data = {} + + if name is not None: + data["name"] = name + if avatar is not None: + data["avatar"] = avatar + if description is not None: + data["description"] = description + if embedding_model is not None: + data["embedding_model"] = embedding_model + if permission is not None: + data["permission"] = permission + if chunk_method is not None: + data["chunk_method"] = chunk_method + if pagerank is not None: + data["pagerank"] = pagerank + if parser_config is not None: + data["parser_config"] = parser_config + + return await self._request('PUT', endpoint, json=data) + + async def list_datasets(self, page: int = 1, page_size: int = 30, orderby: str = "create_time", + desc: str = "true", name: Optional[str] = None, + dataset_id: Optional[str] = None) -> Dict[str, Any]: + """ + 列出数据集 + """ + endpoint = "/api/v1/datasets" + params = { + "page": page, + "page_size": page_size, + "orderby": orderby, + "desc": desc + } + + if name: + params["name"] = name + if dataset_id: + params["id"] = dataset_id + + return await self._request('GET', endpoint, params=params) + + # ==================== + # 文档管理 + # ==================== + + async def upload_documents(self, dataset_id: str, file_paths: List[str]) -> Dict[str, Any]: + """ + 上传文档到数据集 + + Args: + dataset_id: 数据集ID + file_paths: 文件路径列表 + """ + if not self._session: + await self.create_session() + + endpoint = f"/api/v1/datasets/{dataset_id}/documents" + url = f"{self.base_url}{endpoint}" + + # 准备multipart数据 + data = aiohttp.FormData() + for file_path in file_paths: + if os.path.exists(file_path): + file_name = Path(file_path).name + with open(file_path, 'rb') as f: + data.add_field('file', f.read(), filename=file_name) + + headers = {'Authorization': f'Bearer {self.api_key}'} + + async with self._session.post(url, headers=headers, data=data) as response: + try: + result = await response.json() + if result.get('code', 0) != 0: + raise RAGFlowError(result.get('code'), result.get('message')) + return result + except (aiohttp.ContentTypeError, json.JSONDecodeError): + text = await response.text() + raise RAGFlowError(response.status, text) + + async def upload_documents_bytes(self, dataset_id: str, file_bytes: List) -> Dict[str, Any]: + """ + 上传文档到数据集 + + Args: + dataset_id: 数据集ID + file_name: 文件名 + file_bytes: 文件二进制列表 + """ + if not self._session: + await self.create_session() + + endpoint = f"/api/v1/datasets/{dataset_id}/documents" + url = f"{self.base_url}{endpoint}" + + # 准备multipart数据 + data = aiohttp.FormData() + for file in file_bytes: + filename = getattr(file, 'filename', None) + payload = None + + read_method = getattr(file, 'read', None) + if callable(read_method): + result = read_method() + payload = await result if inspect.isawaitable(result) else result + elif hasattr(file, 'file') and callable(getattr(file.file, 'read', None)): + payload = await asyncio.to_thread(file.file.read) + elif isinstance(file, tuple) and len(file) == 2: + filename = filename or file[0] + payload = file[1] + elif isinstance(file, (bytes, bytearray)): + payload = file + else: + payload = file + + if filename is None: + filename = getattr(file, 'name', 'upload') + + data.add_field('file', payload, filename=str(filename)) + + headers = {'Authorization': f'Bearer {self.api_key}'} + + async with self._session.post(url, headers=headers, data=data) as response: + try: + result = await response.json() + if result.get('code', 0) != 0: + raise RAGFlowError(result.get('code'), result.get('message')) + return result + except (aiohttp.ContentTypeError, json.JSONDecodeError): + text = await response.text() + raise RAGFlowError(response.status, text) + + async def update_document(self, dataset_id: str, document_id: str, name: Optional[str] = None, + meta_fields: Optional[Dict[str, Any]] = None, + chunk_method: Optional[str] = None, + parser_config: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: + """ + 更新文档配置 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/documents/{document_id}" + data = {} + + if name is not None: + data["name"] = name + if meta_fields is not None: + data["meta_fields"] = meta_fields + if chunk_method is not None: + data["chunk_method"] = chunk_method + if parser_config is not None: + data["parser_config"] = parser_config + + + return await self._request('PUT', endpoint, json=data) + + async def download_document(self, dataset_id: str, document_id: str, save_path: str) -> None: + """ + 下载文档 + """ + if not self._session: + await self.create_session() + + endpoint = f"/api/v1/datasets/{dataset_id}/documents/{document_id}" + url = f"{self.base_url}{endpoint}" + headers = {'Authorization': f'Bearer {self.api_key}'} + + async with self._session.get(url, headers=headers) as response: + if response.status == 200: + content = await response.read() + with open(save_path, 'wb') as f: + f.write(content) + else: + try: + error = await response.json() + raise RAGFlowError(error.get('code'), error.get('message')) + except (aiohttp.ContentTypeError, json.JSONDecodeError): + text = await response.text() + raise RAGFlowError(response.status, text) + + async def list_documents(self, dataset_id: str, page: int = 1, page_size: int = 30, + orderby: str = "create_time", desc: str = "true", + keywords: Optional[str] = None, document_id: Optional[str] = None, + document_name: Optional[str] = None) -> Dict[str, Any]: + """ + 列出数据集中的文档 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/documents" + params = { + "page": page, + "page_size": page_size, + "orderby": orderby, + "desc": desc + } + + if keywords: + params["keywords"] = keywords + if document_id: + params["id"] = document_id + if document_name: + params["name"] = document_name + + return await self._request('GET', endpoint, params=params) + + async def delete_documents(self, dataset_id: str, ids: Optional[List[str]] = None) -> Dict[str, Any]: + """ + 删除文档 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/documents" + data = {"ids": ids} if ids else {} + return await self._request('DELETE', endpoint, json=data) + + async def parse_documents(self, dataset_id: str, document_ids: List[str]) -> Dict[str, Any]: + """ + 解析文档 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/chunks" + data = {"document_ids": document_ids} + return await self._request('POST', endpoint, json=data) + + async def stop_parsing_documents(self, dataset_id: str, document_ids: List[str]) -> Dict[str, Any]: + """ + 停止解析文档 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/chunks" + data = {"document_ids": document_ids} + return await self._request('DELETE', endpoint, json=data) + + # ==================== + # 分块管理 + # ==================== + + async def add_chunk(self, dataset_id: str, document_id: str, content: str, + important_keywords: Optional[List[str]] = None, + questions: Optional[List[str]] = None) -> Dict[str, Any]: + """ + 添加分块 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/documents/{document_id}/chunks" + data = {"content": content} + + if important_keywords: + data["important_keywords"] = important_keywords + if questions: + data["questions"] = questions + + return await self._request('POST', endpoint, json=data) + + async def list_chunks(self, dataset_id: str, document_id: str, keywords: Optional[str] = None, + page: int = 1, page_size: int = 1024, + chunk_id: Optional[str] = None) -> Dict[str, Any]: + """ + 列出分块 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/documents/{document_id}/chunks" + params = {"page": page, "page_size": page_size} + + if keywords: + params["keywords"] = keywords + if chunk_id: + params["id"] = chunk_id + + return await self._request('GET', endpoint, params=params) + + async def delete_chunks(self, dataset_id: str, document_id: str, + chunk_ids: Optional[List[str]] = None) -> Dict[str, Any]: + """ + 删除分块 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/documents/{document_id}/chunks" + data = {"chunk_ids": chunk_ids} if chunk_ids else {} + return await self._request('DELETE', endpoint, json=data) + + async def update_chunk(self, dataset_id: str, document_id: str, chunk_id: str, + content: Optional[str] = None, + important_keywords: Optional[List[str]] = None, + available: Optional[bool] = None) -> Dict[str, Any]: + """ + 更新分块 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/documents/{document_id}/chunks/{chunk_id}" + data = {} + + if content is not None: + data["content"] = content + if important_keywords is not None: + data["important_keywords"] = important_keywords + if available is not None: + data["available"] = available + + return await self._request('PUT', endpoint, json=data) + + async def retrieve_chunks(self, question: str, dataset_ids: Optional[List[str]] = None, + document_ids: Optional[List[str]] = None, page: int = 1, + page_size: int = 30, similarity_threshold: float = 0.2, + vector_similarity_weight: float = 0.3, top_k: int = 1024, + rerank_id: Optional[str] = None, keyword: bool = False, + highlight: bool = False) -> Dict[str, Any]: + """ + 检索分块 + """ + endpoint = "/api/v1/retrieval" + data = { + "question": question, + "page": page, + "page_size": page_size, + "similarity_threshold": similarity_threshold, + "vector_similarity_weight": vector_similarity_weight, + "top_k": top_k, + "keyword": keyword, + "highlight": highlight + } + + if dataset_ids: + data["dataset_ids"] = dataset_ids + if document_ids: + data["document_ids"] = document_ids + if rerank_id: + data["rerank_id"] = rerank_id + + return await self._request('POST', endpoint, json=data) + + # ==================== + # 聊天助手管理 + # ==================== + + async def create_chat_assistant(self, name: str, avatar: Optional[str] = None, + dataset_ids: Optional[List[str]] = None, + llm: Optional[Dict[str, Any]] = None, + prompt: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """ + 创建聊天助手 + """ + endpoint = "/api/v1/chats" + data = {"name": name} + + if avatar: + data["avatar"] = avatar + if dataset_ids: + data["dataset_ids"] = dataset_ids + if llm: + data["llm"] = llm + if prompt: + data["prompt"] = prompt + + return await self._request('POST', endpoint, json=data) + + async def update_chat_assistant(self, chat_id: str, name: Optional[str] = None, + avatar: Optional[str] = None, + dataset_ids: Optional[List[str]] = None, + llm: Optional[Dict[str, Any]] = None, + prompt: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """ + 更新聊天助手 + """ + endpoint = f"/api/v1/chats/{chat_id}" + data = {} + + if name is not None: + data["name"] = name + if avatar is not None: + data["avatar"] = avatar + if dataset_ids is not None: + data["dataset_ids"] = dataset_ids + if llm is not None: + data["llm"] = llm + if prompt is not None: + data["prompt"] = prompt + + return await self._request('PUT', endpoint, json=data) + + async def delete_chat_assistants(self, ids: Optional[List[str]] = None) -> Dict[str, Any]: + """ + 删除聊天助手 + """ + endpoint = "/api/v1/chats" + data = {"ids": ids} if ids else {} + return await self._request('DELETE', endpoint, json=data) + + async def list_chat_assistants(self, page: int = 1, page_size: int = 30, + orderby: str = "create_time", desc: str = "true", + name: Optional[str] = None, + chat_id: Optional[str] = None) -> Dict[str, Any]: + """ + 列出聊天助手 + """ + endpoint = "/api/v1/chats" + params = { + "page": page, + "page_size": page_size, + "orderby": orderby, + "desc": desc + } + + if name: + params["name"] = name + if chat_id: + params["id"] = chat_id + + return await self._request('GET', endpoint, params=params) + + # ==================== + # 会话管理 + # ==================== + + async def create_session_with_chat(self, chat_id: str, name: str, + user_id: Optional[str] = None) -> Dict[str, Any]: + """ + 创建与聊天助手的会话 + """ + endpoint = f"/api/v1/chats/{chat_id}/sessions" + data = {"name": name} + + if user_id: + data["user_id"] = user_id + + return await self._request('POST', endpoint, json=data) + + async def update_chat_session(self, chat_id: str, session_id: str, name: Optional[str] = None, + user_id: Optional[str] = None) -> Dict[str, Any]: + """ + 更新聊天会话 + """ + endpoint = f"/api/v1/chats/{chat_id}/sessions/{session_id}" + data = {} + + if name is not None: + data["name"] = name + if user_id is not None: + data["user_id"] = user_id + + return await self._request('PUT', endpoint, json=data) + + async def list_chat_sessions(self, chat_id: str, page: int = 1, page_size: int = 30, + orderby: str = "create_time", desc: str = "true", + name: Optional[str] = None, session_id: Optional[str] = None, + user_id: Optional[str] = None) -> Dict[str, Any]: + """ + 列出与指定聊天助手相关的聊天会话 + """ + endpoint = f"/api/v1/chats/{chat_id}/sessions" + params = { + "page": page, + "page_size": page_size, + "orderby": orderby, + "desc": desc + } + + if name: + params["name"] = name + if session_id: + params["id"] = session_id + if user_id: + params["user_id"] = user_id + + return await self._request('GET', endpoint, params=params) + + async def delete_chat_sessions(self, chat_id: str, ids: Optional[List[str]] = None) -> Dict[str, Any]: + """ + 删除聊天会话 + """ + endpoint = f"/api/v1/chats/{chat_id}/sessions" + data = {"ids": ids} if ids else {} + return await self._request('DELETE', endpoint, json=data) + + async def converse_with_chat_assistant(self, chat_id: str, question: str, stream: bool = True, + session_id: Optional[str] = None, + user_id: Optional[str] = None) -> Union[Dict[str, Any], AsyncGenerator[Dict[str, Any], None]]: + """ + 与聊天助手对话 + """ + endpoint = f"/api/v1/chats/{chat_id}/completions" + data = {"question": question, "stream": stream} + + if session_id: + data["session_id"] = session_id + + print(f"开始对话: {question} {session_id}") + if user_id: + data["user_id"] = user_id + + if stream: + return self._stream_request('POST', endpoint, json=data) + else: + return await self._request('POST', endpoint, json=data) + + # ==================== + # 代理管理 + # ==================== + + async def create_session_with_agent(self, agent_id: str, user_id: Optional[str] = None, + file_data: Optional[Dict[str, Any]] = None, + **kwargs) -> Dict[str, Any]: + """ + 创建与代理的会话 + """ + if not self._session: + await self.create_session() + + endpoint = f"/api/v1/agents/{agent_id}/sessions" + url = f"{self.base_url}{endpoint}" + params = {} + + if user_id: + params["user_id"] = user_id + + if file_data: + # 处理文件上传 + data = aiohttp.FormData() + + for key, file_path in file_data.items(): + if os.path.exists(file_path): + file_name = Path(file_path).name + with open(file_path, 'rb') as f: + data.add_field(key, f.read(), filename=file_name) + + headers = {'Authorization': f'Bearer {self.api_key}'} + + async with self._session.post(url, headers=headers, data=data, params=params) as response: + try: + result = await response.json() + if result.get('code', 0) != 0: + raise RAGFlowError(result.get('code'), result.get('message')) + return result + except (aiohttp.ContentTypeError, json.JSONDecodeError): + text = await response.text() + raise RAGFlowError(response.status, text) + else: + # 普通JSON请求 + data = kwargs + return await self._request('POST', endpoint, json=data, params=params) + + async def converse_with_agent(self, agent_id: str, question: str, stream: bool = True, + session_id: Optional[str] = None, user_id: Optional[str] = None, + sync_dsl: bool = False, **kwargs) -> Union[Dict[str, Any], AsyncGenerator[Dict[str, Any], None]]: + """ + 与代理对话 + """ + endpoint = f"/api/v1/agents/{agent_id}/completions" + data = {"question": question, "stream": stream, "sync_dsl": sync_dsl} + + if session_id: + data["session_id"] = session_id + if user_id: + data["user_id"] = user_id + + # 添加其他Begin组件参数 + data.update(kwargs) + + if stream: + return self._stream_request('POST', endpoint, json=data) + else: + return await self._request('POST', endpoint, json=data) + + async def list_agent_sessions(self, agent_id: str, page: int = 1, page_size: int = 30, + orderby: str = "create_time", desc: str = "true", + session_id: Optional[str] = None, user_id: Optional[str] = None, + dsl: bool = True) -> Dict[str, Any]: + """ + 列出代理会话 + """ + endpoint = f"/api/v1/agents/{agent_id}/sessions" + params = { + "page": page, + "page_size": page_size, + "orderby": orderby, + "desc": desc, + "dsl": dsl + } + + if session_id: + params["id"] = session_id + if user_id: + params["user_id"] = user_id + + return await self._request('GET', endpoint, params=params) + + async def delete_agent_sessions(self, agent_id: str, ids: Optional[List[str]] = None) -> Dict[str, Any]: + """ + 删除代理会话 + """ + endpoint = f"/api/v1/agents/{agent_id}/sessions" + data = {"ids": ids} if ids else {} + return await self._request('DELETE', endpoint, json=data) + + async def get_related_questions(self, question: str, login_token: str) -> Dict[str, Any]: + """ + 生成相关问题 + """ + endpoint = "/v1/sessions/related_questions" + headers = { + 'Authorization': f'Bearer {login_token}', + 'Content-Type': 'application/json' + } + data = {"question": question} + + return await self._request('POST', endpoint, headers=headers, json=data) + + async def list_agents(self, page: int = 1, page_size: int = 30, orderby: str = "create_time", + desc: str = "true", name: Optional[str] = None, + agent_id: Optional[str] = None) -> Dict[str, Any]: + """ + 列出代理 + """ + endpoint = "/api/v1/agents" + params = { + "page": page, + "page_size": page_size, + "orderby": orderby, + "desc": desc + } + + if name: + params["name"] = name + if agent_id: + params["id"] = agent_id + + return await self._request('GET', endpoint, params=params) + + async def create_agent(self, title: str, description: Optional[str] = None, + dsl: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """ + 创建代理 + """ + endpoint = "/api/v1/agents" + data = {"title": title} + + if description is not None: + data["description"] = description + if dsl is not None: + data["dsl"] = dsl + + return await self._request('POST', endpoint, json=data) + + async def update_agent(self, agent_id: str, title: Optional[str] = None, + description: Optional[str] = None, + dsl: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """ + 更新代理 + + Args: + agent_id: 代理ID + title: 新标题 + description: 新描述 + dsl: 新DSL配置 + """ + endpoint = f"/api/v1/agents/{agent_id}" + data = {} + + if title is not None: + data["title"] = title + if description is not None: + data["description"] = description + if dsl is not None: + data["dsl"] = dsl + + return await self._request('PUT', endpoint, json=data) + + async def delete_agent(self, agent_id: str) -> Dict[str, Any]: + """ + 删除代理 + + Args: + agent_id: 代理ID + """ + endpoint = f"/api/v1/agents/{agent_id}" + return await self._request('DELETE', endpoint) + + +# ==================== +# 使用示例 +# ==================== + +async def example_usage(): + """ + 异步RAGFlow SDK使用示例 + """ + # 使用异步上下文管理器 + async with AsyncRAGFlowClient( + base_url="http://10.0.0.202:82", + api_key="ragflow-hlMjRmNzE2ODNiNTExZjA4ZTNlMDI0Mm" + ) as client: + + try: + # 删除数据集 + await client.delete_datasets(ids=["afe1387883bb11f0a0fd0242ac170006"]) + print("删除数据集成功") + + # 1. 创建数据集 + dataset = await client.create_dataset( + name="我的数据集", + description="这是一个测试数据集", + chunk_method="naive" + ) + dataset_id = dataset['data']['id'] + print(f"创建数据集成功: {dataset_id}") + + # 2. 上传文档 + documents = await client.upload_documents( + dataset_id=dataset_id, + file_paths=[ + "/home/admin-root/haotian/康达瑞贝斯机器人后台/ruoyi-fastapi-backend/requirements.txt", + "/home/admin-root/haotian/康达瑞贝斯机器人后台/ruoyi-fastapi-backend/requirements-pg.txt" + ] + ) + print("文档上传成功") + + # 3. 解析文档 + document_ids = [doc['id'] for doc in documents['data']] + await client.parse_documents(dataset_id, document_ids) + print("开始解析文档") + + # 等待解析完成 + await asyncio.sleep(5) + + # 4. 创建聊天助手 + chat_assistant = await client.create_chat_assistant( + name="我的AI助手", + dataset_ids=[dataset_id] + ) + chat_id = chat_assistant['data']['id'] + print(f"创建聊天助手成功: {chat_id}") + + # 5. 创建会话 + session = await client.create_session_with_chat( + chat_id=chat_id, + name="测试会话" + ) + session_id = session['data']['id'] + print(f"创建会话成功: {session_id}") + + # 6. 开始对话(流式) + responses = client.converse_with_chat_assistant( + chat_id=chat_id, + question="你好,请介绍一下自己", + stream=True, + session_id=session_id + ) + + print("AI回复:") + async for response in responses: + if response.get('data') and isinstance(response['data'], dict): + answer = response['data'].get('answer', '') + if answer: + print(answer, end='', flush=True) + print() + + # 7. 检索相关文档块 + chunks = await client.retrieve_chunks( + question="RAGFlow的优势是什么?", + dataset_ids=[dataset_id], + top_k=5, + highlight=True + ) + print(f"检索到 {chunks['data']['total']} 个相关文档块") + + # 8. 列出数据集 + datasets = await client.list_datasets(page=1, page_size=10) + print(f"当前有 {len(datasets['data'])} 个数据集") + + except RAGFlowError as e: + print(f"RAGFlow API错误: {e}") + except Exception as e: + print(f"其他错误: {e}") + + +async def example_usage_1(): + """测试获取列表方法的异步版本""" + + # 使用异步上下文管理器 + async with AsyncRAGFlowClient( + base_url="http://10.0.0.202:82", + api_key="ragflow-hlMjRmNzE2ODNiNTExZjA4ZTNlMDI0Mm" + ) as client: + + try: + # 1. 获取数据集列表 + results_dataset = await client.list_datasets() + print(f"获取数据集列表成功,共有 {len(results_dataset['data'])} 个数据集") + print("数据集ID:\n", [result["id"] for result in results_dataset['data']]) + + # 2. 获取数据集中文档列表 + for result in results_dataset['data']: + print(f"数据集 {result['id']} 的文档列表为:") + results_doc = await client.list_documents(dataset_id=result['id']) + # 文档名称 + print([t["name"] for t in results_doc["data"]["docs"]]) + + except RAGFlowError as e: + print(f"RAGFlow API错误: {e}") + except Exception as e: + print(f"其他错误: {e}") + + +async def batch_operations_example(): + """ + 批量操作示例 - 演示异步并发处理 + """ + async with AsyncRAGFlowClient( + base_url="http://10.0.0.202:82", + api_key="ragflow-hlMjRmNzE2ODNiNTExZjA4ZTNlMDI0Mm" + ) as client: + + try: + # 并发获取数据集和代理列表 + datasets_task = client.list_datasets() + agents_task = client.list_agents() + chats_task = client.list_chat_assistants() + + # 等待所有任务完成 + datasets, agents, chats = await asyncio.gather( + datasets_task, agents_task, chats_task + ) + + print(f"数据集数量: {len(datasets['data'])}") + print(f"代理数量: {len(agents['data'])}") + print(f"聊天助手数量: {len(chats['data'])}") + + # 如果有数据集,并发获取每个数据集的文档 + if datasets['data']: + doc_tasks = [ + client.list_documents(dataset_id=dataset['id']) + for dataset in datasets['data'][:3] # 限制前3个 + ] + + doc_results = await asyncio.gather(*doc_tasks, return_exceptions=True) + + for i, result in enumerate(doc_results): + if isinstance(result, Exception): + print(f"获取数据集 {datasets['data'][i]['id']} 的文档时出错: {result}") + else: + print(f"数据集 {datasets['data'][i]['id']} 有 {len(result['data']['docs'])} 个文档") + + except Exception as e: + print(f"批量操作出错: {e}") + + +# 运行示例的辅助函数 +def run_example(): + """运行异步示例""" + # 可以选择运行不同的示例 + asyncio.run(example_usage_1()) + # asyncio.run(example_usage()) + # asyncio.run(batch_operations_example()) + + +if __name__ == "__main__": + # 运行示例 + run_example() \ No newline at end of file diff --git a/ruoyi-fastapi-backend/utils/ragflow_client_manager.py b/ruoyi-fastapi-backend/utils/ragflow_client_manager.py new file mode 100644 index 0000000..ddad3d7 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/ragflow_client_manager.py @@ -0,0 +1,54 @@ +import asyncio +from typing import Optional + +from config.env import RAGFlowConfig +from utils.ragflow_asy_util import AsyncRAGFlowClient + + +class RAGFlowClientManager: + """管理RAGFlow异步客户端的生命周期,复用底层HTTP会话""" + + _client: Optional[AsyncRAGFlowClient] = None + _lock: asyncio.Lock = asyncio.Lock() + + @classmethod + async def init_client(cls) -> AsyncRAGFlowClient: + async with cls._lock: + if cls._client is None: + cls._client = AsyncRAGFlowClient( + RAGFlowConfig.RAGFLOW_BASE_URL, + RAGFlowConfig.RAGFLOW_API_KEY, + timeout=60, + connector_kwargs={ + "limit": 128, + "ssl": False, + "keepalive_timeout": 45, + }, + ) + await cls._client.create_session() + return cls._client + + @classmethod + async def get_client(cls) -> AsyncRAGFlowClient: + if cls._client is None: + return await cls.init_client() + return cls._client + + @classmethod + async def shutdown_client(cls) -> None: + async with cls._lock: + if cls._client is not None: + await cls._client.close_session() + cls._client = None + + +async def init_ragflow_client() -> AsyncRAGFlowClient: + return await RAGFlowClientManager.init_client() + + +async def get_ragflow_client() -> AsyncRAGFlowClient: + return await RAGFlowClientManager.get_client() + + +async def shutdown_ragflow_client() -> None: + await RAGFlowClientManager.shutdown_client() diff --git a/ruoyi-fastapi-backend/utils/ragflow_util.py b/ruoyi-fastapi-backend/utils/ragflow_util.py new file mode 100644 index 0000000..8908c4f --- /dev/null +++ b/ruoyi-fastapi-backend/utils/ragflow_util.py @@ -0,0 +1,1152 @@ +import requests +import json +from typing import Optional, List, Dict, Any, Union, Generator +import os + + +class RAGFlowError(Exception): + """RAGFlow API错误异常""" + def __init__(self, code: int, message: str): + self.code = code + self.message = message + super().__init__(f"Error {code}: {message}") + + +class RAGFlowClient: + """RAGFlow API客户端""" + + def __init__(self, base_url: str, api_key: str): + """ + 初始化RAGFlow客户端 + + Args: + base_url: RAGFlow服务器地址 + api_key: API密钥 + """ + self.base_url = base_url.rstrip('/') + self.api_key = api_key + self.headers = { + 'Authorization': f'Bearer {api_key}', + 'Content-Type': 'application/json' + } + + def _request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]: + """发送HTTP请求""" + url = f"{self.base_url}{endpoint}" + + # 处理headers + headers = kwargs.pop('headers', self.headers.copy()) + + response = requests.request(method, url, headers=headers, **kwargs) + + try: + result = response.json() + except json.JSONDecodeError: + if response.status_code == 200: + return {'code': 0, 'data': response.content} + else: + raise RAGFlowError(response.status_code, response.text) + + if result.get('code', 0) != 0: + raise RAGFlowError(result.get('code'), result.get('message', 'Unknown error')) + + return result + + def _stream_request(self, method: str, endpoint: str, **kwargs) -> Generator[Dict[str, Any], None, None]: + """发送流式HTTP请求""" + url = f"{self.base_url}{endpoint}" + headers = kwargs.pop('headers', self.headers.copy()) + + response = requests.request(method, url, headers=headers, stream=True, **kwargs) + + for line in response.iter_lines(): + if line: + line = line.decode('utf-8') + if line.startswith('data:'): + try: + data = json.loads(line[5:].strip()) + yield data + except json.JSONDecodeError: + continue + + # ==================== + # OpenAI兼容API + # ==================== + + def create_chat_completion(self, chat_id: str, model: str, messages: List[Dict[str, str]], + stream: bool = False) -> Union[Dict[str, Any], Generator[Dict[str, Any], None, None]]: + """ + 创建聊天完成 + + Args: + chat_id: 聊天ID + model: 模型名称 + messages: 消息列表 + stream: 是否流式返回 + """ + endpoint = f"/api/v1/chats_openai/{chat_id}/chat/completions" + data = { + "model": model, + "messages": messages, + "stream": stream + } + + if stream: + return self._stream_request('POST', endpoint, json=data) + else: + return self._request('POST', endpoint, json=data) + + def create_agent_completion(self, agent_id: str, model: str, messages: List[Dict[str, str]], + stream: bool = False) -> Union[Dict[str, Any], Generator[Dict[str, Any], None, None]]: + """ + 创建代理完成 + + Args: + agent_id: 代理ID + model: 模型名称 + messages: 消息列表 + stream: 是否流式返回 + """ + endpoint = f"/api/v1/agents_openai/{agent_id}/chat/completions" + data = { + "model": model, + "messages": messages, + "stream": stream + } + + if stream: + return self._stream_request('POST', endpoint, json=data) + else: + return self._request('POST', endpoint, json=data) + + # ==================== + # 数据集管理 + # ==================== + + def create_dataset(self, name: str, avatar: Optional[str] = None, description: Optional[str] = None, + embedding_model: Optional[str] = None, permission: str = "me", + chunk_method: str = "naive", + parser_config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """ + 创建数据集 + + Args: + name: 数据集名称 + avatar: Base64编码的头像 + description: 描述 + embedding_model: 嵌入模型 + permission: 权限设置 ("me" 或 "team") + chunk_method: 分块方法 + "naive": General (default) + parser_config + "auto_keywords": int + Defaults to 0 + Minimum: 0 + Maximum: 32 + "auto_questions": int + Defaults to 0 + Minimum: 0 + Maximum: 10 + "chunk_token_num": int + Defaults to 512 + Minimum: 1 + Maximum: 2048 + "delimiter": string + Defaults to "\n". + "html4excel": bool Indicates whether to convert Excel documents into HTML format. + Defaults to false + "layout_recognize": string + Defaults to DeepDOC + "tag_kb_ids": array refer to Use tag set + Must include a list of dataset IDs, where each dataset is parsed using the ​​Tag Chunking Method + "task_page_size": int For PDF only. + Defaults to 12 + Minimum: 1 + "raptor": object RAPTOR-specific settings. + Defaults to: {"use_raptor": false} + "graphrag": object GRAPHRAG-specific settings. + Defaults to: {"use_graphrag": false} + "book": Book + "email": Email + "laws": Laws + "manual": Manual + "one": One + "paper": Paper + "picture": Picture + "presentation": Presentation + "qa": Q&A + "table": Table + "tag": Tag + pagerank: 页面排名 + parser_config: 解析器配置 + """ + endpoint = "/api/v1/datasets" + data = { + "name": name, + "permission": permission, + "chunk_method": chunk_method, + # "pagerank": pagerank + } + + if avatar: + data["avatar"] = avatar + if description: + data["description"] = description + if embedding_model: + data["embedding_model"] = embedding_model + if parser_config: + data["parser_config"] = parser_config + + return self._request('POST', endpoint, json=data) + + def delete_datasets(self, ids: Optional[List[str]] = None) -> Dict[str, Any]: + """ + 删除数据集 + + Args: + ids: 要删除的数据集ID列表,None表示删除所有 + """ + endpoint = "/api/v1/datasets" + data = {"ids": ids} + return self._request('DELETE', endpoint, json=data) + + def update_dataset(self, dataset_id: str, name: Optional[str] = None, + avatar: Optional[str] = None, description: Optional[str] = None, + embedding_model: Optional[str] = None, permission: Optional[str] = None, + chunk_method: Optional[str] = None, pagerank: Optional[int] = None, + parser_config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """ + 更新数据集 + + Args: + dataset_id: 数据集ID + name: 新名称 + avatar: 新头像 + description: 新描述 + embedding_model: 新嵌入模型 + permission: 新权限设置 + chunk_method: 新分块方法 + pagerank: 新页面排名 + parser_config: 新解析器配置 + """ + endpoint = f"/api/v1/datasets/{dataset_id}" + data = {} + + if name is not None: + data["name"] = name + if avatar is not None: + data["avatar"] = avatar + if description is not None: + data["description"] = description + if embedding_model is not None: + data["embedding_model"] = embedding_model + if permission is not None: + data["permission"] = permission + if chunk_method is not None: + data["chunk_method"] = chunk_method + if pagerank is not None: + data["pagerank"] = pagerank + if parser_config is not None: + data["parser_config"] = parser_config + + return self._request('PUT', endpoint, json=data) + + def list_datasets(self, page: int = 1, page_size: int = 30, orderby: str = "create_time", + desc: bool = True, name: Optional[str] = None, + dataset_id: Optional[str] = None) -> Dict[str, Any]: + """ + 列出数据集 + + Args: + page: 页码 + page_size: 每页数量 + orderby: 排序字段 + desc: 是否降序 + name: 筛选名称 + dataset_id: 筛选ID + """ + endpoint = "/api/v1/datasets" + params = { + "page": page, + "page_size": page_size, + "orderby": orderby, + "desc": desc + } + + if name: + params["name"] = name + if dataset_id: + params["id"] = dataset_id + + return self._request('GET', endpoint, params=params) + + # ==================== + # 文档管理 + # ==================== + + def upload_documents(self, dataset_id: str, file_paths: List[str]) -> Dict[str, Any]: + """ + 上传文档到数据集 + + Args: + dataset_id: 数据集ID + file_paths: 文件路径列表 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/documents" + + files = [] + for file_path in file_paths: + if os.path.exists(file_path): + files.append(('file', open(file_path, 'rb'))) + + headers = { + 'Authorization': f'Bearer {self.api_key}', + } + + try: + response = requests.post(f"{self.base_url}{endpoint}", + headers=headers, files=files) + result = response.json() + + if result.get('code', 0) != 0: + raise RAGFlowError(result.get('code'), result.get('message')) + + return result + finally: + for _, file_obj in files: + file_obj.close() + + def update_document(self, dataset_id: str, document_id: str, name: Optional[str] = None, + meta_fields: Optional[Dict[str, Any]] = None, + chunk_method: Optional[str] = None, + parser_config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """ + 更新文档配置 + + Args: + dataset_id: 数据集ID + document_id: 文档ID + name: 新名称 + meta_fields: 元字段 + chunk_method: 分块方法 + parser_config: 解析器配置 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/documents/{document_id}" + data = {} + + if name is not None: + data["name"] = name + if meta_fields is not None: + data["meta_fields"] = meta_fields + if chunk_method is not None: + data["chunk_method"] = chunk_method + if parser_config is not None: + data["parser_config"] = parser_config + + return self._request('PUT', endpoint, json=data) + + def download_document(self, dataset_id: str, document_id: str, save_path: str) -> None: + """ + 下载文档 + + Args: + dataset_id: 数据集ID + document_id: 文档ID + save_path: 保存路径 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/documents/{document_id}" + headers = {'Authorization': f'Bearer {self.api_key}'} + + response = requests.get(f"{self.base_url}{endpoint}", headers=headers) + + if response.status_code == 200: + with open(save_path, 'wb') as f: + f.write(response.content) + else: + try: + error = response.json() + raise RAGFlowError(error.get('code'), error.get('message')) + except json.JSONDecodeError: + raise RAGFlowError(response.status_code, response.text) + + def list_documents(self, dataset_id: str, page: int = 1, page_size: int = 30, + orderby: str = "create_time", desc: bool = True, + keywords: Optional[str] = None, document_id: Optional[str] = None, + document_name: Optional[str] = None) -> Dict[str, Any]: + """ + 列出数据集中的文档 + + Args: + dataset_id: 数据集ID + page: 页码 + page_size: 每页数量 + orderby: 排序字段 + desc: 是否降序 + keywords: 关键词搜索 + document_id: 筛选文档ID + document_name: 筛选文档名称 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/documents" + params = { + "page": page, + "page_size": page_size, + "orderby": orderby, + "desc": desc + } + + if keywords: + params["keywords"] = keywords + if document_id: + params["id"] = document_id + if document_name: + params["name"] = document_name + + return self._request('GET', endpoint, params=params) + + def delete_documents(self, dataset_id: str, ids: Optional[List[str]] = None) -> Dict[str, Any]: + """ + 删除文档 + + Args: + dataset_id: 数据集ID + ids: 要删除的文档ID列表 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/documents" + data = {"ids": ids} if ids else {} + return self._request('DELETE', endpoint, json=data) + + def parse_documents(self, dataset_id: str, document_ids: List[str]) -> Dict[str, Any]: + """ + 解析文档 + + Args: + dataset_id: 数据集ID + document_ids: 要解析的文档ID列表 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/chunks" + data = {"document_ids": document_ids} + return self._request('POST', endpoint, json=data) + + def stop_parsing_documents(self, dataset_id: str, document_ids: List[str]) -> Dict[str, Any]: + """ + 停止解析文档 + + Args: + dataset_id: 数据集ID + document_ids: 要停止解析的文档ID列表 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/chunks" + data = {"document_ids": document_ids} + return self._request('DELETE', endpoint, json=data) + + # ==================== + # 分块管理 + # ==================== + + def add_chunk(self, dataset_id: str, document_id: str, content: str, + important_keywords: Optional[List[str]] = None, + questions: Optional[List[str]] = None) -> Dict[str, Any]: + """ + 添加分块 + + Args: + dataset_id: 数据集ID + document_id: 文档ID + content: 分块内容 + important_keywords: 重要关键词 + questions: 问题列表 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/documents/{document_id}/chunks" + data = {"content": content} + + if important_keywords: + data["important_keywords"] = important_keywords + if questions: + data["questions"] = questions + + return self._request('POST', endpoint, json=data) + + def list_chunks(self, dataset_id: str, document_id: str, keywords: Optional[str] = None, + page: int = 1, page_size: int = 1024, + chunk_id: Optional[str] = None) -> Dict[str, Any]: + """ + 列出分块 + + Args: + dataset_id: 数据集ID + document_id: 文档ID + keywords: 关键词搜索 + page: 页码 + page_size: 每页数量 + chunk_id: 分块ID筛选 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/documents/{document_id}/chunks" + params = {"page": page, "page_size": page_size} + + if keywords: + params["keywords"] = keywords + if chunk_id: + params["id"] = chunk_id + + return self._request('GET', endpoint, params=params) + + def delete_chunks(self, dataset_id: str, document_id: str, + chunk_ids: Optional[List[str]] = None) -> Dict[str, Any]: + """ + 删除分块 + + Args: + dataset_id: 数据集ID + document_id: 文档ID + chunk_ids: 要删除的分块ID列表 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/documents/{document_id}/chunks" + data = {"chunk_ids": chunk_ids} if chunk_ids else {} + return self._request('DELETE', endpoint, json=data) + + def update_chunk(self, dataset_id: str, document_id: str, chunk_id: str, + content: Optional[str] = None, + important_keywords: Optional[List[str]] = None, + available: Optional[bool] = None) -> Dict[str, Any]: + """ + 更新分块 + + Args: + dataset_id: 数据集ID + document_id: 文档ID + chunk_id: 分块ID + content: 新内容 + important_keywords: 重要关键词 + available: 可用状态 + """ + endpoint = f"/api/v1/datasets/{dataset_id}/documents/{document_id}/chunks/{chunk_id}" + data = {} + + if content is not None: + data["content"] = content + if important_keywords is not None: + data["important_keywords"] = important_keywords + if available is not None: + data["available"] = available + + return self._request('PUT', endpoint, json=data) + + def retrieve_chunks(self, question: str, dataset_ids: Optional[List[str]] = None, + document_ids: Optional[List[str]] = None, page: int = 1, + page_size: int = 30, similarity_threshold: float = 0.2, + vector_similarity_weight: float = 0.3, top_k: int = 1024, + rerank_id: Optional[str] = None, keyword: bool = False, + highlight: bool = False) -> Dict[str, Any]: + """ + 检索分块 + + Args: + question: 查询问题 + dataset_ids: 数据集ID列表 + document_ids: 文档ID列表 + page: 页码 + page_size: 每页数量 + similarity_threshold: 相似度阈值 + vector_similarity_weight: 向量相似度权重 + top_k: TopK数量 + rerank_id: 重排序模型ID + keyword: 是否启用关键词匹配 + highlight: 是否高亮显示 + """ + endpoint = "/api/v1/retrieval" + data = { + "question": question, + "page": page, + "page_size": page_size, + "similarity_threshold": similarity_threshold, + "vector_similarity_weight": vector_similarity_weight, + "top_k": top_k, + "keyword": keyword, + "highlight": highlight + } + + if dataset_ids: + data["dataset_ids"] = dataset_ids + if document_ids: + data["document_ids"] = document_ids + if rerank_id: + data["rerank_id"] = rerank_id + + return self._request('POST', endpoint, json=data) + + # ==================== + # 聊天助手管理 + # ==================== + + def create_chat_assistant(self, name: str, avatar: Optional[str] = None, + dataset_ids: Optional[List[str]] = None, + llm: Optional[Dict[str, Any]] = None, + prompt: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """ + 创建聊天助手 + + Args: + name: 助手名称 + avatar: Base64编码头像 + dataset_ids: 关联的数据集ID列表 + llm: LLM配置 + prompt: 提示配置 + """ + endpoint = "/api/v1/chats" + data = {"name": name} + + if avatar: + data["avatar"] = avatar + if dataset_ids: + data["dataset_ids"] = dataset_ids + if llm: + data["llm"] = llm + if prompt: + data["prompt"] = prompt + + return self._request('POST', endpoint, json=data) + + def update_chat_assistant(self, chat_id: str, name: Optional[str] = None, + avatar: Optional[str] = None, + dataset_ids: Optional[List[str]] = None, + llm: Optional[Dict[str, Any]] = None, + prompt: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """ + 更新聊天助手 + + Args: + chat_id: 聊天助手ID + name: 新名称 + avatar: 新头像 + dataset_ids: 新数据集ID列表 + llm: 新LLM配置 + prompt: 新提示配置 + """ + endpoint = f"/api/v1/chats/{chat_id}" + data = {} + + if name is not None: + data["name"] = name + if avatar is not None: + data["avatar"] = avatar + if dataset_ids is not None: + data["dataset_ids"] = dataset_ids + if llm is not None: + data["llm"] = llm + if prompt is not None: + data["prompt"] = prompt + + return self._request('PUT', endpoint, json=data) + + def delete_chat_assistants(self, ids: Optional[List[str]] = None) -> Dict[str, Any]: + """ + 删除聊天助手 + + Args: + ids: 要删除的聊天助手ID列表 + """ + endpoint = "/api/v1/chats" + data = {"ids": ids} if ids else {} + return self._request('DELETE', endpoint, json=data) + + def list_chat_assistants(self, page: int = 1, page_size: int = 30, + orderby: str = "create_time", desc: bool = True, + name: Optional[str] = None, + chat_id: Optional[str] = None) -> Dict[str, Any]: + """ + 列出聊天助手 + + Args: + page: 页码 + page_size: 每页数量 + orderby: 排序字段 + desc: 是否降序 + name: 筛选名称 + chat_id: 筛选ID + """ + endpoint = "/api/v1/chats" + params = { + "page": page, + "page_size": page_size, + "orderby": orderby, + "desc": desc + } + + if name: + params["name"] = name + if chat_id: + params["id"] = chat_id + + return self._request('GET', endpoint, params=params) + + # ==================== + # 会话管理 + # ==================== + + def create_session_with_chat(self, chat_id: str, name: str, + user_id: Optional[str] = None) -> Dict[str, Any]: + """ + 创建与聊天助手的会话 + + Args: + chat_id: 聊天助手ID + name: 会话名称 + user_id: 可选的用户定义ID + """ + endpoint = f"/api/v1/chats/{chat_id}/sessions" + data = {"name": name} + + if user_id: + data["user_id"] = user_id + + return self._request('POST', endpoint, json=data) + + def update_chat_session(self, chat_id: str, session_id: str, name: Optional[str] = None, + user_id: Optional[str] = None) -> Dict[str, Any]: + """ + 更新聊天会话 + + Args: + chat_id: 聊天助手ID + session_id: 会话ID + name: 新名称 + user_id: 新用户ID + """ + endpoint = f"/api/v1/chats/{chat_id}/sessions/{session_id}" + data = {} + + if name is not None: + data["name"] = name + if user_id is not None: + data["user_id"] = user_id + + return self._request('PUT', endpoint, json=data) + + def list_chat_sessions(self, chat_id: str, page: int = 1, page_size: int = 30, + orderby: str = "create_time", desc: bool = True, + name: Optional[str] = None, session_id: Optional[str] = None, + user_id: Optional[str] = None) -> Dict[str, Any]: + """ + 列出与指定聊天助手相关的聊天会话 + + Args: + chat_id: 聊天助手ID + page: 页码 + page_size: 每页数量 + orderby: 排序字段 + desc: 是否降序 + name: 筛选名称 + session_id: 筛选会话ID + user_id: 筛选用户ID + """ + endpoint = f"/api/v1/chats/{chat_id}/sessions" + params = { + "page": page, + "page_size": page_size, + "orderby": orderby, + "desc": desc + } + + if name: + params["name"] = name + if session_id: + params["id"] = session_id + if user_id: + params["user_id"] = user_id + + return self._request('GET', endpoint, params=params) + + def delete_chat_sessions(self, chat_id: str, ids: Optional[List[str]] = None) -> Dict[str, Any]: + """ + 删除聊天会话 + + Args: + chat_id: 聊天助手ID + ids: 要删除的会话ID列表 + """ + endpoint = f"/api/v1/chats/{chat_id}/sessions" + data = {"ids": ids} if ids else {} + return self._request('DELETE', endpoint, json=data) + + def converse_with_chat_assistant(self, chat_id: str, question: str, stream: bool = True, + session_id: Optional[str] = None, + user_id: Optional[str] = None) -> Union[Dict[str, Any], Generator[Dict[str, Any], None, None]]: + """ + 与聊天助手对话 + + Args: + chat_id: 聊天助手ID + question: 问题 + stream: 是否流式返回 + session_id: 会话ID + user_id: 用户ID + """ + endpoint = f"/api/v1/chats/{chat_id}/completions" + data = {"question": question, "stream": stream} + + if session_id: + data["session_id"] = session_id + if user_id: + data["user_id"] = user_id + + if stream: + return self._stream_request('POST', endpoint, json=data) + else: + return self._request('POST', endpoint, json=data) + + # ==================== + # 代理管理 + # ==================== + + def create_session_with_agent(self, agent_id: str, user_id: Optional[str] = None, + file_data: Optional[Dict[str, Any]] = None, + **kwargs) -> Dict[str, Any]: + """ + 创建与代理的会话 + + Args: + agent_id: 代理ID + user_id: 用户ID + file_data: 文件数据(当Begin组件需要文件参数时) + **kwargs: 其他Begin组件需要的参数 + """ + endpoint = f"/api/v1/agents/{agent_id}/sessions" + params = {} + + if user_id: + params["user_id"] = user_id + + if file_data: + # 处理文件上传 + headers = {'Authorization': f'Bearer {self.api_key}'} + files = {} + + for key, file_path in file_data.items(): + if os.path.exists(file_path): + files[key] = open(file_path, 'rb') + + try: + response = requests.post(f"{self.base_url}{endpoint}", + headers=headers, files=files, params=params) + result = response.json() + + if result.get('code', 0) != 0: + raise RAGFlowError(result.get('code'), result.get('message')) + + return result + finally: + for file_obj in files.values(): + file_obj.close() + else: + # 普通JSON请求 + data = kwargs + return self._request('POST', endpoint, json=data, params=params) + + def converse_with_agent(self, agent_id: str, question: str, stream: bool = True, + session_id: Optional[str] = None, user_id: Optional[str] = None, + sync_dsl: bool = False, **kwargs) -> Union[Dict[str, Any], Generator[Dict[str, Any], None, None]]: + """ + 与代理对话 + + Args: + agent_id: 代理ID + question: 问题 + stream: 是否流式返回 + session_id: 会话ID + user_id: 用户ID + sync_dsl: 是否同步DSL变更到现有会话 + **kwargs: Begin组件需要的其他参数 + """ + endpoint = f"/api/v1/agents/{agent_id}/completions" + data = {"question": question, "stream": stream, "sync_dsl": sync_dsl} + + if session_id: + data["session_id"] = session_id + if user_id: + data["user_id"] = user_id + + # 添加其他Begin组件参数 + data.update(kwargs) + + if stream: + return self._stream_request('POST', endpoint, json=data) + else: + return self._request('POST', endpoint, json=data) + + def list_agent_sessions(self, agent_id: str, page: int = 1, page_size: int = 30, + orderby: str = "create_time", desc: bool = True, + session_id: Optional[str] = None, user_id: Optional[str] = None, + dsl: bool = True) -> Dict[str, Any]: + """ + 列出代理会话 + + Args: + agent_id: 代理ID + page: 页码 + page_size: 每页数量 + orderby: 排序字段 + desc: 是否降序 + session_id: 筛选会话ID + user_id: 筛选用户ID + dsl: 是否在响应中包含dsl字段 + """ + endpoint = f"/api/v1/agents/{agent_id}/sessions" + params = { + "page": page, + "page_size": page_size, + "orderby": orderby, + "desc": desc, + "dsl": dsl + } + + if session_id: + params["id"] = session_id + if user_id: + params["user_id"] = user_id + + return self._request('GET', endpoint, params=params) + + def delete_agent_sessions(self, agent_id: str, ids: Optional[List[str]] = None) -> Dict[str, Any]: + """ + 删除代理会话 + + Args: + agent_id: 代理ID + ids: 要删除的会话ID列表 + """ + endpoint = f"/api/v1/agents/{agent_id}/sessions" + data = {"ids": ids} if ids else {} + return self._request('DELETE', endpoint, json=data) + + def get_related_questions(self, question: str, login_token: str) -> Dict[str, Any]: + """ + 生成相关问题 + 注意:此API需要登录令牌而不是API密钥 + + Args: + question: 原始问题 + login_token: 登录令牌 + """ + endpoint = "/v1/sessions/related_questions" + headers = { + 'Authorization': f'Bearer {login_token}', + 'Content-Type': 'application/json' + } + data = {"question": question} + + return self._request('POST', endpoint, headers=headers, json=data) + + # ==================== + # 代理管理 + # ==================== + + def list_agents(self, page: int = 1, page_size: int = 30, orderby: str = "create_time", + desc: bool = True, name: Optional[str] = None, + agent_id: Optional[str] = None) -> Dict[str, Any]: + """ + 列出代理 + + Args: + page: 页码 + page_size: 每页数量 + orderby: 排序字段 + desc: 是否降序 + name: 筛选名称 + agent_id: 筛选ID + """ + endpoint = "/api/v1/agents" + params = { + "page": page, + "page_size": page_size, + "orderby": orderby, + "desc": desc + } + + if name: + params["name"] = name + if agent_id: + params["id"] = agent_id + + return self._request('GET', endpoint, params=params) + + def create_agent(self, title: str, description: Optional[str] = None, + dsl: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """ + 创建代理 + + Args: + title: 代理标题 + description: 代理描述 + dsl: Canvas DSL对象 + """ + endpoint = "/api/v1/agents" + data = {"title": title} + + if description is not None: + data["description"] = description + if dsl is not None: + data["dsl"] = dsl + + return self._request('POST', endpoint, json=data) + + def update_agent(self, agent_id: str, title: Optional[str] = None, + description: Optional[str] = None, + dsl: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """ + 更新代理 + + Args: + agent_id: 代理ID + title: 新标题 + description: 新描述 + dsl: 新DSL配置 + """ + endpoint = f"/api/v1/agents/{agent_id}" + data = {} + + if title is not None: + data["title"] = title + if description is not None: + data["description"] = description + if dsl is not None: + data["dsl"] = dsl + + return self._request('PUT', endpoint, json=data) + + def delete_agent(self, agent_id: str) -> Dict[str, Any]: + """ + 删除代理 + + Args: + agent_id: 代理ID + """ + endpoint = f"/api/v1/agents/{agent_id}" + return self._request('DELETE', endpoint) + + +# ==================== +# 使用示例 +# ==================== + +def example_usage(): + """ + RAGFlow SDK使用示例 + """ + # 初始化客户端 + client = RAGFlowClient( + base_url="http://10.0.0.202:82", + api_key="ragflow-hlMjRmNzE2ODNiNTExZjA4ZTNlMDI0Mm" + ) + + try: + # 删除数据集 + client.delete_datasets(ids=["afe1387883bb11f0a0fd0242ac170006"]) + print("删除数据集成功") + + # 1. 创建数据集, 都是默认配置 + dataset = client.create_dataset( + name="我的数据集", + description="这是一个测试数据集", + chunk_method="naive" + ) + dataset_id = dataset['data']['id'] + print(f"创建数据集成功: {dataset_id}") + + # 2. 上传文档 + documents = client.upload_documents( + dataset_id=dataset_id, + file_paths=[ + "/home/admin-root/haotian/康达瑞贝斯机器人后台/ruoyi-fastapi-backend/requirements.txt", + "/home/admin-root/haotian/康达瑞贝斯机器人后台/ruoyi-fastapi-backend/requirements-pg.txt" + ] + ) + print("文档上传成功") + + # 3. 解析文档 + document_ids = [doc['id'] for doc in documents['data']] + client.parse_documents(dataset_id, document_ids) + print("开始解析文档") + + import time + time.sleep(5) + + # 4. 创建聊天助手 + chat_assistant = client.create_chat_assistant( + name="我的AI助手", + dataset_ids=[dataset_id] + ) + chat_id = chat_assistant['data']['id'] + print(f"创建聊天助手成功: {chat_id}") + + # 5. 创建会话 + session = client.create_session_with_chat( + chat_id=chat_id, + name="测试会话" + ) + session_id = session['data']['id'] + print(f"创建会话成功: {session_id}") + + # 6. 开始对话(流式) + responses = client.converse_with_chat_assistant( + chat_id=chat_id, + question="你好,请介绍一下自己", + stream=True, + session_id=session_id + ) + + print("AI回复:") + for response in responses: + if response.get('data') and isinstance(response['data'], dict): + answer = response['data'].get('answer', '') + if answer: + print(answer, end='', flush=True) + print() + + # 7. 检索相关文档块 + chunks = client.retrieve_chunks( + question="RAGFlow的优势是什么?", + dataset_ids=[dataset_id], + top_k=5, + highlight=True + ) + print(f"检索到 {chunks['data']['total']} 个相关文档块") + + # 8. 列出数据集 + datasets = client.list_datasets(page=1, page_size=10) + print(f"当前有 {len(datasets['data'])} 个数据集") + + except RAGFlowError as e: + print(f"RAGFlow API错误: {e}") + except Exception as e: + print(f"其他错误: {e}") + + +def example_usage_1(): + """ 测试获取列表方法 + """ + + # 初始化客户端 + client = RAGFlowClient( + base_url="http://10.0.0.202:82", + api_key="ragflow-hlMjRmNzE2ODNiNTExZjA4ZTNlMDI0Mm" + ) + + # 1. 获取数据集列表 + results_dataset = client.list_datasets() + print(f"获取数据集列表成功,共有 {len(results_dataset['data'])} 个数据集") + print("数据集为id:\n", [result["id"] for result in results_dataset['data']]) + # 2. 获取数据集中文档列表 + for result in results_dataset['data']: + print(f"数据集 {result['id']} 的文档列表为:") + results_doc = client.list_documents(dataset_id=result['id']) + # 文档名称 + print([t["name"] for t in results_doc["data"]["docs"]]) + + # results = client.list_documents(dataset_id="d01") + + +if __name__ == "__main__": + # 运行示例 + # example_usage() + + example_usage_1() \ No newline at end of file diff --git a/ruoyi-fastapi-backend/utils/response_util.py b/ruoyi-fastapi-backend/utils/response_util.py new file mode 100644 index 0000000..4ed77ec --- /dev/null +++ b/ruoyi-fastapi-backend/utils/response_util.py @@ -0,0 +1,269 @@ +from datetime import datetime +from fastapi import status +from fastapi.encoders import jsonable_encoder +from fastapi.responses import JSONResponse, Response, StreamingResponse +from pydantic import BaseModel +from starlette.background import BackgroundTask +from typing import Any, Dict, Mapping, Optional +from config.constant import HttpStatusConstant + + +class ResponseUtil: + """ + 响应工具类 + """ + + @classmethod + def success( + cls, + msg: str = '操作成功', + data: Optional[Any] = None, + rows: Optional[Any] = None, + dict_content: Optional[Dict] = None, + model_content: Optional[BaseModel] = None, + headers: Optional[Mapping[str, str]] = None, + media_type: Optional[str] = None, + background: Optional[BackgroundTask] = None, + ) -> Response: + """ + 成功响应方法 + + :param msg: 可选,自定义成功响应信息 + :param data: 可选,成功响应结果中属性为data的值 + :param rows: 可选,成功响应结果中属性为rows的值 + :param dict_content: 可选,dict类型,成功响应结果中自定义属性的值 + :param model_content: 可选,BaseModel类型,成功响应结果中自定义属性的值 + :param headers: 可选,响应头信息 + :param media_type: 可选,响应结果媒体类型 + :param background: 可选,响应返回后执行的后台任务 + :return: 成功响应结果 + """ + result = {'code': HttpStatusConstant.SUCCESS, 'msg': msg} + + if data is not None: + result['data'] = data + if rows is not None: + result['rows'] = rows + if dict_content is not None: + result.update(dict_content) + if model_content is not None: + result.update(model_content.model_dump(by_alias=True)) + + result.update({'success': True, 'time': datetime.now()}) + + return JSONResponse( + status_code=status.HTTP_200_OK, + content=jsonable_encoder(result), + headers=headers, + media_type=media_type, + background=background, + ) + + @classmethod + def failure( + cls, + msg: str = '操作失败', + data: Optional[Any] = None, + rows: Optional[Any] = None, + dict_content: Optional[Dict] = None, + model_content: Optional[BaseModel] = None, + headers: Optional[Mapping[str, str]] = None, + media_type: Optional[str] = None, + background: Optional[BackgroundTask] = None, + ) -> Response: + """ + 失败响应方法 + + :param msg: 可选,自定义失败响应信息 + :param data: 可选,失败响应结果中属性为data的值 + :param rows: 可选,失败响应结果中属性为rows的值 + :param dict_content: 可选,dict类型,失败响应结果中自定义属性的值 + :param model_content: 可选,BaseModel类型,失败响应结果中自定义属性的值 + :param headers: 可选,响应头信息 + :param media_type: 可选,响应结果媒体类型 + :param background: 可选,响应返回后执行的后台任务 + :return: 失败响应结果 + """ + result = {'code': HttpStatusConstant.WARN, 'msg': msg} + + if data is not None: + result['data'] = data + if rows is not None: + result['rows'] = rows + if dict_content is not None: + result.update(dict_content) + if model_content is not None: + result.update(model_content.model_dump(by_alias=True)) + + result.update({'success': False, 'time': datetime.now()}) + + return JSONResponse( + status_code=status.HTTP_200_OK, + content=jsonable_encoder(result), + headers=headers, + media_type=media_type, + background=background, + ) + + @classmethod + def unauthorized( + cls, + msg: str = '登录信息已过期,访问系统资源失败', + data: Optional[Any] = None, + rows: Optional[Any] = None, + dict_content: Optional[Dict] = None, + model_content: Optional[BaseModel] = None, + headers: Optional[Mapping[str, str]] = None, + media_type: Optional[str] = None, + background: Optional[BackgroundTask] = None, + ) -> Response: + """ + 未认证响应方法 + + :param msg: 可选,自定义未认证响应信息 + :param data: 可选,未认证响应结果中属性为data的值 + :param rows: 可选,未认证响应结果中属性为rows的值 + :param dict_content: 可选,dict类型,未认证响应结果中自定义属性的值 + :param model_content: 可选,BaseModel类型,未认证响应结果中自定义属性的值 + :param headers: 可选,响应头信息 + :param media_type: 可选,响应结果媒体类型 + :param background: 可选,响应返回后执行的后台任务 + :return: 未认证响应结果 + """ + result = {'code': HttpStatusConstant.UNAUTHORIZED, 'msg': msg} + + if data is not None: + result['data'] = data + if rows is not None: + result['rows'] = rows + if dict_content is not None: + result.update(dict_content) + if model_content is not None: + result.update(model_content.model_dump(by_alias=True)) + + result.update({'success': False, 'time': datetime.now()}) + + return JSONResponse( + status_code=status.HTTP_200_OK, + content=jsonable_encoder(result), + headers=headers, + media_type=media_type, + background=background, + ) + + @classmethod + def forbidden( + cls, + msg: str = '该用户无此接口权限', + data: Optional[Any] = None, + rows: Optional[Any] = None, + dict_content: Optional[Dict] = None, + model_content: Optional[BaseModel] = None, + headers: Optional[Mapping[str, str]] = None, + media_type: Optional[str] = None, + background: Optional[BackgroundTask] = None, + ) -> Response: + """ + 未授权响应方法 + + :param msg: 可选,自定义未授权响应信息 + :param data: 可选,未授权响应结果中属性为data的值 + :param rows: 可选,未授权响应结果中属性为rows的值 + :param dict_content: 可选,dict类型,未授权响应结果中自定义属性的值 + :param model_content: 可选,BaseModel类型,未授权响应结果中自定义属性的值 + :param headers: 可选,响应头信息 + :param media_type: 可选,响应结果媒体类型 + :param background: 可选,响应返回后执行的后台任务 + :return: 未授权响应结果 + """ + result = {'code': HttpStatusConstant.FORBIDDEN, 'msg': msg} + + if data is not None: + result['data'] = data + if rows is not None: + result['rows'] = rows + if dict_content is not None: + result.update(dict_content) + if model_content is not None: + result.update(model_content.model_dump(by_alias=True)) + + result.update({'success': False, 'time': datetime.now()}) + + return JSONResponse( + status_code=status.HTTP_200_OK, + content=jsonable_encoder(result), + headers=headers, + media_type=media_type, + background=background, + ) + + @classmethod + def error( + cls, + msg: str = '接口异常', + data: Optional[Any] = None, + rows: Optional[Any] = None, + dict_content: Optional[Dict] = None, + model_content: Optional[BaseModel] = None, + headers: Optional[Mapping[str, str]] = None, + media_type: Optional[str] = None, + background: Optional[BackgroundTask] = None, + ) -> Response: + """ + 错误响应方法 + + :param msg: 可选,自定义错误响应信息 + :param data: 可选,错误响应结果中属性为data的值 + :param rows: 可选,错误响应结果中属性为rows的值 + :param dict_content: 可选,dict类型,错误响应结果中自定义属性的值 + :param model_content: 可选,BaseModel类型,错误响应结果中自定义属性的值 + :param headers: 可选,响应头信息 + :param media_type: 可选,响应结果媒体类型 + :param background: 可选,响应返回后执行的后台任务 + :return: 错误响应结果 + """ + result = {'code': HttpStatusConstant.ERROR, 'msg': msg} + + if data is not None: + result['data'] = data + if rows is not None: + result['rows'] = rows + if dict_content is not None: + result.update(dict_content) + if model_content is not None: + result.update(model_content.model_dump(by_alias=True)) + + result.update({'success': False, 'time': datetime.now()}) + + return JSONResponse( + status_code=status.HTTP_200_OK, + content=jsonable_encoder(result), + headers=headers, + media_type=media_type, + background=background, + ) + + @classmethod + def streaming( + cls, + *, + data: Any = None, + headers: Optional[Mapping[str, str]] = None, + media_type: Optional[str] = None, + background: Optional[BackgroundTask] = None, + ) -> Response: + """ + 流式响应方法 + + :param data: 流式传输的内容 + :param headers: 可选,响应头信息 + :param media_type: 可选,响应结果媒体类型 + :param background: 可选,响应返回后执行的后台任务 + :return: 流式响应结果 + """ + return StreamingResponse( + status_code=status.HTTP_200_OK, content=data, headers=headers, media_type=media_type, background=background + ) + + + diff --git a/ruoyi-fastapi-backend/utils/string_util.py b/ruoyi-fastapi-backend/utils/string_util.py new file mode 100644 index 0000000..7196bcf --- /dev/null +++ b/ruoyi-fastapi-backend/utils/string_util.py @@ -0,0 +1,170 @@ +from typing import Dict, List +from config.constant import CommonConstant + + +class StringUtil: + """ + 字符串工具类 + """ + + @classmethod + def is_blank(cls, string: str) -> bool: + """ + 校验字符串是否为''或全空格 + + :param string: 需要校验的字符串 + :return: 校验结果 + """ + if string is None: + return False + str_len = len(string) + if str_len == 0: + return True + else: + for i in range(str_len): + if string[i] != ' ': + return False + return True + + @classmethod + def is_empty(cls, string) -> bool: + """ + 校验字符串是否为''或None + + :param string: 需要校验的字符串 + :return: 校验结果 + """ + return string is None or len(string) == 0 + + @classmethod + def is_not_empty(cls, string: str) -> bool: + """ + 校验字符串是否不是''和None + + :param string: 需要校验的字符串 + :return: 校验结果 + """ + return not cls.is_empty(string) + + @classmethod + def is_http(cls, link: str): + """ + 判断是否为http(s)://开头 + + :param link: 链接 + :return: 是否为http(s)://开头 + """ + return link.startswith(CommonConstant.HTTP) or link.startswith(CommonConstant.HTTPS) + + @classmethod + def contains_ignore_case(cls, search_str: str, compare_str: str): + """ + 查找指定字符串是否包含指定字符串同时忽略大小写 + + :param search_str: 查找的字符串 + :param compare_str: 比对的字符串 + :return: 查找结果 + """ + if compare_str and search_str: + return compare_str.lower() in search_str.lower() + return False + + @classmethod + def contains_any_ignore_case(cls, search_str: str, compare_str_list: List[str]): + """ + 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时忽略大小写 + + :param search_str: 查找的字符串 + :param compare_str_list: 比对的字符串列表 + :return: 查找结果 + """ + if search_str and compare_str_list: + return any([cls.contains_ignore_case(search_str, compare_str) for compare_str in compare_str_list]) + return False + + @classmethod + def equals_ignore_case(cls, search_str: str, compare_str: str): + """ + 比较两个字符串是否相等同时忽略大小写 + + :param search_str: 查找的字符串 + :param compare_str: 比对的字符串 + :return: 比较结果 + """ + if search_str and compare_str: + return search_str.lower() == compare_str.lower() + return False + + @classmethod + def equals_any_ignore_case(cls, search_str: str, compare_str_list: List[str]): + """ + 比较指定字符串是否与指定字符串列表中的任意一个字符串相等同时忽略大小写 + + :param search_str: 查找的字符串 + :param compare_str_list: 比对的字符串列表 + :return: 比较结果 + """ + if search_str and compare_str_list: + return any([cls.equals_ignore_case(search_str, compare_str) for compare_str in compare_str_list]) + return False + + @classmethod + def startswith_case(cls, search_str: str, compare_str: str): + """ + 查找指定字符串是否以指定字符串开头 + + :param search_str: 查找的字符串 + :param compare_str: 比对的字符串 + :return: 查找结果 + """ + if compare_str and search_str: + return search_str.startswith(compare_str) + return False + + @classmethod + def startswith_any_case(cls, search_str: str, compare_str_list: List[str]): + """ + 查找指定字符串是否以指定字符串列表中的任意一个字符串开头 + + :param search_str: 查找的字符串 + :param compare_str_list: 比对的字符串列表 + :return: 查找结果 + """ + if search_str and compare_str_list: + return any([cls.startswith_case(search_str, compare_str) for compare_str in compare_str_list]) + return False + + @classmethod + def convert_to_camel_case(cls, name: str) -> str: + """ + 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串 + + :param name: 转换前的下划线大写方式命名的字符串 + :return: 转换后的驼峰式命名的字符串 + """ + if not name: + return '' + if '_' not in name: + return name[0].upper() + name[1:] + parts = name.split('_') + result = [] + for part in parts: + if not part: + continue + result.append(part[0].upper() + part[1:].lower()) + return ''.join(result) + + @classmethod + def get_mapping_value_by_key_ignore_case(cls, mapping: Dict[str, str], key: str) -> str: + """ + 根据忽略大小写的键获取字典中的对应的值 + + param mapping: 字典 + param key: 字典的键 + :return: 字典键对应的值 + """ + for k, v in mapping.items(): + if key.lower() == k.lower(): + return v + + return '' diff --git a/ruoyi-fastapi-backend/utils/template_util.py b/ruoyi-fastapi-backend/utils/template_util.py new file mode 100644 index 0000000..6765bd0 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/template_util.py @@ -0,0 +1,470 @@ +import json +import os +from datetime import datetime +from jinja2 import Environment, FileSystemLoader +from typing import Dict, List, Set +from config.constant import GenConstant +from config.env import DataBaseConfig +from exceptions.exception import ServiceWarning +from module_generator.entity.vo.gen_vo import GenTableModel, GenTableColumnModel +from utils.common_util import CamelCaseUtil, SnakeCaseUtil +from utils.string_util import StringUtil + + +class TemplateInitializer: + """ + 模板引擎初始化类 + """ + + @classmethod + def init_jinja2(cls): + """ + 初始化 Jinja2 模板引擎 + + :return: Jinja2 环境对象 + """ + try: + template_dir = os.path.join(os.getcwd(), 'module_generator', 'templates') + env = Environment( + loader=FileSystemLoader(template_dir), + keep_trailing_newline=True, + trim_blocks=True, + lstrip_blocks=True, + ) + env.filters.update( + { + 'camel_to_snake': SnakeCaseUtil.camel_to_snake, + 'snake_to_camel': CamelCaseUtil.snake_to_camel, + 'get_sqlalchemy_type': TemplateUtils.get_sqlalchemy_type, + } + ) + return env + except Exception as e: + raise RuntimeError(f'初始化Jinja2模板引擎失败: {e}') + + +class TemplateUtils: + """ + 模板工具类 + """ + + # 项目路径 + FRONTEND_PROJECT_PATH = 'frontend' + BACKEND_PROJECT_PATH = 'backend' + DEFAULT_PARENT_MENU_ID = '3' + + @classmethod + def prepare_context(cls, gen_table: GenTableModel): + """ + 准备模板变量 + + :param gen_table: 生成表的配置信息 + :return: 模板上下文字典 + """ + if not gen_table.options: + raise ServiceWarning(message='请先完善生成配置信息') + class_name = gen_table.class_name + module_name = gen_table.module_name + business_name = gen_table.business_name + package_name = gen_table.package_name + tpl_category = gen_table.tpl_category + function_name = gen_table.function_name + + context = { + 'tplCategory': tpl_category, + 'tableName': gen_table.table_name, + 'functionName': function_name if StringUtil.is_not_empty(function_name) else '【请填写功能名称】', + 'ClassName': class_name, + 'className': class_name.lower(), + 'moduleName': module_name, + 'BusinessName': business_name.capitalize(), + 'businessName': business_name, + 'basePackage': cls.get_package_prefix(package_name), + 'packageName': package_name, + 'author': gen_table.function_author, + 'datetime': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'pkColumn': gen_table.pk_column, + 'doImportList': cls.get_do_import_list(gen_table), + 'voImportList': cls.get_vo_import_list(gen_table), + 'permissionPrefix': cls.get_permission_prefix(module_name, business_name), + 'columns': gen_table.columns, + 'table': gen_table, + 'dicts': cls.get_dicts(gen_table), + 'dbType': DataBaseConfig.db_type, + 'column_not_add_show': GenConstant.COLUMNNAME_NOT_ADD_SHOW, + 'column_not_edit_show': GenConstant.COLUMNNAME_NOT_EDIT_SHOW, + } + + # 设置菜单、树形结构、子表的上下文 + cls.set_menu_context(context, gen_table) + if tpl_category == GenConstant.TPL_TREE: + cls.set_tree_context(context, gen_table) + if tpl_category == GenConstant.TPL_SUB: + cls.set_sub_context(context, gen_table) + + return context + + @classmethod + def set_menu_context(cls, context: Dict, gen_table: GenTableModel): + """ + 设置菜单上下文 + + :param context: 模板上下文字典 + :param gen_table: 生成表的配置信息 + :return: 新的模板上下文字典 + """ + options = gen_table.options + params_obj = json.loads(options) + context['parentMenuId'] = cls.get_parent_menu_id(params_obj) + + @classmethod + def set_tree_context(cls, context: Dict, gen_table: GenTableModel): + """ + 设置树形结构上下文 + + :param context: 模板上下文字典 + :param gen_table: 生成表的配置信息 + :return: 新的模板上下文字典 + """ + options = gen_table.options + params_obj = json.loads(options) + context['treeCode'] = cls.get_tree_code(params_obj) + context['treeParentCode'] = cls.get_tree_parent_code(params_obj) + context['treeName'] = cls.get_tree_name(params_obj) + context['expandColumn'] = cls.get_expand_column(gen_table) + + @classmethod + def set_sub_context(cls, context: Dict, gen_table: GenTableModel): + """ + 设置子表上下文 + + :param context: 模板上下文字典 + :param gen_table: 生成表的配置信息 + :return: 新的模板上下文字典 + """ + sub_table = gen_table.sub_table + sub_table_name = gen_table.sub_table_name + sub_table_fk_name = gen_table.sub_table_fk_name + sub_class_name = sub_table.class_name + sub_table_fk_class_name = StringUtil.convert_to_camel_case(sub_table_fk_name) + context['subTable'] = sub_table + context['subTableName'] = sub_table_name + context['subTableFkName'] = sub_table_fk_name + context['subTableFkClassName'] = sub_table_fk_class_name + context['subTableFkclassName'] = sub_table_fk_class_name.lower() + context['subClassName'] = sub_class_name + context['subclassName'] = sub_class_name.lower() + + @classmethod + def get_template_list(cls, tpl_category: str, tpl_web_type: str): + """ + 获取模板列表 + + :param tpl_category: 生成模板类型 + :param tpl_web_type: 前端类型 + :return: 模板列表 + """ + use_web_type = 'vue' + if tpl_web_type == 'element-plus': + use_web_type = 'vue/v3' + templates = [ + 'python/controller.py.jinja2', + 'python/dao.py.jinja2', + 'python/do.py.jinja2', + 'python/service.py.jinja2', + 'python/vo.py.jinja2', + 'sql/sql.jinja2', + 'js/api.js.jinja2', + ] + if tpl_category == GenConstant.TPL_CRUD: + templates.append(f'{use_web_type}/index.vue.jinja2') + elif tpl_category == GenConstant.TPL_TREE: + templates.append(f'{use_web_type}/index-tree.vue.jinja2') + elif tpl_category == GenConstant.TPL_SUB: + templates.append(f'{use_web_type}/index.vue.jinja2') + # templates.append('python/sub-domain.python.jinja2') + return templates + + @classmethod + def get_file_name(cls, template: List[str], gen_table: GenTableModel): + """ + 根据模板生成文件名 + + :param template: 模板列表 + :param gen_table: 生成表的配置信息 + :return: 模板生成文件名 + """ + package_name = gen_table.package_name + module_name = gen_table.module_name + business_name = gen_table.business_name + + vue_path = cls.FRONTEND_PROJECT_PATH + python_path = f'{cls.BACKEND_PROJECT_PATH}/{package_name.replace(".", "/")}' + + if 'controller.py.jinja2' in template: + return f'{python_path}/controller/{business_name}_controller.py' + elif 'dao.py.jinja2' in template: + return f'{python_path}/dao/{business_name}_dao.py' + elif 'do.py.jinja2' in template: + return f'{python_path}/entity/do/{business_name}_do.py' + elif 'service.py.jinja2' in template: + return f'{python_path}/service/{business_name}_service.py' + elif 'vo.py.jinja2' in template: + return f'{python_path}/entity/vo/{business_name}_vo.py' + elif 'sql.jinja2' in template: + return f'{cls.BACKEND_PROJECT_PATH}/sql/{business_name}_menu.sql' + elif 'api.js.jinja2' in template: + return f'{vue_path}/api/{module_name}/{business_name}.js' + elif 'index.vue.jinja2' in template or 'index-tree.vue.jinja2' in template: + return f'{vue_path}/views/{module_name}/{business_name}/index.vue' + return '' + + @classmethod + def get_package_prefix(cls, package_name: str): + """ + 获取包前缀 + + :param package_name: 包名 + :return: 包前缀 + """ + return package_name[: package_name.rfind('.')] + + @classmethod + def get_vo_import_list(cls, gen_table: GenTableModel): + """ + 获取vo模板导入包列表 + + :param gen_table: 生成表的配置信息 + :return: 导入包列表 + """ + columns = gen_table.columns or [] + import_list = set() + for column in columns: + if column.python_type in GenConstant.TYPE_DATE: + import_list.add(f'from datetime import {column.python_type}') + elif column.python_type == GenConstant.TYPE_DECIMAL: + import_list.add('from decimal import Decimal') + if gen_table.sub: + sub_columns = gen_table.sub_table.columns or [] + for sub_column in sub_columns: + if sub_column.python_type in GenConstant.TYPE_DATE: + import_list.add(f'from datetime import {sub_column.python_type}') + elif sub_column.python_type == GenConstant.TYPE_DECIMAL: + import_list.add('from decimal import Decimal') + return cls.merge_same_imports(list(import_list), 'from datetime import') + + @classmethod + def get_do_import_list(cls, gen_table: GenTableModel): + """ + 获取do模板导入包列表 + + :param gen_table: 生成表的配置信息 + :return: 导入包列表 + """ + columns = gen_table.columns or [] + import_list = set() + import_list.add('from sqlalchemy import Column') + for column in columns: + data_type = cls.get_db_type(column.column_type) + if data_type in GenConstant.COLUMNTYPE_GEOMETRY: + import_list.add('from geoalchemy2 import Geometry') + import_list.add( + f'from sqlalchemy import {StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_SQLALCHEMY_TYPE_MAPPING, data_type)}' + ) + if gen_table.sub: + import_list.add('from sqlalchemy import ForeignKey') + sub_columns = gen_table.sub_table.columns or [] + for sub_column in sub_columns: + data_type = cls.get_db_type(sub_column.column_type) + import_list.add( + f'from sqlalchemy import {StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_SQLALCHEMY_TYPE_MAPPING, data_type)}' + ) + return cls.merge_same_imports(list(import_list), 'from sqlalchemy import') + + @classmethod + def get_db_type(cls, column_type: str) -> str: + """ + 获取数据库类型字段 + + param column_type: 字段类型 + :return: 数据库类型 + """ + if '(' in column_type: + return column_type.split('(')[0] + return column_type + + @classmethod + def merge_same_imports(cls, imports: List[str], import_start: str) -> List[str]: + """ + 合并相同的导入语句 + + :param imports: 导入语句列表 + :param import_start: 导入语句的起始字符串 + :return: 合并后的导入语句列表 + """ + merged_imports = [] + _imports = [] + for import_stmt in imports: + if import_stmt.startswith(import_start): + imported_items = import_stmt.split('import')[1].strip() + _imports.extend(imported_items.split(', ')) + else: + merged_imports.append(import_stmt) + + if _imports: + merged_datetime_import = f'{import_start} {", ".join(_imports)}' + merged_imports.append(merged_datetime_import) + + return merged_imports + + @classmethod + def get_dicts(cls, gen_table: GenTableModel): + """ + 获取字典列表 + + :param gen_table: 生成表的配置信息 + :return: 字典列表 + """ + columns = gen_table.columns or [] + dicts = set() + cls.add_dicts(dicts, columns) + if gen_table.sub_table is not None: + cls.add_dicts(dicts, gen_table.sub_table.columns) + return ', '.join(dicts) + + @classmethod + def add_dicts(cls, dicts: Set[str], columns: List[GenTableColumnModel]): + """ + 添加字典列表 + + :param dicts: 字典列表 + :param columns: 字段列表 + :return: 新的字典列表 + """ + for column in columns: + if ( + not column.super_column + and StringUtil.is_not_empty(column.dict_type) + and StringUtil.equals_any_ignore_case( + column.html_type, [GenConstant.HTML_SELECT, GenConstant.HTML_RADIO, GenConstant.HTML_CHECKBOX] + ) + ): + dicts.add(f"'{column.dict_type}'") + + @classmethod + def get_permission_prefix(cls, module_name: str, business_name: str): + """ + 获取权限前缀 + + :param module_name: 模块名 + :param business_name: 业务名 + :return: 权限前缀 + """ + return f'{module_name}:{business_name}' + + @classmethod + def get_parent_menu_id(cls, params_obj: Dict): + """ + 获取上级菜单ID + + :param params_obj: 菜单参数字典 + :return: 上级菜单ID + """ + if params_obj and params_obj.get(GenConstant.PARENT_MENU_ID): + return params_obj.get(GenConstant.PARENT_MENU_ID) + return cls.DEFAULT_PARENT_MENU_ID + + @classmethod + def get_tree_code(cls, params_obj: Dict): + """ + 获取树编码 + + :param params_obj: 菜单参数字典 + :return: 树编码 + """ + if GenConstant.TREE_CODE in params_obj: + return cls.to_camel_case(params_obj.get(GenConstant.TREE_CODE)) + return '' + + @classmethod + def get_tree_parent_code(cls, params_obj: Dict): + """ + 获取树父编码 + + :param params_obj: 菜单参数字典 + :return: 树父编码 + """ + if GenConstant.TREE_PARENT_CODE in params_obj: + return cls.to_camel_case(params_obj.get(GenConstant.TREE_PARENT_CODE)) + return '' + + @classmethod + def get_tree_name(cls, params_obj: Dict): + """ + 获取树名称 + + :param params_obj: 菜单参数字典 + :return: 树名称 + """ + if GenConstant.TREE_NAME in params_obj: + return cls.to_camel_case(params_obj.get(GenConstant.TREE_NAME)) + return '' + + @classmethod + def get_expand_column(cls, gen_table: GenTableModel): + """ + 获取展开列 + + :param gen_table: 生成表的配置信息 + :return: 展开列 + """ + options = gen_table.options + params_obj = json.loads(options) + tree_name = params_obj.get(GenConstant.TREE_NAME) + num = 0 + for column in gen_table.columns or []: + if column.list: + num += 1 + if column.column_name == tree_name: + break + return num + + @classmethod + def to_camel_case(cls, text: str) -> str: + """ + 将字符串转换为驼峰命名 + + :param text: 待转换的字符串 + :return: 转换后的驼峰命名字符串 + """ + parts = text.split('_') + return parts[0] + ''.join(word.capitalize() for word in parts[1:]) + + @classmethod + def get_sqlalchemy_type(cls, column_type: str): + """ + 获取SQLAlchemy类型 + + :param column_type: 列类型 + :return: SQLAlchemy类型 + """ + if '(' in column_type: + column_type_list = column_type.split('(') + if column_type_list[0] in GenConstant.COLUMNTYPE_STR: + sqlalchemy_type = ( + StringUtil.get_mapping_value_by_key_ignore_case( + GenConstant.DB_TO_SQLALCHEMY_TYPE_MAPPING, column_type_list[0] + ) + + '(' + + column_type_list[1] + ) + else: + sqlalchemy_type = StringUtil.get_mapping_value_by_key_ignore_case( + GenConstant.DB_TO_SQLALCHEMY_TYPE_MAPPING, column_type_list[0] + ) + else: + sqlalchemy_type = StringUtil.get_mapping_value_by_key_ignore_case( + GenConstant.DB_TO_SQLALCHEMY_TYPE_MAPPING, column_type + ) + + return sqlalchemy_type diff --git a/ruoyi-fastapi-backend/utils/time_format_util.py b/ruoyi-fastapi-backend/utils/time_format_util.py new file mode 100644 index 0000000..bfb0481 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/time_format_util.py @@ -0,0 +1,141 @@ +from copy import deepcopy +from datetime import datetime +from dateutil.parser import parse +from typing import Dict, List, Union + + +def object_format_datetime(obj): + """ + :param obj: 输入一个对象 + :return:对目标对象所有datetime类型的属性格式化 + """ + for attr in dir(obj): + value = getattr(obj, attr) + if isinstance(value, datetime): + setattr(obj, attr, value.strftime('%Y-%m-%d %H:%M:%S')) + return obj + + +def list_format_datetime(lst): + """ + :param lst: 输入一个嵌套对象的列表 + :return: 对目标列表中所有对象的datetime类型的属性格式化 + """ + for obj in lst: + for attr in dir(obj): + value = getattr(obj, attr) + if isinstance(value, datetime): + setattr(obj, attr, value.strftime('%Y-%m-%d %H:%M:%S')) + return lst + + +def format_datetime_dict_list(dicts): + """ + 递归遍历嵌套字典,并将 datetime 值转换为字符串格式 + + :param dicts: 输入一个嵌套字典的列表 + :return: 对目标列表中所有字典的datetime类型的属性格式化 + """ + result = [] + + for item in dicts: + new_item = {} + for k, v in item.items(): + if isinstance(v, dict): + # 递归遍历子字典 + new_item[k] = format_datetime_dict_list([v])[0] + elif isinstance(v, datetime): + # 如果值是 datetime 类型,则格式化为字符串 + new_item[k] = v.strftime('%Y-%m-%d %H:%M:%S') + else: + # 否则保留原始值 + new_item[k] = v + result.append(new_item) + + return result + + +class TimeFormatUtil: + """ + 时间格式化工具类 + """ + + @classmethod + def format_time(cls, time_info: Union[str, datetime], format: str = '%Y-%m-%d %H:%M:%S'): + """ + 格式化时间字符串或datetime对象为指定格式 + + :param time_info: 时间字符串或datetime对象 + :param format: 格式化格式,默认为'%Y-%m-%d %H:%M:%S' + :return: 格式化后的时间字符串 + """ + if isinstance(time_info, datetime): + format_date = time_info.strftime(format) + else: + try: + date = parse(time_info) + format_date = date.strftime(format) + except Exception: + format_date = time_info + + return format_date + + @classmethod + def parse_date(cls, time_str: str): + """ + 解析时间字符串提取日期部分 + + :param time_str: 时间字符串 + :return: 日期部分 + """ + try: + dt = parse(time_str) + return dt.date() + except Exception: + return time_str + + @classmethod + def format_time_dict(cls, time_dict: Dict, format: str = '%Y-%m-%d %H:%M:%S'): + """ + 格式化时间字典 + + :param time_dict: 时间字典 + :param format: 格式化格式,默认为'%Y-%m-%d %H:%M:%S' + :return: 格式化后的时间字典 + """ + copy_time_dict = deepcopy(time_dict) + for k, v in copy_time_dict.items(): + if isinstance(v, (str, datetime)): + copy_time_dict[k] = cls.format_time(v, format) + elif isinstance(v, dict): + copy_time_dict[k] = cls.format_time_dict(v, format) + elif isinstance(v, list): + copy_time_dict[k] = cls.format_time_list(v, format) + else: + copy_time_dict[k] = v + + return copy_time_dict + + @classmethod + def format_time_list(cls, time_list: List, format: str = '%Y-%m-%d %H:%M:%S'): + """ + 格式化时间列表 + + :param time_list: 时间列表 + :param format: 格式化格式,默认为'%Y-%m-%d %H:%M:%S' + :return: 格式化后的时间列表 + """ + format_time_list = [] + for item in time_list: + if isinstance(item, (str, datetime)): + format_item = cls.format_time(item, format) + elif isinstance(item, dict): + format_item = cls.format_time_dict(item, format) + elif isinstance(item, list): + format_item = cls.format_time_list(item, format) + else: + format_item = item + + format_time_list.append(format_item) + + return format_time_list diff --git a/ruoyi-fastapi-backend/utils/unitree_util.py b/ruoyi-fastapi-backend/utils/unitree_util.py new file mode 100644 index 0000000..151e81e --- /dev/null +++ b/ruoyi-fastapi-backend/utils/unitree_util.py @@ -0,0 +1 @@ +# from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize \ No newline at end of file diff --git a/ruoyi-fastapi-backend/utils/upload_util.py b/ruoyi-fastapi-backend/utils/upload_util.py new file mode 100644 index 0000000..726789e --- /dev/null +++ b/ruoyi-fastapi-backend/utils/upload_util.py @@ -0,0 +1,105 @@ +import os +import random +from datetime import datetime +from fastapi import UploadFile +from config.env import UploadConfig + + +class UploadUtil: + """ + 上传工具类 + """ + + @classmethod + def generate_random_number(cls): + """ + 生成3位数字构成的字符串 + + :return: 3位数字构成的字符串 + """ + random_number = random.randint(1, 999) + + return f'{random_number:03}' + + @classmethod + def check_file_exists(cls, filepath: str): + """ + 检查文件是否存在 + + :param filepath: 文件路径 + :return: 校验结果 + """ + return os.path.exists(filepath) + + @classmethod + def check_file_extension(cls, file: UploadFile): + """ + 检查文件后缀是否合法 + + :param file: 文件对象 + :return: 校验结果 + """ + file_extension = file.filename.rsplit('.', 1)[-1] + if file_extension in UploadConfig.DEFAULT_ALLOWED_EXTENSION: + return True + return False + + @classmethod + def check_file_timestamp(cls, filename: str): + """ + 校验文件时间戳是否合法 + + :param filename: 文件名称 + :return: 校验结果 + """ + timestamp = filename.rsplit('.', 1)[0].split('_')[-1].split(UploadConfig.UPLOAD_MACHINE)[0] + try: + datetime.strptime(timestamp, '%Y%m%d%H%M%S') + return True + except ValueError: + return False + + @classmethod + def check_file_machine(cls, filename: str): + """ + 校验文件机器码是否合法 + + :param filename: 文件名称 + :return: 校验结果 + """ + if filename.rsplit('.', 1)[0][-4] == UploadConfig.UPLOAD_MACHINE: + return True + return False + + @classmethod + def check_file_random_code(cls, filename: str): + """ + 校验文件随机码是否合法 + + :param filename: 文件名称 + :return: 校验结果 + """ + valid_code_list = [f'{i:03}' for i in range(1, 999)] + if filename.rsplit('.', 1)[0][-3:] in valid_code_list: + return True + return False + + @classmethod + def generate_file(cls, filepath: str): + """ + 根据文件生成二进制数据 + + :param filepath: 文件路径 + :yield: 二进制数据 + """ + with open(filepath, 'rb') as response_file: + yield from response_file + + @classmethod + def delete_file(cls, filepath: str): + """ + 根据文件路径删除对应文件 + + :param filepath: 文件路径 + """ + os.remove(filepath) diff --git a/ruoyi-fastapi-backend/utils/weather_client.py b/ruoyi-fastapi-backend/utils/weather_client.py new file mode 100644 index 0000000..77ec6d3 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/weather_client.py @@ -0,0 +1,49 @@ +import httpx +from typing import Any, Dict + + +class WeatherAPIError(Exception): + """天气API调用异常""" + + def __init__(self, status_code: int, message: str): + self.status_code = status_code + self.message = message + super().__init__(f"Weather API error {status_code}: {message}") + + +class WeatherAPIClient: + """简单的天气数据客户端""" + + def __init__(self, base_url: str, api_key: str, timeout: float = 15.0): + self.base_url = base_url.rstrip('/') if base_url else '' + self.api_key = api_key + self.timeout = timeout + + async def get_current_weather(self, city: str, *, units: str = 'metric', lang: str = 'zh_cn') -> Dict[str, Any]: + if not self.api_key: + raise WeatherAPIError(401, '天气服务未配置 API 密钥') + + endpoint = f"{self.base_url}/weather" if self.base_url else "https://api.openweathermap.org/data/2.5/weather" + params = { + 'q': city, + 'appid': self.api_key, + 'units': units, + 'lang': lang, + } + + try: + async with httpx.AsyncClient(timeout=self.timeout) as client: + response = await client.get(endpoint, params=params) + except httpx.RequestError as exc: + raise WeatherAPIError(0, str(exc)) from exc + + if response.status_code != 200: + detail = '' + try: + payload = response.json() + detail = payload.get('message', '') + except ValueError: + detail = response.text + raise WeatherAPIError(response.status_code, detail or '天气服务调用失败') + + return response.json()