docs: add cloud server deployment guide and update improvement log
- New: 云服务器部署实操手册.md covering Docker, database, frontend deployment, Git sync, paramiko, API testing, menu management, and troubleshooting - Update: 改进完成记录.md with Issue #18, #21, and order_view fix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
540
docs/云服务器部署实操手册.md
Normal file
540
docs/云服务器部署实操手册.md
Normal file
@@ -0,0 +1,540 @@
|
||||
# 云服务器部署实操手册
|
||||
|
||||
> 更新日期:2026-05-17
|
||||
> 基于 101.43.95.130 (腾讯云 2核/7.5GB) 实际部署经验编写
|
||||
|
||||
---
|
||||
|
||||
## 一、服务器基本信息
|
||||
|
||||
| 项目 | 详情 |
|
||||
|------|------|
|
||||
| IP | `101.43.95.130` |
|
||||
| 系统 | CentOS 7 / Ubuntu |
|
||||
| 内存 | 7.5GB(可用约 762MB,仅跑一个 Java 实例) |
|
||||
| SSH | `renjianbo:123456` |
|
||||
| Git 服务 | Gitea `http://101.43.95.130:3001` (admin/123456) |
|
||||
| CI/CD | Drone CI `http://101.43.95.130:3002` |
|
||||
| 管理后台 | `http://101.43.95.130:8050` |
|
||||
| 后端 API | `http://101.43.95.130:8039` |
|
||||
|
||||
### 项目路径
|
||||
|
||||
| 位置 | 路径 |
|
||||
|------|------|
|
||||
| 服务器代码 | `/home/renjianbo/saars/rlz/` |
|
||||
| Docker Compose | `/home/renjianbo/saars/rlz/docker-compose.yml` |
|
||||
| 前端 Nginx | Docker 容器 `rlz-ui-server` |
|
||||
| 本地代码 | `D:\androidPj\rlz\` |
|
||||
|
||||
---
|
||||
|
||||
## 二、Docker 容器架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Docker Host (101.43.95.130) │
|
||||
│ │
|
||||
│ ┌───────────┐ ┌────────────┐ ┌──────────────┐ │
|
||||
│ │rlz-backend│ │rlz-ui-server│ │ rlz-redis │ │
|
||||
│ │ :8039 │ │ :8050 │ │ :6379 │ │
|
||||
│ │ Spring │ │ Nginx + │ │ Redis 7 │ │
|
||||
│ │ Boot │ │ 前端 dist │ │ │ │
|
||||
│ └───────────┘ └────────────┘ └──────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────┐ ┌────────────┐ ┌──────────────┐ │
|
||||
│ │ Gitea │ │ Drone │ │ Drone Runner│ │
|
||||
│ │ :3001 │ │ :3002/3003 │ │ │ │
|
||||
│ └───────────┘ └────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ 腾讯云 CynosDB MySQL│
|
||||
│ gz-...sql.tencentcdb.com:24936 │
|
||||
│ 库: rlz (生产) │
|
||||
│ 库: rlz_dev (开发) │
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
### 常用 Docker 命令
|
||||
|
||||
```bash
|
||||
# 查看所有容器状态
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||
|
||||
# 重建后端(代码改动后)
|
||||
cd /home/renjianbo/saars/rlz
|
||||
docker compose build --no-cache backend
|
||||
docker compose up -d backend
|
||||
|
||||
# 查看后端日志
|
||||
docker logs --tail 100 rlz-backend
|
||||
|
||||
# 重启前端 Nginx
|
||||
docker restart rlz-ui-server
|
||||
|
||||
# 进入 Redis
|
||||
docker exec -it rlz-redis redis-cli
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、数据库操作
|
||||
|
||||
### 连接信息
|
||||
|
||||
```
|
||||
Host: gz-cynosdbmysql-grp-d26pzce5.sql.tencentcdb.com
|
||||
Port: 24936
|
||||
User: root
|
||||
Password: !Rjb12191
|
||||
Databases: rlz (生产), rlz_dev (开发)
|
||||
```
|
||||
|
||||
### 通过服务器 Python 脚本操作数据库
|
||||
|
||||
由于服务器 Docker 容器内没有 `mysql` 客户端,需要用 `pymysql`:
|
||||
|
||||
```python
|
||||
import pymysql
|
||||
|
||||
conn = pymysql.connect(
|
||||
host='gz-cynosdbmysql-grp-d26pzce5.sql.tencentcdb.com',
|
||||
port=24936,
|
||||
user='root',
|
||||
password='!Rjb12191',
|
||||
database='rlz',
|
||||
charset='utf8mb4'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
cur.execute("YOUR SQL HERE")
|
||||
conn.commit()
|
||||
cur.close()
|
||||
conn.close()
|
||||
```
|
||||
|
||||
### 复制表结构到开发库
|
||||
|
||||
```sql
|
||||
-- 先禁用外键检查(避免表依赖顺序问题)
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- 逐表 CREATE TABLE ... LIKE + INSERT ... SELECT
|
||||
CREATE TABLE rlz_dev.sys_xxx LIKE rlz.sys_xxx;
|
||||
INSERT INTO rlz_dev.sys_xxx SELECT * FROM rlz.sys_xxx;
|
||||
|
||||
-- 视图需要等到所有基表创建完成后才能创建
|
||||
-- 恢复外键检查
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
```
|
||||
|
||||
### 修复视图(常见问题)
|
||||
|
||||
视图引用的列在基表中不存在时会导致 SQL 错误。例如 `order_view` 引用了 `sys_user` 不存在的 `age`/`nation` 列:
|
||||
|
||||
```sql
|
||||
-- 用 NULL 占位缺失的列
|
||||
CREATE OR REPLACE VIEW `order_view` AS
|
||||
SELECT
|
||||
o.order_id,
|
||||
ub.user_id AS userb_id,
|
||||
NULL AS userb_age, -- sys_user 表暂无此列
|
||||
NULL AS userb_nation, -- sys_user 表暂无此列
|
||||
...
|
||||
FROM rlz_order o
|
||||
LEFT JOIN sys_user ub ON o.b_id = ub.user_id
|
||||
...
|
||||
```
|
||||
|
||||
### 备份生产库
|
||||
|
||||
在对 `rlz` 做结构变更前务必备份:
|
||||
|
||||
```sql
|
||||
-- 通过 pymysql 导出
|
||||
-- 或使用云数据库自带的备份功能
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、前端部署
|
||||
|
||||
### 本地构建
|
||||
|
||||
```bash
|
||||
# Windows 环境,Node 17+ 需要 OpenSSL legacy 兼容
|
||||
cd D:\androidPj\rlz\rlz-ui
|
||||
|
||||
# 设置兼容模式
|
||||
set NODE_OPTIONS=--openssl-legacy-provider
|
||||
|
||||
# 构建
|
||||
npm run build:prod
|
||||
```
|
||||
|
||||
### 上传到服务器(SFTP)
|
||||
|
||||
构建产物较大(约 3MB tar),不能用 base64 echo 方式传输(会导致 SSH 连接重置),必须用 SFTP:
|
||||
|
||||
```python
|
||||
import paramiko
|
||||
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
client.connect('101.43.95.130', 22, 'renjianbo', '123456')
|
||||
|
||||
sftp = client.open_sftp()
|
||||
sftp.put('local_dist.tar', '/tmp/dist.tar')
|
||||
sftp.close()
|
||||
|
||||
# 部署
|
||||
stdin, stdout, stderr = client.exec_command(
|
||||
'cd /tmp && tar xf dist.tar && cp -r dist/* /home/renjianbo/saars/rlz/rlz-ui/dist/ && docker restart rlz-ui-server'
|
||||
)
|
||||
client.close()
|
||||
```
|
||||
|
||||
### 通过服务器直接构建(不推荐,服务器资源紧张)
|
||||
|
||||
```bash
|
||||
cd /home/renjianbo/saars/rlz/rlz-ui
|
||||
npm install
|
||||
NODE_OPTIONS=--openssl-legacy-provider npm run build:prod
|
||||
# 确保 nginx 指向 dist 目录
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、Git 代码同步
|
||||
|
||||
### 仓库地址
|
||||
|
||||
```
|
||||
http://101.43.95.130:3001/admin/rlz.git
|
||||
```
|
||||
|
||||
### 本地 ↔ Gitea ↔ 服务器 三方同步
|
||||
|
||||
```bash
|
||||
# === 本地 ===
|
||||
cd D:/androidPj/rlz
|
||||
git checkout dev
|
||||
git add <files>
|
||||
git commit -m "message"
|
||||
git push origin dev
|
||||
|
||||
# === 服务器 ===
|
||||
cd /home/renjianbo/saars/rlz
|
||||
git pull origin dev
|
||||
|
||||
# 如果服务器有未跟踪文件冲突
|
||||
git stash --include-untracked
|
||||
git pull origin dev
|
||||
git stash drop
|
||||
```
|
||||
|
||||
### 解决 push 被拒绝
|
||||
|
||||
```bash
|
||||
# 如果远程有本地没有的提交
|
||||
git pull origin dev --no-edit
|
||||
git push origin dev
|
||||
```
|
||||
|
||||
### 服务器有本地修改时提交
|
||||
|
||||
```bash
|
||||
cd /home/renjianbo/saars/rlz
|
||||
git add <files>
|
||||
git commit -m "message"
|
||||
git push origin dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、Python paramiko 远程操作
|
||||
|
||||
### 基础连接
|
||||
|
||||
```python
|
||||
import paramiko
|
||||
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
client.connect('101.43.95.130', 22, 'renjianbo', '123456')
|
||||
|
||||
# 执行命令
|
||||
stdin, stdout, stderr = client.exec_command('command')
|
||||
output = stdout.read().decode('utf-8', errors='replace')
|
||||
error = stderr.read().decode('utf-8', errors='replace')
|
||||
exit_code = stdout.channel.recv_exit_status()
|
||||
|
||||
client.close()
|
||||
```
|
||||
|
||||
### 大文件传输(必须用 SFTP)
|
||||
|
||||
```python
|
||||
sftp = client.open_sftp()
|
||||
sftp.put('/local/path/file', '/remote/path/file') # 上传
|
||||
sftp.get('/remote/path/file', '/local/path/file') # 下载
|
||||
sftp.close()
|
||||
```
|
||||
|
||||
### 编码处理(处理中文日志)
|
||||
|
||||
```python
|
||||
import sys, io
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
```
|
||||
|
||||
### 通过 heredoc 执行 Python 脚本
|
||||
|
||||
```python
|
||||
# 注意:复杂的 f-string 和花括号会导致 bash heredoc 转义问题
|
||||
# 推荐用 SFTP 上传 .py 文件代替 heredoc
|
||||
|
||||
# 简单脚本可以用 heredoc:
|
||||
script = '''
|
||||
import json
|
||||
data = {"key": "value"}
|
||||
print(json.dumps(data))
|
||||
'''
|
||||
# 危险的字符需要转义:$ → \$, ` → \`, \ → \\
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、API 测试与 JWT 认证
|
||||
|
||||
### 获取有效 Token
|
||||
|
||||
RuoYi 框架使用 Redis 存储验证码,需要先获取 captcha 再登录:
|
||||
|
||||
```python
|
||||
import json, subprocess
|
||||
|
||||
# Step 1: 获取验证码 UUID
|
||||
result = subprocess.run(['curl', '-s', 'http://localhost:8039/captchaImage'],
|
||||
capture_output=True, text=True)
|
||||
captcha = json.loads(result.stdout)
|
||||
uuid = captcha['uuid']
|
||||
|
||||
# Step 2: 从 Redis 读取验证码答案
|
||||
result = subprocess.run(['docker', 'exec', 'rlz-redis', 'redis-cli',
|
||||
'GET', f'captcha_codes:{uuid}'],
|
||||
capture_output=True, text=True)
|
||||
code = result.stdout.strip().strip('"')
|
||||
|
||||
# Step 3: 登录获取 token
|
||||
import urllib.request
|
||||
data = json.dumps({'username': 'admin', 'password': 'admin123',
|
||||
'code': code, 'uuid': uuid})
|
||||
req = urllib.request.Request('http://localhost:8039/login',
|
||||
data=data.encode(),
|
||||
headers={'Content-Type': 'application/json'})
|
||||
resp = json.loads(urllib.request.urlopen(req).read().decode())
|
||||
token = resp['token']
|
||||
```
|
||||
|
||||
### 带 Token 调用 API
|
||||
|
||||
```python
|
||||
req = urllib.request.Request('http://localhost:8039/system/renzheng/list',
|
||||
headers={'Authorization': f'Bearer {token}'})
|
||||
resp = urllib.request.urlopen(req)
|
||||
data = json.loads(resp.read().decode())
|
||||
```
|
||||
|
||||
### 管理员默认账号
|
||||
|
||||
- 用户名: `admin`
|
||||
- 密码: `admin123`
|
||||
|
||||
---
|
||||
|
||||
## 八、RuoYi 菜单管理
|
||||
|
||||
### 菜单是数据库驱动的
|
||||
|
||||
前端侧边栏菜单由 `sys_menu` 表动态加载,不需要修改前端代码。
|
||||
|
||||
### sys_menu 表结构
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `menu_id` | 主键,建议新菜单用 2000+ 避免冲突 |
|
||||
| `menu_name` | 菜单名称 |
|
||||
| `parent_id` | 父菜单ID,`0` 表示顶级菜单 |
|
||||
| `menu_type` | `M`=目录, `C`=菜单(页面), `F`=按钮(权限) |
|
||||
| `path` | 路由路径,对应 `@/views/` 下的组件路径 |
|
||||
| `component` | 组件路径,如 `system/hospital/index` |
|
||||
| `perms` | 权限标识,如 `system:hospital:list` |
|
||||
| `icon` | 图标名称(Element UI 图标) |
|
||||
| `visible` | `0`=可见, `1`=隐藏 |
|
||||
| `status` | `0`=启用, `1`=停用 |
|
||||
| `order_num` | 排序 |
|
||||
|
||||
### 添加新菜单的完整流程
|
||||
|
||||
```sql
|
||||
-- 1. 添加菜单项
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path,
|
||||
component, menu_type, perms, icon, create_by, create_time,
|
||||
visible, status, is_frame, is_cache)
|
||||
VALUES (2001, '医院管理', 2000, 1, 'system/hospital',
|
||||
'system/hospital/index', 'C', 'system:hospital:list',
|
||||
'build', 'admin', NOW(), '0', '0', 1, 0);
|
||||
|
||||
-- 2. 添加权限按钮(F 类型)
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num,
|
||||
menu_type, perms, create_by, create_time)
|
||||
VALUES (2002, '医院查询', 2001, 1, 'F', 'system:hospital:list',
|
||||
'admin', NOW());
|
||||
|
||||
-- 3. 授权给管理员角色(role_id=1)
|
||||
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 2001);
|
||||
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 2002);
|
||||
```
|
||||
|
||||
### 菜单结构调整示例
|
||||
|
||||
```
|
||||
调整前: 调整后:
|
||||
系统管理 业务管理
|
||||
├── 用户管理 ├── 医院管理
|
||||
├── 服务类型 ├── 订单管理
|
||||
└── ... ├── 认证管理
|
||||
└── 服务类型 ← 从系统管理移过来
|
||||
|
||||
财务管理
|
||||
├── 退款审核
|
||||
├── 结算管理
|
||||
└── 收入统计
|
||||
|
||||
系统管理(下移)
|
||||
├── 用户管理
|
||||
└── ...
|
||||
```
|
||||
|
||||
只需 `UPDATE sys_menu SET parent_id=2000 WHERE menu_id=1060` 即可移动菜单。
|
||||
|
||||
---
|
||||
|
||||
## 九、常见问题与解决
|
||||
|
||||
### MyBatis XML 解析错误
|
||||
|
||||
**错误**: `SAXParseException: 元素内容必须由正确的字符数据或标记组成`
|
||||
|
||||
**原因**: XML 文件中有不可见字符(如 `\x01` SOH)或行首多余字符
|
||||
|
||||
**修复**:
|
||||
```bash
|
||||
# 检查 XML 里是否有 BOM 或控制字符
|
||||
cat -A mapper.xml | head -5
|
||||
|
||||
# 移除 SOH 字符
|
||||
sed -i "s/\x01//g" mapper.xml
|
||||
|
||||
# 或通过 Python 二进制方式清理
|
||||
with open(file, 'rb') as f:
|
||||
content = f.read()
|
||||
with open(file, 'wb') as f:
|
||||
f.write(content.replace(b'\x01', b'').replace(b'^t', b''))
|
||||
```
|
||||
|
||||
### Docker 构建不生效
|
||||
|
||||
**现象**: `docker compose up -d --build` 后代码未更新
|
||||
|
||||
**修复**: 使用 `--no-cache` 强制重建
|
||||
```bash
|
||||
docker compose build --no-cache backend
|
||||
docker compose up -d backend
|
||||
```
|
||||
|
||||
### 外键约束导致建表失败
|
||||
|
||||
**现象**: `Cannot add foreign key constraint`
|
||||
|
||||
**修复**:
|
||||
```sql
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
-- 执行建表 SQL
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
```
|
||||
|
||||
### 视图创建失败
|
||||
|
||||
**原因**: 视图引用的表尚未创建
|
||||
|
||||
**修复**: 等所有基表建完后再创建视图(放到第二遍执行)
|
||||
|
||||
### SSH heredoc 转义问题
|
||||
|
||||
**现象**: bash heredoc 中 Python f-string 的花括号被 bash 错误解析
|
||||
|
||||
**修复**: 用 SFTP 上传 `.py` 文件代替 heredoc 内联脚本
|
||||
|
||||
### Node.js 构建 OpenSSL 错误
|
||||
|
||||
**现象**: `Error: error:0308010C:digital envelope routines::unsupported`
|
||||
|
||||
**修复**: Node 17+ 不兼容旧版 webpack 插件
|
||||
```bash
|
||||
export NODE_OPTIONS="--openssl-legacy-provider"
|
||||
npm run build:prod
|
||||
```
|
||||
|
||||
### Python GBK 编码错误
|
||||
|
||||
**现象**: `UnicodeEncodeError` 处理中文服务器日志时
|
||||
|
||||
**修复**:
|
||||
```python
|
||||
import sys, io
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
```
|
||||
|
||||
### 云数据库密码特殊字符
|
||||
|
||||
密码 `!Rjb12191` 中的 `!` 在 bash heredoc 中会被解释。用 base64 编码传递或通过 Python 变量直接引用。
|
||||
|
||||
---
|
||||
|
||||
## 十、部署检查清单
|
||||
|
||||
每次部署后确认:
|
||||
|
||||
```bash
|
||||
# 1. 后端健康检查
|
||||
curl -s -o /dev/null -w "%{http_code}" http://localhost:8039
|
||||
|
||||
# 2. 前端健康检查
|
||||
curl -s -o /dev/null -w "%{http_code}" http://localhost:8050
|
||||
|
||||
# 3. 容器状态
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}"
|
||||
|
||||
# 4. 数据库连接(通过 API 间接验证)
|
||||
# 调用任意一个 list 接口确认能正常返回数据
|
||||
|
||||
# 5. Redis 连接
|
||||
docker exec rlz-redis redis-cli PING
|
||||
# 预期返回: PONG
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十一、关键密码与地址速查
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| 服务器 SSH | `ssh renjianbo@101.43.95.130` 密码 `123456` |
|
||||
| Gitea | `http://101.43.95.130:3001` admin/123456 |
|
||||
| 云 MySQL | `gz-cynosdbmysql-grp-d26pzce5.sql.tencentcdb.com:24936` root/!Rjb12191 |
|
||||
| 管理后台 | `http://101.43.95.130:8050` admin/admin123 |
|
||||
| Redis CLI | `docker exec -it rlz-redis redis-cli` |
|
||||
| Drone CI | `http://101.43.95.130:3002` |
|
||||
@@ -53,6 +53,29 @@
|
||||
|
||||
---
|
||||
|
||||
### 8. 认证管理增强 (Issue #18) — 2026-05-17
|
||||
|
||||
- 后端新增 `PUT /system/renzheng/approve/{id}` 审核通过接口
|
||||
- 后端新增 `PUT /system/renzheng/reject/{id}` 审核驳回接口
|
||||
- 前端重写认证管理页面:状态筛选、彩色标签、通过/驳回操作、驳回原因弹窗、详情弹窗
|
||||
- 创建 `sys_user_renzheng` 表(rlz 和 rlz_dev 两库)
|
||||
- 补充 `renzheng.js` API 模块(approveRenzheng / rejectRenzheng)
|
||||
|
||||
### 9. 菜单结构调整 (Issue #21) — 2026-05-17
|
||||
|
||||
- 新增顶级菜单 **业务管理**(医院管理 / 订单管理 / 认证管理 / 服务类型)
|
||||
- 新增顶级菜单 **财务管理**(退款审核 / 结算管理 / 收入统计)
|
||||
- 服务类型从系统管理移至业务管理
|
||||
- 为医院管理、订单管理、认证管理添加完整权限按钮
|
||||
- 所有菜单授权给管理员角色
|
||||
|
||||
### 10. order_view 修复 — 2026-05-17
|
||||
|
||||
- `order_view` 缺少 `userb_age`/`userb_nation`/`userc_age`/`userc_nation` 列导致订单 API 500 错误
|
||||
- 通过 `CREATE OR REPLACE VIEW` 用 `NULL AS` 占位缺失列,两库均已修复
|
||||
|
||||
---
|
||||
|
||||
## 后续需要手动完成的
|
||||
|
||||
| 事项 | 操作 |
|
||||
|
||||
Reference in New Issue
Block a user