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。
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 <guid> 的值(长度至少 5)。DownPath 与 UploadPath 映射 /down、/upload 静态目录,用于文件下载与上传。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/:部署阶段使用的环境配置转换文件。dotnet --version 验证)。ConnectionStrings 中的目标库一致;敏感字段通过 User Secrets 或环境变量注入。appsettings*.json 中设置)及跨域策略(AllowAnyOrigin)。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 等约定参数(若控制器支持)定位日志。Repository<T> 通过 Db.Aop.OnLogExecuting 输出格式化语句)。ApiAuthorizeAttribute 做条件短路或在控制器方法上加 [AllowAnonymous](上线前务必移除)。Gs.HostIIS/appsettings.json、appsettings.Development.json 存储路径、Swagger 分组 XML 路径等;ServicesPath 用于扫描生成的 XML 注释。dotnet user-secrets(开发环境)或系统环境变量注入 ConnectionStrings 等敏感字段,切勿提交到版本库。Gs.HostIIS/.config/*.json,通过发布流程或 CI/CD 在部署阶段覆盖。DownPath、UploadPath 对应的物理目录权限;服务启动会尝试自动创建缺失目录。Repository<T>,直接复用 SqlSugarScope(支持事务、通用分页 CommonPage)。ApiGroup(ApiGroupNames.XXX) 声明 Swagger 分组;使用 RequestMethod 特性指定 HTTP 方法(系统自带的 RequestMethods 枚举)。IHttpContextAccessor、仓储接口),并确保实现类实现对应生命周期接口以被自动注入。UtilityHelper.GetUserGuidAndOrgGuid() 从请求上下文提取用户标识,常用于数据隔离。ResponseResult、ReturnDto<T> 等统一返回对象中,保持前端体验一致。Repository<T> 默认连接 AppSettingsHelper.getValueByKey("ConnectionStrings"),支持 UseTransaction 包装事务块、CommonPage 快速分页。ExcelHelper 提供导出功能,通常在报表模块使用。InterfaceUtil、UtilityHelper 等封装跨系统调用、用户上下文解析。LogHelper 封装常规日志记录;需要时可扩展现有实现接入集中式日志平台。ApiAuthorizeAttribute 校验 token。生产环境应替换长度校验逻辑为真实身份认证(JWT、单点登录等)。/swagger,上线前需确认是否限制外网访问或启用认证。.config 目录仅存放模板/转换文件。_orgFids、_userGuid 等字段控制查询范围。Gs.BaseInfo.Tests),测试文件命名统一为 *Tests.cs。ISqlSugarClient 接口实现自定义 Fake。dotnet test(在解决方案根目录),并将结果写入 PR 描述。ServicesPath 指向的目录存在 XML 文件,可重新执行 dotnet build 生成注释文件。token: token <值>;如需调试,可在本地将最小长度改为 0。ExposeAttribute,且所在程序集已被 Gs.HostIIS 引用。PascalCase 类型、camelCase 变量、开启可空引用。.config 转换文件(如有配置变更)。dotnet build、dotnet test(若存在测试),并完成关键 API 的手工冒烟。BaseInfo: 调整物料分页查询)。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/ # 模板引擎
| 功能 | 位置 |
|---|---|
| 依赖注入配置 | 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 |
IRomteService(标记接口)
↓ 实现
MesItemsManager 等控制器类
↓ 继承
Repository<TEntity>
↓ 依赖
SqlSugarScope(Db 实例)
↓ 连接
SQL Server 数据库
示例:
```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;
}
```
Services/ 目录下创建新的 *Manager.cs 或 *Controller.csRepository<T> 并实现 IRomteService 接口[ApiGroup(ApiGroupNames.xxx)][RequestMethod(RequestMethods.POST)] 等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 }; } }
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 }
};
}
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 };
}
}
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 };
}
}
private readonly IHttpContextAccessor _http;
public void SomeMethod()
{
var (userGuid, orgGuid) = UtilityHelper.GetUserGuidAndOrgGuid(_http);
// 根据用户和组织过滤数据
var userItems = Db.Queryable<MesItems>()
.Where(x => x.OrgGuid == orgGuid)
.ToList();
}
[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
};
}
// 标记为已过期的接口
[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"
};
}
Distinct()、Where() 及早过滤,避免 ORM 层过滤大量数据Select() 投影需要的列OrderBy,否则结果不稳定using 或依赖注入管理资源生命周期///),便于 Swagger 自动生成文档示例 XML 注释:csharp /// <summary> /// 分页查询物料列表 /// </summary> /// <param name="query">分页及搜索参数</param> /// <returns>包含分页数据的返回结果</returns> [RequestMethod(RequestMethods.POST)] public ReturnDto<PageList<MesItems>> GetItemsPage(PageQuery query) { // 实现 }
| 问题 | 排查步骤 |
|---|---|
| 控制器未被发现 | 1. 检查类是否实现 IRomteService2. 检查类所在程序集是否被 Gs.HostIIS 引用3. 检查类是否在 Services/ 目录4. 运行 dotnet build 生成 DLL5. 查看 Swagger /swagger 是否出现 |
| 方法未出现在 Swagger | 1. 检查方法是否标注 [RequestMethod(...)]2. 检查方法上是否有 XML 注释 3. 检查 appsettings.json 中 ServicesPath 配置4. 检查生成的 XML 文件是否存在 |
| 授权失败(token 被拒) | 1. 检查请求头格式:Authorization: token 3fa85f64-5717-4562-b3fc-2c963f66afa62. 检查 token 长度是否 ≥ 5 3. 在本地测试环境可临时修改 ApiAuthorizeAttribute 的最小长度为 04. 确认 [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 中禁用某些分组以加速加载 |
appsettings.json 中 LogPath 指定的目录(默认 logs/)Gs.NewModuleCLAUDE.md 中的 .csproj 模板)Gs.Entity 和 Gs.Toolbox Gs.NewModule/ ├── Models/ # 实体或 DTO ├── Services/ # 控制器类 └── Gs.NewModule.csproj IRomteService、继承 Repository<T>Gs.HostIIS/bin/Debug/,启动后自动被发现InterfaceUtil 发送 HTTP 请求appsettings.json 中配置第三方系统 URL请求 URL: http://localhost:5263/MesItemsManager/GetListPage
请求头: {"token": "token 3fa85f64-5717-4562-b3fc-2c963f66afa6"}
请求体: {
"currentPage": 1,
"everyPageSize": 10,
"sortName": "CreateTime",
"sortOrder": "desc",
"keyWord": "物料名称",
"keyType": "ItemName"
}
请求 URL: http://localhost:5263/SomeController/ExportItems
请求头: {"token": "token ..."}
响应体: {
"rtnCode": 1,
"rtnData": {
"filePath": "/download/items_20250101.xlsx"
},
"rtnMsg": "导出成功"
}
后续通过 http://localhost:5263/download/items_20250101.xlsx 下载文件
// 调用存储过程获取报表数据
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))
);
dotnet build 验证无编译警告.config/*.json)已准备