| WebApi/CLAUDE.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| WebApi/Gs.HostIIS/appsettings.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| WebApi/Gs.Sys/Services/FmController.cs | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| WebApi/Gs.Toolbox/InterfaceUtil.cs | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
WebApi/CLAUDE.md
@@ -4,92 +4,189 @@ ## Project Overview This is a .NET 8 MES (Manufacturing Execution System) solution built with ASP.NET Core Web API. The system is organized as a modular monolith with domain-separated projects for different business areas. This is a .NET 8 MES (Manufacturing Execution System) solution built with ASP.NET Core Web API. The system uses a custom API framework with modular architecture, organizing business functionality into domain-separated projects. ## Solution Structure The solution follows a modular architecture with these main projects: - **Gs.HostIIS**: Main Web API host application with Swagger documentation - **Gs.Entity**: Data entities organized by business domains (BaseInfo, QC, Sys, Warehouse) - **Gs.Toolbox**: Core utilities, API framework, and dependency injection infrastructure - **Business Modules**: - **Gs.BaseInfo**: Basic information management (items, customers, suppliers, staff, etc.) - **Gs.Warehouse**: Inventory and warehouse management - **GS.QC**: Quality control functionality - **Gs.HostIIS**: Main Web API host application (targets .NET 8.0) - **Gs.Toolbox**: Core framework with custom DI, controller discovery, and utilities (targets .NET 6.0) - **Gs.Entity**: Data entity models organized by domain (BaseInfo, QC, Sys, Warehouse) - **Business Modules** (all target .NET 8.0): - **Gs.BaseInfo**: Basic information (items, customers, suppliers, staff, departments) - **Gs.Warehouse**: Inventory and warehouse operations - **GS.QC**: Quality control and inspection - **Gs.Sys**: System administration and user management - **Gs.Report**: Reporting functionality - **Gs.Wom**: Work order management - **Gs.Sales**: Sales management - **Gs.Pda**: Mobile/handheld device support - **Gs.QiTaCk** / **Gs.QiTaRk**: Additional business modules (其它出库/其它入库) - **Gs.QiTaCk** / **Gs.QiTaRk**: Miscellaneous in/out operations (其它出库/其它入库) - **Gs.Ww**: Outsourcing management (委外) - **Gs.JJGZ**: Piece-rate wage management (计件工资) ## Development Commands ### Build and Run - Build entire solution: `dotnet build GsMesSolution.sln` - Run main API: `dotnet run --project Gs.HostIIS` - Build specific project: `dotnet build Gs.ProjectName/Gs.ProjectName.csproj` ```bash # Build entire solution dotnet build GsMesSolution.sln # Run main API (starts on http://localhost:5263 by default) dotnet run --project Gs.HostIIS # Build specific module dotnet build Gs.BaseInfo/Gs.BaseInfo.csproj ``` ### Development Server - The API runs on default ports (check launchSettings.json for specific ports) - Swagger UI is available at `/swagger` endpoint - CORS is configured to allow all origins for development - API runs on `http://localhost:5263` (Project profile) or `http://localhost:37005` (IIS Express) - Swagger UI: `http://localhost:5263/swagger` - CORS configured to allow all origins in development ## Architecture Details ### Custom API Framework The solution uses a custom API framework built in `Gs.Toolbox`: - Custom dependency injection with lifecycle attributes (`ITransient`, `IScope`, `ISingleton`) - Custom controller convention via `CustomApplicationModelConvention` - API grouping for Swagger documentation via `ApiGroupAttribute` - Custom authorization with `ApiAuthorizeAttribute` The solution uses a custom framework in `Gs.Toolbox` that replaces standard ASP.NET Core conventions: #### Controller Discovery - Controllers are discovered by implementing the `IRomteService` interface (not by inheriting `ControllerBase`) - `CustomControllerFeatureProvider` scans assemblies and the `Services` folder for classes implementing `IRomteService` - Controllers are automatically registered without `[ApiController]` or `[Route]` attributes - Business logic classes in `Services/` folders act as controllers #### Routing Convention - Routes automatically generated as `/{ControllerName}/{ActionName}` by `CustomApplicationModelConvention` - HTTP methods specified via `[RequestMethod(RequestMethods.POST)]` attribute (not standard `[HttpPost]`) - Parameter binding: non-primitive types automatically bound from request body for POST/PUT/PATCH #### Dependency Injection - Custom DI system using lifecycle marker interfaces: - `ITransient`: Transient lifetime (new instance per request) - `IScope`: Scoped lifetime (one instance per request) - `ISingleton`: Singleton lifetime (one instance for application) - Classes implementing these interfaces are auto-registered - Use `[Expose(typeof(IYourInterface))]` attribute to specify service interface explicitly - Auto-injection scans all referenced assemblies via `builder.AddCustomInject()` #### API Grouping - Swagger groups defined via `[ApiGroup(ApiGroupNames.BaseInfo)]` attribute on controller classes - Groups defined in `ApiGroupNames` enum (BaseInfo, QC, PerMission, WOM, ErpMes, etc.) - Each group generates separate Swagger document for organization ### Data Access - Uses SqlSugar ORM for database operations - Connection string configured in `appsettings.json` - Repository pattern implemented in `Gs.Toolbox/Repository.cs` ### Configuration - Database: SQL Server (connection string in appsettings.json) - External services: ERP integration endpoints configured - File paths: Services, logs, upload, and download paths configured - Custom JSON serialization with Newtonsoft.Json #### SqlSugar ORM - Base repository: `Repository<T>` in `Gs.Toolbox/Repository.cs` - Connection string: `appsettings.json` → `ConnectionStrings` key - Static `SqlSugarScope Db` instance provides database context - Business classes inherit from `Repository<TEntity>` for data access - Transaction support via `UseTransaction()` method ### Key Components - **CustomContractResolver**: Custom JSON property naming (converts to camelCase) - **ReturnDto<T>**: Standardized API response format with RtnCode, RtnData, RtnMsg - **PageList<T>** and **PageQuery**: Pagination support - **ExcelHelper**: Excel import/export functionality - **LogHelper**: Logging utilities - **DbHelperSQL**: Additional database utilities #### Pagination - Standard pagination using `PageQuery` (input) and `PageList<T>` (output) - `PageQuery` fields: `currentPage`, `everyPageSize`, `sortName`, `sortOrder`, `keyWord`, `keyWhere`, `keyType` - `CommonPage()` methods provide built-in pagination logic ### Framework Features - Custom controller discovery via `CustomControllerFeatureProvider` - Module system with `IModule` interface for extensibility - Dependency injection container with custom lifecycle management - API grouping system for organized Swagger documentation ### Response Format ## Business Domains All API responses use `ReturnDto<T>` wrapper: ```csharp { "rtnCode": 1, // ReturnCode enum: Success=1, Default=-100, Unauthorized=-101, Exception=-102 "rtnData": {...}, // Generic data payload "rtnMsg": "..." // Optional message } ``` The system handles typical MES functionality organized by API groups: - Base information (BaseInfo): Items, customers, suppliers, departments, staff - Quality control (QC): Inspection projects and quality management - Warehouse (PerMission): Inventory and warehouse operations - Work orders (WOM): Manufacturing execution - ERP integration (ErpMes): Data exchange with ERP systems - Reporting (Report/Rport): Various business reports - Mobile support (PDA): Handheld device operations - Additional modules: Sales (XS), Outsourcing (WW), etc. ### JSON Serialization - Uses Newtonsoft.Json with custom `CustomContractResolver` - Property naming: Converts to camelCase (first letter lowercase) - Handles underscore-separated names (e.g., `USER_NAME` → `userName`) - Date format: `"yyyy-MM-dd HH:mm:ss"` - Reference loop handling: Ignore ### Authorization - Custom `ApiAuthorizeAttribute` filter validates `token` header - Token format: `"token {guid}"` in request header - Use `[AllowAnonymous]` to bypass authorization - User context extraction via `UtilityHelper.GetUserGuidAndOrgGuid(IHttpContextAccessor)` ### Configuration (appsettings.json) - `ConnectionStrings`: SQL Server connection string - `TestErpUrl`, `TestErpUrl2`, `ProductionErpUrl`: ERP integration endpoints - `ServicesPath`: Path to compiled DLLs for controller discovery (default: "Services") - `LogPath`: Log file directory (default: "logs") - `UploadPath`: File upload directory (mapped to `/upload` endpoint) - `DownPath`: File download directory (mapped to `/down` endpoint) ### Project Output Configuration Business modules use custom output paths: - Compiled DLLs output to `Gs.HostIIS/bin/Debug/` (BaseOutputPath configured in .csproj) - XML documentation files generated automatically for Swagger - Framework scans `Services/` folder at runtime for controller DLLs ## Creating New Business Modules 1. **Create new class library project** targeting .NET 8.0 2. **Reference required projects**: ```xml <ProjectReference Include="..\Gs.Entity\Gs.Entity.csproj" /> <ProjectReference Include="..\Gs.Toolbox\Gs.Toolbox.csproj" /> ``` 3. **Configure output path** in .csproj: ```xml <BaseOutputPath>..\Gs.HostIIS\bin</BaseOutputPath> <OutputPath>..\Gs.HostIIS\bin\Debug\</OutputPath> <GenerateDocumentationFile>True</GenerateDocumentationFile> ``` 4. **Create Services folder** for controller classes 5. **Implement controller** by inheriting `Repository<TEntity>` and `IRomteService`: ```csharp [ApiGroup(ApiGroupNames.YourGroup)] public class YourManager : Repository<YourEntity>, IRomteService { [RequestMethod(RequestMethods.POST)] public ReturnDto<PageList<YourEntity>> GetListPage(PageQuery query) { ... } } ``` 6. **Add to solution** and build (DLL auto-discovered at runtime) ## Common Patterns ### Creating API Endpoints ```csharp [ApiGroup(ApiGroupNames.BaseInfo)] public class MesItemsManager : Repository<MesItems>, IRomteService { private readonly IHttpContextAccessor _http; public MesItemsManager(IHttpContextAccessor httpContextAccessor) { _http = httpContextAccessor; } [RequestMethod(RequestMethods.POST)] public ReturnDto<PageList<MesItems>> GetListPage(PageQuery query) { // Business logic using Db property from Repository<T> } } ``` ### Using Transactions ```csharp var affectedRows = UseTransaction(db => { db.Insertable(entity).ExecuteCommand(); db.Updateable(otherEntity).ExecuteCommand(); return db.Ado.AffectedRows; }); ``` ## Development Notes - All projects target .NET 8.0 (except Gs.Toolbox which targets .NET 6.0) - Nullable reference types are enabled - The system uses a Chinese interface (comments and some naming in Chinese) - Custom API framework provides dependency injection and controller conventions - Swagger documentation is automatically generated with XML comments from Services folder - Response format follows ReturnDto pattern with standardized error codes - File upload/download paths are configured and served as static files - The system uses Chinese for business domain comments and naming - Gs.Toolbox targets .NET 6.0 for compatibility; all other projects target .NET 8.0 - Controllers are discovered at runtime from `Services/` folder - no need to manually register - Swagger automatically includes all controllers with their XML documentation - Static files (uploads/downloads) served from paths configured in appsettings.json WebApi/Gs.HostIIS/appsettings.json
@@ -6,11 +6,10 @@ } }, "AllowedHosts": "*", /*"ConnectionStrings": "Data Source=192.168.1.146;Initial Catalog=TEST_MES;User ID=testUser;Password =qixi1qaz@WSXtest",*/ "ConnectionStrings": "Data Source=192.168.0.51;Initial Catalog=TEST_MES;User ID=sa;Password =LanBao@2025;Encrypt=True;TrustServerCertificate=True;", "TestErpUrl": "http://192.168.1.149:8066/WebService1.asmx/MesToErpinfoTest", "TestErpUrl2": "http://192.168.1.149:8066/WebService1.asmx/MesToErpUpdateFlag", "ProductionErpUrl": "http://192.168.1.149:8066/WebService1.asmx/mesToErpinfoFormal", "TestErpUrl": "http://192.168.0.52:8055/", "TestErpUrl2": "http://192.168.0.52:8055/", "ProductionErpUrl": "http://192.168.0.52:8055/", "ServicesPath": "Services", "LogPath": "logs", "UploadPath": "upload", WebApi/Gs.Sys/Services/FmController.cs
@@ -28,25 +28,29 @@ GetUserGuidAndOrgGuid(_http); } #region 版面 #region 布局配置 /// <summary> /// 增加 /// 保存或清空表单布局 /// </summary> /// <param name="model"></param> /// <returns></returns> /// <remarks>Saves layouts: intType 1=standard save, 2=personal save, 3=clear standard, 4=clear personal.</remarks> [RequestMethod(RequestMethods.POST)] public ReturnDto<ExpandoObject> EditModel([FromBody] dynamic model) { string applyUserGuid = ""; string formPath = model.formPath; int intType = model.intType; // intType: 1 = save standard layout, 2 = save personal layout, 3 = clear standard layout, 4 = clear personal layout. dynamic m = new ExpandoObject(); m.outMsg = ""; // Gather batched SQL statements so they can be executed transactionally when saving layouts. Hashtable SQLStringList = new Hashtable(); string _groupGuid = Guid.NewGuid().ToString(); //只有超级管理员权限 if (intType == 1 || intType == 3) { // Validate the current user has administrator rights when touching standard layouts. int? isAdmin = 0; try { @@ -65,6 +69,7 @@ //保存标准版 if (intType == 1) { // Persist a new standard layout definition shared by all users. applyUserGuid = null; Gs.Toolbox.DbHelperSQL.ExecuteSql("delete from [FM_LAYOUT] where groupGuid<>'" + _groupGuid + "' and [formPath]=@formPath and applyUserGuid is null", new SqlParameter[] { new SqlParameter("@formPath", formPath) }); // SQLStringList.Add("delete from [FM_LAYOUT] where groupGuid<>'" + _groupGuid + "' and [formPath]=@formPath and applyUserGuid is null", new SqlParameter[] { new SqlParameter("@formPath", formPath) }); @@ -72,6 +77,7 @@ //保存个人版本 if (intType == 2) { // Persist the caller's personal layout copy scoped to their user GUID. applyUserGuid = _userGuid; Gs.Toolbox.DbHelperSQL.ExecuteSql("delete from [FM_LAYOUT] where groupGuid<>'" + _groupGuid + "' and [formPath]=@formPath and applyUserGuid =@applyUserGuid", new SqlParameter[] { new SqlParameter("@formPath", formPath), new SqlParameter("@applyUserGuid", applyUserGuid) }); //SQLStringList.Add("delete from [FM_LAYOUT] where groupGuid<>'" + _groupGuid + "' and [formPath]=@formPath and applyUserGuid =@applyUserGuid", new SqlParameter[] { new SqlParameter("@formPath", formPath), new SqlParameter("@applyUserGuid", applyUserGuid) }); @@ -79,6 +85,7 @@ //清空标准版本 if (intType == 3) { // Administrators can wipe the shared standard layout entirely. applyUserGuid = null; SQLStringList.Add("delete from [FM_LAYOUT] where [formPath]=@formPath and applyUserGuid is null", new SqlParameter[] { new SqlParameter("@formPath", formPath) }); Gs.Toolbox.DbHelperSQL.ExecuteSqlTranRtn(SQLStringList); @@ -88,6 +95,7 @@ //清空个人版本 if (intType == 4) { // Remove the caller's personal layout while keeping the shared standard version intact. applyUserGuid = _userGuid; SQLStringList.Add("delete from [FM_LAYOUT] where [formPath]=@formPath and applyUserGuid =@applyUserGuid", new SqlParameter[] { new SqlParameter("@formPath", formPath), new SqlParameter("@applyUserGuid", applyUserGuid) }); Gs.Toolbox.DbHelperSQL.ExecuteSqlTranRtn(SQLStringList); @@ -103,6 +111,7 @@ JObject job = (JObject)jsonitem; if (job["idName"] != null) { // Compose an insert statement for each UI control (grid, layout panel, splitter, etc.). string idName = job["idName"].ToString(); string idXml = job["idXml"].ToString(); string idType = job["idType"].ToString(); @@ -127,6 +136,7 @@ } catch (Exception ex) { // 捕获保存查询配置时的异常,并将信息返回给前端。 m.outMsg = "操作失败:" + ex.Message; Gs.Toolbox.LogHelper.Debug(this.ToString(), "EditModel error:" + ex.Message); } @@ -140,6 +150,7 @@ /// </summary> /// <param name="guid"></param> /// <returns></returns> /// <remarks>Combines the shared layout (list) with the current user's override (list2).</remarks> [RequestMethod(RequestMethods.POST)] public ReturnDto<ExpandoObject> GetModel([FromBody] dynamic model) { @@ -155,10 +166,12 @@ var dset = new DataSet(); try { // Stored procedure returns both standard layout data and any personal override for the current user. dset = DbHelperSQL.RunProcedure("[fm_get_layout]", parameters, "0"); if (dset != null && dset.Tables.Count > 0 ) { // Table[0] represents the standard definition; table[1] holds the user's personal layout snapshot. var _tb = dset.Tables[0].TableToDynamicList(); m.list = _tb; var _tb2 = dset.Tables[1].TableToDynamicList(); @@ -167,6 +180,7 @@ } catch (Exception ex) { // Log retrieval failure but continue returning default result to caller. LogHelper.Debug(ToString(), ex.Message); } if (m != null) @@ -182,6 +196,7 @@ /// </summary> /// <param name="guid"></param> /// <returns></returns> /// <remarks>Retrieves the serialized layout string for the latest saved version (standard or personal).</remarks> [RequestMethod(RequestMethods.POST)] public ReturnDto<string> GetModelByVersion([FromBody] dynamic model) { @@ -195,6 +210,7 @@ var dset = new DataSet(); try { // Stored procedure exposes the latest serialized layout snapshot based on formPath and user scope. dset = DbHelperSQL.RunProcedure("[fm_get_layout_ver]", parameters, "0"); if (dset != null && dset.Tables.Count > 0 ) @@ -204,6 +220,7 @@ } catch (Exception ex) { // Capture context when reading layout versions fails to help diagnose environment-specific issues. LogHelper.Debug(ToString(), ex.Message+ ",formPath:"+ formPath+ ",_userGuid:"+ _userGuid); } @@ -217,6 +234,7 @@ { int? isAdmin = 0; System.Text.StringBuilder _sb = new System.Text.StringBuilder(); // Uses SYS_USER.IS_SYS flag to decide if the caller has elevated privileges. _sb.Append("select count(1) from [dbo].[SYS_USER] where GUID='" + _userGuid + "' and IS_SYS=1"); object _obj = Gs.Toolbox.DbHelperSQL.GetSingle(_sb.ToString()); if (_obj == null) @@ -235,6 +253,7 @@ /// </summary> /// <param name="model">keyType:1审核,0反审核</param> /// <returns></returns> /// <remarks>Packages MES data into ERP payloads and posts them according to the requested operation.</remarks> [RequestMethod(RequestMethods.POST)] public string SendErp([FromBody] dynamic model) { @@ -245,6 +264,7 @@ //string keyChild = model.keyChild;任务子节点名 //string keyMeth = model.keyMeth;方法名 //string keyNo = model.keyNo;单据编号 //string keyUrl = model.keyUrl;接口地址 int _rtnInt = 0; string _rtnStr = ""; try @@ -256,13 +276,21 @@ string keyGuid = model.keyGuid; string keyNo = model.keyNo; string idtype = model.idtype;//这个仅仅是更新工单状态的时候有 string keyUrl = model.keyUrl; if (string.IsNullOrEmpty(idtype)) (_rtnInt, _rtnStr) = InterfaceUtil.HttpPostErp(_erpJson, keyUserGuid, keyGuid, keyNo); { // 常规接口:按操作类型推送单条业务数据。 (_rtnInt, _rtnStr) = InterfaceUtil.HttpPostErp(_erpJson, keyUserGuid, keyGuid, keyNo,0,keyUrl); } else (_rtnInt, _rtnStr) = InterfaceUtil.HttpPostErp(_erpJson, keyUserGuid, keyGuid, keyNo, 2); { // 带 idtype 的请求用于特殊流程(如关闭、反关闭),ERP 需要额外的状态标记。 (_rtnInt, _rtnStr) = InterfaceUtil.HttpPostErp(_erpJson, keyUserGuid, keyGuid, keyNo, 2,keyUrl); } } catch (Exception ex) { // 记录 ERP 数据转换异常,便于定位存储过程或序列化问题。 Gs.Toolbox.LogHelper.Debug(this.ToString(), "Fm SendErp:" + ex.Message); return "发送erp失败:" + ex.Message; } @@ -289,6 +317,7 @@ string keyNo = model.keyNo; string idtype = model.idtype;//这个仅仅是更新工单状态的时候有 if (keyMeth.ToUpper() == "delete".ToUpper()) // 删除操作无需向 ERP 推送数据,只需返回空串。 return ""; try { @@ -299,6 +328,7 @@ new("@inEdtUserGuid", keyUserGuid), new("@keyMeth", keyMeth.ToLower()), }; // 调用业务定义的存储过程,将 MES 数据打包给 ERP。 dset = DbHelperSQL.RunProcedure(keyProduce, parameters, "0"); if (dset == null) return ""; @@ -309,10 +339,12 @@ //这是普通的接口 if (string.IsNullOrEmpty(idtype)) { // 常规出参:第一张表是主数据,第二张表(若存在)是子表集合。 string _mesGuid = dset.Tables[0].Rows[0][0].ToString(); dynamic _datajson = new ExpandoObject(); if (dset.Tables.Count > 1) { // 多表返回时,需要把子表集合挂到 datajson 中。 //这是这是普通的接口里的结案,结构和其它不一样 if (keyMeth.ToLower() == "toclose".ToLower() || keyMeth.ToLower() == "closure".ToLower() || keyMeth.ToLower() == "unfinish") { @@ -325,30 +357,33 @@ ((IDictionary<string, object>)_datajson)[keyChild] = _lst; } } var _obj = new { mesid = _mesGuid, taskname = keyTaskName, optype = keyMeth, datajson = JsonConvert.SerializeObject(_datajson), }; return JsonConvert.SerializeObject(_obj); // var _obj = new // { // mesid = _mesGuid, // taskname = keyTaskName, // optype = keyMeth, // datajson = JsonConvert.SerializeObject(_datajson), // }; // return JsonConvert.SerializeObject(_obj); return JsonConvert.SerializeObject(_datajson); } //这是订单回传标识 List<dynamic> _datajson22 = new List<dynamic>(); dynamic _ob = new ExpandoObject(); _ob.ENTRY = dset.Tables[0].TableToDynamicList(); _datajson22.Add(_ob); var _obj22 = new { taskname = keyTaskName, idtype = idtype, datajson = JsonConvert.SerializeObject(_datajson22), }; return JsonConvert.SerializeObject(_obj22); // var _obj22 = new // { // taskname = keyTaskName, // idtype = idtype, // datajson = JsonConvert.SerializeObject(_datajson22), // }; // return JsonConvert.SerializeObject(_obj22); return JsonConvert.SerializeObject(_datajson22); } catch (Exception ex) { // 记录 ERP 数据转换异常,便于定位存储过程或序列化问题。 Gs.Toolbox.LogHelper.Debug(this.ToString(), ex.Message); throw ex; } @@ -386,6 +421,7 @@ } ; var lst = new List<dynamic>(); // 将列名和显示标题拼成 "~" 分隔的参数,传给存储过程生成查询配置。 SqlParameter[] parameters = { new("@formPath", formPath), @@ -394,6 +430,7 @@ var dset = new DataSet(); try { // fm_set_query 会返回查询条件、结果字段、排序等多张配置表。 dset = DbHelperSQL.RunProcedure("[fm_set_query]", parameters, "0"); if (dset != null && dset.Tables.Count > 0) { @@ -405,6 +442,7 @@ } catch (Exception ex) { // 记录 ERP 数据转换异常,便于定位存储过程或序列化问题。 LogHelper.Debug(ToString(), ex.Message); } return ReturnDto<dynamic>.QuickReturn(m, ReturnCode.Success, "读取成功!"); @@ -429,16 +467,19 @@ isAdmin = chkAdmin(); if (isAdmin <= 0) { // Query configuration is restricted to administrators to protect shared metadata. m.outMsg = "你不是管理员,操作失败!"; return ReturnDto<dynamic>.QuickReturn(m, ReturnCode.Default, "操作成功!"); } } catch (Exception ex) { // 记录 ERP 数据转换异常,便于定位存储过程或序列化问题。 Gs.Toolbox.LogHelper.Debug(this.ToString(), "EditModel isAdmin error:" + ex.Message); } try { // 先清空原有查询来源表,再批量插入最新配置。 Gs.Toolbox.DbHelperSQL.ExecuteSql("delete from [FM_QUERY_TABLE] where formPath=@formPath ", new SqlParameter[] { new SqlParameter("@formPath", formPath) }); foreach (var _obj in model.list) { @@ -451,6 +492,7 @@ } catch (Exception ex) { // 捕获保存查询配置时的异常,并将信息返回给前端。 m.outMsg = ex.Message; return ReturnDto<dynamic>.QuickReturn(m, ReturnCode.Default, ex.Message); } @@ -473,15 +515,18 @@ isAdmin = chkAdmin(); if (isAdmin <= 0) { // 删除查询配置同样需要管理员权限。 return ReturnDto<int>.QuickReturn(rtnInt, ReturnCode.Default, "你不是管理员,操作失败!"); } } catch (Exception ex) { // 记录 ERP 数据转换异常,便于定位存储过程或序列化问题。 Gs.Toolbox.LogHelper.Debug(this.ToString(), "EditModel isAdmin error:" + ex.Message); } Guid? guid = model.guid; System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder(); // 采用 GUID 精确删除指定的查询数据源记录。 stringBuilder.Append("delete from FM_QUERY_TABLE where guid='" + guid + "'"); rtnInt = Gs.Toolbox.DbHelperSQL.ExecuteSql(stringBuilder.ToString()); if (rtnInt <= 0) @@ -504,11 +549,13 @@ isAdmin = chkAdmin(); if (isAdmin <= 0) { // 只有管理员才能调整查询列字段映射。 return ReturnDto<int>.QuickReturn(rtnInt, ReturnCode.Default, "你不是管理员,操作失败!"); } } catch (Exception ex) { // 记录 ERP 数据转换异常,便于定位存储过程或序列化问题。 Gs.Toolbox.LogHelper.Debug(this.ToString(), "EditModel isAdmin error:" + ex.Message); } Guid? guid = model.guid; WebApi/Gs.Toolbox/InterfaceUtil.cs
@@ -18,16 +18,18 @@ /// <param name="hNo"></param> /// <param name="urlType">如果为2,则是更新工单状态</param> /// <returns>如果成功返回日志guid,否则返回串</returns> public static (int, string) HttpPostErp(string param, string edtUserGuid = "", string abtGuid = "", string hNo = "", int urlType = 0) public static (int, string) HttpPostErp(string param, string edtUserGuid = "", string abtGuid = "", string hNo = "", int urlType = 0, string keyUrl = "") { int _rtn = 0; //日志详细,发送的时候,记录日志,存储过程调用的时候,再累加上mes业务的操作结果 System.Text.StringBuilder sbLog = new System.Text.StringBuilder(); sbLog.Append(DateTime.Now.ToString() + "开始发送"); string strLogGuid = Guid.NewGuid().ToString(); string url = AppSettingsHelper.getValueByKey("TestErpUrl"); string url = AppSettingsHelper.getValueByKey("TestErpUrl") + keyUrl; if (urlType == 2) url = AppSettingsHelper.getValueByKey("TestErpUrl2"); url = AppSettingsHelper.getValueByKey("TestErpUrl2") + keyUrl; HttpWebRequest request = null; StreamWriter requestStream = null; WebResponse response = null; @@ -41,7 +43,9 @@ request.Timeout = 150000; request.AllowAutoRedirect = false; request.ServicePoint.Expect100Continue = false; HttpRequestCachePolicy noCachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore); HttpRequestCachePolicy noCachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel .NoCacheNoStore); request.CachePolicy = noCachePolicy; requestStream = new StreamWriter(request.GetRequestStream()); requestStream.Write(param); @@ -57,7 +61,8 @@ } catch (Exception ex) { LogHelper.Debug(url, "HttpPostErp response:" + param + ",ex:" + ex.Message); LogHelper.Debug(url, "HttpPostErp response:" + param + ",ex:" + ex.Message); responseStr = ex.Message; _rtn = -1; } @@ -67,12 +72,14 @@ requestStream = null; response = null; } if (_rtn != -1) { Result _result = JsonConvert.DeserializeObject<Result>(responseStr); if ("200".Equals(_result.state)) _rtn = 1; } sbLog.Append("," + DateTime.Now.ToString() + "结束发送"); if (_rtn > 0) sbLog.Append(",发送成功"); @@ -81,24 +88,25 @@ try { SqlParameter[] parameters = { new("@edtUserGuid", edtUserGuid), new("@abtGuid", abtGuid), new("@abtTable", ""), new("@detail", sbLog.ToString()), new("@hNo", hNo), new("@RtnLogGuid", strLogGuid), new("@SendJson", param), new("@RtnJson", responseStr), new("@isSuccess", (_rtn>0?1:0)), new("@isErp", 1), }; { new("@edtUserGuid", edtUserGuid), new("@abtGuid", abtGuid), new("@abtTable", ""), new("@detail", sbLog.ToString()), new("@hNo", hNo), new("@RtnLogGuid", strLogGuid), new("@SendJson", param), new("@RtnJson", responseStr), new("@isSuccess", (_rtn > 0 ? 1 : 0)), new("@isErp", 1), }; DbHelperSQL.RunProcedure("[prc_log_create]", parameters); } catch (Exception ex) { LogHelper.Debug(url, "HttpPostErp 写入日志表" + ex.Message); } return (_rtn, (_rtn > 0 ? strLogGuid : responseStr)); } } @@ -109,6 +117,7 @@ /// 200成功,否则失败 /// </summary> public string? state { get; set; } public string? msg { get; set; } public string? status { get; set; }