编辑 | blame | 历史 | 原始文档

GS MES 服务帮助文档

1. 项目总体概览

GS MES Service 是一套 MES(制造执行系统)后端接口,采用 ASP.NET Core 8.0 作为宿主框架。入口项目 Gs.HostIIS 负责注册控制器、加载配置和启用 Swagger,同时通过 builder.AddCustomInject() 自动发现并注册各模块中的业务服务。功能按领域拆分为 Gs.BaseInfoGs.SalesGs.WarehouseGs.Report 等项目,每个模块通过 Services/(控制器)和 Models/(领域模型)向外暴露 API。

2. 核心架构与运行机制

  • 启动流程Program.cs 建立 WebApplication,配置 JSON 序列化(全量 Unicode、Newtonsoft.Json)、全局授权过滤、CORS、Swagger 及静态文件目录,然后调用 app.AddCustomController()builder.AddCustomInject() 扩展。
  • 自动依赖注入Gs.Toolbox.ApiCore.Extensions.ApplicationBuilderExtension 会扫描入口程序集及其引用,识别实现了 ISingletonIScopeITransient 等标记接口或标注了 ExposeAttribute 的类,并按约定注入容器。
  • 控制器加载CustomControllerFeatureProviderCustomApplicationModelConvention 结合 ApiGroup 特性,实现按模块分组的路由与 Swagger 文档划分。
  • 授权机制ApiAuthorizeAttribute 拦截所有未标记 [AllowAnonymous] 的请求,检查请求头 token 是否包含形如 token <guid> 的值(长度至少 5)。
  • 静态资源:根据配置项 DownPathUploadPath 映射 /down/upload 静态目录,用于文件下载与上传。

3. 主要项目与文件夹职责

  • GsMesSolution.sln:解决方案根入口。
  • Gs.HostIIS/:主机项目,包含配置文件、Swagger 设置、自定义授权属性。
  • Gs.<Module>/:领域模块,示例:
  • Gs.BaseInfo:基础资料维护,MesItemsManager 等控制器继承 Repository<T> 并使用 SqlSugar 查询。
  • Gs.Warehouse:仓储相关接口。
  • Gs.Report:报表与导出功能。
  • Gs.Entity/:集中存放 SqlSugar 实体、枚举、DTO。
  • Gs.Toolbox/:通用工具,包括配置读取(AppSettingsHelper)、仓储基类(Repository<T>)、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. 构建、运行与调试

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<T> 通过 Db.Aop.OnLogExecuting 输出格式化语句)。
  • 若需要临时关闭授权,可在测试环境对 ApiAuthorizeAttribute 做条件短路或在控制器方法上加 [AllowAnonymous](上线前务必移除)。

6. 配置管理

  • 基础配置Gs.HostIIS/appsettings.jsonappsettings.Development.json 存储路径、Swagger 分组 XML 路径等;ServicesPath 用于扫描生成的 XML 注释。
  • 敏感信息:使用 dotnet user-secrets(开发环境)或系统环境变量注入 ConnectionStrings 等敏感字段,切勿提交到版本库。
  • 环境转换:生产/测试环境配置放入 Gs.HostIIS/.config/*.json,通过发布流程或 CI/CD 在部署阶段覆盖。
  • 文件目录:确认 DownPathUploadPath 对应的物理目录权限;服务启动会尝试自动创建缺失目录。

7. 业务模块开发模式

  • 控制器通常继承 Repository<T>,直接复用 SqlSugarScope(支持事务、通用分页 CommonPage)。
  • 通过 ApiGroup(ApiGroupNames.XXX) 声明 Swagger 分组;使用 RequestMethod 特性指定 HTTP 方法(系统自带的 RequestMethods 枚举)。
  • 依赖注入:在构造函数中声明所需服务(如 IHttpContextAccessor、仓储接口),并确保实现类实现对应生命周期接口以被自动注入。
  • 用户身份:使用 UtilityHelper.GetUserGuidAndOrgGuid() 从请求上下文提取用户标识,常用于数据隔离。
  • 响应格式:封装在 ResponseResultReturnDto<T> 等统一返回对象中,保持前端体验一致。

8. 数据访问与基础工具

  • SqlSugar 仓储Repository<T> 默认连接 AppSettingsHelper.getValueByKey("ConnectionStrings"),支持 UseTransaction 包装事务块、CommonPage 快速分页。
  • Excel 导出ExcelHelper 提供导出功能,通常在报表模块使用。
  • HTTP 辅助InterfaceUtilUtilityHelper 等封装跨系统调用、用户上下文解析。
  • 日志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 builddotnet 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.csApiAuthorizeAttribute.cs
自定义路由规则 Gs.Toolbox/ApiCore/Extensions/ApplicationExtension.cs
控制器发现机制 Gs.Toolbox/ApiCore/CustomControllerFeatureProvider.cs
仓储基类 Gs.Toolbox/Repository.cs
统一返回格式 Gs.Toolbox/Models/ReturnDto.csGs.Entity
Swagger 分组定义 Gs.Toolbox/ApiCore/ApiGroupNames.cs
配置读取工具 Gs.Toolbox/AppSettingsHelper.cs
Excel 导出 Gs.Toolbox/ExcelHelper.cs
HTTP 调用 Gs.Toolbox/InterfaceUtil.csHttpHelper.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, 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 报表

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 接口

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 如何执行数据库事务

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 如何获取当前用户信息

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 如何实现分页查询

[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 中隐藏或标记接口

// 标记为已过期的接口
[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
2. 检查类所在程序集是否被 Gs.HostIIS 引用
3. 检查类是否在 Services/ 目录
4. 运行 dotnet build 生成 DLL
5. 查看 Swagger /swagger 是否出现
方法未出现在 Swagger 1. 检查方法是否标注 [RequestMethod(...)]
2. 检查方法上是否有 XML 注释
3. 检查 appsettings.jsonServicesPath 配置
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 <database-server>
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.jsonLogPath 指定的目录(默认 logs/
  • 查看 SQL 执行:SqlSugar 会自动输出 SQL 语句到控制台,观察执行计划与参数
  • 异常堆栈:捕获完整的异常堆栈跟踪,定位问题发生在哪一行代码
  • 关联用户:在日志中记录操作用户、时间戳,便于事后审计

18. 扩展与集成

18.1 添加新的业务模块流程

  1. 创建项目:在 VS 中新建 .NET 8.0 类库项目,命名为 Gs.NewModule
  2. 配置项目文件(参考 CLAUDE.md 中的 .csproj 模板)
  3. 添加引用Gs.EntityGs.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)

请求 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 示例

请求 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 与存储过程交互

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