This commit is contained in:
mangomqy
2025-11-13 02:54:06 +00:00
commit c5e51ed069
254 changed files with 54901 additions and 0 deletions

View File

@ -0,0 +1,273 @@
#!/bin/bash
# ==================================================
# OCDP Backend - Docker Compose 快速启动脚本
# ==================================================
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 打印带颜色的消息
print_info() {
echo -e "${BLUE} $1${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
print_header() {
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
}
# 检查 Docker
check_docker() {
if ! command -v docker &> /dev/null; then
print_error "Docker 未安装,请先安装 Docker"
echo "访问: https://docs.docker.com/get-docker/"
exit 1
fi
if ! docker compose version &> /dev/null; then
print_error "Docker Compose 未安装,请先安装 Docker Compose V2"
echo "访问: https://docs.docker.com/compose/install/"
exit 1
fi
print_success "Docker 环境检查通过"
}
# 检查环境变量文件
check_env_file() {
if [ ! -f .env ]; then
print_warning ".env 文件不存在,正在从 env.example 创建..."
cp env.example .env
print_success ".env 文件已创建"
print_warning "请编辑 .env 文件,配置必要的环境变量(特别是生产环境的密钥)"
echo ""
read -p "按回车键继续..."
else
print_success ".env 文件已存在"
fi
}
# 显示菜单
show_menu() {
print_header "OCDP Backend - Docker Compose 快速启动"
echo "请选择运行模式:"
echo ""
echo " 1) Mock 模式 (无需数据库,快速测试)"
echo " 2) 生产模式 (完整功能,需要数据库)"
echo " 3) 开发模式 (热重载,需要数据库)"
echo " 4) 查看服务状态"
echo " 5) 查看日志"
echo " 6) 停止所有服务"
echo " 7) 启动 pgAdmin (数据库管理)"
echo " 0) 退出"
echo ""
}
# 启动 Mock 模式
start_mock() {
print_header "启动 Mock 模式"
print_info "正在启动服务..."
docker compose --profile mock up -d
echo ""
print_success "Mock 模式启动成功!"
echo ""
print_info "服务访问地址:"
echo " 📍 API: http://localhost:8080/api/v1"
echo " 📍 Health: http://localhost:8080/health"
echo ""
print_info "查看日志: docker compose logs -f backend-mock"
print_info "停止服务: docker compose down"
}
# 启动生产模式
start_production() {
print_header "启动生产模式"
print_info "正在启动数据库和后端服务..."
docker compose --profile production up -d
echo ""
print_info "等待服务就绪..."
sleep 5
# 检查服务健康
if curl -sf http://localhost:8080/health > /dev/null 2>&1; then
print_success "生产模式启动成功!"
else
print_warning "服务正在启动中,请稍候..."
fi
echo ""
print_info "服务访问地址:"
echo " 📍 API: http://localhost:8080/api/v1"
echo " 📍 Health: http://localhost:8080/health"
echo ""
print_info "数据库信息:"
echo " 📍 Host: localhost"
echo " 📍 Port: 5432"
echo " 📍 Database: ocdp"
echo ""
print_info "查看日志: docker compose logs -f backend"
print_info "停止服务: docker compose down"
}
# 启动开发模式
start_development() {
print_header "启动开发模式"
print_info "正在启动开发环境(支持热重载)..."
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
echo ""
print_info "等待服务就绪..."
sleep 5
print_success "开发模式启动成功!"
echo ""
print_info "服务访问地址:"
echo " 📍 API: http://localhost:8080/api/v1"
echo " 📍 Health: http://localhost:8080/health"
echo ""
print_info "开发模式特性:"
echo " 🔥 支持代码热重载(修改代码自动重启)"
echo " 📂 源代码已挂载到容器"
echo ""
print_info "查看日志: docker compose logs -f backend-dev"
print_info "停止服务: docker compose down"
}
# 查看状态
show_status() {
print_header "服务状态"
docker compose ps
}
# 查看日志
show_logs() {
print_header "查看日志"
echo "实时查看日志(按 Ctrl+C 退出)..."
echo ""
sleep 2
docker compose logs -f
}
# 停止服务
stop_services() {
print_header "停止服务"
print_info "正在停止所有服务..."
docker compose down
print_success "所有服务已停止"
}
# 启动 pgAdmin
start_pgadmin() {
print_header "启动 pgAdmin"
print_info "正在启动 pgAdmin..."
docker compose --profile tools up -d pgadmin
echo ""
print_success "pgAdmin 启动成功!"
echo ""
print_info "访问地址: http://localhost:5050"
print_info "登录信息:"
echo " 📧 邮箱: admin@ocdp.local"
echo " 🔑 密码: admin"
echo ""
print_info "连接数据库配置:"
echo " 📍 Host: postgres"
echo " 📍 Port: 5432"
echo " 📍 Database: ocdp"
echo " 📍 Username: postgres"
echo " 📍 Password: postgres"
}
# 主函数
main() {
# 获取脚本目录
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$SCRIPT_DIR/.."
# 检查环境
check_docker
check_env_file
while true; do
show_menu
read -p "请选择 [0-7]: " choice
case $choice in
1)
start_mock
echo ""
read -p "按回车键返回菜单..."
;;
2)
start_production
echo ""
read -p "按回车键返回菜单..."
;;
3)
start_development
echo ""
read -p "按回车键返回菜单..."
;;
4)
show_status
echo ""
read -p "按回车键返回菜单..."
;;
5)
show_logs
;;
6)
stop_services
echo ""
read -p "按回车键返回菜单..."
;;
7)
start_pgadmin
echo ""
read -p "按回车键返回菜单..."
;;
0)
print_info "再见!"
exit 0
;;
*)
print_error "无效的选择,请重试"
sleep 1
;;
esac
done
}
# 运行主函数
main

View File

@ -0,0 +1,235 @@
#!/bin/bash
# generate-bootstrap-config.sh
# 从 kubeconfig 生成 bootstrap 配置文件
set -e
echo "🔧 OCDP Bootstrap Configuration Generator"
echo "========================================"
echo ""
# 检查依赖
command -v kubectl >/dev/null 2>&1 || { echo "❌ kubectl is required but not installed. Aborting." >&2; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "❌ jq is required but not installed. Aborting." >&2; exit 1; }
# 默认输出文件
OUTPUT_FILE="${1:-config/bootstrap.json}"
# 临时文件
TMP_FILE=$(mktemp)
# 创建基础配置结构
cat > "$TMP_FILE" <<'EOF'
{
"enabled": true,
"users": [
{
"username": "admin",
"password": "admin123",
"email": "admin@example.com"
}
],
"registries": [],
"clusters": []
}
EOF
echo "📋 请按提示输入信息..."
echo ""
# ===== Registries 配置 =====
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📦 Registry 配置"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
read -p "是否添加 Registry? (y/n) [y]: " ADD_REGISTRY
ADD_REGISTRY=${ADD_REGISTRY:-y}
if [[ "$ADD_REGISTRY" == "y" ]]; then
read -p "Registry 名称 [harbor-bwgdi]: " REGISTRY_NAME
REGISTRY_NAME=${REGISTRY_NAME:-harbor-bwgdi}
read -p "Registry URL [https://harbor.bwgdi.com]: " REGISTRY_URL
REGISTRY_URL=${REGISTRY_URL:-https://harbor.bwgdi.com}
read -p "Registry 描述 [BWGDI Harbor Registry]: " REGISTRY_DESC
REGISTRY_DESC=${REGISTRY_DESC:-"BWGDI Harbor Registry"}
read -p "Registry 用户名 [admin]: " REGISTRY_USER
REGISTRY_USER=${REGISTRY_USER:-admin}
read -sp "Registry 密码: " REGISTRY_PASS
echo ""
read -p "是否跳过 SSL 验证? (y/n) [n]: " REGISTRY_INSECURE
REGISTRY_INSECURE=${REGISTRY_INSECURE:-n}
if [[ "$REGISTRY_INSECURE" == "y" ]]; then
INSECURE_VALUE="true"
else
INSECURE_VALUE="false"
fi
# 添加 Registry 到配置
TMP_REGISTRY=$(cat <<JSON
{
"name": "$REGISTRY_NAME",
"url": "$REGISTRY_URL",
"description": "$REGISTRY_DESC",
"username": "$REGISTRY_USER",
"password": "$REGISTRY_PASS",
"insecure": $INSECURE_VALUE
}
JSON
)
jq ".registries += [$TMP_REGISTRY]" "$TMP_FILE" > "${TMP_FILE}.tmp" && mv "${TMP_FILE}.tmp" "$TMP_FILE"
echo "✅ Registry '$REGISTRY_NAME' 已添加"
fi
echo ""
# ===== Clusters 配置 =====
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "☸️ Kubernetes Cluster 配置"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
read -p "是否从 kubeconfig 导入 Cluster? (y/n) [y]: " ADD_CLUSTER
ADD_CLUSTER=${ADD_CLUSTER:-y}
CLUSTER_INDEX=0
while [[ "$ADD_CLUSTER" == "y" ]]; do
echo ""
echo "--- Cluster $(($CLUSTER_INDEX + 1)) ---"
read -p "Cluster 名称 [cluster$(($CLUSTER_INDEX + 1))]: " CLUSTER_NAME
CLUSTER_NAME=${CLUSTER_NAME:-cluster$(($CLUSTER_INDEX + 1))}
read -p "Cluster 描述: " CLUSTER_DESC
echo ""
echo "📂 请选择数据源:"
echo " 1) 从 kubeconfig 文件提取"
echo " 2) 手动输入"
read -p "选择 [1]: " DATA_SOURCE
DATA_SOURCE=${DATA_SOURCE:-1}
if [[ "$DATA_SOURCE" == "1" ]]; then
# 从 kubeconfig 提取
read -p "kubeconfig 文件路径 [~/.kube/config]: " KUBECONFIG_PATH
KUBECONFIG_PATH=${KUBECONFIG_PATH:-~/.kube/config}
KUBECONFIG_PATH="${KUBECONFIG_PATH/#\~/$HOME}"
if [[ ! -f "$KUBECONFIG_PATH" ]]; then
echo "❌ kubeconfig 文件不存在: $KUBECONFIG_PATH"
continue
fi
# 列出可用的 contexts
echo ""
echo "📋 可用的 contexts:"
kubectl --kubeconfig="$KUBECONFIG_PATH" config get-contexts
echo ""
read -p "选择 context (留空使用当前 context): " CONTEXT_NAME
if [[ -n "$CONTEXT_NAME" ]]; then
KUBECTL_OPTS="--kubeconfig=$KUBECONFIG_PATH --context=$CONTEXT_NAME"
else
KUBECTL_OPTS="--kubeconfig=$KUBECONFIG_PATH"
fi
# 提取数据
echo "🔍 正在提取 Cluster 配置..."
CLUSTER_HOST=$(kubectl $KUBECTL_OPTS config view --raw -o jsonpath='{.clusters[0].cluster.server}')
CLUSTER_CA=$(kubectl $KUBECTL_OPTS config view --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')
CLUSTER_CERT=$(kubectl $KUBECTL_OPTS config view --raw -o jsonpath='{.users[0].user.client-certificate-data}')
CLUSTER_KEY=$(kubectl $KUBECTL_OPTS config view --raw -o jsonpath='{.users[0].user.client-key-data}')
echo " ✓ Server: $CLUSTER_HOST"
echo " ✓ CA Data: ${CLUSTER_CA:0:50}..."
echo " ✓ Cert Data: ${CLUSTER_CERT:0:50}..."
echo " ✓ Key Data: ${CLUSTER_KEY:0:50}..."
else
# 手动输入
read -p "API Server 地址: " CLUSTER_HOST
echo "请输入 CA 证书数据 (Base64 编码,多行输入以空行结束):"
CLUSTER_CA=""
while IFS= read -r line; do
[[ -z "$line" ]] && break
CLUSTER_CA="${CLUSTER_CA}${line}"
done
echo "请输入客户端证书数据 (Base64 编码,多行输入以空行结束):"
CLUSTER_CERT=""
while IFS= read -r line; do
[[ -z "$line" ]] && break
CLUSTER_CERT="${CLUSTER_CERT}${line}"
done
echo "请输入客户端密钥数据 (Base64 编码,多行输入以空行结束):"
CLUSTER_KEY=""
while IFS= read -r line; do
[[ -z "$line" ]] && break
CLUSTER_KEY="${CLUSTER_KEY}${line}"
done
fi
# 添加 Cluster 到配置
TMP_CLUSTER=$(cat <<JSON
{
"name": "$CLUSTER_NAME",
"host": "$CLUSTER_HOST",
"description": "$CLUSTER_DESC",
"caData": "$CLUSTER_CA",
"certData": "$CLUSTER_CERT",
"keyData": "$CLUSTER_KEY"
}
JSON
)
jq ".clusters += [$TMP_CLUSTER]" "$TMP_FILE" > "${TMP_FILE}.tmp" && mv "${TMP_FILE}.tmp" "$TMP_FILE"
echo "✅ Cluster '$CLUSTER_NAME' 已添加"
CLUSTER_INDEX=$(($CLUSTER_INDEX + 1))
read -p "是否继续添加 Cluster? (y/n) [n]: " ADD_CLUSTER
ADD_CLUSTER=${ADD_CLUSTER:-n}
done
echo ""
# ===== 保存配置 =====
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "💾 保存配置"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# 格式化 JSON
jq '.' "$TMP_FILE" > "$OUTPUT_FILE"
rm "$TMP_FILE"
echo "✅ Bootstrap 配置已保存到: $OUTPUT_FILE"
echo ""
# 显示配置摘要
echo "📊 配置摘要:"
echo " - 用户数: $(jq '.users | length' "$OUTPUT_FILE")"
echo " - Registry 数: $(jq '.registries | length' "$OUTPUT_FILE")"
echo " - Cluster 数: $(jq '.clusters | length' "$OUTPUT_FILE")"
echo ""
echo "🚀 接下来的步骤:"
echo " 1. 检查配置文件: cat $OUTPUT_FILE"
echo " 2. 启动应用: make run-mock"
echo " 3. 验证数据:"
echo " curl http://localhost:8080/api/v1/registries"
echo " curl http://localhost:8080/api/v1/clusters"
echo ""
echo "✨ 完成!"

129
backend/scripts/init-db.sql Normal file
View File

@ -0,0 +1,129 @@
-- OCDP Backend PostgreSQL 数据库初始化脚本
-- 创建数据库和必要的表结构
-- ===== Users 表 =====
CREATE TABLE IF NOT EXISTS users (
id VARCHAR(36) PRIMARY KEY,
username VARCHAR(255) NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
email VARCHAR(255) NOT NULL,
revoked_after TIMESTAMP NOT NULL DEFAULT '1970-01-01 00:00:00',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
CREATE INDEX IF NOT EXISTS idx_users_revoked_after ON users(revoked_after);
COMMENT ON TABLE users IS '用户表';
COMMENT ON COLUMN users.id IS '用户 ID (UUID)';
COMMENT ON COLUMN users.username IS '用户名(唯一)';
COMMENT ON COLUMN users.password_hash IS '密码哈希';
COMMENT ON COLUMN users.email IS '邮箱';
-- ===== Clusters 表 =====
CREATE TABLE IF NOT EXISTS clusters (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
host TEXT NOT NULL,
ca_data TEXT,
cert_data TEXT,
key_data TEXT,
token TEXT,
description TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_clusters_name ON clusters(name);
COMMENT ON TABLE clusters IS 'Kubernetes 集群表';
COMMENT ON COLUMN clusters.id IS '集群 ID (UUID)';
COMMENT ON COLUMN clusters.name IS '集群名称(唯一)';
COMMENT ON COLUMN clusters.host IS 'Kubernetes API Server URL';
COMMENT ON COLUMN clusters.ca_data IS 'CA 证书(加密存储)';
COMMENT ON COLUMN clusters.cert_data IS '客户端证书(加密存储)';
COMMENT ON COLUMN clusters.key_data IS '客户端密钥(加密存储)';
COMMENT ON COLUMN clusters.token IS 'Bearer Token加密存储';
-- ===== Registries 表 =====
CREATE TABLE IF NOT EXISTS registries (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
url TEXT NOT NULL,
description TEXT,
username VARCHAR(255),
password TEXT,
insecure BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_registries_name ON registries(name);
COMMENT ON TABLE registries IS 'OCI Registry 表';
COMMENT ON COLUMN registries.id IS 'Registry ID (UUID)';
COMMENT ON COLUMN registries.name IS 'Registry 名称(唯一)';
COMMENT ON COLUMN registries.url IS 'Registry URL';
COMMENT ON COLUMN registries.username IS '认证用户名';
COMMENT ON COLUMN registries.password IS '认证密码(加密存储)';
COMMENT ON COLUMN registries.insecure IS '是否跳过 TLS 验证';
-- ===== Instances 表 =====
CREATE TABLE IF NOT EXISTS instances (
id VARCHAR(36) PRIMARY KEY,
cluster_id VARCHAR(36) NOT NULL,
name VARCHAR(255) NOT NULL,
namespace VARCHAR(255) NOT NULL,
registry_id VARCHAR(36) NOT NULL,
repository TEXT NOT NULL,
chart VARCHAR(255) NOT NULL,
version VARCHAR(255) NOT NULL,
description TEXT,
values JSONB,
values_yaml TEXT,
status VARCHAR(50) NOT NULL,
status_reason TEXT,
last_operation VARCHAR(50),
last_error TEXT,
revision INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_cluster FOREIGN KEY (cluster_id) REFERENCES clusters(id) ON DELETE CASCADE,
CONSTRAINT fk_registry FOREIGN KEY (registry_id) REFERENCES registries(id) ON DELETE CASCADE,
CONSTRAINT unique_cluster_name UNIQUE (cluster_id, name, namespace)
);
CREATE INDEX IF NOT EXISTS idx_instances_cluster ON instances(cluster_id);
CREATE INDEX IF NOT EXISTS idx_instances_registry ON instances(registry_id);
CREATE INDEX IF NOT EXISTS idx_instances_name ON instances(name);
CREATE INDEX IF NOT EXISTS idx_instances_status ON instances(status);
COMMENT ON TABLE instances IS 'Helm 应用实例表';
COMMENT ON COLUMN instances.id IS '实例 ID (UUID)';
COMMENT ON COLUMN instances.cluster_id IS '所属集群 ID';
COMMENT ON COLUMN instances.name IS 'Helm Release 名称';
COMMENT ON COLUMN instances.namespace IS 'Kubernetes 命名空间';
COMMENT ON COLUMN instances.registry_id IS '所属 Registry ID';
COMMENT ON COLUMN instances.repository IS 'OCI Repository';
COMMENT ON COLUMN instances.chart IS 'Chart 名称';
COMMENT ON COLUMN instances.version IS 'Chart 版本';
COMMENT ON COLUMN instances.values IS 'Helm Values (JSON 格式)';
COMMENT ON COLUMN instances.values_yaml IS 'Helm Values (YAML 格式)';
COMMENT ON COLUMN instances.status IS '实例状态';
COMMENT ON COLUMN instances.status_reason IS '状态说明';
COMMENT ON COLUMN instances.last_operation IS '最后一次操作类型';
COMMENT ON COLUMN instances.last_error IS '最近一次错误信息';
COMMENT ON COLUMN instances.revision IS 'Helm Release Revision';
-- ===== 数据库版本表 =====
CREATE TABLE IF NOT EXISTS schema_migrations (
version VARCHAR(50) PRIMARY KEY,
applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE schema_migrations IS '数据库迁移版本记录';
-- 插入初始版本
INSERT INTO schema_migrations (version) VALUES ('v1.0.0')
ON CONFLICT (version) DO NOTHING;

View File

@ -0,0 +1,12 @@
-- Migration: add status_reason / last_operation / last_error columns to instances
-- Applies to PostgreSQL
BEGIN;
ALTER TABLE instances
ADD COLUMN IF NOT EXISTS status_reason TEXT,
ADD COLUMN IF NOT EXISTS last_operation VARCHAR(50),
ADD COLUMN IF NOT EXISTS last_error TEXT;
COMMIT;

View File

@ -0,0 +1,85 @@
#!/bin/bash
# OCDP Backend - Production 模式快速启动脚本
set -e
echo "🚀 OCDP Backend - Production 模式快速启动"
echo "========================================="
echo ""
# 检查 Docker
if ! command -v docker &> /dev/null; then
echo "❌ Docker 未安装,请先安装 Docker"
exit 1
fi
# 检查 docker compose
if ! docker compose version &> /dev/null; then
echo "❌ docker compose 未安装,请先安装 Docker Compose V2"
exit 1
fi
echo "✅ Docker 环境检查通过"
echo ""
# 获取项目根目录
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PROJECT_ROOT="$( cd "$SCRIPT_DIR/../.." && pwd )"
# 启动 PostgreSQL从根目录
echo "📦 启动 PostgreSQL..."
cd "$PROJECT_ROOT"
docker compose up -d postgres
echo "⏳ 等待 PostgreSQL 就绪..."
sleep 5
# 检查 PostgreSQL 是否就绪
docker compose exec -T postgres pg_isready -U postgres || {
echo "❌ PostgreSQL 启动失败"
exit 1
}
# 返回 backend 目录
cd "$PROJECT_ROOT/backend"
echo "✅ PostgreSQL 已就绪"
echo ""
# 设置环境变量
export ADAPTER_MODE=production
export DATABASE_URL="postgres://postgres:postgres@localhost:5432/ocdp?sslmode=disable"
export ENCRYPTION_KEY="default-encryption-key-change-me-32"
export JWT_SECRET="your-jwt-secret-key-change-in-production"
export PORT=8080
echo "🔧 环境配置:"
echo " - ADAPTER_MODE: $ADAPTER_MODE"
echo " - DATABASE_URL: $DATABASE_URL"
echo " - PORT: $PORT"
echo ""
# 编译
echo "🔨 编译应用..."
go build -o bin/ocdp-backend cmd/api/main.go
echo "✅ 编译完成"
echo ""
# 启动应用
echo "🚀 启动 OCDP Backend (Production 模式)..."
echo ""
echo "📍 服务地址:"
echo " - API: http://localhost:8080/api/v1"
echo " - Health: http://localhost:8080/health"
echo ""
echo "📍 数据库管理:"
echo " - pgAdmin: http://localhost:5050"
echo " Email: admin@ocdp.local"
echo " Password: admin"
echo ""
echo "✨ 按 Ctrl+C 停止服务"
echo ""
./bin/ocdp-backend

395
backend/scripts/test-all-modes.sh Executable file
View File

@ -0,0 +1,395 @@
#!/bin/bash
# ==================================================
# OCDP Backend - 三种模式测试脚本
# ==================================================
# 测试三种部署模式:
# 1. Mock 模式(无 docker composebackend 热重载)
# 2. Production 模式docker compose 仅依赖服务backend 热重载)
# 3. Production 模式docker compose 包含 backend
# ==================================================
set -e # 遇到错误立即退出
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${BLUE} $1${NC}"
}
log_success() {
echo -e "${GREEN}$1${NC}"
}
log_error() {
echo -e "${RED}$1${NC}"
}
log_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
log_section() {
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE} $1${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
}
# 等待服务启动
wait_for_service() {
local url=$1
local max_attempts=30
local attempt=1
log_info "等待服务启动: $url"
while [ $attempt -le $max_attempts ]; do
if curl -s -f "$url" > /dev/null 2>&1; then
log_success "服务已启动!"
return 0
fi
echo -n "."
sleep 1
attempt=$((attempt + 1))
done
log_error "服务启动超时"
return 1
}
# 测试健康检查
test_health() {
local mode=$1
log_info "测试健康检查..."
response=$(curl -s http://localhost:8080/health)
if echo "$response" | grep -q "healthy"; then
log_success "$mode 模式健康检查通过"
return 0
else
log_error "$mode 模式健康检查失败"
echo "响应: $response"
return 1
fi
}
# 测试 API
test_api() {
local mode=$1
log_info "测试 API..."
# 测试注册
register_response=$(curl -s -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"testuser'"$RANDOM"'","password":"test123","email":"test@example.com"}')
if echo "$register_response" | grep -q "id"; then
log_success "$mode 模式 API 注册测试通过"
else
log_warning "$mode 模式 API 注册测试失败(可能用户已存在)"
fi
# 测试登录
login_response=$(curl -s -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}')
if echo "$login_response" | grep -q "accessToken"; then
log_success "$mode 模式 API 登录测试通过"
return 0
else
log_error "$mode 模式 API 登录测试失败"
echo "响应: $login_response"
return 1
fi
}
# 清理环境
cleanup() {
log_info "清理环境..."
# 停止所有容器
docker compose down --remove-orphans > /dev/null 2>&1 || true
docker compose --profile backend down --remove-orphans > /dev/null 2>&1 || true
docker compose --profile mock down --remove-orphans > /dev/null 2>&1 || true
# 停止可能在运行的本地进程
pkill -f "tmp/main" > /dev/null 2>&1 || true
pkill -f "cmd/api/main.go" > /dev/null 2>&1 || true
sleep 2
log_success "清理完成"
}
# ==================================================
# 测试 1: Mock 模式(无 docker composebackend 热重载)
# ==================================================
test_mode_1() {
log_section "测试模式 1: Mock 模式(本地运行,热重载)"
log_info "配置:"
log_info " • 运行方式: 本地 Go"
log_info " • 适配器: Mock内存存储"
log_info " • 数据库: 无需"
log_info " • 热重载: 支持Air"
# 检查 Air 是否安装
if ! command -v air &> /dev/null; then
log_error "Air 未安装,跳过热重载测试"
log_info "安装命令: go install github.com/air-verse/air@latest"
return 1
fi
# 启动 Mock 模式(后台)
log_info "启动 Mock 模式..."
export ADAPTER_MODE=mock
export PORT=8080
export JWT_SECRET=test-secret
# 在后台启动
nohup air -c .air.toml > /tmp/ocdp-mock.log 2>&1 &
local pid=$!
log_info "进程 PID: $pid"
# 等待服务启动
if wait_for_service "http://localhost:8080/health"; then
# 测试服务
test_health "Mock"
test_api "Mock"
# 停止服务
log_info "停止服务..."
kill $pid 2>/dev/null || true
pkill -f "tmp/main" 2>/dev/null || true
sleep 2
log_success "模式 1 测试完成"
return 0
else
log_error "服务启动失败,查看日志: /tmp/ocdp-mock.log"
kill $pid 2>/dev/null || true
return 1
fi
}
# ==================================================
# 测试 2: Production 模式docker compose 仅依赖backend 热重载)
# ==================================================
test_mode_2() {
log_section "测试模式 2: Docker Compose 仅依赖服务(本地 backend 热重载)"
log_info "配置:"
log_info " • 数据库: Docker (PostgreSQL)"
log_info " • Backend: 本地 Go"
log_info " • 适配器: Production"
log_info " • 热重载: 支持Air"
# 检查 Air 是否安装
if ! command -v air &> /dev/null; then
log_error "Air 未安装,跳过热重载测试"
return 1
fi
# 启动 PostgreSQL
log_info "启动 PostgreSQL 容器..."
docker compose up -d postgres
# 等待数据库就绪
log_info "等待 PostgreSQL 启动..."
sleep 10
# 检查数据库健康状态
if ! docker compose exec -T postgres pg_isready -U postgres > /dev/null 2>&1; then
log_error "PostgreSQL 未就绪"
docker compose logs postgres
return 1
fi
log_success "PostgreSQL 已启动"
# 启动 Backend后台
log_info "启动 Backend (本地)..."
export ADAPTER_MODE=production
export PORT=8080
export JWT_SECRET=test-secret
export ENCRYPTION_KEY=12345678901234567890123456789012
export DATABASE_URL="postgresql://postgres:postgres@localhost:5432/ocdp?sslmode=disable"
# 在后台启动
nohup air -c .air.toml > /tmp/ocdp-real.log 2>&1 &
local pid=$!
log_info "进程 PID: $pid"
# 等待服务启动
if wait_for_service "http://localhost:8080/health"; then
# 测试服务
test_health "Production (本地 backend)"
test_api "Production (本地 backend)"
# 停止服务
log_info "停止 backend..."
kill $pid 2>/dev/null || true
pkill -f "tmp/main" 2>/dev/null || true
sleep 2
# 停止数据库
log_info "停止 PostgreSQL..."
docker compose down
log_success "模式 2 测试完成"
return 0
else
log_error "服务启动失败,查看日志: /tmp/ocdp-real.log"
kill $pid 2>/dev/null || true
docker compose down
return 1
fi
}
# ==================================================
# 测试 3: Production 模式docker compose 包含 backend
# ==================================================
test_mode_3() {
log_section "测试模式 3: Docker Compose 完整服务(包含 backend"
log_info "配置:"
log_info " • 数据库: Docker (PostgreSQL)"
log_info " • Backend: Docker 容器"
log_info " • 适配器: Production"
log_info " • 热重载: 不支持"
# 确保 .env 文件存在
if [ ! -f .env ]; then
log_info "创建 .env 文件..."
cp env.example .env
fi
# 启动完整服务
log_info "启动完整服务PostgreSQL + Backend..."
docker compose --profile backend up -d
# 等待服务启动
log_info "等待服务启动(可能需要构建镜像,请耐心等待)..."
# 检查容器状态
sleep 15
if ! docker compose ps | grep -q "ocdp-backend"; then
log_error "Backend 容器未启动"
docker compose logs backend
return 1
fi
# 等待健康检查通过
if wait_for_service "http://localhost:8080/health"; then
# 测试服务
test_health "Production (Docker)"
test_api "Production (Docker)"
# 查看容器状态
log_info "容器状态:"
docker compose ps
# 停止服务
log_info "停止服务..."
docker compose --profile backend down
log_success "模式 3 测试完成"
return 0
else
log_error "服务启动失败"
log_info "Backend 日志:"
docker compose logs backend
docker compose --profile backend down
return 1
fi
}
# ==================================================
# 主函数
# ==================================================
main() {
log_section "OCDP Backend - 三种模式自动化测试"
log_info "项目目录: $(pwd)"
log_info "测试时间: $(date)"
# 检查是否在项目根目录
if [ ! -f "docker-compose.yml" ]; then
log_error "请在项目根目录运行此脚本"
exit 1
fi
# 初始清理
cleanup
# 运行测试
local failed=0
# 测试模式 1
if test_mode_1; then
log_success "✅ 模式 1 测试通过"
else
log_error "❌ 模式 1 测试失败"
failed=$((failed + 1))
fi
cleanup
sleep 3
# 测试模式 2
if test_mode_2; then
log_success "✅ 模式 2 测试通过"
else
log_error "❌ 模式 2 测试失败"
failed=$((failed + 1))
fi
cleanup
sleep 3
# 测试模式 3
if test_mode_3; then
log_success "✅ 模式 3 测试通过"
else
log_error "❌ 模式 3 测试失败"
failed=$((failed + 1))
fi
cleanup
# 总结
log_section "测试总结"
if [ $failed -eq 0 ]; then
log_success "🎉 所有测试通过!"
log_info ""
log_info "三种部署模式总结:"
log_info ""
log_info "1⃣ Mock 模式(开发)"
log_info " 命令: make dev-mock"
log_info " 特点: 无需数据库,快速启动,热重载"
log_info ""
log_info "2⃣ Real 模式(开发 + 数据库)"
log_info " 命令: docker compose up -d && make dev-real"
log_info " 特点: PostgreSQL 容器Backend 本地热重载"
log_info ""
log_info "3⃣ Production 模式(生产)"
log_info " 命令: docker compose --profile backend up -d"
log_info " 特点: 完全容器化,适合生产环境"
exit 0
else
log_error "$failed 个测试失败"
exit 1
fi
}
# 执行主函数
main