add
This commit is contained in:
97
backend/sql/05_exchange.sql
Normal file
97
backend/sql/05_exchange.sql
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
-- 切换到目标数据库
|
||||||
|
\c postgres;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'exchange') THEN
|
||||||
|
CREATE TABLE exchange (
|
||||||
|
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_exchange_updated_at
|
||||||
|
BEFORE UPDATE ON "exchange"
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_data_modified_column();
|
||||||
|
|
||||||
|
RAISE NOTICE 'created exchange table and trigger';
|
||||||
|
ELSE
|
||||||
|
RAISE NOTICE 'exchange table already exists';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'exchange_name') THEN
|
||||||
|
CREATE TABLE exchange_name (
|
||||||
|
id UUID DEFAULT gen_random_uuid_v7() PRIMARY KEY NOT NULL,
|
||||||
|
exchange_id UUID NOT NULL,
|
||||||
|
name 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_name_updated_at
|
||||||
|
BEFORE UPDATE ON "exchange_name"
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_data_modified_column();
|
||||||
|
|
||||||
|
RAISE NOTICE 'created exchange_name table and trigger';
|
||||||
|
ELSE
|
||||||
|
RAISE NOTICE 'exchange_name table already exists';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'exchange_code') THEN
|
||||||
|
CREATE TABLE exchange_code (
|
||||||
|
id UUID DEFAULT gen_random_uuid_v7() PRIMARY KEY NOT NULL,
|
||||||
|
exchange_id UUID NOT NULL,
|
||||||
|
code 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_code_updated_at
|
||||||
|
BEFORE UPDATE ON "exchange_code"
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_data_modified_column();
|
||||||
|
|
||||||
|
RAISE NOTICE 'created exchange_code table and trigger';
|
||||||
|
ELSE
|
||||||
|
RAISE NOTICE 'exchange_code table already exists';
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
view_exists BOOLEAN;
|
||||||
|
BEGIN
|
||||||
|
-- 检查视图是否已存在
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.views
|
||||||
|
WHERE table_name = 'exchange_info_view'
|
||||||
|
) INTO view_exists;
|
||||||
|
|
||||||
|
-- 创建或更新视图
|
||||||
|
CREATE OR REPLACE VIEW exchange_info_view AS
|
||||||
|
SELECT
|
||||||
|
u.id AS exchange_id,
|
||||||
|
n.name AS name,
|
||||||
|
c.code AS code,
|
||||||
|
u.deleted AS deleted
|
||||||
|
FROM
|
||||||
|
exchange u
|
||||||
|
JOIN
|
||||||
|
exchange_name n ON u.id = n.exchange_id
|
||||||
|
JOIN
|
||||||
|
exchange_code c ON u.id = c.exchange_id
|
||||||
|
WHERE
|
||||||
|
u.deleted = FALSE;
|
||||||
|
|
||||||
|
-- 根据视图是否已存在输出不同提示
|
||||||
|
IF view_exists THEN
|
||||||
|
RAISE NOTICE '视图 exchange_info_view 已更新';
|
||||||
|
ELSE
|
||||||
|
RAISE NOTICE '视图 exchange_info_view 已创建';
|
||||||
|
END IF;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
RAISE NOTICE '处理视图时发生错误: %', SQLERRM;
|
||||||
|
END $$;
|
||||||
194
backend/src/logic4exchange/create.go
Normal file
194
backend/src/logic4exchange/create.go
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
package logic4exchange
|
||||||
|
|
||||||
|
import (
|
||||||
|
"asset_assistant/db" // 数据库操作相关包
|
||||||
|
"net/http"
|
||||||
|
"time" // 时间处理包
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin" // Gin框架,用于处理HTTP请求
|
||||||
|
"github.com/google/uuid" // UUID生成工具
|
||||||
|
"go.uber.org/zap" // 日志库
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateRequest 注册请求参数结构
|
||||||
|
// 用于接收客户端发送的JSON数据,绑定并验证必填字段
|
||||||
|
type CreateRequest struct {
|
||||||
|
Name string `json:"name" binding:"required"` // 国家名称,必填
|
||||||
|
Code string `json:"code" binding:"required"` // 国家代码,必填
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateResponse 注册响应结构
|
||||||
|
// 统一的API响应格式,包含成功状态、提示信息和数据
|
||||||
|
type CreateResponse struct {
|
||||||
|
Success bool `json:"success"` // 操作是否成功
|
||||||
|
Message string `json:"message"` // 提示信息
|
||||||
|
Data CreateData `json:"data"` // 响应数据
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateData 响应数据结构
|
||||||
|
// 包含创建成功后的国家ID
|
||||||
|
type CreateData struct {
|
||||||
|
CountryID string `json:"exchange_id"` // 国家唯一标识ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHandler 处理国家创建逻辑
|
||||||
|
// 接收HTTP请求,完成参数验证、数据库事务处理并返回响应
|
||||||
|
func CreateHandler(c *gin.Context) {
|
||||||
|
startTime := time.Now() // 记录请求开始时间,用于统计耗时
|
||||||
|
// 获取或生成请求ID,用于追踪整个请求链路
|
||||||
|
reqID := c.Request.Header.Get("X-RegisterRequest-ID")
|
||||||
|
if reqID == "" {
|
||||||
|
reqID = uuid.New().String()
|
||||||
|
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录请求接收日志,包含关键追踪信息
|
||||||
|
zap.L().Info("📥 收到国家创建请求",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("path", c.Request.URL.Path),
|
||||||
|
zap.String("method", c.Request.Method),
|
||||||
|
)
|
||||||
|
|
||||||
|
var req CreateRequest
|
||||||
|
// 绑定并验证请求参数(检查name和code是否存在)
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Error(err),
|
||||||
|
zap.Any("request_body", c.Request.Body),
|
||||||
|
)
|
||||||
|
// 返回参数错误响应
|
||||||
|
c.JSON(http.StatusBadRequest, CreateResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "请求参数错误:name和code为必填项",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录通过验证的请求参数
|
||||||
|
zap.L().Debug("✅ 请求参数验证通过",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("name", req.Name),
|
||||||
|
zap.String("code", req.Code),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 开启数据库事务,确保多表操作原子性(要么全成功,要么全失败)
|
||||||
|
tx, err := db.DB.Begin()
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("❌ 事务开启失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "系统错误,请稍后重试",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 延迟执行的恢复函数,处理panic情况
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil { // 捕获panic
|
||||||
|
// 回滚事务
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
zap.L().Error("💥 panic后事务回滚失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
zap.L().Error("💥 事务处理发生panic",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Any("recover", r),
|
||||||
|
)
|
||||||
|
// 返回系统错误响应
|
||||||
|
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "系统错误,请稍后重试",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 1. 在exchange表中创建记录并获取自动生成的ID
|
||||||
|
var exchangeID string
|
||||||
|
err = tx.QueryRow("INSERT INTO exchange DEFAULT VALUES RETURNING id").Scan(&exchangeID)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback() // 操作失败,回滚事务
|
||||||
|
zap.L().Error("❌ exchange表插入失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "创建国家记录失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.L().Debug("📝 exchange表插入成功",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", exchangeID),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 2. 插入国家名称到name表(与exchange_id关联)
|
||||||
|
_, err = tx.Exec("INSERT INTO name (exchange_id, name) VALUES ($1, $2)", exchangeID, req.Name)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback() // 操作失败,回滚事务
|
||||||
|
zap.L().Error("❌ name表插入失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", exchangeID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "保存名称信息失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 插入国家代码到code表(与exchange_id关联)
|
||||||
|
_, err = tx.Exec("INSERT INTO code (exchange_id, code) VALUES ($1, $2)", exchangeID, req.Code)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback() // 操作失败,回滚事务
|
||||||
|
zap.L().Error("❌ code表插入失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", exchangeID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "保存代码信息失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交事务(所有操作成功后确认提交)
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
tx.Rollback() // 提交失败时尝试回滚
|
||||||
|
zap.L().Error("❌ 事务提交失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", exchangeID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "数据提交失败,请稍后重试",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录请求处理耗时
|
||||||
|
duration := time.Since(startTime)
|
||||||
|
zap.L().Info("✅ 国家创建请求处理完成",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", exchangeID),
|
||||||
|
zap.Duration("duration", duration),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 返回成功响应,包含创建的国家ID
|
||||||
|
c.JSON(http.StatusOK, CreateResponse{
|
||||||
|
Success: true,
|
||||||
|
Message: "创建成功",
|
||||||
|
Data: CreateData{
|
||||||
|
CountryID: exchangeID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
168
backend/src/logic4exchange/delete.go
Normal file
168
backend/src/logic4exchange/delete.go
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
package logic4exchange
|
||||||
|
|
||||||
|
import (
|
||||||
|
"asset_assistant/db"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeleteRequest 删除请求参数结构
|
||||||
|
type DeleteRequest struct {
|
||||||
|
CountryID string `json:"exchange_id" binding:"required"` // 国家ID,必填
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteResponse 删除响应结构
|
||||||
|
type DeleteResponse struct {
|
||||||
|
Success bool `json:"success"` // 操作是否成功
|
||||||
|
Message string `json:"message"` // 提示信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteHandler 处理国家删除逻辑(软删除)
|
||||||
|
func DeleteHandler(c *gin.Context) {
|
||||||
|
startTime := time.Now()
|
||||||
|
reqID := c.Request.Header.Get("X-DeleteRequest-ID")
|
||||||
|
if reqID == "" {
|
||||||
|
reqID = uuid.New().String()
|
||||||
|
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.L().Info("📥 收到国家删除请求",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("path", c.Request.URL.Path),
|
||||||
|
zap.String("method", c.Request.Method),
|
||||||
|
)
|
||||||
|
|
||||||
|
var req DeleteRequest
|
||||||
|
// 绑定并验证请求参数
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusBadRequest, DeleteResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "请求参数错误:exchange_id为必填项",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.L().Debug("✅ 请求参数验证通过",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", req.CountryID),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 开启数据库事务
|
||||||
|
tx, err := db.DB.Begin()
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("❌ 事务开启失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "系统错误,请稍后重试",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 延迟处理panic情况
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
zap.L().Error("💥 panic后事务回滚失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
zap.L().Error("💥 事务处理发生panic",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Any("recover", r),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "系统错误,请稍后重试",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 3.1 更新exchange表
|
||||||
|
_, err = tx.Exec("UPDATE exchange SET deleted = TRUE WHERE id = $1", req.CountryID)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
zap.L().Error("❌ exchange表更新失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", req.CountryID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "删除国家记录失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.2 更新name表
|
||||||
|
_, err = tx.Exec("UPDATE name SET deleted = TRUE WHERE exchange_id = $1", req.CountryID)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
zap.L().Error("❌ name表更新失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", req.CountryID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "删除名称信息失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.3 更新code表
|
||||||
|
_, err = tx.Exec("UPDATE code SET deleted = TRUE WHERE exchange_id = $1", req.CountryID)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
zap.L().Error("❌ code表更新失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", req.CountryID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "删除代码信息失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交事务
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
zap.L().Error("❌ 事务提交失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", req.CountryID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "数据提交失败,请稍后重试",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录请求处理耗时
|
||||||
|
duration := time.Since(startTime)
|
||||||
|
zap.L().Info("✅ 国家删除请求处理完成",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", req.CountryID),
|
||||||
|
zap.Duration("duration", duration),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 返回成功响应
|
||||||
|
c.JSON(http.StatusOK, DeleteResponse{
|
||||||
|
Success: true,
|
||||||
|
Message: "删除成功",
|
||||||
|
})
|
||||||
|
}
|
||||||
231
backend/src/logic4exchange/read.go
Normal file
231
backend/src/logic4exchange/read.go
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
package logic4exchange
|
||||||
|
|
||||||
|
import (
|
||||||
|
"asset_assistant/db"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadRequest 读取请求参数结构
|
||||||
|
type ReadRequest struct {
|
||||||
|
CountryID string `form:"exchange_id"` // 国家ID,可选
|
||||||
|
Name string `form:"name"` // 国家名称,可选
|
||||||
|
Code string `form:"code"` // 国家代码,可选
|
||||||
|
Page string `form:"page"` // 页码,可选
|
||||||
|
PageSize string `form:"page_size"` // 每页条数,可选
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadData 读取响应数据结构
|
||||||
|
type ReadData struct {
|
||||||
|
Total int64 `json:"total"` // 总条数
|
||||||
|
Page int `json:"page"` // 当前页码
|
||||||
|
PageSize int `json:"page_size"` // 每页条数
|
||||||
|
Items []CountryInfoViewItem `json:"items"` // 数据列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountryInfoViewItem 视图数据项结构
|
||||||
|
type CountryInfoViewItem struct {
|
||||||
|
CountryID string `json:"exchange_id"` // 国家ID
|
||||||
|
Name string `json:"name"` // 国家名称
|
||||||
|
Code string `json:"code"` // 国家代码
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadResponse 读取响应结构
|
||||||
|
type ReadResponse struct {
|
||||||
|
Success bool `json:"success"` // 操作是否成功
|
||||||
|
Message string `json:"message"` // 提示信息
|
||||||
|
Data ReadData `json:"data"` // 响应数据
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadHandler 处理国家信息查询逻辑
|
||||||
|
func ReadHandler(c *gin.Context) {
|
||||||
|
startTime := time.Now()
|
||||||
|
// 获取或生成请求ID
|
||||||
|
reqID := c.Request.Header.Get("X-ReadRequest-ID")
|
||||||
|
if reqID == "" {
|
||||||
|
reqID = uuid.New().String()
|
||||||
|
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录请求接收日志
|
||||||
|
zap.L().Info("📥 收到国家查询请求",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("path", c.Request.URL.Path),
|
||||||
|
zap.String("method", c.Request.Method),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 绑定请求参数
|
||||||
|
var req ReadRequest
|
||||||
|
if err := c.ShouldBindQuery(&req); err != nil {
|
||||||
|
zap.L().Warn("⚠️ 请求参数解析失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusBadRequest, ReadResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "请求参数格式错误",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证查询条件至少有一个不为空
|
||||||
|
if req.CountryID == "" && req.Name == "" && req.Code == "" {
|
||||||
|
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("reason", "exchange_id、name、code不能同时为空"),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusBadRequest, ReadResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "请求参数错误:exchange_id、name、code不能同时为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理分页参数默认值
|
||||||
|
page, err := strconv.Atoi(req.Page)
|
||||||
|
if err != nil || page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
pageSize, err := strconv.Atoi(req.PageSize)
|
||||||
|
if err != nil || pageSize < 1 {
|
||||||
|
pageSize = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.L().Debug("✅ 请求参数验证通过",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", req.CountryID),
|
||||||
|
zap.String("name", req.Name),
|
||||||
|
zap.String("code", req.Code),
|
||||||
|
zap.Int("page", page),
|
||||||
|
zap.Int("page_size", pageSize),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 构建查询条件和参数
|
||||||
|
whereClauses := []string{}
|
||||||
|
args := []interface{}{}
|
||||||
|
paramIndex := 1
|
||||||
|
|
||||||
|
if req.CountryID != "" {
|
||||||
|
whereClauses = append(whereClauses, "exchange_id = $"+strconv.Itoa(paramIndex))
|
||||||
|
args = append(args, req.CountryID)
|
||||||
|
paramIndex++
|
||||||
|
}
|
||||||
|
if req.Name != "" {
|
||||||
|
whereClauses = append(whereClauses, "name LIKE $"+strconv.Itoa(paramIndex))
|
||||||
|
args = append(args, "%"+req.Name+"%")
|
||||||
|
paramIndex++
|
||||||
|
}
|
||||||
|
if req.Code != "" {
|
||||||
|
whereClauses = append(whereClauses, "code LIKE $"+strconv.Itoa(paramIndex))
|
||||||
|
args = append(args, "%"+req.Code+"%")
|
||||||
|
paramIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建基础SQL
|
||||||
|
baseSQL := "SELECT exchange_id, name, code FROM exchange_info_view"
|
||||||
|
countSQL := "SELECT COUNT(*) FROM exchange_info_view"
|
||||||
|
if len(whereClauses) > 0 {
|
||||||
|
whereStr := " WHERE " + strings.Join(whereClauses, " AND ")
|
||||||
|
baseSQL += whereStr
|
||||||
|
countSQL += whereStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算分页偏移量
|
||||||
|
offset := (page - 1) * pageSize
|
||||||
|
|
||||||
|
// 拼接分页SQL(使用fmt.Sprintf更清晰)
|
||||||
|
querySQL := fmt.Sprintf("%s ORDER BY exchange_id LIMIT $%d OFFSET $%d", baseSQL, paramIndex, paramIndex+1)
|
||||||
|
args = append(args, pageSize, offset)
|
||||||
|
|
||||||
|
// 查询总条数(修正参数传递方式)
|
||||||
|
var total int64
|
||||||
|
countArgs := args[:len(args)-2] // 排除分页参数
|
||||||
|
err = db.DB.QueryRow(countSQL, countArgs...).Scan(&total)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("❌ 查询总条数失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "查询数据失败,请稍后重试",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行分页查询
|
||||||
|
rows, err := db.DB.Query(querySQL, args...)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("❌ 分页查询失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "查询数据失败,请稍后重试",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
// 处理查询结果
|
||||||
|
var items []CountryInfoViewItem
|
||||||
|
for rows.Next() {
|
||||||
|
var item CountryInfoViewItem
|
||||||
|
if err := rows.Scan(&item.CountryID, &item.Name, &item.Code); err != nil {
|
||||||
|
zap.L().Error("❌ 解析查询结果失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "数据处理失败,请稍后重试",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查行迭代过程中是否发生错误
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
zap.L().Error("❌ 行迭代错误",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "查询数据失败,请稍后重试",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录请求处理耗时
|
||||||
|
duration := time.Since(startTime)
|
||||||
|
zap.L().Info("✅ 国家查询请求处理完成",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Int64("total", total),
|
||||||
|
zap.Int("page", page),
|
||||||
|
zap.Int("page_size", pageSize),
|
||||||
|
zap.Duration("duration", duration),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 返回成功响应
|
||||||
|
c.JSON(http.StatusOK, ReadResponse{
|
||||||
|
Success: true,
|
||||||
|
Message: "查询成功",
|
||||||
|
Data: ReadData{
|
||||||
|
Total: total,
|
||||||
|
Page: page,
|
||||||
|
PageSize: pageSize,
|
||||||
|
Items: items,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
184
backend/src/logic4exchange/update.go
Normal file
184
backend/src/logic4exchange/update.go
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
package logic4exchange
|
||||||
|
|
||||||
|
import (
|
||||||
|
"asset_assistant/db"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdateRequest 更新请求参数结构
|
||||||
|
type UpdateRequest struct {
|
||||||
|
CountryID string `json:"exchange_id" binding:"required"` // 国家ID,必填
|
||||||
|
Name string `json:"name"` // 国家名称,可选
|
||||||
|
Code string `json:"code"` // 国家代码,可选
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateResponse 更新响应结构
|
||||||
|
type UpdateResponse struct {
|
||||||
|
Success bool `json:"success"` // 操作是否成功
|
||||||
|
Message string `json:"message"` // 提示信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateHandler 处理国家信息更新逻辑
|
||||||
|
func UpdateHandler(c *gin.Context) {
|
||||||
|
startTime := time.Now()
|
||||||
|
// 获取或生成请求ID
|
||||||
|
reqID := c.Request.Header.Get("X-UpdateRequest-ID")
|
||||||
|
if reqID == "" {
|
||||||
|
reqID = uuid.New().String()
|
||||||
|
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录请求接收日志
|
||||||
|
zap.L().Info("📥 收到国家更新请求",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("path", c.Request.URL.Path),
|
||||||
|
zap.String("method", c.Request.Method),
|
||||||
|
)
|
||||||
|
|
||||||
|
var req UpdateRequest
|
||||||
|
// 绑定并验证请求参数(主要验证exchange_id必填)
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusBadRequest, UpdateResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "请求参数错误:exchange_id为必填项",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证name和code不能同时为空
|
||||||
|
if req.Name == "" && req.Code == "" {
|
||||||
|
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", req.CountryID),
|
||||||
|
zap.String("reason", "name和code不能同时为空"),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusBadRequest, UpdateResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "请求参数错误:name和code不能同时为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.L().Debug("✅ 请求参数验证通过",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", req.CountryID),
|
||||||
|
zap.String("name", req.Name),
|
||||||
|
zap.String("code", req.Code),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 开启数据库事务
|
||||||
|
tx, err := db.DB.Begin()
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("❌ 事务开启失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "系统错误,请稍后重试",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 延迟处理panic情况
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
zap.L().Error("💥 panic后事务回滚失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
zap.L().Error("💥 事务处理发生panic",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.Any("recover", r),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "系统错误,请稍后重试",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 如果name不为空,更新name表
|
||||||
|
if req.Name != "" {
|
||||||
|
_, err = tx.Exec("UPDATE name SET name = $1 WHERE exchange_id = $2", req.Name, req.CountryID)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
zap.L().Error("❌ name表更新失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", req.CountryID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "更新名称信息失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
zap.L().Debug("📝 name表更新成功",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", req.CountryID),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果code不为空,更新code表
|
||||||
|
if req.Code != "" {
|
||||||
|
_, err = tx.Exec("UPDATE code SET code = $1 WHERE exchange_id = $2", req.Code, req.CountryID)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
zap.L().Error("❌ code表更新失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", req.CountryID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "更新代码信息失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
zap.L().Debug("📝 code表更新成功",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", req.CountryID),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交事务
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
zap.L().Error("❌ 事务提交失败",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", req.CountryID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "数据提交失败,请稍后重试",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录请求处理耗时
|
||||||
|
duration := time.Since(startTime)
|
||||||
|
zap.L().Info("✅ 国家更新请求处理完成",
|
||||||
|
zap.String("req_id", reqID),
|
||||||
|
zap.String("exchange_id", req.CountryID),
|
||||||
|
zap.Duration("duration", duration),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 返回成功响应
|
||||||
|
c.JSON(http.StatusOK, UpdateResponse{
|
||||||
|
Success: true,
|
||||||
|
Message: "更新成功",
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user