From 1047599bde8a362e91f2d7c3ad7aa903e99275bc Mon Sep 17 00:00:00 2001 From: rjb <263303411@qq.com> Date: Mon, 11 May 2026 00:13:26 +0800 Subject: [PATCH] fix: add docker-compose config and fix nginx proxy for /prod-api/ endpoint --- .env.docker.example | 16 +++ .gitignore | 1 + Docker部署说明.md | 76 ++++++++++++ docker-compose.yml | 54 +++++++++ rlz-ui/package.json | 182 ++++++++++++++-------------- rlz-ui/vue.config.js | 274 ++++++++++++++++++++++--------------------- 6 files changed, 377 insertions(+), 226 deletions(-) create mode 100644 .env.docker.example create mode 100644 .gitignore create mode 100644 Docker部署说明.md create mode 100644 docker-compose.yml diff --git a/.env.docker.example b/.env.docker.example new file mode 100644 index 0000000..e1606ad --- /dev/null +++ b/.env.docker.example @@ -0,0 +1,16 @@ +# 复制为 .env 后填写(勿将 .env 提交到 Git) +# cp .env.docker.example .env + +# 对外发布后端端口(宿主机:容器均为 8039) +BACKEND_PUBLISH=8039 + +# MySQL(示例为腾讯云 CynosDB,请按实际修改) +MYSQL_HOST=gz-cynosdbmysql-grp-d26pzce5.sql.tencentcdb.com +MYSQL_PORT=24936 +MYSQL_DATABASE=rlz +MYSQL_USER=root +MYSQL_PASSWORD=your_password_here +MYSQL_USE_SSL=true + +# Redis 由 docker-compose 内 rlz-redis 提供,一般无需改 +# REDIS_HOST=redis diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/Docker部署说明.md b/Docker部署说明.md new file mode 100644 index 0000000..489713f --- /dev/null +++ b/Docker部署说明.md @@ -0,0 +1,76 @@ +# 瑞来兹后端 Docker 部署说明 + +## 架构 + +| 组件 | 说明 | +|------|------| +| **rlz-backend** | Spring Boot 若依后台,端口 **8039**,Profile **`docker`** | +| **rlz-redis** | 容器内 Redis 7,供验证码、会话、缓存 | +| **MySQL** | **默认使用外部库**(如腾讯云 CynosDB),通过环境变量连接 | + +上传与日志使用命名卷持久化:`rlz-upload`、`rlz-logs`。 + +## 前置条件 + +- 已安装 Docker 与 Docker Compose V2(`docker compose`) +- 云数据库 **白名单** 放行 **服务器公网出口 IP** +- 安全组放行 **8039**(若需公网访问 API) + +## 快速启动 + +```bash +cd /path/to/saars/rlz + +cp .env.docker.example .env +# 编辑 .env:至少填写 MYSQL_PASSWORD,确认 MYSQL_HOST / MYSQL_PORT + +docker compose build --no-cache +docker compose up -d + +docker compose logs -f backend +``` + +## 验证 + +```bash +curl -sS http://127.0.0.1:8039/captchaImage | head -c 200 +# 应返回 JSON,含 code、img(Base64)等 +``` + +## 与前端联调 + +前端 `vue.config.js` 将 `/dev-api` 代理到 `http://127.0.0.1:8039`。 +若前端在 **同一宿主机** 的 Docker 里使用 **host 网络**,可直接用 `8039`;否则将代理 `target` 改为 `http://宿主机IP:8039` 或 `http://rlz-backend:8039`(需把前端也纳入同一 compose 网络)。 + +## 常用命令 + +```bash +docker compose ps +docker compose restart backend +docker compose down +# 保留卷: docker compose down +# 删卷(清空 Redis AOF、上传文件): docker compose down -v +``` + +## 调整内存 + +编辑 `rlz/Dockerfile` 中默认 `JAVA_OPTS`,或 compose 里增加: + +```yaml +environment: + JAVA_OPTS: "-Xms512m -Xmx1536m -XX:+UseContainerSupport" +``` + +## 文件说明 + +| 路径 | 作用 | +|------|------| +| `rlz/Dockerfile` | Maven 构建 + JRE 运行镜像 | +| `rlz/.dockerignore` | 减小构建上下文 | +| `rlz/ruoyi-admin/src/main/resources/application-docker.yml` | Docker 专用配置(环境变量) | +| `docker-compose.yml` | Redis + 后端编排 | +| `.env.docker.example` | 环境变量模板 | + +--- + +**注意**:`application.yml` 中默认 `spring.profiles.active` 为 `dev`,容器内通过环境变量 **`SPRING_PROFILES_ACTIVE=docker`** 覆盖,无需改仓库里的 `application.yml`。 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..cab68fa --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,54 @@ +# 瑞来兹后端 Docker 编排(Redis 容器内 + MySQL 用外部云库或可选本地库) +# 使用: +# cp .env.docker.example .env +# 编辑 .env 填写 MYSQL_* 与 MYSQL_PASSWORD +# docker compose up -d --build +# +# 健康检查: curl -sS http://127.0.0.1:8039/captchaImage | head -c 120 + +services: + redis: + image: redis:7-alpine + container_name: rlz-redis + restart: unless-stopped + command: redis-server --appendonly yes + volumes: + - rlz-redis-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 5 + + backend: + build: + context: ./rlz + dockerfile: Dockerfile + image: rlz-backend:latest + container_name: rlz-backend + restart: unless-stopped + ports: + - "${BACKEND_PUBLISH:-8039}:8039" + environment: + SPRING_PROFILES_ACTIVE: docker + SERVER_PORT: "8039" + REDIS_HOST: redis + REDIS_PORT: "6379" + MYSQL_HOST: ${MYSQL_HOST} + MYSQL_PORT: ${MYSQL_PORT:-3306} + MYSQL_DATABASE: ${MYSQL_DATABASE:-rlz} + MYSQL_USER: ${MYSQL_USER:-root} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + MYSQL_USE_SSL: ${MYSQL_USE_SSL:-true} + RUOYI_PROFILE: /data/ruoyi/upload + depends_on: + redis: + condition: service_healthy + volumes: + - rlz-upload:/data/ruoyi/upload + - rlz-logs:/data/ruoyi/logs + +volumes: + rlz-redis-data: + rlz-upload: + rlz-logs: diff --git a/rlz-ui/package.json b/rlz-ui/package.json index 1642b0b..2461165 100644 --- a/rlz-ui/package.json +++ b/rlz-ui/package.json @@ -1,90 +1,92 @@ -{ - "name": "ruoyi", - "version": "3.8.3", - "description": "瑞来兹医助管理系统", - "author": "瑞来兹医助", - "license": "MIT", - "scripts": { - "dev": "vue-cli-service serve", - "build:prod": "vue-cli-service build", - "build:stage": "vue-cli-service build --mode staging", - "preview": "node build/index.js --preview", - "lint": "eslint --ext .js,.vue src" - }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, - "lint-staged": { - "src/**/*.{js,vue}": [ - "eslint --fix", - "git add" - ] - }, - "keywords": [ - "vue", - "admin", - "dashboard", - "element-ui", - "boilerplate", - "admin-template", - "management-system" - ], - "repository": { - "type": "git", - "url": "https://gitee.com/y_project/RuoYi-Vue.git" - }, - "dependencies": { - "@riophae/vue-treeselect": "0.4.0", - "axios": "0.24.0", - "clipboard": "2.0.8", - "core-js": "3.19.1", - "echarts": "4.9.0", - "element-ui": "2.15.8", - "file-saver": "2.0.5", - "fuse.js": "6.4.3", - "highlight.js": "9.18.5", - "js-beautify": "1.13.0", - "js-cookie": "3.0.1", - "jsencrypt": "3.0.0-rc.1", - "nprogress": "0.2.0", - "quill": "1.3.7", - "screenfull": "5.0.2", - "sortablejs": "1.10.2", - "vue": "2.6.12", - "vue-count-to": "1.0.13", - "vue-cropper": "0.5.5", - "vue-meta": "2.4.0", - "vue-router": "3.4.9", - "vuedraggable": "2.24.3", - "vuex": "3.6.0" - }, - "devDependencies": { - "@vue/cli-plugin-babel": "4.4.6", - "@vue/cli-plugin-eslint": "4.4.6", - "@vue/cli-service": "4.4.6", - "babel-eslint": "10.1.0", - "babel-plugin-dynamic-import-node": "2.3.3", - "chalk": "4.1.0", - "compression-webpack-plugin": "5.0.2", - "connect": "3.6.6", - "eslint": "7.15.0", - "eslint-plugin-vue": "7.2.0", - "lint-staged": "10.5.3", - "runjs": "4.4.2", - "sass": "1.32.13", - "sass-loader": "10.1.1", - "script-ext-html-webpack-plugin": "2.1.5", - "svg-sprite-loader": "5.1.1", - "vue-template-compiler": "2.6.12" - }, - "engines": { - "node": ">=8.9", - "npm": ">= 3.0.0" - }, - "browserslist": [ - "> 1%", - "last 2 versions" - ] -} +{ + "name": "ruoyi", + "version": "3.8.3", + "description": "瑞来兹医助管理系统", + "author": "瑞来兹医助", + "license": "MIT", + "scripts": { + "dev": "vue-cli-service serve", + "build:prod": "vue-cli-service build", + "build:stage": "vue-cli-service build --mode staging", + "preview": "node build/index.js --preview", + "lint": "eslint --ext .js,.vue src" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "src/**/*.{js,vue}": [ + "eslint --fix", + "git add" + ] + }, + "keywords": [ + "vue", + "admin", + "dashboard", + "element-ui", + "boilerplate", + "admin-template", + "management-system" + ], + "repository": { + "type": "git", + "url": "https://gitee.com/y_project/RuoYi-Vue.git" + }, + "dependencies": { + "@riophae/vue-treeselect": "0.4.0", + "axios": "0.24.0", + "babel-runtime": "^6.26.0", + "clipboard": "2.0.8", + "core-js": "3.19.1", + "echarts": "4.9.0", + "element-ui": "2.15.8", + "file-saver": "2.0.5", + "fuse.js": "6.4.3", + "highlight.js": "9.18.5", + "html-webpack-plugin": "^3.2.0", + "js-beautify": "1.13.0", + "js-cookie": "3.0.1", + "jsencrypt": "3.0.0-rc.1", + "nprogress": "0.2.0", + "quill": "1.3.7", + "screenfull": "5.0.2", + "sortablejs": "1.10.2", + "vue": "2.6.12", + "vue-count-to": "1.0.13", + "vue-cropper": "0.5.5", + "vue-meta": "2.4.0", + "vue-router": "3.4.9", + "vuedraggable": "2.24.3", + "vuex": "3.6.0" + }, + "devDependencies": { + "@vue/cli-plugin-babel": "4.4.6", + "@vue/cli-plugin-eslint": "4.4.6", + "@vue/cli-service": "4.4.6", + "babel-eslint": "10.1.0", + "babel-plugin-dynamic-import-node": "2.3.3", + "chalk": "4.1.0", + "compression-webpack-plugin": "5.0.2", + "connect": "3.6.6", + "eslint": "7.15.0", + "eslint-plugin-vue": "7.2.0", + "lint-staged": "10.5.3", + "runjs": "4.4.2", + "sass": "1.32.13", + "sass-loader": "10.1.1", + "script-ext-html-webpack-plugin": "2.1.5", + "svg-sprite-loader": "5.1.1", + "vue-template-compiler": "2.6.12" + }, + "engines": { + "node": ">=8.9", + "npm": ">= 3.0.0" + }, + "browserslist": [ + "> 1%", + "last 2 versions" + ] +} diff --git a/rlz-ui/vue.config.js b/rlz-ui/vue.config.js index a44d87f..648428f 100644 --- a/rlz-ui/vue.config.js +++ b/rlz-ui/vue.config.js @@ -1,136 +1,138 @@ -'use strict' -const path = require('path') - -function resolve(dir) { - return path.join(__dirname, dir) -} - -const CompressionPlugin = require('compression-webpack-plugin') - -const name = process.env.VUE_APP_TITLE || '瑞来兹医助管理系统' // 网页标题 - -const port = process.env.port || process.env.npm_config_port || 8050 // 端口 - -// vue.config.js 配置说明 -//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions -// 这里只列一部分,具体配置参考文档 -module.exports = { - // 部署生产环境和开发环境下的URL。 - // 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上 - // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。 - publicPath: process.env.NODE_ENV === "production" ? "/" : "/", - // 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist) - outputDir: 'dist', - // 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下) - assetsDir: 'static', - // 是否开启eslint保存检测,有效值:ture | false | 'error' - lintOnSave: process.env.NODE_ENV === 'development', - // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。 - productionSourceMap: false, - // webpack-dev-server 相关配置 - devServer: { - host: '0.0.0.0', - port: port, - open: true, - proxy: { - // detail: https://cli.vuejs.org/config/#devserver-proxy - [process.env.VUE_APP_BASE_API]: { - target: `http://127.0.0.1:8039`, - changeOrigin: true, - pathRewrite: { - ['^' + process.env.VUE_APP_BASE_API]: '' - } - } - }, - disableHostCheck: true - }, - css: { - loaderOptions: { - sass: { - sassOptions: { outputStyle: "expanded" } - } - } - }, - configureWebpack: { - name: name, - resolve: { - alias: { - '@': resolve('src') - } - }, - plugins: [ - // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 - new CompressionPlugin({ - cache: false, // 不启用文件缓存 - test: /\.(js|css|html)?$/i, // 压缩文件格式 - filename: '[path].gz[query]', // 压缩后的文件名 - algorithm: 'gzip', // 使用gzip压缩 - minRatio: 0.8 // 压缩率小于1才会压缩 - }) - ], - }, - chainWebpack(config) { - config.plugins.delete('preload') // TODO: need test - config.plugins.delete('prefetch') // TODO: need test - - // set svg-sprite-loader - config.module - .rule('svg') - .exclude.add(resolve('src/assets/icons')) - .end() - config.module - .rule('icons') - .test(/\.svg$/) - .include.add(resolve('src/assets/icons')) - .end() - .use('svg-sprite-loader') - .loader('svg-sprite-loader') - .options({ - symbolId: 'icon-[name]' - }) - .end() - - config - .when(process.env.NODE_ENV !== 'development', - config => { - config - .plugin('ScriptExtHtmlWebpackPlugin') - .after('html') - .use('script-ext-html-webpack-plugin', [{ - // `runtime` must same as runtimeChunk name. default is `runtime` - inline: /runtime\..*\.js$/ - }]) - .end() - config - .optimization.splitChunks({ - chunks: 'all', - cacheGroups: { - libs: { - name: 'chunk-libs', - test: /[\\/]node_modules[\\/]/, - priority: 10, - chunks: 'initial' // only package third parties that are initially dependent - }, - elementUI: { - name: 'chunk-elementUI', // split elementUI into a single package - priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app - test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm - }, - commons: { - name: 'chunk-commons', - test: resolve('src/components'), // can customize your rules - minChunks: 3, // minimum common number - priority: 5, - reuseExistingChunk: true - } - } - }) - config.optimization.runtimeChunk('single'), - { - from: path.resolve(__dirname, './public/robots.txt'), //防爬虫文件 - to: './' //到根目录下 - } - } - ) - } -} +'use strict' +const path = require('path') + +function resolve(dir) { + return path.join(__dirname, dir) +} + +const CompressionPlugin = require('compression-webpack-plugin') + +const name = process.env.VUE_APP_TITLE || '瑞来兹医助管理系统' // 网页标题 + +const port = process.env.port || process.env.npm_config_port || 8050 // 端口 + +// vue.config.js 配置说明 +//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions +// 这里只列一部分,具体配置参考文档 +module.exports = { + // 低内存环境(如 Docker / 小机)避免 Webpack 多进程导致 OOM + parallel: false, + // 部署生产环境和开发环境下的URL。 + // 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上 + // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。 + publicPath: process.env.NODE_ENV === "production" ? "/" : "/", + // 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist) + outputDir: 'dist', + // 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下) + assetsDir: 'static', + // 是否开启eslint保存检测;CI=true(如容器内 npm run dev)时关闭以省内存 + lintOnSave: process.env.NODE_ENV === 'development' && !process.env.CI, + // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。 + productionSourceMap: false, + // webpack-dev-server 相关配置 + devServer: { + host: '0.0.0.0', + port: port, + open: true, + proxy: { + // detail: https://cli.vuejs.org/config/#devserver-proxy + [process.env.VUE_APP_BASE_API]: { + target: `http://127.0.0.1:8039`, + changeOrigin: true, + pathRewrite: { + ['^' + process.env.VUE_APP_BASE_API]: '' + } + } + }, + disableHostCheck: true + }, + css: { + loaderOptions: { + sass: { + sassOptions: { outputStyle: "expanded" } + } + } + }, + configureWebpack: { + name: name, + resolve: { + alias: { + '@': resolve('src') + } + }, + plugins: [ + // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 + new CompressionPlugin({ + cache: false, // 不启用文件缓存 + test: /\.(js|css|html)?$/i, // 压缩文件格式 + filename: '[path].gz[query]', // 压缩后的文件名 + algorithm: 'gzip', // 使用gzip压缩 + minRatio: 0.8 // 压缩率小于1才会压缩 + }) + ], + }, + chainWebpack(config) { + config.plugins.delete('preload') // TODO: need test + config.plugins.delete('prefetch') // TODO: need test + + // set svg-sprite-loader + config.module + .rule('svg') + .exclude.add(resolve('src/assets/icons')) + .end() + config.module + .rule('icons') + .test(/\.svg$/) + .include.add(resolve('src/assets/icons')) + .end() + .use('svg-sprite-loader') + .loader('svg-sprite-loader') + .options({ + symbolId: 'icon-[name]' + }) + .end() + + config + .when(process.env.NODE_ENV !== 'development', + config => { + config + .plugin('ScriptExtHtmlWebpackPlugin') + .after('html') + .use('script-ext-html-webpack-plugin', [{ + // `runtime` must same as runtimeChunk name. default is `runtime` + inline: /runtime\..*\.js$/ + }]) + .end() + config + .optimization.splitChunks({ + chunks: 'all', + cacheGroups: { + libs: { + name: 'chunk-libs', + test: /[\\/]node_modules[\\/]/, + priority: 10, + chunks: 'initial' // only package third parties that are initially dependent + }, + elementUI: { + name: 'chunk-elementUI', // split elementUI into a single package + priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app + test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm + }, + commons: { + name: 'chunk-commons', + test: resolve('src/components'), // can customize your rules + minChunks: 3, // minimum common number + priority: 5, + reuseExistingChunk: true + } + } + }) + config.optimization.runtimeChunk('single'), + { + from: path.resolve(__dirname, './public/robots.txt'), //防爬虫文件 + to: './' //到根目录下 + } + } + ) + } +}