feat: 部署到服务器+移动端适配+帮助系统RAG集成+网页图标
- 部署脚本 deploy.sh:一键部署到 154.8.162.18,端口3400,Nginx conf.d - 应用名改为「企业用工专家」(登录/注册/侧边栏/员工端等6处) - 移动端适配修复:Attendance/Dashboard/Settings/Contracts 6处grid响应式 - 帮助系统:HelpModal 组件 + TopNav 帮助图标 + RAG语义搜索集成 - 后端 rag.service.ts 新增23条帮助种子数据 + searchHelp() - 前端搜索调用 /ai/rag/help-search,RAG不可用时回退关键词匹配 - 网页图标 favicon.svg(indigo主色+建筑人形图案) - index.html 标题改为「企业用工专家」
This commit is contained in:
@@ -0,0 +1,386 @@
|
||||
#!/bin/bash
|
||||
# 企业用工专家 一键部署脚本
|
||||
# 部署到 154.8.162.18 -> /var/www/turbohr/ -> https://on.hr8ai.top/
|
||||
#
|
||||
# 用法: bash deploy.sh
|
||||
# 首次部署: bash deploy.sh --init
|
||||
|
||||
set -e
|
||||
|
||||
# ===========================================
|
||||
# 配置区
|
||||
# ===========================================
|
||||
REMOTE_HOST="154.8.162.18"
|
||||
REMOTE_USER="root"
|
||||
REMOTE_PASS="Why_701208"
|
||||
REMOTE_DIR="/var/www/turbohr"
|
||||
DOMAIN="on.hr8ai.top"
|
||||
APP_NAME="turbohr"
|
||||
BACKEND_PORT=3400
|
||||
LOCAL_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${GREEN}[deploy]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[warn]${NC} $1"; }
|
||||
err() { echo -e "${RED}[error]${NC} $1"; }
|
||||
|
||||
# 检查 sshpass
|
||||
if ! command -v sshpass &> /dev/null; then
|
||||
err "请先安装 sshpass: macOS -> brew install hudochenkov/sshpass/sshpass"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SSH_CMD="sshpass -p '$REMOTE_PASS' ssh -o StrictHostKeyChecking=no ${REMOTE_USER}@${REMOTE_HOST}"
|
||||
SCP_CMD="sshpass -p '$REMOTE_PASS' scp -o StrictHostKeyChecking=no"
|
||||
|
||||
# ===========================================
|
||||
# 远程执行命令的封装
|
||||
# ===========================================
|
||||
remote_exec() {
|
||||
sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" "$1"
|
||||
}
|
||||
|
||||
remote_copy() {
|
||||
sshpass -p "$REMOTE_PASS" scp -o StrictHostKeyChecking=no -r "$1" "${REMOTE_USER}@${REMOTE_HOST}:$2"
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# 首次初始化:创建数据库 + Nginx 配置(系统依赖已预装)
|
||||
# ===========================================
|
||||
init_server() {
|
||||
log "========== 首次初始化服务器 =========="
|
||||
|
||||
remote_exec "
|
||||
set -e
|
||||
|
||||
# 服务器已有: Node.js, PM2, PostgreSQL 15, Nginx, Redis, 宝塔面板
|
||||
# 端口 3000-3300 已被占用,本应用使用端口 ${BACKEND_PORT}
|
||||
|
||||
echo '[1/4] 创建 PostgreSQL 数据库...'
|
||||
sudo -u postgres psql -c \"CREATE DATABASE turbohr;\" 2>/dev/null || echo ' 数据库 turbohr 已存在'
|
||||
sudo -u postgres psql -c \"CREATE USER turbohr WITH PASSWORD 'turbohr2026';\" 2>/dev/null || echo ' 用户 turbohr 已存在'
|
||||
sudo -u postgres psql -c \"GRANT ALL PRIVILEGES ON DATABASE turbohr TO turbohr;\" 2>/dev/null || true
|
||||
sudo -u postgres psql -c \"ALTER USER turbohr WITH SUPERUSER;\" 2>/dev/null || true
|
||||
echo ' PostgreSQL 数据库 turbohr 就绪'
|
||||
|
||||
echo '[2/4] 创建部署目录...'
|
||||
mkdir -p ${REMOTE_DIR}/backend
|
||||
mkdir -p ${REMOTE_DIR}/frontend/dist
|
||||
mkdir -p ${REMOTE_DIR}/backend/uploads
|
||||
|
||||
echo '[3/4] 配置 Nginx (conf.d)...'
|
||||
cat > /etc/nginx/conf.d/${DOMAIN}.conf << 'NGINXEOF'
|
||||
server {
|
||||
listen 80;
|
||||
server_name __DOMAIN__;
|
||||
|
||||
# 前端静态文件
|
||||
location / {
|
||||
root __REMOTE_DIR__/frontend/dist;
|
||||
index index.html;
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
|
||||
# 静态资源缓存
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)\$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control \"public, immutable\";
|
||||
}
|
||||
}
|
||||
|
||||
# 后端 API 反向代理
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:__BACKEND_PORT__;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
|
||||
# SSE 流式输出支持(AI 对话)
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
proxy_set_header Connection '';
|
||||
|
||||
# 超时设置(AI 生成可能较慢)
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
|
||||
# 文件上传
|
||||
client_max_body_size 10m;
|
||||
}
|
||||
|
||||
# 上传文件访问
|
||||
location /uploads/ {
|
||||
alias __REMOTE_DIR__/backend/uploads/;
|
||||
}
|
||||
|
||||
# 安全头
|
||||
add_header X-Frame-Options \"SAMEORIGIN\";
|
||||
add_header X-Content-Type-Options \"nosniff\";
|
||||
add_header X-XSS-Protection \"1; mode=block\";
|
||||
|
||||
# Gzip 压缩
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;
|
||||
gzip_min_length 1000;
|
||||
}
|
||||
NGINXEOF
|
||||
|
||||
# 替换占位符
|
||||
sed -i 's|__DOMAIN__|${DOMAIN}|g' /etc/nginx/conf.d/${DOMAIN}.conf
|
||||
sed -i 's|__REMOTE_DIR__|${REMOTE_DIR}|g' /etc/nginx/conf.d/${DOMAIN}.conf
|
||||
sed -i 's|__BACKEND_PORT__|${BACKEND_PORT}|g' /etc/nginx/conf.d/${DOMAIN}.conf
|
||||
|
||||
nginx -t && systemctl reload nginx
|
||||
|
||||
echo '[4/4] 检查 certbot...'
|
||||
if ! command -v certbot &> /dev/null; then
|
||||
echo ' certbot 未安装,SSL 将在部署后手动配置'
|
||||
else
|
||||
echo ' certbot 已安装'
|
||||
fi
|
||||
|
||||
echo ''
|
||||
echo '========================================='
|
||||
echo ' 系统初始化完成!'
|
||||
echo ' 数据库: turbohr (用户: turbohr)'
|
||||
echo ' 端口: ${BACKEND_PORT} (避开 3000-3300)'
|
||||
echo ' Nginx: ${DOMAIN} -> :80 (conf.d)'
|
||||
echo '========================================='
|
||||
"
|
||||
|
||||
log "系统初始化完成"
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# 构建前端
|
||||
# ===========================================
|
||||
build_frontend() {
|
||||
log "========== 构建前端 =========="
|
||||
|
||||
cd "${LOCAL_DIR}/frontend"
|
||||
|
||||
if [ ! -d node_modules ]; then
|
||||
log "安装前端依赖..."
|
||||
npm install
|
||||
fi
|
||||
|
||||
log "构建前端..."
|
||||
npm run build
|
||||
|
||||
if [ ! -d dist ]; then
|
||||
err "前端构建失败,dist 目录不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "前端构建完成: dist/ ($(du -sh dist | cut -f1))"
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# 构建后端
|
||||
# ===========================================
|
||||
build_backend() {
|
||||
log "========== 构建后端 =========="
|
||||
|
||||
cd "${LOCAL_DIR}/backend"
|
||||
|
||||
if [ ! -d node_modules ]; then
|
||||
log "安装后端依赖..."
|
||||
npm install
|
||||
fi
|
||||
|
||||
log "生成 Prisma Client..."
|
||||
npx prisma generate
|
||||
|
||||
log "编译 TypeScript..."
|
||||
npx tsc
|
||||
|
||||
if [ ! -d dist ]; then
|
||||
err "后端编译失败,dist 目录不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "后端编译完成: dist/ ($(du -sh dist | cut -f1))"
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# 部署到服务器
|
||||
# ===========================================
|
||||
deploy_files() {
|
||||
log "========== 部署文件到服务器 =========="
|
||||
|
||||
# 同步后端(排除 node_modules、.env、uploads、备份文件)
|
||||
log "[1/4] 同步后端代码..."
|
||||
sshpass -p "$REMOTE_PASS" rsync -avz --delete \
|
||||
--exclude='node_modules' \
|
||||
--exclude='.env' \
|
||||
--exclude='uploads/*' \
|
||||
--exclude='*.dump' \
|
||||
--exclude='backups' \
|
||||
"${LOCAL_DIR}/backend/" "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/backend/"
|
||||
|
||||
# 同步后端 package.json 和 prisma(确保依赖和 schema 同步)
|
||||
log "[2/4] 同步前端静态文件..."
|
||||
sshpass -p "$REMOTE_PASS" rsync -avz --delete \
|
||||
"${LOCAL_DIR}/frontend/dist/" "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/frontend/dist/"
|
||||
|
||||
# 在服务器上安装后端依赖 + 生成 Prisma Client
|
||||
log "[3/4] 服务器端安装依赖..."
|
||||
remote_exec "cd ${REMOTE_DIR}/backend && npm install --production=false 2>&1 | tail -5"
|
||||
|
||||
log "[4/4] 生成 Prisma Client..."
|
||||
remote_exec "cd ${REMOTE_DIR}/backend && npx prisma generate 2>&1 | tail -3"
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# 配置 .env(首次或更新)
|
||||
# ===========================================
|
||||
setup_env() {
|
||||
log "========== 配置环境变量 =========="
|
||||
|
||||
# 检查远程是否已有 .env,如果没有则创建
|
||||
remote_exec "
|
||||
if [ ! -f ${REMOTE_DIR}/backend/.env ]; then
|
||||
echo '创建 .env 文件...'
|
||||
cat > ${REMOTE_DIR}/backend/.env << 'ENVEOF'
|
||||
# 数据库
|
||||
DATABASE_URL=postgresql://turbohr:turbohr2026@localhost:5432/turbohr?schema=public
|
||||
|
||||
# JWT
|
||||
JWT_SECRET=turbohr-jwt-secret-2026-change-me
|
||||
JWT_REFRESH_SECRET=turbohr-refresh-secret-2026-change-me
|
||||
|
||||
# DashScope (通义千问) - 请填写实际 API Key
|
||||
DASHSCOPE_API_KEY=sk-xxx
|
||||
DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
|
||||
# 加密
|
||||
ENCRYPTION_KEY=turbohr-32byte-encryption-key-here
|
||||
|
||||
# 存储
|
||||
SUPABASE_URL=
|
||||
SUPABASE_KEY=
|
||||
|
||||
# 部署
|
||||
PORT=${BACKEND_PORT}
|
||||
CORS_ORIGIN=https://${DOMAIN}
|
||||
ENVEOF
|
||||
echo '.env 已创建,请编辑 ${REMOTE_DIR}/backend/.env 填写实际配置'
|
||||
else
|
||||
echo '.env 已存在,跳过创建'
|
||||
fi
|
||||
"
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# 数据库迁移
|
||||
# ===========================================
|
||||
migrate_db() {
|
||||
log "========== 数据库迁移 =========="
|
||||
remote_exec "cd ${REMOTE_DIR}/backend && npx prisma db push --accept-data-loss 2>&1 | tail -10"
|
||||
log "数据库 schema 同步完成"
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# 配置 PM2 + 启动服务
|
||||
# ===========================================
|
||||
start_service() {
|
||||
log "========== 启动服务 =========="
|
||||
|
||||
remote_exec "
|
||||
cd ${REMOTE_DIR}/backend
|
||||
|
||||
# 停止旧进程
|
||||
pm2 delete ${APP_NAME} 2>/dev/null || true
|
||||
|
||||
# 使用 tsx 运行 TypeScript 源码(避免 ESM 扩展名问题)
|
||||
pm2 start npx --name ${APP_NAME} --max-memory-restart 512M -- tsx src/index.ts
|
||||
|
||||
# 保存 PM2 配置
|
||||
pm2 save
|
||||
|
||||
# 配置开机自启
|
||||
pm2 startup systemd -u root --hp /root 2>/dev/null | tail -3
|
||||
|
||||
echo 'PM2 进程:'
|
||||
pm2 list
|
||||
"
|
||||
|
||||
log "服务已启动"
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# 配置 SSL 证书
|
||||
# ===========================================
|
||||
setup_ssl() {
|
||||
log "========== 配置 SSL 证书 =========="
|
||||
|
||||
remote_exec "
|
||||
# 使用 --nginx 插件自动配置 SSL
|
||||
certbot --nginx -d ${DOMAIN} --non-interactive --agree-tos --register-unsafely-without-email --redirect 2>&1 && echo 'SSL 证书配置成功' || echo 'SSL 证书配置失败,可稍后手动执行: certbot --nginx -d ${DOMAIN}'
|
||||
"
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# 验证部署
|
||||
# ===========================================
|
||||
verify() {
|
||||
log "========== 验证部署 =========="
|
||||
|
||||
# 检查后端
|
||||
BACKEND_CODE=$(remote_exec "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:${BACKEND_PORT}/api/v1/health 2>/dev/null || echo '000'")
|
||||
log "后端 /api/v1/health -> HTTP ${BACKEND_CODE}"
|
||||
|
||||
# 检查前端
|
||||
FRONTEND_CODE=$(remote_exec "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/ 2>/dev/null || echo '000'")
|
||||
log "前端 (Nginx) -> HTTP ${FRONTEND_CODE}"
|
||||
|
||||
# 检查域名
|
||||
DOMAIN_CODE=$(curl -s -o /dev/null -w '%{http_code}' "https://${DOMAIN}/" 2>/dev/null || echo '000')
|
||||
log "域名 https://${DOMAIN} -> HTTP ${DOMAIN_CODE}"
|
||||
|
||||
# PM2 状态
|
||||
remote_exec "pm2 describe ${APP_NAME} 2>/dev/null | grep -E 'status|restarts|uptime|pid'"
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo " 企业用工专家 部署完成!"
|
||||
echo " 访问地址: https://${DOMAIN}/"
|
||||
echo " 后端 API: https://${DOMAIN}/api/v1/"
|
||||
echo " PM2 管理: pm2 list (服务器上)"
|
||||
echo "========================================="
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# 主流程
|
||||
# ===========================================
|
||||
IS_INIT=false
|
||||
if [ "$1" = "--init" ]; then
|
||||
IS_INIT=true
|
||||
fi
|
||||
|
||||
log "开始部署 企业用工专家 到 ${REMOTE_HOST}"
|
||||
log "域名: ${DOMAIN} 目录: ${REMOTE_DIR}"
|
||||
echo ""
|
||||
|
||||
if [ "$IS_INIT" = true ]; then
|
||||
init_server
|
||||
setup_env
|
||||
echo ""
|
||||
warn "首次初始化完成,请先编辑服务器上的 ${REMOTE_DIR}/backend/.env 填写实际配置"
|
||||
warn "然后重新运行: bash deploy.sh"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
build_frontend
|
||||
build_backend
|
||||
deploy_files
|
||||
setup_env
|
||||
migrate_db
|
||||
start_service
|
||||
verify
|
||||
Reference in New Issue
Block a user