hwh
2024-06-19 eede0d8477e7117e652b870cb29ef78a95b7f38a
全局异常处理和规范返回
6个文件已修改
16个文件已添加
1881 ■■■■■ 已修改文件
HTML/js/public.js 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Entity/ApiResponse.cs 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Enum/ErrorCodeEnum.cs 592 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Enum/ResponseEnum.cs 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Exception/AppFriendlyException.cs 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Exception/ErrorCodeItemMetadataAttribute.cs 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Exception/ErrorCodeTypeAttribute.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Exception/Oops.cs 209 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Extension/ApiResponseActionFilter.cs 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Extension/ApiResponseMiddleware.cs 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Extension/ApplicationBuilderExtensions.cs 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Extension/LogExtends.cs 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Extension/RequestAuditLogFilter.cs 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Extension/ServiceCollectionExtensions.cs 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Filter/CustomerExceptionMiddleware.cs 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/UnitOfWork/IUnitOfWork.cs 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/UnitOfWork/UnitOfWorkAttribute.cs 290 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Utility.csproj 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Wms/Controllers/BllTaskController.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Wms/Program.cs 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Wms/Startup.cs 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Wms/Wms.csproj 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HTML/js/public.js
@@ -282,4 +282,55 @@
      });
    });
  }  
}
//深拷贝
function deepClone(source) {
  if (typeof source !== 'object' || source == null) {
    return source;
  }
  const target = Array.isArray(source) ? [] : {};
  for (const key in source) {
    if (Object.prototype.hasOwnProperty.call(source, key)) {
      if (typeof source[key] === 'object' && source[key] !== null) {
        target[key] = deepClone(source[key]);
      } else {
        target[key] = source[key];
      }
    }
  }
  return target;
}
/**
 * 将表单赋值为指定的对象
 * @param {Object} data - 包含表单数据的对象
 * @param {String} formSelector - 表单的选择器,例如 '#myForm' 或 '.myForm'
 */
function setFormData(data, formSelector) {
  var $form = $(formSelector);
  $.each(data, function(key, value) {
      var $field = $form.find('[name=' + key + ']');
      if ($field.length > 0) {
          var fieldType = $field.attr('type');
          switch (fieldType) {
              case 'checkbox':
                  if (Array.isArray(value)) {
                      $field.each(function() {
                          $(this).prop('checked', value.includes($(this).val()));
                      });
                  } else {
                      $field.prop('checked', value);
                  }
                  break;
              case 'radio':
                  $field.filter('[value=' + value + ']').prop('checked', true);
                  break;
              default:
                  $field.val(value);
                  break;
          }
      }
  });
}
Wms/Utility/Entity/ApiResponse.cs
New file
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Utility.Entity
{
    public class ApiResponse<T>
    {
        public int code { get; set; }
        public string msg { get; set; }
        public T data { get; set; }
        public ApiResponse(int code, string message, T data)
        {
            this.code = code;
            this.msg = message;
            this.data = data;
        }
    }
}
Wms/Utility/Enum/ErrorCodeEnum.cs
New file
@@ -0,0 +1,592 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
namespace Utility
{
    /// <summary>
    /// 系统错误码
    /// </summary>
    [ErrorCodeType]
    [Description("系统错误码")]
    public enum ErrorCodeEnum
    {
        /// <summary>
        /// 验证码错误
        /// </summary>
        [ErrorCodeItemMetadata("验证码错误")]
        D0008,
        /// <summary>
        /// 账号不存在
        /// </summary>
        [ErrorCodeItemMetadata("账号不存在")]
        D0009,
        /// <summary>
        /// 密码不正确
        /// </summary>
        [ErrorCodeItemMetadata("密码不正确")]
        D1000,
        /// <summary>
        /// 非法操作!禁止删除自己
        /// </summary>
        [ErrorCodeItemMetadata("非法操作,禁止删除自己")]
        D1001,
        /// <summary>
        /// 记录不存在
        /// </summary>
        [ErrorCodeItemMetadata("记录不存在")]
        D1002,
        /// <summary>
        /// 账号已存在
        /// </summary>
        [ErrorCodeItemMetadata("账号已存在")]
        D1003,
        /// <summary>
        /// 旧密码不匹配
        /// </summary>
        [ErrorCodeItemMetadata("旧密码输入错误")]
        D1004,
        /// <summary>
        /// 测试数据禁止更改admin密码
        /// </summary>
        [ErrorCodeItemMetadata("测试数据禁止更改用户【admin】密码")]
        D1005,
        /// <summary>
        /// 数据已存在
        /// </summary>
        [ErrorCodeItemMetadata("数据已存在")]
        D1006,
        /// <summary>
        /// 数据不存在或含有关联引用,禁止删除
        /// </summary>
        [ErrorCodeItemMetadata("数据不存在或含有关联引用,禁止删除")]
        D1007,
        /// <summary>
        /// 禁止为管理员分配角色
        /// </summary>
        [ErrorCodeItemMetadata("禁止为管理员分配角色")]
        D1008,
        /// <summary>
        /// 重复数据或记录含有不存在数据
        /// </summary>
        [ErrorCodeItemMetadata("重复数据或记录含有不存在数据")]
        D1009,
        /// <summary>
        /// 禁止为超级管理员角色分配权限
        /// </summary>
        [ErrorCodeItemMetadata("禁止为超级管理员角色分配权限")]
        D1010,
        /// <summary>
        /// 非法操作,未登录
        /// </summary>
        [ErrorCodeItemMetadata("非法操作,未登录")]
        D1011,
        /// <summary>
        /// Id不能为空
        /// </summary>
        [ErrorCodeItemMetadata("Id不能为空")]
        D1012,
        /// <summary>
        /// 所属机构不在自己的数据范围内
        /// </summary>
        [ErrorCodeItemMetadata("没有权限操作该数据")]
        D1013,
        /// <summary>
        /// 禁止删除超级管理员
        /// </summary>
        [ErrorCodeItemMetadata("禁止删除超级管理员")]
        D1014,
        /// <summary>
        /// 禁止修改超级管理员状态
        /// </summary>
        [ErrorCodeItemMetadata("禁止修改超级管理员状态")]
        D1015,
        /// <summary>
        /// 没有权限
        /// </summary>
        [ErrorCodeItemMetadata("没有权限")]
        D1016,
        /// <summary>
        /// 账号已冻结
        /// </summary>
        [ErrorCodeItemMetadata("账号已冻结")]
        D1017,
        /// <summary>
        /// 禁止删除管理员
        /// </summary>
        [ErrorCodeItemMetadata("禁止删除管理员")]
        D1018,
        /// <summary>
        /// 禁止删除系统管理员角色
        /// </summary>
        [ErrorCodeItemMetadata("禁止删除系统管理员角色")]
        D1019,
        /// <summary>
        /// 禁止修改系统管理员角色
        /// </summary>
        [ErrorCodeItemMetadata("禁止修改系统管理员角色")]
        D1020,
        /// <summary>
        /// 禁止为系统管理员角色分配权限
        /// </summary>
        [ErrorCodeItemMetadata("禁止为系统管理员角色分配权限")]
        D1021,
        /// <summary>
        /// 禁止为超级管理员分配角色
        /// </summary>
        [ErrorCodeItemMetadata("禁止为超级管理员分配角色")]
        D1022,
        /// <summary>
        /// 禁止删除默认租户
        /// </summary>
        [ErrorCodeItemMetadata("禁止删除默认租户")]
        D1023,
        /// <summary>
        /// 已将其他地方登录账号下线
        /// </summary>
        [ErrorCodeItemMetadata("已将其他地方登录账号下线")]
        D1024,
        /// <summary>
        /// 父机构不存在
        /// </summary>
        [ErrorCodeItemMetadata("父机构不存在")]
        D2000,
        /// <summary>
        /// 当前机构Id不能与父机构Id相同
        /// </summary>
        [ErrorCodeItemMetadata("当前机构Id不能与父机构Id相同")]
        D2001,
        /// <summary>
        /// 已有相同组织机构,编码或名称相同
        /// </summary>
        [ErrorCodeItemMetadata("已有相同组织机构,编码或名称相同")]
        D2002,
        /// <summary>
        /// 没有权限操作机构
        /// </summary>
        [ErrorCodeItemMetadata("没有权限操作机构")]
        D2003,
        /// <summary>
        /// 该机构下有用户禁止删除
        /// </summary>
        [ErrorCodeItemMetadata("该机构下有用户禁止删除")]
        D2004,
        /// <summary>
        /// 附属机构下有用户禁止删除
        /// </summary>
        [ErrorCodeItemMetadata("附属机构下有用户禁止删除")]
        D2005,
        /// <summary>
        /// 只能增加下级机构
        /// </summary>
        [ErrorCodeItemMetadata("只能增加下级机构")]
        D2006,
        /// <summary>
        /// 下级机构下有用户禁止删除
        /// </summary>
        [ErrorCodeItemMetadata("下级机构下有用户禁止删除")]
        D2007,
        /// <summary>
        /// 租户默认机构禁止删除
        /// </summary>
        [ErrorCodeItemMetadata("租户默认机构禁止删除")]
        D2008,
        /// <summary>
        /// 字典类型不存在
        /// </summary>
        [ErrorCodeItemMetadata("字典类型不存在")]
        D3000,
        /// <summary>
        /// 字典类型已存在
        /// </summary>
        [ErrorCodeItemMetadata("字典类型已存在,名称或编码重复")]
        D3001,
        /// <summary>
        /// 字典类型下面有字典值禁止删除
        /// </summary>
        [ErrorCodeItemMetadata("字典类型下面有字典值禁止删除")]
        D3002,
        /// <summary>
        /// 字典值已存在
        /// </summary>
        [ErrorCodeItemMetadata("字典值已存在,名称或编码重复")]
        D3003,
        /// <summary>
        /// 字典值不存在
        /// </summary>
        [ErrorCodeItemMetadata("字典值不存在")]
        D3004,
        /// <summary>
        /// 字典状态错误
        /// </summary>
        [ErrorCodeItemMetadata("字典状态错误")]
        D3005,
        /// <summary>
        /// 菜单已存在
        /// </summary>
        [ErrorCodeItemMetadata("菜单已存在")]
        D4000,
        /// <summary>
        /// 路由地址为空
        /// </summary>
        [ErrorCodeItemMetadata("路由地址为空")]
        D4001,
        /// <summary>
        /// 打开方式为空
        /// </summary>
        [ErrorCodeItemMetadata("打开方式为空")]
        D4002,
        /// <summary>
        /// 权限标识格式为空
        /// </summary>
        [ErrorCodeItemMetadata("权限标识格式为空")]
        D4003,
        /// <summary>
        /// 权限标识格式错误
        /// </summary>
        [ErrorCodeItemMetadata("权限标识格式错误 如xxx:xxx")]
        D4004,
        /// <summary>
        /// 权限不存在
        /// </summary>
        [ErrorCodeItemMetadata("权限不存在")]
        D4005,
        /// <summary>
        /// 父级菜单不能为当前节点,请重新选择父级菜单
        /// </summary>
        [ErrorCodeItemMetadata("父级菜单不能为当前节点,请重新选择父级菜单")]
        D4006,
        /// <summary>
        /// 不能移动根节点
        /// </summary>
        [ErrorCodeItemMetadata("不能移动根节点")]
        D4007,
        /// <summary>
        /// 已存在同名或同编码应用
        /// </summary>
        [ErrorCodeItemMetadata("已存在同名或同编码应用")]
        D5000,
        /// <summary>
        /// 默认激活系统只能有一个
        /// </summary>
        [ErrorCodeItemMetadata("默认激活系统只能有一个")]
        D5001,
        /// <summary>
        /// 该应用下有菜单禁止删除
        /// </summary>
        [ErrorCodeItemMetadata("该应用下有菜单禁止删除")]
        D5002,
        /// <summary>
        /// 已存在同名或同编码应用
        /// </summary>
        [ErrorCodeItemMetadata("已存在同名或同编码应用")]
        D5003,
        /// <summary>
        /// 已存在同名或同编码职位
        /// </summary>
        [ErrorCodeItemMetadata("已存在同名或同编码职位")]
        D6000,
        /// <summary>
        /// 该职位下有用户禁止删除
        /// </summary>
        [ErrorCodeItemMetadata("该职位下有用户禁止删除")]
        D6001,
        /// <summary>
        /// 通知公告状态错误
        /// </summary>
        [ErrorCodeItemMetadata("通知公告状态错误")]
        D7000,
        /// <summary>
        /// 通知公告删除失败
        /// </summary>
        [ErrorCodeItemMetadata("通知公告删除失败")]
        D7001,
        /// <summary>
        /// 通知公告编辑失败
        /// </summary>
        [ErrorCodeItemMetadata("通知公告编辑失败,类型必须为草稿")]
        D7002,
        /// <summary>
        /// 通知公告操作失败,非发布者不能进行操作
        /// </summary>
        [ErrorCodeItemMetadata("通知公告操作失败,非发布者不能进行操作")]
        D7003,
        /// <summary>
        /// 文件不存在
        /// </summary>
        [ErrorCodeItemMetadata("文件不存在")]
        D8000,
        /// <summary>
        /// 不允许的文件类型
        /// </summary>
        [ErrorCodeItemMetadata("不允许的文件类型")]
        D8001,
        /// <summary>
        /// 文件超过允许大小
        /// </summary>
        [ErrorCodeItemMetadata("文件超过允许大小")]
        D8002,
        /// <summary>
        /// 文件后缀错误
        /// </summary>
        [ErrorCodeItemMetadata("文件后缀错误")]
        D8003,
        /// <summary>
        /// 已存在同名或同编码参数配置
        /// </summary>
        [ErrorCodeItemMetadata("已存在同名或同编码参数配置")]
        D9000,
        /// <summary>
        /// 禁止删除系统参数
        /// </summary>
        [ErrorCodeItemMetadata("禁止删除系统参数")]
        D9001,
        /// <summary>
        /// 已存在同名任务调度
        /// </summary>
        [ErrorCodeItemMetadata("已存在同名任务调度")]
        D1100,
        /// <summary>
        /// 任务调度不存在
        /// </summary>
        [ErrorCodeItemMetadata("任务调度不存在")]
        D1101,
        /// <summary>
        /// 演示环境禁止修改数据
        /// </summary>
        [ErrorCodeItemMetadata("演示环境禁止修改数据")]
        D1200,
        /// <summary>
        /// 已存在同名的租户
        /// </summary>
        [ErrorCodeItemMetadata("已存在同名的租户")]
        D1300,
        /// <summary>
        /// 已存在同名的租户管理员
        /// </summary>
        [ErrorCodeItemMetadata("已存在同名的租户管理员")]
        D1301,
        /// <summary>
        /// 该表代码模板已经生成过
        /// </summary>
        [ErrorCodeItemMetadata("该表代码模板已经生成过")]
        D1400,
        /// <summary>
        /// 该类型不存在
        /// </summary>
        [ErrorCodeItemMetadata("该类型不存在")]
        D1501,
        /// <summary>
        /// 该字段不存在
        /// </summary>
        [ErrorCodeItemMetadata("该字段不存在")]
        D1502,
        /// <summary>
        /// 该类型不是枚举类型
        /// </summary>
        [ErrorCodeItemMetadata("该类型不是枚举类型")]
        D1503,
        /// <summary>
        /// 该实体不存在
        /// </summary>
        [ErrorCodeItemMetadata("该实体不存在")]
        D1504,
        /// <summary>
        /// 父菜单不存在
        /// </summary>
        [ErrorCodeItemMetadata("父菜单不存在")]
        D1505,
        /// <summary>
        /// 父资源不存在
        /// </summary>
        [ErrorCodeItemMetadata("父资源不存在")]
        D1600,
        /// <summary>
        /// 当前资源Id不能与父资源Id相同
        /// </summary>
        [ErrorCodeItemMetadata("当前资源Id不能与父资源Id相同")]
        D1601,
        /// <summary>
        /// 已有相同编码或名称
        /// </summary>
        [ErrorCodeItemMetadata("已有相同编码或名称")]
        D1602,
        /// <summary>
        /// 脚本代码不能为空
        /// </summary>
        [ErrorCodeItemMetadata("脚本代码不能为空")]
        D1701,
        /// <summary>
        /// 脚本代码中的作业类,需要定义 [JobDetail] 特性
        /// </summary>
        [ErrorCodeItemMetadata("脚本代码中的作业类,需要定义 [JobDetail] 特性")]
        D1702,
        /// <summary>
        /// 作业编号需要与脚本代码中的作业类 [JobDetail('jobId')] 一致
        /// </summary>
        [ErrorCodeItemMetadata("作业编号需要与脚本代码中的作业类 [JobDetail('jobId')] 一致")]
        D1703,
        /// <summary>
        /// 禁止修改作业编号
        /// </summary>
        [ErrorCodeItemMetadata("禁止修改作业编号")]
        D1704,
        /// <summary>
        /// 已存在同名或同编码项目
        /// </summary>
        [ErrorCodeItemMetadata("已存在同名或同编码项目")]
        xg1000,
        /// <summary>
        /// 已存在相同证件号码人员
        /// </summary>
        [ErrorCodeItemMetadata("已存在相同证件号码人员")]
        xg1001,
        /// <summary>
        /// 检测数据不存在
        /// </summary>
        [ErrorCodeItemMetadata("检测数据不存在")]
        xg1002,
        /// <summary>
        /// 请添加数据列
        /// </summary>
        [ErrorCodeItemMetadata("请添加数据列")]
        db1000,
        /// <summary>
        /// 数据表不存在
        /// </summary>
        [ErrorCodeItemMetadata("数据表不存在")]
        db1001,
        /// <summary>
        /// 数据表不存在
        /// </summary>
        [ErrorCodeItemMetadata("不允许添加相同字段名")]
        db1002,
        /// <summary>
        /// 父节点不存在
        /// </summary>
        [ErrorCodeItemMetadata("父节点不存在")]
        R2000,
        /// <summary>
        /// 当前节点Id不能与父节点Id相同
        /// </summary>
        [ErrorCodeItemMetadata("当前节点Id不能与父节点Id相同")]
        R2001,
        /// <summary>
        /// 已有相同编码或名称
        /// </summary>
        [ErrorCodeItemMetadata("已有相同编码或名称")]
        R2002,
        /// <summary>
        /// 默认租户状态禁止修改
        /// </summary>
        [ErrorCodeItemMetadata("默认租户状态禁止修改")]
        Z1001,
        /// <summary>
        /// 禁止创建此类型的数据库
        /// </summary>
        [ErrorCodeItemMetadata("禁止创建此类型的数据库")]
        Z1002,
        /// <summary>
        /// 租户已禁用
        /// </summary>
        [ErrorCodeItemMetadata("租户已禁用")]
        Z1003,
    }
}
Wms/Utility/Enum/ResponseEnum.cs
New file
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Utility
{
    public enum ResponseEnum
    {
        Sucess = 0,
        Fail = 1,
        Error = 2,
    }
}
Wms/Utility/Exception/AppFriendlyException.cs
New file
@@ -0,0 +1,80 @@
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
namespace Utility
{
    public class AppFriendlyException : Exception
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        public AppFriendlyException() : base()
        {
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="message"></param>
        /// <param name="errorCode"></param>
        public AppFriendlyException(string message, object errorCode) : base(message)
        {
            ErrorMessage = message;
            ErrorCode = OriginErrorCode = errorCode;
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="message"></param>
        /// <param name="errorCode"></param>
        /// <param name="innerException"></param>
        public AppFriendlyException(string message, object errorCode, Exception innerException) : base(message, innerException)
        {
            ErrorMessage = message;
            ErrorCode = OriginErrorCode = errorCode;
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        public AppFriendlyException(SerializationInfo info, StreamingContext context) : base(info, context)
        {
        }
        /// <summary>
        /// 错误码
        /// </summary>
        public object ErrorCode { get; set; }
        /// <summary>
        /// 错误码(没被复写过的 ErrorCode )
        /// </summary>
        public object OriginErrorCode { get; set; }
        /// <summary>
        /// 错误消息(支持 Object 对象)
        /// </summary>
        public object ErrorMessage { get; set; }
        /// <summary>
        /// 状态码
        /// </summary>
        public int StatusCode { get; set; } = StatusCodes.Status500InternalServerError;
        /// <summary>
        /// 是否是数据验证异常
        /// </summary>
        public bool ValidationException { get; set; } = false;
        /// <summary>
        /// 额外数据
        /// </summary>
        public new object Data { get; set; }
    }
}
Wms/Utility/Exception/ErrorCodeItemMetadataAttribute.cs
New file
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Utility
{
    /// <summary>
    /// 异常元数据特性
    /// </summary>
    [AttributeUsage(AttributeTargets.Field)]
    public sealed class ErrorCodeItemMetadataAttribute : Attribute
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="errorMessage">错误消息</param>
        /// <param name="args">格式化参数</param>
        public ErrorCodeItemMetadataAttribute(string errorMessage, params object[] args)
        {
            ErrorMessage = errorMessage;
            Args = args;
        }
        /// <summary>
        /// 错误消息
        /// </summary>
        public string ErrorMessage { get; set; }
        /// <summary>
        /// 错误码
        /// </summary>
        public object ErrorCode { get; set; }
        /// <summary>
        /// 格式化参数
        /// </summary>
        public object[] Args { get; set; }
    }
}
Wms/Utility/Exception/ErrorCodeTypeAttribute.cs
New file
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Utility
{
    /// <summary>
    /// 错误代码类型特性
    /// </summary>
    [AttributeUsage(AttributeTargets.Enum)]
    public sealed class ErrorCodeTypeAttribute : Attribute
    {
    }
}
Wms/Utility/Exception/Oops.cs
New file
@@ -0,0 +1,209 @@
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Utility
{
    /// <summary>
    /// 抛异常静态类
    /// </summary>
    public static class Oops
    {
        /// <summary>
        /// 错误代码类型
        /// </summary>
        private static readonly IEnumerable<Type> ErrorCodeTypes;
        /// <summary>
        /// 构造函数
        /// </summary>
        static Oops()
        {
            ErrorCodeTypes = GetErrorCodeTypes();
        }
        /// <summary>
        /// 抛出业务异常信息
        /// </summary>
        /// <param name="errorMessage">异常消息</param>
        /// <param name="args">String.Format 参数</param>
        /// <returns>异常实例</returns>
        public static AppFriendlyException Bah(string errorMessage, params object[] args)
        {
            var friendlyException = Oh(errorMessage, typeof(ValidationException), args);
            friendlyException.StatusCode = StatusCodes.Status400BadRequest;
            friendlyException.ValidationException = true;
            return friendlyException;
        }
        /// <summary>
        /// 抛出业务异常信息
        /// </summary>
        /// <param name="errorCode">错误码</param>
        /// <param name="args">String.Format 参数</param>
        /// <returns>异常实例</returns>
        public static AppFriendlyException Bah(object errorCode, params object[] args)
        {
            var friendlyException = Oh(errorCode, typeof(ValidationException), args);
            friendlyException.StatusCode = StatusCodes.Status400BadRequest;
            friendlyException.ValidationException = true;
            return friendlyException;
        }
        /// <summary>
        /// 抛出字符串异常
        /// </summary>
        /// <param name="errorMessage">异常消息</param>
        /// <param name="args">String.Format 参数</param>
        /// <returns>异常实例</returns>
        public static AppFriendlyException Oh(string errorMessage, params object[] args)
        {
            var friendlyException = new AppFriendlyException(errorMessage, default);
            friendlyException.StatusCode = StatusCodes.Status400BadRequest;
            friendlyException.ValidationException = true;
            return friendlyException;
        }
        /// <summary>
        /// 抛出字符串异常
        /// </summary>
        /// <param name="errorMessage">异常消息</param>
        /// <param name="exceptionType">具体异常类型</param>
        /// <param name="args">String.Format 参数</param>
        /// <returns>异常实例</returns>
        public static AppFriendlyException Oh(string errorMessage, Type exceptionType, params object[] args)
        {
            var exceptionMessage = string.Format(errorMessage, args);
            return new AppFriendlyException(exceptionMessage, default,
                Activator.CreateInstance(exceptionType, new object[] { exceptionMessage }) as Exception);
        }
        /// <summary>
        /// 抛出字符串异常
        /// </summary>
        /// <typeparam name="TException">具体异常类型</typeparam>
        /// <param name="errorMessage">异常消息</param>
        /// <param name="args">String.Format 参数</param>
        /// <returns>异常实例</returns>
        public static AppFriendlyException Oh<TException>(string errorMessage, params object[] args)
            where TException : class
        {
            return Oh(errorMessage, typeof(TException), args);
        }
        /// <summary>
        /// 抛出错误码异常
        /// </summary>
        /// <param name="errorCode">错误码</param>
        /// <param name="args">String.Format 参数</param>
        /// <returns>异常实例</returns>
        public static AppFriendlyException Oh(object errorCode, params object[] args)
        {
            var (ErrorCode, Message) = GetErrorCodeMessage(errorCode, args);
            var friendlyException = new AppFriendlyException(Message, errorCode) { ErrorCode = ErrorCode };
            friendlyException.StatusCode = StatusCodes.Status400BadRequest;
            friendlyException.ValidationException = true;
            return friendlyException;
        }
        /// <summary>
        /// 抛出错误码异常
        /// </summary>
        /// <param name="errorCode">错误码</param>
        /// <param name="exceptionType">具体异常类型</param>
        /// <param name="args">String.Format 参数</param>
        /// <returns>异常实例</returns>
        public static AppFriendlyException Oh(object errorCode, Type exceptionType, params object[] args)
        {
            var (ErrorCode, Message) = GetErrorCodeMessage(errorCode, args);
            return new AppFriendlyException(Message, errorCode,
                Activator.CreateInstance(exceptionType, new object[] { Message }) as Exception)
            { ErrorCode = ErrorCode };
        }
        /// <summary>
        /// 抛出错误码异常
        /// </summary>
        /// <typeparam name="TException">具体异常类型</typeparam>
        /// <param name="errorCode">错误码</param>
        /// <param name="args">String.Format 参数</param>
        /// <returns>异常实例</returns>
        public static AppFriendlyException Oh<TException>(object errorCode, params object[] args)
            where TException : class
        {
            return Oh(errorCode, typeof(TException), args);
        }
        /// <summary>
        /// 获取错误码消息
        /// </summary>
        /// <param name="errorCode"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        private static (object ErrorCode, string Message) GetErrorCodeMessage(object errorCode, params object[] args)
        {
            string errorMessage = "";
            (errorCode, errorMessage) = HandleEnumErrorCode(errorCode);
            return (errorCode, errorMessage);
        }
        /// <summary>
        /// 处理枚举类型错误码
        /// </summary>
        /// <param name="errorCode">错误码</param>
        /// <returns></returns>
        private static (object ErrorCode, string Message) HandleEnumErrorCode(object errorCode)
        {
            string errorMessage = "";
            // 获取类型
            var errorType = errorCode.GetType();
            // 判断是否是内置枚举类型,如果是解析特性
            if (ErrorCodeTypes.Any(u => u == errorType))
            {
                var fieldinfo = errorType.GetField(Enum.GetName(errorType, errorCode));
                if (fieldinfo.IsDefined(typeof(ErrorCodeItemMetadataAttribute), true))
                {
                    var info = GetErrorCodeItemInformation(fieldinfo);
                    errorCode = info.Key;
                    errorMessage = info.Value;
                }
            }
            return (errorCode, errorMessage);
        }
        /// <summary>
        /// 获取错误代码信息
        /// </summary>
        /// <param name="fieldInfo">字段对象</param>
        /// <returns>(object key, object value)</returns>
        private static (object Key, string Value) GetErrorCodeItemInformation(FieldInfo fieldInfo)
        {
            var errorCodeItemMetadata = fieldInfo.GetCustomAttribute<ErrorCodeItemMetadataAttribute>();
            return (errorCodeItemMetadata.ErrorCode ?? fieldInfo.Name, string.Format(errorCodeItemMetadata.ErrorMessage, errorCodeItemMetadata.Args));
        }
        /// <summary>
        /// 获取错误代码类型
        /// </summary>
        /// <returns></returns>
        private static IEnumerable<Type> GetErrorCodeTypes()
        {
            var typesWithErrorCodeTypeAttribute = AppDomain.CurrentDomain
            .GetAssemblies()
            .SelectMany(assembly => assembly.GetTypes())
            .Where(type => type.IsEnum && type.GetCustomAttribute<ErrorCodeTypeAttribute>() != null)
            .ToList();
            return typesWithErrorCodeTypeAttribute;
        }
    }
}
Wms/Utility/Extension/ApiResponseActionFilter.cs
New file
@@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Utility.Entity;
namespace Utility
{
    public class ApiResponseActionFilter: IAsyncActionFilter
    {
        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            // 在执行动作之前的逻辑
            var resultContext = await next(); // 执行动作方法并获取执行结果
            // 在执行动作之后的逻辑
            if (resultContext.Result is ObjectResult objectResult)
            {
                var apiResponse = new ApiResponse<object>(
                    context.HttpContext.Response.StatusCode,
                    context.HttpContext.Response.StatusCode == 200 ? "请求成功" : "错误",
                    objectResult.Value
                );
                var json = JsonConvert.SerializeObject(apiResponse);
                context.HttpContext.Response.ContentType = "application/json";
                context.HttpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(json);
                await context.HttpContext.Response.WriteAsync(json);
            }
        }
    }
}
Wms/Utility/Extension/ApiResponseMiddleware.cs
New file
@@ -0,0 +1,97 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Utility.Entity;
namespace Utility.Extension
{
    public class ApiResponseMiddleware : IMiddleware
    {
        public ApiResponseMiddleware()
        {
        }
        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            if (ShouldApplyApiResponseMiddleware(context))
            {
                // 捕获响应
                var originalBodyStream = context.Response.Body;
                using (var responseBody = new MemoryStream())
                {
                    context.Response.Body = responseBody;
                    await next(context);
                    // 读取响应内容
                    context.Response.Body.Seek(0, SeekOrigin.Begin);
                    var body = await new StreamReader(context.Response.Body).ReadToEndAsync();
                    context.Response.Body.Seek(0, SeekOrigin.Begin);
                    // 反序列化响应内容
                    object data = null;
                    if (!string.IsNullOrEmpty(body))
                    {
                        try
                        {
                            data = JsonConvert.DeserializeObject<object>(body);
                        }
                        catch (JsonException)
                        {
                            // 如果响应不是有效的JSON格式,直接将其作为字符串数据处理
                            data = body;
                        }
                    }
                    var apiResponse = new ApiResponse<object>(
                        code: context.Response.StatusCode == StatusCodes.Status200OK ? (int)ResponseEnum.Sucess : (int)ResponseEnum.Fail,
                        message: context.Response.StatusCode == StatusCodes.Status200OK ? "请求成功" : "",
                        data: data
                    );
                    var json = JsonConvert.SerializeObject(apiResponse);
                    context.Response.ContentType = "application/json";
                    context.Response.ContentLength = Encoding.UTF8.GetByteCount(json);
                    context.Response.Body = originalBodyStream;
                    await context.Response.WriteAsync(json);
                }
            }
            else
            {
                await next(context);
            }
        }
        private bool ShouldApplyApiResponseMiddleware(HttpContext context)
        {
            // 获取当前处理请求的控制器信息
            var controllerActionDescriptor = context.GetEndpoint()?.Metadata.GetMetadata<ControllerActionDescriptor>();
            if (controllerActionDescriptor != null)
            {
                // 判断控制器是否带有 ResponseAttribute 特性
                return controllerActionDescriptor.ControllerTypeInfo.IsDefined(typeof(UnifyResponseAttribute), inherit: true);
            }
            return false;
        }
    }
    public static class ApiResponse
    {
        public static void UseApiResponse(this IApplicationBuilder app)
        {
            app.UseMiddleware<ApiResponseMiddleware>();
        }
    }
    public class UnifyResponseAttribute : Attribute
    {
    }
}
Wms/Utility/Extension/ApplicationBuilderExtensions.cs
New file
@@ -0,0 +1,85 @@
using Microsoft.AspNetCore.Builder;
using System;
using Serilog;
using Serilog.Events;
using System.IO;
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Headers;
using System.Runtime.CompilerServices;
using Utility.Tools;
using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
namespace Utility
{
    public static class ApplicationBuilderExtensions
    {
        /// <summary>
        ///  添加使用Serilog请求日志
        /// </summary>
        /// <param name="app"></param>
        /// <returns></returns>
        public static IApplicationBuilder UseSerilogRequestLogging(this IApplicationBuilder app)
        {
            //允许body重用
            app.Use(next => context =>
            {
                context.Request.EnableBuffering();
                return next(context);
            });
            // 添加使用Serilog记录请求日志
            app.UseSerilogRequestLogging(options =>
            {
                // 请求日志输出模板
                options.MessageTemplate = "\n {RequestMethod}={_RequestPath} | 状态={StatusCode} | 时间={Elapsed}ms | 操作人={_UserName} \n 请求内容={_RequestBody} \n 返回结果={_ResponseBody}";
                // 发出调试级别事件而不是默认事件,将请求日志记录到:Debug日志
                options.GetLevel = (httpContext, elapsed, ex) =>
                {
                    var method = httpContext.Request.Method.ToLower();
                    if (method == "options")
                        return LogEventLevel.Debug;
                    return LogEventLevel.Information;
                };
                // 将其他属性附加到请求完成事件,将请求属性附加可以在模板中使用
                options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
                {
                    diagnosticContext.Set("_RequestPath", WebUtility.UrlDecode(httpContext.Request.Path + httpContext.Request.QueryString));
                    var token = httpContext.Request.Headers["Token"].ToString();
                    if (!string.IsNullOrEmpty(token))
                    {
                        var handler = new JwtSecurityTokenHandler();
                        var jwtToken = handler.ReadJwtToken(token);
                        var claim = jwtToken.Payload.Claims.FirstOrDefault(s => s.Type == "LoginName");
                        //foreach (var claim in jwtToken.Payload.Claims)
                        //{
                        //    Console.WriteLine($"{claim.Type}: {claim.Value}");
                        //}
                        if (claim != null)
                            diagnosticContext.Set("_UserName", claim.Value);
                        else
                            diagnosticContext.Set("_UserName", "");
                    }
                    //请求body
                    var requestContent = "{}";
                    var method = httpContext.Request.Method.ToLower();
                    if (method == "post" || method == "put")
                    {
                        httpContext.Request.Body.Position = 0;
                        var requestReader = new StreamReader(httpContext.Request.Body);
                        if (requestReader.BaseStream.Length != 0)
                            requestContent = requestReader.ReadToEnd();
                    }
                    diagnosticContext.Set("_RequestBody", requestContent);
                    diagnosticContext.Set("_Service", AppDomain.CurrentDomain.FriendlyName);
                };
            });
            return app;
        }
    }
}
Wms/Utility/Extension/LogExtends.cs
New file
@@ -0,0 +1,75 @@
using Serilog.Events;
using Serilog;
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Wms
{
    public static class LogExtends
    {
        //const string infoPath = "Logs/Information.log";
        //const string warnPath = "Logs/Warning.log";
        //const string errorPath = "Logs/Error.log";
        //const string fatalPath = "Logs/Fatal.log";
        //const string template = "时间: {Timestamp:yyyy-MM-dd HH:mm:ss}{NewLine}来源: {SourceContext}{NewLine}内容: [{Level:u3}] {Message}{NewLine}{Exception}{NewLine}";
        //// 可以将日志输出到控制台、文件、数据库、ES等
        //public static void AddSerilog(this IServiceCollection c)
        //{
        //    Log.Logger = new LoggerConfiguration()
        //        .MinimumLevel.Information()
        //        .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) // 排除Dotnet自带的日志
        //        .Enrich.FromLogContext()
        //        .WriteTo.Console(outputTemplate: template)
        //        .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(lev => lev.Level == LogEventLevel.Information).WriteTo.Async(congfig => congfig.File(
        //                infoPath,
        //                rollingInterval: RollingInterval.Day,
        //                fileSizeLimitBytes: 1024 * 1024 * 10,    //默认1GB
        //                retainedFileCountLimit: 100,                   //保留最近多少个文件,默认31个
        //                rollOnFileSizeLimit: true,                       //超过文件大小时,自动创建新文件
        //                shared: true,
        //                outputTemplate: template)
        //            ))
        //        .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(lev => lev.Level == LogEventLevel.Warning).WriteTo.Async(congfig => congfig.File(
        //                warnPath,
        //                rollingInterval: RollingInterval.Day,
        //                fileSizeLimitBytes: 1024 * 1024 * 10,
        //                retainedFileCountLimit: 100,
        //                rollOnFileSizeLimit: true,
        //                shared: true,
        //                outputTemplate: template)
        //        ))
        //        .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(lev => lev.Level == LogEventLevel.Error).WriteTo.Async(congfig => congfig.File(
        //                errorPath,
        //                rollingInterval: RollingInterval.Day,
        //                fileSizeLimitBytes: 1024 * 1024 * 10,
        //                retainedFileCountLimit: 100,
        //                rollOnFileSizeLimit: true,
        //                shared: true,
        //                outputTemplate: template)
        //        ))
        //        .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(lev => lev.Level == LogEventLevel.Fatal).WriteTo.Async(congfig => congfig.File(
        //                fatalPath,
        //                rollingInterval: RollingInterval.Day,
        //                fileSizeLimitBytes: 1024 * 1024 * 10,
        //                retainedFileCountLimit: 100,
        //                rollOnFileSizeLimit: true,
        //                shared: true,
        //                outputTemplate: template)
        //        )).CreateLogger();
        //    // 注入到容器
        //    c.AddLogging(opt =>
        //    {
        //        opt.ClearProviders();
        //        opt.AddSerilog(dispose: true);
        //    });
        //}
    }
}
Wms/Utility/Extension/RequestAuditLogFilter.cs
New file
@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;
using Serilog;
using System;
using System.Collections.Generic;
using System.Text;
namespace Utility
{
    public class RequestAuditLogFilter : IResultFilter
    {
        private readonly IDiagnosticContext _diagnosticContext;
        public RequestAuditLogFilter(IDiagnosticContext diagnosticContext) { _diagnosticContext = diagnosticContext; }
        public void OnResultExecuted(ResultExecutedContext context)
        {
            var result = context.Result as ObjectResult;
            var resultJson = JsonConvert.SerializeObject(result?.Value);
            _diagnosticContext.Set("_ResponseBody", resultJson);
        }
        public void OnResultExecuting(ResultExecutingContext context) { }
    }
}
Wms/Utility/Extension/ServiceCollectionExtensions.cs
New file
@@ -0,0 +1,79 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Serilog.Events;
using Serilog;
using System;
using System.Collections.Generic;
using System.Text;
using Serilog.Sinks.SystemConsole.Themes;
using Serilog.Filters;
namespace Utility
{
    public static class ServiceCollectionExtensions
    {
        const string template = "时间: {Timestamp:yyyy-MM-dd HH:mm:ss}{NewLine}来源: {SourceContext}{NewLine}内容: [{Level:u3}] {Message}{NewLine}{Exception}{NewLine}";
        /// <summary>
        /// 添加配置Serilog
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <returns></returns>
        public static IServiceCollection AddConfigSerilog(this IServiceCollection services)
        {
            // 创建Serilog记录日志
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Information()
                .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) // 排除Dotnet自带的日志
                //.MinimumLevel.Verbose()
                //.MinimumLevel.Override("System", LogEventLevel.Debug)
                //.MinimumLevel.Override("Microsoft", LogEventLevel.Debug)
                //.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Error)
                //.MinimumLevel.Override("Microsoft.AspNetCore.Cors.Infrastructure.CorsService", LogEventLevel.Error)
                //.MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Error)
                //.MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Error)
                // 全部日志写入到Console
                .WriteTo.Console()
                .WriteTo.Async(c => c.Console(
                   theme: AnsiConsoleTheme.Literate,
                    outputTemplate: template))
                // Information日志写入到文件
                .WriteTo.Async(c => c.File(
                    path: "Logs/Information_.txt",
                    rollingInterval: RollingInterval.Day,
                    fileSizeLimitBytes: 1024 * 1024 * 10,
                    retainedFileCountLimit: 100,
                    outputTemplate: template,
                    restrictedToMinimumLevel: LogEventLevel.Information))
                // Debug日志写入到文件
                //.WriteTo.Async(c => c.File(
                //    path: "Logs/Debug.txt",
                //    rollingInterval: RollingInterval.Day,
                //    fileSizeLimitBytes: 1024 * 1024 * 10,
                //    retainedFileCountLimit: 100,
                //    outputTemplate: template,
                //    restrictedToMinimumLevel: LogEventLevel.Debug))
                // Warning日志写入到文件
                .WriteTo.Async(c => c.File(
                    path: "Logs/Warning_.txt",
                    rollingInterval: RollingInterval.Day,
                    fileSizeLimitBytes: 1024 * 1024 * 10,
                    retainedFileCountLimit: 100,
                    outputTemplate: template,
                    restrictedToMinimumLevel: LogEventLevel.Warning))
                // Error日志写入到文件
                .WriteTo.Async(c => c.File(
                    path: "Logs/Error_.txt",
                    rollingInterval: RollingInterval.Day,
                    fileSizeLimitBytes: 1024 * 1024 * 10,
                    retainedFileCountLimit: 1000,
                    outputTemplate: template,
                    restrictedToMinimumLevel: LogEventLevel.Error))
                .CreateLogger();
            services.AddSerilog();
            return services;
        }
    }
}
Wms/Utility/Filter/CustomerExceptionMiddleware.cs
New file
@@ -0,0 +1,64 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using System;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Logging;
using Utility.Entity;
using Newtonsoft.Json;
namespace Utility
{
    public class CustomerExceptionMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<CustomerExceptionMiddleware> _logger;
        public CustomerExceptionMiddleware(RequestDelegate next, ILogger<CustomerExceptionMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }
        public async Task Invoke(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, ex.Message);
                context.Response.ContentType = "application/json";
                context.Response.StatusCode = StatusCodes.Status500InternalServerError;
                var result = "系统异常,请联系管理员";
                if (ex is AppFriendlyException)
                    result = ex.Message;
                var apiResponse = new ApiResponse<object>(
                        code: (int)ResponseEnum.Error,
                        message: result,
                        data: result
                    );
                await context.Response.WriteAsync(JsonConvert.SerializeObject(apiResponse));
            }
        }
    }
    /// <summary>
    /// 静态类
    /// </summary>
    public static class ExceptionMiddlewareExtension
    {
        /// <summary>
        /// 静态方法
        /// </summary>
        /// <param name="app">要进行扩展的类型</param>
        public static void UseExceptionMiddleware(this IApplicationBuilder app)
        {
            app.UseMiddleware(typeof(CustomerExceptionMiddleware));
        }
    }
}
Wms/Utility/UnitOfWork/IUnitOfWork.cs
New file
@@ -0,0 +1,38 @@
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Text;
namespace Utility
{
    public interface IUnitOfWork
    {
        /// <summary>
        /// 开启工作单元处理
        /// </summary>
        /// <param name="context"></param>
        /// <param name="unitOfWork"></param>
        void BeginTransaction(FilterContext context, UnitOfWorkAttribute unitOfWork);
        /// <summary>
        /// 提交工作单元处理
        /// </summary>
        /// <param name="resultContext"></param>
        /// <param name="unitOfWork"></param>
        void CommitTransaction(FilterContext resultContext, UnitOfWorkAttribute unitOfWork);
        /// <summary>
        /// 回滚工作单元处理
        /// </summary>
        /// <param name="resultContext"></param>
        /// <param name="unitOfWork"></param>
        void RollbackTransaction(FilterContext resultContext, UnitOfWorkAttribute unitOfWork);
        /// <summary>
        /// 执行完毕(无论成功失败)
        /// </summary>
        /// <param name="context"></param>
        /// <param name="resultContext"></param>
        void OnCompleted(FilterContext context, FilterContext resultContext);
    }
}
Wms/Utility/UnitOfWork/UnitOfWorkAttribute.cs
New file
@@ -0,0 +1,290 @@
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Reflection;
using System.Threading.Tasks;
using System.Transactions;
namespace Utility
{
    /// <summary>
    /// 工作单元配置特性
    /// </summary>
    [AttributeUsage(AttributeTargets.Method)]
    public sealed class UnitOfWorkAttribute : Attribute, IAsyncActionFilter, IAsyncPageFilter, IOrderedFilter
    {
        /// <summary>
        /// 确保事务可用
        /// <para>此方法为了解决静态类方式操作数据库的问题</para>
        /// </summary>
        public bool EnsureTransaction { get; set; } = false;
        /// <summary>
        /// 是否使用分布式环境事务
        /// </summary>
        public bool UseAmbientTransaction { get; set; } = false;
        /// <summary>
        /// 分布式环境事务范围
        /// </summary>
        /// <remarks><see cref="UseAmbientTransaction"/> 为 true 有效</remarks>
        public TransactionScopeOption TransactionScope { get; set; } = TransactionScopeOption.Required;
        /// <summary>
        /// 分布式环境事务隔离级别
        /// </summary>
        /// <remarks><see cref="UseAmbientTransaction"/> 为 true 有效</remarks>
        public IsolationLevel TransactionIsolationLevel { get; set; } = IsolationLevel.ReadCommitted;
        /// <summary>
        /// 分布式环境事务超时时间
        /// </summary>
        /// <remarks>单位秒</remarks>
        public int TransactionTimeout { get; set; } = 0;
        /// <summary>
        /// 支持分布式环境事务异步流
        /// </summary>
        /// <remarks><see cref="UseAmbientTransaction"/> 为 true 有效</remarks>
        public TransactionScopeAsyncFlowOption TransactionScopeAsyncFlow { get; set; } = TransactionScopeAsyncFlowOption.Enabled;
        /// <summary>
        ///  MiniProfiler 分类名
        /// </summary>
        private const string MiniProfilerCategory = "unitOfWork";
        /// <summary>
        /// 过滤器排序
        /// </summary>
        private const int FilterOrder = 9999;
        /// <summary>
        /// 排序属性
        /// </summary>
        public int Order => FilterOrder;
        /// <summary>
        /// 构造函数
        /// </summary>
        public UnitOfWorkAttribute()
        {
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="ensureTransaction"></param>
        public UnitOfWorkAttribute(bool ensureTransaction)
        {
            EnsureTransaction = ensureTransaction;
        }
        /// <summary>
        /// 拦截请求
        /// </summary>
        /// <param name="context">动作方法上下文</param>
        /// <param name="next">中间件委托</param>
        /// <returns></returns>
        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            // 获取动作方法描述器
            var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
            var method = actionDescriptor.MethodInfo;
            // 创建分布式环境事务
            (var transactionScope, var logger) = CreateTransactionScope(context);
            try
            {
                // 打印工作单元开始消息
                Console.WriteLine("Beginning (Ambient)");
                // 开始事务
                BeginTransaction(context, method, out var _unitOfWork, out var unitOfWorkAttribute);
                // 获取执行 Action 结果
                var resultContext = await next();
                // 提交事务
                CommitTransaction(context, _unitOfWork, unitOfWorkAttribute, resultContext);
                // 提交分布式环境事务
                if (resultContext.Exception == null)
                {
                    transactionScope?.Complete();
                    // 打印事务提交消息
                    if (UseAmbientTransaction) Console.WriteLine("Completed (Ambient)");
                }
                else
                {
                    // 打印事务回滚消息
                    if (UseAmbientTransaction) Console.WriteLine("Rollback (Ambient)");
                    logger.LogError(resultContext.Exception, "Transaction Failed.");
                }
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Transaction Failed.");
                // 打印事务回滚消息
                if (UseAmbientTransaction) Console.WriteLine("Rollback (Ambient)");
                throw;
            }
            finally
            {
                transactionScope?.Dispose();
            }
        }
        /// <summary>
        /// 模型绑定拦截
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
        {
            return Task.CompletedTask;
        }
        /// <summary>
        /// 拦截请求
        /// </summary>
        /// <param name="context"></param>
        /// <param name="next"></param>
        /// <returns></returns>
        public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
        {
            // 获取动作方法描述器
            var method = context.HandlerMethod?.MethodInfo;
            // 处理 Blazor Server
            if (method == null)
            {
                _ = await next.Invoke();
                return;
            }
            // 创建分布式环境事务
            (var transactionScope, var logger) = CreateTransactionScope(context);
            try
            {
                // 打印工作单元开始消息
                if (UseAmbientTransaction) Console.WriteLine("Beginning (Ambient)");
                // 开始事务
                BeginTransaction(context, method, out var _unitOfWork, out var unitOfWorkAttribute);
                // 获取执行 Action 结果
                var resultContext = await next.Invoke();
                // 提交事务
                CommitTransaction(context, _unitOfWork, unitOfWorkAttribute, resultContext);
                // 提交分布式环境事务
                if (resultContext.Exception == null)
                {
                    transactionScope?.Complete();
                    // 打印事务提交消息
                    if (UseAmbientTransaction) Console.WriteLine("Completed (Ambient)");
                }
                else
                {
                    // 打印事务回滚消息
                    if (UseAmbientTransaction) Console.WriteLine("Rollback (Ambient)");
                    logger.LogError(resultContext.Exception, "Transaction Failed.");
                }
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Transaction Failed.");
                // 打印事务回滚消息
                if (UseAmbientTransaction) Console.WriteLine("Rollback (Ambient)");
                throw;
            }
            finally
            {
                transactionScope?.Dispose();
            }
        }
        /// <summary>
        /// 创建分布式环境事务
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        private (TransactionScope, ILogger) CreateTransactionScope(FilterContext context)
        {
            // 是否启用分布式环境事务
            var transactionScope = UseAmbientTransaction
                 ? new TransactionScope(TransactionScope,
                new TransactionOptions { IsolationLevel = TransactionIsolationLevel, Timeout = TransactionTimeout > 0 ? TimeSpan.FromSeconds(TransactionTimeout) : default }
                , TransactionScopeAsyncFlow)
                 : default;
            // 创建日志记录器
            var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<UnitOfWorkAttribute>>();
            return (transactionScope, logger);
        }
        /// <summary>
        /// 开始事务
        /// </summary>
        /// <param name="context"></param>
        /// <param name="method"></param>
        /// <param name="_unitOfWork"></param>
        /// <param name="unitOfWorkAttribute"></param>
        private static void BeginTransaction(FilterContext context, MethodInfo method, out IUnitOfWork _unitOfWork, out UnitOfWorkAttribute unitOfWorkAttribute)
        {
            // 解析工作单元服务
            _unitOfWork = context.HttpContext.RequestServices.GetRequiredService<IUnitOfWork>();
            // 获取工作单元特性
            unitOfWorkAttribute = method.GetCustomAttribute<UnitOfWorkAttribute>();
            // 调用开启事务方法
            _unitOfWork.BeginTransaction(context, unitOfWorkAttribute);
            // 打印工作单元开始消息
            if (!unitOfWorkAttribute.UseAmbientTransaction) Console.WriteLine("Beginning");
        }
        /// <summary>
        /// 提交事务
        /// </summary>
        /// <param name="context"></param>
        /// <param name="_unitOfWork"></param>
        /// <param name="unitOfWorkAttribute"></param>
        /// <param name="resultContext"></param>
        private static void CommitTransaction(FilterContext context, IUnitOfWork _unitOfWork, UnitOfWorkAttribute unitOfWorkAttribute, FilterContext resultContext)
        {
            // 获取动态结果上下文
            dynamic dynamicResultContext = resultContext;
            if (dynamicResultContext.Exception == null)
            {
                // 调用提交事务方法
                _unitOfWork.CommitTransaction(resultContext, unitOfWorkAttribute);
            }
            else
            {
                // 调用回滚事务方法
                _unitOfWork.RollbackTransaction(resultContext, unitOfWorkAttribute);
            }
            // 调用执行完毕方法
            _unitOfWork.OnCompleted(context, resultContext);
            // 打印工作单元结束消息
            if (!unitOfWorkAttribute.UseAmbientTransaction) Console.WriteLine("Ending");
        }
    }
}
Wms/Utility/Utility.csproj
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
@@ -13,8 +13,18 @@
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Autofac" Version="8.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.32" />
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
    <PackageReference Include="System.Drawing.Common" Version="5.0.3" />
    <PackageReference Include="Serilog" Version="4.0.0" />
    <PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
    <PackageReference Include="Serilog.Sinks.Async" Version="2.0.0" />
    <PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
    <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
    <PackageReference Include="System.Drawing.Common" Version="6.0.0" />
    <PackageReference Include="ZXing.Net" Version="0.16.7" />
  </ItemGroup>
Wms/Wms/Controllers/BllTaskController.cs
@@ -7,6 +7,8 @@
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Utility;
using Utility.Extension;
using Wms.Tools;
using WMS.IBLL.IBllTaskServer;
@@ -36,6 +38,7 @@
        /// <param name="model"></param>
        /// <returns></returns>
        [HttpPost]
        [AllowAnonymous]
        public IActionResult GetTaskSyncList(TaskSyncVm model)
        {
            try
Wms/Wms/Program.cs
@@ -3,6 +3,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -14,7 +15,18 @@
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
            try
            {
                CreateHostBuilder(args).Build().Run();
            }
            catch (Exception ex)
            {
                Log.Fatal(ex, "*** Program Stop ***");
            }
            finally
            {
                Log.CloseAndFlush();
            }
        }
        public static IHostBuilder CreateHostBuilder(string[] args) =>
@@ -22,6 +34,7 @@
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                }).UseServiceProviderFactory(new AutofacServiceProviderFactory());
                }).UseServiceProviderFactory(new AutofacServiceProviderFactory())
                .UseSerilog();
    }
}
Wms/Wms/Startup.cs
@@ -14,6 +14,12 @@
using Wms.Tools;
using WMS.Entity.Context;
using WMS.IBLL.IDataServer;
using Serilog;
using Autofac.Core;
using Utility;
using Utility.Extension;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Http;
namespace Wms
{
@@ -37,7 +43,9 @@
            services.AddHostedService<DailyTaskService>(provider =>
            new DailyTaskService(url,url2));
            services.AddControllers()
            services.AddControllers(options => {
                options.Filters.Add<RequestAuditLogFilter>();
            })
                .AddJsonOptions(options =>
                    {
                        //不使用驼峰样式的key
@@ -110,6 +118,9 @@
            services.AddAutoMapper(typeof(AutoMapperProfile)); // automapper依赖
            #endregion
            //注册serilog
            services.AddConfigSerilog();
            services.AddScoped<ApiResponseActionFilter>();
        }
        public void ConfigureContainer(ContainerBuilder builder)
        {
@@ -133,7 +144,12 @@
                    c.RoutePrefix = string.Empty;
                });
            }
            //全局返回规范
            //app.UseApiResponse();//弃用 改用Filter [ServiceFilter(typeof(ApiResponseActionFilter))]
            app.UseExceptionMiddleware();
            //使用Serilog记录请求日志
            app.UseSerilogRequestLogging();
            //app.UseHttpsRedirection();
            app.UseRouting();
@@ -141,7 +157,6 @@
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseCors("MyCors");//跨域
            app.UseEndpoints(endpoints =>
            {
Wms/Wms/Wms.csproj
@@ -11,18 +11,28 @@
  </PropertyGroup>
  <ItemGroup>
    <Compile Remove="Logs\**" />
    <Compile Remove="ModelVm\**" />
    <Content Remove="Logs\**" />
    <Content Remove="ModelVm\**" />
    <EmbeddedResource Remove="Logs\**" />
    <EmbeddedResource Remove="ModelVm\**" />
    <None Remove="Logs\**" />
    <None Remove="ModelVm\**" />
  </ItemGroup>
    
  <ItemGroup>
    <PackageReference Include="Autofac" Version="6.5.0" />
    <PackageReference Include="Autofac" Version="8.0.0" />
    <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="8.0.0" />
    <PackageReference Include="AutoMapper" Version="12.0.1" />
    <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.32" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
    <PackageReference Include="Serilog" Version="4.0.0" />
    <PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
    <PackageReference Include="Serilog.Sinks.Async" Version="2.0.0" />
    <PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
    <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
    <PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="7.0.6" />
  </ItemGroup>
@@ -31,6 +41,10 @@
    <ProjectReference Include="..\WMS.BLL\WMS.BLL.csproj" />
  </ItemGroup>
    
  <ItemGroup>
    <Folder Include="Configuration\" />
  </ItemGroup>
  <ProjectExtensions><VisualStudio><UserProperties appsettings_1json__JsonSchema="" /></VisualStudio></ProjectExtensions>