From b44e40f25d216ce83feb523f9e9790e82981f5e1 Mon Sep 17 00:00:00 2001 From: vipg Date: Thu, 9 Oct 2025 18:19:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=9D=E7=95=99=E6=9C=80=E6=96=B0=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=EF=BC=8C=E6=B8=85=E9=99=A4=E5=8E=86=E5=8F=B2=E8=AE=B0?= =?UTF-8?q?=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 26 ++ .gitignore | 56 ++++ README.md | 130 +++++++++ api_template/.dockerignore | 26 ++ api_template/README.md | 7 + api_template/docker-compose.temp.yaml | 16 ++ api_template/dockerfile | 38 +++ api_template/init.py | 69 +++++ api_template/release.sh | 22 ++ create_api.py | 136 +++++++++ deploy/.env | 15 + deploy/api/api_delete/.dockerignore | 26 ++ deploy/api/api_delete/README.md | 7 + .../api/api_delete/docker-compose.delete.yaml | 16 ++ deploy/api/api_delete/dockerfile | 41 +++ deploy/api/api_delete/go.mod | 42 +++ deploy/api/api_delete/go.sum | 91 ++++++ deploy/api/api_delete/init.py | 69 +++++ deploy/api/api_delete/main.go | 146 ++++++++++ deploy/api/api_delete/release.sh | 22 ++ deploy/api/api_gateway/.dockerignore | 26 ++ deploy/api/api_gateway/README.md | 7 + .../api_gateway/docker-compose.gateway.yaml | 11 + deploy/api/api_gateway/dockerfile | 38 +++ deploy/api/api_gateway/go.mod | 42 +++ deploy/api/api_gateway/go.sum | 91 ++++++ deploy/api/api_gateway/init.py | 69 +++++ deploy/api/api_gateway/main.go | 137 +++++++++ deploy/api/api_gateway/release.sh | 22 ++ deploy/api/api_login/.dockerignore | 26 ++ deploy/api/api_login/README.md | 7 + .../api/api_login/docker-compose.login.yaml | 16 ++ deploy/api/api_login/dockerfile | 38 +++ deploy/api/api_login/go.mod | 42 +++ deploy/api/api_login/go.sum | 90 ++++++ deploy/api/api_login/init.py | 69 +++++ deploy/api/api_login/main.go | 162 +++++++++++ deploy/api/api_login/release.sh | 22 ++ deploy/api/api_register/.dockerignore | 26 ++ deploy/api/api_register/README.md | 7 + .../api_register/docker-compose.register.yaml | 16 ++ deploy/api/api_register/dockerfile | 38 +++ deploy/api/api_register/go.mod | 42 +++ deploy/api/api_register/go.sum | 90 ++++++ deploy/api/api_register/init.py | 69 +++++ deploy/api/api_register/main.go | 272 ++++++++++++++++++ deploy/api/api_register/release.sh | 22 ++ deploy/api/api_update_account/.dockerignore | 26 ++ deploy/api/api_update_account/README.md | 7 + .../docker-compose.update.account.yaml | 16 ++ deploy/api/api_update_account/dockerfile | 38 +++ deploy/api/api_update_account/go.mod | 42 +++ deploy/api/api_update_account/go.sum | 90 ++++++ deploy/api/api_update_account/init.py | 69 +++++ deploy/api/api_update_account/main.go | 204 +++++++++++++ deploy/api/api_update_account/release.sh | 22 ++ deploy/api/api_update_password/.dockerignore | 26 ++ deploy/api/api_update_password/README.md | 7 + .../docker-compose.update.password.yaml | 16 ++ deploy/api/api_update_password/dockerfile | 38 +++ deploy/api/api_update_password/go.mod | 42 +++ deploy/api/api_update_password/go.sum | 90 ++++++ deploy/api/api_update_password/init.py | 69 +++++ deploy/api/api_update_password/main.go | 206 +++++++++++++ deploy/api/api_update_password/release.sh | 22 ++ deploy/deploy.sh | 82 ++++++ deploy/docker-compose.yaml | 122 ++++++++ deploy/scripts/db-lanuch-entrypoint.sh | 43 +++ deploy/sql/01_uuid_v7_setup.sql | 48 ++++ deploy/sql/02_create_user_table.sql | 30 ++ deploy/sql/03_create_account_table.sql | 31 ++ deploy/sql/04_create_password_table.sql | 31 ++ .../sql/05_create_account_password_view.sql | 36 +++ docker-compose.base.yaml | 9 + docker-compose.db.admin.yaml | 16 ++ docker-compose.db.yaml | 17 ++ docker-compose.yaml | 122 ++++++++ 77 files changed, 4115 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 README.md create mode 100644 api_template/.dockerignore create mode 100644 api_template/README.md create mode 100644 api_template/docker-compose.temp.yaml create mode 100644 api_template/dockerfile create mode 100644 api_template/init.py create mode 100755 api_template/release.sh create mode 100644 create_api.py create mode 100644 deploy/.env create mode 100644 deploy/api/api_delete/.dockerignore create mode 100644 deploy/api/api_delete/README.md create mode 100644 deploy/api/api_delete/docker-compose.delete.yaml create mode 100644 deploy/api/api_delete/dockerfile create mode 100644 deploy/api/api_delete/go.mod create mode 100644 deploy/api/api_delete/go.sum create mode 100644 deploy/api/api_delete/init.py create mode 100644 deploy/api/api_delete/main.go create mode 100755 deploy/api/api_delete/release.sh create mode 100644 deploy/api/api_gateway/.dockerignore create mode 100644 deploy/api/api_gateway/README.md create mode 100644 deploy/api/api_gateway/docker-compose.gateway.yaml create mode 100644 deploy/api/api_gateway/dockerfile create mode 100644 deploy/api/api_gateway/go.mod create mode 100644 deploy/api/api_gateway/go.sum create mode 100644 deploy/api/api_gateway/init.py create mode 100644 deploy/api/api_gateway/main.go create mode 100755 deploy/api/api_gateway/release.sh create mode 100644 deploy/api/api_login/.dockerignore create mode 100644 deploy/api/api_login/README.md create mode 100644 deploy/api/api_login/docker-compose.login.yaml create mode 100644 deploy/api/api_login/dockerfile create mode 100644 deploy/api/api_login/go.mod create mode 100644 deploy/api/api_login/go.sum create mode 100644 deploy/api/api_login/init.py create mode 100644 deploy/api/api_login/main.go create mode 100755 deploy/api/api_login/release.sh create mode 100644 deploy/api/api_register/.dockerignore create mode 100644 deploy/api/api_register/README.md create mode 100644 deploy/api/api_register/docker-compose.register.yaml create mode 100644 deploy/api/api_register/dockerfile create mode 100644 deploy/api/api_register/go.mod create mode 100644 deploy/api/api_register/go.sum create mode 100644 deploy/api/api_register/init.py create mode 100644 deploy/api/api_register/main.go create mode 100755 deploy/api/api_register/release.sh create mode 100644 deploy/api/api_update_account/.dockerignore create mode 100644 deploy/api/api_update_account/README.md create mode 100644 deploy/api/api_update_account/docker-compose.update.account.yaml create mode 100644 deploy/api/api_update_account/dockerfile create mode 100644 deploy/api/api_update_account/go.mod create mode 100644 deploy/api/api_update_account/go.sum create mode 100644 deploy/api/api_update_account/init.py create mode 100644 deploy/api/api_update_account/main.go create mode 100755 deploy/api/api_update_account/release.sh create mode 100644 deploy/api/api_update_password/.dockerignore create mode 100644 deploy/api/api_update_password/README.md create mode 100644 deploy/api/api_update_password/docker-compose.update.password.yaml create mode 100644 deploy/api/api_update_password/dockerfile create mode 100644 deploy/api/api_update_password/go.mod create mode 100644 deploy/api/api_update_password/go.sum create mode 100644 deploy/api/api_update_password/init.py create mode 100644 deploy/api/api_update_password/main.go create mode 100755 deploy/api/api_update_password/release.sh create mode 100644 deploy/deploy.sh create mode 100644 deploy/docker-compose.yaml create mode 100755 deploy/scripts/db-lanuch-entrypoint.sh create mode 100644 deploy/sql/01_uuid_v7_setup.sql create mode 100644 deploy/sql/02_create_user_table.sql create mode 100644 deploy/sql/03_create_account_table.sql create mode 100644 deploy/sql/04_create_password_table.sql create mode 100644 deploy/sql/05_create_account_password_view.sql create mode 100644 docker-compose.base.yaml create mode 100644 docker-compose.db.admin.yaml create mode 100644 docker-compose.db.yaml create mode 100644 docker-compose.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3d1e553 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,26 @@ +# 排除Go编译产物 +*.exe +*.exe~ +*.dll +*.so +*.dylib +app # 排除本地已构建的二进制文件 + +# 排除依赖目录 +vendor/ +go/pkg/ +db +scripts +shared_data + +# 排除版本控制和日志文件 +.git/ +.gitignore +logs/ +*.log +*.md +*.sql + +# 排除IDE配置文件 +.idea/ +.vscode/ \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f0470c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# 编译产物 +*.o +*.a +*.so +*.exe +*.dll +*.dylib + +# Go 编译生成的中间文件和二进制文件 +__debug_bin +go.mod.sum +*.test +*.out + +# 模块缓存 +go/pkg/ + +# 构建目录(常用于存放编译后的二进制文件) +bin/ +build/ +dist/ +deploy/images +deploy/shared_data + +# 临时文件目录 +tmp/ +temp/ +shared_data/ + +# 日志文件 +*.log +logs/ + +# IDE 和编辑器配置 +.idea/ # JetBrains 系列(GoLand、IntelliJ 等) +.vscode/ # VS Code +*.swp # Vim 交换文件 +*.swo # Vim 交换文件 +.DS_Store # macOS 系统文件 +Thumbs.db # Windows 系统文件 + +# 测试相关 +coverage.out # 测试覆盖率文件 +testdata/ # 测试数据(如果不需要提交) + +# 依赖管理工具生成的文件 +vendor/ # 旧版依赖管理(Go Modules 时代可不提交) +Gopkg.lock # dep 工具 +Gopkg.toml # dep 工具 + +# 其他临时文件 +*.bak +*.tmp +*~ +.DS_Store +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..b9c1570 --- /dev/null +++ b/README.md @@ -0,0 +1,130 @@ +# 用户服务部署使用手册 + +## 一、概述 + +本文档介绍用户服务系统的创建、构建和部署流程,涉及`create_api.py`和`deploy/deploy.sh`两个核心脚本的使用方法,帮助使用者快速搭建和部署用户服务相关的API模块。 + +## 二、环境准备 + +1. 确保系统安装以下工具: + - Python 3.x + - Docker + - Docker Compose + +2. 克隆代码库后,进入项目根目录: + ```bash + cd user_service + ``` + +## 三、核心脚本功能说明 + +### 1. 创建新API服务(create_api.py) + +用于基于模板快速创建新的API服务模块,自动生成目录结构和配置文件。 + +#### 使用方法: +1. 编辑`create_api.py`文件,设置`a`(功能名)和`b`(次功能名)参数: + ```python + a = "aaaa" # 主功能名 + b = "bbbb" # 次功能名(可选,如果不需要,留空) + ``` + +2. 执行脚本: + ```bash + sudo python3 create_api.py + ``` + +#### 功能说明: +- 自动在`deploy/api`目录下创建新服务目录(格式:`api_{a}_{b}` 或 `api_{a}`) +- 复制`api_template`模板文件并替换其中的占位符 +- 生成对应的`docker-compose.{a}.{b}.yaml`或`docker-compose.{a}.yaml`文件 +- 自动修改`release.sh`、`init.py`和`README.md`中的服务标识 + +### 2. 部署服务(deploy/deploy.sh) + +用于停止现有服务、构建API并启动新的Docker Compose编排。 + +#### 使用方法: +```bash +cd deploy +sudo sh deploy.sh +``` + +#### 功能说明: +1. **步骤1:停止现有服务** + - 停止`docker-compose.yaml`定义的`user_service`项目 + +2. **步骤2:构建所有API服务** + - 遍历`./api`目录下的所有服务 + - 执行每个服务的`release.sh`脚本 + - 若构建失败则终止部署流程 + +3. **步骤3:启动新服务** + - 使用`docker-compose.yaml`启动服务 + - 项目名称为`user_service` + +#### 配置说明: +脚本开头可修改以下配置参数: +```bash +COMPOSE_PROJECT_NAME="user_service" # Docker Compose项目名称 +DOCKER_COMPOSE_FILE="./docker-compose.yaml" # 配置文件路径 +API_DIR="./api" # API服务所在目录 +``` + +## 四、完整工作流程 + +1. **创建新API服务**: + ```bash + # 编辑create_api.py设置功能名 + sudo python3 create_api.py + ``` + +2. **开发API功能**: + - 在生成的`deploy/api/api_{a}_{b}`或`deploy/api/api_{a}`目录中编写代码(如`main.go`) + - 根据需要修改`init.py`中的初始化逻辑 + +3. **构建服务镜像**: + ```bash + python3 build.py + ``` + +4. **部署服务**: + ```bash + cd deploy + ./deploy.sh + ``` + +## 五、常见问题处理 + +1. **权限错误**: + - 执行脚本时若出现`Permission denied`,尝试添加执行权限: + ```bash + chmod +x deploy/deploy.sh + ``` + - 或使用`sudo`运行相关命令 + +2. **镜像构建失败**: + - 检查`release.sh`中的镜像名称和构建命令是否正确 + - 确保Docker服务正常运行:`systemctl status docker` + +3. **服务启动失败**: + - 检查`docker-compose.yaml`配置是否正确 + - 查看容器日志排查问题:`docker logs <容器名>` + +4. **端口冲突**: + - 若启动时提示端口被占用,修改`init.py`中的端口映射配置 + +## 六、服务目录结构说明 + +成功创建并部署后,项目主要目录结构如下: +``` +user_service_副本/ +├── deploy/ +│ ├── api/ # 所有API服务目录 +│ │ ├── api_login/ # 登录服务 +│ │ ├── api_register/ # 注册服务 +│ │ └── ... # 其他API服务 +│ ├── docker-compose.yaml # 服务编排配置 +│ └── deploy.sh # 部署脚本 +└── create_api.py # 创建新API的脚本 +``` \ No newline at end of file diff --git a/api_template/.dockerignore b/api_template/.dockerignore new file mode 100644 index 0000000..3d1e553 --- /dev/null +++ b/api_template/.dockerignore @@ -0,0 +1,26 @@ +# 排除Go编译产物 +*.exe +*.exe~ +*.dll +*.so +*.dylib +app # 排除本地已构建的二进制文件 + +# 排除依赖目录 +vendor/ +go/pkg/ +db +scripts +shared_data + +# 排除版本控制和日志文件 +.git/ +.gitignore +logs/ +*.log +*.md +*.sql + +# 排除IDE配置文件 +.idea/ +.vscode/ \ No newline at end of file diff --git a/api_template/README.md b/api_template/README.md new file mode 100644 index 0000000..9ca6de6 --- /dev/null +++ b/api_template/README.md @@ -0,0 +1,7 @@ +sudo docker rm -f user_template_api && sudo docker run -itd --name user_template_api -v ./:/app -p 20001:80 golang:1.25.0-alpine3.22 + +---- + +# 格式:docker build -t 镜像名:标签 . +sudo docker rmi -f user-template-api:1.0.0 +sudo docker build -t user-template-api:1.0.0 . \ No newline at end of file diff --git a/api_template/docker-compose.temp.yaml b/api_template/docker-compose.temp.yaml new file mode 100644 index 0000000..4f78a53 --- /dev/null +++ b/api_template/docker-compose.temp.yaml @@ -0,0 +1,16 @@ +services: + user_template: + image: user-template-api:1.0.0 + container_name: api_user_template + restart: always + depends_on: + - postgres # 依赖数据库服务 + networks: + - user-network + environment: + DB_HOST: postgres + DB_PORT: ${DB_PORT} # 引用.env变量 + DB_USER: ${DB_USER} # 引用.env变量 + DB_PASSWORD: ${DB_PASSWORD} # 引用.env变量 + DB_NAME: ${DB_NAME} + TZ: ${TZ} # 引用.env变量 \ No newline at end of file diff --git a/api_template/dockerfile b/api_template/dockerfile new file mode 100644 index 0000000..9c811fc --- /dev/null +++ b/api_template/dockerfile @@ -0,0 +1,38 @@ +# ==================== 第一阶段:构建Go程序(构建阶段)==================== +# 使用官方Go镜像作为构建基础,选择与项目匹配的Go版本(示例用1.25.0,可根据实际调整) +FROM golang:1.25.0-alpine3.22 AS builder + +# 设置工作目录(容器内的目录,规范文件位置) +WORKDIR /app + +# 复制go.mod和go.sum(先复制依赖文件,利用Docker缓存机制,避免每次代码变动都重新下载依赖) +COPY go.mod go.sum ./ + +# 下载项目依赖(仅当go.mod/go.sum变动时才会重新执行) +RUN go mod download + +# 复制整个项目代码到工作目录 +COPY . . + +# 构建Go程序: +# - CGO_ENABLED=0:禁用CGO,生成静态链接的二进制文件(避免依赖系统库,保证镜像兼容性) +# - -o app:指定输出二进制文件名为app +# - ./main.go:指定入口文件 +RUN CGO_ENABLED=0 GOOS=linux go build -o app ./main.go + + +# ==================== 第二阶段:运行程序(运行阶段)==================== +# 使用轻量级的alpine镜像(仅5MB左右,大幅减小最终镜像体积) +FROM alpine:3.19 + +# 设置工作目录 +WORKDIR /app + +# 从构建阶段复制编译好的二进制文件到当前镜像(仅复制最终产物,减小体积) +COPY --from=builder /app/app ./ + +# 暴露程序运行端口(与代码中一致) +EXPOSE 8080 + +# 容器启动时执行的命令:运行二进制文件 +CMD ["./app"] \ No newline at end of file diff --git a/api_template/init.py b/api_template/init.py new file mode 100644 index 0000000..a588a29 --- /dev/null +++ b/api_template/init.py @@ -0,0 +1,69 @@ +import subprocess +import os + +def run_command(command, check=True, shell=True): + """执行shell命令并返回结果,包含错误处理""" + try: + print(f"执行命令: {command}") + result = subprocess.run( + command, + shell=shell, + check=check, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + print(f"命令输出: {result.stdout}") + return result + except subprocess.CalledProcessError as e: + print(f"命令执行失败: {e.stderr}") + raise + +def main(): + container_name = "user_template_api" + script_dir = os.path.dirname(os.path.abspath(__file__)) # 获取当前脚本所在目录 + + try: + # 1. 删除已存在的容器 + print("===== 步骤1: 删除已存在的容器 =====") + run_command(f"sudo docker rm -f {container_name}", check=False) + + # 2. 启动新容器 + print("\n===== 步骤2: 启动新容器 =====") + run_command( + f"sudo docker run -itd --name {container_name} " + f"-v {script_dir}:/app -p 30001:80 golang:1.25.0-alpine3.22" + ) + + # 3-5. 进入容器、进入app目录并执行go mod tidy(使用sh而非bash) + print("\n===== 步骤3-5: 容器内操作 =====") + exec_commands = ( + "cd /app && " # 进入app目录 + "echo '当前目录内容:' && ls -la && " # 新增目录检查 + "go version && " # 检查Go版本 + "go mod init user_template && " # 执行依赖整理 + "go mod tidy && " # 执行依赖整理 + "exit" # 退出容器 + ) + + # 使用sh代替bash执行命令 + run_command( + f"sudo docker exec -it {container_name} sh -c '{exec_commands}'" + ) + + # 7. 删除容器 + print("\n===== 步骤7: 删除容器 =====") + run_command(f"sudo docker rm -f {container_name}") + + # 8. 删除虚悬镜像 + print("\n===== 步骤8: 清理虚悬镜像 =====") + run_command("sudo docker images -f 'dangling=true' -q | xargs -r sudo docker rmi") + + print("\n===== 所有操作完成 =====") + + except Exception as e: + print(f"\n操作失败: {str(e)}") + exit(1) + +if __name__ == "__main__": + main() diff --git a/api_template/release.sh b/api_template/release.sh new file mode 100755 index 0000000..4309c5b --- /dev/null +++ b/api_template/release.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e # 当任何命令失败时立即退出脚本 + +# 定义镜像名称和标签 +IMAGE_NAME="user-template-api" +IMAGE_TAG="1.0.0" +FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}" + +echo "开始删除现有镜像 ${FULL_IMAGE}..." +if sudo docker rmi -f "${FULL_IMAGE}" >/dev/null 2>&1; then + echo "镜像 ${FULL_IMAGE} 删除成功" +else + echo "镜像 ${FULL_IMAGE} 不存在,跳过删除步骤" +fi + +echo "开始构建新镜像 ${FULL_IMAGE}..." +if sudo docker build -t "${FULL_IMAGE}" .; then + echo "镜像 ${FULL_IMAGE} 构建成功!" +else + echo "错误:镜像构建失败" >&2 + exit 1 +fi \ No newline at end of file diff --git a/create_api.py b/create_api.py new file mode 100644 index 0000000..e26284a --- /dev/null +++ b/create_api.py @@ -0,0 +1,136 @@ +import os +import shutil + +# 1. 定义全局参数 +a = "gateway1" # 功能名 +b = "" # 次功能名 + +def main(): + # 确定新目录名称 + if b: + new_dir_name = f"api_{a}_{b}" + else: + new_dir_name = f"api_{a}" + + # 目标目录调整为当前目录的deploy/api + new_dir = os.path.join("deploy", "api", new_dir_name) + + # 确保deploy/api目录存在 + os.makedirs(os.path.join("deploy", "api"), exist_ok=True) + + # 2. 复制并重命名api_template目录 + source_dir = "api_template" + if not os.path.exists(source_dir): + print(f"错误:源目录 {source_dir} 不存在") + return + + if os.path.exists(new_dir): + print(f"警告:目标目录 {new_dir} 已存在,将被覆盖") + shutil.rmtree(new_dir) + + shutil.copytree(source_dir, new_dir) + print(f"已创建目录:{new_dir}") + + # 确定新yaml文件名 + if b: + yaml_filename = f"docker-compose.{a}.{b}.yaml" + else: + yaml_filename = f"docker-compose.{a}.yaml" + + # 3. 重命名docker-compose.temp.yaml + temp_yaml = os.path.join(new_dir, "docker-compose.temp.yaml") + new_yaml = os.path.join(new_dir, yaml_filename) + if os.path.exists(temp_yaml): + os.rename(temp_yaml, new_yaml) + print(f"已重命名yaml文件:{yaml_filename}") + + # 4-5. 处理yaml文件内容(移除端口号修改) + with open(new_yaml, 'r', encoding='utf-8') as f: + content = f.read() + + # 替换"template-api" + if b: + content = content.replace("template-api", f"{a}-{b}-api") + else: + content = content.replace("template-api", f"{a}-api") + + # 替换"_template" + if b: + content = content.replace("_template", f"_{a}_{b}") + else: + content = content.replace("_template", f"_{a}") + + with open(new_yaml, 'w', encoding='utf-8') as f: + f.write(content) + print(f"已更新yaml文件内容") + else: + print(f"警告:未找到 {temp_yaml} 文件,跳过yaml处理") + + # 6-8. 处理README.md(移除端口号修改) + readme_path = os.path.join(new_dir, "README.md") + if os.path.exists(readme_path): + with open(readme_path, 'r', encoding='utf-8') as f: + content = f.read() + + # 替换"template_" + if b: + content = content.replace("template_", f"{a}_{b}_") + else: + content = content.replace("template_", f"{a}_") + + # 替换"template-" + if b: + content = content.replace("template-", f"{a}-{b}-") + else: + content = content.replace("template-", f"{a}-") + + with open(readme_path, 'w', encoding='utf-8') as f: + f.write(content) + print(f"已更新README.md内容") + else: + print(f"警告:未找到 {readme_path} 文件,跳过README处理") + + # 9. 处理release.sh + release_path = os.path.join(new_dir, "release.sh") + if os.path.exists(release_path): + with open(release_path, 'r', encoding='utf-8') as f: + content = f.read() + + # 替换"template" + if b: + content = content.replace("template", f"{a}-{b}") + else: + content = content.replace("template", f"{a}") + + with open(release_path, 'w', encoding='utf-8') as f: + f.write(content) + print(f"已更新release.sh内容") + else: + print(f"警告:未找到 {release_path} 文件,跳过release.sh处理") + + # 10-11. 处理init.py(移除端口号修改) + init_path = os.path.join(new_dir, "init.py") + if os.path.exists(init_path): + with open(init_path, 'r', encoding='utf-8') as f: + content = f.read() + + # 替换"_template_" + if b: + content = content.replace("_template_", f"_{a}_{b}_") + else: + content = content.replace("_template_", f"_{a}_") + + # 替换"template &&" + if b: + content = content.replace("template &&", f"{a}_{b} &&") + else: + content = content.replace("template &&", f"{a} &&") + + with open(init_path, 'w', encoding='utf-8') as f: + f.write(content) + print(f"已更新init.py内容") + else: + print(f"警告:未找到 {init_path} 文件,跳过init.py处理") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/deploy/.env b/deploy/.env new file mode 100644 index 0000000..9a5b466 --- /dev/null +++ b/deploy/.env @@ -0,0 +1,15 @@ +# 数据库配置 +DB_USER=postgres +DB_PASSWORD=postgres12341234 +DB_NAME=postgres +DB_PORT=5432 + +# 时区配置 +TZ=Asia/Shanghai + +# pgAdmin配置 +PGADMIN_EMAIL=fish@fish.com +PGADMIN_PASSWORD=12345678 + +# 网关端口 +GATEWAY_PORT=80 \ No newline at end of file diff --git a/deploy/api/api_delete/.dockerignore b/deploy/api/api_delete/.dockerignore new file mode 100644 index 0000000..3d1e553 --- /dev/null +++ b/deploy/api/api_delete/.dockerignore @@ -0,0 +1,26 @@ +# 排除Go编译产物 +*.exe +*.exe~ +*.dll +*.so +*.dylib +app # 排除本地已构建的二进制文件 + +# 排除依赖目录 +vendor/ +go/pkg/ +db +scripts +shared_data + +# 排除版本控制和日志文件 +.git/ +.gitignore +logs/ +*.log +*.md +*.sql + +# 排除IDE配置文件 +.idea/ +.vscode/ \ No newline at end of file diff --git a/deploy/api/api_delete/README.md b/deploy/api/api_delete/README.md new file mode 100644 index 0000000..b20dcb5 --- /dev/null +++ b/deploy/api/api_delete/README.md @@ -0,0 +1,7 @@ +sudo docker rm -f user_delete_api && sudo docker run -itd --name user_delete_api -v ./:/app -p 20005:80 golang:1.25.0-alpine3.22 + +---- + +# 格式:docker build -t 镜像名:标签 . +sudo docker rmi -f user-delete-api:1.0.0 +sudo docker build -t user-delete-api:1.0.0 . \ No newline at end of file diff --git a/deploy/api/api_delete/docker-compose.delete.yaml b/deploy/api/api_delete/docker-compose.delete.yaml new file mode 100644 index 0000000..e5c0722 --- /dev/null +++ b/deploy/api/api_delete/docker-compose.delete.yaml @@ -0,0 +1,16 @@ +services: + user_delete: + image: user-delete-api:1.0.0 + container_name: api_user_delete + restart: always + depends_on: + - postgres # 依赖数据库服务 + networks: + - user-network + environment: + DB_HOST: postgres + DB_PORT: ${DB_PORT} # 引用.env变量 + DB_USER: ${DB_USER} # 引用.env变量 + DB_PASSWORD: ${DB_PASSWORD} # 引用.env变量 + DB_NAME: ${DB_NAME} + TZ: ${TZ} # 引用.env变量 \ No newline at end of file diff --git a/deploy/api/api_delete/dockerfile b/deploy/api/api_delete/dockerfile new file mode 100644 index 0000000..30834ea --- /dev/null +++ b/deploy/api/api_delete/dockerfile @@ -0,0 +1,41 @@ +# ==================== 第一阶段:构建Go程序(构建阶段)==================== +# 使用官方Go镜像作为构建基础,选择与项目匹配的Go版本(示例用1.25.0,可根据实际调整) +FROM golang:1.25.0-alpine3.22 AS builder + +# 设置工作目录(容器内的目录,规范文件位置) +WORKDIR /app + +# 复制go.mod和go.sum(先复制依赖文件,利用Docker缓存机制,避免每次代码变动都重新下载依赖) +# COPY go.mod go.sum ./ +COPY go.mod ./ + +RUN go mod tidy + +# 下载项目依赖(仅当go.mod/go.sum变动时才会重新执行) +RUN go mod download + +# 复制整个项目代码到工作目录 +COPY . . + +# 构建Go程序: +# - CGO_ENABLED=0:禁用CGO,生成静态链接的二进制文件(避免依赖系统库,保证镜像兼容性) +# - -o app:指定输出二进制文件名为app +# - ./main.go:指定入口文件 +RUN CGO_ENABLED=0 GOOS=linux go build -o app ./main.go + + +# ==================== 第二阶段:运行程序(运行阶段)==================== +# 使用轻量级的alpine镜像(仅5MB左右,大幅减小最终镜像体积) +FROM alpine:3.19 + +# 设置工作目录 +WORKDIR /app + +# 从构建阶段复制编译好的二进制文件到当前镜像(仅复制最终产物,减小体积) +COPY --from=builder /app/app ./ + +# 暴露程序运行端口(与代码中一致) +EXPOSE 8080 + +# 容器启动时执行的命令:运行二进制文件 +CMD ["./app"] \ No newline at end of file diff --git a/deploy/api/api_delete/go.mod b/deploy/api/api_delete/go.mod new file mode 100644 index 0000000..a2b8603 --- /dev/null +++ b/deploy/api/api_delete/go.mod @@ -0,0 +1,42 @@ +module user_delete + +go 1.25.0 + +require ( + github.com/gin-gonic/gin v1.11.0 + github.com/lib/pq v1.10.9 +) + +require ( + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + go.uber.org/mock v0.5.0 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/mod v0.25.0 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.27.0 // indirect + golang.org/x/tools v0.34.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect +) diff --git a/deploy/api/api_delete/go.sum b/deploy/api/api_delete/go.sum new file mode 100644 index 0000000..3a6f937 --- /dev/null +++ b/deploy/api/api_delete/go.sum @@ -0,0 +1,91 @@ +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/deploy/api/api_delete/init.py b/deploy/api/api_delete/init.py new file mode 100644 index 0000000..4922225 --- /dev/null +++ b/deploy/api/api_delete/init.py @@ -0,0 +1,69 @@ +import subprocess +import os + +def run_command(command, check=True, shell=True): + """执行shell命令并返回结果,包含错误处理""" + try: + print(f"执行命令: {command}") + result = subprocess.run( + command, + shell=shell, + check=check, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + print(f"命令输出: {result.stdout}") + return result + except subprocess.CalledProcessError as e: + print(f"命令执行失败: {e.stderr}") + raise + +def main(): + container_name = "user_delete_api" + script_dir = os.path.dirname(os.path.abspath(__file__)) # 获取当前脚本所在目录 + + try: + # 1. 删除已存在的容器 + print("===== 步骤1: 删除已存在的容器 =====") + run_command(f"sudo docker rm -f {container_name}", check=False) + + # 2. 启动新容器 + print("\n===== 步骤2: 启动新容器 =====") + run_command( + f"sudo docker run -itd --name {container_name} " + f"-v {script_dir}:/app -p 30001:80 golang:1.25.0-alpine3.22" + ) + + # 3-5. 进入容器、进入app目录并执行go mod tidy(使用sh而非bash) + print("\n===== 步骤3-5: 容器内操作 =====") + exec_commands = ( + "cd /app && " # 进入app目录 + "echo '当前目录内容:' && ls -la && " # 新增目录检查 + "go version && " # 检查Go版本 + "go mod init user_delete && " # 执行依赖整理 + "go mod tidy && " # 执行依赖整理 + "exit" # 退出容器 + ) + + # 使用sh代替bash执行命令 + run_command( + f"sudo docker exec -it {container_name} sh -c '{exec_commands}'" + ) + + # 7. 删除容器 + print("\n===== 步骤7: 删除容器 =====") + run_command(f"sudo docker rm -f {container_name}") + + # 8. 删除虚悬镜像 + print("\n===== 步骤8: 清理虚悬镜像 =====") + run_command("sudo docker images -f 'dangling=true' -q | xargs -r sudo docker rmi") + + print("\n===== 所有操作完成 =====") + + except Exception as e: + print(f"\n操作失败: {str(e)}") + exit(1) + +if __name__ == "__main__": + main() diff --git a/deploy/api/api_delete/main.go b/deploy/api/api_delete/main.go new file mode 100644 index 0000000..67bea35 --- /dev/null +++ b/deploy/api/api_delete/main.go @@ -0,0 +1,146 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "net/http" + "os" + + "github.com/gin-gonic/gin" + _ "github.com/lib/pq" +) + +// DeleteAccountRequest 删除账号请求参数 +type DeleteAccountRequest struct { + UserID string `json:"user_id" binding:"required"` +} + +// DeleteAccountResponse 删除账号响应结构 +type DeleteAccountResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +var db *sql.DB + +func main() { + log.Println("开始初始化应用程序") + + // 初始化Gin引擎 + r := gin.Default() + log.Println("Gin引擎初始化完成") + + // 从环境变量获取数据库配置 + dbHost := os.Getenv("DB_HOST") + dbPort := os.Getenv("DB_PORT") + dbUser := os.Getenv("DB_USER") + dbPassword := os.Getenv("DB_PASSWORD") + dbName := os.Getenv("DB_NAME") + log.Printf("加载数据库配置: host=%s, port=%s, user=%s, dbname=%s", + dbHost, dbPort, dbUser, dbName) + + // 构建数据库连接字符串 + connStr := fmt.Sprintf( + "host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", + dbHost, dbPort, dbUser, dbPassword, dbName, + ) + log.Println("数据库连接字符串构建完成") + + var err error + db, err = sql.Open("postgres", connStr) + if err != nil { + log.Panicf("无法连接数据库: %v", err) + } + defer func() { + log.Println("关闭数据库连接") + if err := db.Close(); err != nil { + log.Printf("关闭数据库连接出错: %v", err) + } + }() + log.Println("数据库连接对象创建成功") + + // 验证数据库连接 + if err := db.Ping(); err != nil { + log.Panicf("数据库连接失败: %v", err) + } + log.Println("数据库连接验证成功") + + // 注册删除账号接口 + r.POST("/user/delete/account", deleteAccountHandler) + log.Println("已注册删除账号接口: POST /user/delete/account") + + // 启动服务,监听80端口 + log.Println("服务启动在80端口") + if err := r.Run(":80"); err != nil { + log.Panicf("服务启动失败: %v", err) + } +} + +// deleteAccountHandler 处理账号删除逻辑(逻辑删除) +func deleteAccountHandler(c *gin.Context) { + reqID := c.Request.Header.Get("X-Request-ID") + if reqID == "" { + reqID = fmt.Sprintf("req-%d", gin.Mode()) + } + log.Printf("[%s] 收到删除账号请求,客户端IP: %s", reqID, c.ClientIP()) + + var req DeleteAccountRequest + // 绑定并验证请求参数 + if err := c.ShouldBindJSON(&req); err != nil { + log.Printf("[%s] 请求参数绑定失败: %v", reqID, err) + c.JSON(http.StatusBadRequest, DeleteAccountResponse{ + Success: false, + Message: "请求参数错误: " + err.Error(), + }) + return + } + log.Printf("[%s] 请求参数绑定成功,用户ID: %s", reqID, req.UserID) + + // 更新用户表中的deleted字段为true + updateQuery := ` + UPDATE "user" + SET deleted = true + WHERE id = $1 AND deleted = false + ` + log.Printf("[%s] 执行更新SQL: %s,参数: %s", reqID, updateQuery, req.UserID) + + result, err := db.Exec(updateQuery, req.UserID) + if err != nil { + log.Printf("[%s] 执行更新操作失败: %v", reqID, err) + c.JSON(http.StatusInternalServerError, DeleteAccountResponse{ + Success: false, + Message: "删除账号失败: " + err.Error(), + }) + return + } + log.Printf("[%s] 更新操作执行完成", reqID) + + // 检查是否有记录被更新 + rowsAffected, err := result.RowsAffected() + if err != nil { + log.Printf("[%s] 获取影响行数失败: %v", reqID, err) + c.JSON(http.StatusInternalServerError, DeleteAccountResponse{ + Success: false, + Message: "检查删除结果失败: " + err.Error(), + }) + return + } + log.Printf("[%s] 更新操作影响行数: %d", reqID, rowsAffected) + + if rowsAffected == 0 { + log.Printf("[%s] 未找到可删除的用户记录,用户ID: %s", reqID, req.UserID) + c.JSON(http.StatusOK, DeleteAccountResponse{ + Success: false, + Message: "用户不存在或已被删除", + }) + return + } + + // 删除成功 + log.Printf("[%s] 账号删除成功,用户ID: %s", reqID, req.UserID) + c.JSON(http.StatusOK, DeleteAccountResponse{ + Success: true, + Message: "账号删除成功。", + }) +} \ No newline at end of file diff --git a/deploy/api/api_delete/release.sh b/deploy/api/api_delete/release.sh new file mode 100755 index 0000000..5c208a9 --- /dev/null +++ b/deploy/api/api_delete/release.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e # 当任何命令失败时立即退出脚本 + +# 定义镜像名称、标签和压缩包名称 +IMAGE_NAME="user-delete-api" +IMAGE_TAG="1.0.0" +FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}" + +echo "开始删除现有镜像 ${FULL_IMAGE}..." +if sudo docker rmi -f "${FULL_IMAGE}" >/dev/null 2>&1; then + echo "镜像 ${FULL_IMAGE} 删除成功" +else + echo "镜像 ${FULL_IMAGE} 不存在,跳过删除步骤" +fi + +echo "开始构建新镜像 ${FULL_IMAGE}..." +if sudo docker build -t "${FULL_IMAGE}" .; then + echo "镜像 ${FULL_IMAGE} 构建成功!" +else + echo "错误:镜像构建失败" >&2 + exit 1 +fi \ No newline at end of file diff --git a/deploy/api/api_gateway/.dockerignore b/deploy/api/api_gateway/.dockerignore new file mode 100644 index 0000000..3d1e553 --- /dev/null +++ b/deploy/api/api_gateway/.dockerignore @@ -0,0 +1,26 @@ +# 排除Go编译产物 +*.exe +*.exe~ +*.dll +*.so +*.dylib +app # 排除本地已构建的二进制文件 + +# 排除依赖目录 +vendor/ +go/pkg/ +db +scripts +shared_data + +# 排除版本控制和日志文件 +.git/ +.gitignore +logs/ +*.log +*.md +*.sql + +# 排除IDE配置文件 +.idea/ +.vscode/ \ No newline at end of file diff --git a/deploy/api/api_gateway/README.md b/deploy/api/api_gateway/README.md new file mode 100644 index 0000000..eb16278 --- /dev/null +++ b/deploy/api/api_gateway/README.md @@ -0,0 +1,7 @@ +sudo docker rm -f user_gateway_api && sudo docker run -itd --name user_gateway_api -v ./:/app -p 20000:80 golang:1.25.0-alpine3.22 + +---- + +# 格式:docker build -t 镜像名:标签 . +sudo docker rmi -f user-gateway-api:1.0.0 +sudo docker build -t user-gateway-api:1.0.0 . \ No newline at end of file diff --git a/deploy/api/api_gateway/docker-compose.gateway.yaml b/deploy/api/api_gateway/docker-compose.gateway.yaml new file mode 100644 index 0000000..a9c5a64 --- /dev/null +++ b/deploy/api/api_gateway/docker-compose.gateway.yaml @@ -0,0 +1,11 @@ +services: + user_gateway: + image: user-gateway-api:1.0.0 + container_name: api_user_gateway + restart: always + ports: + - "20000:80" + networks: + - user-network + environment: + GATEWAY_PORT: ${GATEWAY_PORT} # 引用.env变量 \ No newline at end of file diff --git a/deploy/api/api_gateway/dockerfile b/deploy/api/api_gateway/dockerfile new file mode 100644 index 0000000..9c811fc --- /dev/null +++ b/deploy/api/api_gateway/dockerfile @@ -0,0 +1,38 @@ +# ==================== 第一阶段:构建Go程序(构建阶段)==================== +# 使用官方Go镜像作为构建基础,选择与项目匹配的Go版本(示例用1.25.0,可根据实际调整) +FROM golang:1.25.0-alpine3.22 AS builder + +# 设置工作目录(容器内的目录,规范文件位置) +WORKDIR /app + +# 复制go.mod和go.sum(先复制依赖文件,利用Docker缓存机制,避免每次代码变动都重新下载依赖) +COPY go.mod go.sum ./ + +# 下载项目依赖(仅当go.mod/go.sum变动时才会重新执行) +RUN go mod download + +# 复制整个项目代码到工作目录 +COPY . . + +# 构建Go程序: +# - CGO_ENABLED=0:禁用CGO,生成静态链接的二进制文件(避免依赖系统库,保证镜像兼容性) +# - -o app:指定输出二进制文件名为app +# - ./main.go:指定入口文件 +RUN CGO_ENABLED=0 GOOS=linux go build -o app ./main.go + + +# ==================== 第二阶段:运行程序(运行阶段)==================== +# 使用轻量级的alpine镜像(仅5MB左右,大幅减小最终镜像体积) +FROM alpine:3.19 + +# 设置工作目录 +WORKDIR /app + +# 从构建阶段复制编译好的二进制文件到当前镜像(仅复制最终产物,减小体积) +COPY --from=builder /app/app ./ + +# 暴露程序运行端口(与代码中一致) +EXPOSE 8080 + +# 容器启动时执行的命令:运行二进制文件 +CMD ["./app"] \ No newline at end of file diff --git a/deploy/api/api_gateway/go.mod b/deploy/api/api_gateway/go.mod new file mode 100644 index 0000000..b2d718b --- /dev/null +++ b/deploy/api/api_gateway/go.mod @@ -0,0 +1,42 @@ +module user_gateway + +go 1.25.0 + +require ( + github.com/gin-contrib/cors v1.7.6 + github.com/gin-gonic/gin v1.11.0 +) + +require ( + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + go.uber.org/mock v0.5.0 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/mod v0.25.0 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.27.0 // indirect + golang.org/x/tools v0.34.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect +) diff --git a/deploy/api/api_gateway/go.sum b/deploy/api/api_gateway/go.sum new file mode 100644 index 0000000..7539074 --- /dev/null +++ b/deploy/api/api_gateway/go.sum @@ -0,0 +1,91 @@ +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= +github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY= +github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/deploy/api/api_gateway/init.py b/deploy/api/api_gateway/init.py new file mode 100644 index 0000000..9bc7e95 --- /dev/null +++ b/deploy/api/api_gateway/init.py @@ -0,0 +1,69 @@ +import subprocess +import os + +def run_command(command, check=True, shell=True): + """执行shell命令并返回结果,包含错误处理""" + try: + print(f"执行命令: {command}") + result = subprocess.run( + command, + shell=shell, + check=check, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + print(f"命令输出: {result.stdout}") + return result + except subprocess.CalledProcessError as e: + print(f"命令执行失败: {e.stderr}") + raise + +def main(): + container_name = "user_gateway_api" + script_dir = os.path.dirname(os.path.abspath(__file__)) # 获取当前脚本所在目录 + + try: + # 1. 删除已存在的容器 + print("===== 步骤1: 删除已存在的容器 =====") + run_command(f"sudo docker rm -f {container_name}", check=False) + + # 2. 启动新容器 + print("\n===== 步骤2: 启动新容器 =====") + run_command( + f"sudo docker run -itd --name {container_name} " + f"-v {script_dir}:/app -p 30001:80 golang:1.25.0-alpine3.22" + ) + + # 3-5. 进入容器、进入app目录并执行go mod tidy(使用sh而非bash) + print("\n===== 步骤3-5: 容器内操作 =====") + exec_commands = ( + "cd /app && " # 进入app目录 + "echo '当前目录内容:' && ls -la && " # 新增目录检查 + "go version && " # 检查Go版本 + "go mod init user_gateway && " # 执行依赖整理 + "go mod tidy && " # 执行依赖整理 + "exit" # 退出容器 + ) + + # 使用sh代替bash执行命令 + run_command( + f"sudo docker exec -it {container_name} sh -c '{exec_commands}'" + ) + + # 7. 删除容器 + print("\n===== 步骤7: 删除容器 =====") + run_command(f"sudo docker rm -f {container_name}") + + # 8. 删除虚悬镜像 + print("\n===== 步骤8: 清理虚悬镜像 =====") + run_command("sudo docker images -f 'dangling=true' -q | xargs -r sudo docker rmi") + + print("\n===== 所有操作完成 =====") + + except Exception as e: + print(f"\n操作失败: {str(e)}") + exit(1) + +if __name__ == "__main__": + main() diff --git a/deploy/api/api_gateway/main.go b/deploy/api/api_gateway/main.go new file mode 100644 index 0000000..0a19d17 --- /dev/null +++ b/deploy/api/api_gateway/main.go @@ -0,0 +1,137 @@ +package main + +import ( + "log" + "net/http" + "net/http/httputil" + "net/url" + "os" + "time" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" +) + +var serviceMap = map[string]string{ + "/user/login": "http://user_login:80", + "/user/register": "http://user_register:80", + "/user/delete": "http://user_delete:80", + "/user/update/account": "http://user_update_account:80", + "/user/update/password": "http://user_update_password:80", +} + +// 日志中间件(记录网关接收的请求) +func loggerMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + startTime := time.Now() + c.Next() + endTime := time.Now() + + log.Printf( + "[网关请求] %s %s | 状态码: %d | 耗时: %s | 客户端IP: %s", + c.Request.Method, + c.Request.URL.Path, + c.Writer.Status(), + endTime.Sub(startTime), + c.ClientIP(), + ) + } +} + +// 反向代理处理(添加详细转发日志) +func reverseProxy(target string) gin.HandlerFunc { + return func(c *gin.Context) { + // 解析目标服务地址 + targetURL, err := url.Parse(target) + if err != nil { + log.Printf("[代理错误] 目标服务地址解析失败: %s, 错误: %v", target, err) + c.JSON(http.StatusInternalServerError, gin.H{ + "success": false, + "message": "服务配置错误", + }) + return + } + + // 创建反向代理实例 + proxy := httputil.NewSingleHostReverseProxy(targetURL) + + // 记录转发前的请求信息 + reqStart := time.Now() + log.Printf( + "[开始转发] 方法: %s | 原始路径: %s | 目标服务: %s | 客户端IP: %s", + c.Request.Method, + c.Request.URL.Path, + targetURL.String(), + c.ClientIP(), + ) + + // 自定义请求转发逻辑(并记录请求头) + originalDirector := proxy.Director + proxy.Director = func(req *http.Request) { + originalDirector(req) + // 传递关键头信息 + req.Header.Set("Origin", c.Request.Header.Get("Origin")) + req.Host = targetURL.Host + req.Header.Set("X-Forwarded-By", "user-gateway") + + // 打印转发的请求头(调试用,生产环境可注释) + log.Printf("[转发请求头] 目标服务: %s | Origin: %s | Host: %s", + targetURL.Host, + req.Header.Get("Origin"), + req.Host, + ) + } + + // 记录响应信息 + proxy.ModifyResponse = func(resp *http.Response) error { + // 计算转发耗时 + proxyDuration := time.Since(reqStart) + + // 打印响应状态 + log.Printf( + "[转发响应] 目标服务: %s | 状态码: %d | 耗时: %s | 响应头Origin: %s", + targetURL.Host, + resp.StatusCode, + proxyDuration, + resp.Header.Get("Access-Control-Allow-Origin"), + ) + return nil + } + + // 执行代理转发 + proxy.ServeHTTP(c.Writer, c.Request) + } +} + +func main() { + r := gin.Default() + + // 注册日志中间件 + r.Use(loggerMiddleware()) + + // 跨域配置 + r.Use(cors.New(cors.Config{ + AllowOrigins: []string{"*"}, + AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, + AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"}, + ExposeHeaders: []string{"Content-Length"}, + AllowCredentials: true, + MaxAge: 12 * time.Hour, + })) + + // 注册路由 + for path, target := range serviceMap { + r.Any(path, reverseProxy(target)) + r.Any(path+"/*any", reverseProxy(target)) + } + + // 启动服务 + port := os.Getenv("GATEWAY_PORT") + if port == "" { + port = "80" + } + log.Printf("网关服务启动在 %s 端口", port) + if err := r.Run(":" + port); err != nil { + log.Fatalf("服务启动失败: %v", err) + } +} \ No newline at end of file diff --git a/deploy/api/api_gateway/release.sh b/deploy/api/api_gateway/release.sh new file mode 100755 index 0000000..418ec0e --- /dev/null +++ b/deploy/api/api_gateway/release.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e # 当任何命令失败时立即退出脚本 + +# 定义镜像名称和标签 +IMAGE_NAME="user-gateway-api" +IMAGE_TAG="1.0.0" +FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}" + +echo "开始删除现有镜像 ${FULL_IMAGE}..." +if sudo docker rmi -f "${FULL_IMAGE}" >/dev/null 2>&1; then + echo "镜像 ${FULL_IMAGE} 删除成功" +else + echo "镜像 ${FULL_IMAGE} 不存在,跳过删除步骤" +fi + +echo "开始构建新镜像 ${FULL_IMAGE}..." +if sudo docker build -t "${FULL_IMAGE}" .; then + echo "镜像 ${FULL_IMAGE} 构建成功!" +else + echo "错误:镜像构建失败" >&2 + exit 1 +fi \ No newline at end of file diff --git a/deploy/api/api_login/.dockerignore b/deploy/api/api_login/.dockerignore new file mode 100644 index 0000000..3d1e553 --- /dev/null +++ b/deploy/api/api_login/.dockerignore @@ -0,0 +1,26 @@ +# 排除Go编译产物 +*.exe +*.exe~ +*.dll +*.so +*.dylib +app # 排除本地已构建的二进制文件 + +# 排除依赖目录 +vendor/ +go/pkg/ +db +scripts +shared_data + +# 排除版本控制和日志文件 +.git/ +.gitignore +logs/ +*.log +*.md +*.sql + +# 排除IDE配置文件 +.idea/ +.vscode/ \ No newline at end of file diff --git a/deploy/api/api_login/README.md b/deploy/api/api_login/README.md new file mode 100644 index 0000000..8fc8876 --- /dev/null +++ b/deploy/api/api_login/README.md @@ -0,0 +1,7 @@ +sudo docker rm -f user_login_api && sudo docker run -itd --name user_login_api -v ./:/app -p 20001:80 golang:1.25.0-alpine3.22 + +---- + +# 格式:docker build -t 镜像名:标签 . +sudo docker rmi -f user-login-api:1.0.0 +sudo docker build -t user-login-api:1.0.0 . \ No newline at end of file diff --git a/deploy/api/api_login/docker-compose.login.yaml b/deploy/api/api_login/docker-compose.login.yaml new file mode 100644 index 0000000..690f81c --- /dev/null +++ b/deploy/api/api_login/docker-compose.login.yaml @@ -0,0 +1,16 @@ +services: + user_login: + image: user-login-api:1.0.0 + container_name: api_user_login + restart: always + depends_on: + - postgres # 依赖数据库服务 + networks: + - user-network + environment: + DB_HOST: postgres + DB_PORT: ${DB_PORT} # 引用.env变量 + DB_USER: ${DB_USER} # 引用.env变量 + DB_PASSWORD: ${DB_PASSWORD} # 引用.env变量 + DB_NAME: ${DB_NAME} + TZ: ${TZ} # 引用.env变量 \ No newline at end of file diff --git a/deploy/api/api_login/dockerfile b/deploy/api/api_login/dockerfile new file mode 100644 index 0000000..9c811fc --- /dev/null +++ b/deploy/api/api_login/dockerfile @@ -0,0 +1,38 @@ +# ==================== 第一阶段:构建Go程序(构建阶段)==================== +# 使用官方Go镜像作为构建基础,选择与项目匹配的Go版本(示例用1.25.0,可根据实际调整) +FROM golang:1.25.0-alpine3.22 AS builder + +# 设置工作目录(容器内的目录,规范文件位置) +WORKDIR /app + +# 复制go.mod和go.sum(先复制依赖文件,利用Docker缓存机制,避免每次代码变动都重新下载依赖) +COPY go.mod go.sum ./ + +# 下载项目依赖(仅当go.mod/go.sum变动时才会重新执行) +RUN go mod download + +# 复制整个项目代码到工作目录 +COPY . . + +# 构建Go程序: +# - CGO_ENABLED=0:禁用CGO,生成静态链接的二进制文件(避免依赖系统库,保证镜像兼容性) +# - -o app:指定输出二进制文件名为app +# - ./main.go:指定入口文件 +RUN CGO_ENABLED=0 GOOS=linux go build -o app ./main.go + + +# ==================== 第二阶段:运行程序(运行阶段)==================== +# 使用轻量级的alpine镜像(仅5MB左右,大幅减小最终镜像体积) +FROM alpine:3.19 + +# 设置工作目录 +WORKDIR /app + +# 从构建阶段复制编译好的二进制文件到当前镜像(仅复制最终产物,减小体积) +COPY --from=builder /app/app ./ + +# 暴露程序运行端口(与代码中一致) +EXPOSE 8080 + +# 容器启动时执行的命令:运行二进制文件 +CMD ["./app"] \ No newline at end of file diff --git a/deploy/api/api_login/go.mod b/deploy/api/api_login/go.mod new file mode 100644 index 0000000..5e0ec0c --- /dev/null +++ b/deploy/api/api_login/go.mod @@ -0,0 +1,42 @@ +module user_login + +go 1.25.0 + +require ( + github.com/gin-gonic/gin v1.11.0 + github.com/lib/pq v1.10.9 + golang.org/x/crypto v0.43.0 +) + +require ( + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + go.uber.org/mock v0.5.0 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.45.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/tools v0.37.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect +) diff --git a/deploy/api/api_login/go.sum b/deploy/api/api_login/go.sum new file mode 100644 index 0000000..653eb06 --- /dev/null +++ b/deploy/api/api_login/go.sum @@ -0,0 +1,90 @@ +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/deploy/api/api_login/init.py b/deploy/api/api_login/init.py new file mode 100644 index 0000000..ce9408c --- /dev/null +++ b/deploy/api/api_login/init.py @@ -0,0 +1,69 @@ +import subprocess +import os + +def run_command(command, check=True, shell=True): + """执行shell命令并返回结果,包含错误处理""" + try: + print(f"执行命令: {command}") + result = subprocess.run( + command, + shell=shell, + check=check, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + print(f"命令输出: {result.stdout}") + return result + except subprocess.CalledProcessError as e: + print(f"命令执行失败: {e.stderr}") + raise + +def main(): + container_name = "user_login_api" + script_dir = os.path.dirname(os.path.abspath(__file__)) # 获取当前脚本所在目录 + + try: + # 1. 删除已存在的容器 + print("===== 步骤1: 删除已存在的容器 =====") + run_command(f"sudo docker rm -f {container_name}", check=False) + + # 2. 启动新容器 + print("\n===== 步骤2: 启动新容器 =====") + run_command( + f"sudo docker run -itd --name {container_name} " + f"-v {script_dir}:/app -p 30001:80 golang:1.25.0-alpine3.22" + ) + + # 3-5. 进入容器、进入app目录并执行go mod tidy(使用sh而非bash) + print("\n===== 步骤3-5: 容器内操作 =====") + exec_commands = ( + "cd /app && " # 进入app目录 + "echo '当前目录内容:' && ls -la && " # 新增目录检查 + "go version && " # 检查Go版本 + "go mod init user_login && " # 执行依赖整理 + "go mod tidy && " # 执行依赖整理 + "exit" # 退出容器 + ) + + # 使用sh代替bash执行命令 + run_command( + f"sudo docker exec -it {container_name} sh -c '{exec_commands}'" + ) + + # 7. 删除容器 + print("\n===== 步骤7: 删除容器 =====") + run_command(f"sudo docker rm -f {container_name}") + + # 8. 删除虚悬镜像 + print("\n===== 步骤8: 清理虚悬镜像 =====") + run_command("sudo docker images -f 'dangling=true' -q | xargs -r sudo docker rmi") + + print("\n===== 所有操作完成 =====") + + except Exception as e: + print(f"\n操作失败: {str(e)}") + exit(1) + +if __name__ == "__main__": + main() diff --git a/deploy/api/api_login/main.go b/deploy/api/api_login/main.go new file mode 100644 index 0000000..3e469fb --- /dev/null +++ b/deploy/api/api_login/main.go @@ -0,0 +1,162 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "net/http" + "os" + + "github.com/gin-gonic/gin" + _ "github.com/lib/pq" + "golang.org/x/crypto/bcrypt" +) + +// LoginRequest 登录请求参数结构 +type LoginRequest struct { + Account string `json:"account" binding:"required"` + Password string `json:"password" binding:"required"` +} + +// LoginResponse 登录响应结构 +type LoginResponse struct { + Success bool `json:"success"` + Message string `json:"message"` + Data struct { + UserID string `json:"user_id,omitempty"` + } `json:"data,omitempty"` +} + +var db *sql.DB + +func main() { + log.Println("开始初始化应用程序") + + // 初始化Gin引擎 + r := gin.Default() + log.Println("Gin引擎初始化完成") + + // 从环境变量获取数据库配置 + dbHost := os.Getenv("DB_HOST") + dbPort := os.Getenv("DB_PORT") + dbUser := os.Getenv("DB_USER") + dbPassword := os.Getenv("DB_PASSWORD") + dbName := os.Getenv("DB_NAME") + log.Printf("获取数据库配置: host=%s, port=%s, user=%s, dbname=%s", dbHost, dbPort, dbUser, dbName) + + // 构建数据库连接字符串 + connStr := fmt.Sprintf( + "host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", + dbHost, dbPort, dbUser, dbPassword, dbName, + ) + log.Printf("数据库连接字符串构建完成: %s", connStr) + + var err error + db, err = sql.Open("postgres", connStr) + if err != nil { + log.Fatalf("无法连接数据库: %v", err) + panic(fmt.Sprintf("无法连接数据库: %v", err)) + } + defer db.Close() + log.Println("数据库连接对象创建成功") + + // 验证数据库连接 + if err := db.Ping(); err != nil { + log.Fatalf("数据库连接失败: %v", err) + panic(fmt.Sprintf("数据库连接失败: %v", err)) + } + log.Println("数据库连接验证成功") + + // 登录接口 + r.POST("/user/login", loginHandler) + log.Println("登录接口注册完成: POST /user/login") + + // 启动服务,监听80端口 + log.Println("服务启动在80端口") + r.Run(":80") +} + +// loginHandler 处理登录逻辑 +func loginHandler(c *gin.Context) { + reqID := c.Request.Header.Get("X-Request-ID") + if reqID == "" { + reqID = fmt.Sprintf("%d", gin.Mode()) // 简单生成一个请求标识 + } + log.Printf("[%s] 收到登录请求 from %s", reqID, c.ClientIP()) + + var req LoginRequest + // 绑定并验证请求参数 + if err := c.ShouldBindJSON(&req); err != nil { + log.Printf("[%s] 请求参数绑定失败: %v, 请求体: %+v", reqID, err, c.Request.Body) + c.JSON(http.StatusBadRequest, LoginResponse{ + Success: false, + Message: "账号或密码不能为空", + }) + return + } + log.Printf("[%s] 请求参数绑定成功: 账号=%s", reqID, req.Account) + + // 1. 判断账号和密码是否为空(双重保险) + if req.Account == "" || req.Password == "" { + log.Printf("[%s] 账号或密码为空: 账号=%s", reqID, req.Account) + c.JSON(http.StatusBadRequest, LoginResponse{ + Success: false, + Message: "账号或密码不能为空", + }) + return + } + + // 2. 查询账号对应的密码和用户ID + var storedPassword string + var userID string + query := ` + SELECT password, user_id + FROM user_account_password_view + WHERE account = $1 AND deleted = false + ` + log.Printf("[%s] 执行查询: %s, 参数: %s", reqID, query, req.Account) + err := db.QueryRow(query, req.Account).Scan(&storedPassword, &userID) + switch { + case err == sql.ErrNoRows: + // 账号不存在或已被删除 + log.Printf("[%s] 账号不存在或已删除: %s", reqID, req.Account) + c.JSON(http.StatusOK, LoginResponse{ + Success: false, + Message: "账号不存在", + }) + return + case err != nil: + log.Printf("[%s] 查询账号信息失败: %v, 账号=%s", reqID, err, req.Account) + c.JSON(http.StatusInternalServerError, LoginResponse{ + Success: false, + Message: "查询账号信息失败", + }) + return + } + log.Printf("[%s] 查询账号信息成功: 账号=%s, userID=%s", reqID, req.Account, userID) + + // 3. 验证密码(使用bcrypt比较原始密码和存储的加密密码) + err = bcrypt.CompareHashAndPassword([]byte(storedPassword), []byte(req.Password)) + if err != nil { + // 密码不匹配 + log.Printf("[%s] 密码验证失败: 账号=%s, 错误=%v", reqID, req.Account, err) + c.JSON(http.StatusOK, LoginResponse{ + Success: false, + Message: "密码错误", + }) + return + } + log.Printf("[%s] 密码验证成功: 账号=%s", reqID, req.Account) + + // 4. 登录成功 + log.Printf("[%s] 登录成功: 账号=%s, userID=%s", reqID, req.Account, userID) + c.JSON(http.StatusOK, LoginResponse{ + Success: true, + Message: "登录成功", + Data: struct { + UserID string `json:"user_id,omitempty"` + }{ + UserID: userID, + }, + }) +} \ No newline at end of file diff --git a/deploy/api/api_login/release.sh b/deploy/api/api_login/release.sh new file mode 100755 index 0000000..b38aecf --- /dev/null +++ b/deploy/api/api_login/release.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e # 当任何命令失败时立即退出脚本 + +# 定义镜像名称和标签 +IMAGE_NAME="user-login-api" +IMAGE_TAG="1.0.0" +FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}" + +echo "开始删除现有镜像 ${FULL_IMAGE}..." +if sudo docker rmi -f "${FULL_IMAGE}" >/dev/null 2>&1; then + echo "镜像 ${FULL_IMAGE} 删除成功" +else + echo "镜像 ${FULL_IMAGE} 不存在,跳过删除步骤" +fi + +echo "开始构建新镜像 ${FULL_IMAGE}..." +if sudo docker build -t "${FULL_IMAGE}" .; then + echo "镜像 ${FULL_IMAGE} 构建成功!" +else + echo "错误:镜像构建失败" >&2 + exit 1 +fi \ No newline at end of file diff --git a/deploy/api/api_register/.dockerignore b/deploy/api/api_register/.dockerignore new file mode 100644 index 0000000..3d1e553 --- /dev/null +++ b/deploy/api/api_register/.dockerignore @@ -0,0 +1,26 @@ +# 排除Go编译产物 +*.exe +*.exe~ +*.dll +*.so +*.dylib +app # 排除本地已构建的二进制文件 + +# 排除依赖目录 +vendor/ +go/pkg/ +db +scripts +shared_data + +# 排除版本控制和日志文件 +.git/ +.gitignore +logs/ +*.log +*.md +*.sql + +# 排除IDE配置文件 +.idea/ +.vscode/ \ No newline at end of file diff --git a/deploy/api/api_register/README.md b/deploy/api/api_register/README.md new file mode 100644 index 0000000..7f009bf --- /dev/null +++ b/deploy/api/api_register/README.md @@ -0,0 +1,7 @@ +sudo docker rm -f user_register_api && sudo docker run -itd --name user_register_api -v ./:/app -p 20002:80 golang:1.25.0-alpine3.22 + +---- + +# 格式:docker build -t 镜像名:标签 . +sudo docker rmi -f user-register-api:1.0.0 +sudo docker build -t user-register-api:1.0.0 . \ No newline at end of file diff --git a/deploy/api/api_register/docker-compose.register.yaml b/deploy/api/api_register/docker-compose.register.yaml new file mode 100644 index 0000000..712b371 --- /dev/null +++ b/deploy/api/api_register/docker-compose.register.yaml @@ -0,0 +1,16 @@ +services: + user_register: + image: user-register-api:1.0.0 + container_name: api_user_register + restart: always + depends_on: + - postgres # 依赖数据库服务 + networks: + - user-network + environment: + DB_HOST: postgres + DB_PORT: ${DB_PORT} # 引用.env变量 + DB_USER: ${DB_USER} # 引用.env变量 + DB_PASSWORD: ${DB_PASSWORD} # 引用.env变量 + DB_NAME: ${DB_NAME} + TZ: ${TZ} # 引用.env变量 \ No newline at end of file diff --git a/deploy/api/api_register/dockerfile b/deploy/api/api_register/dockerfile new file mode 100644 index 0000000..9c811fc --- /dev/null +++ b/deploy/api/api_register/dockerfile @@ -0,0 +1,38 @@ +# ==================== 第一阶段:构建Go程序(构建阶段)==================== +# 使用官方Go镜像作为构建基础,选择与项目匹配的Go版本(示例用1.25.0,可根据实际调整) +FROM golang:1.25.0-alpine3.22 AS builder + +# 设置工作目录(容器内的目录,规范文件位置) +WORKDIR /app + +# 复制go.mod和go.sum(先复制依赖文件,利用Docker缓存机制,避免每次代码变动都重新下载依赖) +COPY go.mod go.sum ./ + +# 下载项目依赖(仅当go.mod/go.sum变动时才会重新执行) +RUN go mod download + +# 复制整个项目代码到工作目录 +COPY . . + +# 构建Go程序: +# - CGO_ENABLED=0:禁用CGO,生成静态链接的二进制文件(避免依赖系统库,保证镜像兼容性) +# - -o app:指定输出二进制文件名为app +# - ./main.go:指定入口文件 +RUN CGO_ENABLED=0 GOOS=linux go build -o app ./main.go + + +# ==================== 第二阶段:运行程序(运行阶段)==================== +# 使用轻量级的alpine镜像(仅5MB左右,大幅减小最终镜像体积) +FROM alpine:3.19 + +# 设置工作目录 +WORKDIR /app + +# 从构建阶段复制编译好的二进制文件到当前镜像(仅复制最终产物,减小体积) +COPY --from=builder /app/app ./ + +# 暴露程序运行端口(与代码中一致) +EXPOSE 8080 + +# 容器启动时执行的命令:运行二进制文件 +CMD ["./app"] \ No newline at end of file diff --git a/deploy/api/api_register/go.mod b/deploy/api/api_register/go.mod new file mode 100644 index 0000000..5813d4f --- /dev/null +++ b/deploy/api/api_register/go.mod @@ -0,0 +1,42 @@ +module user_register + +go 1.25.0 + +require ( + github.com/gin-gonic/gin v1.11.0 + github.com/lib/pq v1.10.9 + golang.org/x/crypto v0.43.0 +) + +require ( + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + go.uber.org/mock v0.5.0 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.45.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/tools v0.37.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect +) diff --git a/deploy/api/api_register/go.sum b/deploy/api/api_register/go.sum new file mode 100644 index 0000000..653eb06 --- /dev/null +++ b/deploy/api/api_register/go.sum @@ -0,0 +1,90 @@ +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/deploy/api/api_register/init.py b/deploy/api/api_register/init.py new file mode 100644 index 0000000..7131e22 --- /dev/null +++ b/deploy/api/api_register/init.py @@ -0,0 +1,69 @@ +import subprocess +import os + +def run_command(command, check=True, shell=True): + """执行shell命令并返回结果,包含错误处理""" + try: + print(f"执行命令: {command}") + result = subprocess.run( + command, + shell=shell, + check=check, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + print(f"命令输出: {result.stdout}") + return result + except subprocess.CalledProcessError as e: + print(f"命令执行失败: {e.stderr}") + raise + +def main(): + container_name = "user_register_api" + script_dir = os.path.dirname(os.path.abspath(__file__)) # 获取当前脚本所在目录 + + try: + # 1. 删除已存在的容器 + print("===== 步骤1: 删除已存在的容器 =====") + run_command(f"sudo docker rm -f {container_name}", check=False) + + # 2. 启动新容器 + print("\n===== 步骤2: 启动新容器 =====") + run_command( + f"sudo docker run -itd --name {container_name} " + f"-v {script_dir}:/app -p 30001:80 golang:1.25.0-alpine3.22" + ) + + # 3-5. 进入容器、进入app目录并执行go mod tidy(使用sh而非bash) + print("\n===== 步骤3-5: 容器内操作 =====") + exec_commands = ( + "cd /app && " # 进入app目录 + "echo '当前目录内容:' && ls -la && " # 新增目录检查 + "go version && " # 检查Go版本 + "go mod init user_update_password && " # 执行依赖整理 + "go mod tidy && " # 执行依赖整理 + "exit" # 退出容器 + ) + + # 使用sh代替bash执行命令 + run_command( + f"sudo docker exec -it {container_name} sh -c '{exec_commands}'" + ) + + # 7. 删除容器 + print("\n===== 步骤7: 删除容器 =====") + run_command(f"sudo docker rm -f {container_name}") + + # 8. 删除虚悬镜像 + print("\n===== 步骤8: 清理虚悬镜像 =====") + run_command("sudo docker images -f 'dangling=true' -q | xargs -r sudo docker rmi") + + print("\n===== 所有操作完成 =====") + + except Exception as e: + print(f"\n操作失败: {str(e)}") + exit(1) + +if __name__ == "__main__": + main() diff --git a/deploy/api/api_register/main.go b/deploy/api/api_register/main.go new file mode 100644 index 0000000..8438e06 --- /dev/null +++ b/deploy/api/api_register/main.go @@ -0,0 +1,272 @@ +package main + +import ( + "database/sql" + "fmt" + "net/http" + "os" + "time" + + "github.com/gin-gonic/gin" + _ "github.com/lib/pq" + "golang.org/x/crypto/bcrypt" +) + +// RegisterRequest 注册请求参数结构 +type RegisterRequest struct { + Account string `json:"account" binding:"required"` + Password string `json:"password" binding:"required"` +} + +// RegisterResponse 注册响应结构 +type RegisterResponse struct { + Success bool `json:"success"` + Message string `json:"message"` + Data struct { + UserID string `json:"user_id,omitempty"` + Account string `json:"account,omitempty"` + } `json:"data,omitempty"` +} + +var db *sql.DB + +func main() { + // 初始化Gin引擎 + fmt.Println("[主程序] 初始化Gin引擎") + r := gin.Default() + + // 从环境变量获取数据库配置 + fmt.Println("[主程序] 读取数据库配置环境变量") + dbHost := os.Getenv("DB_HOST") + dbPort := os.Getenv("DB_PORT") + dbUser := os.Getenv("DB_USER") + dbPassword := os.Getenv("DB_PASSWORD") + dbName := os.Getenv("DB_NAME") + + // 构建数据库连接字符串 + connStr := fmt.Sprintf( + "host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", + dbHost, dbPort, dbUser, dbPassword, dbName, + ) + fmt.Printf("[主程序] 数据库连接字符串构建完成: %s\n", maskPassword(connStr)) + + // 连接数据库 + var err error + db, err = sql.Open("postgres", connStr) + if err != nil { + panic(fmt.Sprintf("[主程序] 无法连接数据库: %v", err)) + } + defer db.Close() + fmt.Println("[主程序] 数据库连接对象创建成功") + + // 验证数据库连接 + if err := db.Ping(); err != nil { + panic(fmt.Sprintf("[主程序] 数据库连接失败: %v", err)) + } + fmt.Println("[主程序] 数据库连接验证成功") + + // 注册账号接口 + r.POST("/user/register", registerHandler) + fmt.Println("[主程序] 注册接口路由已配置") + + // 启动服务,监听80端口 + fmt.Println("[主程序] 服务启动在80端口") + r.Run(":80") +} + +// 屏蔽连接字符串中的密码,避免日志泄露敏感信息 +func maskPassword(connStr string) string { + // 简单处理:替换password=后的内容直到下一个空格 + start := false + result := "" + for _, c := range connStr { + if start && c == ' ' { + start = false + } + if start { + continue + } + result += string(c) + // 先检查长度是否足够,避免索引越界 + if len(result) >= 10 && result[len(result)-10:] == "password=" { + start = true + result += "***" + } + } + return result +} + +// registerHandler 处理用户注册逻辑 +func registerHandler(c *gin.Context) { + startTime := time.Now() + reqID := fmt.Sprintf("req-%d", time.Now().UnixNano()) // 生成请求唯一标识 + fmt.Printf("[%s] 收到注册请求,开始处理\n", reqID) + + var req RegisterRequest + // 绑定并验证请求参数 + if err := c.ShouldBindJSON(&req); err != nil { + fmt.Printf("[%s] 请求参数绑定失败: %v\n", reqID, err) + c.JSON(http.StatusBadRequest, RegisterResponse{ + Success: false, + Message: "请求参数错误: " + err.Error(), + }) + return + } + fmt.Printf("[%s] 请求参数绑定成功,账号: %s\n", reqID, req.Account) + + // 1. 判断接口的账号和密码是否为空 + if req.Account == "" || req.Password == "" { + fmt.Printf("[%s] 账号或密码为空,拒绝注册\n", reqID) + c.JSON(http.StatusBadRequest, RegisterResponse{ + Success: false, + Message: "账号和密码不能为空", + }) + return + } + + // 2. 使用接口账号查询视图,检查账号是否已存在 + var exists bool + query := ` + SELECT EXISTS( + SELECT 1 + FROM user_account_password_view + WHERE account = $1 AND deleted = false + ) + ` + fmt.Printf("[%s] 执行账号存在性查询: %s\n", reqID, query) + err := db.QueryRow(query, req.Account).Scan(&exists) + if err != nil { + fmt.Printf("[%s] 账号查询失败: %v\n", reqID, err) + c.JSON(http.StatusInternalServerError, RegisterResponse{ + Success: false, + Message: "查询账号信息失败: " + err.Error(), + }) + return + } + fmt.Printf("[%s] 账号存在性查询完成,存在: %v\n", reqID, exists) + + // 3. 判断查询结果,若存在则提示账号已存在 + if exists { + fmt.Printf("[%s] 账号已存在: %s\n", reqID, req.Account) + c.JSON(http.StatusOK, RegisterResponse{ + Success: false, + Message: "账号已存在", + }) + return + } + + // 4. 开启数据库事务,确保数据一致性 + tx, err := db.Begin() + if err != nil { + fmt.Printf("[%s] 开启事务失败: %v\n", reqID, err) + c.JSON(http.StatusInternalServerError, RegisterResponse{ + Success: false, + Message: "开启事务失败: " + err.Error(), + }) + return + } + defer func() { + if r := recover(); r != nil { + fmt.Printf("[%s] 发生恐慌,回滚事务: %v\n", reqID, r) + tx.Rollback() + } + }() + fmt.Printf("[%s] 数据库事务开启成功\n", reqID) + + // 5. 在user表生成新用户ID + var userID string + insertUserQuery := ` + INSERT INTO "user" DEFAULT VALUES + RETURNING id + ` + fmt.Printf("[%s] 执行用户创建: %s\n", reqID, insertUserQuery) + err = tx.QueryRow(insertUserQuery).Scan(&userID) + if err != nil { + fmt.Printf("[%s] 创建用户失败: %v\n", reqID, err) + tx.Rollback() + c.JSON(http.StatusInternalServerError, RegisterResponse{ + Success: false, + Message: "创建用户失败: " + err.Error(), + }) + return + } + fmt.Printf("[%s] 用户创建成功,用户ID: %s\n", reqID, userID) + + // 6. 对密码进行加密处理 + fmt.Printf("[%s] 开始密码加密\n", reqID) + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) + if err != nil { + fmt.Printf("[%s] 密码加密失败: %v\n", reqID, err) + tx.Rollback() + c.JSON(http.StatusInternalServerError, RegisterResponse{ + Success: false, + Message: "密码加密失败: " + err.Error(), + }) + return + } + fmt.Printf("[%s] 密码加密成功\n", reqID) + + // 7. 插入user_account表 + insertAccountQuery := ` + INSERT INTO user_account (user_id, account) + VALUES ($1, $2) + ` + fmt.Printf("[%s] 执行账号插入: %s, 参数: user_id=%s, account=%s\n", + reqID, insertAccountQuery, userID, req.Account) + _, err = tx.Exec(insertAccountQuery, userID, req.Account) + if err != nil { + fmt.Printf("[%s] 保存账号信息失败: %v\n", reqID, err) + tx.Rollback() + c.JSON(http.StatusInternalServerError, RegisterResponse{ + Success: false, + Message: "保存账号信息失败: " + err.Error(), + }) + return + } + fmt.Printf("[%s] 账号信息保存成功\n", reqID) + + // 8. 插入user_password表 + insertPasswordQuery := ` + INSERT INTO user_password (user_id, password) + VALUES ($1, $2) + ` + fmt.Printf("[%s] 执行密码插入: %s, 参数: user_id=%s\n", + reqID, insertPasswordQuery, userID) + _, err = tx.Exec(insertPasswordQuery, userID, string(hashedPassword)) + if err != nil { + fmt.Printf("[%s] 保存密码信息失败: %v\n", reqID, err) + tx.Rollback() + c.JSON(http.StatusInternalServerError, RegisterResponse{ + Success: false, + Message: "保存密码信息失败: " + err.Error(), + }) + return + } + fmt.Printf("[%s] 密码信息保存成功\n", reqID) + + // 9. 提交事务 + if err := tx.Commit(); err != nil { + fmt.Printf("[%s] 提交事务失败: %v\n", reqID, err) + tx.Rollback() + c.JSON(http.StatusInternalServerError, RegisterResponse{ + Success: false, + Message: "提交事务失败: " + err.Error(), + }) + return + } + fmt.Printf("[%s] 事务提交成功\n", reqID) + + // 10. 注册成功 + response := RegisterResponse{ + Success: true, + Message: "注册成功", + } + response.Data.UserID = userID + response.Data.Account = req.Account + + duration := time.Since(startTime) + fmt.Printf("[%s] 注册成功,耗时: %v, 用户ID: %s, 账号: %s\n", + reqID, duration, userID, req.Account) + + c.JSON(http.StatusOK, response) +} \ No newline at end of file diff --git a/deploy/api/api_register/release.sh b/deploy/api/api_register/release.sh new file mode 100755 index 0000000..62ea272 --- /dev/null +++ b/deploy/api/api_register/release.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e # 当任何命令失败时立即退出脚本 + +# 定义镜像名称和标签 +IMAGE_NAME="user-register-api" +IMAGE_TAG="1.0.0" +FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}" + +echo "开始删除现有镜像 ${FULL_IMAGE}..." +if sudo docker rmi -f "${FULL_IMAGE}" >/dev/null 2>&1; then + echo "镜像 ${FULL_IMAGE} 删除成功" +else + echo "镜像 ${FULL_IMAGE} 不存在,跳过删除步骤" +fi + +echo "开始构建新镜像 ${FULL_IMAGE}..." +if sudo docker build -t "${FULL_IMAGE}" .; then + echo "镜像 ${FULL_IMAGE} 构建成功!" +else + echo "错误:镜像构建失败" >&2 + exit 1 +fi \ No newline at end of file diff --git a/deploy/api/api_update_account/.dockerignore b/deploy/api/api_update_account/.dockerignore new file mode 100644 index 0000000..3d1e553 --- /dev/null +++ b/deploy/api/api_update_account/.dockerignore @@ -0,0 +1,26 @@ +# 排除Go编译产物 +*.exe +*.exe~ +*.dll +*.so +*.dylib +app # 排除本地已构建的二进制文件 + +# 排除依赖目录 +vendor/ +go/pkg/ +db +scripts +shared_data + +# 排除版本控制和日志文件 +.git/ +.gitignore +logs/ +*.log +*.md +*.sql + +# 排除IDE配置文件 +.idea/ +.vscode/ \ No newline at end of file diff --git a/deploy/api/api_update_account/README.md b/deploy/api/api_update_account/README.md new file mode 100644 index 0000000..9fda8c4 --- /dev/null +++ b/deploy/api/api_update_account/README.md @@ -0,0 +1,7 @@ +sudo docker rm -f user_update_account_api && sudo docker run -itd --name user_update_account_api -v ./:/app -p 20003:80 golang:1.25.0-alpine3.22 + +---- + +# 格式:docker build -t 镜像名:标签 . +sudo docker rmi -f user-update-account-api:1.0.0 +sudo docker build -t user-update-account-api:1.0.0 . \ No newline at end of file diff --git a/deploy/api/api_update_account/docker-compose.update.account.yaml b/deploy/api/api_update_account/docker-compose.update.account.yaml new file mode 100644 index 0000000..fd9063f --- /dev/null +++ b/deploy/api/api_update_account/docker-compose.update.account.yaml @@ -0,0 +1,16 @@ +services: + user_update_account: + image: user-update-account-api:1.0.0 + container_name: api_user_update_account + restart: always + depends_on: + - postgres # 依赖数据库服务 + networks: + - user-network + environment: + DB_HOST: postgres + DB_PORT: ${DB_PORT} # 引用.env变量 + DB_USER: ${DB_USER} # 引用.env变量 + DB_PASSWORD: ${DB_PASSWORD} # 引用.env变量 + DB_NAME: ${DB_NAME} + TZ: ${TZ} # 引用.env变量 \ No newline at end of file diff --git a/deploy/api/api_update_account/dockerfile b/deploy/api/api_update_account/dockerfile new file mode 100644 index 0000000..9c811fc --- /dev/null +++ b/deploy/api/api_update_account/dockerfile @@ -0,0 +1,38 @@ +# ==================== 第一阶段:构建Go程序(构建阶段)==================== +# 使用官方Go镜像作为构建基础,选择与项目匹配的Go版本(示例用1.25.0,可根据实际调整) +FROM golang:1.25.0-alpine3.22 AS builder + +# 设置工作目录(容器内的目录,规范文件位置) +WORKDIR /app + +# 复制go.mod和go.sum(先复制依赖文件,利用Docker缓存机制,避免每次代码变动都重新下载依赖) +COPY go.mod go.sum ./ + +# 下载项目依赖(仅当go.mod/go.sum变动时才会重新执行) +RUN go mod download + +# 复制整个项目代码到工作目录 +COPY . . + +# 构建Go程序: +# - CGO_ENABLED=0:禁用CGO,生成静态链接的二进制文件(避免依赖系统库,保证镜像兼容性) +# - -o app:指定输出二进制文件名为app +# - ./main.go:指定入口文件 +RUN CGO_ENABLED=0 GOOS=linux go build -o app ./main.go + + +# ==================== 第二阶段:运行程序(运行阶段)==================== +# 使用轻量级的alpine镜像(仅5MB左右,大幅减小最终镜像体积) +FROM alpine:3.19 + +# 设置工作目录 +WORKDIR /app + +# 从构建阶段复制编译好的二进制文件到当前镜像(仅复制最终产物,减小体积) +COPY --from=builder /app/app ./ + +# 暴露程序运行端口(与代码中一致) +EXPOSE 8080 + +# 容器启动时执行的命令:运行二进制文件 +CMD ["./app"] \ No newline at end of file diff --git a/deploy/api/api_update_account/go.mod b/deploy/api/api_update_account/go.mod new file mode 100644 index 0000000..42d7ef2 --- /dev/null +++ b/deploy/api/api_update_account/go.mod @@ -0,0 +1,42 @@ +module user_update_account + +go 1.25.0 + +require ( + github.com/gin-gonic/gin v1.11.0 + github.com/lib/pq v1.10.9 +) + +require ( + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + go.uber.org/mock v0.5.0 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/mod v0.25.0 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.27.0 // indirect + golang.org/x/tools v0.34.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect +) diff --git a/deploy/api/api_update_account/go.sum b/deploy/api/api_update_account/go.sum new file mode 100644 index 0000000..2e645e7 --- /dev/null +++ b/deploy/api/api_update_account/go.sum @@ -0,0 +1,90 @@ +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/deploy/api/api_update_account/init.py b/deploy/api/api_update_account/init.py new file mode 100644 index 0000000..f66f690 --- /dev/null +++ b/deploy/api/api_update_account/init.py @@ -0,0 +1,69 @@ +import subprocess +import os + +def run_command(command, check=True, shell=True): + """执行shell命令并返回结果,包含错误处理""" + try: + print(f"执行命令: {command}") + result = subprocess.run( + command, + shell=shell, + check=check, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + print(f"命令输出: {result.stdout}") + return result + except subprocess.CalledProcessError as e: + print(f"命令执行失败: {e.stderr}") + raise + +def main(): + container_name = "user_update_account_api" + script_dir = os.path.dirname(os.path.abspath(__file__)) # 获取当前脚本所在目录 + + try: + # 1. 删除已存在的容器 + print("===== 步骤1: 删除已存在的容器 =====") + run_command(f"sudo docker rm -f {container_name}", check=False) + + # 2. 启动新容器 + print("\n===== 步骤2: 启动新容器 =====") + run_command( + f"sudo docker run -itd --name {container_name} " + f"-v {script_dir}:/app -p 30001:80 golang:1.25.0-alpine3.22" + ) + + # 3-5. 进入容器、进入app目录并执行go mod tidy(使用sh而非bash) + print("\n===== 步骤3-5: 容器内操作 =====") + exec_commands = ( + "cd /app && " # 进入app目录 + "echo '当前目录内容:' && ls -la && " # 新增目录检查 + "go version && " # 检查Go版本 + "go mod init user_update_account && " # 执行依赖整理 + "go mod tidy && " # 执行依赖整理 + "exit" # 退出容器 + ) + + # 使用sh代替bash执行命令 + run_command( + f"sudo docker exec -it {container_name} sh -c '{exec_commands}'" + ) + + # 7. 删除容器 + print("\n===== 步骤7: 删除容器 =====") + run_command(f"sudo docker rm -f {container_name}") + + # 8. 删除虚悬镜像 + print("\n===== 步骤8: 清理虚悬镜像 =====") + run_command("sudo docker images -f 'dangling=true' -q | xargs -r sudo docker rmi") + + print("\n===== 所有操作完成 =====") + + except Exception as e: + print(f"\n操作失败: {str(e)}") + exit(1) + +if __name__ == "__main__": + main() diff --git a/deploy/api/api_update_account/main.go b/deploy/api/api_update_account/main.go new file mode 100644 index 0000000..ae49099 --- /dev/null +++ b/deploy/api/api_update_account/main.go @@ -0,0 +1,204 @@ +package main + +import ( + "database/sql" + "fmt" + "net/http" + "os" + "time" + + "github.com/gin-gonic/gin" + _ "github.com/lib/pq" +) + +// UpdateAccountRequest 更新账号请求参数 +type UpdateAccountRequest struct { + UserID string `json:"user_id" binding:"required"` + Account string `json:"account" binding:"required"` +} + +// UpdateAccountResponse 更新账号响应结构 +type UpdateAccountResponse struct { + Success bool `json:"success"` + Message string `json:"message"` + Data struct { + UserID string `json:"user_id,omitempty"` + Account string `json:"account,omitempty"` + } `json:"data,omitempty"` +} + +var db *sql.DB + +func main() { + // 记录程序启动时间 + startTime := time.Now() + fmt.Printf("[%s] 程序开始启动\n", startTime.Format(time.RFC3339)) + + // 初始化Gin引擎 + r := gin.Default() + fmt.Println("[INFO] Gin引擎初始化完成") + + // 从环境变量获取数据库配置 + fmt.Println("[INFO] 开始读取数据库配置") + dbHost := os.Getenv("DB_HOST") + dbPort := os.Getenv("DB_PORT") + dbUser := os.Getenv("DB_USER") + dbPassword := os.Getenv("DB_PASSWORD") + dbName := os.Getenv("DB_NAME") + + fmt.Printf("[INFO] 数据库配置: host=%s, port=%s, user=%s, dbname=%s\n", + dbHost, dbPort, dbUser, dbName) + + // 构建数据库连接字符串 + connStr := fmt.Sprintf( + "host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", + dbHost, dbPort, dbUser, dbPassword, dbName, + ) + fmt.Println("[INFO] 数据库连接字符串构建完成") + + var err error + db, err = sql.Open("postgres", connStr) + if err != nil { + panic(fmt.Sprintf("[ERROR] 无法连接数据库: %v", err)) + } + defer func() { + // 关闭数据库连接时记录日志 + if err := db.Close(); err != nil { + fmt.Printf("[ERROR] 关闭数据库连接失败: %v\n", err) + } else { + fmt.Println("[INFO] 数据库连接已关闭") + } + }() + + // 验证数据库连接 + fmt.Println("[INFO] 开始验证数据库连接") + if err := db.Ping(); err != nil { + panic(fmt.Sprintf("[ERROR] 数据库连接失败: %v", err)) + } + fmt.Println("[INFO] 数据库连接验证成功") + + // 注册更新账号接口 + r.POST("/user/update/account", updateAccountHandler) + fmt.Println("[INFO] 已注册接口: POST /user/update/account") + + // 启动服务,监听80端口 + startMsg := "服务启动在80端口" + fmt.Printf("[%s] %s\n", time.Now().Format(time.RFC3339), startMsg) + fmt.Printf("[INFO] 启动耗时: %v\n", time.Since(startTime)) + + if err := r.Run(":80"); err != nil { + fmt.Printf("[ERROR] 服务启动失败: %v\n", err) + } +} + +// updateAccountHandler 处理账号更新逻辑 +func updateAccountHandler(c *gin.Context) { + // 记录请求开始时间和请求ID + startTime := time.Now() + reqID := c.Request.Header.Get("X-Request-ID") + if reqID == "" { + reqID = fmt.Sprintf("req-%d", time.Now().UnixNano()) + } + fmt.Printf("[%s] [REQUEST] %s - 收到请求: %s %s, RequestID: %s\n", + startTime.Format(time.RFC3339), + c.ClientIP(), + c.Request.Method, + c.Request.URL.Path, + reqID, + ) + + var req UpdateAccountRequest + // 绑定并验证请求参数 + if err := c.ShouldBindJSON(&req); err != nil { + errMsg := fmt.Sprintf("请求参数错误: %v", err) + fmt.Printf("[%s] [ERROR] %s, RequestID: %s, 错误详情: %v\n", + time.Now().Format(time.RFC3339), + errMsg, + reqID, + err, + ) + c.JSON(http.StatusBadRequest, UpdateAccountResponse{ + Success: false, + Message: errMsg, + }) + return + } + + // 打印接收到的请求参数 + fmt.Printf("[%s] [INFO] 接收到更新请求, RequestID: %s, UserID: %s, 新账号: %s\n", + time.Now().Format(time.RFC3339), + reqID, + req.UserID, + req.Account, + ) + + // 更新账号信息 + updateQuery := ` + UPDATE user_account + SET account = $1 + WHERE user_id = $2 + RETURNING id, user_id, account + ` + fmt.Printf("[%s] [INFO] 执行SQL: %q, 参数: [%s, %s], RequestID: %s\n", + time.Now().Format(time.RFC3339), + updateQuery, + req.Account, + req.UserID, + reqID, + ) + + var ( + id string + userID string + account string + ) + err := db.QueryRow(updateQuery, req.Account, req.UserID).Scan(&id, &userID, &account) + switch { + case err == sql.ErrNoRows: + errMsg := "未找到该用户的账号信息" + fmt.Printf("[%s] [WARN] %s, UserID: %s, RequestID: %s\n", + time.Now().Format(time.RFC3339), + errMsg, + req.UserID, + reqID, + ) + c.JSON(http.StatusOK, UpdateAccountResponse{ + Success: false, + Message: errMsg, + }) + return + case err != nil: + errMsg := fmt.Sprintf("更新账号失败: %v", err) + fmt.Printf("[%s] [ERROR] %s, UserID: %s, RequestID: %s, 错误详情: %v\n", + time.Now().Format(time.RFC3339), + errMsg, + req.UserID, + reqID, + err, + ) + c.JSON(http.StatusInternalServerError, UpdateAccountResponse{ + Success: false, + Message: errMsg, + }) + return + } + + // 更新成功 + response := UpdateAccountResponse{ + Success: true, + Message: "账号更新成功", + } + response.Data.UserID = userID + response.Data.Account = account + + // 记录成功日志 + fmt.Printf("[%s] [INFO] 账号更新成功, RequestID: %s, UserID: %s, 新账号: %s, 耗时: %v\n", + time.Now().Format(time.RFC3339), + reqID, + userID, + account, + time.Since(startTime), + ) + + c.JSON(http.StatusOK, response) +} \ No newline at end of file diff --git a/deploy/api/api_update_account/release.sh b/deploy/api/api_update_account/release.sh new file mode 100755 index 0000000..ce683a6 --- /dev/null +++ b/deploy/api/api_update_account/release.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e # 当任何命令失败时立即退出脚本 + +# 定义镜像名称和标签 +IMAGE_NAME="user-update-account-api" +IMAGE_TAG="1.0.0" +FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}" + +echo "开始删除现有镜像 ${FULL_IMAGE}..." +if sudo docker rmi -f "${FULL_IMAGE}" >/dev/null 2>&1; then + echo "镜像 ${FULL_IMAGE} 删除成功" +else + echo "镜像 ${FULL_IMAGE} 不存在,跳过删除步骤" +fi + +echo "开始构建新镜像 ${FULL_IMAGE}..." +if sudo docker build -t "${FULL_IMAGE}" .; then + echo "镜像 ${FULL_IMAGE} 构建成功!" +else + echo "错误:镜像构建失败" >&2 + exit 1 +fi \ No newline at end of file diff --git a/deploy/api/api_update_password/.dockerignore b/deploy/api/api_update_password/.dockerignore new file mode 100644 index 0000000..3d1e553 --- /dev/null +++ b/deploy/api/api_update_password/.dockerignore @@ -0,0 +1,26 @@ +# 排除Go编译产物 +*.exe +*.exe~ +*.dll +*.so +*.dylib +app # 排除本地已构建的二进制文件 + +# 排除依赖目录 +vendor/ +go/pkg/ +db +scripts +shared_data + +# 排除版本控制和日志文件 +.git/ +.gitignore +logs/ +*.log +*.md +*.sql + +# 排除IDE配置文件 +.idea/ +.vscode/ \ No newline at end of file diff --git a/deploy/api/api_update_password/README.md b/deploy/api/api_update_password/README.md new file mode 100644 index 0000000..4e8dd4b --- /dev/null +++ b/deploy/api/api_update_password/README.md @@ -0,0 +1,7 @@ +sudo docker rm -f user_update_password_api && sudo docker run -itd --name user_update_password_api -v ./:/app -p 20004:80 golang:1.25.0-alpine3.22 + +---- + +# 格式:docker build -t 镜像名:标签 . +sudo docker rmi -f user-update-password-api:1.0.0 +sudo docker build -t user-update-password-api:1.0.0 . \ No newline at end of file diff --git a/deploy/api/api_update_password/docker-compose.update.password.yaml b/deploy/api/api_update_password/docker-compose.update.password.yaml new file mode 100644 index 0000000..a668c79 --- /dev/null +++ b/deploy/api/api_update_password/docker-compose.update.password.yaml @@ -0,0 +1,16 @@ +services: + user_update_password: + image: user-update-password-api:1.0.0 + container_name: api_user_update_password + restart: always + depends_on: + - postgres # 依赖数据库服务 + networks: + - user-network + environment: + DB_HOST: postgres + DB_PORT: ${DB_PORT} # 引用.env变量 + DB_USER: ${DB_USER} # 引用.env变量 + DB_PASSWORD: ${DB_PASSWORD} # 引用.env变量 + DB_NAME: ${DB_NAME} + TZ: ${TZ} # 引用.env变量 \ No newline at end of file diff --git a/deploy/api/api_update_password/dockerfile b/deploy/api/api_update_password/dockerfile new file mode 100644 index 0000000..9c811fc --- /dev/null +++ b/deploy/api/api_update_password/dockerfile @@ -0,0 +1,38 @@ +# ==================== 第一阶段:构建Go程序(构建阶段)==================== +# 使用官方Go镜像作为构建基础,选择与项目匹配的Go版本(示例用1.25.0,可根据实际调整) +FROM golang:1.25.0-alpine3.22 AS builder + +# 设置工作目录(容器内的目录,规范文件位置) +WORKDIR /app + +# 复制go.mod和go.sum(先复制依赖文件,利用Docker缓存机制,避免每次代码变动都重新下载依赖) +COPY go.mod go.sum ./ + +# 下载项目依赖(仅当go.mod/go.sum变动时才会重新执行) +RUN go mod download + +# 复制整个项目代码到工作目录 +COPY . . + +# 构建Go程序: +# - CGO_ENABLED=0:禁用CGO,生成静态链接的二进制文件(避免依赖系统库,保证镜像兼容性) +# - -o app:指定输出二进制文件名为app +# - ./main.go:指定入口文件 +RUN CGO_ENABLED=0 GOOS=linux go build -o app ./main.go + + +# ==================== 第二阶段:运行程序(运行阶段)==================== +# 使用轻量级的alpine镜像(仅5MB左右,大幅减小最终镜像体积) +FROM alpine:3.19 + +# 设置工作目录 +WORKDIR /app + +# 从构建阶段复制编译好的二进制文件到当前镜像(仅复制最终产物,减小体积) +COPY --from=builder /app/app ./ + +# 暴露程序运行端口(与代码中一致) +EXPOSE 8080 + +# 容器启动时执行的命令:运行二进制文件 +CMD ["./app"] \ No newline at end of file diff --git a/deploy/api/api_update_password/go.mod b/deploy/api/api_update_password/go.mod new file mode 100644 index 0000000..39a557a --- /dev/null +++ b/deploy/api/api_update_password/go.mod @@ -0,0 +1,42 @@ +module user_update_password + +go 1.25.0 + +require ( + github.com/gin-gonic/gin v1.11.0 + github.com/lib/pq v1.10.9 + golang.org/x/crypto v0.43.0 +) + +require ( + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + go.uber.org/mock v0.5.0 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.45.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/tools v0.37.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect +) diff --git a/deploy/api/api_update_password/go.sum b/deploy/api/api_update_password/go.sum new file mode 100644 index 0000000..653eb06 --- /dev/null +++ b/deploy/api/api_update_password/go.sum @@ -0,0 +1,90 @@ +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/deploy/api/api_update_password/init.py b/deploy/api/api_update_password/init.py new file mode 100644 index 0000000..feecdf7 --- /dev/null +++ b/deploy/api/api_update_password/init.py @@ -0,0 +1,69 @@ +import subprocess +import os + +def run_command(command, check=True, shell=True): + """执行shell命令并返回结果,包含错误处理""" + try: + print(f"执行命令: {command}") + result = subprocess.run( + command, + shell=shell, + check=check, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + print(f"命令输出: {result.stdout}") + return result + except subprocess.CalledProcessError as e: + print(f"命令执行失败: {e.stderr}") + raise + +def main(): + container_name = "user_update_password_api" + script_dir = os.path.dirname(os.path.abspath(__file__)) # 获取当前脚本所在目录 + + try: + # 1. 删除已存在的容器 + print("===== 步骤1: 删除已存在的容器 =====") + run_command(f"sudo docker rm -f {container_name}", check=False) + + # 2. 启动新容器 + print("\n===== 步骤2: 启动新容器 =====") + run_command( + f"sudo docker run -itd --name {container_name} " + f"-v {script_dir}:/app -p 30001:80 golang:1.25.0-alpine3.22" + ) + + # 3-5. 进入容器、进入app目录并执行go mod tidy(使用sh而非bash) + print("\n===== 步骤3-5: 容器内操作 =====") + exec_commands = ( + "cd /app && " # 进入app目录 + "echo '当前目录内容:' && ls -la && " # 新增目录检查 + "go version && " # 检查Go版本 + "go mod init user_update_password && " # 执行依赖整理 + "go mod tidy && " # 执行依赖整理 + "exit" # 退出容器 + ) + + # 使用sh代替bash执行命令 + run_command( + f"sudo docker exec -it {container_name} sh -c '{exec_commands}'" + ) + + # 7. 删除容器 + print("\n===== 步骤7: 删除容器 =====") + run_command(f"sudo docker rm -f {container_name}") + + # 8. 删除虚悬镜像 + print("\n===== 步骤8: 清理虚悬镜像 =====") + run_command("sudo docker images -f 'dangling=true' -q | xargs -r sudo docker rmi") + + print("\n===== 所有操作完成 =====") + + except Exception as e: + print(f"\n操作失败: {str(e)}") + exit(1) + +if __name__ == "__main__": + main() diff --git a/deploy/api/api_update_password/main.go b/deploy/api/api_update_password/main.go new file mode 100644 index 0000000..26e13a0 --- /dev/null +++ b/deploy/api/api_update_password/main.go @@ -0,0 +1,206 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "net/http" + "os" + + "github.com/gin-gonic/gin" + _ "github.com/lib/pq" + "golang.org/x/crypto/bcrypt" +) + +// UpdatePasswordRequest 更新密码请求参数结构 +type UpdatePasswordRequest struct { + UserID string `json:"user_id" binding:"required"` + OldPassword string `json:"old_password" binding:"required"` + NewPassword string `json:"new_password" binding:"required,min=6"` +} + +// UpdatePasswordResponse 更新密码响应结构 +type UpdatePasswordResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +var db *sql.DB + +func main() { + // 初始化日志输出格式 + log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) + log.Println("开始初始化应用程序") + + // 初始化Gin引擎 + r := gin.Default() + log.Println("Gin引擎初始化完成") + + // 从环境变量获取数据库配置 + dbHost := os.Getenv("DB_HOST") + dbPort := os.Getenv("DB_PORT") + dbUser := os.Getenv("DB_USER") + dbPassword := os.Getenv("DB_PASSWORD") + dbName := os.Getenv("DB_NAME") + log.Printf("读取数据库配置: host=%s, port=%s, user=%s, dbname=%s", dbHost, dbPort, dbUser, dbName) + + // 构建数据库连接字符串 + connStr := fmt.Sprintf( + "host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", + dbHost, dbPort, dbUser, dbPassword, dbName, + ) + + var err error + db, err = sql.Open("postgres", connStr) + if err != nil { + log.Panicf("无法连接数据库: %v", err) + } + defer db.Close() + log.Println("数据库连接对象创建成功") + + // 验证数据库连接 + if err := db.Ping(); err != nil { + log.Panicf("数据库连接失败: %v", err) + } + log.Println("数据库连接验证成功") + + // 更新密码接口 + r.POST("/user/update/password", updatePasswordHandler) + log.Println("注册更新密码接口: POST /user/update/password") + + // 启动服务,监听80端口 + log.Println("服务启动在80端口") + if err := r.Run(":80"); err != nil { + log.Panicf("服务启动失败: %v", err) + } +} + +// updatePasswordHandler 处理密码更新逻辑 +func updatePasswordHandler(c *gin.Context) { + requestID := c.Request.Header.Get("X-Request-ID") + if requestID == "" { + requestID = fmt.Sprintf("req-%d", gin.Mode()) + } + log.Printf("[%s] 收到更新密码请求 from %s", requestID, c.ClientIP()) + + var req UpdatePasswordRequest + // 绑定并验证请求参数 + if err := c.ShouldBindJSON(&req); err != nil { + log.Printf("[%s] 请求参数绑定失败: %v", requestID, err) + c.JSON(http.StatusBadRequest, UpdatePasswordResponse{ + Success: false, + Message: "请求参数错误: " + err.Error(), + }) + return + } + log.Printf("[%s] 请求参数验证通过, 用户ID: %s", requestID, req.UserID) + + // 1. 检查新旧密码是否一致 + if req.OldPassword == req.NewPassword { + log.Printf("[%s] 新密码与旧密码相同, 用户ID: %s", requestID, req.UserID) + c.JSON(http.StatusOK, UpdatePasswordResponse{ + Success: false, + Message: "新密码不能与旧密码相同", + }) + return + } + + // 2. 对旧密码进行加密处理 + log.Printf("[%s] 开始对旧密码进行加密, 用户ID: %s", requestID, req.UserID) + hashedOldPassword, err := bcrypt.GenerateFromPassword([]byte(req.OldPassword), bcrypt.DefaultCost) + if err != nil { + log.Printf("[%s] 旧密码加密失败: %v, 用户ID: %s", requestID, err, req.UserID) + c.JSON(http.StatusInternalServerError, UpdatePasswordResponse{ + Success: false, + Message: "旧密码加密失败: " + err.Error(), + }) + return + } + log.Printf("[%s] 旧密码加密完成, 用户ID: %s", requestID, req.UserID) + + // 3. 使用加密后的旧密码查询并验证(按user_id匹配) + var count int + query := ` + SELECT COUNT(*) FROM user_password + WHERE user_id = $1 AND password = $2 + ` + log.Printf("[%s] 执行旧密码验证查询, SQL: %s, 参数: [%s, ****]", requestID, query, req.UserID) + err = db.QueryRow(query, req.UserID, string(hashedOldPassword)).Scan(&count) + if err != nil { + log.Printf("[%s] 旧密码验证查询失败: %v, 用户ID: %s", requestID, err, req.UserID) + c.JSON(http.StatusInternalServerError, UpdatePasswordResponse{ + Success: false, + Message: "验证旧密码失败: " + err.Error(), + }) + return + } + + // 验证失败处理 + if count == 0 { + log.Printf("[%s] 旧密码验证失败, 用户ID: %s", requestID, req.UserID) + c.JSON(http.StatusOK, UpdatePasswordResponse{ + Success: false, + Message: "旧密码不正确", + }) + return + } + log.Printf("[%s] 旧密码验证成功, 用户ID: %s", requestID, req.UserID) + + // 4. 对新密码进行加密处理 + log.Printf("[%s] 开始对新密码进行加密, 用户ID: %s", requestID, req.UserID) + hashedNewPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost) + if err != nil { + log.Printf("[%s] 新密码加密失败: %v, 用户ID: %s", requestID, err, req.UserID) + c.JSON(http.StatusInternalServerError, UpdatePasswordResponse{ + Success: false, + Message: "新密码加密失败: " + err.Error(), + }) + return + } + log.Printf("[%s] 新密码加密完成, 用户ID: %s", requestID, req.UserID) + + // 5. 更新密码表中的密码(按user_id匹配) + updateQuery := ` + UPDATE user_password + SET password = $1 + WHERE user_id = $2 + ` + log.Printf("[%s] 执行密码更新, SQL: %s, 参数: [****, %s]", requestID, updateQuery, req.UserID) + result, err := db.Exec(updateQuery, string(hashedNewPassword), req.UserID) + if err != nil { + log.Printf("[%s] 密码更新执行失败: %v, 用户ID: %s", requestID, err, req.UserID) + c.JSON(http.StatusInternalServerError, UpdatePasswordResponse{ + Success: false, + Message: "更新密码失败: " + err.Error(), + }) + return + } + + // 6. 检查更新结果 + rowsAffected, err := result.RowsAffected() + if err != nil { + log.Printf("[%s] 获取更新行数失败: %v, 用户ID: %s", requestID, err, req.UserID) + c.JSON(http.StatusInternalServerError, UpdatePasswordResponse{ + Success: false, + Message: "检查更新结果失败: " + err.Error(), + }) + return + } + log.Printf("[%s] 密码更新影响行数: %d, 用户ID: %s", requestID, rowsAffected, req.UserID) + + if rowsAffected == 0 { + log.Printf("[%s] 未找到用户或密码未变化, 用户ID: %s", requestID, req.UserID) + c.JSON(http.StatusOK, UpdatePasswordResponse{ + Success: false, + Message: "未找到用户或密码未发生变化", + }) + return + } + + // 7. 更新成功 + log.Printf("[%s] 密码更新成功, 用户ID: %s", requestID, req.UserID) + c.JSON(http.StatusOK, UpdatePasswordResponse{ + Success: true, + Message: "密码更新成功", + }) +} \ No newline at end of file diff --git a/deploy/api/api_update_password/release.sh b/deploy/api/api_update_password/release.sh new file mode 100755 index 0000000..760d402 --- /dev/null +++ b/deploy/api/api_update_password/release.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e # 当任何命令失败时立即退出脚本 + +# 定义镜像名称和标签 +IMAGE_NAME="user-update-password-api" +IMAGE_TAG="1.0.0" +FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}" + +echo "开始删除现有镜像 ${FULL_IMAGE}..." +if sudo docker rmi -f "${FULL_IMAGE}" >/dev/null 2>&1; then + echo "镜像 ${FULL_IMAGE} 删除成功" +else + echo "镜像 ${FULL_IMAGE} 不存在,跳过删除步骤" +fi + +echo "开始构建新镜像 ${FULL_IMAGE}..." +if sudo docker build -t "${FULL_IMAGE}" .; then + echo "镜像 ${FULL_IMAGE} 构建成功!" +else + echo "错误:镜像构建失败" >&2 + exit 1 +fi \ No newline at end of file diff --git a/deploy/deploy.sh b/deploy/deploy.sh new file mode 100644 index 0000000..8222bab --- /dev/null +++ b/deploy/deploy.sh @@ -0,0 +1,82 @@ +#!/bin/bash +set -e # 遇到错误立即退出,确保部署流程的可靠性 + +# 颜色输出配置(增强可读性) +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # 重置颜色 + +COMPOSE_PROJECT_NAME="user_service" +DOCKER_COMPOSE_FILE="./docker-compose.yaml" +API_DIR="./api" # API服务所在目录 + +# 步骤1:停止指定名称的docker-compose编排 +echo -e "${YELLOW}===== 第一步:停止编排名称为 $COMPOSE_PROJECT_NAME 的服务 ====="${NC} +if [ -f "$DOCKER_COMPOSE_FILE" ]; then + echo "正在停止服务..." + docker-compose -f "$DOCKER_COMPOSE_FILE" -p "$COMPOSE_PROJECT_NAME" down + if [ $? -eq 0 ]; then + echo -e "${GREEN}编排 $COMPOSE_PROJECT_NAME 已成功停止${NC}" + else + echo -e "${RED}停止编排 $COMPOSE_PROJECT_NAME 失败!${NC}" + exit 1 + fi +else + echo -e "${RED}错误:未找到 $DOCKER_COMPOSE_FILE 文件${NC}" + exit 1 +fi + +# 步骤2:处理所有API服务的release.sh +echo -e "\n${YELLOW}===== 第二步:执行所有API服务的构建脚本 ====="${NC} +if [ -d "$API_DIR" ]; then + # 遍历API目录下的所有子文件夹 + for api_folder in "$API_DIR"/*/; do + # 检查是否为目录 + if [ -d "$api_folder" ]; then + # 提取文件夹名称(用于日志显示) + folder_name=$(basename "$api_folder") + echo -e "\n${YELLOW}处理服务: $folder_name${NC}" + + # 检查release.sh是否存在 + release_script="${api_folder}/release.sh" + if [ -f "$release_script" ]; then + echo "给予 $release_script 执行权限..." + chmod +x "$release_script" + + echo "执行 $release_script ..." + (cd "$api_folder" && ./release.sh) # 进入目录并执行脚本 + + if [ $? -eq 0 ]; then + echo -e "${GREEN}$folder_name 服务构建完成${NC}" + else + echo -e "${RED}$folder_name 服务构建失败!${NC}" + exit 1 + fi + else + echo -e "${YELLOW}警告:$api_folder 中未找到 release.sh,跳过该目录${NC}" + fi + fi + done +else + echo -e "${RED}错误:未找到 $API_DIR 目录${NC}" + exit 1 +fi + +# 步骤3:启动指定名称的docker-compose编排 +echo -e "\n${YELLOW}===== 第三步:启动编排名称为 $COMPOSE_PROJECT_NAME 的服务 ====="${NC} +if [ -f "$DOCKER_COMPOSE_FILE" ]; then + echo "正在启动服务..." + docker-compose -f "$DOCKER_COMPOSE_FILE" -p "$COMPOSE_PROJECT_NAME" up -d + if [ $? -eq 0 ]; then + echo -e "${GREEN}编排 $COMPOSE_PROJECT_NAME 启动成功!${NC}" + else + echo -e "${RED}启动编排 $COMPOSE_PROJECT_NAME 失败!${NC}" + exit 1 + fi +else + echo -e "${RED}错误:未找到 $DOCKER_COMPOSE_FILE 文件${NC}" + exit 1 +fi + +echo -e "\n${GREEN}===== 所有部署步骤已完成 ====="${NC} \ No newline at end of file diff --git a/deploy/docker-compose.yaml b/deploy/docker-compose.yaml new file mode 100644 index 0000000..d963c70 --- /dev/null +++ b/deploy/docker-compose.yaml @@ -0,0 +1,122 @@ +services: + pgadmin: + image: dpage/pgadmin4:9.5.0 + container_name: user_pgadmin + ports: + - 20001:80 + environment: + PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL} + PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD} + TZ: ${TZ} + volumes: + - ./shared_data/data4pgadmin:/var/lib/pgadmin + networks: + - user-network + depends_on: + - postgres + postgres: + image: postgres:17.4-alpine + container_name: user_db + restart: always + entrypoint: + - /scripts/db-lanuch-entrypoint.sh + environment: + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DB: ${DB_NAME} + TZ: ${TZ} + volumes: + - ./shared_data/user_db:/var/lib/postgresql/data + - ./sql:/docker-entrypoint-initdb.d + - ./scripts:/scripts + networks: + - user-network + user_login: + image: user-login-api:1.0.0 + container_name: api_user_login + restart: always + depends_on: + - postgres + networks: + - user-network + environment: + DB_HOST: postgres + DB_PORT: ${DB_PORT} + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + DB_NAME: ${DB_NAME} + TZ: ${TZ} + user_update_account: + image: user-update-account-api:1.0.0 + container_name: api_user_update_account + restart: always + depends_on: + - postgres + networks: + - user-network + environment: + DB_HOST: postgres + DB_PORT: ${DB_PORT} + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + DB_NAME: ${DB_NAME} + TZ: ${TZ} + user_gateway: + image: user-gateway-api:1.0.0 + container_name: api_user_gateway + restart: always + ports: + - 20000:80 + networks: + - user-network + environment: + GATEWAY_PORT: ${GATEWAY_PORT} + user_update_password: + image: user-update-password-api:1.0.0 + container_name: api_user_update_password + restart: always + depends_on: + - postgres + networks: + - user-network + environment: + DB_HOST: postgres + DB_PORT: ${DB_PORT} + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + DB_NAME: ${DB_NAME} + TZ: ${TZ} + user_register: + image: user-register-api:1.0.0 + container_name: api_user_register + restart: always + depends_on: + - postgres + networks: + - user-network + environment: + DB_HOST: postgres + DB_PORT: ${DB_PORT} + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + DB_NAME: ${DB_NAME} + TZ: ${TZ} + user_delete: + image: user-delete-api:1.0.0 + container_name: api_user_delete + restart: always + depends_on: + - postgres + networks: + - user-network + environment: + DB_HOST: postgres + DB_PORT: ${DB_PORT} + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + DB_NAME: ${DB_NAME} + TZ: ${TZ} +networks: + user-network: + driver: bridge +volumes: {} diff --git a/deploy/scripts/db-lanuch-entrypoint.sh b/deploy/scripts/db-lanuch-entrypoint.sh new file mode 100755 index 0000000..69d6aba --- /dev/null +++ b/deploy/scripts/db-lanuch-entrypoint.sh @@ -0,0 +1,43 @@ +#!/bin/sh +set -e # 脚本执行出错时立即退出 + +# -------------------------- +# 1. 启动PostgreSQL服务(后台运行) +# -------------------------- +# 调用PostgreSQL默认初始化逻辑(即使数据目录已存在,也会启动服务) +docker-entrypoint.sh postgres & +# 记录PostgreSQL主进程ID,后续等待用 +PG_PID=$! + +# -------------------------- +# 2. 等待数据库服务就绪(避免脚本执行时数据库未启动) +# -------------------------- +echo "等待PostgreSQL服务就绪..." +# 将所有参数放在同一行,避免换行解析问题 +until pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" -h "localhost" -p "5432"; do + sleep 1 # 每1秒检查一次 +done +echo "PostgreSQL服务已就绪,开始强制执行脚本..." + +# -------------------------- +# 3. 强制执行所有挂载的SQL脚本(每次启动都执行) +# -------------------------- +# 遍历/docker-entrypoint-initdb.d目录下的所有.sql脚本(按文件名排序) +for script in /docker-entrypoint-initdb.d/*.sql; do + if [ -f "$script" ]; then # 确保是文件(排除目录) + echo "正在执行脚本: $script" + # 用psql客户端执行脚本,指定用户和数据库 + psql -U "$POSTGRES_USER" \ + -d "$POSTGRES_DB" \ + -h "localhost" \ + -p "5432" \ + -f "$script" \ + --set=ON_ERROR_STOP=1 # 脚本执行出错时停止(可选,根据需求调整) + echo "脚本执行完成: $script" + fi +done + +# -------------------------- +# 4. 等待PostgreSQL主进程(避免容器启动后退出) +# -------------------------- +wait $PG_PID \ No newline at end of file diff --git a/deploy/sql/01_uuid_v7_setup.sql b/deploy/sql/01_uuid_v7_setup.sql new file mode 100644 index 0000000..b317d1c --- /dev/null +++ b/deploy/sql/01_uuid_v7_setup.sql @@ -0,0 +1,48 @@ +-- 切换到目标数据库 +\c postgres; + +-- 检查并创建UUID扩展(如果不存在) +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- 定义检测UUID v7支持的函数 +CREATE OR REPLACE FUNCTION check_uuid_v7_support() RETURNS BOOLEAN AS $$ +DECLARE + test_uuid UUID; +BEGIN + BEGIN + SELECT gen_random_uuid_v7() INTO test_uuid; + RETURN TRUE; + EXCEPTION + WHEN undefined_function THEN + RETURN FALSE; + END; +END; +$$ LANGUAGE plpgsql VOLATILE; + +-- 创建UUID v7兼容函数(修复UUID格式长度问题) +CREATE OR REPLACE FUNCTION gen_random_uuid_v7() RETURNS uuid AS $$ +DECLARE + unix_ts_ms BIGINT; + rand_a BIGINT; + rand_b BIGINT; + hex_str TEXT; +BEGIN + -- 获取当前毫秒级Unix时间戳 + unix_ts_ms := (EXTRACT(EPOCH FROM NOW()) * 1000)::BIGINT; + + -- 生成随机数(调整随机数范围以确保总长度正确) + rand_a := (random() * (2^20 - 1))::BIGINT; + rand_b := (random() * (2^44 - 1))::BIGINT; -- 从48位调整为44位,减少2个字节 + + -- 组合UUID v7格式(确保总长度为32个十六进制字符) + hex_str := + lpad(to_hex(unix_ts_ms >> 12), 8, '0') || + lpad(to_hex((unix_ts_ms & 4095) << 4), 4, '0') || + '7' || lpad(to_hex(rand_a >> 18), 3, '0') || + lpad(to_hex(8 + (rand_a & 16383) >> 12), 2, '0') || + lpad(to_hex(rand_a & 4095), 3, '0') || + lpad(to_hex(rand_b), 11, '0'); -- 从12位调整为11位 + + RETURN hex_str::uuid; +END; +$$ LANGUAGE plpgsql VOLATILE; \ No newline at end of file diff --git a/deploy/sql/02_create_user_table.sql b/deploy/sql/02_create_user_table.sql new file mode 100644 index 0000000..0de15d2 --- /dev/null +++ b/deploy/sql/02_create_user_table.sql @@ -0,0 +1,30 @@ +-- 切换到目标数据库 +\c postgres; + +CREATE OR REPLACE FUNCTION update_user_modified_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; +END; +$$ LANGUAGE plpgsql VOLATILE; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'user') THEN + CREATE TABLE "user" ( -- user是关键字,用双引号包裹 + id UUID DEFAULT gen_random_uuid_v7() PRIMARY KEY NOT NULL, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + CREATE TRIGGER update_user_updated_at + BEFORE UPDATE ON "user" + FOR EACH ROW + EXECUTE FUNCTION update_user_modified_column(); + + RAISE NOTICE 'Created user table and trigger'; + ELSE + RAISE NOTICE 'user table already exists'; + END IF; +END $$; \ No newline at end of file diff --git a/deploy/sql/03_create_account_table.sql b/deploy/sql/03_create_account_table.sql new file mode 100644 index 0000000..754794b --- /dev/null +++ b/deploy/sql/03_create_account_table.sql @@ -0,0 +1,31 @@ +-- 切换到目标数据库 +\c postgres; + +CREATE OR REPLACE FUNCTION update_account_modified_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; +END; +$$ LANGUAGE plpgsql VOLATILE; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'user_account') THEN + CREATE TABLE user_account ( + id UUID DEFAULT gen_random_uuid_v7() PRIMARY KEY NOT NULL, + user_id UUID NOT NULL, + account VARCHAR NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + CREATE TRIGGER update_user_account_updated_at + BEFORE UPDATE ON "user_account" + FOR EACH ROW + EXECUTE FUNCTION update_account_modified_column(); + + RAISE NOTICE 'Created user_account table and trigger'; + ELSE + RAISE NOTICE 'user_account table already exists'; + END IF; +END $$; \ No newline at end of file diff --git a/deploy/sql/04_create_password_table.sql b/deploy/sql/04_create_password_table.sql new file mode 100644 index 0000000..bff72b7 --- /dev/null +++ b/deploy/sql/04_create_password_table.sql @@ -0,0 +1,31 @@ +-- 切换到目标数据库 +\c postgres; + +CREATE OR REPLACE FUNCTION update_password_modified_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; +END; +$$ LANGUAGE plpgsql VOLATILE; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'user_password') THEN + CREATE TABLE user_password ( + id UUID DEFAULT gen_random_uuid_v7() PRIMARY KEY NOT NULL, + user_id UUID NOT NULL, + password VARCHAR NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + CREATE TRIGGER update_user_password_updated_at + BEFORE UPDATE ON "user_password" + FOR EACH ROW + EXECUTE FUNCTION update_password_modified_column(); + + RAISE NOTICE 'Created user_password table and trigger'; + ELSE + RAISE NOTICE 'user_password table already exists'; + END IF; +END $$; \ No newline at end of file diff --git a/deploy/sql/05_create_account_password_view.sql b/deploy/sql/05_create_account_password_view.sql new file mode 100644 index 0000000..32d2f5f --- /dev/null +++ b/deploy/sql/05_create_account_password_view.sql @@ -0,0 +1,36 @@ +\c postgres; + +DO $$ +DECLARE + view_exists BOOLEAN; +BEGIN + -- 检查视图是否已存在 + SELECT EXISTS ( + SELECT 1 FROM information_schema.views + WHERE table_name = 'user_account_password_view' + ) INTO view_exists; + + -- 创建或更新视图 + CREATE OR REPLACE VIEW user_account_password_view AS + SELECT + u.id AS user_id, + ua.account AS account, + up.password AS password, + u.deleted AS deleted + FROM + "user" u + JOIN + user_account ua ON u.id = ua.user_id + JOIN + user_password up ON u.id = up.user_id; + + -- 根据视图是否已存在输出不同提示 + IF view_exists THEN + RAISE NOTICE '视图 user_account_password_view 已更新'; + ELSE + RAISE NOTICE '视图 user_account_password_view 已创建'; + END IF; +EXCEPTION + WHEN OTHERS THEN + RAISE NOTICE '处理视图时发生错误: %', SQLERRM; +END $$; diff --git a/docker-compose.base.yaml b/docker-compose.base.yaml new file mode 100644 index 0000000..ed268aa --- /dev/null +++ b/docker-compose.base.yaml @@ -0,0 +1,9 @@ +# 共享网络和基础配置 +networks: + user-network: + driver: bridge + +# 如需跨服务共享数据卷,可在此定义 +# volumes: + # 示例:若未来有共享数据需求,可在此声明 + # shared-data: \ No newline at end of file diff --git a/docker-compose.db.admin.yaml b/docker-compose.db.admin.yaml new file mode 100644 index 0000000..fba30da --- /dev/null +++ b/docker-compose.db.admin.yaml @@ -0,0 +1,16 @@ +services: + pgadmin: + image: dpage/pgadmin4:9.5.0 + container_name: user_pgadmin + ports: + - "20001:80" + environment: + PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL} + PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD} + TZ: ${TZ} + volumes: + - ./shared_data/data4pgadmin:/var/lib/pgadmin + networks: + - user-network + depends_on: + - postgres # 依赖数据库服务(非必须,仅控制启动顺序) \ No newline at end of file diff --git a/docker-compose.db.yaml b/docker-compose.db.yaml new file mode 100644 index 0000000..00de517 --- /dev/null +++ b/docker-compose.db.yaml @@ -0,0 +1,17 @@ +services: + postgres: + image: postgres:17.4-alpine + container_name: user_db + restart: always + entrypoint: ["/scripts/db-lanuch-entrypoint.sh"] + environment: + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DB: ${DB_NAME} + TZ: ${TZ} + volumes: + - ./shared_data/user_db:/var/lib/postgresql/data + - ./sql:/docker-entrypoint-initdb.d + - ./scripts:/scripts + networks: + - user-network \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..d963c70 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,122 @@ +services: + pgadmin: + image: dpage/pgadmin4:9.5.0 + container_name: user_pgadmin + ports: + - 20001:80 + environment: + PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL} + PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD} + TZ: ${TZ} + volumes: + - ./shared_data/data4pgadmin:/var/lib/pgadmin + networks: + - user-network + depends_on: + - postgres + postgres: + image: postgres:17.4-alpine + container_name: user_db + restart: always + entrypoint: + - /scripts/db-lanuch-entrypoint.sh + environment: + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DB: ${DB_NAME} + TZ: ${TZ} + volumes: + - ./shared_data/user_db:/var/lib/postgresql/data + - ./sql:/docker-entrypoint-initdb.d + - ./scripts:/scripts + networks: + - user-network + user_login: + image: user-login-api:1.0.0 + container_name: api_user_login + restart: always + depends_on: + - postgres + networks: + - user-network + environment: + DB_HOST: postgres + DB_PORT: ${DB_PORT} + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + DB_NAME: ${DB_NAME} + TZ: ${TZ} + user_update_account: + image: user-update-account-api:1.0.0 + container_name: api_user_update_account + restart: always + depends_on: + - postgres + networks: + - user-network + environment: + DB_HOST: postgres + DB_PORT: ${DB_PORT} + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + DB_NAME: ${DB_NAME} + TZ: ${TZ} + user_gateway: + image: user-gateway-api:1.0.0 + container_name: api_user_gateway + restart: always + ports: + - 20000:80 + networks: + - user-network + environment: + GATEWAY_PORT: ${GATEWAY_PORT} + user_update_password: + image: user-update-password-api:1.0.0 + container_name: api_user_update_password + restart: always + depends_on: + - postgres + networks: + - user-network + environment: + DB_HOST: postgres + DB_PORT: ${DB_PORT} + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + DB_NAME: ${DB_NAME} + TZ: ${TZ} + user_register: + image: user-register-api:1.0.0 + container_name: api_user_register + restart: always + depends_on: + - postgres + networks: + - user-network + environment: + DB_HOST: postgres + DB_PORT: ${DB_PORT} + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + DB_NAME: ${DB_NAME} + TZ: ${TZ} + user_delete: + image: user-delete-api:1.0.0 + container_name: api_user_delete + restart: always + depends_on: + - postgres + networks: + - user-network + environment: + DB_HOST: postgres + DB_PORT: ${DB_PORT} + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + DB_NAME: ${DB_NAME} + TZ: ${TZ} +networks: + user-network: + driver: bridge +volumes: {}