├── .github ├── FUNDING.yml └── workflows │ └── test.yml ├── ruoyi-fastapi-backend ├── module_task │ ├── __init__.py │ └── scheduler_test.py ├── alembic │ ├── README │ └── script.py.mako ├── assets │ └── font │ │ └── Arial.ttf ├── utils │ ├── message_util.py │ ├── pwd_util.py │ ├── dependency_util.py │ └── log_util.py ├── common │ ├── aspect │ │ └── db_seesion.py │ └── enums.py ├── sub_applications │ ├── handle.py │ └── staticfiles.py ├── Dockerfile.my ├── Dockerfile.pg ├── middlewares │ ├── gzip_middleware.py │ ├── trace_middleware │ │ ├── __init__.py │ │ ├── ctx.py │ │ ├── middle.py │ │ └── span.py │ ├── cors_middleware.py │ ├── handle.py │ └── context_middleware.py ├── app.py ├── requirements.txt ├── requirements-pg.txt ├── module_admin │ ├── entity │ │ ├── vo │ │ │ ├── common_vo.py │ │ │ ├── cache_vo.py │ │ │ └── online_vo.py │ │ └── do │ │ │ ├── post_do.py │ │ │ ├── config_do.py │ │ │ ├── notice_do.py │ │ │ └── dept_do.py │ ├── dao │ │ └── login_dao.py │ ├── controller │ │ ├── server_controller.py │ │ └── captcha_controller.py │ └── service │ │ └── captcha_service.py ├── config │ ├── get_db.py │ └── database.py ├── module_generator │ └── templates │ │ ├── js │ │ └── api.js.jinja2 │ │ └── python │ │ └── do.py.jinja2 ├── .env.dev ├── .env.prod ├── .env.dockermy ├── .env.dockerpg └── exceptions │ └── exception.py ├── ruoyi-fastapi-frontend ├── src │ ├── store │ │ ├── index.js │ │ └── modules │ │ │ ├── app.js │ │ │ ├── dict.js │ │ │ └── settings.js │ ├── components │ │ ├── ParentView │ │ │ └── index.vue │ │ ├── IconSelect │ │ │ └── requireIcons.js │ │ ├── RuoYi │ │ │ ├── Doc │ │ │ │ └── index.vue │ │ │ └── Git │ │ │ │ └── index.vue │ │ ├── SvgIcon │ │ │ ├── svgicon.js │ │ │ └── index.vue │ │ ├── Screenfull │ │ │ └── index.vue │ │ ├── iFrame │ │ │ └── index.vue │ │ ├── Hamburger │ │ │ └── index.vue │ │ └── SizeSelect │ │ │ └── index.vue │ ├── assets │ │ ├── logo │ │ │ └── logo.png │ │ ├── 401_images │ │ │ └── 401.gif │ │ ├── 404_images │ │ │ ├── 404.png │ │ │ └── 404_cloud.png │ │ ├── images │ │ │ ├── profile.jpg │ │ │ └── login-background.jpg │ │ ├── icons │ │ │ └── svg │ │ │ │ ├── chart.svg │ │ │ │ ├── size.svg │ │ │ │ ├── link.svg │ │ │ │ ├── guide.svg │ │ │ │ ├── money.svg │ │ │ │ ├── email.svg │ │ │ │ ├── drag.svg │ │ │ │ ├── documentation.svg │ │ │ │ ├── fullscreen.svg │ │ │ │ ├── user.svg │ │ │ │ ├── lock.svg │ │ │ │ ├── excel.svg │ │ │ │ ├── example.svg │ │ │ │ ├── slider.svg │ │ │ │ ├── star.svg │ │ │ │ ├── table.svg │ │ │ │ ├── search.svg │ │ │ │ ├── moon.svg │ │ │ │ ├── education.svg │ │ │ │ ├── tab.svg │ │ │ │ ├── message.svg │ │ │ │ ├── switch.svg │ │ │ │ ├── theme.svg │ │ │ │ ├── code.svg │ │ │ │ ├── druid.svg │ │ │ │ ├── peoples.svg │ │ │ │ ├── input.svg │ │ │ │ ├── server.svg │ │ │ │ ├── textarea.svg │ │ │ │ ├── time.svg │ │ │ │ ├── edit.svg │ │ │ │ ├── nested.svg │ │ │ │ ├── row.svg │ │ │ │ ├── monitor.svg │ │ │ │ ├── tree-table.svg │ │ │ │ ├── eye.svg │ │ │ │ ├── build.svg │ │ │ │ ├── clipboard.svg │ │ │ │ ├── list.svg │ │ │ │ ├── download.svg │ │ │ │ ├── icon.svg │ │ │ │ ├── enter.svg │ │ │ │ ├── international.svg │ │ │ │ ├── more-up.svg │ │ │ │ ├── question.svg │ │ │ │ ├── wechat.svg │ │ │ │ ├── skill.svg │ │ │ │ ├── people.svg │ │ │ │ ├── post.svg │ │ │ │ ├── checkbox.svg │ │ │ │ ├── language.svg │ │ │ │ ├── eye-open.svg │ │ │ │ ├── validCode.svg │ │ │ │ ├── radio.svg │ │ │ │ ├── select.svg │ │ │ │ ├── upload.svg │ │ │ │ ├── sunny.svg │ │ │ │ ├── 404.svg │ │ │ │ ├── zip.svg │ │ │ │ ├── phone.svg │ │ │ │ ├── log.svg │ │ │ │ ├── bug.svg │ │ │ │ ├── github.svg │ │ │ │ ├── pdf.svg │ │ │ │ ├── logininfor.svg │ │ │ │ ├── rate.svg │ │ │ │ ├── job.svg │ │ │ │ ├── exit-fullscreen.svg │ │ │ │ ├── tree.svg │ │ │ │ ├── swagger.svg │ │ │ │ ├── password.svg │ │ │ │ └── date-range.svg │ │ └── styles │ │ │ ├── transition.scss │ │ │ ├── mixin.scss │ │ │ ├── element-ui.scss │ │ │ └── btn.scss │ ├── utils │ │ ├── errorCode.js │ │ ├── auth.js │ │ ├── dynamicTitle.js │ │ ├── generator │ │ │ ├── css.js │ │ │ └── drawingDefault.js │ │ ├── dict.js │ │ ├── jsencrypt.js │ │ ├── permission.js │ │ ├── theme.js │ │ └── scroll-to.js │ ├── api │ │ ├── menu.js │ │ ├── monitor │ │ │ ├── server.js │ │ │ ├── online.js │ │ │ ├── operlog.js │ │ │ ├── jobLog.js │ │ │ ├── logininfor.js │ │ │ ├── cache.js │ │ │ └── job.js │ │ ├── system │ │ │ ├── post.js │ │ │ ├── notice.js │ │ │ ├── dept.js │ │ │ ├── dict │ │ │ │ ├── data.js │ │ │ │ └── type.js │ │ │ ├── menu.js │ │ │ └── config.js │ │ ├── login.js │ │ └── tool │ │ │ └── gen.js │ ├── layout │ │ └── components │ │ │ ├── index.js │ │ │ ├── Copyright │ │ │ └── index.vue │ │ │ ├── IframeToggle │ │ │ └── index.vue │ │ │ ├── Sidebar │ │ │ └── Link.vue │ │ │ └── InnerLink │ │ │ └── index.vue │ ├── views │ │ ├── tool │ │ │ ├── swagger │ │ │ │ └── index.vue │ │ │ ├── gen │ │ │ │ ├── createTable.vue │ │ │ │ └── basicInfoForm.vue │ │ │ └── build │ │ │ │ └── CodeTypeDialog.vue │ │ ├── redirect │ │ │ └── index.vue │ │ ├── monitor │ │ │ └── druid │ │ │ │ └── index.vue │ │ ├── dashboard │ │ │ └── editable-link-group.vue │ │ └── error │ │ │ └── 401.vue │ ├── directive │ │ ├── index.js │ │ ├── permission │ │ │ ├── hasRole.js │ │ │ └── hasPermi.js │ │ └── common │ │ │ └── copyText.js │ ├── App.vue │ ├── plugins │ │ ├── index.js │ │ ├── auth.js │ │ ├── cache.js │ │ └── modal.js │ └── settings.js ├── bin │ ├── build.bat │ ├── package.bat │ ├── run-web.bat │ ├── nginx.dockermy.conf │ └── nginx.dockerpg.conf ├── public │ └── favicon.ico ├── .env.development ├── vite │ └── plugins │ │ ├── setup-extend.js │ │ ├── auto-import.js │ │ ├── svg-icon.js │ │ ├── index.js │ │ └── compression.js ├── .env.docker ├── .env.staging ├── .env.production ├── .gitignore ├── Dockerfile ├── LICENSE └── package.json ├── LICENSE ├── docker-compose.pg.yml └── docker-compose.my.yml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [insistence] -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/module_task/__init__.py: -------------------------------------------------------------------------------- 1 | from . import scheduler_test # noqa: F401 2 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration with an async dbapi. -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/store/index.js: -------------------------------------------------------------------------------- 1 | const store = createPinia() 2 | 3 | export default store -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/components/ParentView/index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/bin/build.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insistence/RuoYi-Vue3-FastAPI/HEAD/ruoyi-fastapi-frontend/bin/build.bat -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/bin/package.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insistence/RuoYi-Vue3-FastAPI/HEAD/ruoyi-fastapi-frontend/bin/package.bat -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/bin/run-web.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insistence/RuoYi-Vue3-FastAPI/HEAD/ruoyi-fastapi-frontend/bin/run-web.bat -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insistence/RuoYi-Vue3-FastAPI/HEAD/ruoyi-fastapi-frontend/public/favicon.ico -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/assets/font/Arial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insistence/RuoYi-Vue3-FastAPI/HEAD/ruoyi-fastapi-backend/assets/font/Arial.ttf -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insistence/RuoYi-Vue3-FastAPI/HEAD/ruoyi-fastapi-frontend/src/assets/logo/logo.png -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/401_images/401.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insistence/RuoYi-Vue3-FastAPI/HEAD/ruoyi-fastapi-frontend/src/assets/401_images/401.gif -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insistence/RuoYi-Vue3-FastAPI/HEAD/ruoyi-fastapi-frontend/src/assets/404_images/404.png -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/images/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insistence/RuoYi-Vue3-FastAPI/HEAD/ruoyi-fastapi-frontend/src/assets/images/profile.jpg -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/utils/message_util.py: -------------------------------------------------------------------------------- 1 | from utils.log_util import logger 2 | 3 | 4 | def message_service(sms_code: str) -> None: 5 | logger.info(f'短信验证码为{sms_code}') 6 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insistence/RuoYi-Vue3-FastAPI/HEAD/ruoyi-fastapi-frontend/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/images/login-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insistence/RuoYi-Vue3-FastAPI/HEAD/ruoyi-fastapi-frontend/src/assets/images/login-background.jpg -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/utils/errorCode.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '401': '认证失败,无法访问系统资源', 3 | '403': '当前操作没有权限', 4 | '404': '访问资源不存在', 5 | 'default': '系统未知错误,请反馈给管理员' 6 | } 7 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/.env.development: -------------------------------------------------------------------------------- 1 | # 页面标题 2 | VITE_APP_TITLE = vfadmin管理系统 3 | 4 | # 开发环境配置 5 | VITE_APP_ENV = 'development' 6 | 7 | # vfadmin管理系统/开发环境 8 | VITE_APP_BASE_API = '/dev-api' 9 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/vite/plugins/setup-extend.js: -------------------------------------------------------------------------------- 1 | import setupExtend from 'unplugin-vue-setup-extend-plus/vite' 2 | 3 | export default function createSetupExtend() { 4 | return setupExtend({}) 5 | } 6 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/menu.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 获取路由 4 | export const getRouters = () => { 5 | return request({ 6 | url: '/getRouters', 7 | method: 'get' 8 | }) 9 | } -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/monitor/server.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 获取服务信息 4 | export function getServer() { 5 | return request({ 6 | url: '/monitor/server', 7 | method: 'get' 8 | }) 9 | } -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/chart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/size.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/.env.docker: -------------------------------------------------------------------------------- 1 | # 页面标题 2 | VITE_APP_TITLE = vfadmin管理系统 3 | 4 | # 生产环境配置 5 | VITE_APP_ENV = 'docker' 6 | 7 | # vfadmin管理系统/生产环境 8 | VITE_APP_BASE_API = '/docker-api' 9 | 10 | # 是否在打包时开启压缩,支持 gzip 和 brotli 11 | VITE_BUILD_COMPRESS = gzip -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/.env.staging: -------------------------------------------------------------------------------- 1 | # 页面标题 2 | VITE_APP_TITLE = vfadmin管理系统 3 | 4 | # 生产环境配置 5 | VITE_APP_ENV = 'staging' 6 | 7 | # vfadmin管理系统/生产环境 8 | VITE_APP_BASE_API = '/stage-api' 9 | 10 | # 是否在打包时开启压缩,支持 gzip 和 brotli 11 | VITE_BUILD_COMPRESS = gzip -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/.env.production: -------------------------------------------------------------------------------- 1 | # 页面标题 2 | VITE_APP_TITLE = vfadmin管理系统 3 | 4 | # 生产环境配置 5 | VITE_APP_ENV = 'production' 6 | 7 | # vfadmin管理系统/生产环境 8 | VITE_APP_BASE_API = '/prod-api' 9 | 10 | # 是否在打包时开启压缩,支持 gzip 和 brotli 11 | VITE_BUILD_COMPRESS = gzip -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as AppMain } from './AppMain' 2 | export { default as Navbar } from './Navbar' 3 | export { default as Settings } from './Settings' 4 | export { default as TagsView } from './TagsView/index.vue' 5 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/views/tool/swagger/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/components/IconSelect/requireIcons.js: -------------------------------------------------------------------------------- 1 | let icons = [] 2 | const modules = import.meta.glob('./../../assets/icons/svg/*.svg'); 3 | for (const path in modules) { 4 | const p = path.split('assets/icons/svg/')[1].split('.svg')[0]; 5 | icons.push(p); 6 | } 7 | 8 | export default icons -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/common/aspect/db_seesion.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends, params 2 | 3 | from config.get_db import get_db 4 | 5 | 6 | def DBSessionDependency() -> params.Depends: # noqa: N802 7 | """ 8 | 数据库会话依赖 9 | 10 | :return: 数据库会话依赖 11 | """ 12 | return Depends(get_db) 13 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/sub_applications/handle.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | 3 | from sub_applications.staticfiles import mount_staticfiles 4 | 5 | 6 | def handle_sub_applications(app: FastAPI) -> None: 7 | """ 8 | 全局处理子应用挂载 9 | """ 10 | # 挂载静态文件 11 | mount_staticfiles(app) 12 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/Dockerfile.my: -------------------------------------------------------------------------------- 1 | FROM python:3.10 2 | WORKDIR /app 3 | 4 | # 复制源代码 5 | COPY . . 6 | 7 | # 安装依赖 8 | RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple 9 | 10 | # 暴露端口 11 | EXPOSE 9099 12 | 13 | # 启动命令 14 | CMD ["python", "app.py", "--env=dockermy"] -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/Dockerfile.pg: -------------------------------------------------------------------------------- 1 | FROM python:3.10 2 | WORKDIR /app 3 | 4 | # 复制源代码 5 | COPY . . 6 | 7 | # 安装依赖 8 | RUN pip install --no-cache-dir -r requirements-pg.txt -i https://pypi.tuna.tsinghua.edu.cn/simple 9 | 10 | # 暴露端口 11 | EXPOSE 9099 12 | 13 | # 启动命令 14 | CMD ["python", "app.py", "--env=dockerpg"] -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/components/RuoYi/Doc/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/components/RuoYi/Git/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/vite/plugins/auto-import.js: -------------------------------------------------------------------------------- 1 | import autoImport from 'unplugin-auto-import/vite' 2 | 3 | export default function createAutoImport() { 4 | return autoImport({ 5 | imports: [ 6 | 'vue', 7 | 'vue-router', 8 | 'pinia' 9 | ], 10 | dts: false 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/directive/index.js: -------------------------------------------------------------------------------- 1 | import hasRole from './permission/hasRole' 2 | import hasPermi from './permission/hasPermi' 3 | import copyText from './common/copyText' 4 | 5 | export default function directive(app){ 6 | app.directive('hasRole', hasRole) 7 | app.directive('hasPermi', hasPermi) 8 | app.directive('copyText', copyText) 9 | } -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/guide.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/components/SvgIcon/svgicon.js: -------------------------------------------------------------------------------- 1 | import * as components from '@element-plus/icons-vue' 2 | 3 | export default { 4 | install: (app) => { 5 | for (const key in components) { 6 | const componentConfig = components[key]; 7 | app.component(componentConfig.name, componentConfig); 8 | } 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/money.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const TokenKey = 'Admin-Token' 4 | 5 | export function getToken() { 6 | return Cookies.get(TokenKey) 7 | } 8 | 9 | export function setToken(token) { 10 | return Cookies.set(TokenKey, token) 11 | } 12 | 13 | export function removeToken() { 14 | return Cookies.remove(TokenKey) 15 | } 16 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/views/redirect/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/middlewares/gzip_middleware.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from starlette.middleware.gzip import GZipMiddleware 3 | 4 | 5 | def add_gzip_middleware(app: FastAPI) -> None: 6 | """ 7 | 添加gzip压缩中间件 8 | 9 | :param app: FastAPI对象 10 | :return: 11 | """ 12 | app.add_middleware(GZipMiddleware, minimum_size=1000, compresslevel=9) 13 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/email.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | **/*.log 8 | 9 | tests/**/coverage/ 10 | tests/e2e/reports 11 | selenium-debug.log 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | *.local 21 | 22 | package-lock.json 23 | yarn.lock 24 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/drag.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/sub_applications/staticfiles.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from fastapi.staticfiles import StaticFiles 3 | 4 | from config.env import UploadConfig 5 | 6 | 7 | def mount_staticfiles(app: FastAPI) -> None: 8 | """ 9 | 挂载静态文件 10 | """ 11 | app.mount(f'{UploadConfig.UPLOAD_PREFIX}', StaticFiles(directory=f'{UploadConfig.UPLOAD_PATH}'), name='profile') 12 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/vite/plugins/svg-icon.js: -------------------------------------------------------------------------------- 1 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' 2 | import path from 'path' 3 | 4 | export default function createSvgIcon(isBuild) { 5 | return createSvgIconsPlugin({ 6 | iconDirs: [path.resolve(process.cwd(), 'src/assets/icons/svg')], 7 | symbolId: 'icon-[dir]-[name]', 8 | svgoOptions: isBuild 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/views/monitor/druid/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/app.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | 3 | from config.env import AppConfig 4 | from server import create_app 5 | 6 | app = create_app() 7 | 8 | if __name__ == '__main__': 9 | uvicorn.run( 10 | app='app:app', 11 | host=AppConfig.app_host, 12 | port=AppConfig.app_port, 13 | root_path=AppConfig.app_root_path, 14 | reload=AppConfig.app_reload, 15 | ) 16 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/monitor/online.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询在线用户列表 4 | export function list(query) { 5 | return request({ 6 | url: '/monitor/online/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 强退用户 13 | export function forceLogout(tokenId) { 14 | return request({ 15 | url: '/monitor/online/' + tokenId, 16 | method: 'delete' 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/module_task/scheduler_test.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | 4 | def job(*args, **kwargs) -> None: 5 | """ 6 | 定时任务执行同步函数示例 7 | """ 8 | print(args) 9 | print(kwargs) 10 | print(f'{datetime.now()}同步函数执行了') 11 | 12 | 13 | async def async_job(*args, **kwargs) -> None: 14 | """ 15 | 定时任务执行异步函数示例 16 | """ 17 | print(args) 18 | print(kwargs) 19 | print(f'{datetime.now()}异步函数执行了') 20 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/middlewares/trace_middleware/__init__.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | 3 | from .ctx import TraceCtx 4 | from .middle import TraceASGIMiddleware 5 | 6 | __all__ = ('TraceASGIMiddleware', 'TraceCtx') 7 | 8 | __version__ = '0.1.0' 9 | 10 | 11 | def add_trace_middleware(app: FastAPI) -> None: 12 | """ 13 | 添加trace中间件 14 | 15 | :param app: FastAPI对象 16 | :return: 17 | """ 18 | app.add_middleware(TraceASGIMiddleware) 19 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/documentation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/fullscreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/utils/dynamicTitle.js: -------------------------------------------------------------------------------- 1 | import defaultSettings from '@/settings' 2 | import useSettingsStore from '@/store/modules/settings' 3 | 4 | /** 5 | * 动态修改标题 6 | */ 7 | export function useDynamicTitle() { 8 | const settingsStore = useSettingsStore(); 9 | if (settingsStore.dynamicTitle) { 10 | document.title = settingsStore.title + ' - ' + defaultSettings.title; 11 | } else { 12 | document.title = defaultSettings.title; 13 | } 14 | } -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/middlewares/trace_middleware/ctx.py: -------------------------------------------------------------------------------- 1 | import contextvars 2 | from uuid import uuid4 3 | 4 | CTX_REQUEST_ID: contextvars.ContextVar[str] = contextvars.ContextVar('request-id', default='') 5 | 6 | 7 | class TraceCtx: 8 | @staticmethod 9 | def set_id() -> str: 10 | _id = uuid4().hex 11 | CTX_REQUEST_ID.set(_id) 12 | return _id 13 | 14 | @staticmethod 15 | def get_id() -> str: 16 | return CTX_REQUEST_ID.get() 17 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/lock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles==25.1.0 2 | alembic==1.16.5 3 | APScheduler==3.11.1 4 | async-lru==2.0.5 5 | asyncmy==0.2.10 6 | bcrypt==5.0.0 7 | fastapi[all]==0.125.0 8 | loguru==0.7.3 9 | openpyxl==3.1.5 10 | pandas==2.3.3 11 | Pillow==11.3.0 12 | psutil==7.1.3 13 | pydantic-validation-decorator==0.1.5 14 | PyJWT[crypto]==2.10.1 15 | PyMySQL==1.1.2 16 | redis==6.4.0 17 | ruff==0.14.10 18 | SQLAlchemy[asyncio]==2.0.45 19 | sqlglot[rs]==28.5.0 20 | user-agents==2.2.0 21 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/requirements-pg.txt: -------------------------------------------------------------------------------- 1 | aiofiles==25.1.0 2 | alembic==1.16.5 3 | APScheduler==3.11.1 4 | async-lru==2.0.5 5 | asyncpg==0.31.0 6 | bcrypt==5.0.0 7 | fastapi[all]==0.125.0 8 | loguru==0.7.3 9 | openpyxl==3.1.5 10 | pandas==2.3.3 11 | Pillow==11.3.0 12 | psutil==7.1.3 13 | pydantic-validation-decorator==0.1.5 14 | PyJWT[crypto]==2.10.1 15 | psycopg2==2.9.11 16 | redis==6.4.0 17 | ruff==0.14.10 18 | SQLAlchemy[asyncio]==2.0.45 19 | sqlglot[rs]==28.5.0 20 | user-agents==2.2.0 21 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/excel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | # 构建阶段 2 | FROM node:18-slim AS builder 3 | WORKDIR /app 4 | 5 | # 复制源代码 6 | COPY . . 7 | 8 | # 设置npm镜像源 9 | RUN npm config set registry https://registry.npmmirror.com 10 | 11 | # 安装依赖 12 | RUN npm install 13 | 14 | # 执行docker构建命令 15 | RUN npm run build:docker 16 | 17 | # 运行阶段 18 | FROM nginx:latest 19 | WORKDIR /usr/share/nginx/html 20 | 21 | # 复制构建产物 22 | COPY --from=builder /app/dist . 23 | 24 | # 暴露端口 25 | EXPOSE 80 26 | 27 | # 启动nginx 28 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/example.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/plugins/index.js: -------------------------------------------------------------------------------- 1 | import tab from './tab' 2 | import auth from './auth' 3 | import cache from './cache' 4 | import modal from './modal' 5 | import download from './download' 6 | 7 | export default function installPlugins(app){ 8 | // 页签操作 9 | app.config.globalProperties.$tab = tab 10 | // 认证对象 11 | app.config.globalProperties.$auth = auth 12 | // 缓存对象 13 | app.config.globalProperties.$cache = cache 14 | // 模态框对象 15 | app.config.globalProperties.$modal = modal 16 | // 下载文件 17 | app.config.globalProperties.$download = download 18 | } 19 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/components/Screenfull/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/monitor/operlog.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询操作日志列表 4 | export function list(query) { 5 | return request({ 6 | url: '/monitor/operlog/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 删除操作日志 13 | export function delOperlog(operId) { 14 | return request({ 15 | url: '/monitor/operlog/' + operId, 16 | method: 'delete' 17 | }) 18 | } 19 | 20 | // 清空操作日志 21 | export function cleanOperlog() { 22 | return request({ 23 | url: '/monitor/operlog/clean', 24 | method: 'delete' 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/slider.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/monitor/jobLog.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询调度日志列表 4 | export function listJobLog(query) { 5 | return request({ 6 | url: '/monitor/jobLog/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 删除调度日志 13 | export function delJobLog(jobLogId) { 14 | return request({ 15 | url: '/monitor/jobLog/' + jobLogId, 16 | method: 'delete' 17 | }) 18 | } 19 | 20 | // 清空调度日志 21 | export function cleanJobLog() { 22 | return request({ 23 | url: '/monitor/jobLog/clean', 24 | method: 'delete' 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/utils/generator/css.js: -------------------------------------------------------------------------------- 1 | const styles = { 2 | 'el-rate': '.el-rate{display: inline-block; vertical-align: text-top;}', 3 | 'el-upload': '.el-upload__tip{line-height: 1.2;}' 4 | } 5 | 6 | function addCss(cssList, el) { 7 | const css = styles[el.tag] 8 | css && cssList.indexOf(css) === -1 && cssList.push(css) 9 | if (el.children) { 10 | el.children.forEach(el2 => addCss(cssList, el2)) 11 | } 12 | } 13 | 14 | export function makeUpCss(conf) { 15 | const cssList = [] 16 | conf.fields.forEach(el => addCss(cssList, el)) 17 | return cssList.join('\n') 18 | } 19 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/vite/plugins/index.js: -------------------------------------------------------------------------------- 1 | import vue from '@vitejs/plugin-vue' 2 | 3 | import createAutoImport from './auto-import' 4 | import createSvgIcon from './svg-icon' 5 | import createCompression from './compression' 6 | import createSetupExtend from './setup-extend' 7 | 8 | export default function createVitePlugins(viteEnv, isBuild = false) { 9 | const vitePlugins = [vue()] 10 | vitePlugins.push(createAutoImport()) 11 | vitePlugins.push(createSetupExtend()) 12 | vitePlugins.push(createSvgIcon(isBuild)) 13 | isBuild && vitePlugins.push(...createCompression(viteEnv)) 14 | return vitePlugins 15 | } 16 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/middlewares/cors_middleware.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from fastapi.middleware.cors import CORSMiddleware 3 | 4 | 5 | def add_cors_middleware(app: FastAPI) -> None: 6 | """ 7 | 添加跨域中间件 8 | 9 | :param app: FastAPI对象 10 | :return: 11 | """ 12 | # 前端页面url 13 | origins = [ 14 | 'http://localhost:80', 15 | 'http://127.0.0.1:80', 16 | ] 17 | 18 | # 后台api允许跨域 19 | app.add_middleware( 20 | CORSMiddleware, 21 | allow_origins=origins, 22 | allow_credentials=True, 23 | allow_methods=['*'], 24 | allow_headers=['*'], 25 | ) 26 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/moon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/education.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/module_admin/entity/vo/common_vo.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel, ConfigDict, Field 4 | from pydantic.alias_generators import to_camel 5 | 6 | 7 | class UploadResponseModel(BaseModel): 8 | """ 9 | 上传响应模型 10 | """ 11 | 12 | model_config = ConfigDict(alias_generator=to_camel) 13 | 14 | file_name: Optional[str] = Field(default=None, description='新文件映射路径') 15 | new_file_name: Optional[str] = Field(default=None, description='新文件名称') 16 | original_filename: Optional[str] = Field(default=None, description='原文件名称') 17 | url: Optional[str] = Field(default=None, description='新文件url') 18 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/middlewares/handle.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | 3 | from middlewares.context_middleware import add_context_cleanup_middleware 4 | from middlewares.cors_middleware import add_cors_middleware 5 | from middlewares.gzip_middleware import add_gzip_middleware 6 | from middlewares.trace_middleware import add_trace_middleware 7 | 8 | 9 | def handle_middleware(app: FastAPI) -> None: 10 | """ 11 | 全局中间件处理 12 | """ 13 | # 加载上下文清理中间件 14 | add_context_cleanup_middleware(app) 15 | # 加载跨域中间件 16 | add_cors_middleware(app) 17 | # 加载gzip压缩中间件 18 | add_gzip_middleware(app) 19 | # 加载trace中间件 20 | add_trace_middleware(app) 21 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/message.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/switch.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/theme.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | lint-format: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v5 14 | 15 | - name: Set up Python 16 | uses: actions/setup-python@v5 17 | with: 18 | python-version: "3.9" 19 | 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | pip install ruff 24 | 25 | - name: Run linting 26 | run: | 27 | ruff check ruoyi-fastapi-backend 28 | 29 | - name: Run format check 30 | run: | 31 | ruff format ruoyi-fastapi-backend --check -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/code.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/druid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/bin/nginx.dockermy.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | 5 | location / { 6 | root /usr/share/nginx/html; 7 | index index.html index.htm; 8 | try_files $uri $uri/ /index.html; 9 | } 10 | 11 | # API代理配置(MySQL版本) 12 | location /docker-api/ { 13 | proxy_pass http://ruoyi-backend-my:9099/; 14 | proxy_set_header Host $host; 15 | proxy_set_header X-Real-IP $remote_addr; 16 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 17 | proxy_set_header X-Forwarded-Proto $scheme; 18 | } 19 | 20 | error_page 500 502 503 504 /50x.html; 21 | location = /50x.html { 22 | root /usr/share/nginx/html; 23 | } 24 | } -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/bin/nginx.dockerpg.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | 5 | location / { 6 | root /usr/share/nginx/html; 7 | index index.html index.htm; 8 | try_files $uri $uri/ /index.html; 9 | } 10 | 11 | # API代理配置(PostgreSQL版本) 12 | location /docker-api/ { 13 | proxy_pass http://ruoyi-backend-pg:9099/; 14 | proxy_set_header Host $host; 15 | proxy_set_header X-Real-IP $remote_addr; 16 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 17 | proxy_set_header X-Forwarded-Proto $scheme; 18 | } 19 | 20 | error_page 500 502 503 504 /50x.html; 21 | location = /50x.html { 22 | root /usr/share/nginx/html; 23 | } 24 | } -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/peoples.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/input.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/server.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/directive/permission/hasRole.js: -------------------------------------------------------------------------------- 1 | /** 2 | * v-hasRole 角色权限处理 3 | * Copyright (c) 2019 ruoyi 4 | */ 5 | import useUserStore from '@/store/modules/user' 6 | 7 | export default { 8 | mounted(el, binding, vnode) { 9 | const { value } = binding 10 | const super_admin = "admin"; 11 | const roles = useUserStore().roles 12 | 13 | if (value && value instanceof Array && value.length > 0) { 14 | const roleFlag = value 15 | 16 | const hasRole = roles.some(role => { 17 | return super_admin === role || roleFlag.includes(role) 18 | }) 19 | 20 | if (!hasRole) { 21 | el.parentNode && el.parentNode.removeChild(el) 22 | } 23 | } else { 24 | throw new Error(`请设置角色权限标签值`) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/config/get_db.py: -------------------------------------------------------------------------------- 1 | from collections.abc import AsyncGenerator 2 | 3 | from sqlalchemy.ext.asyncio import AsyncSession 4 | 5 | from config.database import AsyncSessionLocal, Base, async_engine 6 | from utils.log_util import logger 7 | 8 | 9 | async def get_db() -> AsyncGenerator[AsyncSession, None]: 10 | """ 11 | 每一个请求处理完毕后会关闭当前连接,不同的请求使用不同的连接 12 | 13 | :return: 14 | """ 15 | async with AsyncSessionLocal() as current_db: 16 | yield current_db 17 | 18 | 19 | async def init_create_table() -> None: 20 | """ 21 | 应用启动时初始化数据库连接 22 | 23 | :return: 24 | """ 25 | logger.info('🔎 初始化数据库连接...') 26 | async with async_engine.begin() as conn: 27 | await conn.run_sync(Base.metadata.create_all) 28 | logger.info('✅️ 数据库连接成功') 29 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/monitor/logininfor.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询登录日志列表 4 | export function list(query) { 5 | return request({ 6 | url: '/monitor/logininfor/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 删除登录日志 13 | export function delLogininfor(infoId) { 14 | return request({ 15 | url: '/monitor/logininfor/' + infoId, 16 | method: 'delete' 17 | }) 18 | } 19 | 20 | // 解锁用户登录状态 21 | export function unlockLogininfor(userName) { 22 | return request({ 23 | url: '/monitor/logininfor/unlock/' + userName, 24 | method: 'get' 25 | }) 26 | } 27 | 28 | // 清空登录日志 29 | export function cleanLogininfor() { 30 | return request({ 31 | url: '/monitor/logininfor/clean', 32 | method: 'delete' 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/layout/components/Copyright/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/textarea.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/layout/components/IframeToggle/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 26 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/time.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/layout/components/Sidebar/Link.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 41 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/components/iFrame/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 36 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/row.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/middlewares/context_middleware.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, Request 2 | from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint 3 | from starlette.responses import Response 4 | 5 | from common.context import RequestContext 6 | 7 | 8 | class ContextCleanupMiddleware(BaseHTTPMiddleware): 9 | """ 10 | 上下文清理中间件 11 | """ 12 | 13 | async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response: 14 | """ 15 | 在每个请求处理完成后清理上下文信息 16 | """ 17 | response = await call_next(request) 18 | # 请求处理完成后清理所有上下文变量 19 | RequestContext.clear_all() 20 | return response 21 | 22 | 23 | def add_context_cleanup_middleware(app: FastAPI) -> None: 24 | """ 25 | 添加上下文清理中间件 26 | 27 | :param app: FastAPI对象 28 | """ 29 | app.add_middleware(ContextCleanupMiddleware) 30 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/utils/pwd_util.py: -------------------------------------------------------------------------------- 1 | import bcrypt 2 | 3 | 4 | class PwdUtil: 5 | """ 6 | 密码工具类 7 | """ 8 | 9 | @classmethod 10 | def verify_password(cls, plain_password: str, hashed_password: str) -> bool: 11 | """ 12 | 工具方法:校验当前输入的密码与数据库存储的密码是否一致 13 | 14 | :param plain_password: 当前输入的密码 15 | :param hashed_password: 数据库存储的密码 16 | :return: 校验结果 17 | """ 18 | return ( 19 | bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8')) if hashed_password else None 20 | ) 21 | 22 | @classmethod 23 | def get_password_hash(cls, input_password: str) -> str: 24 | """ 25 | 工具方法:对当前输入的密码进行加密 26 | 27 | :param input_password: 输入的密码 28 | :return: 加密成功的密码 29 | """ 30 | return bcrypt.hashpw(input_password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') 31 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/monitor.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/system/post.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询岗位列表 4 | export function listPost(query) { 5 | return request({ 6 | url: '/system/post/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询岗位详细 13 | export function getPost(postId) { 14 | return request({ 15 | url: '/system/post/' + postId, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 新增岗位 21 | export function addPost(data) { 22 | return request({ 23 | url: '/system/post', 24 | method: 'post', 25 | data: data 26 | }) 27 | } 28 | 29 | // 修改岗位 30 | export function updatePost(data) { 31 | return request({ 32 | url: '/system/post', 33 | method: 'put', 34 | data: data 35 | }) 36 | } 37 | 38 | // 删除岗位 39 | export function delPost(postId) { 40 | return request({ 41 | url: '/system/post/' + postId, 42 | method: 'delete' 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/tree-table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/system/notice.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询公告列表 4 | export function listNotice(query) { 5 | return request({ 6 | url: '/system/notice/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询公告详细 13 | export function getNotice(noticeId) { 14 | return request({ 15 | url: '/system/notice/' + noticeId, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 新增公告 21 | export function addNotice(data) { 22 | return request({ 23 | url: '/system/notice', 24 | method: 'post', 25 | data: data 26 | }) 27 | } 28 | 29 | // 修改公告 30 | export function updateNotice(data) { 31 | return request({ 32 | url: '/system/notice', 33 | method: 'put', 34 | data: data 35 | }) 36 | } 37 | 38 | // 删除公告 39 | export function delNotice(noticeId) { 40 | return request({ 41 | url: '/system/notice/' + noticeId, 42 | method: 'delete' 43 | }) 44 | } -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/settings.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /** 3 | * 网页标题 4 | */ 5 | title: import.meta.env.VITE_APP_TITLE, 6 | 7 | /** 8 | * 侧边栏主题 深色主题theme-dark,浅色主题theme-light 9 | */ 10 | sideTheme: 'theme-dark', 11 | 12 | /** 13 | * 是否系统布局配置 14 | */ 15 | showSettings: true, 16 | 17 | /** 18 | * 菜单导航模式 1、纯左侧 2、混合(左侧+顶部) 3、纯顶部 19 | */ 20 | navType: 1, 21 | 22 | /** 23 | * 是否显示 tagsView 24 | */ 25 | tagsView: true, 26 | 27 | /** 28 | * 显示页签图标 29 | */ 30 | tagsIcon: false, 31 | 32 | /** 33 | * 是否固定头部 34 | */ 35 | fixedHeader: true, 36 | 37 | /** 38 | * 是否显示logo 39 | */ 40 | sidebarLogo: true, 41 | 42 | /** 43 | * 是否显示动态标题 44 | */ 45 | dynamicTitle: false, 46 | 47 | /** 48 | * 是否显示底部版权 49 | */ 50 | footerVisible: false, 51 | 52 | /** 53 | * 底部版权文本内容 54 | */ 55 | footerContent: 'Copyright © 2024-2025 insistence.tech All Rights Reserved.' 56 | } 57 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/build.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/clipboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/vite/plugins/compression.js: -------------------------------------------------------------------------------- 1 | import compression from 'vite-plugin-compression' 2 | 3 | export default function createCompression(env) { 4 | const { VITE_BUILD_COMPRESS } = env 5 | const plugin = [] 6 | if (VITE_BUILD_COMPRESS) { 7 | const compressList = VITE_BUILD_COMPRESS.split(',') 8 | if (compressList.includes('gzip')) { 9 | // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 10 | plugin.push( 11 | compression({ 12 | ext: '.gz', 13 | deleteOriginFile: false 14 | }) 15 | ) 16 | } 17 | if (compressList.includes('brotli')) { 18 | plugin.push( 19 | compression({ 20 | ext: '.br', 21 | algorithm: 'brotliCompress', 22 | deleteOriginFile: false 23 | }) 24 | ) 25 | } 26 | } 27 | return plugin 28 | } 29 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/module_admin/dao/login_dao.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from sqlalchemy import Row, and_, select 4 | from sqlalchemy.ext.asyncio import AsyncSession 5 | 6 | from module_admin.entity.do.dept_do import SysDept 7 | from module_admin.entity.do.user_do import SysUser 8 | 9 | 10 | async def login_by_account(db: AsyncSession, user_name: str) -> Union[Row[tuple[SysUser, SysDept]], None]: 11 | """ 12 | 根据用户名查询用户信息 13 | 14 | :param db: orm对象 15 | :param user_name: 用户名 16 | :return: 用户对象 17 | """ 18 | user = ( 19 | await db.execute( 20 | select(SysUser, SysDept) 21 | .where(SysUser.user_name == user_name, SysUser.del_flag == '0') 22 | .join( 23 | SysDept, 24 | and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'), 25 | isouter=True, 26 | ) 27 | .distinct() 28 | ) 29 | ).first() 30 | 31 | return user 32 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/styles/transition.scss: -------------------------------------------------------------------------------- 1 | // global transition css 2 | 3 | /* fade */ 4 | .fade-enter-active, 5 | .fade-leave-active { 6 | transition: opacity 0.28s; 7 | } 8 | 9 | .fade-enter-from, 10 | .fade-leave-active { 11 | opacity: 0; 12 | } 13 | 14 | /* fade-transform */ 15 | .fade-transform--move, 16 | .fade-transform-leave-active, 17 | .fade-transform-enter-active { 18 | transition: all .5s; 19 | } 20 | 21 | .fade-transform-enter-from { 22 | opacity: 0; 23 | transform: translateX(-30px); 24 | } 25 | 26 | .fade-transform-leave-to { 27 | opacity: 0; 28 | transform: translateX(30px); 29 | } 30 | 31 | /* breadcrumb transition */ 32 | .breadcrumb-enter-active, 33 | .breadcrumb-leave-active { 34 | transition: all .5s; 35 | } 36 | 37 | .breadcrumb-enter-from, 38 | .breadcrumb-leave-active { 39 | opacity: 0; 40 | transform: translateX(20px); 41 | } 42 | 43 | .breadcrumb-move { 44 | transition: all .5s; 45 | } 46 | 47 | .breadcrumb-leave-active { 48 | position: absolute; 49 | } 50 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/list.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/download.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/module_admin/entity/vo/cache_vo.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from pydantic import BaseModel, ConfigDict, Field 4 | from pydantic.alias_generators import to_camel 5 | 6 | 7 | class CacheMonitorModel(BaseModel): 8 | """ 9 | 缓存监控信息对应pydantic模型 10 | """ 11 | 12 | model_config = ConfigDict(alias_generator=to_camel) 13 | 14 | command_stats: Optional[list] = Field(default=[], description='命令统计') 15 | db_size: Optional[int] = Field(default=None, description='Key数量') 16 | info: Optional[dict] = Field(default={}, description='Redis信息') 17 | 18 | 19 | class CacheInfoModel(BaseModel): 20 | """ 21 | 缓存监控对象对应pydantic模型 22 | """ 23 | 24 | model_config = ConfigDict(alias_generator=to_camel) 25 | 26 | cache_key: Optional[str] = Field(default=None, description='缓存键名') 27 | cache_name: Optional[str] = Field(default=None, description='缓存名称') 28 | cache_value: Optional[Any] = Field(default=None, description='缓存内容') 29 | remark: Optional[str] = Field(default=None, description='备注') 30 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/utils/generator/drawingDefault.js: -------------------------------------------------------------------------------- 1 | export const drawingDefaultValue = [] 2 | 3 | export function initDrawingDefaultValue() { 4 | if (drawingDefaultValue.length === 0) { 5 | drawingDefaultValue.push({ 6 | layout: 'colFormItem', 7 | tagIcon: 'input', 8 | label: '手机号', 9 | vModel: 'mobile', 10 | formId: 6, 11 | tag: 'el-input', 12 | placeholder: '请输入手机号', 13 | defaultValue: '', 14 | span: 24, 15 | style: {width: '100%'}, 16 | clearable: true, 17 | prepend: '', 18 | append: '', 19 | 'prefix-icon': 'Cellphone', 20 | 'suffix-icon': '', 21 | maxlength: 11, 22 | 'show-word-limit': true, 23 | readonly: false, 24 | disabled: false, 25 | required: true, 26 | changeTag: true, 27 | regList: [{ 28 | pattern: '/^1(3|4|5|7|8|9)\\d{9}$/', 29 | message: '手机号格式错误' 30 | }] 31 | }) 32 | } 33 | } 34 | 35 | export function cleanDrawingDefaultValue() { 36 | drawingDefaultValue.splice(0, drawingDefaultValue.length) 37 | } -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/system/dept.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询部门列表 4 | export function listDept(query) { 5 | return request({ 6 | url: '/system/dept/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询部门列表(排除节点) 13 | export function listDeptExcludeChild(deptId) { 14 | return request({ 15 | url: '/system/dept/list/exclude/' + deptId, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 查询部门详细 21 | export function getDept(deptId) { 22 | return request({ 23 | url: '/system/dept/' + deptId, 24 | method: 'get' 25 | }) 26 | } 27 | 28 | // 新增部门 29 | export function addDept(data) { 30 | return request({ 31 | url: '/system/dept', 32 | method: 'post', 33 | data: data 34 | }) 35 | } 36 | 37 | // 修改部门 38 | export function updateDept(data) { 39 | return request({ 40 | url: '/system/dept', 41 | method: 'put', 42 | data: data 43 | }) 44 | } 45 | 46 | // 删除部门 47 | export function delDept(deptId) { 48 | return request({ 49 | url: '/system/dept/' + deptId, 50 | method: 'delete' 51 | }) 52 | } -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/enter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/international.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 insistence 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/system/dict/data.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询字典数据列表 4 | export function listData(query) { 5 | return request({ 6 | url: '/system/dict/data/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询字典数据详细 13 | export function getData(dictCode) { 14 | return request({ 15 | url: '/system/dict/data/' + dictCode, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 根据字典类型查询字典数据信息 21 | export function getDicts(dictType) { 22 | return request({ 23 | url: '/system/dict/data/type/' + dictType, 24 | method: 'get' 25 | }) 26 | } 27 | 28 | // 新增字典数据 29 | export function addData(data) { 30 | return request({ 31 | url: '/system/dict/data', 32 | method: 'post', 33 | data: data 34 | }) 35 | } 36 | 37 | // 修改字典数据 38 | export function updateData(data) { 39 | return request({ 40 | url: '/system/dict/data', 41 | method: 'put', 42 | data: data 43 | }) 44 | } 45 | 46 | // 删除字典数据 47 | export function delData(dictCode) { 48 | return request({ 49 | url: '/system/dict/data/' + dictCode, 50 | method: 'delete' 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/more-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/question.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 RuoYi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/wechat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 36 | 37 | 54 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/skill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/module_admin/controller/server_controller.py: -------------------------------------------------------------------------------- 1 | from fastapi import Request, Response 2 | 3 | from common.aspect.interface_auth import UserInterfaceAuthDependency 4 | from common.aspect.pre_auth import PreAuthDependency 5 | from common.router import APIRouterPro 6 | from common.vo import DataResponseModel 7 | from module_admin.entity.vo.server_vo import ServerMonitorModel 8 | from module_admin.service.server_service import ServerService 9 | from utils.log_util import logger 10 | from utils.response_util import ResponseUtil 11 | 12 | server_controller = APIRouterPro( 13 | prefix='/monitor/server', order_num=14, tags=['系统监控-服务监控'], dependencies=[PreAuthDependency()] 14 | ) 15 | 16 | 17 | @server_controller.get( 18 | '', 19 | summary='获取服务器监控信息接口', 20 | description='用于获取当前服务器的监控信息', 21 | response_model=DataResponseModel[ServerMonitorModel], 22 | dependencies=[UserInterfaceAuthDependency('monitor:server:list')], 23 | ) 24 | async def get_monitor_server_info(request: Request) -> Response: 25 | # 获取全量数据 26 | server_info_query_result = await ServerService.get_server_monitor_info() 27 | logger.info('获取成功') 28 | 29 | return ResponseUtil.success(data=server_info_query_result) 30 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/people.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/login.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 登录方法 4 | export function login(username, password, code, uuid) { 5 | const data = { 6 | username, 7 | password, 8 | code, 9 | uuid 10 | } 11 | return request({ 12 | url: '/login', 13 | headers: { 14 | isToken: false, 15 | repeatSubmit: false, 16 | 'Content-Type': 'application/x-www-form-urlencoded' 17 | }, 18 | method: 'post', 19 | data: data 20 | }) 21 | } 22 | 23 | // 注册方法 24 | export function register(data) { 25 | return request({ 26 | url: '/register', 27 | headers: { 28 | isToken: false 29 | }, 30 | method: 'post', 31 | data: data 32 | }) 33 | } 34 | 35 | // 获取用户详细信息 36 | export function getInfo() { 37 | return request({ 38 | url: '/getInfo', 39 | method: 'get' 40 | }) 41 | } 42 | 43 | // 退出方法 44 | export function logout() { 45 | return request({ 46 | url: '/logout', 47 | method: 'post' 48 | }) 49 | } 50 | 51 | // 获取验证码 52 | export function getCodeImg() { 53 | return request({ 54 | url: '/captchaImage', 55 | headers: { 56 | isToken: false 57 | }, 58 | method: 'get', 59 | timeout: 20000 60 | }) 61 | } -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/post.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/system/menu.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询菜单列表 4 | export function listMenu(query) { 5 | return request({ 6 | url: '/system/menu/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询菜单详细 13 | export function getMenu(menuId) { 14 | return request({ 15 | url: '/system/menu/' + menuId, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 查询菜单下拉树结构 21 | export function treeselect() { 22 | return request({ 23 | url: '/system/menu/treeselect', 24 | method: 'get' 25 | }) 26 | } 27 | 28 | // 根据角色ID查询菜单下拉树结构 29 | export function roleMenuTreeselect(roleId) { 30 | return request({ 31 | url: '/system/menu/roleMenuTreeselect/' + roleId, 32 | method: 'get' 33 | }) 34 | } 35 | 36 | // 新增菜单 37 | export function addMenu(data) { 38 | return request({ 39 | url: '/system/menu', 40 | method: 'post', 41 | data: data 42 | }) 43 | } 44 | 45 | // 修改菜单 46 | export function updateMenu(data) { 47 | return request({ 48 | url: '/system/menu', 49 | method: 'put', 50 | data: data 51 | }) 52 | } 53 | 54 | // 删除菜单 55 | export function delMenu(menuId) { 56 | return request({ 57 | url: '/system/menu/' + menuId, 58 | method: 'delete' 59 | }) 60 | } -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/checkbox.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/language.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/module_generator/templates/js/api.js.jinja2: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询{{ functionName }}列表 4 | export function list{{ BusinessName }}(query) { 5 | return request({ 6 | url: '/{{ moduleName }}/{{ businessName }}/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询{{ functionName }}详细 13 | export function get{{ BusinessName }}({{ pkColumn.python_field }}) { 14 | return request({ 15 | url: '/{{ moduleName }}/{{ businessName }}/' + {{ pkColumn.python_field }}, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 新增{{ functionName }} 21 | export function add{{ BusinessName }}(data) { 22 | return request({ 23 | url: '/{{ moduleName }}/{{ businessName }}', 24 | method: 'post', 25 | data: data 26 | }) 27 | } 28 | 29 | // 修改{{ functionName }} 30 | export function update{{ BusinessName }}(data) { 31 | return request({ 32 | url: '/{{ moduleName }}/{{ businessName }}', 33 | method: 'put', 34 | data: data 35 | }) 36 | } 37 | 38 | // 删除{{ functionName }} 39 | export function del{{ BusinessName }}({{ pkColumn.python_field }}) { 40 | return request({ 41 | url: '/{{ moduleName }}/{{ businessName }}/' + {{ pkColumn.python_field }}, 42 | method: 'delete' 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/utils/jsencrypt.js: -------------------------------------------------------------------------------- 1 | import JSEncrypt from 'jsencrypt/bin/jsencrypt.min' 2 | 3 | // 密钥对生成 http://web.chacuo.net/netrsakeypair 4 | 5 | const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' + 6 | 'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==' 7 | 8 | const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' + 9 | '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' + 10 | 'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' + 11 | 'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' + 12 | 'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' + 13 | 'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' + 14 | 'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' + 15 | 'UP8iWi1Qw0Y=' 16 | 17 | // 加密 18 | export function encrypt(txt) { 19 | const encryptor = new JSEncrypt() 20 | encryptor.setPublicKey(publicKey) // 设置公钥 21 | return encryptor.encrypt(txt) // 对数据进行加密 22 | } 23 | 24 | // 解密 25 | export function decrypt(txt) { 26 | const encryptor = new JSEncrypt() 27 | encryptor.setPrivateKey(privateKey) // 设置私钥 28 | return encryptor.decrypt(txt) // 对数据进行解密 29 | } 30 | 31 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/config/database.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import quote_plus 2 | 3 | from sqlalchemy.ext.asyncio import AsyncAttrs, async_sessionmaker, create_async_engine 4 | from sqlalchemy.orm import DeclarativeBase 5 | 6 | from config.env import DataBaseConfig 7 | 8 | ASYNC_SQLALCHEMY_DATABASE_URL = ( 9 | f'mysql+asyncmy://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@' 10 | f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}' 11 | ) 12 | if DataBaseConfig.db_type == 'postgresql': 13 | ASYNC_SQLALCHEMY_DATABASE_URL = ( 14 | f'postgresql+asyncpg://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@' 15 | f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}' 16 | ) 17 | 18 | async_engine = create_async_engine( 19 | ASYNC_SQLALCHEMY_DATABASE_URL, 20 | echo=DataBaseConfig.db_echo, 21 | max_overflow=DataBaseConfig.db_max_overflow, 22 | pool_size=DataBaseConfig.db_pool_size, 23 | pool_recycle=DataBaseConfig.db_pool_recycle, 24 | pool_timeout=DataBaseConfig.db_pool_timeout, 25 | ) 26 | AsyncSessionLocal = async_sessionmaker(autocommit=False, autoflush=False, bind=async_engine) 27 | 28 | 29 | class Base(AsyncAttrs, DeclarativeBase): 30 | pass 31 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/system/dict/type.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询字典类型列表 4 | export function listType(query) { 5 | return request({ 6 | url: '/system/dict/type/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询字典类型详细 13 | export function getType(dictId) { 14 | return request({ 15 | url: '/system/dict/type/' + dictId, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 新增字典类型 21 | export function addType(data) { 22 | return request({ 23 | url: '/system/dict/type', 24 | method: 'post', 25 | data: data 26 | }) 27 | } 28 | 29 | // 修改字典类型 30 | export function updateType(data) { 31 | return request({ 32 | url: '/system/dict/type', 33 | method: 'put', 34 | data: data 35 | }) 36 | } 37 | 38 | // 删除字典类型 39 | export function delType(dictId) { 40 | return request({ 41 | url: '/system/dict/type/' + dictId, 42 | method: 'delete' 43 | }) 44 | } 45 | 46 | // 刷新字典缓存 47 | export function refreshCache() { 48 | return request({ 49 | url: '/system/dict/type/refreshCache', 50 | method: 'delete' 51 | }) 52 | } 53 | 54 | // 获取字典选择框列表 55 | export function optionselect() { 56 | return request({ 57 | url: '/system/dict/type/optionselect', 58 | method: 'get' 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/system/config.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询参数列表 4 | export function listConfig(query) { 5 | return request({ 6 | url: '/system/config/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询参数详细 13 | export function getConfig(configId) { 14 | return request({ 15 | url: '/system/config/' + configId, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 根据参数键名查询参数值 21 | export function getConfigKey(configKey) { 22 | return request({ 23 | url: '/system/config/configKey/' + configKey, 24 | method: 'get' 25 | }) 26 | } 27 | 28 | // 新增参数配置 29 | export function addConfig(data) { 30 | return request({ 31 | url: '/system/config', 32 | method: 'post', 33 | data: data 34 | }) 35 | } 36 | 37 | // 修改参数配置 38 | export function updateConfig(data) { 39 | return request({ 40 | url: '/system/config', 41 | method: 'put', 42 | data: data 43 | }) 44 | } 45 | 46 | // 删除参数配置 47 | export function delConfig(configId) { 48 | return request({ 49 | url: '/system/config/' + configId, 50 | method: 'delete' 51 | }) 52 | } 53 | 54 | // 刷新参数缓存 55 | export function refreshCache() { 56 | return request({ 57 | url: '/system/config/refreshCache', 58 | method: 'delete' 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/eye-open.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/views/tool/gen/createTable.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 47 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/monitor/cache.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询缓存详细 4 | export function getCache() { 5 | return request({ 6 | url: '/monitor/cache', 7 | method: 'get' 8 | }) 9 | } 10 | 11 | // 查询缓存名称列表 12 | export function listCacheName() { 13 | return request({ 14 | url: '/monitor/cache/getNames', 15 | method: 'get' 16 | }) 17 | } 18 | 19 | // 查询缓存键名列表 20 | export function listCacheKey(cacheName) { 21 | return request({ 22 | url: '/monitor/cache/getKeys/' + cacheName, 23 | method: 'get' 24 | }) 25 | } 26 | 27 | // 查询缓存内容 28 | export function getCacheValue(cacheName, cacheKey) { 29 | return request({ 30 | url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey, 31 | method: 'get' 32 | }) 33 | } 34 | 35 | // 清理指定名称缓存 36 | export function clearCacheName(cacheName) { 37 | return request({ 38 | url: '/monitor/cache/clearCacheName/' + cacheName, 39 | method: 'delete' 40 | }) 41 | } 42 | 43 | // 清理指定键名缓存 44 | export function clearCacheKey(cacheKey) { 45 | return request({ 46 | url: '/monitor/cache/clearCacheKey/' + cacheKey, 47 | method: 'delete' 48 | }) 49 | } 50 | 51 | // 清理全部缓存 52 | export function clearCacheAll() { 53 | return request({ 54 | url: '/monitor/cache/clearCacheAll', 55 | method: 'delete' 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/common/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Union 3 | 4 | 5 | class BusinessType(Enum): 6 | """ 7 | 业务操作类型 8 | 9 | OTHER: 其它 10 | INSERT: 新增 11 | UPDATE: 修改 12 | DELETE: 删除 13 | GRANT: 授权 14 | EXPORT: 导出 15 | IMPORT: 导入 16 | FORCE: 强退 17 | GENCODE: 生成代码 18 | CLEAN: 清空数据 19 | """ 20 | 21 | OTHER = 0 22 | INSERT = 1 23 | UPDATE = 2 24 | DELETE = 3 25 | GRANT = 4 26 | EXPORT = 5 27 | IMPORT = 6 28 | FORCE = 7 29 | GENCODE = 8 30 | CLEAN = 9 31 | 32 | 33 | class RedisInitKeyConfig(Enum): 34 | """ 35 | 系统内置Redis键名 36 | """ 37 | 38 | @property 39 | def key(self) -> Union[str, None]: 40 | return self.value.get('key') 41 | 42 | @property 43 | def remark(self) -> Union[str, None]: 44 | return self.value.get('remark') 45 | 46 | ACCESS_TOKEN = {'key': 'access_token', 'remark': '登录令牌信息'} 47 | SYS_DICT = {'key': 'sys_dict', 'remark': '数据字典'} 48 | SYS_CONFIG = {'key': 'sys_config', 'remark': '配置信息'} 49 | CAPTCHA_CODES = {'key': 'captcha_codes', 'remark': '图片验证码'} 50 | ACCOUNT_LOCK = {'key': 'account_lock', 'remark': '用户锁定'} 51 | PASSWORD_ERROR_COUNT = {'key': 'password_error_count', 'remark': '密码错误次数'} 52 | SMS_CODE = {'key': 'sms_code', 'remark': '短信验证码'} 53 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/validCode.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/views/dashboard/editable-link-group.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | 22 | 52 | 53 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/radio.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 30 | 31 | 43 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/select.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/upload.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/components/SizeSelect/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 38 | 39 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/module_admin/entity/do/post_do.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, String 4 | 5 | from config.database import Base 6 | from config.env import DataBaseConfig 7 | from utils.common_util import SqlalchemyUtil 8 | 9 | 10 | class SysPost(Base): 11 | """ 12 | 岗位信息表 13 | """ 14 | 15 | __tablename__ = 'sys_post' 16 | __table_args__ = {'comment': '岗位信息表'} 17 | 18 | post_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='岗位ID') 19 | post_code = Column(String(64), nullable=False, comment='岗位编码') 20 | post_name = Column(String(50), nullable=False, comment='岗位名称') 21 | post_sort = Column(Integer, nullable=False, comment='显示顺序') 22 | status = Column(CHAR(1), nullable=False, comment='状态(0正常 1停用)') 23 | create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') 24 | create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') 25 | update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') 26 | update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') 27 | remark = Column( 28 | String(500), 29 | nullable=True, 30 | server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), 31 | comment='备注', 32 | ) 33 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const useAppStore = defineStore( 4 | 'app', 5 | { 6 | state: () => ({ 7 | sidebar: { 8 | opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, 9 | withoutAnimation: false, 10 | hide: false 11 | }, 12 | device: 'desktop', 13 | size: Cookies.get('size') || 'default' 14 | }), 15 | actions: { 16 | toggleSideBar(withoutAnimation) { 17 | if (this.sidebar.hide) { 18 | return false; 19 | } 20 | this.sidebar.opened = !this.sidebar.opened 21 | this.sidebar.withoutAnimation = withoutAnimation 22 | if (this.sidebar.opened) { 23 | Cookies.set('sidebarStatus', 1) 24 | } else { 25 | Cookies.set('sidebarStatus', 0) 26 | } 27 | }, 28 | closeSideBar({ withoutAnimation }) { 29 | Cookies.set('sidebarStatus', 0) 30 | this.sidebar.opened = false 31 | this.sidebar.withoutAnimation = withoutAnimation 32 | }, 33 | toggleDevice(device) { 34 | this.device = device 35 | }, 36 | setSize(size) { 37 | this.size = size; 38 | Cookies.set('size', size) 39 | }, 40 | toggleSideBarHide(status) { 41 | this.sidebar.hide = status 42 | } 43 | } 44 | }) 45 | 46 | export default useAppStore 47 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/sunny.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/monitor/job.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询定时任务调度列表 4 | export function listJob(query) { 5 | return request({ 6 | url: '/monitor/job/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询定时任务调度详细 13 | export function getJob(jobId) { 14 | return request({ 15 | url: '/monitor/job/' + jobId, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 新增定时任务调度 21 | export function addJob(data) { 22 | return request({ 23 | url: '/monitor/job', 24 | method: 'post', 25 | data: data 26 | }) 27 | } 28 | 29 | // 修改定时任务调度 30 | export function updateJob(data) { 31 | return request({ 32 | url: '/monitor/job', 33 | method: 'put', 34 | data: data 35 | }) 36 | } 37 | 38 | // 删除定时任务调度 39 | export function delJob(jobId) { 40 | return request({ 41 | url: '/monitor/job/' + jobId, 42 | method: 'delete' 43 | }) 44 | } 45 | 46 | // 任务状态修改 47 | export function changeJobStatus(jobId, status) { 48 | const data = { 49 | jobId, 50 | status 51 | } 52 | return request({ 53 | url: '/monitor/job/changeStatus', 54 | method: 'put', 55 | data: data 56 | }) 57 | } 58 | 59 | 60 | // 定时任务立即执行一次 61 | export function runJob(jobId, jobGroup) { 62 | const data = { 63 | jobId, 64 | jobGroup 65 | } 66 | return request({ 67 | url: '/monitor/job/run', 68 | method: 'put', 69 | data: data 70 | }) 71 | } -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/404.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/middlewares/trace_middleware/middle.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | from starlette.types import ASGIApp, Message, Receive, Scope, Send 4 | 5 | from .span import Span, get_current_span 6 | 7 | 8 | class TraceASGIMiddleware: 9 | """ 10 | fastapi-example: 11 | app = FastAPI() 12 | app.add_middleware(TraceASGIMiddleware) 13 | """ 14 | 15 | def __init__(self, app: ASGIApp) -> None: 16 | self.app = app 17 | 18 | @staticmethod 19 | async def my_receive(receive: Receive, span: Span) -> Receive: 20 | await span.request_before() 21 | 22 | @wraps(receive) 23 | async def my_receive() -> Message: 24 | message = await receive() 25 | await span.request_after(message) 26 | return message 27 | 28 | return my_receive 29 | 30 | async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: 31 | if scope['type'] != 'http': 32 | await self.app(scope, receive, send) 33 | return 34 | 35 | async with get_current_span(scope) as span: 36 | handle_outgoing_receive = await self.my_receive(receive, span) 37 | 38 | async def handle_outgoing_request(message: 'Message') -> None: 39 | await span.response(message) 40 | await send(message) 41 | 42 | await self.app(scope, handle_outgoing_receive, handle_outgoing_request) 43 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/store/modules/dict.js: -------------------------------------------------------------------------------- 1 | const useDictStore = defineStore( 2 | 'dict', 3 | { 4 | state: () => ({ 5 | dict: new Array() 6 | }), 7 | actions: { 8 | // 获取字典 9 | getDict(_key) { 10 | if (_key == null && _key == "") { 11 | return null; 12 | } 13 | try { 14 | for (let i = 0; i < this.dict.length; i++) { 15 | if (this.dict[i].key == _key) { 16 | return this.dict[i].value; 17 | } 18 | } 19 | } catch (e) { 20 | return null; 21 | } 22 | }, 23 | // 设置字典 24 | setDict(_key, value) { 25 | if (_key !== null && _key !== "") { 26 | this.dict.push({ 27 | key: _key, 28 | value: value 29 | }); 30 | } 31 | }, 32 | // 删除字典 33 | removeDict(_key) { 34 | var bln = false; 35 | try { 36 | for (let i = 0; i < this.dict.length; i++) { 37 | if (this.dict[i].key == _key) { 38 | this.dict.splice(i, 1); 39 | return true; 40 | } 41 | } 42 | } catch (e) { 43 | bln = false; 44 | } 45 | return bln; 46 | }, 47 | // 清空字典 48 | cleanDict() { 49 | this.dict = new Array(); 50 | }, 51 | // 初始字典 52 | initDict() { 53 | } 54 | } 55 | }) 56 | 57 | export default useDictStore 58 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/zip.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/.env.dev: -------------------------------------------------------------------------------- 1 | # -------- 应用配置 -------- 2 | # 应用运行环境 3 | APP_ENV = 'dev' 4 | # 应用名称 5 | APP_NAME = 'RuoYi-FastAPI' 6 | # 应用代理路径 7 | APP_ROOT_PATH = '/dev-api' 8 | # 应用主机 9 | APP_HOST = '0.0.0.0' 10 | # 应用端口 11 | APP_PORT = 9099 12 | # 应用版本 13 | APP_VERSION= '1.8.0' 14 | # 应用是否开启热重载 15 | APP_RELOAD = true 16 | # 应用是否开启IP归属区域查询 17 | APP_IP_LOCATION_QUERY = true 18 | # 应用是否允许账号同时登录 19 | APP_SAME_TIME_LOGIN = true 20 | 21 | # -------- Jwt配置 -------- 22 | # Jwt秘钥 23 | JWT_SECRET_KEY = 'b01c66dc2c58dc6a0aabfe2144256be36226de378bf87f72c0c795dda67f4d55' 24 | # Jwt算法 25 | JWT_ALGORITHM = 'HS256' 26 | # 令牌过期时间 27 | JWT_EXPIRE_MINUTES = 1440 28 | # redis中令牌过期时间 29 | JWT_REDIS_EXPIRE_MINUTES = 30 30 | 31 | 32 | # -------- 数据库配置 -------- 33 | # 数据库类型,可选的有'mysql'、'postgresql',默认为'mysql' 34 | DB_TYPE = 'mysql' 35 | # 数据库主机 36 | DB_HOST = '127.0.0.1' 37 | # 数据库端口 38 | DB_PORT = 3306 39 | # 数据库用户名 40 | DB_USERNAME = 'root' 41 | # 数据库密码 42 | DB_PASSWORD = 'mysqlroot' 43 | # 数据库名称 44 | DB_DATABASE = 'ruoyi-fastapi' 45 | # 是否开启sqlalchemy日志 46 | DB_ECHO = true 47 | # 允许溢出连接池大小的最大连接数 48 | DB_MAX_OVERFLOW = 10 49 | # 连接池大小,0表示连接数无限制 50 | DB_POOL_SIZE = 50 51 | # 连接回收时间(单位:秒) 52 | DB_POOL_RECYCLE = 3600 53 | # 连接池中没有线程可用时,最多等待的时间(单位:秒) 54 | DB_POOL_TIMEOUT = 30 55 | 56 | # -------- Redis配置 -------- 57 | # Redis主机 58 | REDIS_HOST = '127.0.0.1' 59 | # Redis端口 60 | REDIS_PORT = 6379 61 | # Redis用户名 62 | REDIS_USERNAME = '' 63 | # Redis密码 64 | REDIS_PASSWORD = '' 65 | # Redis数据库 66 | REDIS_DATABASE = 2 -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/.env.prod: -------------------------------------------------------------------------------- 1 | # -------- 应用配置 -------- 2 | # 应用运行环境 3 | APP_ENV = 'prod' 4 | # 应用名称 5 | APP_NAME = 'RuoYi-FastAPI' 6 | # 应用代理路径 7 | APP_ROOT_PATH = '/prod-api' 8 | # 应用主机 9 | APP_HOST = '0.0.0.0' 10 | # 应用端口 11 | APP_PORT = 9099 12 | # 应用版本 13 | APP_VERSION= '1.8.0' 14 | # 应用是否开启热重载 15 | APP_RELOAD = false 16 | # 应用是否开启IP归属区域查询 17 | APP_IP_LOCATION_QUERY = true 18 | # 应用是否允许账号同时登录 19 | APP_SAME_TIME_LOGIN = true 20 | 21 | # -------- Jwt配置 -------- 22 | # Jwt秘钥 23 | JWT_SECRET_KEY = 'b01c66dc2c58dc6a0aabfe2144256be36226de378bf87f72c0c795dda67f4d55' 24 | # Jwt算法 25 | JWT_ALGORITHM = 'HS256' 26 | # 令牌过期时间 27 | JWT_EXPIRE_MINUTES = 1440 28 | # redis中令牌过期时间 29 | JWT_REDIS_EXPIRE_MINUTES = 30 30 | 31 | 32 | # -------- 数据库配置 -------- 33 | # 数据库类型,可选的有'mysql'、'postgresql',默认为'mysql' 34 | DB_TYPE = 'mysql' 35 | # 数据库主机 36 | DB_HOST = '127.0.0.1' 37 | # 数据库端口 38 | DB_PORT = 3306 39 | # 数据库用户名 40 | DB_USERNAME = 'root' 41 | # 数据库密码 42 | DB_PASSWORD = 'root' 43 | # 数据库名称 44 | DB_DATABASE = 'ruoyi-fastapi' 45 | # 是否开启sqlalchemy日志 46 | DB_ECHO = true 47 | # 允许溢出连接池大小的最大连接数 48 | DB_MAX_OVERFLOW = 10 49 | # 连接池大小,0表示连接数无限制 50 | DB_POOL_SIZE = 50 51 | # 连接回收时间(单位:秒) 52 | DB_POOL_RECYCLE = 3600 53 | # 连接池中没有线程可用时,最多等待的时间(单位:秒) 54 | DB_POOL_TIMEOUT = 30 55 | 56 | # -------- Redis配置 -------- 57 | # Redis主机 58 | REDIS_HOST = '127.0.0.1' 59 | # Redis端口 60 | REDIS_PORT = 6379 61 | # Redis用户名 62 | REDIS_USERNAME = '' 63 | # Redis密码 64 | REDIS_PASSWORD = '' 65 | # Redis数据库 66 | REDIS_DATABASE = 2 -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/utils/permission.js: -------------------------------------------------------------------------------- 1 | import useUserStore from '@/store/modules/user' 2 | 3 | /** 4 | * 字符权限校验 5 | * @param {Array} value 校验值 6 | * @returns {Boolean} 7 | */ 8 | export function checkPermi(value) { 9 | if (value && value instanceof Array && value.length > 0) { 10 | const permissions = useUserStore().permissions 11 | const permissionDatas = value 12 | const all_permission = "*:*:*"; 13 | 14 | const hasPermission = permissions.some(permission => { 15 | return all_permission === permission || permissionDatas.includes(permission) 16 | }) 17 | 18 | if (!hasPermission) { 19 | return false 20 | } 21 | return true 22 | } else { 23 | console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`) 24 | return false 25 | } 26 | } 27 | 28 | /** 29 | * 角色权限校验 30 | * @param {Array} value 校验值 31 | * @returns {Boolean} 32 | */ 33 | export function checkRole(value) { 34 | if (value && value instanceof Array && value.length > 0) { 35 | const roles = useUserStore().roles 36 | const permissionRoles = value 37 | const super_admin = "admin"; 38 | 39 | const hasRole = roles.some(role => { 40 | return super_admin === role || permissionRoles.includes(role) 41 | }) 42 | 43 | if (!hasRole) { 44 | return false 45 | } 46 | return true 47 | } else { 48 | console.error(`need roles! Like checkRole="['admin','editor']"`) 49 | return false 50 | } 51 | } -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/.env.dockermy: -------------------------------------------------------------------------------- 1 | # -------- 应用配置 -------- 2 | # 应用运行环境 3 | APP_ENV = 'prod' 4 | # 应用名称 5 | APP_NAME = 'RuoYi-FastAPI' 6 | # 应用代理路径 7 | APP_ROOT_PATH = '/docker-api' 8 | # 应用主机 9 | APP_HOST = '0.0.0.0' 10 | # 应用端口 11 | APP_PORT = 9099 12 | # 应用版本 13 | APP_VERSION= '1.8.0' 14 | # 应用是否开启热重载 15 | APP_RELOAD = false 16 | # 应用是否开启IP归属区域查询 17 | APP_IP_LOCATION_QUERY = true 18 | # 应用是否允许账号同时登录 19 | APP_SAME_TIME_LOGIN = true 20 | 21 | # -------- Jwt配置 -------- 22 | # Jwt秘钥 23 | JWT_SECRET_KEY = 'b01c66dc2c58dc6a0aabfe2144256be36226de378bf87f72c0c795dda67f4d55' 24 | # Jwt算法 25 | JWT_ALGORITHM = 'HS256' 26 | # 令牌过期时间 27 | JWT_EXPIRE_MINUTES = 1440 28 | # redis中令牌过期时间 29 | JWT_REDIS_EXPIRE_MINUTES = 30 30 | 31 | 32 | # -------- 数据库配置 -------- 33 | # 数据库类型,可选的有'mysql'、'postgresql',默认为'mysql' 34 | DB_TYPE = 'mysql' 35 | # 数据库主机 36 | DB_HOST = 'ruoyi-mysql' 37 | # 数据库端口 38 | DB_PORT = 3306 39 | # 数据库用户名 40 | DB_USERNAME = 'root' 41 | # 数据库密码 42 | DB_PASSWORD = 'root' 43 | # 数据库名称 44 | DB_DATABASE = 'ruoyi-fastapi' 45 | # 是否开启sqlalchemy日志 46 | DB_ECHO = true 47 | # 允许溢出连接池大小的最大连接数 48 | DB_MAX_OVERFLOW = 10 49 | # 连接池大小,0表示连接数无限制 50 | DB_POOL_SIZE = 50 51 | # 连接回收时间(单位:秒) 52 | DB_POOL_RECYCLE = 3600 53 | # 连接池中没有线程可用时,最多等待的时间(单位:秒) 54 | DB_POOL_TIMEOUT = 30 55 | 56 | # -------- Redis配置 -------- 57 | # Redis主机 58 | REDIS_HOST = 'ruoyi-redis' 59 | # Redis端口 60 | REDIS_PORT = 6379 61 | # Redis用户名 62 | REDIS_USERNAME = '' 63 | # Redis密码 64 | REDIS_PASSWORD = '' 65 | # Redis数据库 66 | REDIS_DATABASE = 2 -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/.env.dockerpg: -------------------------------------------------------------------------------- 1 | # -------- 应用配置 -------- 2 | # 应用运行环境 3 | APP_ENV = 'prod' 4 | # 应用名称 5 | APP_NAME = 'RuoYi-FastAPI' 6 | # 应用代理路径 7 | APP_ROOT_PATH = '/docker-api' 8 | # 应用主机 9 | APP_HOST = '0.0.0.0' 10 | # 应用端口 11 | APP_PORT = 9099 12 | # 应用版本 13 | APP_VERSION= '1.8.0' 14 | # 应用是否开启热重载 15 | APP_RELOAD = false 16 | # 应用是否开启IP归属区域查询 17 | APP_IP_LOCATION_QUERY = true 18 | # 应用是否允许账号同时登录 19 | APP_SAME_TIME_LOGIN = true 20 | 21 | # -------- Jwt配置 -------- 22 | # Jwt秘钥 23 | JWT_SECRET_KEY = 'b01c66dc2c58dc6a0aabfe2144256be36226de378bf87f72c0c795dda67f4d55' 24 | # Jwt算法 25 | JWT_ALGORITHM = 'HS256' 26 | # 令牌过期时间 27 | JWT_EXPIRE_MINUTES = 1440 28 | # redis中令牌过期时间 29 | JWT_REDIS_EXPIRE_MINUTES = 30 30 | 31 | 32 | # -------- 数据库配置 -------- 33 | # 数据库类型,可选的有'mysql'、'postgresql',默认为'mysql' 34 | DB_TYPE = 'postgresql' 35 | # 数据库主机 36 | DB_HOST = 'ruoyi-pg' 37 | # 数据库端口 38 | DB_PORT = 5432 39 | # 数据库用户名 40 | DB_USERNAME = 'postgres' 41 | # 数据库密码 42 | DB_PASSWORD = 'root' 43 | # 数据库名称 44 | DB_DATABASE = 'ruoyi-fastapi' 45 | # 是否开启sqlalchemy日志 46 | DB_ECHO = true 47 | # 允许溢出连接池大小的最大连接数 48 | DB_MAX_OVERFLOW = 10 49 | # 连接池大小,0表示连接数无限制 50 | DB_POOL_SIZE = 50 51 | # 连接回收时间(单位:秒) 52 | DB_POOL_RECYCLE = 3600 53 | # 连接池中没有线程可用时,最多等待的时间(单位:秒) 54 | DB_POOL_TIMEOUT = 30 55 | 56 | # -------- Redis配置 -------- 57 | # Redis主机 58 | REDIS_HOST = 'ruoyi-redis' 59 | # Redis端口 60 | REDIS_PORT = 6379 61 | # Redis用户名 62 | REDIS_USERNAME = '' 63 | # Redis密码 64 | REDIS_PASSWORD = '' 65 | # Redis数据库 66 | REDIS_DATABASE = 2 -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/module_admin/entity/do/config_do.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from sqlalchemy import CHAR, Column, DateTime, Integer, String 4 | 5 | from config.database import Base 6 | from config.env import DataBaseConfig 7 | from utils.common_util import SqlalchemyUtil 8 | 9 | 10 | class SysConfig(Base): 11 | """ 12 | 参数配置表 13 | """ 14 | 15 | __tablename__ = 'sys_config' 16 | __table_args__ = {'comment': '参数配置表'} 17 | 18 | config_id = Column(Integer, primary_key=True, nullable=False, autoincrement=True, comment='参数主键') 19 | config_name = Column(String(100), nullable=True, server_default="''", comment='参数名称') 20 | config_key = Column(String(100), nullable=True, server_default="''", comment='参数键名') 21 | config_value = Column(String(500), nullable=True, server_default="''", comment='参数键值') 22 | config_type = Column(CHAR(1), nullable=True, server_default='N', comment='系统内置(Y是 N否)') 23 | create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') 24 | create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') 25 | update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') 26 | update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') 27 | remark = Column( 28 | String(500), 29 | nullable=True, 30 | server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), 31 | comment='备注', 32 | ) 33 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/utils/theme.js: -------------------------------------------------------------------------------- 1 | // 处理主题样式 2 | export function handleThemeStyle(theme) { 3 | document.documentElement.style.setProperty('--el-color-primary', theme) 4 | for (let i = 1; i <= 9; i++) { 5 | document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(theme, i / 10)}`) 6 | } 7 | for (let i = 1; i <= 9; i++) { 8 | document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, `${getDarkColor(theme, i / 10)}`) 9 | } 10 | } 11 | 12 | // hex颜色转rgb颜色 13 | export function hexToRgb(str) { 14 | str = str.replace('#', '') 15 | let hexs = str.match(/../g) 16 | for (let i = 0; i < 3; i++) { 17 | hexs[i] = parseInt(hexs[i], 16) 18 | } 19 | return hexs 20 | } 21 | 22 | // rgb颜色转Hex颜色 23 | export function rgbToHex(r, g, b) { 24 | let hexs = [r.toString(16), g.toString(16), b.toString(16)] 25 | for (let i = 0; i < 3; i++) { 26 | if (hexs[i].length == 1) { 27 | hexs[i] = `0${hexs[i]}` 28 | } 29 | } 30 | return `#${hexs.join('')}` 31 | } 32 | 33 | // 变浅颜色值 34 | export function getLightColor(color, level) { 35 | let rgb = hexToRgb(color) 36 | for (let i = 0; i < 3; i++) { 37 | rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i]) 38 | } 39 | return rgbToHex(rgb[0], rgb[1], rgb[2]) 40 | } 41 | 42 | // 变深颜色值 43 | export function getDarkColor(color, level) { 44 | let rgb = hexToRgb(color) 45 | for (let i = 0; i < 3; i++) { 46 | rgb[i] = Math.floor(rgb[i] * (1 - level)) 47 | } 48 | return rgbToHex(rgb[0], rgb[1], rgb[2]) 49 | } 50 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/phone.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/log.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/plugins/auth.js: -------------------------------------------------------------------------------- 1 | import useUserStore from '@/store/modules/user' 2 | 3 | function authPermission(permission) { 4 | const all_permission = "*:*:*"; 5 | const permissions = useUserStore().permissions 6 | if (permission && permission.length > 0) { 7 | return permissions.some(v => { 8 | return all_permission === v || v === permission 9 | }) 10 | } else { 11 | return false 12 | } 13 | } 14 | 15 | function authRole(role) { 16 | const super_admin = "admin"; 17 | const roles = useUserStore().roles 18 | if (role && role.length > 0) { 19 | return roles.some(v => { 20 | return super_admin === v || v === role 21 | }) 22 | } else { 23 | return false 24 | } 25 | } 26 | 27 | export default { 28 | // 验证用户是否具备某权限 29 | hasPermi(permission) { 30 | return authPermission(permission); 31 | }, 32 | // 验证用户是否含有指定权限,只需包含其中一个 33 | hasPermiOr(permissions) { 34 | return permissions.some(item => { 35 | return authPermission(item) 36 | }) 37 | }, 38 | // 验证用户是否含有指定权限,必须全部拥有 39 | hasPermiAnd(permissions) { 40 | return permissions.every(item => { 41 | return authPermission(item) 42 | }) 43 | }, 44 | // 验证用户是否具备某角色 45 | hasRole(role) { 46 | return authRole(role); 47 | }, 48 | // 验证用户是否含有指定角色,只需包含其中一个 49 | hasRoleOr(roles) { 50 | return roles.some(item => { 51 | return authRole(item) 52 | }) 53 | }, 54 | // 验证用户是否含有指定角色,必须全部拥有 55 | hasRoleAnd(roles) { 56 | return roles.every(item => { 57 | return authRole(item) 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/bug.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/module_admin/service/captcha_service.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import io 3 | import os 4 | import random 5 | 6 | from PIL import Image, ImageDraw, ImageFont 7 | 8 | 9 | class CaptchaService: 10 | """ 11 | 验证码模块服务层 12 | """ 13 | 14 | @classmethod 15 | async def create_captcha_image_service(cls) -> list[str, int]: 16 | # 创建空白图像 17 | image = Image.new('RGB', (160, 60), color='#EAEAEA') 18 | 19 | # 创建绘图对象 20 | draw = ImageDraw.Draw(image) 21 | 22 | # 设置字体 23 | font = ImageFont.truetype(os.path.join(os.path.abspath(os.getcwd()), 'assets', 'font', 'Arial.ttf'), size=30) 24 | 25 | # 生成两个0-9之间的随机整数 26 | num1 = random.randint(0, 9) 27 | num2 = random.randint(0, 9) 28 | # 从运算符列表中随机选择一个 29 | operational_character_list = ['+', '-', '*'] 30 | operational_character = random.choice(operational_character_list) 31 | # 根据选择的运算符进行计算 32 | if operational_character == '+': 33 | result = num1 + num2 34 | elif operational_character == '-': 35 | result = num1 - num2 36 | else: 37 | result = num1 * num2 38 | # 绘制文本 39 | text = f'{num1} {operational_character} {num2} = ?' 40 | draw.text((25, 15), text, fill='blue', font=font) 41 | 42 | # 将图像数据保存到内存中 43 | buffer = io.BytesIO() 44 | image.save(buffer, format='PNG') 45 | 46 | # 将图像数据转换为base64字符串 47 | base64_string = base64.b64encode(buffer.getvalue()).decode() 48 | 49 | return [base64_string, result] 50 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix { 2 | &:after { 3 | content: ""; 4 | display: table; 5 | clear: both; 6 | } 7 | } 8 | 9 | @mixin scrollBar { 10 | &::-webkit-scrollbar-track-piece { 11 | background: #d3dce6; 12 | } 13 | 14 | &::-webkit-scrollbar { 15 | width: 6px; 16 | } 17 | 18 | &::-webkit-scrollbar-thumb { 19 | background: #99a9bf; 20 | border-radius: 20px; 21 | } 22 | } 23 | 24 | @mixin relative { 25 | position: relative; 26 | width: 100%; 27 | height: 100%; 28 | } 29 | 30 | @mixin pct($pct) { 31 | width: #{$pct}; 32 | position: relative; 33 | margin: 0 auto; 34 | } 35 | 36 | @mixin triangle($width, $height, $color, $direction) { 37 | $width: $width/2; 38 | $color-border-style: $height solid $color; 39 | $transparent-border-style: $width solid transparent; 40 | height: 0; 41 | width: 0; 42 | 43 | @if $direction==up { 44 | border-bottom: $color-border-style; 45 | border-left: $transparent-border-style; 46 | border-right: $transparent-border-style; 47 | } 48 | 49 | @else if $direction==right { 50 | border-left: $color-border-style; 51 | border-top: $transparent-border-style; 52 | border-bottom: $transparent-border-style; 53 | } 54 | 55 | @else if $direction==down { 56 | border-top: $color-border-style; 57 | border-left: $transparent-border-style; 58 | border-right: $transparent-border-style; 59 | } 60 | 61 | @else if $direction==left { 62 | border-right: $color-border-style; 63 | border-top: $transparent-border-style; 64 | border-bottom: $transparent-border-style; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/middlewares/trace_middleware/span.py: -------------------------------------------------------------------------------- 1 | from collections.abc import AsyncGenerator 2 | from contextlib import asynccontextmanager 3 | 4 | from starlette.types import Message, Scope 5 | 6 | from .ctx import TraceCtx 7 | 8 | 9 | class Span: 10 | """ 11 | 整个http生命周期: 12 | request(before) --> request(after) --> response(before) --> response(after) 13 | """ 14 | 15 | def __init__(self, scope: Scope) -> None: 16 | self.scope = scope 17 | 18 | async def request_before(self) -> None: 19 | """ 20 | request_before: 处理header信息等, 如记录请求体信息 21 | """ 22 | TraceCtx.set_id() 23 | 24 | async def request_after(self, message: Message) -> Message: 25 | """ 26 | request_after: 处理请求bytes, 如记录请求参数 27 | 28 | example: 29 | message: {'type': 'http.request', 'body': b'{\r\n "name": "\xe8\x8b\x8f\xe8\x8b\x8f\xe8\x8b\x8f"\r\n}', 'more_body': False} 30 | """ 31 | return message 32 | 33 | async def response(self, message: Message) -> Message: 34 | """ 35 | if message['type'] == "http.response.start": -----> request-before 36 | pass 37 | if message['type'] == "http.response.body": -----> request-after 38 | message.get('body', b'') 39 | pass 40 | """ 41 | if message['type'] == 'http.response.start': 42 | message['headers'].append((b'request-id', TraceCtx.get_id().encode())) 43 | return message 44 | 45 | 46 | @asynccontextmanager 47 | async def get_current_span(scope: Scope) -> AsyncGenerator[Span, None]: 48 | yield Span(scope) 49 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/utils/dependency_util.py: -------------------------------------------------------------------------------- 1 | from fastapi import Request 2 | 3 | from common.context import RequestContext 4 | from config.env import AppConfig 5 | from exceptions.exception import PermissionException 6 | 7 | 8 | class DependencyUtil: 9 | """ 10 | 依赖项工具类 11 | """ 12 | 13 | @classmethod 14 | def check_exclude_routes(cls, request: Request, err_msg: str = '当前路由不在认证规则内,不可使用该依赖项') -> None: 15 | """ 16 | 检查路径和方法是否匹配排除路由模式 17 | 18 | :param request: 请求对象 19 | :param err_msg: 错误信息 20 | :return: None 21 | """ 22 | # 获取当前请求路径和方法 23 | path = request.url.path 24 | method = request.method.upper() 25 | 26 | # 从配置中获取APP_ROOT_PATH 27 | app_root_path = AppConfig.app_root_path 28 | 29 | # 去掉APP_ROOT_PATH前缀 30 | if app_root_path and path.startswith(app_root_path): 31 | path = path[len(app_root_path) :] 32 | 33 | # 获取编译后的排除路由模式列表 34 | exclude_patterns = RequestContext.get_current_exclude_patterns() 35 | 36 | # 检查当前路由是否在排除路由列表中 37 | if path and method and exclude_patterns: 38 | for item in exclude_patterns: 39 | pattern = item['pattern'] 40 | exclude_methods = item['methods'] 41 | ignore_paths = item['ignore_paths'] 42 | 43 | # 检查当前路径是否在忽略列表中 44 | if path in ignore_paths: 45 | continue 46 | 47 | # 检查路径是否匹配,并且methods为空列表(匹配所有方法)或者当前方法在允许列表中 48 | if pattern.match(path) and (not exclude_methods or method in exclude_methods): 49 | raise PermissionException(data='', message=err_msg) 50 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vfadmin", 3 | "version": "1.8.0", 4 | "description": "vfadmin管理系统", 5 | "author": "insistence", 6 | "license": "MIT", 7 | "type": "module", 8 | "scripts": { 9 | "dev": "vite", 10 | "build:prod": "vite build", 11 | "build:docker": "vite build --mode docker", 12 | "build:stage": "vite build --mode staging", 13 | "preview": "vite preview" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI.git" 18 | }, 19 | "dependencies": { 20 | "@ant-design/icons-vue": "^7.0.1", 21 | "@antv/g2plot": "^2.4.31", 22 | "@element-plus/icons-vue": "2.3.1", 23 | "@vueup/vue-quill": "1.2.0", 24 | "@vueuse/core": "13.3.0", 25 | "ant-design-vue": "^4.1.1", 26 | "axios": "1.9.0", 27 | "clipboard": "2.0.11", 28 | "echarts": "5.6.0", 29 | "element-plus": "2.10.7", 30 | "file-saver": "2.0.5", 31 | "fuse.js": "6.6.2", 32 | "js-beautify": "1.15.1", 33 | "js-cookie": "3.0.5", 34 | "jsencrypt": "3.3.2", 35 | "nprogress": "0.2.0", 36 | "pinia": "3.0.2", 37 | "splitpanes": "4.0.4", 38 | "vue": "3.5.16", 39 | "vue-cropper": "1.1.1", 40 | "vue-router": "4.5.1", 41 | "vuedraggable": "4.1.0" 42 | }, 43 | "devDependencies": { 44 | "@vitejs/plugin-vue": "5.2.4", 45 | "less": "^4.2.0", 46 | "sass-embedded": "1.89.1", 47 | "unplugin-auto-import": "0.18.6", 48 | "unplugin-vue-setup-extend-plus": "1.0.1", 49 | "vite": "6.3.5", 50 | "vite-plugin-compression": "0.5.1", 51 | "vite-plugin-svg-icons": "2.0.1" 52 | }, 53 | "overrides": { 54 | "quill": "2.0.2" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/exceptions/exception.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | 4 | class LoginException(Exception): 5 | """ 6 | 自定义登录异常LoginException 7 | """ 8 | 9 | def __init__(self, data: Optional[str] = None, message: Optional[str] = None) -> None: 10 | self.data = data 11 | self.message = message 12 | 13 | 14 | class AuthException(Exception): 15 | """ 16 | 自定义令牌异常AuthException 17 | """ 18 | 19 | def __init__(self, data: Optional[str] = None, message: Optional[str] = None) -> None: 20 | self.data = data 21 | self.message = message 22 | 23 | 24 | class PermissionException(Exception): 25 | """ 26 | 自定义权限异常PermissionException 27 | """ 28 | 29 | def __init__(self, data: Optional[str] = None, message: Optional[str] = None) -> None: 30 | self.data = data 31 | self.message = message 32 | 33 | 34 | class ServiceException(Exception): 35 | """ 36 | 自定义服务异常ServiceException 37 | """ 38 | 39 | def __init__(self, data: Optional[str] = None, message: Optional[str] = None) -> None: 40 | self.data = data 41 | self.message = message 42 | 43 | 44 | class ServiceWarning(Exception): 45 | """ 46 | 自定义服务警告ServiceWarning 47 | """ 48 | 49 | def __init__(self, data: Optional[str] = None, message: Optional[str] = None) -> None: 50 | self.data = data 51 | self.message = message 52 | 53 | 54 | class ModelValidatorException(Exception): 55 | """ 56 | 自定义模型校验异常ModelValidatorException 57 | """ 58 | 59 | def __init__(self, data: Optional[str] = None, message: Optional[str] = None) -> None: 60 | self.data = data 61 | self.message = message 62 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from sqlalchemy import CHAR, Column, DateTime, Integer, LargeBinary, String 4 | from sqlalchemy.dialects import mysql 5 | 6 | from config.database import Base 7 | from config.env import DataBaseConfig 8 | from utils.common_util import SqlalchemyUtil 9 | 10 | 11 | class SysNotice(Base): 12 | """ 13 | 通知公告表 14 | """ 15 | 16 | __tablename__ = 'sys_notice' 17 | __table_args__ = {'comment': '通知公告表'} 18 | 19 | notice_id = Column(Integer, primary_key=True, nullable=False, autoincrement=True, comment='公告ID') 20 | notice_title = Column(String(50), nullable=False, comment='公告标题') 21 | notice_type = Column(CHAR(1), nullable=False, comment='公告类型(1通知 2公告)') 22 | notice_content = Column( 23 | mysql.LONGBLOB if DataBaseConfig.db_type == 'mysql' else LargeBinary, 24 | nullable=True, 25 | server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type, False), 26 | comment='公告内容', 27 | ) 28 | status = Column(CHAR(1), nullable=True, server_default='0', comment='公告状态(0正常 1关闭)') 29 | create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') 30 | create_time = Column(DateTime, nullable=True, comment='创建时间', default=datetime.now()) 31 | update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') 32 | update_time = Column(DateTime, nullable=True, comment='更新时间', default=datetime.now()) 33 | remark = Column( 34 | String(255), 35 | nullable=True, 36 | server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), 37 | comment='备注', 38 | ) 39 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/pdf.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/logininfor.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/rate.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/module_admin/entity/vo/online_vo.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional 3 | 4 | from pydantic import BaseModel, ConfigDict, Field 5 | from pydantic.alias_generators import to_camel 6 | 7 | 8 | class OnlineModel(BaseModel): 9 | """ 10 | 在线用户对应pydantic模型 11 | """ 12 | 13 | model_config = ConfigDict(alias_generator=to_camel) 14 | 15 | token_id: Optional[str] = Field(default=None, description='会话编号') 16 | user_name: Optional[str] = Field(default=None, description='登录名称') 17 | dept_name: Optional[str] = Field(default=None, description='所属部门') 18 | ipaddr: Optional[str] = Field(default=None, description='主机') 19 | login_location: Optional[str] = Field(default=None, description='登录地点') 20 | browser: Optional[str] = Field(default=None, description='浏览器类型') 21 | os: Optional[str] = Field(default=None, description='操作系统') 22 | login_time: Optional[datetime] = Field(default=None, description='登录时间') 23 | 24 | 25 | class OnlineQueryModel(OnlineModel): 26 | """ 27 | 岗位管理不分页查询模型 28 | """ 29 | 30 | begin_time: Optional[str] = Field(default=None, description='开始时间') 31 | end_time: Optional[str] = Field(default=None, description='结束时间') 32 | 33 | 34 | class OnlinePageResponseModel(BaseModel): 35 | """ 36 | 在线用户分页响应模型 37 | """ 38 | 39 | model_config = ConfigDict(alias_generator=to_camel) 40 | 41 | rows: list[OnlineModel] = Field(description='在线用户记录列表') 42 | total: int = Field(description='总记录数') 43 | 44 | 45 | class DeleteOnlineModel(BaseModel): 46 | """ 47 | 强退在线用户模型 48 | """ 49 | 50 | model_config = ConfigDict(alias_generator=to_camel) 51 | 52 | token_ids: str = Field(description='需要强退的会话编号') 53 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/job.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/plugins/cache.js: -------------------------------------------------------------------------------- 1 | const sessionCache = { 2 | set (key, value) { 3 | if (!sessionStorage) { 4 | return 5 | } 6 | if (key != null && value != null) { 7 | sessionStorage.setItem(key, value) 8 | } 9 | }, 10 | get (key) { 11 | if (!sessionStorage) { 12 | return null 13 | } 14 | if (key == null) { 15 | return null 16 | } 17 | return sessionStorage.getItem(key) 18 | }, 19 | setJSON (key, jsonValue) { 20 | if (jsonValue != null) { 21 | this.set(key, JSON.stringify(jsonValue)) 22 | } 23 | }, 24 | getJSON (key) { 25 | const value = this.get(key) 26 | if (value != null) { 27 | return JSON.parse(value) 28 | } 29 | return null 30 | }, 31 | remove (key) { 32 | sessionStorage.removeItem(key); 33 | } 34 | } 35 | const localCache = { 36 | set (key, value) { 37 | if (!localStorage) { 38 | return 39 | } 40 | if (key != null && value != null) { 41 | localStorage.setItem(key, value) 42 | } 43 | }, 44 | get (key) { 45 | if (!localStorage) { 46 | return null 47 | } 48 | if (key == null) { 49 | return null 50 | } 51 | return localStorage.getItem(key) 52 | }, 53 | setJSON (key, jsonValue) { 54 | if (jsonValue != null) { 55 | this.set(key, JSON.stringify(jsonValue)) 56 | } 57 | }, 58 | getJSON (key) { 59 | const value = this.get(key) 60 | if (value != null) { 61 | return JSON.parse(value) 62 | } 63 | return null 64 | }, 65 | remove (key) { 66 | localStorage.removeItem(key); 67 | } 68 | } 69 | 70 | export default { 71 | /** 72 | * 会话级缓存 73 | */ 74 | session: sessionCache, 75 | /** 76 | * 本地缓存 77 | */ 78 | local: localCache 79 | } 80 | -------------------------------------------------------------------------------- /docker-compose.pg.yml: -------------------------------------------------------------------------------- 1 | services: 2 | # 前端服务 3 | ruoyi-frontend: 4 | build: 5 | context: ./ruoyi-fastapi-frontend 6 | dockerfile: Dockerfile 7 | image: ruoyi-frontend:latest 8 | container_name: ruoyi-frontend 9 | ports: 10 | - "12580:80" 11 | volumes: 12 | - ./ruoyi-fastapi-frontend/bin/nginx.dockerpg.conf:/etc/nginx/conf.d/default.conf 13 | depends_on: 14 | - ruoyi-backend-pg 15 | networks: 16 | - ruoyi-network 17 | 18 | # 后端服务(PostgreSQL版本) 19 | ruoyi-backend-pg: 20 | build: 21 | context: ./ruoyi-fastapi-backend 22 | dockerfile: Dockerfile.pg 23 | image: ruoyi-backend-pg:latest 24 | container_name: ruoyi-backend-pg 25 | ports: 26 | - "19099:9099" 27 | depends_on: 28 | - ruoyi-pg 29 | - ruoyi-redis 30 | networks: 31 | - ruoyi-network 32 | 33 | # PostgreSQL服务 34 | ruoyi-pg: 35 | image: postgres:14 36 | container_name: ruoyi-pg 37 | environment: 38 | POSTGRES_PASSWORD: root 39 | POSTGRES_DB: ruoyi-fastapi 40 | POSTGRES_INITDB_ARGS: --encoding=UTF8 --lc-collate=C --lc-ctype=C 41 | ports: 42 | - "15432:5432" 43 | volumes: 44 | - ./ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql:/docker-entrypoint-initdb.d/ruoyi-fastapi-pg.sql 45 | networks: 46 | - ruoyi-network 47 | healthcheck: 48 | test: ["CMD", "pg_isready", "-U", "postgres"] 49 | interval: 5s 50 | timeout: 10s 51 | retries: 30 52 | 53 | # Redis服务 54 | ruoyi-redis: 55 | image: redis:latest 56 | container_name: ruoyi-redis 57 | ports: 58 | - "16379:6379" 59 | networks: 60 | - ruoyi-network 61 | 62 | # 网络配置 63 | networks: 64 | ruoyi-network: 65 | name: ruoyi-network 66 | driver: bridge -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/module_admin/controller/captcha_controller.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from datetime import timedelta 3 | 4 | from fastapi import Request, Response 5 | 6 | from common.enums import RedisInitKeyConfig 7 | from common.router import APIRouterPro 8 | from common.vo import DynamicResponseModel 9 | from module_admin.entity.vo.login_vo import CaptchaCode 10 | from module_admin.service.captcha_service import CaptchaService 11 | from utils.log_util import logger 12 | from utils.response_util import ResponseUtil 13 | 14 | captcha_controller = APIRouterPro(order_num=2, tags=['验证码模块']) 15 | 16 | 17 | @captcha_controller.get( 18 | '/captchaImage', 19 | summary='获取图片验证码接口', 20 | description='用于获取图片验证码', 21 | response_model=DynamicResponseModel[CaptchaCode], 22 | ) 23 | async def get_captcha_image(request: Request) -> Response: 24 | captcha_enabled = ( 25 | await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled') == 'true' 26 | ) 27 | register_enabled = ( 28 | await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.registerUser') == 'true' 29 | ) 30 | session_id = str(uuid.uuid4()) 31 | captcha_result = await CaptchaService.create_captcha_image_service() 32 | image = captcha_result[0] 33 | computed_result = captcha_result[1] 34 | await request.app.state.redis.set( 35 | f'{RedisInitKeyConfig.CAPTCHA_CODES.key}:{session_id}', computed_result, ex=timedelta(minutes=2) 36 | ) 37 | logger.info(f'编号为{session_id}的会话获取图片验证码成功') 38 | 39 | return ResponseUtil.success( 40 | model_content=CaptchaCode( 41 | captchaEnabled=captcha_enabled, registerEnabled=register_enabled, img=image, uuid=session_id 42 | ) 43 | ) 44 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/styles/element-ui.scss: -------------------------------------------------------------------------------- 1 | // cover some element-ui styles 2 | 3 | .el-breadcrumb__inner, 4 | .el-breadcrumb__inner a { 5 | font-weight: 400 !important; 6 | } 7 | 8 | .el-upload { 9 | input[type="file"] { 10 | display: none !important; 11 | } 12 | } 13 | 14 | .el-upload__input { 15 | display: none; 16 | } 17 | 18 | .cell { 19 | .el-tag { 20 | margin-right: 0px; 21 | } 22 | } 23 | 24 | .small-padding { 25 | .cell { 26 | padding-left: 5px; 27 | padding-right: 5px; 28 | } 29 | } 30 | 31 | .fixed-width { 32 | .el-button--mini { 33 | padding: 7px 10px; 34 | width: 60px; 35 | } 36 | } 37 | 38 | .status-col { 39 | .cell { 40 | padding: 0 10px; 41 | text-align: center; 42 | 43 | .el-tag { 44 | margin-right: 0px; 45 | } 46 | } 47 | } 48 | 49 | // to fixed https://github.com/ElemeFE/element/issues/2461 50 | .el-dialog { 51 | transform: none; 52 | left: 0; 53 | position: relative; 54 | margin: 0 auto; 55 | } 56 | 57 | // refine element ui upload 58 | .upload-container { 59 | .el-upload { 60 | width: 100%; 61 | 62 | .el-upload-dragger { 63 | width: 100%; 64 | height: 200px; 65 | } 66 | } 67 | } 68 | 69 | // dropdown 70 | .el-dropdown-menu { 71 | a { 72 | display: block 73 | } 74 | } 75 | 76 | // fix date-picker ui bug in filter-item 77 | .el-range-editor.el-input__inner { 78 | display: inline-flex !important; 79 | } 80 | 81 | // to fix el-date-picker css style 82 | .el-range-separator { 83 | box-sizing: content-box; 84 | } 85 | 86 | .el-menu--collapse 87 | > div 88 | > .el-submenu 89 | > .el-submenu__title 90 | .el-submenu__icon-arrow { 91 | display: none; 92 | } 93 | 94 | .el-dropdown .el-dropdown-link{ 95 | color: var(--el-color-primary) !important; 96 | } -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/views/tool/gen/basicInfoForm.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 49 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/exit-fullscreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/tree.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/swagger.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/module_generator/templates/python/do.py.jinja2: -------------------------------------------------------------------------------- 1 | {% for do_import in doImportList %} 2 | {{ do_import }} 3 | {% endfor %} 4 | {% if table.sub %} 5 | from sqlalchemy.orm import relationship 6 | {% endif %} 7 | 8 | from config.database import Base 9 | 10 | 11 | class {{ ClassName }}(Base): 12 | """ 13 | {{ functionName }}表 14 | """ 15 | 16 | __tablename__ = '{{ tableName }}' 17 | __table_args__ = {'comment': '{{ tableComment }}'} 18 | 19 | {% for column in columns %} 20 | {{ 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 }}') 21 | {% endfor %} 22 | 23 | {% if table.sub %} 24 | {{ subclassName }}_list = relationship('{{ subClassName }}', back_populates='{{ businessName }}') 25 | {% endif %} 26 | 27 | 28 | {% if table.sub %} 29 | class {{ subClassName }}(Base): 30 | """ 31 | {{ subTable.function_name }}表 32 | """ 33 | 34 | __tablename__ = '{{ subTableName }}' 35 | 36 | {% for column in subTable.columns %} 37 | {{ 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 }}') 38 | {% endfor %} 39 | 40 | {% if table.sub %} 41 | {{ businessName }} = relationship('{{ ClassName }}', back_populates='{{ subclassName }}_list') 42 | {% endif %} 43 | {% endif %} 44 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/api/tool/gen.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询生成表数据 4 | export function listTable(query) { 5 | return request({ 6 | url: '/tool/gen/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | // 查询db数据库列表 12 | export function listDbTable(query) { 13 | return request({ 14 | url: '/tool/gen/db/list', 15 | method: 'get', 16 | params: query 17 | }) 18 | } 19 | 20 | // 查询表详细信息 21 | export function getGenTable(tableId) { 22 | return request({ 23 | url: '/tool/gen/' + tableId, 24 | method: 'get' 25 | }) 26 | } 27 | 28 | // 修改代码生成信息 29 | export function updateGenTable(data) { 30 | return request({ 31 | url: '/tool/gen', 32 | method: 'put', 33 | data: data 34 | }) 35 | } 36 | 37 | // 导入表 38 | export function importTable(data) { 39 | return request({ 40 | url: '/tool/gen/importTable', 41 | method: 'post', 42 | params: data 43 | }) 44 | } 45 | 46 | // 创建表 47 | export function createTable(data) { 48 | return request({ 49 | url: '/tool/gen/createTable', 50 | method: 'post', 51 | params: data 52 | }) 53 | } 54 | 55 | // 预览生成代码 56 | export function previewTable(tableId) { 57 | return request({ 58 | url: '/tool/gen/preview/' + tableId, 59 | method: 'get' 60 | }) 61 | } 62 | 63 | // 删除表数据 64 | export function delTable(tableId) { 65 | return request({ 66 | url: '/tool/gen/' + tableId, 67 | method: 'delete' 68 | }) 69 | } 70 | 71 | // 生成代码(自定义路径) 72 | export function genCode(tableName) { 73 | return request({ 74 | url: '/tool/gen/genCode/' + tableName, 75 | method: 'get' 76 | }) 77 | } 78 | 79 | // 同步数据库 80 | export function synchDb(tableName) { 81 | return request({ 82 | url: '/tool/gen/synchDb/' + tableName, 83 | method: 'get' 84 | }) 85 | } 86 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/directive/common/copyText.js: -------------------------------------------------------------------------------- 1 | /** 2 | * v-copyText 复制文本内容 3 | * Copyright (c) 2022 ruoyi 4 | */ 5 | export default { 6 | beforeMount(el, { value, arg }) { 7 | if (arg === "callback") { 8 | el.$copyCallback = value; 9 | } else { 10 | el.$copyValue = value; 11 | const handler = () => { 12 | copyTextToClipboard(el.$copyValue); 13 | if (el.$copyCallback) { 14 | el.$copyCallback(el.$copyValue); 15 | } 16 | }; 17 | el.addEventListener("click", handler); 18 | el.$destroyCopy = () => el.removeEventListener("click", handler); 19 | } 20 | } 21 | } 22 | 23 | function copyTextToClipboard(input, { target = document.body } = {}) { 24 | const element = document.createElement('textarea'); 25 | const previouslyFocusedElement = document.activeElement; 26 | 27 | element.value = input; 28 | 29 | // Prevent keyboard from showing on mobile 30 | element.setAttribute('readonly', ''); 31 | 32 | element.style.contain = 'strict'; 33 | element.style.position = 'absolute'; 34 | element.style.left = '-9999px'; 35 | element.style.fontSize = '12pt'; // Prevent zooming on iOS 36 | 37 | const selection = document.getSelection(); 38 | const originalRange = selection.rangeCount > 0 && selection.getRangeAt(0); 39 | 40 | target.append(element); 41 | element.select(); 42 | 43 | // Explicit selection workaround for iOS 44 | element.selectionStart = 0; 45 | element.selectionEnd = input.length; 46 | 47 | let isSuccess = false; 48 | try { 49 | isSuccess = document.execCommand('copy'); 50 | } catch { } 51 | 52 | element.remove(); 53 | 54 | if (originalRange) { 55 | selection.removeAllRanges(); 56 | selection.addRange(originalRange); 57 | } 58 | 59 | // Get the focus back on the previously focused element, if any 60 | if (previouslyFocusedElement) { 61 | previouslyFocusedElement.focus(); 62 | } 63 | 64 | return isSuccess; 65 | } 66 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/styles/btn.scss: -------------------------------------------------------------------------------- 1 | @use './variables.module.scss' as *; 2 | 3 | @mixin colorBtn($color) { 4 | background: $color; 5 | 6 | &:hover { 7 | color: $color; 8 | 9 | &:before, 10 | &:after { 11 | background: $color; 12 | } 13 | } 14 | } 15 | 16 | .blue-btn { 17 | @include colorBtn($blue) 18 | } 19 | 20 | .light-blue-btn { 21 | @include colorBtn($light-blue) 22 | } 23 | 24 | .red-btn { 25 | @include colorBtn($red) 26 | } 27 | 28 | .pink-btn { 29 | @include colorBtn($pink) 30 | } 31 | 32 | .green-btn { 33 | @include colorBtn($green) 34 | } 35 | 36 | .tiffany-btn { 37 | @include colorBtn($tiffany) 38 | } 39 | 40 | .yellow-btn { 41 | @include colorBtn($yellow) 42 | } 43 | 44 | .pan-btn { 45 | font-size: 14px; 46 | color: #fff; 47 | padding: 14px 36px; 48 | border-radius: 8px; 49 | border: none; 50 | outline: none; 51 | transition: 600ms ease all; 52 | position: relative; 53 | display: inline-block; 54 | 55 | &:hover { 56 | background: #fff; 57 | 58 | &:before, 59 | &:after { 60 | width: 100%; 61 | transition: 600ms ease all; 62 | } 63 | } 64 | 65 | &:before, 66 | &:after { 67 | content: ''; 68 | position: absolute; 69 | top: 0; 70 | right: 0; 71 | height: 2px; 72 | width: 0; 73 | transition: 400ms ease all; 74 | } 75 | 76 | &::after { 77 | right: inherit; 78 | top: inherit; 79 | left: 0; 80 | bottom: 0; 81 | } 82 | } 83 | 84 | .custom-button { 85 | display: inline-block; 86 | line-height: 1; 87 | white-space: nowrap; 88 | cursor: pointer; 89 | background: #fff; 90 | color: #fff; 91 | -webkit-appearance: none; 92 | text-align: center; 93 | box-sizing: border-box; 94 | outline: 0; 95 | margin: 0; 96 | padding: 10px 15px; 97 | font-size: 14px; 98 | border-radius: 4px; 99 | } 100 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/utils/log_util.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | 5 | from loguru import logger as _logger 6 | from loguru._logger import Logger 7 | 8 | from middlewares.trace_middleware import TraceCtx 9 | 10 | 11 | class LoggerInitializer: 12 | def __init__(self) -> None: 13 | self.log_path = os.path.join(os.getcwd(), 'logs') 14 | self.__ensure_log_directory_exists() 15 | self.log_path_error = os.path.join(self.log_path, f'{time.strftime("%Y-%m-%d")}_error.log') 16 | 17 | def __ensure_log_directory_exists(self) -> None: 18 | """ 19 | 确保日志目录存在,如果不存在则创建 20 | """ 21 | if not os.path.exists(self.log_path): 22 | os.mkdir(self.log_path) 23 | 24 | @staticmethod 25 | def __filter(log: dict) -> dict: 26 | """ 27 | 自定义日志过滤器,添加trace_id 28 | """ 29 | log['trace_id'] = TraceCtx.get_id() 30 | return log 31 | 32 | def init_log(self) -> Logger: 33 | """ 34 | 初始化日志配置 35 | """ 36 | # 自定义日志格式 37 | format_str = ( 38 | '{time:YYYY-MM-DD HH:mm:ss.SSS} | ' 39 | '{trace_id} | ' 40 | '{level: <8} | ' 41 | '{name}:{function}:{line} - ' 42 | '{message}' 43 | ) 44 | _logger.remove() 45 | # 移除后重新添加sys.stderr, 目的: 控制台输出与文件日志内容和结构一致 46 | _logger.add(sys.stderr, filter=self.__filter, format=format_str, enqueue=True) 47 | _logger.add( 48 | self.log_path_error, 49 | filter=self.__filter, 50 | format=format_str, 51 | rotation='50MB', 52 | encoding='utf-8', 53 | enqueue=True, 54 | compression='zip', 55 | ) 56 | 57 | return _logger 58 | 59 | 60 | # 初始化日志处理器 61 | log_initializer = LoggerInitializer() 62 | logger = log_initializer.init_log() 63 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/utils/scroll-to.js: -------------------------------------------------------------------------------- 1 | Math.easeInOutQuad = function(t, b, c, d) { 2 | t /= d / 2 3 | if (t < 1) { 4 | return c / 2 * t * t + b 5 | } 6 | t-- 7 | return -c / 2 * (t * (t - 2) - 1) + b 8 | } 9 | 10 | // requestAnimationFrame for Smart Animating http://goo.gl/sx5sts 11 | var requestAnimFrame = (function() { 12 | return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) } 13 | })() 14 | 15 | /** 16 | * Because it's so fucking difficult to detect the scrolling element, just move them all 17 | * @param {number} amount 18 | */ 19 | function move(amount) { 20 | document.documentElement.scrollTop = amount 21 | document.body.parentNode.scrollTop = amount 22 | document.body.scrollTop = amount 23 | } 24 | 25 | function position() { 26 | return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop 27 | } 28 | 29 | /** 30 | * @param {number} to 31 | * @param {number} duration 32 | * @param {Function} callback 33 | */ 34 | export function scrollTo(to, duration, callback) { 35 | const start = position() 36 | const change = to - start 37 | const increment = 20 38 | let currentTime = 0 39 | duration = (typeof (duration) === 'undefined') ? 500 : duration 40 | var animateScroll = function() { 41 | // increment the time 42 | currentTime += increment 43 | // find the value with the quadratic in-out easing function 44 | var val = Math.easeInOutQuad(currentTime, start, change, duration) 45 | // move the document.body 46 | move(val) 47 | // do the animation unless its over 48 | if (currentTime < duration) { 49 | requestAnimFrame(animateScroll) 50 | } else { 51 | if (callback && typeof (callback) === 'function') { 52 | // the animation is done so lets callback 53 | callback() 54 | } 55 | } 56 | } 57 | animateScroll() 58 | } 59 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/views/error/401.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 43 | 44 | 83 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/plugins/modal.js: -------------------------------------------------------------------------------- 1 | import { ElMessage, ElMessageBox, ElNotification, ElLoading } from 'element-plus' 2 | 3 | let loadingInstance; 4 | 5 | export default { 6 | // 消息提示 7 | msg(content) { 8 | ElMessage.info(content) 9 | }, 10 | // 错误消息 11 | msgError(content) { 12 | ElMessage.error(content) 13 | }, 14 | // 成功消息 15 | msgSuccess(content) { 16 | ElMessage.success(content) 17 | }, 18 | // 警告消息 19 | msgWarning(content) { 20 | ElMessage.warning(content) 21 | }, 22 | // 弹出提示 23 | alert(content) { 24 | ElMessageBox.alert(content, "系统提示") 25 | }, 26 | // 错误提示 27 | alertError(content) { 28 | ElMessageBox.alert(content, "系统提示", { type: 'error' }) 29 | }, 30 | // 成功提示 31 | alertSuccess(content) { 32 | ElMessageBox.alert(content, "系统提示", { type: 'success' }) 33 | }, 34 | // 警告提示 35 | alertWarning(content) { 36 | ElMessageBox.alert(content, "系统提示", { type: 'warning' }) 37 | }, 38 | // 通知提示 39 | notify(content) { 40 | ElNotification.info(content) 41 | }, 42 | // 错误通知 43 | notifyError(content) { 44 | ElNotification.error(content); 45 | }, 46 | // 成功通知 47 | notifySuccess(content) { 48 | ElNotification.success(content) 49 | }, 50 | // 警告通知 51 | notifyWarning(content) { 52 | ElNotification.warning(content) 53 | }, 54 | // 确认窗体 55 | confirm(content) { 56 | return ElMessageBox.confirm(content, "系统提示", { 57 | confirmButtonText: '确定', 58 | cancelButtonText: '取消', 59 | type: "warning", 60 | }) 61 | }, 62 | // 提交内容 63 | prompt(content) { 64 | return ElMessageBox.prompt(content, "系统提示", { 65 | confirmButtonText: '确定', 66 | cancelButtonText: '取消', 67 | type: "warning", 68 | }) 69 | }, 70 | // 打开遮罩层 71 | loading(content) { 72 | loadingInstance = ElLoading.service({ 73 | lock: true, 74 | text: content, 75 | background: "rgba(0, 0, 0, 0.7)", 76 | }) 77 | }, 78 | // 关闭遮罩层 79 | closeLoading() { 80 | loadingInstance.close(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, String 4 | 5 | from config.database import Base 6 | from config.env import DataBaseConfig 7 | from utils.common_util import SqlalchemyUtil 8 | 9 | 10 | class SysDept(Base): 11 | """ 12 | 部门表 13 | """ 14 | 15 | __tablename__ = 'sys_dept' 16 | __table_args__ = {'comment': '部门表'} 17 | 18 | dept_id = Column(BigInteger, primary_key=True, autoincrement=True, comment='部门id') 19 | parent_id = Column(BigInteger, server_default='0', comment='父部门id') 20 | ancestors = Column(String(50), nullable=True, server_default="''", comment='祖级列表') 21 | dept_name = Column(String(30), nullable=True, server_default="''", comment='部门名称') 22 | order_num = Column(Integer, server_default='0', comment='显示顺序') 23 | leader = Column( 24 | String(20), 25 | nullable=True, 26 | server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), 27 | comment='负责人', 28 | ) 29 | phone = Column( 30 | String(11), 31 | nullable=True, 32 | server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), 33 | comment='联系电话', 34 | ) 35 | email = Column( 36 | String(50), 37 | nullable=True, 38 | server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), 39 | comment='邮箱', 40 | ) 41 | status = Column(CHAR(1), nullable=True, server_default='0', comment='部门状态(0正常 1停用)') 42 | del_flag = Column(CHAR(1), nullable=True, server_default='0', comment='删除标志(0代表存在 2代表删除)') 43 | create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') 44 | create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') 45 | update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') 46 | update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') 47 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/views/tool/build/CodeTypeDialog.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | -------------------------------------------------------------------------------- /docker-compose.my.yml: -------------------------------------------------------------------------------- 1 | services: 2 | # 前端服务 3 | ruoyi-frontend: 4 | build: 5 | context: ./ruoyi-fastapi-frontend 6 | dockerfile: Dockerfile 7 | image: ruoyi-frontend:latest 8 | container_name: ruoyi-frontend 9 | ports: 10 | - "12580:80" 11 | volumes: 12 | - ./ruoyi-fastapi-frontend/bin/nginx.dockermy.conf:/etc/nginx/conf.d/default.conf 13 | depends_on: 14 | - ruoyi-backend-my 15 | networks: 16 | - ruoyi-network 17 | 18 | # 后端服务(MySQL版本) 19 | ruoyi-backend-my: 20 | build: 21 | context: ./ruoyi-fastapi-backend 22 | dockerfile: Dockerfile.my 23 | image: ruoyi-backend-my:latest 24 | container_name: ruoyi-backend-my 25 | ports: 26 | - "19099:9099" 27 | depends_on: 28 | ruoyi-mysql: 29 | condition: service_healthy 30 | ruoyi-redis: 31 | condition: service_healthy 32 | networks: 33 | - ruoyi-network 34 | 35 | # MySQL服务 36 | ruoyi-mysql: 37 | image: mysql:8.0 38 | container_name: ruoyi-mysql 39 | environment: 40 | MYSQL_ROOT_PASSWORD: root 41 | MYSQL_DATABASE: ruoyi-fastapi 42 | ports: 43 | - "13306:3306" 44 | volumes: 45 | - ./ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql:/docker-entrypoint-initdb.d/ruoyi-fastapi.sql 46 | command: --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci --skip-character-set-client-handshake=1 47 | networks: 48 | - ruoyi-network 49 | healthcheck: 50 | test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot"] 51 | interval: 5s 52 | timeout: 10s 53 | retries: 30 54 | 55 | # Redis服务 56 | ruoyi-redis: 57 | image: redis:latest 58 | container_name: ruoyi-redis 59 | ports: 60 | - "16379:6379" 61 | networks: 62 | - ruoyi-network 63 | healthcheck: 64 | test: ["CMD", "redis-cli", "ping"] 65 | interval: 5s 66 | timeout: 10s 67 | retries: 30 68 | 69 | # 网络配置 70 | networks: 71 | ruoyi-network: 72 | name: ruoyi-network 73 | driver: bridge -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/store/modules/settings.js: -------------------------------------------------------------------------------- 1 | import defaultSettings from '@/settings' 2 | import { useDark, useToggle } from '@vueuse/core' 3 | import { useDynamicTitle } from '@/utils/dynamicTitle' 4 | 5 | const isDark = useDark() 6 | const toggleDark = useToggle(isDark) 7 | 8 | const { sideTheme, showSettings, navType, tagsView, tagsIcon, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings 9 | 10 | const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || '' 11 | 12 | const useSettingsStore = defineStore( 13 | 'settings', 14 | { 15 | state: () => ({ 16 | title: '', 17 | theme: storageSetting.theme || '#409EFF', 18 | sideTheme: storageSetting.sideTheme || sideTheme, 19 | showSettings: showSettings, 20 | navType: storageSetting.navType === undefined ? navType : storageSetting.navType, 21 | tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView, 22 | tagsIcon: storageSetting.tagsIcon === undefined ? tagsIcon : storageSetting.tagsIcon, 23 | fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader, 24 | sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo, 25 | dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle, 26 | footerVisible: storageSetting.footerVisible === undefined ? footerVisible : storageSetting.footerVisible, 27 | footerContent: footerContent, 28 | isDark: isDark.value 29 | }), 30 | actions: { 31 | // 修改布局设置 32 | changeSetting(data) { 33 | const { key, value } = data 34 | if (this.hasOwnProperty(key)) { 35 | this[key] = value 36 | } 37 | }, 38 | // 设置网页标题 39 | setTitle(title) { 40 | this.title = title 41 | useDynamicTitle() 42 | }, 43 | // 切换暗黑模式 44 | toggleTheme() { 45 | this.isDark = !this.isDark 46 | toggleDark() 47 | } 48 | } 49 | }) 50 | 51 | export default useSettingsStore 52 | -------------------------------------------------------------------------------- /ruoyi-fastapi-frontend/src/assets/icons/svg/date-range.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------