// ============================================================================
// 【文件说明】HwPortalControllerBase.cs - 门户控制器基类
// ============================================================================
// 这个类是所有门户控制器的"公共基类"。
// 在 Java Spring Boot 中,你可能会写一个 BaseController,提供公共方法。
// 这里概念完全一样:把"返回成功"、"返回失败"、"分页"等公共逻辑封装起来。
//
// 【继承关系】
// HwPortalControllerBase : ControllerBase
// ↑
// HwWebController, HwProductInfoController, ... (具体控制器)
//
// ControllerBase 是 ASP.NET Core 提供的控制器基类,
// 包含 Request、Response、User、HttpContext 等常用属性。
// ============================================================================
namespace Admin.NET.Plugin.HwPortal;
///
/// 门户控制器基类。
///
/// 【C# 语法知识点 - abstract 抽象类】
/// abstract class 表示"抽象类",和 Java 的 abstract class 含义完全一致:
/// - 不能直接 new 实例化,只能被继承
/// - 可以包含抽象方法(没有实现体)和具体方法(有实现体)
/// - 子类必须实现所有抽象成员
///
///
/// 【C# 语法知识点 - ControllerBase】
/// ControllerBase 是 ASP.NET Core 提供的控制器基类。
/// 它提供了:
/// - Request:HTTP 请求对象
/// - Response:HTTP 响应对象
/// - User:当前登录用户信息
/// - HttpContext:HTTP 上下文
/// - Ok()、BadRequest()、NotFound() 等返回方法
///
/// 对比 Java Spring Boot:
/// Spring 的控制器通常继承或使用 @RestController 注解,
/// 方法参数可以注入 HttpServletRequest、HttpServletResponse 等。
///
/// ASP.NET Core 的方式更"面向对象":
/// 控制器本身就是请求上下文的载体,通过属性访问请求/响应。
///
///
/// 【与 Java Spring Boot 的对比】
/// Java 若依的 BaseController:
/// public class BaseController {
/// protected AjaxResult success() { ... }
/// protected AjaxResult error() { ... }
/// protected TableDataInfo getDataTable(List> list) { ... }
/// }
///
/// C# 这里的设计几乎一样,只是语法不同。
///
///
[ApiController]
// 【C# 语法知识点 - 特性(Attribute)】
// [ApiController] 是 ASP.NET Core 的特性,用于标记这是一个 API 控制器。
// 它会自动启用:
// 1. 自动 HTTP 400 响应(模型验证失败时)
// 2. 绑定源推断(自动从 body/query/route 绑定参数)
// 3. 问题详情响应(错误时返回标准格式)
//
// 对比 Java Spring Boot:
// Java 用 @RestController 注解,功能类似:
// @RestController
// public class MyController { ... }
//
// C# 的特性用方括号 [],Java 的注解用 @。
public abstract class HwPortalControllerBase : ControllerBase
{
///
/// 返回成功结果(带数据)。
///
/// 【C# 语法知识点 - protected 访问修饰符】
/// protected 表示"受保护的",只能在当前类或子类中访问。
///
/// 为什么用 protected?
/// 这些方法是给子控制器用的,不应该被外部直接调用。
///
/// 对比 Java:
/// Java 的 protected 含义一样,但 C# 没有"包级访问"的概念。
/// Java 的 protected 还允许同一个包内的类访问,C# 不行。
///
///
/// 【方法重载】
/// Success()、Success(data)、Success(msg)、Success(msg, data)
/// 这是"方法重载"(Overload):同名方法,参数不同。
///
/// 对比 Java:
/// Java 的方法重载语法完全一样。
/// 但 C# 还支持"可选参数",可以减少重载数量:
/// protected HwPortalAjaxResult Success(string msg = "操作成功", object data = null)
///
/// 这里保持多个重载是为了和原 Java 代码风格一致。
///
///
/// 返回数据,默认为 null
/// 返回消息,默认为 "操作成功"
/// 成功结果
protected HwPortalAjaxResult Success(object data = null, string msg = "操作成功")
{
return HwPortalAjaxResult.Success(msg, data);
}
///
/// 返回成功结果(仅消息)。
///
/// 返回消息
/// 成功结果
protected HwPortalAjaxResult Success(string msg)
{
return HwPortalAjaxResult.Success(msg);
}
///
/// 返回警告结果。
///
/// 警告消息
/// 返回数据,默认为 null
/// 警告结果
protected HwPortalAjaxResult Warn(string msg, object data = null)
{
return HwPortalAjaxResult.Warn(msg, data);
}
///
/// 返回错误结果。
///
/// 错误消息
/// 错误码,默认为 500
/// 错误结果
protected HwPortalAjaxResult Error(string msg, int code = 500)
{
// 【三元运算符】
// condition ? valueIfTrue : valueIfFalse
// 这里判断:如果 code 等于默认错误码,用简化方法;否则用带自定义码的方法。
return code == HwPortalAjaxResult.ErrorCode
? HwPortalAjaxResult.Error(msg)
: HwPortalAjaxResult.Error(code, msg);
}
///
/// 根据影响行数返回成功或失败结果。
///
/// 【业务场景】
/// 在数据库操作中,增删改会返回"影响行数":
/// - rows > 0:表示操作成功,有数据被修改
/// - rows = 0:表示操作失败,没有数据被修改
///
/// 这个方法把"影响行数"转换为"响应结果",简化控制器代码:
/// return ToAjax(await service.Insert(input));
///
/// 对比 Java 若依:
/// 若依的 BaseController.toAjax(int rows) 做同样的事情:
/// protected AjaxResult toAjax(int rows) {
/// return rows > 0 ? success() : error();
/// }
///
///
/// 数据库影响行数
/// 成功或失败结果
protected HwPortalAjaxResult ToAjax(int rows)
{
return HwPortalAjaxResult.FromRows(rows);
}
///
/// 导出 Excel 文件。
///
/// 【C# 语法知识点 - 泛型方法 <T>】
/// protected IActionResult ExportExcel<T>(...)
/// 这里的 <T> 是泛型参数,表示"这个方法可以处理任何类型的数据"。
///
/// 调用示例:
/// ExportExcel<HwWeb>(list, "官网数据"); // 显式指定类型
/// ExportExcel(list, "官网数据"); // 类型推断,编译器自动识别
///
/// 对比 Java:
/// Java 的泛型方法写法类似:
/// protected <T> ResponseEntity exportExcel(List<T> source, String fileName)
///
/// 但 Java 的泛型有"类型擦除",运行时 T 只是 Object。
/// C# 的泛型在运行时是真实类型,性能更好。
///
///
/// 数据类型
/// 数据列表
/// 文件名
/// Excel 文件响应
protected IActionResult ExportExcel(IReadOnlyList source, string fileName)
{
// ExcelHelper 是项目中的工具类,负责把列表导出为 Excel 文件。
// 返回的 IActionResult 会被框架转换为文件下载响应。
return ExcelHelper.ExportData(source, fileName);
}
///
/// 获取分页数据(内存分页)。
///
/// 【内存分页 vs 数据库分页】
/// 这个方法做的是"内存分页":先查出所有数据,再在内存中分页。
///
/// 优点:简单,不依赖数据库的分页语法
/// 缺点:数据量大时性能差
///
/// 适用场景:数据量小(几百条以内),或者已经做过数据库分页的二次分页。
///
///
/// 【C# 语法知识点 - IReadOnlyList<T>】
/// IReadOnlyList<T> 是"只读列表接口"。
/// 它比 List<T> 更安全,因为调用者不能修改列表内容(不能 Add/Remove)。
///
/// 对比 Java:
/// Java 没有内置的只读列表接口,通常用 List<T> 或 Collections.unmodifiableList()。
///
///
/// 数据类型
/// 完整数据列表
/// 分页后的数据包装对象
protected HwPortalTableDataInfo GetDataTable(IReadOnlyList source)
{
// 【获取分页参数】
// Request.Query 用来读取 URL 查询字符串,例如 ?pageNum=1&pageSize=20。
//
// 对比 Java Spring Boot:
/// Java 通常用 @RequestParam 注解获取:
/// public Result list(@RequestParam(defaultValue = "1") int pageNum,
/// @RequestParam(defaultValue = "10") int pageSize)
///
/// 这里直接从 Request.Query 读取,更灵活但需要手动处理默认值。
int pageNum = ParsePositiveInt(Request.Query["pageNum"], 1);
int pageSize = ParsePositiveInt(Request.Query["pageSize"], source.Count == 0 ? 10 : source.Count);
// 【计算跳过的记录数】
// skip = 需要跳过多少条。
// 这是最常见的内存分页算法:
// 第1页:skip = 0,取 1-10 条
// 第2页:skip = 10,取 11-20 条
// 第3页:skip = 20,取 21-30 条
//
// Math.Max(0, ...) 确保不会出现负数(比如 pageNum = 0 的情况)。
int skip = Math.Max(0, (pageNum - 1) * pageSize);
// 【LINQ 分页操作】
// source.Skip(skip).Take(pageSize).ToList()
//
// Skip(n):跳过前 n 条记录
// Take(n):取接下来的 n 条记录
// ToList():将结果转换为 List<T>
//
// 对比 Java Stream:
/// Java 写法:
/// list.stream().skip(skip).limit(pageSize).collect(Collectors.toList());
///
/// C# 的 LINQ 和 Java Stream 非常相似,都是函数式编程风格。
IReadOnlyList rows = source.Skip(skip).Take(pageSize).ToList();
// 【对象初始化器】
// new HwPortalTableDataInfo<T> { Total = ..., Rows = ... }
// 这是"对象初始化器"语法,可以在创建对象时直接给属性赋值。
//
// 对比 Java:
/// Java 需要这样写:
/// HwPortalTableDataInfo<T> dto = new HwPortalTableDataInfo<>();
/// dto.setTotal(source.size());
/// dto.setRows(rows);
/// return dto;
///
/// C# 一行搞定,更简洁。
return new HwPortalTableDataInfo
{
Total = source.Count,
Rows = rows
};
}
///
/// 获取不分页数据(返回全部)。
///
/// 数据类型
/// 数据列表
/// 数据包装对象(不分页)
protected HwPortalTableDataInfo GetDataTableWithoutPaging(IReadOnlyList source)
{
return new HwPortalTableDataInfo
{
Total = source.Count,
Rows = source
};
}
///
/// 解析逗号分隔的长整型数组。
///
/// 【业务场景】
/// 前端传来的删除请求通常是:DELETE /api/users/1,2,3,4,5
/// 这里的 "1,2,3,4,5" 需要解析成 long[] 数组。
///
///
/// 【C# 语法知识点 - static 静态方法】
/// protected static 表示"受保护的静态方法"。
/// 静态方法不依赖实例状态,可以直接通过类名调用。
///
/// 为什么用 static?
/// 这个方法不访问任何实例成员,只是做数据转换。
/// 标记为 static 可以让编译器优化,也方便子类直接调用。
///
///
/// 逗号分隔的字符串,如 "1,2,3"
/// 解析后的长整型数组
protected static long[] ParseLongArray(string value)
{
// 【空值处理】
// string.IsNullOrWhiteSpace(value) 检查:
// - null
// - 空字符串 ""
// - 纯空白字符串 " "
//
// 对比 Java:
/// Java 需要手动判断:
/// if (value == null || value.trim().isEmpty()) { ... }
if (string.IsNullOrWhiteSpace(value))
{
// 【Array.Empty<T>()】
// Array.Empty<long>() 返回一个共享的空数组实例。
// 比 new long[0] 更高效,不会每次都创建新对象。
//
// 这是一个小优化点:避免每次 new 空数组产生垃圾对象。
return Array.Empty();
}
// 【LINQ 链式操作】
// value.Split(...):按逗号分割字符串
// .Select(long.Parse):把每个字符串解析成 long
// .ToArray():转换成数组
//
// 对比 Java Stream:
/// Java 写法:
/// Arrays.stream(value.split(","))
/// .map(Long::parseLong)
/// .toArray(Long[]::new);
///
/// C# 的 LINQ 更简洁,而且返回的是 long[] 而不是 Long[](值类型数组,更高效)。
///
/// 【StringSplitOptions】
/// RemoveEmptyEntries:移除空字符串(比如 "1,,2" 中的空项)
/// TrimEntries:去除每项的前后空白
/// | 是"位或"运算符,用于组合多个枚举标志。
return value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Select(long.Parse)
.ToArray();
}
///
/// 解析逗号分隔的字符串数组。
///
/// 逗号分隔的字符串
/// 解析后的字符串数组
protected static string[] ParseStringArray(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return Array.Empty();
}
return value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
}
///
/// 解析正整数(私有辅助方法)。
///
/// 【C# 语法知识点 - int.TryParse】
/// int.TryParse 是"安全转换"方法:
/// - 转换成功:返回 true,out 参数得到转换结果
/// - 转换失败:返回 false,out 参数得到默认值 0
// 不会抛异常!
///
/// 对比 Java:
/// Java 的 Integer.parseInt 失败会抛 NumberFormatException:
/// try {
/// int parsed = Integer.parseInt(value);
/// if (parsed > 0) return parsed;
/// } catch (NumberFormatException e) {
/// // 处理异常
/// }
/// return fallback;
///
/// C# 的 TryParse 更优雅,不需要 try-catch。
///
///
/// 【C# 语法知识点 - out 参数】
/// out 关键字表示"输出参数",方法必须给它赋值。
///
// 调用语法:
/// if (int.TryParse(value, out int parsed)) { ... }
///
/// 这里 out int parsed 是"内联变量声明":
/// 在调用方法的同时声明变量,C# 7.0 引入的特性。
///
/// 对比 Java:
/// Java 没有 out 参数的概念,通常用返回值包装类:
/// OptionalInt result = parseIntSafe(value);
///
///
/// 字符串值
/// 失败时的默认值
/// 解析后的正整数,或默认值
private static int ParsePositiveInt(string value, int fallback)
{
// int.TryParse(value, out int parsed) 尝试把字符串解析成整数。
// && parsed > 0 确保是正整数。
if (int.TryParse(value, out int parsed) && parsed > 0)
{
return parsed;
}
// 解析失败或不是正整数时,返回默认值。
return fallback;
}
}