# SaveMattressExcutor.cs 方法调用分析 本文档详细分析了 `SaveMattressExcutor.cs` 文件中每一个嵌套方法调用的功能和实现细节。 --- ## 1. BllHelper.GetToken(string token) **位置**: `BLL/BllHelper.cs:36-46` **功能**: 从内存缓存中获取用户token对应的会话信息 **实现细节**: ```csharp public static TokenData GetToken(string token) { if (_tokens.ContainsKey(token)) { return _tokens[token]; } else { return null; } } ``` **关键点**: - 使用 `LJCache` 缓存,默认过期时间120分钟 - Token在用户登录时通过 `SetToken()` 方法绑定 - 返回的 `TokenData` 包含用户ID、用户名、员工ID等信息 - 如果token不存在或已过期,返回null **使用场景**: 验证用户会话是否有效,防止未授权访问 --- ## 2. HelperBase.GetHelper(SqlCommand cmd, Context context) **位置**: `BLL/HelperBase.cs:32-38` **功能**: 泛型工厂方法,创建并初始化Helper实例 **实现细节**: ```csharp public static T GetHelper(SqlCommand cmd, Context context = null) where T : HelperBase, new() { var rslt = new T(); rslt.cmd = cmd; rslt.context = context ?? new Context(); return rslt; } ``` **关键点**: - 泛型约束:T必须继承自 `HelperBase` 且有无参构造函数 - 注入数据库命令对象和上下文信息 - Context包含操作时间和token数据 **使用场景**: - 获取 `MattressHelper` 实例用于床垫业务逻辑 - 获取 `BedNetHelper` 实例用于床网业务逻辑 --- ## 3. ObjectHelper.DeepCopy(T obj) **位置**: `Tools/ObjectHelper.cs:14-17` **功能**: 对对象进行深度复制,创建完全独立的副本 **实现原理**: ```csharp public static T DeepCopy(T obj) { return Copy(obj, true); } private static T Copy(T obj, bool deep = false) { // 处理值类型和字符串 if (obj == null || obj.GetType().IsValueType || obj is string) { return obj; } // 创建新实例 object retval = Activator.CreateInstance(obj.GetType()); // 处理字典类型 if (obj is IDictionary) { /* 复制字典项 */ } // 处理列表类型 else if (obj is IList) { /* 复制列表项 */ } // 处理普通对象 else { // 使用反射获取所有字段(包括私有字段) var fields = obj.GetType().GetFields( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static ).ToList(); // 递归复制基类字段 var type = obj.GetType(); while ((type = type.BaseType) != typeof(object)) { fields.AddRange(type.GetFields( BindingFlags.NonPublic | BindingFlags.Instance )); } // 深度复制每个字段 foreach (FieldInfo field in fields) { if (field.IsLiteral) continue; // 跳过常量 var value = field.GetValue(obj); if (deep) { value = Copy(value, true); // 递归深度复制 } field.SetValue(retval, value); } } return (T)retval; } ``` **关键特性**: - **递归复制**: 对嵌套对象也进行深度复制 - **完整复制**: 包括私有字段和基类字段 - **类型保持**: 保持原始对象的类型结构 - **性能考虑**: 使用反射,有一定性能开销 **使用场景**: 创建床垫数据的独立副本,避免修改原始请求数据 --- ## 4. AutoInit.AutoInitS(SqlCommand cmd, T instance) **位置**: `Tools/AutoInit.cs:21-104` **功能**: 根据数据库表结构自动初始化对象的非空属性 **实现机制**: ### 4.1 缓存机制 ```csharp private static readonly ConcurrentDictionary ColumnInfoCache = new ConcurrentDictionary(); private static readonly TimeSpan CacheExpirationTime = TimeSpan.FromMinutes(60); ``` ### 4.2 主逻辑 ```csharp public static void AutoInitS(SqlCommand cmd, T instance) { var tableName = instance.GetType().Name; var currentTime = DateTime.Now; // 检查缓存 if (ColumnInfoCache.TryGetValue(tableName, out var cache) && currentTime - cache.CacheTime < CacheExpirationTime) { // 使用缓存的列信息 foreach (var item in cache.ColumnInfo) { var column = item.Value.Column; var isNullable = item.Value.IsNullable; var property = instance.GetType().GetProperties() .FirstOrDefault(prop => prop.Name.Equals(column, StringComparison.OrdinalIgnoreCase)); if (property == null) continue; // 初始化非空且值为null的属性 if (isNullable.Equals("NO") && property.GetValue(instance) == null) { var type = property.PropertyType; // 处理Nullable类型 if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { type = type.GetGenericArguments()[0]; } // 根据类型设置默认值 if (type.IsAssignableFrom(typeof(string))) { property.SetValue(instance, ""); } else if (type.IsAssignableFrom(typeof(int)) || type.IsAssignableFrom(typeof(decimal)) || type.IsAssignableFrom(typeof(byte))) { property.SetValue(instance, Activator.CreateInstance(type)); } } } } else { // 缓存过期,从数据库重新加载列信息 var columnInfo = new Dictionary(); cmd.CommandText = @"SELECT COLUMN_NAME, IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @tableName"; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@tableName", instance.GetType().Name); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { var column = Convert.ToString(reader["COLUMN_NAME"]).Trim(); var isNullable = Convert.ToString(reader["IS_NULLABLE"]).Trim(); columnInfo[column] = new ColumnItem(column, isNullable); } } // 更新缓存 ColumnInfoCache[tableName] = new CacheItem { CacheTime = currentTime, ColumnInfo = columnInfo }; // 使用新加载的列信息初始化属性(逻辑同上) } } ``` **关键特性**: - **性能优化**: 使用60分钟缓存避免频繁查询数据库 - **智能匹配**: 属性名不区分大小写匹配 - **类型安全**: 正确处理Nullable类型 - **默认值策略**: - string → 空字符串 - int/decimal/byte → 0 **使用场景**: - 初始化床垫主表对象 - 初始化床垫明细列表中的每个对象 --- ## 5. MattressHelper.GetMattress(int mattressid, string fields) **位置**: `Helper/MattressHelper.cs:1667` **功能**: 从数据库获取床垫报价单数据 **实现细节**: ```csharp public u_mattress GetMattress(int mattressid, string fields = null) { if(mattressid <= 0) { throw new LJCommonException("查找床垫报价失败,ID错误!"); } var mattress = new u_mattress() { mattressid = mattressid }; // 默认字段列表(非常长,包含所有业务字段) fields = fields ?? @"mattressid,mattressname,deptid,mattresstypeid, mattresscode,mattress_width,mattress_length, mattress_height,packtype,packqty,woodpallettype, old_mtrlname,total_hr_cost,total_material_cost, fees_dscrp,total_fees_cost,taxrate,commissionrate, commission,fob,profitrate,profitrate_point, butao_point,chaizhuang_point,haimian_point, dannum_rate,hrcost,biandaicost,zhizao_amt, guanli_rate,discount,extras_cost,dept_profitrate, dept_profitrate_rangli,moneyrate,mattressrelcode, flag,auditingrep,auditingdate,createtime,createby, total_cost,nottax_factory_cost,nottax_dept_cost, taxes,dept_cost,foreign_cost,foreign_cost_bz, xd_flag,xd_auditingrep,xd_auditingdate,qr_flag, qr_auditingrep,qr_auditingdate,js1_flag, erp_mtrlcode,erp_cb_updatetime,if_bcp_type, if_zhedie_type,if_w_butao,biandai_qty,other_rate, if_moneyrate,parentid,..."; DbSqlHelper.SelectOne(cmd, mattress, fields); return mattress; } ``` **关键点**: - 参数校验:mattressid必须大于0 - 字段选择:支持自定义字段列表,默认获取所有业务字段 - 状态检查:用于验证床垫是否已审核或已下单 - 异常处理:ID错误时抛出业务异常 **使用场景**: - 检查床垫报价单状态(是否已审核、已下单) - 获取现有床垫数据进行修改 --- ## 6. MattressHelper.MattressCalculateCost(...) **位置**: `Helper/MattressHelper.cs:2174` **功能**: 计算床垫成本和价格,包括多种单据类型的价格 **核心逻辑**: ### 6.1 多单据类型计算 ```csharp public void MattressCalculateCost(u_mattress mattress, List mattressMx, List extraProcesses, List extraCosts) { // 默认只保存标准的单据 mattress.dannum_type = 2; // 作为记录不含税出厂价、部门含税价的各个大小单价格的载体 var _mattressMain = ObjectHelper.DeepCopy(mattress); List sumMattress = new List(); // 循环计算4种单据类型的价格 for (var i = 1; i < 5; i++) { var _mattress = ObjectHelper.DeepCopy(mattress); if (mattress.dannum_type.Value != i) { _mattress.dannum_type = i; // 调用公式计算 CalCulateFormula(_mattress, mattressMx, true, true, extraProcesses, extraCosts); // 提取关键价格 var nottax_factory_cost = Replacements .Where(o => o.label == "【不含税出厂价】").ToList(); var dept_cost = Replacements .Where(o => o.label == "【部门含税价】").ToList(); if (nottax_factory_cost.Count > 0) { decimal nottax_value = Convert.ToDecimal( nottax_factory_cost[0].value); // 分别记录4种单据类型的不含税价 if (i == 1) _mattress.dijia_cost1 = nottax_value; else if (i == 2) _mattress.dijia_cost2 = nottax_value; else if (i == 3) _mattress.dijia_cost3 = nottax_value; else if (i == 4) _mattress.dijia_cost4 = nottax_value; } // 同样记录4种单据类型的含税价 if (dept_cost.Count > 0) { decimal dept_costValue = Convert.ToDecimal( dept_cost[0].value); if (i == 1) _mattress.dannum_cost1 = dept_costValue; else if (i == 2) _mattress.dannum_cost2 = dept_costValue; else if (i == 3) _mattress.dannum_cost3 = dept_costValue; else if (i == 4) _mattress.dannum_cost4 = dept_costValue; } sumMattress.Add(_mattress); } } // 计算当前单据类型的价格 CalCulateFormula(mattress, mattressMx, true, true, extraProcesses, extraCosts); // 将其他单据类型的价格合并到主对象 for (var i = 1; i < 5; i++) { if (mattress.dannum_type.Value == i) { // 当前类型直接提取 // (代码逻辑同上) } else { // 其他类型从sumMattress中获取 var matchMattress = sumMattress .Where(o => o.dannum_type == i).ToList(); if (matchMattress.Count > 0) { // 合并价格信息 } } } } ``` **关键特性**: - **4种单据类型**: 小单、中单、大单、特大单 - **价格双轨制**: - 不含税出厂价 (dijia_cost1-4) - 部门含税价 (dannum_cost1-4) - **深度复制**: 每种类型独立计算,互不影响 - **公式计算**: 底层调用 `CalCulateFormula()` 方法 **使用场景**: - 保存前计算床垫成本 - 生成多种规格的价格供客户选择 --- ## 7. MattressHelper.SaveMattressPro(u_mattress mattress, ...) **位置**: `Helper/MattressHelper.cs:2532` **功能**: 保存床垫报价单主表和明细数据到数据库 **核心流程**: ### 7.1 数据校验 ```csharp SaveMattressCheck(mattress); ``` ### 7.2 字段定义 ```csharp var fields = @"mattressname, deptid, mattresscode, mattresstypeid, mattress_width, mattress_length, mattress_height, packtype, packqty, woodpallettype, total_hr_cost, total_material_cost, fees_dscrp, total_fees_cost, total_cost, taxrate, taxes, commissionrate, commission, fob, profitrate, dept_profitrate, moneyrate, nottax_factory_cost, nottax_dept_cost, foreign_cost, diameter, area, cabinet_type, hrcost, biandaicost, mattressrelcode, other_rate, flag, dept_profitrate_rangli, profitrate_point, if_moneyrate, discount, if_m_chai, if_z_chai, if_d_chai, if_n_butao, if_w_butao, if_m_wbutao_way, s_cover_qty, z_cover_qty, x_cover_qty, biandai_qty, s_m_cover_qty, z_m_cover_qty, x_m_cover_qty, chaizhuang_point, haimian_point, if_zhedie_type, qr_auditingrep, qr_auditingdate, if_bcp_type, zhizao_amt, foreign_cost_bz, cubage, extras_cost, extras_cost_dscrp, parentid, flag, xd_flag, dannum_type, dannum_cost1,dannum_cost2, dannum_cost3, dannum_cost4, dijia_cost1,dijia_cost2,dijia_cost3,dijia_cost4, version,total_mtrl_hr_cost"; var fieldsMx = "mattressmxid,mattressid,formulaid,formula,replace_formula, if_success,priceunit,shrinkage,mtrlid,price,gram_weight, cloth_width,if_inputqty,qty,costamt,if_areaprice,thickness, chastr,xu,useqty,useformula,replace_useformula,gydscrp, mattress_width,mattress_length,sidecover"; var fieldsExtra = "mattressmxid,mattressid, extraid, extramxid, extraname, price, qty, dscrp, mtrlid"; ``` ### 7.3 新建/修改逻辑 ```csharp mattress.qr_auditingdate = context.opdate; mattress.qr_auditingrep = context.tokendata.username; if (mattress.mattressid <= 0) { // 新建床垫 AutoInit.AutoInitS(mattress); mattress.createtime = context.opdate; if (string.IsNullOrEmpty(mattress.createby)) mattress.createby = context.tokendata.username; // 获取新ID mattress.mattressid = BllHelper.GetID(cmd, "u_mattress"); mattress.version = 1; fields += ",mattressid, createtime, createby"; // 自动生成床垫编码 if (string.IsNullOrEmpty(mattress.mattresscode)) { var mattresstype = new u_mattress_type() { mattresstypeid = mattress.mattresstypeid }; DbSqlHelper.SelectOne(cmd, mattresstype, "typecode"); mattress.mattresscode = $"{mattresstype.typecode}-" + $"{context.opdate.ToString("yyyyMMdd")}" + $"{(mattress.mattressid % 10000).ToString("D4")}"; } DbSqlHelper.Insert(cmd, "u_mattress", null, mattress, fields); } else { // 修改床垫 AutoInit.AutoInitS(mattress); mattress.version++; DbSqlHelper.Update(cmd, "u_mattress", null, mattress, "mattressid", fields); // 删除所有明细 cmd.CommandText = @"DELETE u_mattress_mx_mtrl WITH (ROWLOCK) WHERE mattressid = @mattressid"; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@mattressid", mattress.mattressid); cmd.ExecuteNonQuery(); // 删除所有额外费用明细 cmd.CommandText = @"DELETE u_mattress_mx_extra WITH (ROWLOCK) WHERE mattressid = @mattressid"; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@mattressid", mattress.mattressid); cmd.ExecuteNonQuery(); } ``` ### 7.4 保存明细 ```csharp // 保存材料明细 if (mattress.mxList != null && mattress.mxList.Count > 0) { foreach (var item in mattress.mxList) { AutoInit.AutoInitS(cmd, item); item.mattressid = mattress.mattressid; if (item.mattressmxid == null || item.mattressmxid <= 0) { item.mattressmxid = BllHelper.GetID(cmd, "u_mattress_mx_mtrl"); } DbSqlHelper.Insert(cmd, "u_mattress_mx_mtr l", null, item, fieldsMx); } } // 保存特殊工艺 if (mattress.extraList != null && mattress.extraList.Count > 0) { foreach (var item in mattress.extraList) { AutoInit.AutoInitS(cmd, item); item.mattressid = mattress.mattressid; item.mattressmxid = BllHelper.GetID(cmd, "u_mattress_mx_extra"); DbSqlHelper.Insert(cmd, "u_mattress_mx_extra", null, item, fieldsExtra); } } // 保存特殊费用 if (mattress.extraCostList != null && mattress.extraCostList.Count > 0) { foreach (var item in mattress.extraCostList) { AutoInit.AutoInitS(cmd, item); item.mattressid = mattress.mattressid; item.mattressmxid = BllHelper.GetID(cmd, "u_mattress_mx_extra"); DbSqlHelper.Insert(cmd, "u_mattress_mx_extra", null, item, fieldsExtra); } } ``` ### 7.5 保存价格历史 ```csharp var hisprice = new u_his_price { bednetid_mattressid = mattress.mattressid, typeid = 1, cmpdate = context.opdate, cmpemp = context.tokendata.username, nottax_dept_cost = mattress.nottax_dept_cost, dept_cost = mattress.dept_cost, foreign_cost = mattress.foreign_cost, // ... 保存4种单据类型的价格 dannum_cost1 = mattress.dannum_cost1, // ... (dannum_cost2-4) dijia_cost1 = mattress.dijia_cost1, // ... (dijia_cost2-4) fob = mattress.fob, cabinet_type = mattress.cabinet_type, taxrate = mattress.taxrate, commission = mattress.commission }; DbSqlHelper.Insert(cmd, "u_his_price", null, hisprice, fieldsHs); ``` ### 7.6 处理子规格 ```csharp ProcessSubSpecs(mattress, ifErp); ``` ### 7.7 解锁 ```csharp if (!ignoreLock) LockHelper.UnLockBill(cmd, BillKeyWord, mattress.mattressid, context.tokendata.username, 0); ``` **关键特性**: - **版本控制**: 每次修改version自增 - **自动编码**: 自动生成床垫编码(类型代码+日期+序号) - **完整性**: 先删后插明细,确保数据一致性 - **审计**: 记录确认人和确认时间 - **历史记录**: 保存价格变更历史 - **锁机制**: 保存后自动解锁 --- ## 8. MattressHelper.CopyMattressInterface(int copyid, int mattressid) **位置**: `Helper/MattressHelper.cs:1918` **功能**: 复制床垫接口清单数据 **实现细节**: ```csharp public void CopyMattressInterface(int copyid, int mattressid) { if (copyid == 0 || mattressid == 0) return; // 复制主接口清单 cmd.CommandText = @"INSERT INTO u_mattress_interface ( mattressid, printid, itemname, bj_pzname, bj_namemx, bj_inputtype, actual_size, sb_craft, actual_size_sb, erp_pzid, ss_rate, ls_rate ) SELECT @mattressid, printid, itemname, bj_pzname, bj_namemx, bj_inputtype, actual_size, sb_craft, actual_size_sb, erp_pzid, ss_rate, ls_rate FROM u_mattress_interface WHERE mattressid = @copy_id"; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@mattressid", mattressid); cmd.Parameters.AddWithValue("@copy_id", copyid); cmd.ExecuteNonQuery(); // 复制接口清单明细 cmd.CommandText = @"INSERT INTO u_mattress_interface_qd ( mattressid, printid, itemname, bj_pzname, bj_pzname_mx, mtrlid, erp_mtrlid, wrkgrpid, useqty, dscrp, actual_useqty, qd_actual_size, qd_pfgroupqty, formulaid ) SELECT @mattressid, printid, itemname, bj_pzname, bj_pzname_mx, mtrlid, erp_mtrlid, wrkgrpid, useqty, dscrp, actual_useqty, qd_actual_size, qd_pfgroupqty, formulaid FROM u_mattress_interface_qd WHERE mattressid = @copy_id"; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@mattressid", mattressid); cmd.Parameters.AddWithValue("@copy_id", copyid); cmd.ExecuteNonQuery(); } ``` **关键特性**: - **批量复制**: 使用INSERT INTO...SELECT语句批量复制 - **两表复制**: - `u_mattress_interface`: 主接口清单 - `u_mattress_interface_qd`: 接口清单明细(材料清单) - **参数验证**: copyid和mattressid必须都大于0 **使用场景**: - 复制报价时保留原有接口清单配置 - 快速创建相似产品配置 --- ## 方法调用流程图 ``` SaveMattressExcutor.Execute() │ ├─ BllHelper.GetToken() // 验证用户会话 │ ├─ HelperBase.GetHelper() // 获取床垫Helper ├─ HelperBase.GetHelper() // 获取床网Helper │ ├─ ObjectHelper.DeepCopy() // 创建数据副本 │ ├─ AutoInit.AutoInitS() // 初始化主表对象 ├─ AutoInit.AutoInitS() [循环] // 初始化明细对象 │ ├─ MattressHelper.GetMattress() // 获取现有数据(修改场景) │ └─ 检查状态:已审核/已下单 │ ├─ MattressHelper.MattressCalculateCost() // 计算成本价格 │ └─ CalCulateFormula() // 公式计算 │ ├─ 床网计算 │ ├─ 材料成本 │ └─ 价格计算 │ ├─ [如果是复制] 复制初始化 │ └─ 重置ID和审核状态 │ ├─ [开始事务] │ │ │ ├─ MattressHelper.SaveMattressPro() // 保存主表和明细 │ │ ├─ SaveMattressCheck() // 数据校验 │ │ ├─ AutoInit.AutoInitS() // 初始化属性 │ │ ├─ BllHelper.GetID() // 获取新ID │ │ ├─ DbSqlHelper.Insert/Update() // 数据库操作 │ │ ├─ [保存明细] │ │ ├─ [保存价格历史] │ │ ├─ ProcessSubSpecs() // 处理子规格 │ │ └─ LockHelper.UnLockBill() // 解锁 │ │ │ └─ [如果是复制] │ └─ MattressHelper.CopyMattressInterface() // 复制接口清单 │ ├─ 复制主接口清单 │ └─ 复制接口清单明细 │ └─ [提交事务] ``` --- ## 性能优化要点 1. **缓存机制**: AutoInit使用60分钟缓存减少数据库查询 2. **批量操作**: CopyMattressInterface使用批量INSERT提升性能 3. **事务管理**: 整个保存过程在事务中完成,确保数据一致性 4. **版本控制**: 使用version字段防止并发修改冲突 5. **锁机制**: 使用表锁和行锁防止并发问题 --- ## 异常处理策略 1. **业务异常**: 使用LJCommonException抛出业务错误 2. **状态验证**: 检查审核状态、下单状态等 3. **事务回滚**: 发生异常时回滚所有操作 4. **用户友好**: 返回清晰的错误信息 --- ## 安全考虑 1. **会话验证**: 每个请求验证token有效性 2. **权限检查**: 虽然此方法未展示,但其他方法有权限验证 3. **SQL注入**: 使用参数化查询防止SQL注入 4. **数据完整性**: 使用事务和锁保证数据一致性 --- ## 总结 SaveMattressExcutor实现了一个完整的床垫报价保存流程,涵盖了: - **数据验证**: 用户会话、业务状态、数据完整性 - **业务逻辑**: 成本计算、价格生成、子规格处理 - **数据持久化**: 主表、明细、历史记录保存 - **并发控制**: 版本控制、锁机制、事务管理 - **性能优化**: 缓存、批量操作、合理的事务范围 整个方法调用链清晰、职责明确,是典型的企业级业务处理模式。