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#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// ============================================================================
// 【文件说明】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;
}
}