fix: add docker-compose config and fix nginx proxy for /prod-api/ endpoint

This commit is contained in:
rjb
2026-05-11 00:13:26 +08:00
parent a02773e383
commit 1047599bde
6 changed files with 377 additions and 226 deletions

16
.env.docker.example Normal file
View File

@@ -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

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.env

76
Docker部署说明.md Normal file
View File

@@ -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、imgBase64
```
## 与前端联调
前端 `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`

54
docker-compose.yml Normal file
View File

@@ -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:

View File

@@ -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"
]
}

View File

@@ -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: './' //到根目录下
}
}
)
}
}