add
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'package:asset_assistant/pages/country_page.dart';
|
||||
import 'package:asset_assistant/pages/exchange_add_page.dart';
|
||||
import 'package:asset_assistant/pages/exchange_page.dart';
|
||||
import 'package:asset_assistant/pages/home_page.dart';
|
||||
@@ -27,6 +28,7 @@ class MyApp extends StatelessWidget {
|
||||
routes: {
|
||||
'/login': (context) => const LoginPage(),
|
||||
'/home': (context) => HomePage(),
|
||||
'/country': (context) => CountryPage(),
|
||||
'/exchange': (context) => ExchangePage(),
|
||||
'/exchange/add': (context) => AddExchangePage(),
|
||||
},
|
||||
|
||||
209
frontend/asset_assistant/lib/pages/country_add_page.dart
Normal file
209
frontend/asset_assistant/lib/pages/country_add_page.dart
Normal file
@@ -0,0 +1,209 @@
|
||||
import 'package:asset_assistant/utils/host_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class AddCountryPage extends StatefulWidget {
|
||||
const AddCountryPage({super.key});
|
||||
|
||||
@override
|
||||
State<AddCountryPage> createState() => _AddCountryPageState();
|
||||
}
|
||||
|
||||
class _AddCountryPageState extends State<AddCountryPage> {
|
||||
// 输入控制器
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
final TextEditingController _codeController = TextEditingController();
|
||||
|
||||
// 加载状态
|
||||
bool _isLoading = false;
|
||||
|
||||
// 表单验证键
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
// 创建国家
|
||||
Future<void> _createCountry() async {
|
||||
if (!_formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
// 获取用户ID
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final userId = prefs.getString('user_id');
|
||||
if (userId == null) {
|
||||
if (mounted) {
|
||||
_showDialog('错误', '请先登录');
|
||||
Navigator.pushReplacementNamed(context, '/login');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 准备请求数据
|
||||
final baseUrl = HostUtils().currentHost;
|
||||
const path = '/country/create';
|
||||
final url = '$baseUrl$path';
|
||||
|
||||
final requestData = {
|
||||
'name': _nameController.text.trim(),
|
||||
'code': _codeController.text.trim(),
|
||||
};
|
||||
|
||||
// 发送请求
|
||||
final dio = Dio();
|
||||
final response = await dio.post(
|
||||
url,
|
||||
data: requestData,
|
||||
options: Options(headers: {'Content-Type': 'application/json'}),
|
||||
);
|
||||
|
||||
// 处理响应
|
||||
if (response.statusCode == 200) {
|
||||
final result = response.data;
|
||||
if (result['success'] == true) {
|
||||
if (mounted) {
|
||||
_showDialog('成功', '国家创建成功', () {
|
||||
Navigator.pop(context, true); // 返回并通知上一页刷新
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (mounted) {
|
||||
_showDialog('失败', result['message'] ?? '创建失败,请重试');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mounted) {
|
||||
_showDialog('错误', '服务器响应异常: ${response.statusCode}');
|
||||
}
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
String errorMessage = '网络请求失败';
|
||||
if (e.response != null) {
|
||||
errorMessage = '请求失败: ${e.response?.statusCode}';
|
||||
} else if (e.type == DioExceptionType.connectionTimeout) {
|
||||
errorMessage = '连接超时,请检查网络';
|
||||
} else if (e.type == DioExceptionType.connectionError) {
|
||||
errorMessage = '网络连接错误';
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
_showDialog('错误', errorMessage);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
_showDialog('错误', '发生未知错误: $e');
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 显示对话框
|
||||
void _showDialog(String title, String content, [VoidCallback? onConfirm]) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(title),
|
||||
content: Text(content),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
onConfirm?.call();
|
||||
},
|
||||
child: const Text('确定'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('新增国家'),
|
||||
centerTitle: true,
|
||||
elevation: 4,
|
||||
shadowColor: Colors.black12,
|
||||
backgroundColor: theme.colorScheme.surfaceContainerHighest,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.save),
|
||||
onPressed: _isLoading ? null : _createCountry,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Container(
|
||||
color: theme.colorScheme.surface,
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
// 国家名称输入框
|
||||
TextFormField(
|
||||
controller: _nameController,
|
||||
style: TextStyle(color: theme.colorScheme.onSurface),
|
||||
decoration: InputDecoration(
|
||||
labelText: '国家名称',
|
||||
hintText: '请输入国家名称',
|
||||
prefixIcon: Icon(
|
||||
Icons.account_balance,
|
||||
color: theme.colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return '请输入国家名称';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 国家代码输入框
|
||||
TextFormField(
|
||||
controller: _codeController,
|
||||
style: TextStyle(color: theme.colorScheme.onSurface),
|
||||
decoration: InputDecoration(
|
||||
labelText: '国家代码',
|
||||
hintText: '请输入国家代码',
|
||||
prefixIcon: Icon(
|
||||
Icons.code,
|
||||
color: theme.colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return '请输入国家代码';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
361
frontend/asset_assistant/lib/pages/country_page.dart
Normal file
361
frontend/asset_assistant/lib/pages/country_page.dart
Normal file
@@ -0,0 +1,361 @@
|
||||
import 'package:asset_assistant/pages/country_add_page.dart';
|
||||
import 'package:asset_assistant/utils/host_utils.dart';
|
||||
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'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 接口响应模型
|
||||
class CountryResponse {
|
||||
final bool success;
|
||||
final String message;
|
||||
final CountryData data;
|
||||
|
||||
CountryResponse({
|
||||
required this.success,
|
||||
required this.message,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
factory CountryResponse.fromJson(Map<String, dynamic> json) {
|
||||
return CountryResponse(
|
||||
success: json['success'],
|
||||
message: json['message'],
|
||||
data: CountryData.fromJson(json['data']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 响应数据模型
|
||||
class CountryData {
|
||||
final int total;
|
||||
final int page;
|
||||
final int pageSize;
|
||||
final List<Country> items;
|
||||
|
||||
CountryData({
|
||||
required this.total,
|
||||
required this.page,
|
||||
required this.pageSize,
|
||||
required this.items,
|
||||
});
|
||||
|
||||
factory CountryData.fromJson(Map<String, dynamic> json) {
|
||||
var itemsList = json['items'] as List;
|
||||
List<Country> items = itemsList.map((i) => Country.fromJson(i)).toList();
|
||||
|
||||
return CountryData(
|
||||
total: json['total'],
|
||||
page: json['page'],
|
||||
pageSize: json['page_size'],
|
||||
items: items,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CountryPage extends StatefulWidget {
|
||||
const CountryPage({super.key});
|
||||
|
||||
@override
|
||||
State<CountryPage> createState() => _CountryPageState();
|
||||
}
|
||||
|
||||
class _CountryPageState extends State<CountryPage> {
|
||||
List<Country> _countries = [];
|
||||
bool _isLoading = true;
|
||||
String? _errorMessage;
|
||||
int _currentPage = 1;
|
||||
final int _pageSize = 20;
|
||||
bool _hasMoreData = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchCountries();
|
||||
}
|
||||
|
||||
// 加载国家列表数据
|
||||
Future<void> _fetchCountries({bool isRefresh = false}) async {
|
||||
if (isRefresh) {
|
||||
setState(() {
|
||||
_currentPage = 1;
|
||||
_hasMoreData = true;
|
||||
});
|
||||
}
|
||||
|
||||
if (!_hasMoreData && !isRefresh) return;
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final baseUrl = HostUtils().currentHost;
|
||||
final path = '/country/read';
|
||||
final url = '$baseUrl$path';
|
||||
|
||||
final dio = Dio();
|
||||
final response = await dio.get(
|
||||
url,
|
||||
queryParameters: {
|
||||
'page': _currentPage,
|
||||
'page_size': _pageSize,
|
||||
// 为空时查询所有国家
|
||||
'name': '',
|
||||
'code': '',
|
||||
'country_id': '',
|
||||
},
|
||||
options: Options(headers: {'Content-Type': 'application/json'}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final CountryResponse countryResponse = CountryResponse.fromJson(
|
||||
response.data,
|
||||
);
|
||||
|
||||
if (countryResponse.success) {
|
||||
setState(() {
|
||||
if (isRefresh) {
|
||||
_countries = countryResponse.data.items;
|
||||
} else {
|
||||
_countries.addAll(countryResponse.data.items);
|
||||
}
|
||||
|
||||
// 检查是否还有更多数据
|
||||
_hasMoreData = _countries.length < countryResponse.data.total;
|
||||
_currentPage++;
|
||||
_errorMessage = null;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_errorMessage = countryResponse.message;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
_errorMessage = '服务器响应异常: ${response.statusCode}';
|
||||
});
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
String errorMsg = '网络请求失败';
|
||||
if (e.response != null) {
|
||||
errorMsg = '请求失败: ${e.response?.statusCode}';
|
||||
} else if (e.type == DioExceptionType.connectionTimeout) {
|
||||
errorMsg = '连接超时,请检查网络';
|
||||
} else if (e.type == DioExceptionType.connectionError) {
|
||||
errorMsg = '网络连接错误';
|
||||
}
|
||||
setState(() {
|
||||
_errorMessage = errorMsg;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_errorMessage = '发生未知错误: $e';
|
||||
});
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
Future<void> _refresh() async {
|
||||
await _fetchCountries(isRefresh: true);
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
void _loadMore() {
|
||||
if (!_isLoading && _hasMoreData) {
|
||||
_fetchCountries();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('国家列表'),
|
||||
centerTitle: true,
|
||||
elevation: 4,
|
||||
shadowColor: Colors.black12,
|
||||
backgroundColor: theme.colorScheme.surfaceContainerHighest,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () async {
|
||||
// 跳转到新增页面,返回时刷新列表
|
||||
final result = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const AddCountryPage()),
|
||||
);
|
||||
if (result == true) {
|
||||
_fetchCountries(isRefresh: true);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(child: _buildBody(theme)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody(ThemeData theme) {
|
||||
if (_isLoading && _countries.isEmpty) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (_errorMessage != null && _countries.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
_errorMessage!,
|
||||
style: TextStyle(color: theme.colorScheme.error),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () => _fetchCountries(isRefresh: true),
|
||||
child: const Text('重试'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: _refresh,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
itemCount: _countries.length + (_hasMoreData ? 1 : 0),
|
||||
itemBuilder: (context, index) {
|
||||
if (index < _countries.length) {
|
||||
final country = _countries[index];
|
||||
return _buildCountryItem(theme, country);
|
||||
} else {
|
||||
// 加载更多指示器
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Center(
|
||||
child: _isLoading
|
||||
? const CircularProgressIndicator()
|
||||
: const Text('没有更多数据了'),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
// 监听滚动加载更多
|
||||
controller: ScrollController()
|
||||
..addListener(() {
|
||||
if (_isLoading) return;
|
||||
if (_hasMoreData &&
|
||||
ScrollController().position.pixels >=
|
||||
ScrollController().position.maxScrollExtent - 200) {
|
||||
_loadMore();
|
||||
}
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 构建国家列表项
|
||||
Widget _buildCountryItem(ThemeData theme, Country country) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
// 可以添加点击事件
|
||||
},
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
splashColor: theme.colorScheme.primary.withAlpha(26),
|
||||
highlightColor: theme.colorScheme.primary.withAlpha(13),
|
||||
child: Container(
|
||||
height: 64,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.flag,
|
||||
size: 24,
|
||||
color: theme.colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
country.name,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'代码: ${country.code}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 18,
|
||||
color: theme.hintColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
thickness: 1,
|
||||
indent: 72,
|
||||
endIndent: 16,
|
||||
color: theme.dividerColor,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ class HomePage extends StatelessWidget {
|
||||
final List<Map<String, dynamic>> features = [
|
||||
{'icon': Icons.bar_chart, 'title': '数据分析', 'route': null},
|
||||
{'icon': Icons.balance, 'title': '交易', 'route': null},
|
||||
{'icon': Icons.flag_circle, 'title': '国家', 'route': '/country'},
|
||||
{'icon': Icons.account_balance, 'title': '交易所', 'route': '/exchange'},
|
||||
{'icon': Icons.branding_watermark, 'title': '品种', 'route': null},
|
||||
];
|
||||
@@ -115,14 +116,12 @@ class HomePage extends StatelessWidget {
|
||||
onTap: () {
|
||||
// 点击事件处理 - 如果有路由信息则导航
|
||||
if (route != null && context.mounted) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => ExchangePage()),
|
||||
);
|
||||
// 使用路由路径进行导航
|
||||
Navigator.pushNamed(context, route!);
|
||||
}
|
||||
},
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
splashColor: theme.colorScheme.primary.withAlpha(26), // 修改为withAlpha更兼容
|
||||
splashColor: theme.colorScheme.primary.withAlpha(26),
|
||||
highlightColor: theme.colorScheme.primary.withAlpha(13),
|
||||
child: Container(
|
||||
height: 64,
|
||||
|
||||
231
frontend/asset_assistant/lib/pages/read.go
Normal file
231
frontend/asset_assistant/lib/pages/read.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package logic4country
|
||||
|
||||
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:"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,
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user