add
This commit is contained in:
21
backend/user/.env
Normal file
21
backend/user/.env
Normal file
@@ -0,0 +1,21 @@
|
||||
# 数据库配置
|
||||
DB_USER=postgres
|
||||
DB_PASSWORD=postgres12341234
|
||||
DB_NAME=postgres
|
||||
DB_PORT=5432
|
||||
DB_SSL_MODE=disable
|
||||
DB_MAX_OPEN_CONNS=25
|
||||
DB_MAX_IDLE_CONNS=25
|
||||
DB_TIMEOUT=30s
|
||||
|
||||
# 时区配置
|
||||
TZ=Asia/Shanghai
|
||||
|
||||
# 网关端口
|
||||
PORT=80
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL=info
|
||||
|
||||
# Gin模式 (debug/release/test)
|
||||
GIN_MODE=debug
|
||||
58
backend/user/build.sh
Normal file
58
backend/user/build.sh
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# 定义日志函数(带时间戳和级别)
|
||||
log_info() {
|
||||
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [INFO] $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [WARN] $1" >&2
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [ERROR] $1" >&2
|
||||
}
|
||||
|
||||
# 定义镜像信息
|
||||
IMAGE_NAME="user-api"
|
||||
IMAGE_TAG="1.0.0"
|
||||
FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
COMPOSE_PROJECT_NAME="user_service"
|
||||
DOCKER_COMPOSE_FILE="./docker-compose.yaml"
|
||||
|
||||
log_info "===== 开始执行构建脚本 ====="
|
||||
|
||||
# 步骤1:删除现有镜像
|
||||
log_info "尝试删除现有镜像: ${FULL_IMAGE}"
|
||||
if sudo docker rmi -f "${FULL_IMAGE}" >/dev/null 2>&1; then
|
||||
log_info "镜像 ${FULL_IMAGE} 删除成功"
|
||||
else
|
||||
log_warn "镜像 ${FULL_IMAGE} 不存在,跳过删除步骤"
|
||||
fi
|
||||
|
||||
# 步骤2:构建新镜像
|
||||
log_info "开始构建新镜像: ${FULL_IMAGE}(Dockerfile位于src目录)"
|
||||
if sudo docker build -t "${FULL_IMAGE}" -f ./src/Dockerfile .; then
|
||||
log_info "镜像 ${FULL_IMAGE} 构建成功"
|
||||
else
|
||||
log_error "镜像 ${FULL_IMAGE} 构建失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 步骤3:停止docker-compose服务
|
||||
log_info "开始停止编排服务: ${COMPOSE_PROJECT_NAME}"
|
||||
if [ ! -f "$DOCKER_COMPOSE_FILE" ]; then
|
||||
log_error "未找到docker-compose文件: ${DOCKER_COMPOSE_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "执行 docker-compose down 命令"
|
||||
if docker-compose -f "$DOCKER_COMPOSE_FILE" -p "$COMPOSE_PROJECT_NAME" down; then
|
||||
log_info "编排服务 ${COMPOSE_PROJECT_NAME} 已成功停止"
|
||||
else
|
||||
log_error "编排服务 ${COMPOSE_PROJECT_NAME} 停止失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "===== 构建脚本执行完成 ====="
|
||||
1
backend/user/dev-test.sh
Normal file
1
backend/user/dev-test.sh
Normal file
@@ -0,0 +1 @@
|
||||
docker run -itd --name go_user_dev -v $(pwd)/src:/app -p 12345:80 golang:1.25.0-alpine3.22
|
||||
32
backend/user/dev.sh
Normal file
32
backend/user/dev.sh
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [DEV_COMPOSE] $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [DEV_ERROR] $1" >&2
|
||||
}
|
||||
|
||||
COMPOSE_FILE="docker-compose-dev.yaml"
|
||||
|
||||
log_info "开始启动开发环境docker-compose服务"
|
||||
|
||||
# 检查文件是否存在
|
||||
if [ ! -f "$COMPOSE_FILE" ]; then
|
||||
log_error "未找到docker-compose文件: $COMPOSE_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 启动服务
|
||||
log_info "执行命令: sudo docker-compose -f $COMPOSE_FILE up -d"
|
||||
if sudo docker-compose -f "$COMPOSE_FILE" up -d; then
|
||||
log_info "开发环境服务启动成功"
|
||||
# 额外输出运行中的容器信息
|
||||
log_info "当前运行的容器:"
|
||||
sudo docker-compose -f "$COMPOSE_FILE" ps
|
||||
else
|
||||
log_error "开发环境服务启动失败"
|
||||
exit 1
|
||||
fi
|
||||
44
backend/user/docker-compose-dev.yaml
Normal file
44
backend/user/docker-compose-dev.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17.4-alpine
|
||||
container_name: user_db
|
||||
restart: always
|
||||
ports:
|
||||
- 20001:5432
|
||||
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:
|
||||
image: golang:1.25.0-alpine3.22
|
||||
container_name: api_user
|
||||
restart: always
|
||||
ports:
|
||||
- 20000:80
|
||||
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}
|
||||
volumes:
|
||||
- ./src:/app
|
||||
command: sh -c "cd /app && go mod tidy && go run main.go"
|
||||
networks:
|
||||
user-network:
|
||||
driver: bridge
|
||||
volumes: {}
|
||||
40
backend/user/docker-compose.yaml
Normal file
40
backend/user/docker-compose.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17.4-alpine
|
||||
container_name: user_db
|
||||
restart: always
|
||||
user: 0:0
|
||||
ports:
|
||||
- 20001:5432
|
||||
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:
|
||||
image: user-api:1.0.0
|
||||
container_name: api_user
|
||||
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: {}
|
||||
59
backend/user/scripts/db-lanuch-entrypoint.sh
Executable file
59
backend/user/scripts/db-lanuch-entrypoint.sh
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# 日志函数(带时间戳)
|
||||
log_info() {
|
||||
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [DB_INIT] $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [DB_ERROR] $1" >&2
|
||||
}
|
||||
|
||||
# 1. 启动PostgreSQL服务
|
||||
log_info "启动PostgreSQL服务(后台运行)"
|
||||
docker-entrypoint.sh postgres &
|
||||
PG_PID=$!
|
||||
log_info "PostgreSQL主进程ID: $PG_PID"
|
||||
|
||||
# 2. 等待数据库就绪
|
||||
log_info "等待PostgreSQL服务就绪(主机: localhost, 端口: 5432)"
|
||||
retry_count=0
|
||||
max_retries=30 # 最多等待30秒
|
||||
until pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" -h "localhost" -p "5432"; do
|
||||
retry_count=$((retry_count + 1))
|
||||
if [ $retry_count -ge $max_retries ]; then
|
||||
log_error "等待PostgreSQL超时(超过30秒)"
|
||||
exit 1
|
||||
fi
|
||||
log_info "数据库未就绪,等待1秒(重试次数: $retry_count)"
|
||||
sleep 1
|
||||
done
|
||||
log_info "PostgreSQL服务已就绪"
|
||||
|
||||
# 3. 执行SQL脚本
|
||||
log_info "开始执行/docker-entrypoint-initdb.d目录下的SQL脚本"
|
||||
script_count=0
|
||||
for script in /docker-entrypoint-initdb.d/*.sql; do
|
||||
if [ -f "$script" ]; then
|
||||
script_count=$((script_count + 1))
|
||||
log_info "执行脚本 ($script_count): $script"
|
||||
if psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -h "localhost" -p "5432" -f "$script" --set=ON_ERROR_STOP=1; then
|
||||
log_info "脚本执行成功: $script"
|
||||
else
|
||||
log_error "脚本执行失败: $script"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $script_count -eq 0 ]; then
|
||||
log_info "未发现需要执行的SQL脚本"
|
||||
else
|
||||
log_info "所有SQL脚本执行完成(共$script_count个)"
|
||||
fi
|
||||
|
||||
# 4. 等待主进程
|
||||
log_info "等待PostgreSQL主进程结束(PID: $PG_PID)"
|
||||
wait $PG_PID
|
||||
log_info "PostgreSQL进程已退出"
|
||||
48
backend/user/sql/01_uuid_v7_setup.sql
Normal file
48
backend/user/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
backend/user/sql/02_create_user_table.sql
Normal file
30
backend/user/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 $$;
|
||||
32
backend/user/sql/03_create_account_table.sql
Normal file
32
backend/user/sql/03_create_account_table.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
-- 切换到目标数据库
|
||||
\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,
|
||||
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_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 $$;
|
||||
32
backend/user/sql/04_create_password_table.sql
Normal file
32
backend/user/sql/04_create_password_table.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
-- 切换到目标数据库
|
||||
\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,
|
||||
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_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
backend/user/sql/05_create_account_password_view.sql
Normal file
36
backend/user/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 $$;
|
||||
49
backend/user/src/db/postgres.go
Normal file
49
backend/user/src/db/postgres.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var DB *sql.DB
|
||||
|
||||
// 初始化数据库连接
|
||||
func Init() {
|
||||
// 从环境变量获取数据库配置
|
||||
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")
|
||||
zap.L().Info(
|
||||
"💡 读取数据库配置",
|
||||
zap.String("host", dbHost), // key: "host", value: dbHost(string类型)
|
||||
zap.String("port", dbPort), // key: "port", value: dbPort(string类型)
|
||||
zap.String("user", dbUser), // key: "user", value: dbUser(string类型)
|
||||
zap.String("dbname", dbName), // key: "dbname", value: dbName(string类型)
|
||||
)
|
||||
|
||||
// 构建数据库连接字符串
|
||||
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 {
|
||||
zap.L().Panic("❌ 无法连接数据库", zap.Error(err))
|
||||
}
|
||||
defer DB.Close()
|
||||
zap.L().Info("✅ 数据库连接对象创建成功")
|
||||
|
||||
// 验证数据库连接
|
||||
if err := DB.Ping(); err != nil {
|
||||
zap.L().Panic("❌ 数据库连接失败", zap.Error(err))
|
||||
}
|
||||
zap.L().Info("✅ 数据库连接验证成功")
|
||||
}
|
||||
38
backend/user/src/dockerfile
Normal file
38
backend/user/src/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 80
|
||||
|
||||
# 容器启动时执行的命令:运行二进制文件
|
||||
CMD ["./app"]
|
||||
56
backend/user/src/go.mod
Normal file
56
backend/user/src/go.mod
Normal file
@@ -0,0 +1,56 @@
|
||||
module user
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/spf13/viper v1.21.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.43.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
)
|
||||
|
||||
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/fsnotify/fsnotify v1.9.0 // 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/go-viper/mapstructure/v2 v2.4.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/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/subosito/gotenv v1.6.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
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // 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
|
||||
)
|
||||
130
backend/user/src/go.sum
Normal file
130
backend/user/src/go.sum
Normal file
@@ -0,0 +1,130 @@
|
||||
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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
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/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
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/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
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/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
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=
|
||||
66
backend/user/src/logger/logger.go
Normal file
66
backend/user/src/logger/logger.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
// Init 初始化日志(依赖配置文件已加载)
|
||||
func Init() {
|
||||
// 日志级别转换
|
||||
level := zap.InfoLevel
|
||||
switch viper.GetString("logger.level") {
|
||||
case "debug":
|
||||
level = zap.DebugLevel
|
||||
case "warn":
|
||||
level = zap.WarnLevel
|
||||
case "error":
|
||||
level = zap.ErrorLevel
|
||||
}
|
||||
|
||||
// 日志轮转配置(lumberjack)
|
||||
hook := lumberjack.Logger{
|
||||
Filename: viper.GetString("logger.path") + "app.log", // 日志文件路径
|
||||
MaxSize: viper.GetInt("logger.max_size"), // 单个文件最大大小(MB)
|
||||
MaxBackups: viper.GetInt("logger.max_backup"), // 最大备份数
|
||||
MaxAge: viper.GetInt("logger.max_age"), // 最大保留天数
|
||||
Compress: true, // 是否压缩
|
||||
}
|
||||
|
||||
// 编码器配置
|
||||
encoderConfig := zapcore.EncoderConfig{
|
||||
TimeKey: "time",
|
||||
LevelKey: "level",
|
||||
NameKey: "logger",
|
||||
CallerKey: "caller",
|
||||
MessageKey: "msg",
|
||||
StacktraceKey: "stacktrace",
|
||||
LineEnding: zapcore.DefaultLineEnding,
|
||||
EncodeLevel: zapcore.CapitalLevelEncoder, // 日志级别大写(DEBUG/INFO)
|
||||
EncodeTime: customTimeEncoder, // 自定义时间格式
|
||||
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||
EncodeCaller: zapcore.ShortCallerEncoder, // 精简调用者路径
|
||||
}
|
||||
|
||||
// 输出配置(控制台+文件)
|
||||
core := zapcore.NewTee(
|
||||
zapcore.NewCore(zapcore.NewConsoleEncoder(encoderConfig), zapcore.AddSync(os.Stdout), level),
|
||||
zapcore.NewCore(zapcore.NewJSONEncoder(encoderConfig), zapcore.AddSync(&hook), level),
|
||||
)
|
||||
|
||||
// 创建logger实例(开启调用者信息和堆栈跟踪)
|
||||
logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))
|
||||
zap.ReplaceGlobals(logger)
|
||||
|
||||
zap.L().Info("✅ 日志初始化成功", zap.String("level", level.String()))
|
||||
}
|
||||
|
||||
// customTimeEncoder 自定义时间格式
|
||||
func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
|
||||
enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
|
||||
}
|
||||
166
backend/user/src/logic/login.go
Normal file
166
backend/user/src/logic/login.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"user/db"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// LoginRequest 登录请求参数结构
|
||||
// 用于接收前端传递的登录账号和密码
|
||||
// json标签指定JSON序列化/反序列化的字段名
|
||||
// binding:"required"表示该字段为必填项,用于参数校验
|
||||
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"` // 用户ID,omitempty表示为空时不序列化
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// LoginHandler 处理用户登录请求的处理器函数
|
||||
// 参数c是gin.Context,用于获取请求信息和返回响应
|
||||
func LoginHandler(c *gin.Context) {
|
||||
// 获取请求ID,用于追踪请求链路,若请求头中没有则生成一个新的UUID
|
||||
reqID := c.Request.Header.Get("X-LoginRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
}
|
||||
// 记录收到登录请求的日志,包含请求ID和客户端IP
|
||||
zap.L().Info("💡 收到登录请求",
|
||||
zap.String("reqID", reqID),
|
||||
zap.String("clientIP", c.ClientIP()),
|
||||
)
|
||||
|
||||
// 声明一个LoginRequest类型变量用于接收请求参数
|
||||
var req LoginRequest
|
||||
// 绑定并验证请求参数(JSON格式)
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
// 绑定失败时记录警告日志,并返回错误响应
|
||||
zap.L().Warn("⚠️ 请求参数绑定失败",
|
||||
zap.String("reqID", reqID),
|
||||
zap.Error(err),
|
||||
zap.Any("请求体", c.Request.Body),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, LoginResponse{
|
||||
Success: false,
|
||||
Message: "账号或密码不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 记录参数绑定成功的日志
|
||||
zap.L().Info("✅ 请求参数绑定成功",
|
||||
zap.String("reqID", reqID),
|
||||
zap.String("账号", req.Account),
|
||||
)
|
||||
|
||||
// 1. 二次校验账号和密码是否为空(双重保险,防止校验规则被绕过)
|
||||
if req.Account == "" || req.Password == "" {
|
||||
zap.L().Warn("⚠️ 账号或密码为空",
|
||||
zap.String("reqID", reqID),
|
||||
zap.String("账号", req.Account),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, LoginResponse{
|
||||
Success: false,
|
||||
Message: "账号或密码不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 从数据库查询账号对应的密码和用户ID
|
||||
var storedPassword string // 数据库中存储的加密密码
|
||||
var userID string // 用户ID
|
||||
// 查询语句:从用户账号密码视图中查询指定账号(未删除)的密码和用户ID
|
||||
query := `
|
||||
SELECT password, user_id
|
||||
FROM user_account_password_view
|
||||
WHERE account = $1 AND deleted = false
|
||||
`
|
||||
zap.L().Info("💡 执行查询",
|
||||
zap.String("reqID", reqID),
|
||||
zap.String("query", query),
|
||||
zap.String("参数", req.Account),
|
||||
)
|
||||
// 执行查询并将结果扫描到变量中
|
||||
err := db.DB.QueryRow(query, req.Account).Scan(&storedPassword, &userID)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
// 账号不存在或已被删除的情况
|
||||
zap.L().Warn("⚠️ 账号不存在或已删除",
|
||||
zap.String("reqID", reqID),
|
||||
zap.String("账号", req.Account),
|
||||
)
|
||||
c.JSON(http.StatusOK, LoginResponse{
|
||||
Success: false,
|
||||
Message: "账号不存在",
|
||||
})
|
||||
return
|
||||
case err != nil:
|
||||
// 查询过程发生错误的情况
|
||||
zap.L().Error("❌ 查询账号信息失败",
|
||||
zap.String("reqID", reqID),
|
||||
zap.Error(err),
|
||||
zap.String("账号", req.Account),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, LoginResponse{
|
||||
Success: false,
|
||||
Message: "查询账号信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 记录查询账号信息成功的日志
|
||||
zap.L().Info("✅ 查询账号信息成功",
|
||||
zap.String("reqID", reqID),
|
||||
zap.String("账号", req.Account),
|
||||
zap.String("userID", userID),
|
||||
)
|
||||
|
||||
// 3. 验证密码(使用bcrypt比较原始密码和存储的加密密码)
|
||||
err = bcrypt.CompareHashAndPassword([]byte(storedPassword), []byte(req.Password))
|
||||
if err != nil {
|
||||
// 密码不匹配的情况
|
||||
zap.L().Warn("⚠️ 密码验证失败",
|
||||
zap.String("reqID", reqID),
|
||||
zap.String("账号", req.Account),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusOK, LoginResponse{
|
||||
Success: false,
|
||||
Message: "密码错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 记录密码验证成功的日志
|
||||
zap.L().Info("✅ 密码验证成功",
|
||||
zap.String("reqID", reqID),
|
||||
zap.String("账号", req.Account),
|
||||
)
|
||||
|
||||
// 4. 登录成功,返回成功响应并包含用户ID
|
||||
zap.L().Info("✅ 登录成功",
|
||||
zap.String("reqID", reqID),
|
||||
zap.String("账号", req.Account),
|
||||
zap.String("userID", userID),
|
||||
)
|
||||
c.JSON(http.StatusOK, LoginResponse{
|
||||
Success: true,
|
||||
Message: "登录成功",
|
||||
Data: struct {
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
}{
|
||||
UserID: userID,
|
||||
},
|
||||
})
|
||||
}
|
||||
251
backend/user/src/logic/register.go
Normal file
251
backend/user/src/logic/register.go
Normal file
@@ -0,0 +1,251 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
"user/db"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
_ "github.com/lib/pq"
|
||||
"go.uber.org/zap"
|
||||
"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"`
|
||||
}
|
||||
|
||||
// registerHandler 处理用户注册逻辑
|
||||
func RegisterHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
reqID := c.Request.Header.Get("X-RegisterRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
}
|
||||
// 使用zap.Info记录开始处理日志,添加请求ID字段
|
||||
zap.L().Info("⌛️ 收到注册请求,开始处理", zap.String("req_id", reqID))
|
||||
|
||||
var req RegisterRequest
|
||||
// 绑定并验证请求参数
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
zap.L().Error("❌ 请求参数绑定失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err))
|
||||
c.JSON(http.StatusBadRequest, RegisterResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Info("✅ 请求参数绑定成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("account", req.Account))
|
||||
|
||||
// 1. 判断接口的账号和密码是否为空
|
||||
if req.Account == "" || req.Password == "" {
|
||||
zap.L().Warn("⚠️ 账号或密码为空,拒绝注册",
|
||||
zap.String("req_id", 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
|
||||
)
|
||||
`
|
||||
zap.L().Info("💡 执行账号存在性查询",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("query", query))
|
||||
|
||||
err := db.DB.QueryRow(query, req.Account).Scan(&exists)
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 账号查询失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, RegisterResponse{
|
||||
Success: false,
|
||||
Message: "查询账号信息失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Info("💡 账号存在性查询完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Bool("exists", exists))
|
||||
|
||||
// 3. 判断查询结果,若存在则提示账号已存在
|
||||
if exists {
|
||||
zap.L().Warn("⚠️ 账号已存在",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("account", req.Account))
|
||||
c.JSON(http.StatusOK, RegisterResponse{
|
||||
Success: false,
|
||||
Message: "账号已存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 4. 开启数据库事务,确保数据一致性
|
||||
tx, err := db.DB.Begin()
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 开启事务失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, RegisterResponse{
|
||||
Success: false,
|
||||
Message: "开启事务失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
zap.L().Error("❌ 发生恐慌,回滚事务",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Any("recover", r))
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
zap.L().Info("✅ 数据库事务开启成功", zap.String("req_id", reqID))
|
||||
|
||||
// 5. 在user表生成新用户ID
|
||||
var userID string
|
||||
insertUserQuery := `
|
||||
INSERT INTO "user" DEFAULT VALUES
|
||||
RETURNING id
|
||||
`
|
||||
zap.L().Info("💡 执行用户创建",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("query", insertUserQuery))
|
||||
|
||||
err = tx.QueryRow(insertUserQuery).Scan(&userID)
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 创建用户失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err))
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, RegisterResponse{
|
||||
Success: false,
|
||||
Message: "创建用户失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Info("✅ 用户创建成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("user_id", userID))
|
||||
|
||||
// 6. 对密码进行加密处理
|
||||
zap.L().Info("💡 开始密码加密", zap.String("req_id", reqID))
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 密码加密失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err))
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, RegisterResponse{
|
||||
Success: false,
|
||||
Message: "密码加密失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Info("✅ 密码加密成功", zap.String("req_id", reqID))
|
||||
|
||||
// 7. 插入user_account表
|
||||
insertAccountQuery := `
|
||||
INSERT INTO user_account (user_id, account)
|
||||
VALUES ($1, $2)
|
||||
`
|
||||
zap.L().Info("💡 执行账号插入",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("query", insertAccountQuery),
|
||||
zap.String("user_id", userID),
|
||||
zap.String("account", req.Account))
|
||||
|
||||
_, err = tx.Exec(insertAccountQuery, userID, req.Account)
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 保存账号信息失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err))
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, RegisterResponse{
|
||||
Success: false,
|
||||
Message: "保存账号信息失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Info("✅ 账号信息保存成功", zap.String("req_id", reqID))
|
||||
|
||||
// 8. 插入user_password表
|
||||
insertPasswordQuery := `
|
||||
INSERT INTO user_password (user_id, password)
|
||||
VALUES ($1, $2)
|
||||
`
|
||||
zap.L().Info("💡 执行密码插入",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("query", insertPasswordQuery),
|
||||
zap.String("user_id", userID))
|
||||
|
||||
_, err = tx.Exec(insertPasswordQuery, userID, string(hashedPassword))
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 保存密码信息失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err))
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, RegisterResponse{
|
||||
Success: false,
|
||||
Message: "保存密码信息失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Info("✅ 密码信息保存成功", zap.String("req_id", reqID))
|
||||
|
||||
// 9. 提交事务
|
||||
if err := tx.Commit(); err != nil {
|
||||
zap.L().Error("❌ 提交事务失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err))
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, RegisterResponse{
|
||||
Success: false,
|
||||
Message: "提交事务失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Info("✅ 事务提交成功", zap.String("req_id", reqID))
|
||||
|
||||
// 10. 注册成功
|
||||
response := RegisterResponse{
|
||||
Success: true,
|
||||
Message: "注册成功",
|
||||
}
|
||||
response.Data.UserID = userID
|
||||
response.Data.Account = req.Account
|
||||
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 注册成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Duration("duration", duration),
|
||||
zap.String("user_id", userID),
|
||||
zap.String("account", req.Account))
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
34
backend/user/src/main.go
Normal file
34
backend/user/src/main.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"user/db"
|
||||
"user/logger"
|
||||
"user/logic"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
_ "github.com/lib/pq"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
zap.L().Info("🚀 用户服务初始化")
|
||||
logger.Init()
|
||||
zap.L().Info("⌛️ 数据库初始化开始")
|
||||
db.Init()
|
||||
zap.L().Info("✅ 数据库初始化成功")
|
||||
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
r := gin.Default()
|
||||
|
||||
// 登录接口
|
||||
r.POST("/user/login", logic.LoginHandler)
|
||||
zap.L().Info("✅ 登录接口注册完成: POST /user/login")
|
||||
// 注册接口
|
||||
r.POST("/user/register", logic.RegisterHandler)
|
||||
zap.L().Info("✅ 登录接口注册完成: POST /user/register")
|
||||
|
||||
// 启动服务,监听80端口
|
||||
zap.L().Info("✅ 服务启动在80端口")
|
||||
r.Run(":80")
|
||||
zap.L().Info("💻 用户服务启动成功")
|
||||
}
|
||||
2
client/README.md
Normal file
2
client/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# asset_assistant
|
||||
|
||||
2
frontend/README.md
Normal file
2
frontend/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# asset_assistant
|
||||
|
||||
Reference in New Issue
Block a user