This commit is contained in:
vipg
2025-11-14 14:37:42 +08:00
parent 80f8d059f0
commit b428d91f89
5 changed files with 386 additions and 773 deletions

View File

@@ -1 +1,387 @@
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
// 国家数据模型
class Country {
final String countryId;
final String name;
final String code;
Country({required this.countryId, required this.name, required this.code});
// 从JSON构建对象
factory Country.fromJson(Map<String, dynamic> json) {
return Country(
countryId: json['country_id'],
name: json['name'],
code: json['code'],
);
}
// 转换为JSON
Map<String, dynamic> toJson() {
return {'country_id': countryId, 'name': name, 'code': code};
}
}
class CountryHomePage extends StatefulWidget {
const CountryHomePage({super.key});
@override
State<CountryHomePage> createState() => _CountryHomePageState();
}
class _CountryHomePageState extends State<CountryHomePage> {
// 文本编辑控制器
final TextEditingController _nameController = TextEditingController();
final TextEditingController _codeController = TextEditingController();
// 国家列表
List<Country> _countries = [];
bool _isLoading = false;
// Dio实例
final Dio _dio = Dio();
final String _baseUrl = 'https://api.fishestlife.com';
@override
void initState() {
super.initState();
_fetchCountries();
}
// 获取国家列表
Future<void> _fetchCountries() async {
setState(() {
_isLoading = true;
});
try {
final response = await _dio.post(
'$_baseUrl/country/read',
data: {}, // 空参数表示获取所有国家
);
if (response.data['success'] == true) {
final List<dynamic> items = response.data['data']['items'];
setState(() {
_countries = items.map((item) => Country.fromJson(item)).toList();
});
} else {
_showErrorDialog('获取失败', response.data['message'] ?? '未知错误');
}
} catch (e) {
_showErrorDialog('网络错误', e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
// 创建新国家
Future<void> _createCountry() async {
final name = _nameController.text.trim();
final code = _codeController.text.trim();
if (name.isEmpty || code.isEmpty) {
_showErrorDialog('输入错误', '国家名称和代码不能为空');
return;
}
setState(() {
_isLoading = true;
});
try {
final response = await _dio.post(
'$_baseUrl/country/create',
data: {'name': name, 'code': code},
);
if (response.data['success'] == true) {
// 清空输入框
_nameController.clear();
_codeController.clear();
// 重新获取列表
_fetchCountries();
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('创建成功')));
} else {
_showErrorDialog('创建失败', response.data['message'] ?? '未知错误');
}
} catch (e) {
_showErrorDialog('网络错误', e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
// 更新国家信息
Future<void> _updateCountry(Country country) async {
setState(() {
_isLoading = true;
});
try {
final response = await _dio.post(
'$_baseUrl/country/update',
data: country.toJson(),
);
if (response.data['success'] == true) {
_fetchCountries();
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('更新成功')));
} else {
_showErrorDialog('更新失败', response.data['message'] ?? '未知错误');
}
} catch (e) {
_showErrorDialog('网络错误', e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
// 删除国家
Future<void> _deleteCountry(String countryId) async {
if (!await _showConfirmationDialog('确认删除', '确定要删除这个国家吗?')) {
return;
}
setState(() {
_isLoading = true;
});
try {
final response = await _dio.post(
'$_baseUrl/country/delete',
data: {'country_id': countryId},
);
if (response.data['success'] == true) {
_fetchCountries();
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('删除成功')));
} else {
_showErrorDialog('删除失败', response.data['message'] ?? '未知错误');
}
} catch (e) {
_showErrorDialog('网络错误', e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
// 显示编辑对话框
void _showEditDialog(Country country) {
final TextEditingController nameController = TextEditingController(
text: country.name,
);
final TextEditingController codeController = TextEditingController(
text: country.code,
);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('编辑国家'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: nameController,
decoration: const InputDecoration(labelText: '国家名称'),
),
TextField(
controller: codeController,
decoration: const InputDecoration(labelText: '国家代码'),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () {
final updatedCountry = Country(
countryId: country.countryId,
name: nameController.text.trim(),
code: codeController.text.trim(),
);
_updateCountry(updatedCountry);
Navigator.pop(context);
},
child: const Text('保存'),
),
],
),
);
}
// 显示错误对话框
void _showErrorDialog(String title, String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('确定'),
),
],
),
);
}
// 显示确认对话框
Future<bool> _showConfirmationDialog(String title, String message) async {
return await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('确认'),
),
],
),
) ??
false;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('国家管理')),
body: Column(
children: [
// 顶部输入区域
Padding(
padding: const EdgeInsets.all(16.0),
child: Card(
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'添加新国家',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: '国家名称',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextField(
controller: _codeController,
decoration: const InputDecoration(
labelText: '国家代码',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _isLoading ? null : _createCountry,
child: _isLoading
? const CircularProgressIndicator()
: const Text('保存'),
),
],
),
),
),
),
// 底部列表区域
Expanded(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: _countries.isEmpty
? const Center(child: Text('没有国家数据'))
: GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1,
childAspectRatio: 4,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemCount: _countries.length,
itemBuilder: (context, index) {
final country = _countries[index];
return Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// 国家名称(可点击编辑)
InkWell(
onTap: () => _showEditDialog(country),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
country.name,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text('代码: ${country.code}'),
],
),
),
// 删除按钮
IconButton(
icon: const Icon(
Icons.delete,
color: Colors.red,
),
onPressed: () =>
_deleteCountry(country.countryId),
),
],
),
),
);
},
),
),
],
),
);
}
}

View File

@@ -1,193 +0,0 @@
package logic
import (
"net/http"
"country/db" // 数据库操作相关包
"time" // 时间处理包
"github.com/google/uuid" // UUID生成工具
"github.com/gin-gonic/gin" // Gin框架用于处理HTTP请求
"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:"country_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. 在country表中创建记录并获取自动生成的ID
var countryID string
err = tx.QueryRow("INSERT INTO country DEFAULT VALUES RETURNING id").Scan(&countryID)
if err != nil {
tx.Rollback() // 操作失败,回滚事务
zap.L().Error("❌ country表插入失败",
zap.String("req_id", reqID),
zap.Error(err),
)
c.JSON(http.StatusInternalServerError, CreateResponse{
Success: false,
Message: "创建国家记录失败",
})
return
}
zap.L().Debug("📝 country表插入成功",
zap.String("req_id", reqID),
zap.String("country_id", countryID),
)
// 2. 插入国家名称到name表与country_id关联
_, err = tx.Exec("INSERT INTO name (country_id, name) VALUES ($1, $2)", countryID, req.Name)
if err != nil {
tx.Rollback() // 操作失败,回滚事务
zap.L().Error("❌ name表插入失败",
zap.String("req_id", reqID),
zap.String("country_id", countryID),
zap.Error(err),
)
c.JSON(http.StatusInternalServerError, CreateResponse{
Success: false,
Message: "保存名称信息失败",
})
return
}
// 3. 插入国家代码到code表与country_id关联
_, err = tx.Exec("INSERT INTO code (country_id, code) VALUES ($1, $2)", countryID, req.Code)
if err != nil {
tx.Rollback() // 操作失败,回滚事务
zap.L().Error("❌ code表插入失败",
zap.String("req_id", reqID),
zap.String("country_id", countryID),
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("country_id", countryID),
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("country_id", countryID),
zap.Duration("duration", duration),
)
// 返回成功响应包含创建的国家ID
c.JSON(http.StatusOK, CreateResponse{
Success: true,
Message: "创建成功",
Data: CreateData{
CountryID: countryID,
},
})
}

View File

@@ -1,167 +0,0 @@
package logic
import (
"net/http"
"country/db"
"time"
"github.com/google/uuid"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// DeleteRequest 删除请求参数结构
type DeleteRequest struct {
CountryID string `json:"country_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: "请求参数错误country_id为必填项",
})
return
}
zap.L().Debug("✅ 请求参数验证通过",
zap.String("req_id", reqID),
zap.String("country_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 更新country表
_, err = tx.Exec("UPDATE country SET deleted = TRUE WHERE id = $1", req.CountryID)
if err != nil {
tx.Rollback()
zap.L().Error("❌ country表更新失败",
zap.String("req_id", reqID),
zap.String("country_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 country_id = $1", req.CountryID)
if err != nil {
tx.Rollback()
zap.L().Error("❌ name表更新失败",
zap.String("req_id", reqID),
zap.String("country_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 country_id = $1", req.CountryID)
if err != nil {
tx.Rollback()
zap.L().Error("❌ code表更新失败",
zap.String("req_id", reqID),
zap.String("country_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("country_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("country_id", req.CountryID),
zap.Duration("duration", duration),
)
// 返回成功响应
c.JSON(http.StatusOK, DeleteResponse{
Success: true,
Message: "删除成功",
})
}

View File

@@ -1,229 +0,0 @@
package logic
import (
"country/db"
"net/http"
"strconv"
"time"
"strings"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/zap"
)
// ReadRequest 读取请求参数结构
type ReadRequest struct {
CountryID string `form:"country_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:"country_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", "country_id、name、code不能同时为空"),
)
c.JSON(http.StatusBadRequest, ReadResponse{
Success: false,
Message: "请求参数错误country_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("country_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, "country_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 country_id, name, code FROM country_info_view"
countSQL := "SELECT COUNT(*) FROM country_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 country_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,
},
})
}

View File

@@ -1,184 +0,0 @@
package logic
import (
"country/db"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/zap"
)
// UpdateRequest 更新请求参数结构
type UpdateRequest struct {
CountryID string `json:"country_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
// 绑定并验证请求参数主要验证country_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: "请求参数错误country_id为必填项",
})
return
}
// 验证name和code不能同时为空
if req.Name == "" && req.Code == "" {
zap.L().Warn("⚠️ 请求参数验证失败",
zap.String("req_id", reqID),
zap.String("country_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("country_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 country_id = $2", req.Name, req.CountryID)
if err != nil {
tx.Rollback()
zap.L().Error("❌ name表更新失败",
zap.String("req_id", reqID),
zap.String("country_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("country_id", req.CountryID),
)
}
// 如果code不为空更新code表
if req.Code != "" {
_, err = tx.Exec("UPDATE code SET code = $1 WHERE country_id = $2", req.Code, req.CountryID)
if err != nil {
tx.Rollback()
zap.L().Error("❌ code表更新失败",
zap.String("req_id", reqID),
zap.String("country_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("country_id", req.CountryID),
)
}
// 提交事务
if err := tx.Commit(); err != nil {
tx.Rollback()
zap.L().Error("❌ 事务提交失败",
zap.String("req_id", reqID),
zap.String("country_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("country_id", req.CountryID),
zap.Duration("duration", duration),
)
// 返回成功响应
c.JSON(http.StatusOK, UpdateResponse{
Success: true,
Message: "更新成功",
})
}