保留最新代码,清除历史记录
This commit is contained in:
26
.dockerignore
Normal file
26
.dockerignore
Normal file
@@ -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/
|
56
.gitignore
vendored
Normal file
56
.gitignore
vendored
Normal file
@@ -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
|
130
README.md
Normal file
130
README.md
Normal file
@@ -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的脚本
|
||||||
|
```
|
26
api_template/.dockerignore
Normal file
26
api_template/.dockerignore
Normal file
@@ -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/
|
7
api_template/README.md
Normal file
7
api_template/README.md
Normal file
@@ -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 .
|
16
api_template/docker-compose.temp.yaml
Normal file
16
api_template/docker-compose.temp.yaml
Normal file
@@ -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变量
|
38
api_template/dockerfile
Normal file
38
api_template/dockerfile
Normal file
@@ -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"]
|
69
api_template/init.py
Normal file
69
api_template/init.py
Normal file
@@ -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()
|
22
api_template/release.sh
Executable file
22
api_template/release.sh
Executable file
@@ -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
|
136
create_api.py
Normal file
136
create_api.py
Normal file
@@ -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()
|
15
deploy/.env
Normal file
15
deploy/.env
Normal file
@@ -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
|
26
deploy/api/api_delete/.dockerignore
Normal file
26
deploy/api/api_delete/.dockerignore
Normal file
@@ -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/
|
7
deploy/api/api_delete/README.md
Normal file
7
deploy/api/api_delete/README.md
Normal file
@@ -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 .
|
16
deploy/api/api_delete/docker-compose.delete.yaml
Normal file
16
deploy/api/api_delete/docker-compose.delete.yaml
Normal file
@@ -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变量
|
41
deploy/api/api_delete/dockerfile
Normal file
41
deploy/api/api_delete/dockerfile
Normal file
@@ -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"]
|
42
deploy/api/api_delete/go.mod
Normal file
42
deploy/api/api_delete/go.mod
Normal file
@@ -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
|
||||||
|
)
|
91
deploy/api/api_delete/go.sum
Normal file
91
deploy/api/api_delete/go.sum
Normal file
@@ -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=
|
69
deploy/api/api_delete/init.py
Normal file
69
deploy/api/api_delete/init.py
Normal file
@@ -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()
|
146
deploy/api/api_delete/main.go
Normal file
146
deploy/api/api_delete/main.go
Normal file
@@ -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: "账号删除成功。",
|
||||||
|
})
|
||||||
|
}
|
22
deploy/api/api_delete/release.sh
Executable file
22
deploy/api/api_delete/release.sh
Executable file
@@ -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
|
26
deploy/api/api_gateway/.dockerignore
Normal file
26
deploy/api/api_gateway/.dockerignore
Normal file
@@ -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/
|
7
deploy/api/api_gateway/README.md
Normal file
7
deploy/api/api_gateway/README.md
Normal file
@@ -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 .
|
11
deploy/api/api_gateway/docker-compose.gateway.yaml
Normal file
11
deploy/api/api_gateway/docker-compose.gateway.yaml
Normal file
@@ -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变量
|
38
deploy/api/api_gateway/dockerfile
Normal file
38
deploy/api/api_gateway/dockerfile
Normal file
@@ -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"]
|
42
deploy/api/api_gateway/go.mod
Normal file
42
deploy/api/api_gateway/go.mod
Normal file
@@ -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
|
||||||
|
)
|
91
deploy/api/api_gateway/go.sum
Normal file
91
deploy/api/api_gateway/go.sum
Normal file
@@ -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=
|
69
deploy/api/api_gateway/init.py
Normal file
69
deploy/api/api_gateway/init.py
Normal file
@@ -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()
|
137
deploy/api/api_gateway/main.go
Normal file
137
deploy/api/api_gateway/main.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
22
deploy/api/api_gateway/release.sh
Executable file
22
deploy/api/api_gateway/release.sh
Executable file
@@ -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
|
26
deploy/api/api_login/.dockerignore
Normal file
26
deploy/api/api_login/.dockerignore
Normal file
@@ -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/
|
7
deploy/api/api_login/README.md
Normal file
7
deploy/api/api_login/README.md
Normal file
@@ -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 .
|
16
deploy/api/api_login/docker-compose.login.yaml
Normal file
16
deploy/api/api_login/docker-compose.login.yaml
Normal file
@@ -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变量
|
38
deploy/api/api_login/dockerfile
Normal file
38
deploy/api/api_login/dockerfile
Normal file
@@ -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"]
|
42
deploy/api/api_login/go.mod
Normal file
42
deploy/api/api_login/go.mod
Normal file
@@ -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
|
||||||
|
)
|
90
deploy/api/api_login/go.sum
Normal file
90
deploy/api/api_login/go.sum
Normal file
@@ -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=
|
69
deploy/api/api_login/init.py
Normal file
69
deploy/api/api_login/init.py
Normal file
@@ -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()
|
162
deploy/api/api_login/main.go
Normal file
162
deploy/api/api_login/main.go
Normal file
@@ -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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
22
deploy/api/api_login/release.sh
Executable file
22
deploy/api/api_login/release.sh
Executable file
@@ -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
|
26
deploy/api/api_register/.dockerignore
Normal file
26
deploy/api/api_register/.dockerignore
Normal file
@@ -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/
|
7
deploy/api/api_register/README.md
Normal file
7
deploy/api/api_register/README.md
Normal file
@@ -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 .
|
16
deploy/api/api_register/docker-compose.register.yaml
Normal file
16
deploy/api/api_register/docker-compose.register.yaml
Normal file
@@ -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变量
|
38
deploy/api/api_register/dockerfile
Normal file
38
deploy/api/api_register/dockerfile
Normal file
@@ -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"]
|
42
deploy/api/api_register/go.mod
Normal file
42
deploy/api/api_register/go.mod
Normal file
@@ -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
|
||||||
|
)
|
90
deploy/api/api_register/go.sum
Normal file
90
deploy/api/api_register/go.sum
Normal file
@@ -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=
|
69
deploy/api/api_register/init.py
Normal file
69
deploy/api/api_register/init.py
Normal file
@@ -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()
|
272
deploy/api/api_register/main.go
Normal file
272
deploy/api/api_register/main.go
Normal file
@@ -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)
|
||||||
|
}
|
22
deploy/api/api_register/release.sh
Executable file
22
deploy/api/api_register/release.sh
Executable file
@@ -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
|
26
deploy/api/api_update_account/.dockerignore
Normal file
26
deploy/api/api_update_account/.dockerignore
Normal file
@@ -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/
|
7
deploy/api/api_update_account/README.md
Normal file
7
deploy/api/api_update_account/README.md
Normal file
@@ -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 .
|
@@ -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变量
|
38
deploy/api/api_update_account/dockerfile
Normal file
38
deploy/api/api_update_account/dockerfile
Normal file
@@ -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"]
|
42
deploy/api/api_update_account/go.mod
Normal file
42
deploy/api/api_update_account/go.mod
Normal file
@@ -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
|
||||||
|
)
|
90
deploy/api/api_update_account/go.sum
Normal file
90
deploy/api/api_update_account/go.sum
Normal file
@@ -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=
|
69
deploy/api/api_update_account/init.py
Normal file
69
deploy/api/api_update_account/init.py
Normal file
@@ -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()
|
204
deploy/api/api_update_account/main.go
Normal file
204
deploy/api/api_update_account/main.go
Normal file
@@ -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)
|
||||||
|
}
|
22
deploy/api/api_update_account/release.sh
Executable file
22
deploy/api/api_update_account/release.sh
Executable file
@@ -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
|
26
deploy/api/api_update_password/.dockerignore
Normal file
26
deploy/api/api_update_password/.dockerignore
Normal file
@@ -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/
|
7
deploy/api/api_update_password/README.md
Normal file
7
deploy/api/api_update_password/README.md
Normal file
@@ -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 .
|
@@ -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变量
|
38
deploy/api/api_update_password/dockerfile
Normal file
38
deploy/api/api_update_password/dockerfile
Normal file
@@ -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"]
|
42
deploy/api/api_update_password/go.mod
Normal file
42
deploy/api/api_update_password/go.mod
Normal file
@@ -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
|
||||||
|
)
|
90
deploy/api/api_update_password/go.sum
Normal file
90
deploy/api/api_update_password/go.sum
Normal file
@@ -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=
|
69
deploy/api/api_update_password/init.py
Normal file
69
deploy/api/api_update_password/init.py
Normal file
@@ -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()
|
206
deploy/api/api_update_password/main.go
Normal file
206
deploy/api/api_update_password/main.go
Normal file
@@ -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: "密码更新成功",
|
||||||
|
})
|
||||||
|
}
|
22
deploy/api/api_update_password/release.sh
Executable file
22
deploy/api/api_update_password/release.sh
Executable file
@@ -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
|
82
deploy/deploy.sh
Normal file
82
deploy/deploy.sh
Normal file
@@ -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}
|
122
deploy/docker-compose.yaml
Normal file
122
deploy/docker-compose.yaml
Normal file
@@ -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: {}
|
43
deploy/scripts/db-lanuch-entrypoint.sh
Executable file
43
deploy/scripts/db-lanuch-entrypoint.sh
Executable file
@@ -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
|
48
deploy/sql/01_uuid_v7_setup.sql
Normal file
48
deploy/sql/01_uuid_v7_setup.sql
Normal file
@@ -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;
|
30
deploy/sql/02_create_user_table.sql
Normal file
30
deploy/sql/02_create_user_table.sql
Normal file
@@ -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 $$;
|
31
deploy/sql/03_create_account_table.sql
Normal file
31
deploy/sql/03_create_account_table.sql
Normal file
@@ -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 $$;
|
31
deploy/sql/04_create_password_table.sql
Normal file
31
deploy/sql/04_create_password_table.sql
Normal file
@@ -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 $$;
|
36
deploy/sql/05_create_account_password_view.sql
Normal file
36
deploy/sql/05_create_account_password_view.sql
Normal file
@@ -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 $$;
|
9
docker-compose.base.yaml
Normal file
9
docker-compose.base.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# 共享网络和基础配置
|
||||||
|
networks:
|
||||||
|
user-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
# 如需跨服务共享数据卷,可在此定义
|
||||||
|
# volumes:
|
||||||
|
# 示例:若未来有共享数据需求,可在此声明
|
||||||
|
# shared-data:
|
16
docker-compose.db.admin.yaml
Normal file
16
docker-compose.db.admin.yaml
Normal file
@@ -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 # 依赖数据库服务(非必须,仅控制启动顺序)
|
17
docker-compose.db.yaml
Normal file
17
docker-compose.db.yaml
Normal file
@@ -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
|
122
docker-compose.yaml
Normal file
122
docker-compose.yaml
Normal file
@@ -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: {}
|
Reference in New Issue
Block a user