add
This commit is contained in:
@@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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: "删除成功",
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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: "更新成功",
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user