WeifangBeiwei.merryLibrary 1.1.5

dotnet add package WeifangBeiwei.merryLibrary --version 1.1.5
                    
NuGet\Install-Package WeifangBeiwei.merryLibrary -Version 1.1.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="WeifangBeiwei.merryLibrary" Version="1.1.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="WeifangBeiwei.merryLibrary" Version="1.1.5" />
                    
Directory.Packages.props
<PackageReference Include="WeifangBeiwei.merryLibrary" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add WeifangBeiwei.merryLibrary --version 1.1.5
                    
#r "nuget: WeifangBeiwei.merryLibrary, 1.1.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package WeifangBeiwei.merryLibrary@1.1.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=WeifangBeiwei.merryLibrary&version=1.1.5
                    
Install as a Cake Addin
#tool nuget:?package=WeifangBeiwei.merryLibrary&version=1.1.5
                    
Install as a Cake Tool

###【一】内存缓存cache:模块用法

    1.服务添加.AddMerryCache
    2.private readonly IMemoryCache _cache;
    3._cache.GetorCreate<T>(string key, Func<T> func, int expireMin = 100)

###【二】merryEncypt 模块

   {
    static abstract string AesDecrypt(string cipherText, string password);
    static abstract string AesEncrypt(string plainText, string password);
    static abstract string MD5Sign(string input);
    static abstract string Sha265Sign(string data, string key = "abc,@123");
    static abstract string Sha265SignBase64(string data, string key = "abc,@123");
    static abstract bool Sha265Verify(string data, string signature, string key = "abc,@123");
    }

###【三】 IMResult模块

{
    static abstract dynamic? Aabd();
    static abstract IActionResult Err(string _msg);
    static abstract IActionResult Err(string _msg, dynamic _data);
    static abstract IActionResult Good<T>(T _data);
    static abstract IActionResult GoodList<T>(List<T> _data);
    static abstract IActionResult Layui<T>(int _code, string _msg, int _count, List<T> _data);
    static abstract IActionResult Layui2<T>(int _code, string _msg, int _count, List<T> _data, dynamic _item);
    static abstract IActionResult Msg(int _code, string _msg);
    static abstract IActionResult Msg(string _msg);
    static abstract IActionResult Ok(dynamic? _data);
    static abstract IActionResult Ok(int _code, dynamic? _data);
    static abstract IActionResult Ok(int _code, string? _msg, dynamic? data = null);
    static abstract IActionResult Ok2(dynamic? _data, dynamic? _item);
    static abstract IActionResult Ok2(int _code, string _msg, dynamic? _data, dynamic? _item);
}

【四】拼音模块 // 获取汉字的拼音码?

     { 
         abc=Pinyin.GetInitials("潍坊北纬信息科技有限公司"); //WFBBW
         var py = Pinyin.GetPinyin("潍坊北纬信息科技有限公司");//WEIFANGBEIWEI
      }

【五】Json模块

public interface IJsonHelper
{
    static abstract T? FromJson<T>(string json, bool useCamelCase = true);
    static abstract string ToJson<T>(T obj, bool useCamelCase = true);
}

【六】Mapster模块

    ```
    /*
    * 默认情况下,无需任何配置,Mapster会根据两个实体字段名称相同进行匹配
    * 第一次调用时,配置会被缓存,第二次将会从缓存中取,以此提升性能
    */
    var user = new User();
    var dto = user.Adapt<UserDto>();//映射为新对象
    user.Adapt(dto);//在目标对象的基础上进行映射
    //注意:Adapt扩展方法使用的配置为 `TypeAdapterConfig.GlobalSettings`
    
         var user = new UserEntity
    {
        Id = 1,
        FirstName = "三",
        LastName = "张",
        Email = "zhang@example.com",
        BirthDate = new DateTime(1990, 5, 15),
        CreatedAt = DateTime.Now,
        Remark = "VIP用户"
    };
    
    // 最基础用法
    var dto = user.Adapt<UserDto>();
    // 结果:UserId=0, FullName="", Age=0(因为这些属性名不同未映射)
    
    // ========== 2. 全局配置(解决属性名不一致) ==========
    // 在程序启动时配置一次,全局生效
    TypeAdapterConfig<UserEntity, UserDto>.NewConfig()
        .Map(dest => dest.UserId, src => src.Id)                          // 属性名不同
        .Map(dest => dest.FullName, src => $"{src.LastName}{src.FirstName}")  // 计算字段
        .Map(dest => dest.Age, src => CalculateAge(src.BirthDate))        // 自定义方法
        .Map(dest => dest.CreatedDate, src => src.CreatedAt.ToString("yyyy-MM-dd"))
        .IgnoreNullValues(true)                                            // 忽略空值
        .Ignore(dest => dest.Remark);                                      // 忽略某个属性不映射
    
    // 重新映射,现在结果正确
    var dto2 = user.Adapt<UserDto>();
    Console.WriteLine($"【基础配置】UserId={dto2.UserId}, FullName={dto2.FullName}, Age={dto2.Age}");
    // 输出:UserId=1, FullName=张三, Age=34
    
    // ========== 3. 临时配置(不影响全局配置) ==========
    var dto3 = user.Adapt<UserDto>(config =>
    {
        config.ForType<UserEntity, UserDto>()
            .Map(dest => dest.Email, src => src.Email.ToLower());
    });
    
    // ========== 4. 集合映射 ==========
    var users = new List<UserEntity> { user, user };
    var dtoList = users.Adapt<List<UserDto>>();        // List 转 List
    var dtoArray = users.Adapt<UserDto[]>();            // List 转 Array
    Console.WriteLine($"【集合映射】数量={dtoList.Count}");

    // 创建一个静态配置类:MapsterConfig.cs
    using Mapster;

    public static class MapsterConfig
    {
    private static bool _isConfigured = false;

    public static void Configure()
    {
        if (_isConfigured) return;
    
        TypeAdapterConfig<UserEntity, UserDto>.NewConfig()
            .Map(dest => dest.UserId, src => src.Id)
            .Map(dest => dest.FullName, src => $"{src.LastName}{src.FirstName}")
            .Map(dest => dest.Age, src => CalculateAge(src.BirthDate))
            .Map(dest => dest.CreatedDate, src => src.CreatedAt.ToString("yyyy-MM-dd"))
            .IgnoreNullValues(true)
            .Ignore(dest => dest.Remark);
    
        // 可以继续添加其他映射配置
        // TypeAdapterConfig<Order, OrderDto>.NewConfig()...
    
        _isConfigured = true;
    }

    private static int CalculateAge(DateTime birthDate)
    {
        // 计算逻辑
    }
  }
 // Program.cs 或 Main 方法中调用
 MapsterConfig.Configure();
 ```

【七】Flurl模块

   {  using Flurl;
      var url = "https://some-api.com"
        .AppendPathSegment("endpoint")
        .SetQueryParams(new {
            api_key = _config.GetValue<string>("MyApiKey"),
            max_results = 20,
            q = "I'll get encoded!"
        })
        .SetFragment("after-hash");
    // result: https://some-api.com/endpoint?api_key=xxx&max_results=20&q=I%27ll%20get%20encoded%21#after-hash
    
    using Flurl;
    using Flurl.Http;
    var result = await "https://some-api.com"
        .AppendPathSegment("endpoint") 
        .GetStringAsync();

    Get things other than strings:
    T poco = await "http://api.foo.com".GetJsonAsync<T>();
    byte[] bytes = await "http://site.com/image.jpg".GetBytesAsync();
    Stream stream = await "http://site.com/music.mp3".GetStreamAsync();
    The above examples all send an HTTP GET request. All other common verbs are also supported:

    var result = await "http://api.foo.com".PostJsonAsync(requestObj).ReceiveJson<T>();
    var resultStr = await "http://api.foo.com/1".PatchJsonAsync(requestObj).ReceiveString();
    var resultStr2 = await "http://api.foo.com/2".PutStringAsync("hello").ReceiveString();
    var resp = await "http://api.foo.com".OptionsAsync();
    await "http://api.foo.com".HeadAsync(); 

【八】文件上传模块

     /// <summary>
    /// 保存上传的文件
    /// </summary>
    /// <param name="file">上传的文件</param>
    /// <param name="uploadFolder">保存目录,默认 "Uploads"支持绝对 D: 和相对路径 \MyApp </param>
    /// <param name="allowedExtensions">允许的扩展名,如 .jpg,.png,不传则不限制</param>
    /// <param name="maxSizeMB">最大文件大小(MB),默认 50MB</param>
   ///<param name="isSafeName">是否重命名文件guid格式</param>
    /// <returns>(是否成功, 保存的文件名, 完整路径, 错误信息)</returns>
    public static async Task<(int code, string? msg,dynamic? data,dynamic size)> SaveFileAsync(
        IFormFile? file,
        string uploadFolder = "Uploads",
        string[]? allowedExtensions = null,
        int  maxSizeMB = 50
        bool isSafeName   )

【9】Excel导入导出神器Npoi.Mapper

```cs
我们在日常开发中对Excel的操作可能会比较频繁,好多功能都会涉及到Excel的操作。
在.Net Core中大家可能使用Npoi比较多,这款软件功能也十分强大,而且接近原始编程。
但是直接使用Npoi大部分时候我们可能都会自己封装一下,毕竟根据二八原则,
我们百分之八十的场景可能都是进行简单的导入导出操作,这里就引出我们的主角Npoi.Mapper了。
关于Npoi.Mapper看名字我们就知道,它并不是一款创新型的软件,
而是针对Npoi的二次封装增强了关于Mapper相关的操作。秉承着使用非常简单的原则,不过这样能够满足我们日常开发工作中很大一部分应用场景。
它的GitHub地址为https://github.com/donnytian/Npoi.Mapper,目前Star并不多才240多,但是确实是非常好用,这里强烈推荐一波。接下来我们就大概演示一下的它的使用。
常规操作#

Npoi.Mapper的主题内容包括两大块,一个是针对导入,一个是针对导出。接下来我们先来简单演示一下最基础的导入导出。首先我们新建一个Student类作为数据承载的载体,简单定义大致如下

   public class SampleClass
{
    [Column(CustomFormat = "yyyy-MM-dd")]
    public DateTime DateTimeFormatProperty { get; set; }
    [Column(CustomFormat = "0%")]
    public double CustomFormatProperty { get; set; }
}

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Sex { get; set; }
    public DateTime BirthDay { get; set; }
}
然后引入Npoi.Mapper的nuget包
<PackageReference Include="Npoi.Mapper" Version="3.5.1" />
导出操作#
接下来我们构建一个Student集合,然后初始化一部分简单的数据,将这些数据导出到Excel,接下来做一个简单的演示

static void Main(string[] args)
{
  List<Student> students = new List<Student>
  {
      new Student{ Id = 1,Name="夫子",Sex="男",BirthDay=new DateTime(1999,10,11) },
      new Student{ Id = 2,Name="余帘",Sex="女",BirthDay=new DateTime(1999,12,12) },
      new Student{ Id = 3,Name="李慢慢",Sex="男",BirthDay=new DateTime(1999,11,11) },
      new Student{ Id = 4,Name="叶红鱼",Sex="女",BirthDay=new DateTime(1999,10,10) }
  };
     //声明mapper操作对象
      var mapper = new Mapper();
      //第一个参数为导出Excel名称
      //第二个参数为Excel数据来源
      //第三个参数为导出的Sheet名称
      //overwrite参数如果是要覆盖已存在的Excel或者新建Excel则为true,如果在原有Excel上追加数据则为false
      //xlsx参数是用于区分导出的数据格式为xlsx还是xls
      mapper.Save("Students.xlsx", students, "sheet1", overwrite: true, xlsx:true);
      //var objects = ...
        var mapper = new Mapper();
        mapper.Save("test.xlsx",  objects, "newSheet", leaveOpen: false, overwrite: false);
      Console.WriteLine("执行完成");

}

    var mapper = new Mapper("Book1.xlsx");
    mapper.Put(products, "sheet1", true);
    mapper.Put(orders, "sheet2", false);
    mapper.Save("Book1.xlsx", leaveOpen: false);
其中overwrite参数如果是要覆盖已存在的Excel或者新建Excel则为true,如果在原有Excel上追加数据则为false,说白了就是控制是新建Excel文件还是在原有基础上直接追加。xlsx参数是用于区分导出的Excel格式为xlsx还是xls。通过上述简单代码便可以实现Excel的导出功能,真的是非常简单,如果你只是进行简单的导出操作,通过Npoi.Mapper操作真的是不二的选择。这样导出的Excel效果如下所示
但是这样导出的Excel头信息为属性的名称,而且我们Student类中包含了一个时间字段BirthDay为DateTime类型,这个表示格式好像也不太符合我们常规的阅读习惯,那该怎么办呢?Npoi.Mapper为我们提供了两种处理方式,一种是通过Fluent的方式指定映射关系如下所示

var mapper = new Mapper();
//第一个参数表示导出的列名,第二个表示对应的属性字段
mapper.Map<Student>("姓名", s => s.Name)
    .Map<Student>("学号", s => s.Id)
    .Map<Student>("性别", s => s.Sex)
    .Map<Student>("生日", s => s.BirthDay)
     //格式化操作,第一个参数表示格式,第二表示对应字段
     //Format不仅仅只支持时间操作,还可以是数字或金额等
    .Format<Student>("yyyy-MM-dd", s => s.BirthDay);
mapper.Save("Students.xlsx", students, "sheet1", overwrite: true, xlsx:true);
经过上面相关操作之后导出后的效果如下所示还有一种形式是通过ColumnAttribute的形式在导出的实体类的属性上进行声明导出列相关设置,具体操作如下

public class Student
{
    [Column("学号")]
    public int Id { get; set; }
    [Column("姓名")]
    public string Name { get; set; }
    [Column("性别")]
    public string Sex { get; set; }
    [Column("生日",CustomFormat = "yyyy-MM-dd")]
    public DateTime BirthDay { get; set; }
}
通过这种方式操作和通过Fluent的效果是完全一样的,至于使用哪一种完全看个人喜好,不过我个人更喜欢在属性上直接声明的方式,这样看起来显得一目了然。
有时候我们可能需要将不同的数据源导入到同一个Excel的不同Sheet中,Npoi.Mapper也提供了这方面的支持,具体操作方式如下所示

static void Main(string[] args)
{
      //构建Student集合
      List<Student> students = new List<Student>
      {
          new Student{ Id = 1,Name="夫子",Sex="男",BirthDay=new DateTime(1999,10,11) },
          new Student{ Id = 2,Name="余帘",Sex="女",BirthDay=new DateTime(1999,12,12) }
      };
      //构建Person集合
      List<Person> persons = new List<Person>
      {
          new Person{ Id = 1,Name="陈某", Tel= 18833445566},
          new Person{ Id = 2,Name="柯浩然", Tel = 15588997766}
      };
      var mapper = new Mapper();
      //放入Mapper中
      //第一个参数是数据集合,第二个参数是Sheet名称,第三个参数表示是追加数据还是覆盖数据
      mapper.Put<Student>(students, "student",true);
      mapper.Put<Person>(persons, "person",true);
      mapper.Save("Human.xlsx");
}
不过很多时候我们是通过Web程序直接将数据转换为文件流返回的,并不会生成Excel文件,Npoi.Mapper很贴心的为我们提供了将数据读取到Stream的操作,操作方式如下

[HttpGet]
public ActionResult DownLoadFile()
{
    List<Student> students = new List<Student>
    {
        new Student{ Id = 1,Name="夫子",Sex="男",BirthDay=new DateTime(1999,10,11) },
        new Student{ Id = 2,Name="余帘",Sex="女",BirthDay=new DateTime(1999,12,12) },
        new Student{ Id = 3,Name="李慢慢",Sex="男",BirthDay=new DateTime(1999,11,11) },
        new Student{ Id = 4,Name="叶红鱼",Sex="女",BirthDay=new DateTime(1999,10,10) }
    };

    var mapper = new Mapper();
    MemoryStream stream = new MemoryStream();
    //将students集合生成的Excel直接放置到Stream中
    mapper.Save(stream, students, "sheet1", overwrite: true, xlsx: true);

    return File(stream.ToArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet","Student.xlsx");
}
Save提供了几个重载方法,其中有一个就是将数据保存到Stream中,但是这里也踩到了一个坑,不过这个是Npoi的坑并不是Npoi.Mapper的坑,那就是Workbook.Write(stream)的时候会将stream关闭,如果继续操作这个Stream会报流已关闭的错误,而Npoi.Mapper的Save到Stream的方法恰恰是对这个方法的封装,这也是为何上面我没直接在File中直接返回Stream,而是将其转换为byte数组再返回的原因。

导入操作#
上面我们演示了使用Npoi.Mapper将数据导出的场景,接下来我们来演示通过Npoi.Mapper的读取Excel的相关操作,操作也是非常的简单,话不多说直接上代码,比如我读取上面导出的Excel

//Excel文件的路径
var mapper = new Mapper("Students.xlsx");
//读取的sheet信息
var studentRows = mapper.Take<Student>("sheet1");
foreach (var row in studentRows)
{
    //映射的数据保留在value中
    Student student = row.Value;
    Console.WriteLine($"姓名:[{student.Name}],学号:[{student.Id}],性别:[{student.Sex}],生日:[{student.BirthDay:yyyy-MM-dd}]");
}
通过Take方法直接读取出来的是RowInfo集合,RowInfo是用来包装读取数据的包装类。通过它可以获取读取的行号,或读取过程中可能会出现异常情况,比如某一列读取失败,它会将列信息和报错信息记录下来,如果你不需要这些信息或者觉得遍历的时候比较麻烦想直接拿到需要的集合,可以通过如下方式转换一下

var studentRows = mapper.Take<Student>("sheet1");
//通过lambda获取到Student集合
var students = studentRows.Select(i => i.Value);
有的时候你可能不想定义一个POCO去接收返回的结果,而是想直接拿到读取信息,转换成你需要的数据格式。比如你想读取Excel中的数据,将结果转换为实体类直接入库,但是你不想定义一个专门的映射类去接收读取结果,这时候你需要一个动态类型去接收,而Npoi.Mapper恰恰提供了这样的功能,可以将Excel中的数据直接读取到dynamic中去,具体操作和上面类似

    var mapper = new Mapper(@"d:\MYAPP\PcData.xlsx");
    var studentRows = mapper.Take<dynamic>("sheet1");
    foreach (var row in studentRows)
    {
        var student = row.Value;
        Console.WriteLine($"姓名:[{student.姓名}],学号:[{student.学号}],性别:[{student.性别}],生日:[{student.生日:yyyy-MM-dd}]");
    }
其中你要操作的字段名称和Excel的列名是一致的,比如我的Excel列名叫姓名,那么我读取的时候对应的属性名称也叫姓名。
同样的情况也存在于导入操作,比如许多情况下我们是通过Web接口直接上传的文件,这种场景下,我们通常能拿到上传的流信息,Npoi.Mapper也支持读取Excel文件流的形式获取Excel数据,如下所示

[HttpPost] 
public IEnumerable<Student> UploadFile(IFormFile formFile)
{
    //通过上传文件流初始化Mapper
    var mapper = new Mapper(formFile.OpenReadStream());
    //读取sheet1的数据
    return mapper.Take<Student>("sheet1").Select(i=>i.Value);
}
其他功能#
除了上面介绍的主要功能之外Npoi.Mapper还提供了一些其他的功能,简单介绍一下几个比较实用的点

忽略操作#
有时候我们的导出或导入数据可能想忽略某些列不导出,Npoi.Mapper为了我们提供了类似EF的Ignore操作

[Ignore]
public string IgnoredProperty { get; set; }
这样的话无论是导入还是导出都会忽略这个属性,即导出不会显示这个列,导入不会映射这一列的数据

合并单元格#
如果我们导入的数据有一列数据的值是大家都拥有的,在Excel上可以通过合并单元格的操作来显示这一列,对于合并单元格的列,对于程序来讲就是等价于所有列都是同一个值,Npoi.Mapper为我们做了这种处理

[UseLastNonBlankValue]
public string ClassName { get; set; }
自定义Map规则#
虽然默认情况下Npoi.Mapper能帮我们满足大部分的类型映射关系,但是有时候我们需要根据我们自己的规则处理处理数据映射关系,这时候我们需要用到Map功能,他有许多重载的方法,我们就查看一个比较常用的方法做参数讲解

/// <param name="columnName">对应Excel列的名称</param>
/// <param name="propertyName">对应实体的属性名称</param>
/// <param name="tryTake">该函数用于处理从Excel读取时针对单元格数据的处理</param>
/// <param name="tryPut">该函数用于处理将数据导出到Excel是针对源数据的处理</param>
        public static Mapper Map<T>(this Mapper mapper, string columnName, string propertyName,
                    Func<IColumnInfo, object, bool> tryTake = null,
                    Func<IColumnInfo, object, bool> tryPut = null)
        {
        }
其中tryTake用于处理从Excel导出时针对单元格数据的处理,IColumnInfo代表数据的来源,
object代表对应将Row导入到某个实体中。tryPut恰恰相反,用于处理将数据导出到Excel是针对源数据的处理。
其中IColumnInfo代表要导出到的列信息,object代表数据的源。简单演示一下,比如我想将上述示例中,
读取到Excel里的性别数据映射到实体中的时候做一下中英文的处理,就可以使用以下操作

     var mapper = new Mapper("Students.xlsx");
     mapper.Map<Student>("性别", "Sex", (c, t) => {
     Student student = t as Student;
     student.Sex = c.CurrentValue == "男" ? "MAN" : "WOMAN";
     return true;
    }, null);

因为我是要读取Excel,所以使用tryTake函数,t代表target表示要映射到的实体,
c代表读取到的单元格信息,我将读取到target里的数据做一下处理,
如果在单元格中读取的是"男"那么对应到Student转换为"MAN",反之则为"WOMAN"。
总之你想处理一下,自定义映射逻辑都可以使用这个功能。

总结#
以上是我们对Npoi.Mapper的大致讲解,我个人还是非常推荐的。
它的使用足够简单而且功能非常完善,因为它既可以处理Excel导入操作,
也可以处理Excel导出操作。它很强大,因为它可以满足我们日常开发中,
大部分关于导入导出Excel的场景。但是它还不够强大,因为它还存在一定的缺陷,
而且许多细节可能还没考虑到。不过庆幸的是,它的源码非常的简单一共不到20个类,
而且逻辑非常清晰。如果有的情况它真的不能满足,我们完全可以下载它的源码自己扩展操作。
最后再次贴上它的GitHub地址https://github.com/donnytian/Npoi.Mapper
如果大家有类似的场景可以尝试使用一下。

###【十】Bing壁纸获取 2个函数

    //得到Bing列表
    public static async Task<List<Bimg>> GetBingImgListAsync()
    //得到当天Bimg对象
     public static async Task<Bimg?> GetBingImgAsync()

###【十一】数据校验数据校验 扩展方法;

     public class User
        {
             [Length(2, 10, ErrorMessage = "姓名长度必须在 {1} 到 {2} 个字符之间")]
             [Required(ErrorMessage = "姓名不能为空")]
             public string Name { get; set; }
             [Range(18, 60, ErrorMessage = "年龄必须在18-60之间")]
             public int Age { get; set; }
        }
    
   public interface IValidatorHelper
        {   ///类数据校验 一个返回 list,一个返回string
            static abstract bool MerryValidata<T>(this T obj, out string errors) where T : class;
            static abstract bool MerryValidataList<T>(this T obj, out List<string> errors) where T : class;
        }

###【12】网页爬虫 AngleSharp

 {## Simple dome 演示
     The simple example will use the website of Wikipedia for data retrieval.
    ```cs
    var config = Configuration.Default.WithDefaultLoader();
    var address = "https://en.wikipedia.org/wiki/List_of_The_Big_Bang_Theory_episodes";
    var context = BrowsingContext.New(config);
    var document = await context.OpenAsync(address);
    var cellSelector = "tr.vevent td:nth-child(3)";
    var cells = document.QuerySelectorAll(cellSelector);
    var titles = cells.Select(m => m.TextContent);
    ```

###【13】jwt认证 cookie认证

    控制器或者页面支持三种自定义属性(MyAuthorJwt,MyAuthorCookieOrJwt,MyAuthorCookie)
    appseting.json文件配置;
     ```cs
           {
              "Jwt": {
                "Key": "ErpSystemSecretKey20260610!@#$%^&*()",
                "Issuer": "ErpSystem"
              },
              "Authentication": {
                // 认证启用开关
                "EnableJwt": true,
                "EnableCookie": true,
                "DefaultScheme": "Cookies", // 可选:Cookies, JwtBearer, 或留空自动选择
                // Cookie 配置
                "CookieName": "MerryAuthCookie",
                "LoginPath": "/Login",
                "LogoutPath": "/login",
                "AccessDeniedPath": "/lenovo",
                "ExpireTimeSpan": "00:30:00",
                "SlidingExpiration": true,

                // API 配置
                "ApiPathPrefix": "/api",
                "ReturnJsonForApi": true
              }
            }


    program.css //文件详细配置
    3. 使用示例
    场景一:同时启用 JWT + Cookie(默认,适合混合应用)
    csharp
    // Program.cs
    builder.Services.AddCustomAuthentication(builder.Configuration);

    场景二:仅启用 JWT(纯 API 应用)
    csharp
    builder.Services.AddCustomAuthentication(builder.Configuration, options =>
    {
        options.EnableJwt = true;
        options.EnableCookie = false;
        options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; // 可选
    });
    场景三:仅启用 Cookie(传统 MVC 应用)
    csharp
    builder.Services.AddCustomAuthentication(builder.Configuration, options =>
    {
        options.EnableJwt = false;
        options.EnableCookie = true;
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    });
    场景四:都启用,但自定义默认方案
    csharp
    builder.Services.AddCustomAuthentication(builder.Configuration, options =>
    {
        options.EnableJwt = true;
        options.EnableCookie = true;
    
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; // 默认使用 JWT
    });
    场景五:完整自定义配置
    csharp
    builder.Services.AddCustomAuthentication(builder.Configuration, options =>
    {
        // 认证启用
        options.EnableJwt = true;
        options.EnableCookie = true;
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;

        // JWT 配置
        options.JwtKey = "MySuperSecretKey2026!@#$%^&*()";
        options.JwtIssuer = "MyERP";
        options.JwtAudience = "MyERPClient";

        // Cookie 配置
        options.CookieName = "MyAppAuth";
        options.LoginPath = "/Account/Login";
        options.LogoutPath = "/Account/Logout";
        options.AccessDeniedPath = "/Account/Forbidden";
        options.ExpireTimeSpan = TimeSpan.FromHours(2);
        options.SlidingExpiration = true;

        // API 配置
        options.ApiPathPrefix = "/api/v1";
        options.ReturnJsonForApi = true;
    });
    ```

【14】 APP静态获取服务服务定位器和配置文件;

```
///静态获取服务定位器
public interface IAppService
{
    static abstract T Get<T>() where T : class;
    static abstract void GetScoped<T>(this IServiceProvider provider, Action<T> action) where T : class;
    static abstract Task GetScopedAsync<T>(this IServiceProvider provider, Func<T, Task> action) where T : class;
    static abstract T GetSingleton<T>(this IServiceProvider provider) where T : class;
    static abstract T? GetSingletonOrDefault<T>(this IServiceProvider provider) where T : class;
    static abstract void SetServiceProvider(IServiceProvider provider);
}
 ///静态获取配置文件
    public interface IAppHelper
{
    static abstract string? Get(string key);
    static abstract T? Get<T>(string key);
    static abstract string GetAppRoot();
    static abstract T? GetSection<T>(string section) where T : class, new();
    static abstract string GetWwwRoot();
}

```

###【15】授权控制器贴过滤器属性 [ApiSignature] API接口校验签名(标准hmacSha256算法) 需要Header 有【sign】【timestamp】字段 或者 timestamp在url中 sign必须在header中 ###【16】fastReport到处图片或者Pdf

   ```
    // 1. 导出为图片(默认 JPEG)
    var result = FastReportHelper.FastToImg(
        path: @"D:\Reports\MyReport.frx",
        jsonStr: "[{\"id\":1,\"name\":\"测试\"}]",
        parameters: new Dictionary<string, dynamic>
        {
            { "pym", "小新" },
            { "userId", 10001 },
            { "isActive", true }
        }
    );

    // 2. 导出为 PNG 图片
    var pngResult = FastReportHelper.FastToImg(
        path: @"D:\Reports\MyReport.frx",
        jsonStr: jsonData,
        parameters: paramDict,
        imageFormat: ImageExportFormat.Png
    );

    // 3. 导出为 PDF
    var pdfResult = FastReportHelper.FastToPdf(
        path: @"D:\Reports\MyReport.frx",
        jsonStr: jsonData,
        parameters: paramDict
    );
    ```
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.5 116 7/28/2026
1.1.4 113 7/27/2026
1.1.3 109 7/13/2026
1.1.2 115 7/4/2026
1.1.1 119 6/26/2026
1.1.0 106 6/26/2026
1.0.9 107 6/24/2026
1.0.8 122 6/24/2026
1.0.7 113 6/13/2026
1.0.6 109 6/10/2026
1.0.5 106 6/7/2026
1.0.4 118 6/7/2026
1.0.3 103 6/7/2026
1.0.2 107 6/7/2026
1.0.1 108 6/7/2026
1.0.0 105 6/7/2026