You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

423 lines
16 KiB
C#

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