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:
renjianbo
2026-05-17 01:18:22 +08:00
parent 46e9ade6b5
commit 68f830c75c
2 changed files with 563 additions and 0 deletions

View 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` |

View File

@@ -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` 占位缺失列,两库均已修复
---
## 后续需要手动完成的
| 事项 | 操作 |