# GS MES 服务帮助文档 ## 1. 项目总体概览 GS MES Service 是一套 MES(制造执行系统)后端接口,采用 ASP.NET Core 8.0 作为宿主框架。入口项目 `Gs.HostIIS` 负责注册控制器、加载配置和启用 Swagger,同时通过 `builder.AddCustomInject()` 自动发现并注册各模块中的业务服务。功能按领域拆分为 `Gs.BaseInfo`、`Gs.Sales`、`Gs.Warehouse`、`Gs.Report` 等项目,每个模块通过 `Services/`(控制器)和 `Models/`(领域模型)向外暴露 API。 ## 2. 核心架构与运行机制 - **启动流程**:`Program.cs` 建立 WebApplication,配置 JSON 序列化(全量 Unicode、Newtonsoft.Json)、全局授权过滤、CORS、Swagger 及静态文件目录,然后调用 `app.AddCustomController()` 与 `builder.AddCustomInject()` 扩展。 - **自动依赖注入**:`Gs.Toolbox.ApiCore.Extensions.ApplicationBuilderExtension` 会扫描入口程序集及其引用,识别实现了 `ISingleton`、`IScope`、`ITransient` 等标记接口或标注了 `ExposeAttribute` 的类,并按约定注入容器。 - **控制器加载**:`CustomControllerFeatureProvider` 与 `CustomApplicationModelConvention` 结合 `ApiGroup` 特性,实现按模块分组的路由与 Swagger 文档划分。 - **授权机制**:`ApiAuthorizeAttribute` 拦截所有未标记 `[AllowAnonymous]` 的请求,检查请求头 `token` 是否包含形如 `token ` 的值(长度至少 5)。 - **静态资源**:根据配置项 `DownPath` 与 `UploadPath` 映射 `/down`、`/upload` 静态目录,用于文件下载与上传。 ## 3. 主要项目与文件夹职责 - `GsMesSolution.sln`:解决方案根入口。 - `Gs.HostIIS/`:主机项目,包含配置文件、Swagger 设置、自定义授权属性。 - `Gs./`:领域模块,示例: - `Gs.BaseInfo`:基础资料维护,`MesItemsManager` 等控制器继承 `Repository` 并使用 SqlSugar 查询。 - `Gs.Warehouse`:仓储相关接口。 - `Gs.Report`:报表与导出功能。 - `Gs.Entity/`:集中存放 SqlSugar 实体、枚举、DTO。 - `Gs.Toolbox/`:通用工具,包括配置读取(`AppSettingsHelper`)、仓储基类(`Repository`)、Excel 导出、日志、ApiCore 扩展等。 - `TemplateEngineHost/`:模版引擎集成(若需生成报表/文档)。 - `.config/`:部署阶段使用的环境配置转换文件。 ## 4. 开发环境准备 1. 安装 .NET SDK 8.0(`dotnet --version` 验证)。 2. 准备数据库访问账号,与 `ConnectionStrings` 中的目标库一致;敏感字段通过 User Secrets 或环境变量注入。 3. 配置 Git、PowerShell/Bash、SQL 客户端及常用调试工具(Postman、curl)。 4. 若需前端联调,确认本地端口(默认在 `appsettings*.json` 中设置)及跨域策略(`AllowAnyOrigin`)。 ## 5. 构建、运行与调试 ```bash dotnet restore GsMesSolution.sln # 安装所有 NuGet 依赖(SqlSugar、Swashbuckle 等) dotnet build GsMesSolution.sln -c Release dotnet run --project Gs.HostIIS/Gs.HostIIS.csproj ``` 启动后访问 `http://localhost:<端口>/swagger`,可见按照 `ApiGroupNames` 分组的接口文档。调用需要授权的接口时,在请求头添加 `token: token 3fa85f64-5717-4562-b3fc-2c963f66afa6`(示例)。 ### 常见调试技巧 - 使用 `?trace=true` 等约定参数(若控制器支持)定位日志。 - 关注控制台输出的 SqlSugar SQL 日志(`Repository` 通过 `Db.Aop.OnLogExecuting` 输出格式化语句)。 - 若需要临时关闭授权,可在测试环境对 `ApiAuthorizeAttribute` 做条件短路或在控制器方法上加 `[AllowAnonymous]`(上线前务必移除)。 ## 6. 配置管理 - **基础配置**:`Gs.HostIIS/appsettings.json`、`appsettings.Development.json` 存储路径、Swagger 分组 XML 路径等;`ServicesPath` 用于扫描生成的 XML 注释。 - **敏感信息**:使用 `dotnet user-secrets`(开发环境)或系统环境变量注入 `ConnectionStrings` 等敏感字段,切勿提交到版本库。 - **环境转换**:生产/测试环境配置放入 `Gs.HostIIS/.config/*.json`,通过发布流程或 CI/CD 在部署阶段覆盖。 - **文件目录**:确认 `DownPath`、`UploadPath` 对应的物理目录权限;服务启动会尝试自动创建缺失目录。 ## 7. 业务模块开发模式 - 控制器通常继承 `Repository`,直接复用 SqlSugarScope(支持事务、通用分页 `CommonPage`)。 - 通过 `ApiGroup(ApiGroupNames.XXX)` 声明 Swagger 分组;使用 `RequestMethod` 特性指定 HTTP 方法(系统自带的 `RequestMethods` 枚举)。 - 依赖注入:在构造函数中声明所需服务(如 `IHttpContextAccessor`、仓储接口),并确保实现类实现对应生命周期接口以被自动注入。 - 用户身份:使用 `UtilityHelper.GetUserGuidAndOrgGuid()` 从请求上下文提取用户标识,常用于数据隔离。 - 响应格式:封装在 `ResponseResult`、`ReturnDto` 等统一返回对象中,保持前端体验一致。 ## 8. 数据访问与基础工具 - **SqlSugar 仓储**:`Repository` 默认连接 `AppSettingsHelper.getValueByKey("ConnectionStrings")`,支持 `UseTransaction` 包装事务块、`CommonPage` 快速分页。 - **Excel 导出**:`ExcelHelper` 提供导出功能,通常在报表模块使用。 - **HTTP 辅助**:`InterfaceUtil`、`UtilityHelper` 等封装跨系统调用、用户上下文解析。 - **日志**:`LogHelper` 封装常规日志记录;需要时可扩展现有实现接入集中式日志平台。 ## 9. 安全与授权 - 所有接口默认经过 `ApiAuthorizeAttribute` 校验 `token`。生产环境应替换长度校验逻辑为真实身份认证(JWT、单点登录等)。 - Swagger UI 暴露在 `/swagger`,上线前需确认是否限制外网访问或启用认证。 - 请勿在仓库中存储真实密钥、数据库账号或生产配置;`.config` 目录仅存放模板/转换文件。 - 数据权限:模块内可根据 `_orgFids`、`_userGuid` 等字段控制查询范围。 ## 10. 测试与质量保证 - 建议为每个领域模块创建对应的 xUnit 测试项目(如 `Gs.BaseInfo.Tests`),测试文件命名统一为 `*Tests.cs`。 - 测试中通过模拟仓储或 SqlSugar 客户端,避免依赖真实数据库;可利用 `ISqlSugarClient` 接口实现自定义 Fake。 - 执行测试:`dotnet test`(在解决方案根目录),并将结果写入 PR 描述。 - 在自动化覆盖完善之前,需在 PR 中记录手工冒烟步骤(Swagger 加载、核心接口、关键存储过程执行情况)。 ## 11. 常见问题排查 - **Swagger 文档缺失**:确认 `ServicesPath` 指向的目录存在 XML 文件,可重新执行 `dotnet build` 生成注释文件。 - **静态文件 404**:核查物理目录路径是否正确,或日志中是否提示访问权限问题。 - **token 被拒绝**:检查请求头格式是否为 `token: token <值>`;如需调试,可在本地将最小长度改为 0。 - **SqlSugar 连接失败**:确保环境变量中配置正确连接字符串,并验证数据库服务器可达。 - **依赖注入找不到实现**:确认实现类引用了标记接口或 `ExposeAttribute`,且所在程序集已被 `Gs.HostIIS` 引用。 - **跨域请求失败**:默认策略允许所有来源;如仍报错,检查浏览器请求头或是否有代理层拦截。 ## 12. 贡献与协作流程 1. 从最新主干创建功能分支,保持本地环境与远端同步。 2. 按规范编写代码:四空格缩进、`PascalCase` 类型、`camelCase` 变量、开启可空引用。 3. 新增或修改模块时,同步更新文档(README、help、模块说明)以及 `.config` 转换文件(如有配置变更)。 4. 本地执行 `dotnet build`、`dotnet test`(若存在测试),并完成关键 API 的手工冒烟。 5. 使用简洁中文单行作为提交说明(示例:`BaseInfo: 调整物料分页查询`)。 6. 提交 PR 时列出受影响模块、配置/数据库变更、自动化测试结果、手工验证结果及必要的请求/响应示例或截图。 ## 13. 参考资料 - SqlSugar 官方文档:https://www.donet5.com/Home/Doc - 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 ↓ 依赖 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, IRomteService { private readonly UserContextService _userContext; public MesItemsManager(UserContextService userContext) => _userContext = userContext; } ``` ## 15. 常见开发任务速查表 ### 15.1 如何添加新的 API 端点 1. 在相应模块的 `Services/` 目录下创建新的 `*Manager.cs` 或 `*Controller.cs` 2. 继承 `Repository` 并实现 `IRomteService` 接口 3. 在类上标注 `[ApiGroup(ApiGroupNames.xxx)]` 4. 在方法上标注 `[RequestMethod(RequestMethods.POST)]` 等 5. 编译后无需配置,`AddCustomController()` 会自动发现 示例代码: ```csharp [ApiGroup(ApiGroupNames.BaseInfo)] public class CustomItemManager : Repository, IRomteService { [RequestMethod(RequestMethods.POST)] public ReturnDto> GetActiveItems(ItemQuery query) { var items = Db.Queryable() .Where(x => x.IsActive == 1) .ToList(); return new ReturnDto> { RtnCode = ReturnCode.Success, RtnData = items }; } } ``` ### 15.2 如何导出 Excel 报表 ```csharp public ReturnDto ExportItems(ItemQuery query) { var data = Db.Queryable().ToList(); var filePath = ExcelHelper.ExportList( data, "物料清单", new[] { "ItemCode", "ItemName", "ItemSpec" } // 列名 ); return new ReturnDto { RtnCode = ReturnCode.Success, RtnData = new { FilePath = filePath } }; } ``` ### 15.3 如何调用外部 ERP 接口 ```csharp public ReturnDto 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 { RtnCode = ReturnCode.Success, RtnData = result }; } else { LogHelper.Error($"ERP 同步失败:{result.Message}"); return new ReturnDto { RtnCode = ReturnCode.Exception, RtnMsg = result.Message }; } } ``` ### 15.4 如何执行数据库事务 ```csharp public ReturnDto BatchUpdateItems(List items) { try { var affectedRows = UseTransaction(db => { foreach (var item in items) { db.Updateable(item).ExecuteCommand(); } return db.Ado.AffectedRows; }); return new ReturnDto { RtnCode = ReturnCode.Success, RtnData = new { AffectedRows = affectedRows } }; } catch (Exception ex) { LogHelper.Error($"批量更新失败:{ex.Message}"); return new ReturnDto { 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() .Where(x => x.OrgGuid == orgGuid) .ToList(); } ``` ### 15.6 如何实现分页查询 ```csharp [RequestMethod(RequestMethods.POST)] public ReturnDto> GetItemsPage(PageQuery query) { // 自动处理分页、排序、模糊搜索 var pageData = CommonPage( Db.Queryable() .Where(x => x.ItemStatus == 1), // 基础过滤 query // PageQuery 包含分页参数 ); return new ReturnDto> { RtnCode = ReturnCode.Success, RtnData = pageData }; } ``` ### 15.7 如何在 Swagger 中隐藏或标记接口 ```csharp // 标记为已过期的接口 [Obsolete("已被 GetItemsPageV2 替代")] [RequestMethod(RequestMethods.POST)] public ReturnDto> GetItemsPage(PageQuery query) { // ... } // 使用 [AllowAnonymous] 绕过授权(生产环境需谨慎) [AllowAnonymous] [RequestMethod(RequestMethods.GET)] public ReturnDto GetVersion() { return new ReturnDto { 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 /// /// 分页查询物料列表 /// /// 分页及搜索参数 /// 包含分页数据的返回结果 [RequestMethod(RequestMethods.POST)] public ReturnDto> GetItemsPage(PageQuery query) { // 实现 } ``` ## 17. 故障排查进阶指南 ### 17.1 调试技巧 | 问题 | 排查步骤 | |------|--------| | 控制器未被发现 | 1. 检查类是否实现 `IRomteService`
2. 检查类所在程序集是否被 `Gs.HostIIS` 引用
3. 检查类是否在 `Services/` 目录
4. 运行 `dotnet build` 生成 DLL
5. 查看 Swagger `/swagger` 是否出现 | | 方法未出现在 Swagger | 1. 检查方法是否标注 `[RequestMethod(...)]`
2. 检查方法上是否有 XML 注释
3. 检查 `appsettings.json` 中 `ServicesPath` 配置
4. 检查生成的 XML 文件是否存在 | | 授权失败(token 被拒) | 1. 检查请求头格式:`Authorization: token 3fa85f64-5717-4562-b3fc-2c963f66afa6`
2. 检查 token 长度是否 ≥ 5
3. 在本地测试环境可临时修改 `ApiAuthorizeAttribute` 的最小长度为 0
4. 确认 `[AllowAnonymous]` 标注的接口不在控制器级别,否则整个控制器将被跳过 | | 数据库连接失败 | 1. 检查 `ConnectionStrings` 是否正确
2. 验证 SQL Server 服务是否运行
3. 确认网络连通性:`ping `
4. 检查防火墙是否开放 SQL Server 端口(默认 1433)
5. 验证数据库用户名密码 | | CORS 跨域失败 | 1. 检查浏览器控制台的 CORS 错误详情
2. 检查 `Gs.HostIIS/Program.cs` 中的 CORS 策略配置
3. 验证请求的 `Origin` 是否在允许列表中
4. 尝试使用 Postman 或 curl 排除浏览器限制 | | Swagger UI 打开缓慢或超时 | 1. 检查 Swagger 分组配置是否正确
2. 删除 `bin/` 和 `obj/` 重新编译
3. 检查是否有大量未使用的接口或循环依赖
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` 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`)已准备 - [ ] 运行完整的冒烟测试(核心业务流程) - [ ] 性能测试或基准测试已完成 - [ ] 备份计划与回滚方案已准备 - [ ] 监控告警规则已配置 - [ ] 用户文档与技术文档已更新