tjx
2025-10-16 5725a7b4d620ae617729a2aaa289024bef4d63ea
WebApi/help.md
@@ -93,3 +93,389 @@
- ASP.NET Core 官方文档:https://learn.microsoft.com/aspnet/core
- Swashbuckle.AspNetCore 文档:https://github.com/domaindrivendev/Swashbuckle.AspNetCore
- .NET 用户密钥管理:https://learn.microsoft.com/aspnet/core/security/app-secrets
## 14. 项目结构深入理解
### 14.1 解决方案组成
```
GsMesSolution/
├── Gs.HostIIS/          # 主机应用,Web API 入口
├── Gs.Toolbox/          # 核心框架库(.NET 6.0)
├── Gs.Entity/           # 统一实体模型库
├── Gs.BaseInfo/         # 基础信息模块(物料、客户、供应商等)
├── Gs.Warehouse/        # 仓储管理模块
├── Gs.Sales/            # 销售模块
├── Gs.Report/           # 报表模块
├── Gs.Wom/              # 工单管理模块
├── GS.QC/               # 质量管理模块
├── Gs.Sys/              # 系统管理模块
├── Gs.JJGZ/             # 薪资管理模块
├── Gs.Ww/               # 委外管理模块
├── Gs.QiTaRk/           # 其他入库模块
├── Gs.QiTaCk/           # 其他出库模块
├── Gs.Pda/              # 移动端支持模块
├── Gs.OpenApi/          # 对外接口模块
└── TemplateEngineHost/  # 模板引擎
```
### 14.2 关键文件位置索引
| 功能 | 位置 |
|------|------|
| 依赖注入配置 | `Gs.HostIIS/Program.cs` |
| 全局授权过滤 | `Gs.HostIIS/RequestAuthorizeAttribute.cs` 或 `ApiAuthorizeAttribute.cs` |
| 自定义路由规则 | `Gs.Toolbox/ApiCore/Extensions/ApplicationExtension.cs` |
| 控制器发现机制 | `Gs.Toolbox/ApiCore/CustomControllerFeatureProvider.cs` |
| 仓储基类 | `Gs.Toolbox/Repository.cs` |
| 统一返回格式 | `Gs.Toolbox/Models/ReturnDto.cs` 或 `Gs.Entity` |
| Swagger 分组定义 | `Gs.Toolbox/ApiCore/ApiGroupNames.cs` |
| 配置读取工具 | `Gs.Toolbox/AppSettingsHelper.cs` |
| Excel 导出 | `Gs.Toolbox/ExcelHelper.cs` |
| HTTP 调用 | `Gs.Toolbox/InterfaceUtil.cs` 或 `HttpHelper.cs` |
| 日志记录 | `Gs.Toolbox/LogHelper.cs` |
| 用户上下文提取 | `Gs.Toolbox/UtilityHelper.cs` |
### 14.3 核心类关系图
```
IRomteService(标记接口)
    ↓ 实现
MesItemsManager 等控制器类
    ↓ 继承
Repository<TEntity>
    ↓ 依赖
SqlSugarScope(Db 实例)
    ↓ 连接
SQL Server 数据库
```
### 14.4 DI 生命周期标记的使用场景
- **ISingleton**:全局配置读取器、缓存管理器、数据库连接池
- **IScope**:事务管理器、用户上下文提取器(每个 HTTP 请求一个实例)
- **ITransient**:业务逻辑类、临时工具类(每次注入创建新实例)
示例:
```csharp
// 定义 Scoped 服务
public class UserContextService : IScope
{
    private readonly IHttpContextAccessor _http;
    public UserContextService(IHttpContextAccessor http) => _http = http;
    public Guid GetCurrentUserId() => UtilityHelper.GetUserGuidAndOrgGuid(_http).Item1;
}
// 在其他类中使用
[ApiGroup(ApiGroupNames.BaseInfo)]
public class MesItemsManager : Repository<MesItems>, IRomteService
{
    private readonly UserContextService _userContext;
    public MesItemsManager(UserContextService userContext) => _userContext = userContext;
}
```
## 15. 常见开发任务速查表
### 15.1 如何添加新的 API 端点
1. 在相应模块的 `Services/` 目录下创建新的 `*Manager.cs` 或 `*Controller.cs`
2. 继承 `Repository<T>` 并实现 `IRomteService` 接口
3. 在类上标注 `[ApiGroup(ApiGroupNames.xxx)]`
4. 在方法上标注 `[RequestMethod(RequestMethods.POST)]` 等
5. 编译后无需配置,`AddCustomController()` 会自动发现
示例代码:
```csharp
[ApiGroup(ApiGroupNames.BaseInfo)]
public class CustomItemManager : Repository<MesItems>, IRomteService
{
    [RequestMethod(RequestMethods.POST)]
    public ReturnDto<List<MesItems>> GetActiveItems(ItemQuery query)
    {
        var items = Db.Queryable<MesItems>()
            .Where(x => x.IsActive == 1)
            .ToList();
        return new ReturnDto<List<MesItems>>
        {
            RtnCode = ReturnCode.Success,
            RtnData = items
        };
    }
}
```
### 15.2 如何导出 Excel 报表
```csharp
public ReturnDto<object> ExportItems(ItemQuery query)
{
    var data = Db.Queryable<MesItems>().ToList();
    var filePath = ExcelHelper.ExportList(
        data,
        "物料清单",
        new[] { "ItemCode", "ItemName", "ItemSpec" }  // 列名
    );
    return new ReturnDto<object>
    {
        RtnCode = ReturnCode.Success,
        RtnData = new { FilePath = filePath }
    };
}
```
### 15.3 如何调用外部 ERP 接口
```csharp
public ReturnDto<object> SyncDataToErp(SyncRequest req)
{
    var erpUrl = AppSettingsHelper.getValueByKey("ProductionErpUrl");
    var result = InterfaceUtil.PostJson(erpUrl + "/api/sync", JsonConvert.SerializeObject(req));
    if (result.Success)
    {
        LogHelper.Info($"ERP 同步成功:{result.Data}");
        return new ReturnDto<object> { RtnCode = ReturnCode.Success, RtnData = result };
    }
    else
    {
        LogHelper.Error($"ERP 同步失败:{result.Message}");
        return new ReturnDto<object> { RtnCode = ReturnCode.Exception, RtnMsg = result.Message };
    }
}
```
### 15.4 如何执行数据库事务
```csharp
public ReturnDto<object> BatchUpdateItems(List<MesItems> items)
{
    try
    {
        var affectedRows = UseTransaction(db =>
        {
            foreach (var item in items)
            {
                db.Updateable(item).ExecuteCommand();
            }
            return db.Ado.AffectedRows;
        });
        return new ReturnDto<object>
        {
            RtnCode = ReturnCode.Success,
            RtnData = new { AffectedRows = affectedRows }
        };
    }
    catch (Exception ex)
    {
        LogHelper.Error($"批量更新失败:{ex.Message}");
        return new ReturnDto<object> { RtnCode = ReturnCode.Exception, RtnMsg = ex.Message };
    }
}
```
### 15.5 如何获取当前用户信息
```csharp
private readonly IHttpContextAccessor _http;
public void SomeMethod()
{
    var (userGuid, orgGuid) = UtilityHelper.GetUserGuidAndOrgGuid(_http);
    // 根据用户和组织过滤数据
    var userItems = Db.Queryable<MesItems>()
        .Where(x => x.OrgGuid == orgGuid)
        .ToList();
}
```
### 15.6 如何实现分页查询
```csharp
[RequestMethod(RequestMethods.POST)]
public ReturnDto<PageList<MesItems>> GetItemsPage(PageQuery query)
{
    // 自动处理分页、排序、模糊搜索
    var pageData = CommonPage(
        Db.Queryable<MesItems>()
            .Where(x => x.ItemStatus == 1),  // 基础过滤
        query  // PageQuery 包含分页参数
    );
    return new ReturnDto<PageList<MesItems>>
    {
        RtnCode = ReturnCode.Success,
        RtnData = pageData
    };
}
```
### 15.7 如何在 Swagger 中隐藏或标记接口
```csharp
// 标记为已过期的接口
[Obsolete("已被 GetItemsPageV2 替代")]
[RequestMethod(RequestMethods.POST)]
public ReturnDto<PageList<MesItems>> GetItemsPage(PageQuery query)
{
    // ...
}
// 使用 [AllowAnonymous] 绕过授权(生产环境需谨慎)
[AllowAnonymous]
[RequestMethod(RequestMethods.GET)]
public ReturnDto<string> GetVersion()
{
    return new ReturnDto<string>
    {
        RtnCode = ReturnCode.Success,
        RtnData = "1.0.0"
    };
}
```
## 16. 性能优化与最佳实践
### 16.1 查询优化建议
- 使用 SqlSugar 的 `Distinct()`、`Where()` 及早过滤,避免 ORM 层过滤大量数据
- 为频繁查询的字段添加数据库索引(与 DBA 协作)
- 禁用自动映射关联表,使用显式 `Select()` 投影需要的列
- 分页查询务必指定 `OrderBy`,否则结果不稳定
### 16.2 内存优化建议
- 避免在循环中创建新的数据库连接或 HttpClient
- 使用 `using` 或依赖注入管理资源生命周期
- 大量数据导出时考虑流式处理或分批导出
- 缓存配置值(如应用设置)而非每次读取文件
### 16.3 安全建议
- **验证输入**:所有来自客户端的参数应进行类型检查与长度限制
- **SQL 注入防护**:使用 SqlSugar 的参数化查询,禁止字符串拼接 SQL
- **敏感数据**:加密存储密码、API Key、数据库连接字符串,使用环境变量或密钥库
- **日志脱敏**:避免记录用户密码、支付信息等敏感数据
- **CORS 控制**:生产环境应限制 CORS 来源,而非允许所有来源
### 16.4 代码规范建议
- 在类、方法、参数上添加 XML 文档注释(`///`),便于 Swagger 自动生成文档
- 使用有意义的变量名,避免单字母变量(除循环计数器外)
- 异常处理应区分业务异常与系统异常,返回对应的错误码
- 复杂业务逻辑应拆分为多个小方法,便于测试与维护
示例 XML 注释:
```csharp
/// <summary>
/// 分页查询物料列表
/// </summary>
/// <param name="query">分页及搜索参数</param>
/// <returns>包含分页数据的返回结果</returns>
[RequestMethod(RequestMethods.POST)]
public ReturnDto<PageList<MesItems>> GetItemsPage(PageQuery query)
{
    // 实现
}
```
## 17. 故障排查进阶指南
### 17.1 调试技巧
| 问题 | 排查步骤 |
|------|--------|
| 控制器未被发现 | 1. 检查类是否实现 `IRomteService`<br>2. 检查类所在程序集是否被 `Gs.HostIIS` 引用<br>3. 检查类是否在 `Services/` 目录<br>4. 运行 `dotnet build` 生成 DLL<br>5. 查看 Swagger `/swagger` 是否出现 |
| 方法未出现在 Swagger | 1. 检查方法是否标注 `[RequestMethod(...)]`<br>2. 检查方法上是否有 XML 注释<br>3. 检查 `appsettings.json` 中 `ServicesPath` 配置<br>4. 检查生成的 XML 文件是否存在 |
| 授权失败(token 被拒) | 1. 检查请求头格式:`Authorization: token 3fa85f64-5717-4562-b3fc-2c963f66afa6`<br>2. 检查 token 长度是否 ≥ 5<br>3. 在本地测试环境可临时修改 `ApiAuthorizeAttribute` 的最小长度为 0<br>4. 确认 `[AllowAnonymous]` 标注的接口不在控制器级别,否则整个控制器将被跳过 |
| 数据库连接失败 | 1. 检查 `ConnectionStrings` 是否正确<br>2. 验证 SQL Server 服务是否运行<br>3. 确认网络连通性:`ping <database-server>`<br>4. 检查防火墙是否开放 SQL Server 端口(默认 1433)<br>5. 验证数据库用户名密码 |
| CORS 跨域失败 | 1. 检查浏览器控制台的 CORS 错误详情<br>2. 检查 `Gs.HostIIS/Program.cs` 中的 CORS 策略配置<br>3. 验证请求的 `Origin` 是否在允许列表中<br>4. 尝试使用 Postman 或 curl 排除浏览器限制 |
| Swagger UI 打开缓慢或超时 | 1. 检查 Swagger 分组配置是否正确<br>2. 删除 `bin/` 和 `obj/` 重新编译<br>3. 检查是否有大量未使用的接口或循环依赖<br>4. 考虑在 `appsettings.json` 中禁用某些分组以加速加载 |
### 17.2 日志分析
- **日志位置**:`appsettings.json` 中 `LogPath` 指定的目录(默认 `logs/`)
- **查看 SQL 执行**:SqlSugar 会自动输出 SQL 语句到控制台,观察执行计划与参数
- **异常堆栈**:捕获完整的异常堆栈跟踪,定位问题发生在哪一行代码
- **关联用户**:在日志中记录操作用户、时间戳,便于事后审计
## 18. 扩展与集成
### 18.1 添加新的业务模块流程
1. **创建项目**:在 VS 中新建 .NET 8.0 类库项目,命名为 `Gs.NewModule`
2. **配置项目文件**(参考 `CLAUDE.md` 中的 `.csproj` 模板)
3. **添加引用**:`Gs.Entity` 和 `Gs.Toolbox`
4. **创建目录结构**:
   ```
   Gs.NewModule/
   ├── Models/          # 实体或 DTO
   ├── Services/        # 控制器类
   └── Gs.NewModule.csproj
   ```
5. **编写服务类**:实现 `IRomteService`、继承 `Repository<T>`
6. **编译部署**:DLL 会自动输出到 `Gs.HostIIS/bin/Debug/`,启动后自动被发现
### 18.2 与第三方系统集成(如 ERP)
- 使用 `InterfaceUtil` 发送 HTTP 请求
- 在 `appsettings.json` 中配置第三方系统 URL
- 实现重试机制与异常处理
- 记录集成日志便于问题排查
### 18.3 消息队列集成建议(未来扩展)
- 对于异步操作(如大批量导入、ERP 同步),考虑接入 RabbitMQ、Redis 等队列
- 使用后台任务库(如 Hangfire、BackgroundService)处理耗时操作
- 提供异步接口给前端,使用轮询或 WebSocket 获取进度
## 19. 常见 API 调用示例
### 19.1 分页查询示例(POST)
```json
请求 URL: http://localhost:5263/MesItemsManager/GetListPage
请求头: {"token": "token 3fa85f64-5717-4562-b3fc-2c963f66afa6"}
请求体: {
  "currentPage": 1,
  "everyPageSize": 10,
  "sortName": "CreateTime",
  "sortOrder": "desc",
  "keyWord": "物料名称",
  "keyType": "ItemName"
}
```
### 19.2 导出 Excel 示例
```json
请求 URL: http://localhost:5263/SomeController/ExportItems
请求头: {"token": "token ..."}
响应体: {
  "rtnCode": 1,
  "rtnData": {
    "filePath": "/download/items_20250101.xlsx"
  },
  "rtnMsg": "导出成功"
}
```
后续通过 `http://localhost:5263/download/items_20250101.xlsx` 下载文件
### 19.3 与存储过程交互
```csharp
// 调用存储过程获取报表数据
var result = Db.Ado.GetDataTable(
    "EXEC sp_GetSalesReport @StartDate=@StartDate, @EndDate=@EndDate",
    new SqlParameter("@StartDate", DateTime.Parse(query.StartDate)),
    new SqlParameter("@EndDate", DateTime.Parse(query.EndDate))
);
```
## 20. 总结与行动清单
### 快速开始清单
- [ ] 环境准备(SDK、数据库、Git)
- [ ] 解决方案编译与启动
- [ ] 访问 Swagger UI 验证接口加载
- [ ] 使用样例 token 测试授权机制
- [ ] 阅读 CLAUDE.md 了解框架细节
- [ ] 阅读 MODULES.md 了解各模块职责
- [ ] 选择一个小功能,完整实现一个新端点
- [ ] 提交测试 PR 并收集反馈
### 日常开发清单
- [ ] 编写新接口时添加 XML 注释
- [ ] 本地编译通过、Swagger 显示正确
- [ ] 测试接口的授权、参数验证、异常处理
- [ ] 提交前运行 `dotnet build` 验证无编译警告
- [ ] PR 中记录测试步骤与验证结果
- [ ] 更新相关文档(若有配置或数据库变更)
### 上线前清单
- [ ] 所有敏感信息从代码中移除
- [ ] 生产环境配置文件(`.config/*.json`)已准备
- [ ] 运行完整的冒烟测试(核心业务流程)
- [ ] 性能测试或基准测试已完成
- [ ] 备份计划与回滚方案已准备
- [ ] 监控告警规则已配置
- [ ] 用户文档与技术文档已更新