| | |
| | | - 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`)已准备 |
| | | - [ ] 运行完整的冒烟测试(核心业务流程) |
| | | - [ ] 性能测试或基准测试已完成 |
| | | - [ ] 备份计划与回滚方案已准备 |
| | | - [ ] 监控告警规则已配置 |
| | | - [ ] 用户文档与技术文档已更新 |