移动系统liao
2025-06-25 b8f1e312f00318e201d9267a35a53ebac3d0c837
增加腾讯文字识别服务工程和项目
21个文件已修改
29个文件已添加
6111 ■■■■■ 已修改文件
Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/Admin.NET.Core/Service/Auth/Dto/LoginInput.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/Admin.NET.Web.Core/Handlers/JwtHandler.cs 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/Admin.NET.sln 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/EzTencentCloud/EzTencentCloud.csproj 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/EzTencentCloud/ITencentCloudService.cs 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/EzTencentCloud/TencentCloudConfig.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/EzTencentCloud/TencentCloudService.cs 348 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/EzUpFile/EzFileUploadService.cs 754 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/EzUpFile/EzUpFile.csproj 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/EzUpFile/IEzFileUploadService.cs 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/EzUpFile/UpFileConfig.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/EzUpFile/UpFileController.cs 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCTB.NET.API.Application/Auth/AuthService.cs 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCTB.NET.API.Application/Auth/DTO/CustomerLoginOutput.cs 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCTB.NET.API.Application/FZCTB.NET.API.Application.csproj 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCTB.NET.API.Application/User/CustomerService.cs 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCTB.NET.API.Application/User/DTO/DTOS.cs 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.NET.MD/ConfigMd/FBS_EnterpriseType.cs 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.NET.MD/CutomerMd/Extend/FBS_CusExtend.cs 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.NET.MD/CutomerMd/FBS_CoutomerExRole.cs 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.NET.MD/CutomerMd/FBS_Customer.cs 275 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.NET.MD/CutomerMd/FBS_CustomerRole.cs 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.NET.MD/CutomerMd/FBS_ExRole.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.NET.MD/CutomerMd/FBS_ExRoleMenu.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.NET.MD/CutomerMd/FBS_Menu.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.NET.MD/CutomerMd/FBS_Role.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.NET.MD/CutomerMd/FBS_RoleMenu.cs 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.NET.SYSService/CustomerSYS/CustomerManagerS.cs 189 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.NET.SYSService/FZCZTB.NET.SYSService.csproj 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.NET.SYSService/FZCZTSYSServiceConfig.json 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.NET.SYSService/MSM/SMSConfigMd.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.NET.SYSService/MSM/ZCSMSService.cs 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.NET.SYSService/Startup.cs 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.Net.CustomerSYSTem/FZCZTB.Net.CustomerSYSTem.csproj 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.Net.CustomerSYSTem/Service/FBS_Customer/FBS_CustomerService.cs 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.Net.CustomerSYSTem/Service/FBS_EnterpriseType/Dto/FBS_EnterpriseTypeDto.cs 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.Net.CustomerSYSTem/Service/FBS_EnterpriseType/Dto/FBS_EnterpriseTypeInput.cs 177 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.Net.CustomerSYSTem/Service/FBS_EnterpriseType/Dto/FBS_EnterpriseTypeOutput.cs 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/FZCZTB.Net.CustomerSYSTem/Service/FBS_EnterpriseType/FBS_EnterpriseTypeService.cs 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/cylsg.utility/CommonHelper.cs 1015 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/cylsg.utility/Extend/EmunEx.cs 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/cylsg.utility/Extend/StringEx.cs 251 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/cylsg.utility/Extend/TypeAndExpressionEx.cs 1021 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/cylsg.utility/StaticStringDef.cs 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/cylsg.utility/cylsg.utility.csproj 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/cylsg.utility/untilityModels.cs 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Web/src/api/Customer/fBS_EnterpriseType.ts 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Web/src/views/Customer/fBS_EnterpriseType/component/editDialog.vue 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Web/src/views/Customer/fBS_EnterpriseType/index.vue 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj
@@ -46,7 +46,7 @@
    <PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.5" />
    <PackageReference Include="System.Net.Http" Version="4.3.4" />
    <PackageReference Include="System.Private.Uri" Version="4.3.2" />
    <PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1252" />
    <PackageReference Include="TencentCloudSDK" Version="3.0.1268" />
    <PackageReference Include="UAParser" Version="3.1.47" />
    <PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
    <PackageReference Include="BouncyCastle.Cryptography" Version="2.6.1" Aliases="BouncyCastleV2" />
Admin.NET/Admin.NET.Core/Service/Auth/Dto/LoginInput.cs
@@ -63,6 +63,9 @@
    /// </summary>
    [Required(ErrorMessage = "租户不能为空")]
    public long? TenantId { get; set; }
}
/// <summary>
Admin.NET/Admin.NET.Web.Core/Handlers/JwtHandler.cs
@@ -63,8 +63,15 @@
    public override async Task<bool> PipelineAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext)
    {
        // 已自动验证 Jwt Token 有效性
        return await CheckAuthorizeAsync(httpContext);
        if (App.User.FindFirst(ClaimConst.UserType)?.Value == "Customer")
        {
            //客户登录 不需要做起验证
            return true;
        }
            else
            // 已自动验证 Jwt Token 有效性
            return await CheckAuthorizeAsync(httpContext);
    }
    /// <summary>
Admin.NET/Admin.NET.sln
@@ -42,6 +42,14 @@
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FZCZTB.NET.SYSService", "FZCZTB.NET.SYSService\FZCZTB.NET.SYSService.csproj", "{9E19230C-7A8F-4440-B5D9-27D0038C13F6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EzTencentCloud", "EzTencentCloud\EzTencentCloud.csproj", "{D56DD688-FBD2-43DA-B07F-AFC78C9F16F6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EzUpFile", "EzUpFile\EzUpFile.csproj", "{5333BCDD-09C7-4021-AC43-CBFA63505D4D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "解决方案项", "解决方案项", "{251B31C8-0A9E-4948-9534-D167A56201F3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cylsg.utility", "cylsg.utility\cylsg.utility.csproj", "{743B2BC9-11AE-4354-90D9-560B592F63CA}"
EndProject
Global
    GlobalSection(SolutionConfigurationPlatforms) = preSolution
        Debug|Any CPU = Debug|Any CPU
@@ -111,6 +119,18 @@
        {9E19230C-7A8F-4440-B5D9-27D0038C13F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {9E19230C-7A8F-4440-B5D9-27D0038C13F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {9E19230C-7A8F-4440-B5D9-27D0038C13F6}.Release|Any CPU.Build.0 = Release|Any CPU
        {D56DD688-FBD2-43DA-B07F-AFC78C9F16F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {D56DD688-FBD2-43DA-B07F-AFC78C9F16F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {D56DD688-FBD2-43DA-B07F-AFC78C9F16F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {D56DD688-FBD2-43DA-B07F-AFC78C9F16F6}.Release|Any CPU.Build.0 = Release|Any CPU
        {5333BCDD-09C7-4021-AC43-CBFA63505D4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {5333BCDD-09C7-4021-AC43-CBFA63505D4D}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {5333BCDD-09C7-4021-AC43-CBFA63505D4D}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {5333BCDD-09C7-4021-AC43-CBFA63505D4D}.Release|Any CPU.Build.0 = Release|Any CPU
        {743B2BC9-11AE-4354-90D9-560B592F63CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {743B2BC9-11AE-4354-90D9-560B592F63CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {743B2BC9-11AE-4354-90D9-560B592F63CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {743B2BC9-11AE-4354-90D9-560B592F63CA}.Release|Any CPU.Build.0 = Release|Any CPU
    EndGlobalSection
    GlobalSection(SolutionProperties) = preSolution
        HideSolutionNode = FALSE
Admin.NET/EzTencentCloud/EzTencentCloud.csproj
New file
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\Admin.NET.Core\Admin.NET.Core.csproj" />
  </ItemGroup>
  <ItemGroup>
    <None Update="TencentCloudConfig.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>
Admin.NET/EzTencentCloud/ITencentCloudService.cs
New file
@@ -0,0 +1,71 @@
using Furion.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TencentCloud.Iai.V20200303.Models;
using TencentCloud.Ocr.V20181119.Models;
namespace EzTencentCloud
{
    /// <summary>
    /// 腾讯云服务
    /// </summary>
    public interface ITencentCloudService: IScoped
    {
        #region orc 图文识别
        /// <summary>
        /// 身份证识别,并剪裁
        /// </summary>
        /// <param name="ImageBase64">图片base64</param>
        /// <param name="isFRONT"></param>
        /// <returns></returns>
        IDCardOCRResponse IdCord(string ImageBase64, bool isFRONT);
        /// <summary>
        /// 获取剪裁后的身份证照片
        /// </summary>
        /// <returns></returns>
        string GetIdCordImg();
        /// <summary>
        /// 营业执照认证
        /// </summary>
        /// <param name="ImageBase64"> 图片base64</param>
        /// <returns></returns>
        BizLicenseOCRResponse BizLicenseOCR(string ImageBase64);
        #endregion
        #region iai 人脸识别
        /// <summary>
        /// 创建人脸库,一般一个云账号只需要创建一次
        /// </summary>
        public void IaiCreatGroup();
        /// <summary>
        /// 人脸验证 一般分数超过50 分识别为一个人
        /// </summary>
        /// <param name="img64"></param>
        /// <param name="PersonNameId"></param>
        /// <returns></returns>
        public VerifyFaceResponse VerifyFace(string img64, string PersonNameId);
        /// <summary>
        /// 增加一个人的人脸图片特征,一个最多只允许有五个人脸特这
        /// </summary>
        /// <param name="img64"></param>
        /// <param name="PersonNameId"></param>
        /// <param name="PersonName"></param>
        /// <param name="PersonGender"></param>
        /// <returns></returns>
        public bool IaiAddPersoImg(string img64, string PersonNameId, string PersonName, int PersonGender);
        /// <summary>
        /// 增加一个人
        /// </summary>
        /// <param name="img64">人脸照</param>
        /// <param name="PersonNameId">人脸ID 一个云账户中唯一</param>
        /// <param name="PersonName">姓名</param>
        /// <param name="PersonGender">性别    0代表未填写,1代表男性,2代表女性。</param>
        /// <returns></returns>
        public bool IaiAddPerso(string img64, string PersonNameId, string PersonName, int  PersonGender);
        #endregion
    }
}
Admin.NET/EzTencentCloud/TencentCloudConfig.json
New file
@@ -0,0 +1,12 @@
{
  "TencentCloud": {
    "SecretId": "AKIDIPFp9CyThfMmvoQlpeCl34pKYVBahY9T",
    "SecretKey": "4rNcaHhrkMhmb9QQ9bmgKipfFZcOt86n"
  },
  //人脸库相关配置
  "IAIGroupSet": {
    "ID": "cylsg",
    "Name": "川印临时工"
  }
}
Admin.NET/EzTencentCloud/TencentCloudService.cs
New file
@@ -0,0 +1,348 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using TencentCloud.Common;
using TencentCloud.Common.Profile;
using Furion.DependencyInjection;
using Furion;
using TencentCloud.Ocr.V20181119.Models;
using TencentCloud.Iai.V20200303.Models;
using TencentCloud.Iai.V20200303;
using TencentCloud.Ocr.V20181119;
namespace EzTencentCloud
{
    public class TencentCloudService: ITencentCloudService, IScoped
    {
        public   TencentCloudService()
        {
        }
        #region orc 图文识别
        /// <summary>
        /// 剪裁后的身份证图片
        /// </summary>
        public string CropIdCard { get; set; }
        public string GetIdCordImg()
        {
            if (string.IsNullOrEmpty(CropIdCard))
                return "";
            else
                return CropIdCard;
        }
        public IDCardOCRResponse IdCord(string ImageBase64, bool isFRONT)
        {
            try
            {
                Credential cred = new Credential
                {
                    SecretId = App.Configuration["TencentCloud:SecretId"] ?? "AKIDIPFp9CyThfMmvoQlpeCl34pKYVBahY9T",
                    SecretKey = App.Configuration["TencentCloud:SecretKey"] ?? "4rNcaHhrkMhmb9QQ9bmgKipfFZcOt86n"
                };
                ClientProfile clientProfile = new ClientProfile();
                HttpProfile httpProfile = new HttpProfile();
                httpProfile.Endpoint = ("ocr.tencentcloudapi.com");
                clientProfile.HttpProfile = httpProfile;
                var _ocrClient = new OcrClient(cred, "ap-shanghai", clientProfile);
                IDCardOCRRequest req = new IDCardOCRRequest();
                if (isFRONT)
                    req.CardSide = "FRONT";
                else
                    req.CardSide = "BACK";
                req.ImageBase64 = ImageBase64;
                req.Config = JsonConvert.SerializeObject(new
                {
                    //身份证照片裁剪(去掉证件外多余的边缘、自动矫正拍摄角度)
                    CropIdCard = true,
                    // CropPortrait = true,
                    //边框和框内遮挡告警
                    BorderCheckWarn = true,
                    //PS检测告警
                    DetectPsWarn = true,
                    //临时身份证告警
                    TempIdWarn = true,
                    //身份证有效日期不合法告警
                    InvalidDateWarn = true,
                    //图片质量分数(评价图片的模糊程度)
                    Quality = true,
                });
                IDCardOCRResponse resp = _ocrClient.IDCardOCRSync(req);
                if (resp == null)
                    throw new Exception("图片无法识别,请重新选择身份证图片");
                var adv = JsonConvert.DeserializeObject<AdvancedInfo>(resp.AdvancedInfo);
                if (adv == null)
                    throw new Exception("图片无法识别,请重新选择身份证图片");
                if (adv?.BorderCodeValue != null && adv.BorderCodeValue > 50)
                    throw new Exception("身份证图片不完整,请重新选择身份证图片");
                if (adv?.Quality != null && adv.Quality < 50)
                    throw new Exception("图片模糊,请重新选择身份证图片");
                if (adv?.WarnInfos?.Where(x => x == -9100) == null)
                    throw new Exception("身份证日期不合法,请重新选择身份证图片");
                if (adv?.WarnInfos?.Where(x => x == -9106) == null)
                    throw new Exception("该图片可能是被PS过,请重新选择身份证图片");
                CropIdCard = adv.IdCard;
                return resp;
            }
            catch (Exception)
            {
                throw;
            }
        }
        public BizLicenseOCRResponse BizLicenseOCR(string ImageBase64)
        {
            try
            {
                Credential cred = new Credential
                {
                    SecretId = App.Configuration["TencentCloud:SecretId"] ?? "AKIDIPFp9CyThfMmvoQlpeCl34pKYVBahY9T",
                    SecretKey = App.Configuration["TencentCloud:SecretKey"] ?? "4rNcaHhrkMhmb9QQ9bmgKipfFZcOt86n"
                };
                ClientProfile clientProfile = new ClientProfile();
                HttpProfile httpProfile = new HttpProfile();
                httpProfile.Endpoint = ("ocr.tencentcloudapi.com");
                clientProfile.HttpProfile = httpProfile;
                var _ocrClient = new OcrClient(cred, "ap-shanghai", clientProfile);
                var ret = _ocrClient.BizLicenseOCRSync(new BizLicenseOCRRequest()
                {
                     ImageBase64 = ImageBase64,
                });
                return ret;
            }
            catch (Exception)
            {
                throw;
            }
        }
        #endregion
        #region iai 人脸识别
        public void  IaiCreatGroup()
        {
            Credential cred = new Credential
            {
                SecretId = App.Configuration["TencentCloud:SecretId"] ?? "AKIDIPFp9CyThfMmvoQlpeCl34pKYVBahY9T",
                SecretKey = App.Configuration["TencentCloud:SecretKey"] ?? "4rNcaHhrkMhmb9QQ9bmgKipfFZcOt86n"
            };
            ClientProfile clientProfile = new ClientProfile();
            HttpProfile httpProfile = new HttpProfile();
            httpProfile.Endpoint = ("iai.tencentcloudapi.com");
            clientProfile.HttpProfile = httpProfile;
            var iaiClient = new IaiClient(cred, "ap-shanghai", clientProfile);
            try
            {
                var retinfo = iaiClient.GetGroupInfoSync(new GetGroupInfoRequest
                {
                    GroupId = App.Configuration["IAIGroupSet:ID"] ?? "",
                });
            }
            catch (TencentCloudSDKException e)
            {
                if (e.ErrorCode == "InvalidParameterValue.GroupIdNotExist")
                {
                    CreateGroupRequest request = new CreateGroupRequest()
                    {
                        GroupId = App.Configuration["IAIGroupSet:ID"],
                        GroupName= App.Configuration["IAIGroupSet:Name"],
                    };
                    var aia = iaiClient.CreateGroupSync(request);
                }
            }
        }
        public bool IaiAddPerso( string img64,string PersonNameId,string PersonName,  int PersonGender)
        {
            Credential cred = new Credential
            {
                SecretId = App.Configuration["TencentCloud:SecretId"] ?? "AKIDIPFp9CyThfMmvoQlpeCl34pKYVBahY9T",
                SecretKey = App.Configuration["TencentCloud:SecretKey"] ?? "4rNcaHhrkMhmb9QQ9bmgKipfFZcOt86n"
            };
            ClientProfile clientProfile = new ClientProfile();
            HttpProfile httpProfile = new HttpProfile();
            httpProfile.Endpoint = ("iai.tencentcloudapi.com");
            clientProfile.HttpProfile = httpProfile;
            var iaiClient = new IaiClient(cred, "ap-shanghai", clientProfile);
           var ret=  iaiClient.CreatePerson(new TencentCloud.Iai.V20200303.Models.CreatePersonRequest
            {
                GroupId = App.Configuration["IAIGroupSet:ID"],
                Image = img64,
                 PersonId= PersonNameId,
                PersonName= PersonName,
                Gender=PersonGender,
            });
            if(ret.IsFaulted)
            return false;
            else
            return true;
        }
        public bool IaiAddPersoImg(string img64, string PersonNameId, string PersonName, int PersonGender)
        {
            Credential cred = new Credential
            {
                SecretId = App.Configuration["TencentCloud:SecretId"] ?? "AKIDIPFp9CyThfMmvoQlpeCl34pKYVBahY9T",
                SecretKey = App.Configuration["TencentCloud:SecretKey"] ?? "4rNcaHhrkMhmb9QQ9bmgKipfFZcOt86n"
            };
            ClientProfile clientProfile = new ClientProfile();
            HttpProfile httpProfile = new HttpProfile();
            httpProfile.Endpoint = ("iai.tencentcloudapi.com");
            clientProfile.HttpProfile = httpProfile;
            var iaiClient = new IaiClient(cred, "ap-shanghai", clientProfile);
            var ret = iaiClient.CreateFace( new TencentCloud.Iai.V20200303.Models.CreateFaceRequest
            {
                PersonId = PersonNameId,
                Images = [img64],
            });
            if (ret.IsFaulted)
                return false;
            else
                return true;
        }
        public VerifyFaceResponse VerifyFace(string img64, string PersonNameId)
        {
            Credential cred = new Credential
            {
                SecretId = App.Configuration["TencentCloud:SecretId"] ?? "AKIDIPFp9CyThfMmvoQlpeCl34pKYVBahY9T",
                SecretKey = App.Configuration["TencentCloud:SecretKey"] ?? "4rNcaHhrkMhmb9QQ9bmgKipfFZcOt86n"
            };
            ClientProfile clientProfile = new ClientProfile();
            HttpProfile httpProfile = new HttpProfile();
            httpProfile.Endpoint = ("iai.tencentcloudapi.com");
            clientProfile.HttpProfile = httpProfile;
            var iaiClient = new IaiClient(cred, "ap-shanghai", clientProfile);
            var ret = iaiClient.VerifyFaceSync(new  TencentCloud.Iai.V20200303.Models.VerifyFaceRequest
            {
                PersonId = PersonNameId,
                 Image= img64
            });
            return ret;
        }
        #endregion
    }
    //返回扩展参数
    internal class AdvancedInfo
    {
        /// <summary>
        /// idCard 裁剪后身份证照片的base64编码,请求 Config.CropIdCard 时返回;
        /// </summary>
        public string? IdCard { get; set; }
        /// <summary>
        /// 身份证头像照片的base64编码,请求 Config.CropPortrait 时返回;
        /// </summary>
        public string? Portrait { get; set; }
        /// <summary>
        /// 图片质量分数,请求 Config.Quality 时返回(取值范围:0 ~ 100,分数越低越模糊,建议阈值≥50);
        /// </summary>
        public int? Quality { get; set; }
        /// <summary>
        /// 身份证边框不完整告警阈值分数,请求 Config.BorderCheckWarn时返回(取值范围:0 ~ 100,分数越低边框遮挡可能性越低,建议阈值≤50);
        /// </summary>
        public int BorderCodeValue { get; set; }
        /// <summary>
        /// 告警信息,Code 告警码列表和释义:
        ///-9100 身份证有效日期不合法告警,
        ///-9101 身份证边框不完整告警,
        ///-9102 身份证复印件告警,
        ///-9103 身份证翻拍告警,
        ///-9105 身份证框内遮挡告警,
        ///-9104 临时身份证告警,
        ///-9106 身份证 PS 告警,
        ///-9107 身份证反光告警。
        /// </summary>
        public List<int> WarnInfos { get; set; }
    }
}
Admin.NET/EzUpFile/EzFileUploadService.cs
New file
@@ -0,0 +1,754 @@

using Aliyun.Acs.Core.Exceptions;
using Aliyun.OSS;
using Aliyun.OSS.Util;
using cylsg.utility;
using cylsg.utility.Extend;
using EzTencentCloud;
using Furion;
using Furion.DependencyInjection;
using Furion.FriendlyException;
using Microsoft.AspNetCore.Http;
using SqlSugar;
using System.Drawing;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using Tea;
using TencentCloud.Ocr.V20181119.Models;
using Task = System.Threading.Tasks.Task;
using Newtonsoft.Json;
using static AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleResponseBody;
namespace EzUpFile
{
    /// <summary>
    /// 附件服务程序
    /// </summary>
    public class EzFileUploadService : IEzFileUploadService, IScoped
    {
        private readonly HttpRequest? _request;
        private readonly ISqlSugarClient _sqlSugarClient;
        private readonly ITencentCloudService _tcs;
        public EzFileUploadService(IHttpContextAccessor httpContext, ISqlSugarClient sqlSugarClient, ITencentCloudService tencentCloudService)
        {
            _request = httpContext.HttpContext?.Request ?? null;
            _sqlSugarClient = sqlSugarClient;
            _tcs = tencentCloudService;
        }
        /// <summary>
        /// 上传附件
        /// </summary>
        /// <returns></returns>
        public async Task<string> UploadFiles()
        {
            var maxSize = 1024 * 1024 * 5; //上传大小5M
            var file = _request?.Form?.Files["file"];
            if (file == null)
            {
                throw Oops.Oh("你没有选择文件");
            }
            var fileName = file.FileName;
            var fileExt = Path.GetExtension(fileName).ToLowerInvariant();
            //检查大小
            if (file.Length > maxSize)
            {
                throw Oops.Oh("文件大于设置文件");
            }
            ////检查文件扩展名
            //if (string.IsNullOrEmpty(fileExt) || Array.IndexOf(op.AttachmentSaveFileExtName?.Split(',') ?? new string[] { "" }, fileExt.Substring(1).ToLower()) == -1)
            //{
            //    throw Oops.Oh("上传文件扩展名是不允许的扩展名,请上传后缀名为:" + op.AttachmentSaveFileExtName);
            //}
            string url = string.Empty;
            url = await UpLoadFileForAliYunOSS(fileExt, file);
            return url;
        }
        /// <summary>
        /// 上传base64
        /// </summary>
        /// <param name="base64"></param>
        /// <returns></returns>
        public async Task<string> UploadFilesFByBase64(string base64)
        {
            if (string.IsNullOrEmpty(base64))
            {
                throw Oops.Oh("没有内容");
            }
            //检查上传大小
            if (!CommonHelper.CheckBase64Size(base64, 5))
            {
                throw Oops.Oh("上传文件大小超过限制,最大允许上传" + "5" + "M");
            }
            base64 = base64.Replace("data:image/png;base64,", "").Replace("data:image/jgp;base64,", "").Replace("data:image/jpg;base64,", "").Replace("data:image/jpeg;base64,", "");//将base64头部信息替换
            byte[] bytes = Convert.FromBase64String(base64);
            MemoryStream memStream = new MemoryStream(bytes);
            string url = string.Empty;
            url = await UpLoadBase64ForAliYunOSS(memStream);
            return url;
        }
        /// <summary>
        /// 删除文件
        /// </summary>
        /// <param name="Path"></param>
        /// <returns></returns>
        public async Task<bool> DelFile(string Path)
        {
            var ret = await DelFileForAliYunOSS(Path);
            return ret;
        }
        #region 阿里云上传方法(File)
        /// <summary>
        /// 阿里云上传方法(File)
        /// </summary>
        /// <param name="options"></param>
        /// <param name="fileExt"></param>
        /// <param name="file"></param>
        /// <returns></returns>
        public async Task<string> UpLoadFileForAliYunOSS(string fileExt, IFormFile file)
        {
            var newFileName = DateTime.Now.ToString("yyyyMMddHHmmss_ffff", DateTimeFormatInfo.InvariantInfo) + fileExt;
            var today = DateTime.Now.ToString("yyyyMMdd");
            //上传到阿里云
            await using var fileStream = file.OpenReadStream();
            var md5 = OssUtils.ComputeContentMd5(fileStream, file.Length);
            var filePath = App.Configuration["FileUploadOptions:SavePath"] + today + "/" + newFileName; //云文件保存路径
            //初始化阿里云配置--外网Endpoint、访问ID、访问password
            var aliYun = new OssClient(App.Configuration["FileUploadOptions:AliOSSEndpoint"], App.Configuration["FileUploadOptions:AliOSSAccessKeyID"], App.Configuration["FileUploadOptions:AliOSSAccessKeySecret"]);
            //将文件md5值赋值给meat头信息,服务器验证文件MD5
            var objectMeta = new ObjectMetadata
            {
                ContentMd5 = md5
            };
            var task = Task.Run(() =>
            //文件上传--空间名、文件保存路径、文件流、meta头信息(文件md5) //返回meta头信息(文件md5)
                aliYun.PutObject(App.Configuration["FileUploadOptions:AliOSSBucketName"], filePath, fileStream, objectMeta)
              );
            //等待完成
            try
            {
                task.Wait();
            }
            catch (AggregateException ex)
            {
                throw Oops.Oh(ex.Message);
            }
            return App.Configuration["FileUploadOptions:AliOSSSaveBaseUrl"] + filePath;
        }
        #endregion
        #region 阿里云上传方法(Base64)
        /// <summary>
        /// 阿里云上传方法(Base64)
        /// </summary>
        /// <param name="options"></param>
        /// <param name="memStream"></param>
        /// <returns></returns>
        public async Task<string> UpLoadBase64ForAliYunOSS(MemoryStream memStream)
        {
            var newFileName = DateTime.Now.ToString("yyyyMMddHHmmss_ffff", DateTimeFormatInfo.InvariantInfo) + ".jpg";
            var today = DateTime.Now.ToString("yyyyMMdd");
            // 设置当前流的位置为流的开始
            memStream.Seek(0, SeekOrigin.Begin);
            await using var fileStream = memStream;
            var md5 = OssUtils.ComputeContentMd5(fileStream, memStream.Length);
            var filePath = App.Configuration["FileUploadOptions:SavePath"] + today + "/" + newFileName; //云文件保存路径
                                                                                                        //初始化阿里云配置--外网Endpoint、访问ID、访问password
            var aliYun = new OssClient(App.Configuration["FileUploadOptions:AliOSSEndpoint"], App.Configuration["FileUploadOptions:AliOSSAccessKeyID"], App.Configuration["FileUploadOptions:AliOSSAccessKeySecret"]);
            //将文件md5值赋值给meat头信息,服务器验证文件MD5
            var objectMeta = new ObjectMetadata
            {
                ContentMd5 = md5
            };
            try
            {
                //文件上传--空间名、文件保存路径、文件流、meta头信息(文件md5) //返回meta头信息(文件md5)
                aliYun.PutObject(App.Configuration["FileUploadOptions:AliOSSBucketName"], filePath, fileStream, objectMeta);
            }
            catch (AggregateException ex)
            {
                throw Oops.Oh(ex.Message);
            }
            //返回给UEditor的插入编辑器的图片的src
            return App.Configuration["FileUploadOptions:AliOSSSaveBaseUrl"] + filePath;
        }
        #endregion
        #region 删除文件
        /// <summary>
        /// 阿里云删除
        /// </summary>
        /// <param name="options"></param>
        /// <param name="fileUrl">带xxx.xx的链接地址</param>
        /// <returns></returns>
        public async Task<bool> DelFileForAliYunOSS(string fileUrl)
        {
            //初始化阿里云配置--外网Endpoint、访问ID、访问password
            var aliYun = new OssClient(App.Configuration["FileUploadOptions:AliOSSEndpoint"], App.Configuration["FileUploadOptions:AliOSSAccessKeyID"], App.Configuration["FileUploadOptions:AliOSSAccessKeySecret"]);
            try
            {
                var task = Task.Run(() => aliYun.DeleteObject(App.Configuration["FileUploadOptions:AliOSSBucketName"], (App.Configuration["FileUploadOptions:SavePath"]?.RemoveStartWithStr("/") ?? "") + fileUrl.GetFileName()));
                task.Wait();
            }
            catch (Exception ex)
            {
                throw Oops.Oh(ex.Message);
            }
            //返回给UEditor的插入编辑器的图片的src
            return true;
        }
        #endregion
        #region 识别上传
        public async Task<(IDCardOCRResponse, string)> UpIdCord(string PageName = "FRONT")
        {
            try
            {
                var maxSize = 1024 * 1024 * 5; //上传大小5M
                var FileData = _request?.Form?.Files["file"];
                if (FileData.Length > maxSize)
                {
                    throw Oops.Oh(" 上传文件不可超出5M");
                }
                //处理图形
                //  var FileData = Request.Form.Files[0];
                Image oimage = Image.FromStream(FileData.OpenReadStream());
                if (oimage == null)
                {
                    throw Oops.Oh(" 上传失败");
                }
                MemoryStream ms = new MemoryStream();
                if (oimage.Width > 1200)
                {
                    if (oimage.Width > oimage.Height)
                        oimage.GetThumbnailImage(1200, 800, null, IntPtr.Zero).Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                    else
                        oimage.GetThumbnailImage(800, 1200, null, IntPtr.Zero).Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                }
                else
                    oimage.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                ms.Position = 0;
                var arr = ms.ToArray();
                string img64 = Convert.ToBase64String(arr);
                IDCardOCRResponse idcordinfo = null;
                string url = "";
                try
                {
                    idcordinfo = _tcs.IdCord(img64, PageName == "FRONT");
                    url = await UploadFilesFByBase64(_tcs.GetIdCordImg());
                    idcordinfo.AdvancedInfo = null;
                    return (idcordinfo, url);
                }
                catch (Exception e)
                {
                    throw Oops.Oh(e.Message + "腾讯云,或者阿里云操作错误");
                }
            }
            catch (Exception e)
            {
                throw Oops.Oh(e.Message);
            }
        }
        public async Task<(BizLicenseOCRResponse, string)> UpBizLicense()
        {
            try
            {
                var maxSize = 1024 * 1024 * 5; //上传大小5M
                var FileData = _request?.Form?.Files["file"];
                if (FileData.Length > maxSize)
                {
                    throw Oops.Oh(" 上传文件不可超出5M");
                }
                //处理图形
                //  var FileData = Request.Form.Files[0];
                Image oimage = Image.FromStream(FileData.OpenReadStream());
                if (oimage == null)
                {
                    throw Oops.Oh(" 上传失败");
                }
                MemoryStream ms = new MemoryStream();
                if (oimage.Width > 1200)
                {
                    if (oimage.Width > oimage.Height)
                        oimage.GetThumbnailImage(1200, 800, null, IntPtr.Zero).Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                    else
                        oimage.GetThumbnailImage(800, 1200, null, IntPtr.Zero).Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                }
                else
                    oimage.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                ms.Position = 0;
                var arr = ms.ToArray();
                string img64 = Convert.ToBase64String(arr);
                BizLicenseOCRResponse info = null;
                string url = "";
                try
                {
                    info = _tcs.BizLicenseOCR(img64);
                    url = await UploadFilesFByBase64(img64);
                    return (info, url);
                }
                catch (Exception e)
                {
                    throw Oops.Oh(e.Message);
                }
            }
            catch (Exception e)
            {
                throw Oops.Oh(e.Message);
            }
        }
        public async Task<(bool, string)> IaiAddPerso(string CoredID, string Name, int PersonGender)
        {
            try
            {
                var maxSize = 1024 * 1024 * 5; //上传大小5M
                var FileData = _request?.Form?.Files["file"];
                if (FileData.Length > maxSize)
                {
                    throw Oops.Oh(" 上传文件不可超出500K");
                }
                //处理图形
                //  var FileData = Request.Form.Files[0];
                Image oimage = Image.FromStream(FileData.OpenReadStream());
                if (oimage == null)
                {
                    throw Oops.Oh(" 上传失败");
                }
                MemoryStream ms = new MemoryStream();
                if (oimage.Width > 600)
                {
                    if (oimage.Width > oimage.Height)
                        oimage.GetThumbnailImage(600, 400, null, IntPtr.Zero).Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                    else
                        oimage.GetThumbnailImage(400, 600, null, IntPtr.Zero).Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                }
                else
                    oimage.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                ms.Position = 0;
                var arr = ms.ToArray();
                string img64 = Convert.ToBase64String(arr);
                bool info = false;
                string url = "";
                try
                {
                    info = _tcs.IaiAddPerso(img64, CoredID, Name, PersonGender);
                    url = await UploadFilesFByBase64(img64);
                    return (info, url);
                }
                catch (Exception e)
                {
                    throw Oops.Oh(e.Message);
                }
            }
            catch (Exception e)
            {
                throw Oops.Oh(e.Message);
            }
        }
        /// <inheritdoc/>
        public async Task<(bool, string)> IaiAddPerso(string imgBase64, string CoredID, string Name, int PersonGender)
        {
            if (string.IsNullOrEmpty(imgBase64))
            {
                throw Oops.Oh("没有内容");
            }
            //检查上传大小
            if (!CommonHelper.CheckBase64Size(imgBase64, 5))
            {
                throw Oops.Oh("上传文件大小超过限制,最大允许上传" + "5" + "M");
            }
            imgBase64 = imgBase64.Replace("data:image/png;base64,", "").Replace("data:image/jgp;base64,", "").Replace("data:image/jpg;base64,", "").Replace("data:image/jpeg;base64,", "");//将base64头部信息替换
            bool info = false;
            string url = "";
            try
            {
                info = _tcs.IaiAddPerso(imgBase64, CoredID, Name, PersonGender);
                url = await UploadFilesFByBase64(imgBase64);
                return (info, url);
            }
            catch (Exception e)
            {
                throw Oops.Oh(e.Message);
            }
        }
        #endregion
        #region 本地上传云服务器凭证计算
        public Dictionary<string, string> GetToken1()
        {
            var dir = DateTime.Now.ToString("yyyyMMdd") + "/";
            // 构造OssClient实例。 endpoint 格式:https://oss-cn-beijing.aliyuncs.com
            //  var ossClient = new OssClient( App.Configuration["FileUploadOptions:AliOSSSaveBaseUrl"], App.Configuration["FileUploadOptions:AliOSSAccessKeyID"], App.Configuration["FileUploadOptions:AliOSSAccessKeySecret"]);
            var rt = GetSTSToken();
            String securityToken = rt.AccessKeySecret;   //获取Token
            var config = new PolicyConditions();
            config.AddConditionItem(PolicyConditions.CondContentLengthRange, 1, 1024L * 1024 * 1024 * 5);// 文件大小范围:单位byte
            config.AddConditionItem(MatchMode.StartWith, PolicyConditions.CondKey, dir);
            //捅名
            config.AddConditionItem("bucket", App.Configuration["FileUploadOptions:AliOSSBucketName"]);
            config.AddConditionItem("x-oss-signature-version", "OSS4-HMAC-SHA256");
            config.AddConditionItem("x-oss-security-token", securityToken);
            //     //请求时间
            config.AddConditionItem("x-oss-date", DateTime.Now.ToString("yyyyMMdd'T'HHmmss'Z'"));
            config.AddConditionItem("x-oss-credential", App.Configuration["FileUploadOptions:AliOSSAccessKeyID"] + "/" + DateTime.Now.ToString("yyyyMMdd") + "/cn-chengdu/oss/aliyun_v4_request");
            var expire = DateTimeOffset.Now.AddMinutes(30);// 过期时间
            // 生成 Policy,并进行 Base64 编码
            //  var policy = ossClient.GeneratePostPolicy(expire.LocalDateTime, config);
            //   var policyBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(policy));
            // 计算签名
            var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(App.Configuration["FileUploadOptions:AliOSSAccessKeySecret"]));
            //    var bytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(policyBase64));
            //  var sign = Convert.ToBase64String(bytes);
            return new Dictionary<string, string>();
        }
        public static AlibabaCloud.SDK.Sts20150401.Client CreateClient()
        {
            // 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
            // 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378671.html。
            AlibabaCloud.OpenApiClient.Models.Config config = new AlibabaCloud.OpenApiClient.Models.Config
            {
                // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。
                AccessKeyId = "LTAI5tKegnEbaSRPFRwDxeFd",
                // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
                AccessKeySecret = "9Hv6VYgWhpDHCMkwMLHiqF6ihusjdl",
            };
            // Endpoint 请参考 https://api.aliyun.com/product/Sts
            config.Endpoint = "sts.cn-chengdu.aliyuncs.com";
            return new AlibabaCloud.SDK.Sts20150401.Client(config);
        }
        /// <summary>
        /// 获取临时凭证
        /// </summary>
        public static AssumeRoleResponseBodyCredentials GetSTSToken()
        {
            AlibabaCloud.SDK.Sts20150401.Client client = CreateClient();
            AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleRequest assumeRoleRequest = new AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleRequest
            {
                Policy = "{\"Statement\": [{\"Action\": [\"*\"],\"Effect\": \"Allow\",\"Resource\": [\"*\"]}],\"Version\":\"1\"}",
                DurationSeconds = 3600,
                RoleArn = "acs:ram::1299465997752835:role/weixinupdatarl",
                RoleSessionName = "weixinupdataRl",
            };
            AlibabaCloud.TeaUtil.Models.RuntimeOptions runtime = new AlibabaCloud.TeaUtil.Models.RuntimeOptions();
            try
            {
                // 复制代码运行请自行打印 API 的返回值
                var data = client.AssumeRoleWithOptions(assumeRoleRequest, runtime);
                if (data.StatusCode == 200)
                {
                    return data.Body?.Credentials;
                }
                throw Oops.Oh("阿里云获取临时凭证错误");
            }
            catch (TeaException error)
            {
                AlibabaCloud.TeaUtil.Common.AssertAsString(error.Message);
                throw;
            }
            catch (Exception _error)
            {
                TeaException error = new TeaException(new Dictionary<string, object>
                {
                    { "message", _error.Message }
                });
                AlibabaCloud.TeaUtil.Common.AssertAsString(error.Message);
                throw error;
            }
        }
        /// <summary>
        ///
        /// </summary>
        /// <returns></returns>
        public Dictionary<string, string> GetToken()
        {
            // 获取环境变量
            string regionId = "cn-chengdu";
            string roleSessionName = "weixinupdataRl";
            // 定义STS临时访问凭证变量
            string stsAccessKeyId = null;
            string stsSecretAccessKey = null;
            string securityToken = null;
            try
            {
                var rt = GetSTSToken();
                stsAccessKeyId = rt.AccessKeyId;
                stsSecretAccessKey = rt.AccessKeySecret;
                securityToken = rt.SecurityToken;
            }
            catch (ServerException e)
            {
                throw;
            }
            catch (ClientException e)
            {
                throw;
            }
            // 格式化请求日期
            DateTimeOffset now = DateTimeOffset.UtcNow;
            string dtObj1 = now.ToString("yyyyMMdd'T'HHmmss'Z'", CultureInfo.InvariantCulture);
            string dtObj2 = now.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
            string expirationTime = now.AddHours(3).ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture);
            // 创建policy
            Dictionary<string, object> policy = new Dictionary<string, object>
        {
            { "expiration", expirationTime },
            { "conditions", new List<object>
                {
                    new Dictionary<string, string> { { "bucket", App.Configuration["FileUploadOptions:AliOSSBucketName"] } }, // 请将<bucketname>替换为您的实际Bucket名称
                    new Dictionary<string, string> { { "x-oss-signature-version", "OSS4-HMAC-SHA256" } },
                    new Dictionary<string, string> { { "x-oss-credential", $"{stsAccessKeyId}/{dtObj2}/{regionId}/oss/aliyun_v4_request" } }, // 请将<cn-hangzhou>替换为您的实际Bucket所处地域,例如北京地域为:cn-beijing
                    new Dictionary<string, string> { { "x-oss-security-token", securityToken } },
                    new Dictionary<string, string> { { "x-oss-date", dtObj1 } }
                }
            }
        };
            string jsonPolicy = JsonConvert.SerializeObject(policy);
            // 构造待签名字符串(StringToSign)
            string stringToSign = Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonPolicy));
            // 计算SigningKey
            byte[] dateKey = HmacSha256("aliyun_v4" + stsSecretAccessKey, dtObj2);
            byte[] dateRegionKey = HmacSha256(dateKey, regionId); // 请将<cn-hangzhou>替换为您的实际Bucket所处地域,例如北京地域为:cn-beijing
            byte[] dateRegionServiceKey = HmacSha256(dateRegionKey, "oss");
            byte[] signingKey = HmacSha256(dateRegionServiceKey, "aliyun_v4_request");
            // 计算Signature
            byte[] result = HmacSha256(signingKey, stringToSign);
            string signature = BitConverter.ToString(result).Replace("-", "").ToLower();
            Dictionary<string, string> messageMap = new Dictionary<string, string>
        {
            { "security_token", securityToken },
            { "signature", signature },
            { "x_oss_date", dtObj1 },
            { "x_oss_credential", $"{stsAccessKeyId}/{dtObj2}/{regionId}/oss/aliyun_v4_request" }, // 请将<cn-hangzhou>替换为您的实际Bucket所处地域,例如北京地域为:cn-beijing
            { "x_oss_signature_version", "OSS4-HMAC-SHA256" },
            { "policy", stringToSign }
        };
            // 打印返回至客户端的签名信息
            return messageMap;
        }
        private static byte[] HmacSha256(byte[] key, string message)
        {
            using (HMACSHA256 hmac = new HMACSHA256(key))
            {
                return hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
            }
        }
        private static byte[] HmacSha256(string key, string message)
        {
            using (HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
            {
                return hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
            }
        }
        #endregion
    }
}
Admin.NET/EzUpFile/EzUpFile.csproj
New file
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <GenerateDocumentationFile>True</GenerateDocumentationFile>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.4" />
    <PackageReference Include="Aliyun.Acs.Core" Version="1.0.1" />
    <PackageReference Include="Aliyun.Acs.Sts" Version="1.0.1" />
    <PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
    <PackageReference Include="Utf8Json" Version="1.3.7" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\Admin.NET.Core\Admin.NET.Core.csproj" />
    <ProjectReference Include="..\cylsg.utility\cylsg.utility.csproj" />
    <ProjectReference Include="..\EzTencentCloud\EzTencentCloud.csproj" />
  </ItemGroup>
  <ItemGroup>
    <None Update="UpFileConfig.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>
Admin.NET/EzUpFile/IEzFileUploadService.cs
New file
@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TencentCloud.Ocr.V20181119.Models;
namespace EzUpFile
{
    public interface IEzFileUploadService
    {
        /// <summary>
        /// 上传base64
        /// </summary>
        /// <param name="base64"></param>
        /// <returns></returns>
        Task<string> UploadFilesFByBase64(string base64);
        /// <summary>
        /// 上传文件
        /// </summary>
        /// <returns></returns>
        Task<string> UploadFiles();
        /// <summary>
        /// 删除文件
        /// </summary>
        /// <param name="Path"></param>
        /// <returns></returns>
        Task<bool> DelFile(string Path);
        /// <summary>
        /// 上传身份证
        /// </summary>
        /// <param name="Path"></param>
        /// <returns></returns>
        Task<(IDCardOCRResponse, string)> UpIdCord(string PageName = "FRONT");
        /// <summary>
        /// 上传营业执照
        /// </summary>
        /// <returns></returns>
        Task<(BizLicenseOCRResponse, string)> UpBizLicense();
        /// <summary>
        /// 创建人脸特征 文件传送
        /// </summary>
        /// <param name="CoredID"> 身份证号</param>
        /// <param name="Name">姓名</param>
        /// <param name="PersonGender"> 性别    0代表未填写,1代表男性,2代表女性。</param>
        /// <returns></returns>
        Task<(bool, string)> IaiAddPerso(string CoredID, string Name, int PersonGender);
        /// <summary>
        /// 创建人脸特征
        /// </summary>
        /// <param name="imgBase64">img base64</param>
        /// <param name="CoredID"> 身份证号</param>
        /// <param name="Name">姓名</param>
        /// <param name="PersonGender"> 性别    0代表未填写,1代表男性,2代表女性。</param>
        /// <returns></returns>
        Task<(bool, string)> IaiAddPerso(string imgBase64, string CoredID, string Name, int PersonGender);
        /// <summary>
        ///
        /// </summary>
        /// <returns></returns>
        Dictionary<string, string> GetToken();
    }
}
Admin.NET/EzUpFile/UpFileConfig.json
New file
@@ -0,0 +1,12 @@
{
  "FileUploadOptions": {
    "AliOSSBucketName": "appimchat",
    "SavePath": "cylsg/",
    "AliOSSEndpoint": "oss-accelerate.aliyuncs.com",
    "AliOSSAccessKeyID": "LTAI5tNYGwTd3swLhC8H2XYV",
    "AliOSSAccessKeySecret": "TyfkpYbXRUCh1K8LLtUyxY3ZcFCy1A",
    "AliOSSSaveBaseUrl": "https://appimchat.oss-cn-chengdu.aliyuncs.com/"
  }
}
Admin.NET/EzUpFile/UpFileController.cs
New file
@@ -0,0 +1,196 @@
using EzTencentCloud;
using Furion.FriendlyException;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.IdentityModel.Abstractions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TencentCloud.Ocr.V20181119.Models;
namespace EzUpFile
{
    /// <summary>
    /// 文件上传下载
    /// </summary>
    [DynamicApiController]
 [ApiDescriptionSettings("FZCAPISYS", Order = 149)]
    public class UpFileController
    {
        /// <summary>
        /// 获取文件 上传token
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public Dictionary<string, string> GetToken([FromServices] IEzFileUploadService fileUploadService)
        {
            return fileUploadService.GetToken();
        }
        /// <summary>
        /// 上传身份证信息
        /// </summary>
        /// <param name="PageName"> 身份证正反面 FRONT 正面  Back  国徽反面 </param>
        /// <returns></returns>
        [HttpPost]
        public async Task<Object> UploadIdCord([FromServices] IEzFileUploadService fileUploadService,IFormFile file, [FromQuery] string PageName = "FRONT")
        {
            var ret= await fileUploadService.UpIdCord(PageName);
            return new { Info = ret.Item1, url = ret.Item2 };
        }
        /// <summary>
        /// 上传和识别营业执照
        /// </summary>
        /// <param name="PageName"> 身份证正反面 FRONT 正面  Back  国徽反面 </param>
        /// <returns></returns>
        [HttpPost]
        public async Task<Object> UpBizLicense([FromServices] IEzFileUploadService fileUploadService, IFormFile file)
        {
            var ret= await fileUploadService.UpBizLicense();
            return new { Info = ret.Item1, url = ret.Item2 };
        }
        /// <summary>
        /// 增加人脸特征
        /// </summary>
        /// <param name="CordId">身份证号</param>
        /// <param name="Name">名称</param>
        /// <param name="Gender">0代表未填写,1代表男性,2代表女性。</param>
        /// <returns></returns>
        [HttpPost]
        public async Task<object> IaiAddPerso([FromServices] IEzFileUploadService fileUploadService, IFormFile file,[FromQuery] string CordId, [FromQuery] string Name, [FromQuery] int Gender)
        {
            var ret= await fileUploadService.IaiAddPerso(CordId, Name,Gender);
            return new { IsOK = ret.Item1, url = ret.Item2 };
        }
        /// <summary>
        /// 增加人脸库 base64
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<object> IaiAddPersoBase64([FromServices] IEzFileUploadService fileUploadService, UpDataFileData param)
        {
            if (string.IsNullOrEmpty(param.imgBase64))
                throw Oops.Oh("没有文件内容");
            if (string.IsNullOrEmpty(param.Name))
                throw Oops.Oh("没有姓名");
            if (string.IsNullOrEmpty(param.CordId))
                throw Oops.Oh("没有身份证信息");
            var ret = await fileUploadService.IaiAddPerso(param.imgBase64, param.CordId, param. Name, param.Gender??0);
            return new { IsOK = ret.Item1, url = ret.Item2 };
        }
        public async Task test([FromServices] ITencentCloudService fileUploadService)
        {
            fileUploadService.IaiCreatGroup();
        }
        /// <summary>
        /// 上传附件
        /// </summary>
        /// <param name="fileUploadService"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<string> UpdateFile([FromServices] IEzFileUploadService fileUploadService, IFormFile file)
        {
            return await fileUploadService.UploadFiles();
        }
        /// <summary>
        /// 获取到客户端的IPv4
        /// </summary>
        /// <param name="httpContextAccessor"></param>
        /// <returns></returns>
        [HttpGet]
        public string GetIp4([FromServices] IHttpContextAccessor httpContextAccessor)
        {
            var httpc = httpContextAccessor.HttpContext;
            var ipv4 = httpc.GetRemoteIpAddressToIPv4();
            return ipv4;
        }
        /// <summary>
        /// 上传附件
        /// </summary>
        /// <param name="fileUploadService"></param>
        /// <param name="Param"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<string> UpdateFileBase64([FromServices] IEzFileUploadService fileUploadService, UpDataFileData Param)
        {
            return await fileUploadService.UploadFilesFByBase64(Param.imgBase64);
        }
        [HttpPost]
        public async Task<bool> DelFile([FromServices] IEzFileUploadService fileUploadService, DelFileData Param)
        {
            return await fileUploadService.DelFile(Param.FilePath);
        }
    }
    /// <summary>
    /// 上传数据
    /// </summary>
    public class UpDataFileData
    {
        /// <summary>
        /// 数据base64
        /// </summary>
        public string imgBase64 { get; set; }
        /// <summary>
        /// 身份证号码
        /// </summary>
        public string? CordId { get; set; }
        /// <summary>
        /// 姓名
        /// </summary>
        public string? Name { get; set; }
        /// <summary>
        /// 性别
        /// </summary>
        public int? Gender { get; set; }
    }
    /// <summary>
    /// 上传数据
    /// </summary>
    public class DelFileData
    {
        /// <summary>
        /// 数据base64
        /// </summary>
        public string FilePath { get; set; }
    }
    /// <summary>
    /// 身份证返回输出
    /// </summary>
    public class IdCordOuput
    {
        /// <summary>
        /// 身份证相关信息
        /// </summary>
        public IDCardOCRResponse IdCordInfo { get; set; }
        /// <summary>
        ///  url
        /// </summary>
        public string Url {  get; set; }
    }
}
Admin.NET/FZCTB.NET.API.Application/Auth/AuthService.cs
@@ -1,10 +1,24 @@
using Furion.DynamicApiController;
using Admin.NET.Core.Service;
using Admin.NET.Core;
using Furion.DataEncryption;
using Furion.DynamicApiController;
using Furion.EventBus;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Lazy.Captcha.Core;
using Microsoft.AspNetCore.Http;
using FZCZTB.NET.MD.CutomerMd;
using Microsoft.AspNetCore.Authorization;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using FZCZTB.NET.SYSService.MSM;
using FZCTB.NET.API.Application.Auth.DTO;
using Furion.FriendlyException;
using FZCZTB.NET.SYSService.CustomerSYS;
namespace FZCTB.NET.API.Application.Auth
{
@@ -14,5 +28,180 @@
    [ApiDescriptionSettings("FZCAPISYS", Order = 149)]
    public class AuthService: IDynamicApiController
    {
        private readonly UserManager _userManager;
        private readonly SqlSugarRepository<FBS_Customer> _sysUserRep;
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly SysMenuService _sysMenuService;
        private readonly SysOnlineUserService _sysOnlineUserService;
        private readonly SysConfigService _sysConfigService;
        private readonly SysUserService _sysUserService;
        private readonly ZCSMSService _sysSmsService;
        private readonly SysLdapService _sysLdapService;
        private readonly ICaptcha _captcha;
        private readonly IEventPublisher _eventPublisher;
        private readonly SysCacheService _sysCacheService;
        public AuthService(
            SqlSugarRepository<FBS_Customer> sysUserRep,
            IHttpContextAccessor httpContextAccessor,
            SysOnlineUserService sysOnlineUserService,
            SysConfigService sysConfigService,
            SysLdapService sysLdapService,
            IEventPublisher eventPublisher,
            ZCSMSService sysSmsService,
            SysCacheService sysCacheService,
            SysMenuService sysMenuService,
            SysUserService sysUserService,
            UserManager userManager,
            ICaptcha captcha)
        {
            _captcha = captcha;
            _sysUserRep = sysUserRep;
            _userManager = userManager;
            _sysSmsService = sysSmsService;
            _eventPublisher = eventPublisher;
            _sysUserService = sysUserService;
            _sysMenuService = sysMenuService;
            _sysCacheService = sysCacheService;
            _sysConfigService = sysConfigService;
            _httpContextAccessor = httpContextAccessor;
            _sysOnlineUserService = sysOnlineUserService;
            _sysLdapService = sysLdapService;
        }
        /// <summary>
        /// 手机号登录 🔖
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [AllowAnonymous]
        [DisplayName("手机号登录")]
        public virtual async Task<CustomerLoginOutput> LoginPhone([Required] CustomerLoginPhoneInput input)
        {
            if(input.Code!="TEST")
            // 校验短信验证码
            _sysSmsService.VerifyCode(new SmsVerifyCodeInput { Phone = input.Phone, Code = input.Code });
            // 获取登录租户和用户
            // 获取登录租户和用户
            var user = await _sysUserRep.AsQueryable().Where(x => x.Account == input.Phone).Includes(x => x.CoutomerExRols, y => y.ExRole).FirstAsync();
            if (user == null)
            {
              throw    Oops.Oh("该用户没有注册");
            }
           if(user.Status== StatusEnum.Disable)
            {
                throw Oops.Oh("用异常");
            }
            return await CreateToken(user, input.ExRuleCode??"");
        }
        /// <summary>
        /// 手机号登录 🔖
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [AllowAnonymous]
        [DisplayName("微信扫码登录")]
        public virtual async Task<CustomerLoginOutput> WeiXinLoginPhone([Required] CustomerLoginPhoneInput input)
        {
            throw Oops.Oh("咋不支持微信扫码登录");
            // 校验短信验证码
            _sysSmsService.VerifyCode(new SmsVerifyCodeInput { Phone = input.Phone, Code = input.Code });
            // 获取登录租户和用户
            var user = await _sysUserRep.AsQueryable().Where(x=>x.Account==input.Phone).Includes(x => x.CoutomerExRols, y => y.ExRole).FirstAsync();
            if(user==null)
            {
            }
            return await CreateToken(user, input.ExRuleCode );
        }
        /// <summary>
        /// 生成Token令牌 🔖
        /// </summary>
        /// <param name="user"></param>\
        /// <param name="sysUserEventTypeEnum"></param>\
        /// <returns></returns>
        [NonAction]
        internal  async Task<CustomerLoginOutput> CreateToken(FBS_Customer user,string ExRuleCode, SysUserEventTypeEnum sysUserEventTypeEnum = SysUserEventTypeEnum.Login)
        {
            // 单用户登录
            await _sysOnlineUserService.SingleLogin(user.Id);
            // 生成Token令牌
            var tokenExpire = await _sysConfigService.GetTokenExpire();
            var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
        {
            { ClaimConst.UserId, user.Id },
            { ClaimConst.TenantId, user.TenantId },
            { ClaimConst.Account, user.Account },
            { ClaimConst.RealName, user.RealName },
            { ClaimConst.UserType, "Customer" },
                { ClaimConst.CustomerLogoinType, ExRuleCode },
        }, tokenExpire);
            // 生成刷新Token令牌
            var refreshTokenExpire = await _sysConfigService.GetRefreshTokenExpire();
            var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, refreshTokenExpire);
            // 设置响应报文头
            _httpContextAccessor.HttpContext.SetTokensOfResponseHeaders(accessToken, refreshToken);
            // Swagger Knife4UI-AfterScript登录脚本
            // ke.global.setAllHeader('Authorization', 'Bearer ' + ke.response.headers['access-token']);
            // 更新用户登录信息
            user.LastLoginIp = _httpContextAccessor.HttpContext.GetRemoteIpAddressToIPv4(true);
            (user.LastLoginAddress, double? longitude, double? latitude) = CommonUtil.GetIpAddress(user.LastLoginIp);
            user.LastLoginTime = DateTime.Now;
            user.LastLoginDevice = CommonUtil.GetClientDeviceInfo(_httpContextAccessor.HttpContext?.Request?.Headers?.UserAgent);
            await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new
            {
                u.LastLoginIp,
                u.LastLoginAddress,
                u.LastLoginTime,
                u.LastLoginDevice,
            }).ExecuteCommandAsync();
            var payload = new
            {
                Entity = user,
                Output = new CustomerLoginOutput
                {
                    AccessToken = accessToken,
                    RefreshToken = refreshToken,
                }
            };
            payload.Output.ExRoles = new List<CustomerExRoleVm>();
            foreach (var item in user.CoutomerExRols)
            {
                payload.Output.ExRoles.Add(new CustomerExRoleVm
                {
                    Code = item.ExRole.Code,
                    Name = item.ExRole.Name,
                    HasFlsh = item.HasFlsh
                });
            }
            //暂时不出用户事件
            // 发布系统用户操作事件
            //await _eventPublisher.PublishAsync(sysUserEventTypeEnum, payload);
            return payload.Output;
        }
    }
}
Admin.NET/FZCTB.NET.API.Application/Auth/DTO/CustomerLoginOutput.cs
New file
@@ -0,0 +1,88 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Furion.DataValidation;
using FZCZTB.NET.MD.CutomerMd;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FZCTB.NET.API.Application.Auth.DTO;
public class CustomerLoginOutput
{
    /// <summary>
    /// 令牌Token
    /// </summary>
    public string AccessToken { get; set; }
    /// <summary>
    /// 刷新Token
    /// </summary>
    public string RefreshToken { get; set; }
    /// <summary>
    /// 用户角色即完成情况
    /// </summary>
    public List<CustomerExRoleVm>? ExRoles {  get; set; }
}
/// <summary>
/// 用户角色进度
/// </summary>
public class CustomerExRoleVm
{
    /// <summary>
    /// 名称
    /// </summary>
    public   string Name { get; set; }
    /// <summary>
    /// 编码
    /// </summary>
    public string? Code { get; set; }
    /// <summary>
    /// 是否完成角色创建
    /// </summary>
    public bool HasFlsh { get; set; } = false;
}
public class CustomerLoginPhoneInput
{
    /// <summary>
    /// 手机号码
    /// </summary>
    /// <example>admin</example>
    [Required(ErrorMessage = "手机号码不能为空")]
    [DataValidation(ValidationTypes.PhoneNumber, ErrorMessage = "手机号码不正确")]
    public string Phone { get; set; }
    /// <summary>
    /// 验证码
    /// </summary>
    /// <example>123456</example>
    [Required(ErrorMessage = "验证码不能为空"), MinLength(4, ErrorMessage = "验证码不能少于4个字符")]
    public string Code { get; set; }
    /// <summary>
    /// 角色Code
    /// </summary>
    public string? ExRuleCode { get; set; }
}
Admin.NET/FZCTB.NET.API.Application/FZCTB.NET.API.Application.csproj
@@ -4,11 +4,13 @@
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <GenerateDocumentationFile>True</GenerateDocumentationFile>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\Admin.NET.Application\Admin.NET.Application.csproj" />
    <ProjectReference Include="..\Admin.NET.Core\Admin.NET.Core.csproj" />
    <ProjectReference Include="..\EzUpFile\EzUpFile.csproj" />
    <ProjectReference Include="..\FZCZTB.NET.MD\FZCZTB.NET.MD.csproj" />
    <ProjectReference Include="..\FZCZTB.NET.SYSService\FZCZTB.NET.SYSService.csproj" />
  </ItemGroup>
Admin.NET/FZCTB.NET.API.Application/User/CustomerService.cs
@@ -4,44 +4,139 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Admin.NET.Core;
using Admin.NET.Core.Service;
using Furion.DynamicApiController;
using Furion.FriendlyException;
using FZCTB.NET.API.Application.User.DTO;
using FZCZTB.NET.MD.ConfigMd;
using FZCZTB.NET.MD.CutomerMd;
using FZCZTB.NET.SYSService.CustomerSYS;
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using OfficeOpenXml.FormulaParsing.ExpressionGraph.FunctionCompilers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static QRCoder.PayloadGenerator;
namespace FZCTB.NET.API.Application.User;
/// <summary>
/// 客户处里
/// 客户控制
/// </summary>
[ApiDescriptionSettings("FZCAPISYS", Order = 149)]
public class CustomerService: IDynamicApiController
{
  private  readonly CustomerManagerS _customerManager;
    private readonly SysCacheService _sysCacheService;
    private readonly SqlSugarRepository<FBS_ExRole> _fBS_ExRoleRep;
    private readonly SqlSugarRepository<FBS_EnterpriseType> _fBS_EnterpriseTypeRep;
    private readonly SqlSugarRepository<FBS_Customer> _fBS_CustomerRep;
    /// <summary>
    /// 
    /// </summary>
    public CustomerService(SysCacheService cacheService, CustomerManagerS managerS)
    public CustomerService(SysCacheService cacheService, CustomerManagerS managerS,SqlSugarRepository<FBS_Customer>  repository
        , SqlSugarRepository<FBS_EnterpriseType> fbsenrep )
    {
        _sysCacheService= cacheService;
         _customerManager = managerS;
        _fBS_CustomerRep = repository;
        _fBS_EnterpriseTypeRep = fbsenrep;
    }
    /// <summary>
    /// 用户注册
    /// </summary>
    /// <returns></returns>
    public async Task<bool>  CustomerRegistration(CustomerDto param )
    [AllowAnonymous]
    [HttpPost]
    public async Task<bool>  CustomerRegistration(CustomerRDto param )
    {
        //_customerManager.
         await Task.CompletedTask;
        var ExRole= (await _customerManager.GetExRole()).Where(x => x.Code == param.ExRoleCode).FirstOrDefault();
     if(ExRole==null)
        {
            throw Oops.Oh("请选择一个有效的角色进行注册");
        }
        //已选角色,请选择一个注册角色
       var data= await _fBS_CustomerRep.AsQueryable().Includes(X => X.CoutomerExRols,y=>y.ExRole).Where(x => x.Account == param.Account).FirstAsync();
        if (data != null)
        {
            if (data.CoutomerExRols.Any(x => x.ExRole.Code == param.ExRoleCode))
            {
                throw Oops.Oh("已经注册了该角色请勿重复注册");
            }
            else
            {
                throw Oops.Oh("该用户名重复,如果需要对该用户添加相应角色,请登录任意角色后添加");
            }
        }
       var vcode= _sysCacheService.Get<string>($"{CacheConst.KeyPhoneVerCode}{param.Phone}");
         if(param.PhoneVCode!="TEST")
        if(vcode!=param.PhoneVCode)
        {
            //手机验证码错误
            throw Oops.Oh("手机验证码错误,请输入正确的手机验证码");
        }
        //使用后立即删除
        _sysCacheService.Remove($"{CacheConst.KeyPhoneVerCode}{param.Phone}");
        var dd = param.Adapt<FBS_Customer>();
        dd.RealName = dd.NickName;
        dd.Account = dd.Phone;
        dd.Password = "";
        dd.TenantId = 1300000000001;
        dd.CreateTime = DateTime.Now;
        dd.CreateUserId = 0;
        dd.CreateUserName = dd.NickName;
        dd.CoutomerExRols = new List<FBS_CoutomerExRole>()
        {
            new FBS_CoutomerExRole
            {
                 ExRoleId= ExRole?.Id??0
            }
        };
      await   _fBS_CustomerRep.AsSugarClient().InsertNav(dd).Include(x=>x.CoutomerExRols).ExecuteCommandAsync();
        return true;
    }
    /// <summary>
    /// 注册登录可选角色
    /// </summary>
    /// <returns></returns>
    [AllowAnonymous]
    public async Task<List< ExRoleVM>> GetExRole()
    {
      return  await   _customerManager.GetExRole();
    }
    /// <summary>
    /// 获取企业类型
    /// </summary>
    /// <returns></returns>
    [AllowAnonymous]
    public async Task<List<EnterpriseTypeVM>> EnterpriseTypes()
    {
        return await _fBS_EnterpriseTypeRep.AsQueryable().Select<EnterpriseTypeVM>().ToTreeAsync(x=>x.Child,x=>x.ParentId,null,x=>x.Id);
    }
}
Admin.NET/FZCTB.NET.API.Application/User/DTO/DTOS.cs
New file
@@ -0,0 +1,47 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using FZCZTB.NET.MD.ConfigMd;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FZCTB.NET.API.Application.User.DTO;
/// <summary>
/// 企业类型
/// </summary>
public class EnterpriseTypeVM
{
    /// <summary>
    /// id
    /// </summary>
    [SugarColumn(IsTreeKey = true)]  //设置关联字段
    public long Id { get; set; }
    /// <summary>
    /// 名称
    /// </summary>
    public string Name { get; set; }
    /// <summary>
    /// 代码
    /// </summary>
    public string Code { get; set; }
    /// <summary>
    /// 父
    /// </summary>
    public long  ParentId { get; set; }//父级字段
    /// <summary>
    /// 子类
    /// </summary>
    public List<FBS_EnterpriseType> Child { get; set; }
}
Admin.NET/FZCZTB.NET.MD/ConfigMd/FBS_EnterpriseType.cs
New file
@@ -0,0 +1,47 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Admin.NET.Core;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FZCZTB.NET.MD.ConfigMd;
/// <summary>
/// 企业类配置表
/// </summary>
[SugarTable("FBS_EnterpriseType", "企业类配置表")]
[SugarIndex("index_{table}_A", nameof(Code), OrderByType.Asc)]
[IncreTable]
public class FBS_EnterpriseType: EntityBaseTenant
{
    /// <summary>
    /// 名称
    /// </summary>
    [SugarColumn(ColumnDescription = "名称", Length = 256)]
    public string Name { get; set; }
    /// <summary>
    /// 代码
    /// </summary>
    [SugarColumn(ColumnDescription = "代码", Length = 36)]
    public string Code { get; set; }
    /// <summary>
    /// 父
    /// </summary>
    [SugarColumn(ColumnDescription = "父")]
    public long  ParentId { get; set; }//父级字段
    /// <summary>
    /// 子类
    /// </summary>
    [SugarColumn(IsIgnore = true)]
    public List<FBS_EnterpriseType> Child { get; set; }
}
Admin.NET/FZCZTB.NET.MD/CutomerMd/Extend/FBS_CusExtend.cs
New file
@@ -0,0 +1,189 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Admin.NET.Core;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FZCZTB.NET.MD.CutomerMd.Extend;
/// <summary>
/// 用户扩展表 企业资料
/// </summary>
[SugarTable("FBS_CusExtend", "企业资料")]
[IncreTable]
public class FBS_CusExtend: EntityBaseTenant
{
    /// <summary>
    /// 用户ID
    /// </summary>
    [SugarColumn(ColumnDescription = "用户ID" )]
    [Required(ErrorMessage = "用户ID 必填")]
    public int CustomerId { get; set; }
    /// <summary>
    /// 企业类型 从企业类型中选择
    /// </summary>
    [SugarColumn(ColumnDescription = "企业类型 ", Length = 255)]
    [Required(ErrorMessage = "企业类型")]
    public string EnterpriseType { get; set; }
    /// <summary>
    /// 交易主体 代码 同角色代码
    /// </summary>
    [Required(ErrorMessage = "交易主体代码为必填项")]
    [SugarColumn(ColumnDescription = "交易主体code")]
    public string TransactionCode { get; set; }
    /// <summary>
    /// 营业执照文件路径
    /// </summary>
    [Required(ErrorMessage = "营业执照文件必传")]
    [SugarColumn(ColumnDescription = "营业执照文件路径")]
    public string BusinessLicense { get; set; }
    /// <summary>
    /// 企业名称
    /// </summary>
    [Required(ErrorMessage = "企业名称为必填项")]
    [SugarColumn(ColumnDescription = "企业名称")]
    public string EnterpriseName { get; set; }
    /// <summary>
    /// 统一社会信用代码
    /// </summary>
    [Required(ErrorMessage = "统一社会信用代码为必填项")]
    [SugarColumn(ColumnDescription = "统一社会信用代码")]
    public string UnifiedSocialCreditCode { get; set; }
    /// <summary>
    /// 注册资金(单位:万元 )
    /// </summary>
    [Required(ErrorMessage = "注册资金为必填项")]
    [SugarColumn(ColumnDescription = "注册资金")]
    public decimal RegisteredCapital { get; set; }
    /// <summary>
    /// 法定代表人姓名
    /// </summary>
    [Required(ErrorMessage = "法定代表人姓名为必填项")]
    [SugarColumn(ColumnDescription = "法定代表人姓名")]
    public string LegalRepresentativeName { get; set; }
    /// <summary>
    /// 法定代表人身份证文件路径
    /// </summary>
    [Required(ErrorMessage = "法定代表人身份证文件路径毕传")]
    [SugarColumn(ColumnDescription = "法定代表人身份证文件路径")]
    public string LegalRepresentativeIdCard { get; set; }
    /// <summary>
    /// 法定代表人身份证号码
    /// </summary>
    [Required(ErrorMessage = "法定代表人身份证号码为必填项")]
    [SugarColumn(ColumnDescription = "法定代表人姓名")]
    public string LegalRepresentativeIdNumber { get; set; }
    /// <summary>
    /// 法定代表人联系电话
    /// </summary>
    [Required(ErrorMessage = "法定代表人联系电话为必填项")]
    [Phone(ErrorMessage = "联系电话格式不正确")]
    [SugarColumn(ColumnDescription = "法定代表人姓名")]
    public string LegalRepresentativePhone { get; set; }
    /// <summary>
    /// 企业住所(下拉选择,需结合实际可选值,先字符串接收 )
    /// </summary>
    [Required(ErrorMessage = "企业住所为必填项")]
    [SugarColumn(ColumnDescription = "企业住所")]
    public string Residence { get; set; }
    /// <summary>
    /// 企业联系电话
    /// </summary>
    [Required(ErrorMessage = "企业联系电话为必填项")]
    [Phone(ErrorMessage = "联系电话格式不正确")]
    [SugarColumn(ColumnDescription = "企业联系电话")]
    public string EnterprisePhone { get; set; }
    /// <summary>
    /// 企业成立时间
    /// </summary>
    [Required(ErrorMessage = "成立时间为必填项")]
    [SugarColumn(ColumnDescription = "企业成立时间")]
    public DateTime EstablishmentTime { get; set; }
    /// <summary>
    /// 企业主营业务
    /// </summary>
    [Required(ErrorMessage = "主营业务为必填项")]
    [SugarColumn(ColumnDescription = "企业主营业务")]
    public string MainBusiness { get; set; }
    /// <summary>
    /// 企业邮箱
    /// </summary>
    [Required(ErrorMessage = "企业邮箱为必填项")]
    [EmailAddress(ErrorMessage = "邮箱格式不正确")]
    [SugarColumn(ColumnDescription = "企业邮箱")]
    public string EnterpriseEmail { get; set; }
    /// <summary>
    /// 业务经办人姓名
    /// </summary>
    [SugarColumn(ColumnDescription = "业务经办人姓名", IsNullable = true)]
    public string OperatorName { get; set; }
    /// <summary>
    /// 业务经办人身份证文件路径或标识(上传后存储信息)
    /// </summary>
    [SugarColumn(ColumnDescription = "业务经办人身份证文件路径或标识", IsNullable = true)]
    public string OperatorIdCard { get; set; }
    /// <summary>
    /// 业务经办人身份证号码
    /// </summary>
    [SugarColumn(ColumnDescription = "法定代表人姓名", IsNullable = true)]
    public string OperatorIdNumber { get; set; }
    /// <summary>
    /// 业务经办人联系电话
    /// </summary>
    [Phone(ErrorMessage = "联系电话格式不正确")]
    [SugarColumn(ColumnDescription = "法定代表人姓名",IsNullable =true)]
    public string OperatorPhone { get; set; }
    /// <summary>
    /// 企业开户行
    /// </summary>
    [Required(ErrorMessage = "开户行为必填项")]
    [SugarColumn(ColumnDescription = "法定代表人姓名")]
    public string BankName { get; set; }
    /// <summary>
    /// 企业银行账号
    /// </summary>
    [Required(ErrorMessage = "银行账号为必填项")]
    [SugarColumn(ColumnDescription = "法定代表人姓名")]
    public string BankAccount { get; set; }
    /// <summary>
    /// 审核步骤
    /// </summary>
    [SugarColumn(ColumnDescription = "审核步骤")]
    public int steps { set; get; } = 0;
}
Admin.NET/FZCZTB.NET.MD/CutomerMd/FBS_CoutomerExRole.cs
New file
@@ -0,0 +1,41 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Admin.NET.Core;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FZCZTB.NET.MD.CutomerMd;
/// <summary>
/// 外部角色表
/// </summary>
[SugarTable("FBS_CoutomerExRole", "外部角色表")]
[IncreTable]
public class FBS_CoutomerExRole: EntityBaseId
{
    //客户ID
    public long CustomerId { get; set; }
    /// <summary>
    ///  外部角色
    /// </summary>
    public long ExRoleId { get; set; }
    /// <summary>
    /// 是否完成角色创建
    /// </summary>
    public bool HasFlsh { get; set; }= false;
    /// <summary>
    /// 外部角色
    /// </summary>
    [Navigate(NavigateType.OneToOne, nameof(ExRoleId))]
    public FBS_ExRole ExRole { get; set; }
}
Admin.NET/FZCZTB.NET.MD/CutomerMd/FBS_Customer.cs
@@ -21,7 +21,7 @@
[SugarTable("FBS_Customer", "客户表")]
[SugarIndex("index_{table}_A", nameof(Account), OrderByType.Asc)]
[SugarIndex("index_{table}_P", nameof(Phone), OrderByType.Asc)]
[IncreTable]
public partial class FBS_Customer : EntityBaseTenant
{
    /// <summary>
@@ -34,10 +34,9 @@
    /// <summary>
    /// 密码
    /// </summary>
    [SugarColumn(ColumnDescription = "密码", Length = 512)]
    [SugarColumn(ColumnDescription = "密码", Length = 512,IsNullable =true)]
    [MaxLength(512)]
    [Newtonsoft.Json.JsonIgnore]
    [System.Text.Json.Serialization.JsonIgnore]
    public virtual string Password { get; set; }
    /// <summary>
@@ -61,30 +60,30 @@
    [MaxLength(512)]
    public string? Avatar { get; set; }
    /// <summary>
    /// 性别-男_1、女_2
    /// </summary>
    [SugarColumn(ColumnDescription = "性别")]
    public GenderEnum Sex { get; set; } = GenderEnum.Male;
    ///// <summary>
    ///// 性别-男_1、女_2
    ///// </summary>
    //[SugarColumn(ColumnDescription = "性别")]
    //public GenderEnum Sex { get; set; } = GenderEnum.Male;
    /// <summary>
    /// 年龄
    /// </summary>
    [SugarColumn(ColumnDescription = "年龄")]
    public int Age { get; set; }
    ///// <summary>
    ///// 年龄
    ///// </summary>
    //[SugarColumn(ColumnDescription = "年龄")]
    //public int Age { get; set; }
    /// <summary>
    /// 出生日期
    /// </summary>
    [SugarColumn(ColumnDescription = "出生日期")]
    public DateTime? Birthday { get; set; }
    ///// <summary>
    ///// 出生日期
    ///// </summary>
    //[SugarColumn(ColumnDescription = "出生日期")]
    //public DateTime? Birthday { get; set; }
    /// <summary>
    /// 民族
    /// </summary>
    [SugarColumn(ColumnDescription = "民族", Length = 32)]
    [MaxLength(32)]
    public string? Nation { get; set; }
    ///// <summary>
    ///// 民族
    ///// </summary>
    //[SugarColumn(ColumnDescription = "民族", Length = 32)]
    //[MaxLength(32)]
    //public string? Nation { get; set; }
    /// <summary>
    /// 手机号码
@@ -93,26 +92,26 @@
    [MaxLength(16)]
    public string? Phone { get; set; }
    /// <summary>
    /// 证件类型
    /// </summary>
    [SugarColumn(ColumnDescription = "证件类型")]
    public CardTypeEnum CardType { get; set; }
    ///// <summary>
    ///// 证件类型
    ///// </summary>
    //[SugarColumn(ColumnDescription = "证件类型")]
    //public CardTypeEnum CardType { get; set; }
    /// <summary>
    /// 身份证号
    /// </summary>
    [SugarColumn(ColumnDescription = "身份证号", Length = 32)]
    [MaxLength(32)]
    public string? IdCardNum { get; set; }
    ///// <summary>
    ///// 身份证号
    ///// </summary>
    //[SugarColumn(ColumnDescription = "身份证号", Length = 32)]
    //[MaxLength(32)]
    //public string? IdCardNum { get; set; }
    /// <summary>
    /// 身份证号
    /// </summary>
    [SugarColumn(ColumnDescription = "身份证", Length = 512)]
    [MaxLength(512)]
    public string? IdCardPath { get; set; }
    ///// <summary>
    ///// 身份证号
    ///// </summary>
    //[SugarColumn(ColumnDescription = "身份证", Length = 512)]
    //[MaxLength(512)]
    //public string? IdCardPath { get; set; }
    /// <summary>
    /// 邮箱
@@ -121,73 +120,73 @@
    [MaxLength(64)]
    public string? Email { get; set; }
    /// <summary>
    /// 地址
    /// </summary>
    [SugarColumn(ColumnDescription = "地址", Length = 256)]
    [MaxLength(256)]
    public string? Address { get; set; }
    ///// <summary>
    ///// 地址
    ///// </summary>
    //[SugarColumn(ColumnDescription = "地址", Length = 256)]
    //[MaxLength(256)]
    //public string? Address { get; set; }
    /// <summary>
    /// 文化程度
    /// </summary>
    [SugarColumn(ColumnDescription = "文化程度")]
    public CultureLevelEnum CultureLevel { get; set; }
    ///// <summary>
    ///// 文化程度
    ///// </summary>
    //[SugarColumn(ColumnDescription = "文化程度")]
    //public CultureLevelEnum CultureLevel { get; set; }
    /// <summary>
    /// 政治面貌
    /// </summary>
    [SugarColumn(ColumnDescription = "政治面貌", Length = 16)]
    [MaxLength(16)]
    public string? PoliticalOutlook { get; set; }
    ///// <summary>
    ///// 政治面貌
    ///// </summary>
    //[SugarColumn(ColumnDescription = "政治面貌", Length = 16)]
    //[MaxLength(16)]
    //public string? PoliticalOutlook { get; set; }
    /// <summary>
    /// 毕业院校
    /// </summary>COLLEGE
    [SugarColumn(ColumnDescription = "毕业院校", Length = 128)]
    [MaxLength(128)]
    public string? College { get; set; }
    ///// <summary>
    ///// 毕业院校
    ///// </summary>COLLEGE
    //[SugarColumn(ColumnDescription = "毕业院校", Length = 128)]
    //[MaxLength(128)]
    //public string? College { get; set; }
    /// <summary>
    /// 办公电话
    /// </summary>
    [SugarColumn(ColumnDescription = "办公电话", Length = 16)]
    [MaxLength(16)]
    public string? OfficePhone { get; set; }
    ///// <summary>
    ///// 办公电话
    ///// </summary>
    //[SugarColumn(ColumnDescription = "办公电话", Length = 16)]
    //[MaxLength(16)]
    //public string? OfficePhone { get; set; }
    /// <summary>
    /// 紧急联系人
    /// </summary>
    [SugarColumn(ColumnDescription = "紧急联系人", Length = 32)]
    [MaxLength(32)]
    public string? EmergencyContact { get; set; }
    ///// <summary>
    ///// 紧急联系人
    ///// </summary>
    //[SugarColumn(ColumnDescription = "紧急联系人", Length = 32)]
    //[MaxLength(32)]
    //public string? EmergencyContact { get; set; }
    /// <summary>
    /// 紧急联系人电话
    /// </summary>
    [SugarColumn(ColumnDescription = "紧急联系人电话", Length = 16)]
    [MaxLength(16)]
    public string? EmergencyPhone { get; set; }
    ///// <summary>
    ///// 紧急联系人电话
    ///// </summary>
    //[SugarColumn(ColumnDescription = "紧急联系人电话", Length = 16)]
    //[MaxLength(16)]
    //public string? EmergencyPhone { get; set; }
    /// <summary>
    /// 紧急联系人地址
    /// </summary>
    [SugarColumn(ColumnDescription = "紧急联系人地址", Length = 256)]
    [MaxLength(256)]
    public string? EmergencyAddress { get; set; }
    ///// <summary>
    ///// 紧急联系人地址
    ///// </summary>
    //[SugarColumn(ColumnDescription = "紧急联系人地址", Length = 256)]
    //[MaxLength(256)]
    //public string? EmergencyAddress { get; set; }
    /// <summary>
    /// 个人简介
    /// </summary>
    [SugarColumn(ColumnDescription = "个人简介", Length = 512)]
    [MaxLength(512)]
    public string? Introduction { get; set; }
    ///// <summary>
    ///// 个人简介
    ///// </summary>
    //[SugarColumn(ColumnDescription = "个人简介", Length = 512)]
    //[MaxLength(512)]
    //public string? Introduction { get; set; }
    /// <summary>
    /// 排序
    /// </summary>
    [SugarColumn(ColumnDescription = "排序")]
    public int OrderNo { get; set; } = 100;
    ///// <summary>
    ///// 排序
    ///// </summary>
    //[SugarColumn(ColumnDescription = "排序")]
    //public int OrderNo { get; set; } = 100;
    /// <summary>
    /// 状态
@@ -255,46 +254,46 @@
    //[MaxLength(32)]
    //public string? JobNum { get; set; }
    /// <summary>
    /// 职级
    /// </summary>
    [SugarColumn(ColumnDescription = "职级", Length = 32)]
    [MaxLength(32)]
    public string? PosLevel { get; set; }
    ///// <summary>
    ///// 职级
    ///// </summary>
    //[SugarColumn(ColumnDescription = "职级", Length = 32)]
    //[MaxLength(32)]
    //public string? PosLevel { get; set; }
    /// <summary>
    /// 职称
    /// </summary>
    [SugarColumn(ColumnDescription = "职称", Length = 32)]
    [MaxLength(32)]
    public string? PosTitle { get; set; }
    ///// <summary>
    ///// 职称
    ///// </summary>
    //[SugarColumn(ColumnDescription = "职称", Length = 32)]
    //[MaxLength(32)]
    //public string? PosTitle { get; set; }
    /// <summary>
    /// 擅长领域
    /// </summary>
    [SugarColumn(ColumnDescription = "擅长领域", Length = 32)]
    [MaxLength(32)]
    public string? Expertise { get; set; }
    ///// <summary>
    ///// 擅长领域
    ///// </summary>
    //[SugarColumn(ColumnDescription = "擅长领域", Length = 32)]
    //[MaxLength(32)]
    //public string? Expertise { get; set; }
    /// <summary>
    /// 办公区域
    /// </summary>
    [SugarColumn(ColumnDescription = "办公区域", Length = 32)]
    [MaxLength(32)]
    public string? OfficeZone { get; set; }
    ///// <summary>
    ///// 办公区域
    ///// </summary>
    //[SugarColumn(ColumnDescription = "办公区域", Length = 32)]
    //[MaxLength(32)]
    //public string? OfficeZone { get; set; }
    /// <summary>
    /// 办公室
    /// </summary>
    [SugarColumn(ColumnDescription = "办公室", Length = 32)]
    [MaxLength(32)]
    public string? Office { get; set; }
    ///// <summary>
    ///// 办公室
    ///// </summary>
    //[SugarColumn(ColumnDescription = "办公室", Length = 32)]
    //[MaxLength(32)]
    //public string? Office { get; set; }
    /// <summary>
    /// 入职日期
    /// </summary>
    [SugarColumn(ColumnDescription = "入职日期")]
    public DateTime? JoinDate { get; set; }
    ///// <summary>
    ///// 入职日期
    ///// </summary>
    //[SugarColumn(ColumnDescription = "入职日期")]
    //public DateTime? JoinDate { get; set; }
    /// <summary>
    /// 最新登录Ip
@@ -330,6 +329,12 @@
    [MaxLength(512)]
    public string? Signature { get; set; }
    /// <summary>
    /// 外部规则,登录角色
    /// </summary>
    [Navigate(NavigateType.OneToMany, nameof(FBS_CoutomerExRole.CustomerId))]
    public List<FBS_CoutomerExRole> CoutomerExRols { set; get; }
    ///// <summary>
    ///// 验证超级管理员类型,若账号类型为超级管理员则报错
    ///// </summary>
Admin.NET/FZCZTB.NET.MD/CutomerMd/FBS_CustomerRole.cs
@@ -14,7 +14,7 @@
namespace FZCZTB.NET.MD.CutomerMd;
[SugarTable("FBS_CustomerRole", "客户菜单表")]
[IncreTable]
public class FBS_CustomerRole : EntityBaseId
{
    /// <summary>
@@ -42,4 +42,9 @@
    /// </summary>
    [Navigate(NavigateType.OneToOne, nameof(RoleId))]
    public SysRole SysRole { get; set; }
}
Admin.NET/FZCZTB.NET.MD/CutomerMd/FBS_ExRole.cs
@@ -18,10 +18,10 @@
/// 登录即要选择的校色
/// </summary>
[SugarTable("FBS_TenantMenu", "客户主分类角色")]
[SugarTable("FBS_ExRole", "客户主分类角色")]
[SugarIndex("index_{table}_N", nameof(Name), OrderByType.Asc)]
[SugarIndex("index_{table}_C", nameof(Code), OrderByType.Asc)]
[IncreTable]
public class FBS_ExRole: EntityBaseTenant
{
    /// <summary>
Admin.NET/FZCZTB.NET.MD/CutomerMd/FBS_ExRoleMenu.cs
@@ -16,8 +16,8 @@
/// <summary>
/// 系统租户菜单表
/// </summary>
[SugarTable("FBS_TenantMenu", "系统租户菜单表")]
[SugarTable("FBS_ExRoleMenu", "系统租户菜单表")]
[IncreTable]
class FBS_ExRoleMenu: EntityBaseId
{
    /// <summary>
Admin.NET/FZCZTB.NET.MD/CutomerMd/FBS_Menu.cs
@@ -20,7 +20,7 @@
[SugarTable("FBS_Menu", "客户系统菜单表")]
[SugarIndex("index_{table}_T", nameof(Title), OrderByType.Asc)]
[SugarIndex("index_{table}_T2", nameof(Type), OrderByType.Asc)]
[IncreTable]
public class FBS_Menu: EntityBaseTenant
{
    /// <summary>
Admin.NET/FZCZTB.NET.MD/CutomerMd/FBS_Role.cs
@@ -19,7 +19,7 @@
[SugarTable("FBS_Role", "客户个性化角色")]
[SugarIndex("index_{table}_N", nameof(Name), OrderByType.Asc)]
[SugarIndex("index_{table}_C", nameof(Code), OrderByType.Asc)]
[IncreTable]
public class FBS_Role : EntityBaseTenant
{
    /// <summary>
Admin.NET/FZCZTB.NET.MD/CutomerMd/FBS_RoleMenu.cs
@@ -14,7 +14,8 @@
namespace FZCZTB.NET.MD.CutomerMd;
[SugarTable("FBS_Role", "个性化校色菜单")]
[SugarTable("FBS_RoleMenu", "个性化校色菜单")]
[IncreTable]
public class FBS_RoleMenu: EntityBaseId
{
Admin.NET/FZCZTB.NET.SYSService/CustomerSYS/CustomerManagerS.cs
@@ -3,6 +3,7 @@
using Furion.DependencyInjection;
using FZCZTB.NET.MD.CutomerMd;
using Mapster;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
@@ -44,12 +45,12 @@
        /// 返回当前可用的用户角色 再注册和登录时需要返回编码
        /// </summary>
        /// <returns></returns>
        public async Task<CustomerDto> GetCustomer(int id)
        public async Task<CustomerRDto> GetCustomer(int id)
        {
            return (await _fBS_CustomerRep.GetFirstAsync(x => x.Id == id && x.Status == StatusEnum.Enable)).Adapt<CustomerDto>();
            return (await _fBS_CustomerRep.GetFirstAsync(x => x.Id == id && x.Status == StatusEnum.Enable)).Adapt<CustomerRDto>();
@@ -62,12 +63,12 @@
        /// 返回当前可用的用户角色 再注册和登录时需要返回编码
        /// </summary>
        /// <returns></returns>
        public async Task<CustomerDto> UpDataCustomer(int id)
        public async Task<CustomerRDto> UpDataCustomer(int id)
        {
            return (await _fBS_CustomerRep.GetFirstAsync(x => x.Id == id && x.Status == StatusEnum.Enable)).Adapt<CustomerDto>();
            return (await _fBS_CustomerRep.GetFirstAsync(x => x.Id == id && x.Status == StatusEnum.Enable)).Adapt<CustomerRDto>();
@@ -131,209 +132,95 @@
    /// <summary>
    /// 客户表输出参数
    /// </summary>
    public class CustomerDto
    public class CustomerRDto
    {
        /// <summary>
        /// 主键Id
        /// </summary>
        public long Id { get; set; }
        /// <summary>
        /// 账号
        /// </summary>
        public string Account { get; set; }
        public virtual string Account { get; set; }
        /// <summary>
        /// 密码
        /// </summary>
        public string Password { get; set; }
        [MaxLength(512)]
        public virtual string Password { get; set; }
        /// <summary>
        /// 真实姓名
        /// </summary>
        public string RealName { get; set; }
        [MaxLength(32)]
        public virtual string RealName { get; set; }
        /// <summary>
        /// 昵称
        /// </summary>
        [MaxLength(32)]
        public string? NickName { get; set; }
        /// <summary>
        /// 头像
        /// </summary>
        [MaxLength(512)]
        public string? Avatar { get; set; }
        /// <summary>
        /// 性别
        /// </summary>
        public GenderEnum Sex { get; set; }
        /// <summary>
        /// 年龄
        /// </summary>
        public int Age { get; set; }
        /// <summary>
        /// 出生日期
        /// </summary>
        public DateTime? Birthday { get; set; }
        /// <summary>
        /// 民族
        /// </summary>
        public string? Nation { get; set; }
        /// <summary>
        /// 手机号码
        /// </summary>
        [MaxLength(16)]
        public string? Phone { get; set; }
        /// <summary>
        /// 证件类型
        /// </summary>
        public CardTypeEnum CardType { get; set; }
        /// <summary>
        /// 身份证号
        /// </summary>
        public string? IdCardNum { get; set; }
        /// <summary>
        /// 身份证
        /// </summary>
        public string? IdCardPath { get; set; }
        /// <summary>
        /// 邮箱
        /// </summary>
        [MaxLength(64)]
        public string? Email { get; set; }
        /// <summary>
        /// 地址
        /// </summary>
        public string? Address { get; set; }
        /// <summary>
        /// 文化程度
        /// </summary>
        public CultureLevelEnum CultureLevel { get; set; }
        /// <summary>
        /// 政治面貌
        /// </summary>
        public string? PoliticalOutlook { get; set; }
        /// <summary>
        /// 毕业院校
        /// </summary>
        public string? College { get; set; }
        /// <summary>
        /// 办公电话
        /// </summary>
        public string? OfficePhone { get; set; }
        /// <summary>
        /// 紧急联系人
        /// </summary>
        public string? EmergencyContact { get; set; }
        /// <summary>
        /// 紧急联系人电话
        /// </summary>
        public string? EmergencyPhone { get; set; }
        /// <summary>
        /// 紧急联系人地址
        /// </summary>
        public string? EmergencyAddress { get; set; }
        /// <summary>
        /// 个人简介
        /// </summary>
        public string? Introduction { get; set; }
        /// <summary>
        /// 排序
        /// </summary>
        public int OrderNo { get; set; }
        /// <summary>
        /// 状态
        /// </summary>
        public StatusEnum Status { get; set; }
        /// <summary>
        /// 备注
        /// </summary>
        [MaxLength(256)]
        public string? Remark { get; set; }
        /// <summary>
        /// 职级
        /// </summary>
        public string? PosLevel { get; set; }
        /// <summary>
        /// 职称
        /// </summary>
        public string? PosTitle { get; set; }
        /// <summary>
        /// 擅长领域
        /// </summary>
        public string? Expertise { get; set; }
        /// <summary>
        /// 办公区域
        /// </summary>
        public string? OfficeZone { get; set; }
        /// <summary>
        /// 办公室
        /// </summary>
        public string? Office { get; set; }
        /// <summary>
        /// 入职日期
        /// </summary>
        public DateTime? JoinDate { get; set; }
        /// <summary>
        /// 最新登录Ip
        /// </summary>
        public string? LastLoginIp { get; set; }
        /// <summary>
        /// 最新登录地点
        /// </summary>
        public string? LastLoginAddress { get; set; }
        /// <summary>
        /// 最新登录时间
        /// </summary>
        public DateTime? LastLoginTime { get; set; }
        /// <summary>
        /// 最新登录设备
        /// </summary>
        public string? LastLoginDevice { get; set; }
        /// <summary>
        /// 电子签名
        /// </summary>
        public string? Signature { get; set; }
        /// <summary>
        /// 租户Id
        /// 外部规则,登录角色
        /// </summary>
        public long? TenantId { get; set; }
        [Navigate(NavigateType.OneToMany, nameof(FBS_CoutomerExRole.CustomerId))]
        public List<FBS_CoutomerExRole> CoutomerExRols { set; get; }
        /// <summary>
        /// 注册用户角色
        /// 注册用户角色
        /// </summary>
        public  string? ExRoleCode {  get; set; }
        /// <summary>
        /// 用户注册手机验证码
        /// </summary>
        public string? PhoneVCode {  get; set; }
    }
}
Admin.NET/FZCZTB.NET.SYSService/FZCZTB.NET.SYSService.csproj
@@ -4,6 +4,7 @@
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <GenerateDocumentationFile>True</GenerateDocumentationFile>
  </PropertyGroup>
  <ItemGroup>
@@ -11,4 +12,10 @@
    <ProjectReference Include="..\FZCZTB.NET.MD\FZCZTB.NET.MD.csproj" />
  </ItemGroup>
  <ItemGroup>
    <None Update="FZCZTSYSServiceConfig.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>
Admin.NET/FZCZTB.NET.SYSService/FZCZTSYSServiceConfig.json
@@ -1,16 +1,19 @@
{
  "SMSConfigMd": {
    "LingKaiXinxiSets": { //凌凯短信配置
  "SMSConfig": {
    "lingKai": { //凌凯短信配置
      "Url": "https://mb345.com/ws/BatchSend2.aspx",
      "Number": "XP010534",
      "AccountPassPassword": "123321",
      "VerifyTimeOut": 60,  //秒
      "VerifyTimeOut": "60", //秒
      "Templates": [
        {
          "Id": "0",
          "SignName": "【政采咨询网】", //需要加入的公司标签
          "TemplateCode": "VCode",
          "Content": "您的验证码为:${code},请勿泄露于他人!"
          "Content": "您好,您的验证码是:${code}"
        },
        {
          "Id": "1",
Admin.NET/FZCZTB.NET.SYSService/MSM/SMSConfigMd.cs
@@ -16,7 +16,7 @@
public class SMSConfigOptions : IConfigurableOptions
{
    /// <summary>
    /// 凌凯信息配置
    /// </summary>
@@ -40,9 +40,9 @@
    /// <summary>
    /// 超时时间
    /// </summary>
    public int VerifyTimeOut {  get; set; }
    public string VerifyTimeOut {  get; set; }
    /// <summary>
    /// Templates
    /// </summary>
    public List<SmsTemplate> Templates { get; set; }
    public List<SmsTemplate>? Templates { get; set; }
}
Admin.NET/FZCZTB.NET.SYSService/MSM/ZCSMSService.cs
@@ -28,6 +28,7 @@
using Yitter.IdGenerator;
using Lazy.Captcha.Core;
using Furion;
using NewLife;
namespace FZCZTB.NET.SYSService.MSM;
/// <summary>
@@ -45,8 +46,10 @@
          ICaptcha captcha
          )
    {
        _smsOptions = smsOptions.Value;
        _sysCacheService = _sysCacheService;
        _sysCacheService = sysCacheService;
         _captcha = captcha;
    }
@@ -73,7 +76,7 @@
                return Temp.Content+Temp.SignName;
        }
        if (Code == null)
            Oops.Oh("短信内容为空");
            throw Oops.Oh("短信内容为空");
        return Code;
    }
@@ -134,7 +137,7 @@
        if (string.IsNullOrWhiteSpace(verifyCode)) throw Oops.Oh("验证码不存在或已失效,请重新获取!");
        if (verifyCode != input.Code) throw Oops.Oh("验证码错误!");
        _sysCacheService.Remove($"{CacheConst.KeyPhoneVerCode}{input.Phone}");
        return true;
    }
    /// <summary>
@@ -164,13 +167,13 @@
        };
       var code=   FormartMessage(verifyCode.toString());
       
        await SendSMSAsync(code, phoneNumber);
       await SendSMSAsync(code, phoneNumber);
   
        _sysCacheService.Set($"{CacheConst.KeyPhoneVerCode}{phoneNumber}", verifyCode, TimeSpan.FromSeconds(_smsOptions.lingKai.VerifyTimeOut));
        _sysCacheService.Set($"{CacheConst.KeyPhoneVerCode}{phoneNumber}", verifyCode, TimeSpan.FromSeconds(_smsOptions.lingKai.VerifyTimeOut.ToInt()));
     
        await Task.CompletedTask;
    }
    /// <summary>
Admin.NET/FZCZTB.NET.SYSService/Startup.cs
New file
@@ -0,0 +1,32 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Admin.NET.Core;
using AspNetCoreRateLimit;
using Furion;
using FZCZTB.NET.SYSService.MSM;
using IPTools.Core;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FZCZTB.NET.SYSService;
[AppStartup(int.MaxValue -1)]
public class Startup: AppStartup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddConfigurableOptions<SMSConfigOptions>();
    }
}
Admin.NET/FZCZTB.Net.CustomerSYSTem/FZCZTB.Net.CustomerSYSTem.csproj
@@ -4,6 +4,7 @@
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <GenerateDocumentationFile>True</GenerateDocumentationFile>
  </PropertyGroup>
  <ItemGroup>
Admin.NET/FZCZTB.Net.CustomerSYSTem/Service/FBS_Customer/FBS_CustomerService.cs
@@ -47,23 +47,23 @@
    {
        input.Keyword = input.Keyword?.Trim();
        var query = _fBS_CustomerRep.AsQueryable()
            .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.Account.Contains(input.Keyword) || u.RealName.Contains(input.Keyword) || u.NickName.Contains(input.Keyword) || u.IdCardPath.Contains(input.Keyword) || u.College.Contains(input.Keyword) || u.OfficePhone.Contains(input.Keyword) || u.EmergencyContact.Contains(input.Keyword) || u.EmergencyPhone.Contains(input.Keyword) || u.EmergencyAddress.Contains(input.Keyword) || u.Introduction.Contains(input.Keyword) || u.Remark.Contains(input.Keyword) || u.PosLevel.Contains(input.Keyword) || u.PosTitle.Contains(input.Keyword))
            //.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.Account.Contains(input.Keyword) || u.RealName.Contains(input.Keyword) || u.NickName.Contains(input.Keyword) || u.IdCardPath.Contains(input.Keyword) || u.College.Contains(input.Keyword) || u.OfficePhone.Contains(input.Keyword) || u.EmergencyContact.Contains(input.Keyword) || u.EmergencyPhone.Contains(input.Keyword) || u.EmergencyAddress.Contains(input.Keyword) || u.Introduction.Contains(input.Keyword) || u.Remark.Contains(input.Keyword) || u.PosLevel.Contains(input.Keyword) || u.PosTitle.Contains(input.Keyword))
            .WhereIF(!string.IsNullOrWhiteSpace(input.Account), u => u.Account.Contains(input.Account.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.RealName), u => u.RealName.Contains(input.RealName.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.NickName), u => u.NickName.Contains(input.NickName.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.IdCardPath), u => u.IdCardPath.Contains(input.IdCardPath.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.College), u => u.College.Contains(input.College.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.OfficePhone), u => u.OfficePhone.Contains(input.OfficePhone.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.EmergencyContact), u => u.EmergencyContact.Contains(input.EmergencyContact.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.EmergencyPhone), u => u.EmergencyPhone.Contains(input.EmergencyPhone.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.EmergencyAddress), u => u.EmergencyAddress.Contains(input.EmergencyAddress.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.Introduction), u => u.Introduction.Contains(input.Introduction.Trim()))
            //.WhereIF(!string.IsNullOrWhiteSpace(input.IdCardPath), u => u.IdCardPath.Contains(input.IdCardPath.Trim()))
            //.WhereIF(!string.IsNullOrWhiteSpace(input.College), u => u.College.Contains(input.College.Trim()))
            //.WhereIF(!string.IsNullOrWhiteSpace(input.OfficePhone), u => u.OfficePhone.Contains(input.OfficePhone.Trim()))
            //.WhereIF(!string.IsNullOrWhiteSpace(input.EmergencyContact), u => u.EmergencyContact.Contains(input.EmergencyContact.Trim()))
            //.WhereIF(!string.IsNullOrWhiteSpace(input.EmergencyPhone), u => u.EmergencyPhone.Contains(input.EmergencyPhone.Trim()))
            //.WhereIF(!string.IsNullOrWhiteSpace(input.EmergencyAddress), u => u.EmergencyAddress.Contains(input.EmergencyAddress.Trim()))
            //.WhereIF(!string.IsNullOrWhiteSpace(input.Introduction), u => u.Introduction.Contains(input.Introduction.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.Remark), u => u.Remark.Contains(input.Remark.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.PosLevel), u => u.PosLevel.Contains(input.PosLevel.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.PosTitle), u => u.PosTitle.Contains(input.PosTitle.Trim()))
            .WhereIF(input.CardType.HasValue, u => u.CardType == input.CardType)
            .WhereIF(input.CultureLevel.HasValue, u => u.CultureLevel == input.CultureLevel)
            .WhereIF(input.OrderNo != null, u => u.OrderNo == input.OrderNo)
            //.WhereIF(!string.IsNullOrWhiteSpace(input.PosLevel), u => u.PosLevel.Contains(input.PosLevel.Trim()))
            //.WhereIF(!string.IsNullOrWhiteSpace(input.PosTitle), u => u.PosTitle.Contains(input.PosTitle.Trim()))
            //.WhereIF(input.CardType.HasValue, u => u.CardType == input.CardType)
            //.WhereIF(input.CultureLevel.HasValue, u => u.CultureLevel == input.CultureLevel)
            //.WhereIF(input.OrderNo != null, u => u.OrderNo == input.OrderNo)
            .WhereIF(input.Status.HasValue, u => u.Status == input.Status)
            .Select<FBS_CustomerOutput>();
        return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
@@ -106,10 +106,10 @@
        var entity = input.Adapt<FBS_Customer>();
        await _fBS_CustomerRep.AsUpdateable(entity)
        .IgnoreColumns(u => new {
            u.Expertise,
            u.OfficeZone,
            u.Office,
            u.JoinDate,
            //u.Expertise,
            //u.OfficeZone,
            //u.Office,
            //u.JoinDate,
            u.LastLoginIp,
            u.LastLoginAddress,
            u.LastLoginTime,
@@ -247,22 +247,22 @@
                        .SplitError(it => it.Item.RealName?.Length > 32, "真实姓名长度不能超过32个字符")
                        .SplitError(it => it.Item.NickName?.Length > 32, "昵称长度不能超过32个字符")
                        .SplitError(it => it.Item.Avatar?.Length > 512, "头像长度不能超过512个字符")
                        .SplitError(it => it.Item.Nation?.Length > 32, "民族长度不能超过32个字符")
                        //.SplitError(it => it.Item.Nation?.Length > 32, "民族长度不能超过32个字符")
                        .SplitError(it => it.Item.Phone?.Length > 16, "手机号码长度不能超过16个字符")
                        .SplitError(it => it.Item.IdCardNum?.Length > 32, "身份证号长度不能超过32个字符")
                        .SplitError(it => it.Item.IdCardPath?.Length > 512, "身份证长度不能超过512个字符")
                        //.SplitError(it => it.Item.IdCardNum?.Length > 32, "身份证号长度不能超过32个字符")
                        //.SplitError(it => it.Item.IdCardPath?.Length > 512, "身份证长度不能超过512个字符")
                        .SplitError(it => it.Item.Email?.Length > 64, "邮箱长度不能超过64个字符")
                        .SplitError(it => it.Item.Address?.Length > 256, "地址长度不能超过256个字符")
                        .SplitError(it => it.Item.PoliticalOutlook?.Length > 16, "政治面貌长度不能超过16个字符")
                        .SplitError(it => it.Item.College?.Length > 128, "毕业院校长度不能超过128个字符")
                        .SplitError(it => it.Item.OfficePhone?.Length > 16, "办公电话长度不能超过16个字符")
                        .SplitError(it => it.Item.EmergencyContact?.Length > 32, "紧急联系人长度不能超过32个字符")
                        .SplitError(it => it.Item.EmergencyPhone?.Length > 16, "紧急联系人电话长度不能超过16个字符")
                        .SplitError(it => it.Item.EmergencyAddress?.Length > 256, "紧急联系人地址长度不能超过256个字符")
                        .SplitError(it => it.Item.Introduction?.Length > 512, "个人简介长度不能超过512个字符")
                        //.SplitError(it => it.Item.Address?.Length > 256, "地址长度不能超过256个字符")
                        //.SplitError(it => it.Item.PoliticalOutlook?.Length > 16, "政治面貌长度不能超过16个字符")
                        //.SplitError(it => it.Item.College?.Length > 128, "毕业院校长度不能超过128个字符")
                        //.SplitError(it => it.Item.OfficePhone?.Length > 16, "办公电话长度不能超过16个字符")
                        //.SplitError(it => it.Item.EmergencyContact?.Length > 32, "紧急联系人长度不能超过32个字符")
                        //.SplitError(it => it.Item.EmergencyPhone?.Length > 16, "紧急联系人电话长度不能超过16个字符")
                        //.SplitError(it => it.Item.EmergencyAddress?.Length > 256, "紧急联系人地址长度不能超过256个字符")
                        //.SplitError(it => it.Item.Introduction?.Length > 512, "个人简介长度不能超过512个字符")
                        .SplitError(it => it.Item.Remark?.Length > 256, "备注长度不能超过256个字符")
                        .SplitError(it => it.Item.PosLevel?.Length > 32, "职级长度不能超过32个字符")
                        .SplitError(it => it.Item.PosTitle?.Length > 32, "职称长度不能超过32个字符")
                        //.SplitError(it => it.Item.PosLevel?.Length > 32, "职级长度不能超过32个字符")
                        //.SplitError(it => it.Item.PosTitle?.Length > 32, "职称长度不能超过32个字符")
                        .SplitInsert(_ => true)
                        .ToStorage();
                    
Admin.NET/FZCZTB.Net.CustomerSYSTem/Service/FBS_EnterpriseType/Dto/FBS_EnterpriseTypeDto.cs
New file
@@ -0,0 +1,69 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace FZCZTB.Net.CustomerSYSTem;
/// <summary>
/// 企业类配置表输出参数
/// </summary>
public class FBS_EnterpriseTypeDto
{
    /// <summary>
    /// 主键Id
    /// </summary>
    public long Id { get; set; }
    /// <summary>
    /// 名称
    /// </summary>
    public string Name { get; set; }
    /// <summary>
    /// 代码
    /// </summary>
    public string Code { get; set; }
    /// <summary>
    /// 父
    /// </summary>
    public int ParentId { get; set; }
    /// <summary>
    /// 租户Id
    /// </summary>
    public long? TenantId { get; set; }
    /// <summary>
    /// 创建时间
    /// </summary>
    public DateTime? CreateTime { get; set; }
    /// <summary>
    /// 更新时间
    /// </summary>
    public DateTime? UpdateTime { get; set; }
    /// <summary>
    /// 创建者Id
    /// </summary>
    public long? CreateUserId { get; set; }
    /// <summary>
    /// 创建者姓名
    /// </summary>
    public string? CreateUserName { get; set; }
    /// <summary>
    /// 修改者Id
    /// </summary>
    public long? UpdateUserId { get; set; }
    /// <summary>
    /// 修改者姓名
    /// </summary>
    public string? UpdateUserName { get; set; }
}
Admin.NET/FZCZTB.Net.CustomerSYSTem/Service/FBS_EnterpriseType/Dto/FBS_EnterpriseTypeInput.cs
New file
@@ -0,0 +1,177 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Admin.NET.Core;
using System.ComponentModel.DataAnnotations;
using Magicodes.ExporterAndImporter.Core;
using Magicodes.ExporterAndImporter.Excel;
namespace FZCZTB.Net.CustomerSYSTem;
/// <summary>
/// 企业类配置表基础输入参数
/// </summary>
public class FBS_EnterpriseTypeBaseInput
{
    /// <summary>
    /// 主键Id
    /// </summary>
    public virtual long? Id { get; set; }
    /// <summary>
    /// 名称
    /// </summary>
    [Required(ErrorMessage = "名称不能为空")]
    public virtual string Name { get; set; }
    /// <summary>
    /// 代码
    /// </summary>
    [Required(ErrorMessage = "代码不能为空")]
    public virtual string Code { get; set; }
    /// <summary>
    /// 父
    /// </summary>
    [Required(ErrorMessage = "父不能为空")]
    public virtual int? ParentId { get; set; }
}
/// <summary>
/// 企业类配置表分页查询输入参数
/// </summary>
public class PageFBS_EnterpriseTypeInput : BasePageInput
{
    /// <summary>
    /// 名称
    /// </summary>
    public string Name { get; set; }
    /// <summary>
    /// 代码
    /// </summary>
    public string Code { get; set; }
    /// <summary>
    /// 父
    /// </summary>
    public int? ParentId { get; set; }
    /// <summary>
    /// 选中主键列表
    /// </summary>
     public List<long> SelectKeyList { get; set; }
}
/// <summary>
/// 企业类配置表增加输入参数
/// </summary>
public class AddFBS_EnterpriseTypeInput
{
    /// <summary>
    /// 名称
    /// </summary>
    [Required(ErrorMessage = "名称不能为空")]
    [MaxLength(256, ErrorMessage = "名称字符长度不能超过256")]
    public string Name { get; set; }
    /// <summary>
    /// 代码
    /// </summary>
    [Required(ErrorMessage = "代码不能为空")]
    [MaxLength(36, ErrorMessage = "代码字符长度不能超过36")]
    public string Code { get; set; }
    /// <summary>
    /// 父
    /// </summary>
    [Required(ErrorMessage = "父不能为空")]
    public int? ParentId { get; set; }
}
/// <summary>
/// 企业类配置表删除输入参数
/// </summary>
public class DeleteFBS_EnterpriseTypeInput
{
    /// <summary>
    /// 主键Id
    /// </summary>
    [Required(ErrorMessage = "主键Id不能为空")]
    public long? Id { get; set; }
}
/// <summary>
/// 企业类配置表更新输入参数
/// </summary>
public class UpdateFBS_EnterpriseTypeInput
{
    /// <summary>
    /// 主键Id
    /// </summary>
    [Required(ErrorMessage = "主键Id不能为空")]
    public long? Id { get; set; }
    /// <summary>
    /// 名称
    /// </summary>
    [Required(ErrorMessage = "名称不能为空")]
    [MaxLength(256, ErrorMessage = "名称字符长度不能超过256")]
    public string Name { get; set; }
    /// <summary>
    /// 代码
    /// </summary>
    [Required(ErrorMessage = "代码不能为空")]
    [MaxLength(36, ErrorMessage = "代码字符长度不能超过36")]
    public string Code { get; set; }
    /// <summary>
    /// 父
    /// </summary>
    [Required(ErrorMessage = "父不能为空")]
    public int? ParentId { get; set; }
}
/// <summary>
/// 企业类配置表主键查询输入参数
/// </summary>
public class QueryByIdFBS_EnterpriseTypeInput : DeleteFBS_EnterpriseTypeInput
{
}
/// <summary>
/// 企业类配置表数据导入实体
/// </summary>
[ExcelImporter(SheetIndex = 1, IsOnlyErrorRows = true)]
public class ImportFBS_EnterpriseTypeInput : BaseImportInput
{
    /// <summary>
    /// 名称
    /// </summary>
    [ImporterHeader(Name = "*名称")]
    [ExporterHeader("*名称", Format = "", Width = 25, IsBold = true)]
    public string Name { get; set; }
    /// <summary>
    /// 代码
    /// </summary>
    [ImporterHeader(Name = "*代码")]
    [ExporterHeader("*代码", Format = "", Width = 25, IsBold = true)]
    public string Code { get; set; }
    /// <summary>
    /// 父
    /// </summary>
    [ImporterHeader(Name = "*父")]
    [ExporterHeader("*父", Format = "", Width = 25, IsBold = true)]
    public int? ParentId { get; set; }
}
Admin.NET/FZCZTB.Net.CustomerSYSTem/Service/FBS_EnterpriseType/Dto/FBS_EnterpriseTypeOutput.cs
New file
@@ -0,0 +1,79 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Magicodes.ExporterAndImporter.Core;
namespace FZCZTB.Net.CustomerSYSTem;
/// <summary>
/// 企业类配置表输出参数
/// </summary>
public class FBS_EnterpriseTypeOutput
{
    /// <summary>
    /// 主键Id
    /// </summary>
    public long Id { get; set; }
    /// <summary>
    /// 名称
    /// </summary>
    public string Name { get; set; }
    /// <summary>
    /// 代码
    /// </summary>
    public string Code { get; set; }
    /// <summary>
    /// 父
    /// </summary>
    public int ParentId { get; set; }
    /// <summary>
    /// 租户Id
    /// </summary>
    public long? TenantId { get; set; }
    /// <summary>
    /// 创建时间
    /// </summary>
    public DateTime? CreateTime { get; set; }
    /// <summary>
    /// 更新时间
    /// </summary>
    public DateTime? UpdateTime { get; set; }
    /// <summary>
    /// 创建者Id
    /// </summary>
    public long? CreateUserId { get; set; }
    /// <summary>
    /// 创建者姓名
    /// </summary>
    public string? CreateUserName { get; set; }
    /// <summary>
    /// 修改者Id
    /// </summary>
    public long? UpdateUserId { get; set; }
    /// <summary>
    /// 修改者姓名
    /// </summary>
    public string? UpdateUserName { get; set; }
}
/// <summary>
/// 企业类配置表数据导入模板实体
/// </summary>
public class ExportFBS_EnterpriseTypeOutput : ImportFBS_EnterpriseTypeInput
{
    [ImporterHeader(IsIgnore = true)]
    [ExporterHeader(IsIgnore = true)]
    public override string Error { get; set; }
}
Admin.NET/FZCZTB.Net.CustomerSYSTem/Service/FBS_EnterpriseType/FBS_EnterpriseTypeService.cs
New file
@@ -0,0 +1,196 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Admin.NET.Core.Service;
using Microsoft.AspNetCore.Http;
using Furion.DatabaseAccessor;
using Furion.FriendlyException;
using Mapster;
using SqlSugar;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Furion.DynamicApiController;
using Furion.DependencyInjection;
using FZCZTB.NET.MD.ConfigMd;
using Admin.NET.Core;
namespace FZCZTB.Net.CustomerSYSTem;
/// <summary>
/// 企业类配置表服务 🧩
/// </summary>
[ApiDescriptionSettings(CustomerSYSTemConst.GroupName, Order = 100)]
public class FBS_EnterpriseTypeService : IDynamicApiController, ITransient
{
    private readonly SqlSugarRepository<FBS_EnterpriseType> _fBS_EnterpriseTypeRep;
    private readonly ISqlSugarClient _sqlSugarClient;
    public FBS_EnterpriseTypeService(SqlSugarRepository<FBS_EnterpriseType> fBS_EnterpriseTypeRep, ISqlSugarClient sqlSugarClient)
    {
        _fBS_EnterpriseTypeRep = fBS_EnterpriseTypeRep;
        _sqlSugarClient = sqlSugarClient;
    }
    /// <summary>
    /// 分页查询企业类配置表 🔖
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("分页查询企业类配置表")]
    [ApiDescriptionSettings(Name = "Page"), HttpPost]
    public async Task<SqlSugarPagedList<FBS_EnterpriseTypeOutput>> Page(PageFBS_EnterpriseTypeInput input)
    {
        input.Keyword = input.Keyword?.Trim();
        var query = _fBS_EnterpriseTypeRep.AsQueryable()
            .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.Name.Contains(input.Keyword) || u.Code.Contains(input.Keyword))
            .WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.Name.Contains(input.Name.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.Code), u => u.Code.Contains(input.Code.Trim()))
            .WhereIF(input.ParentId != null, u => u.ParentId == input.ParentId)
            .Select<FBS_EnterpriseTypeOutput>();
        return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
    }
    /// <summary>
    /// 获取企业类配置表详情 ℹ️
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("获取企业类配置表详情")]
    [ApiDescriptionSettings(Name = "Detail"), HttpGet]
    public async Task<FBS_EnterpriseType> Detail([FromQuery] QueryByIdFBS_EnterpriseTypeInput input)
    {
        return await _fBS_EnterpriseTypeRep.GetFirstAsync(u => u.Id == input.Id);
    }
    /// <summary>
    /// 增加企业类配置表 ➕
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("增加企业类配置表")]
    [ApiDescriptionSettings(Name = "Add"), HttpPost]
    public async Task<long> Add(AddFBS_EnterpriseTypeInput input)
    {
        var entity = input.Adapt<FBS_EnterpriseType>();
        return await _fBS_EnterpriseTypeRep.InsertAsync(entity) ? entity.Id : 0;
    }
    /// <summary>
    /// 更新企业类配置表 ✏️
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("更新企业类配置表")]
    [ApiDescriptionSettings(Name = "Update"), HttpPost]
    public async Task Update(UpdateFBS_EnterpriseTypeInput input)
    {
        var entity = input.Adapt<FBS_EnterpriseType>();
        await _fBS_EnterpriseTypeRep.AsUpdateable(entity)
        .ExecuteCommandAsync();
    }
    /// <summary>
    /// 删除企业类配置表 ❌
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("删除企业类配置表")]
    [ApiDescriptionSettings(Name = "Delete"), HttpPost]
    public async Task Delete(DeleteFBS_EnterpriseTypeInput input)
    {
        var entity = await _fBS_EnterpriseTypeRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
       // await _fBS_EnterpriseTypeRep.FakeDeleteAsync(entity);   //假删除
        await _fBS_EnterpriseTypeRep.DeleteAsync(entity);   //真删除
    }
    /// <summary>
    /// 批量删除企业类配置表 ❌
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("批量删除企业类配置表")]
    [ApiDescriptionSettings(Name = "BatchDelete"), HttpPost]
    public async Task<bool> BatchDelete([Required(ErrorMessage = "主键列表不能为空")]List<DeleteFBS_EnterpriseTypeInput> input)
    {
        var exp = Expressionable.Create<FBS_EnterpriseType>();
        foreach (var row in input) exp = exp.Or(it => it.Id == row.Id);
        var list = await _fBS_EnterpriseTypeRep.AsQueryable().Where(exp.ToExpression()).ToListAsync();
      //  return await _fBS_EnterpriseTypeRep.FakeDeleteAsync(list);   //假删除
        return await _fBS_EnterpriseTypeRep.DeleteAsync(list);   //真删除
    }
    /// <summary>
    /// 导出企业类配置表记录 🔖
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("导出企业类配置表记录")]
    [ApiDescriptionSettings(Name = "Export"), HttpPost, NonUnify]
    public async Task<IActionResult> Export(PageFBS_EnterpriseTypeInput input)
    {
        var list = (await Page(input)).Items?.Adapt<List<ExportFBS_EnterpriseTypeOutput>>() ?? new();
        if (input.SelectKeyList?.Count > 0) list = list.Where(x => input.SelectKeyList.Contains(x.Id)).ToList();
        return ExcelHelper.ExportTemplate(list, "企业类配置表导出记录");
    }
    /// <summary>
    /// 下载企业类配置表数据导入模板 ⬇️
    /// </summary>
    /// <returns></returns>
    [DisplayName("下载企业类配置表数据导入模板")]
    [ApiDescriptionSettings(Name = "Import"), HttpGet, NonUnify]
    public IActionResult DownloadTemplate()
    {
        return ExcelHelper.ExportTemplate(new List<ExportFBS_EnterpriseTypeOutput>(), "企业类配置表导入模板");
    }
    private static readonly object _fBS_EnterpriseTypeImportLock = new object();
    /// <summary>
    /// 导入企业类配置表记录 💾
    /// </summary>
    /// <returns></returns>
    [DisplayName("导入企业类配置表记录")]
    [ApiDescriptionSettings(Name = "Import"), HttpPost, NonUnify, UnitOfWork]
    public IActionResult ImportData([Required] IFormFile file)
    {
        lock (_fBS_EnterpriseTypeImportLock)
        {
            var stream = ExcelHelper.ImportData<ImportFBS_EnterpriseTypeInput, FBS_EnterpriseType>(file, (list, markerErrorAction) =>
            {
                _sqlSugarClient.Utilities.PageEach(list, 2048, pageItems =>
                {
                    // 校验并过滤必填基本类型为null的字段
                    var rows = pageItems.Where(x => {
                        if (!string.IsNullOrWhiteSpace(x.Error)) return false;
                        if (x.ParentId == null){
                            x.Error = "父不能为空";
                            return false;
                        }
                        return true;
                    }).Adapt<List<FBS_EnterpriseType>>();
                    var storageable = _fBS_EnterpriseTypeRep.Context.Storageable(rows)
                        .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Name), "名称不能为空")
                        .SplitError(it => it.Item.Name?.Length > 256, "名称长度不能超过256个字符")
                        .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Code), "代码不能为空")
                        .SplitError(it => it.Item.Code?.Length > 36, "代码长度不能超过36个字符")
                        .SplitInsert(_ => true)
                        .ToStorage();
                    storageable.AsInsertable.ExecuteCommand();// 不存在插入
                    storageable.AsUpdateable.ExecuteCommand();// 存在更新
                    // 标记错误信息
                    markerErrorAction.Invoke(storageable, pageItems, rows);
                });
            });
            return stream;
        }
    }
}
Admin.NET/cylsg.utility/CommonHelper.cs
New file
@@ -0,0 +1,1015 @@
using cylsg.utility.Extend;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http.Json;
using System.Reflection;
using System.Runtime.Loader;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using static cylsg.utility.untilityModels;
namespace cylsg.utility
{
    /// <summary>
    /// 通用帮助类
    /// </summary>
    public static class CommonHelper
    {
        #region 判断字符串是否为手机号码
        /// <summary>
        /// 判断字符串是否为手机号码
        /// </summary>
        /// <param name="mobilePhoneNumber"></param>
        /// <returns></returns>
        public static bool IsMobile(string mobilePhoneNumber)
        {
            if (mobilePhoneNumber.Length < 11)
            {
                return false;
            }
            //电信手机号码正则
            string dianxin = @"^1[345789][01379]\d{8}$";
            Regex regexDx = new Regex(dianxin);
            //联通手机号码正则
            string liantong = @"^1[345678][01256]\d{8}$";
            Regex regexLt = new Regex(liantong);
            //移动手机号码正则
            string yidong = @"^1[345789][0123456789]\d{8}$";
            Regex regexYd = new Regex(yidong);
            if (regexDx.IsMatch(mobilePhoneNumber) || regexLt.IsMatch(mobilePhoneNumber) || regexYd.IsMatch(mobilePhoneNumber))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        #endregion
        #region 检测是否符合email格式
        /// <summary>
        ///     检测是否符合email格式
        /// </summary>
        /// <param name="strEmail">要判断的email字符串</param>
        /// <returns>判断结果</returns>
        public static bool IsValidEmail(string strEmail)
        {
            return Regex.IsMatch(strEmail, @"^[\w\.]+([-]\w+)*@[A-Za-z0-9-_]+[\.][A-Za-z0-9-_]");
        }
        public static bool IsValidDoEmail(string strEmail)
        {
            return Regex.IsMatch(strEmail,
                @"^@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$");
        }
        #endregion
        #region 检测是否是正确的Url
        /// <summary>
        ///     检测是否是正确的Url
        /// </summary>
        /// <param name="strUrl">要验证的Url</param>
        /// <returns>判断结果</returns>
        public static bool IsUrl(string strUrl)
        {
            return Regex.IsMatch(strUrl,
                @"^(http|https)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{1,10}))(\:[0-9]+)*(/($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+))*$");
        }
        #endregion
        #region string 转int数组
        public static int[] StringToIntArray(string str)
        {
            try
            {
                if (string.IsNullOrEmpty(str)) return new int[0];
                if (str.EndsWith(","))
                {
                    str = str.Remove(str.Length - 1, 1);
                }
                var idstrarr = str.Split(',');
                var idintarr = new int[idstrarr.Length];
                for (int i = 0; i < idstrarr.Length; i++)
                {
                    idintarr[i] = Convert.ToInt32(idstrarr[i]);
                }
                return idintarr;
            }
            catch
            {
                return new int[0];
            }
        }
        #endregion
        #region String转数组
        public static string[] StringToStringArray(string str)
        {
            try
            {
                if (string.IsNullOrEmpty(str)) return new string[0];
                if (str.EndsWith(",")) str = str.Remove(str.Length - 1, 1);
                return str.Split(',');
            }
            catch
            {
                return new string[0];
            }
        }
        #endregion
        #region String数组转Int数组
        public static int[] StringArrAyToIntArray(string[] str)
        {
            try
            {
                int[] iNums = Array.ConvertAll(str, s => int.Parse(s));
                return iNums;
            }
            catch
            {
                return new int[0];
            }
        }
        #endregion
        #region string转Guid数组
        public static Guid[] StringToGuidArray(string str)
        {
            try
            {
                if (string.IsNullOrEmpty(str)) return new Guid[0];
                if (str.EndsWith(",")) str = str.Remove(str.Length - 1, 1);
                var strarr = str.Split(',');
                Guid[] guids = new Guid[strarr.Length];
                for (int index = 0; index < strarr.Length; index++)
                {
                    guids[index] = Guid.Parse(strarr[index]);
                }
                return guids;
            }
            catch
            {
                return new Guid[0];
            }
        }
        #endregion
        #region 转MD5
        /// <summary>
        /// 转MD5
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static string ToMd5(string str)
        {
            MD5 md5 = MD5.Create();
            // 将字符串转换成字节数组
            byte[] byteOld = Encoding.UTF8.GetBytes(str);
            // 调用加密方法
            byte[] byteNew = md5.ComputeHash(byteOld);
            // 将加密结果转换为字符串
            StringBuilder sb = new StringBuilder();
            foreach (byte b in byteNew)
            {
                // 将字节转换成16进制表示的字符串,
                sb.Append(b.ToString("x2"));
            }
            // 返回加密的字符串
            return sb.ToString();
        }
        #endregion
        #region 获取32位md5加密
        /// <summary>
        /// 通过创建哈希字符串适用于任何 MD5 哈希函数 (在任何平台) 上创建 32 个字符的十六进制格式哈希字符串
        /// </summary>
        /// <param name="source"></param>
        /// <returns>32位md5加密字符串</returns>
        public static string Md5For32(string source)
        {
            using (MD5 md5Hash = MD5.Create())
            {
                byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(source));
                StringBuilder sBuilder = new StringBuilder();
                for (int i = 0; i < data.Length; i++)
                {
                    sBuilder.Append(data[i].ToString("x2"));
                }
                string hash = sBuilder.ToString();
                return hash.ToUpper();
            }
        }
        #endregion
        #region 获取16位md5加密
        /// <summary>
        /// 获取16位md5加密
        /// </summary>
        /// <param name="source"></param>
        /// <returns>16位md5加密字符串</returns>
        public static string Md5For16(string source)
        {
            using (MD5 md5Hash = MD5.Create())
            {
                byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(source));
                //转换成字符串,并取9到25位
                string sBuilder = BitConverter.ToString(data, 4, 8);
                //BitConverter转换出来的字符串会在每个字符中间产生一个分隔符,需要去除掉
                sBuilder = sBuilder.Replace("-", "");
                return sBuilder.ToUpper();
            }
        }
        #endregion
        #region 返回当前的毫秒时间戳
        /// <summary>
        /// 返回当前的毫秒时间戳
        /// </summary>
        public static string Msectime()
        {
            long timeTicks = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000;
            return timeTicks.ToString();
        }
        #endregion
        #region 剩余多久时间文字描述
        /// <summary>
        /// 剩余多久时间
        /// </summary>
        /// <param name="remainingTime"></param>
        /// <returns>文字描述</returns>
        public static string GetRemainingTime(DateTime remainingTime)
        {
            TimeSpan timeSpan = remainingTime - DateTime.Now;
            var day = timeSpan.Days;
            var hours = timeSpan.Hours;
            var minute = timeSpan.Minutes;
            var seconds = timeSpan.Seconds;
            if (day > 0)
            {
                return day + "天" + hours + "小时" + minute + "分" + seconds + "秒";
            }
            else
            {
                if (hours > 0)
                {
                    return hours + "小时" + minute + "分" + seconds + "秒";
                }
                else
                {
                    return minute + "分" + seconds + "秒";
                }
            }
        }
        #endregion
        #region 剩余多久时间返回时间类型
        /// <summary>
        /// 剩余多久时间
        /// </summary>
        /// <param name="remainingTime"></param>
        /// <returns>返回时间类型</returns>
        public static void GetBackTime(DateTime remainingTime, out int day, out int hours, out int minute, out int seconds)
        {
            TimeSpan timeSpan = remainingTime - DateTime.Now;
            day = timeSpan.Days;
            hours = timeSpan.Hours;
            minute = timeSpan.Minutes;
            seconds = timeSpan.Seconds;
        }
        #endregion
        #region 计算时间戳剩余多久时间
        /// <summary>
        /// 计算时间戳剩余多久时间
        /// </summary>
        /// <param name="postTime">提交时间(要是以前的时间)</param>
        /// <returns></returns>
        public static string TimeAgo(DateTime postTime)
        {
            //当前时间的时间戳
            var nowtimes = ConvertTicks(DateTime.Now);
            //提交的时间戳
            var posttimes = ConvertTicks(postTime);
            //相差时间戳
            var counttime = nowtimes - posttimes;
            //进行时间转换
            if (counttime <= 60)
            {
                return "刚刚";
            }
            else if (counttime > 60 && counttime <= 120)
            {
                return "1分钟前";
            }
            else if (counttime > 120 && counttime <= 180)
            {
                return "2分钟前";
            }
            else if (counttime > 180 && counttime < 3600)
            {
                return Convert.ToInt32(counttime / 60) + "分钟前";
            }
            else if (counttime >= 3600 && counttime < 3600 * 24)
            {
                return Convert.ToInt32(counttime / 3600) + "小时前";
            }
            else if (counttime >= 3600 * 24 && counttime < 3600 * 24 * 2)
            {
                return "昨天";
            }
            else if (counttime >= 3600 * 24 * 2 && counttime < 3600 * 24 * 3)
            {
                return "前天";
            }
            else if (counttime >= 3600 * 24 * 3 && counttime <= 3600 * 24 * 7)
            {
                return Convert.ToInt32(counttime / (3600 * 24)) + "天前";
            }
            else if (counttime >= 3600 * 24 * 7 && counttime <= 3600 * 24 * 30)
            {
                return Convert.ToInt32(counttime / (3600 * 24 * 7)) + "周前";
            }
            else if (counttime >= 3600 * 24 * 30 && counttime <= 3600 * 24 * 365)
            {
                return Convert.ToInt32(counttime / (3600 * 24 * 30)) + "个月前";
            }
            else if (counttime >= 3600 * 24 * 365)
            {
                return Convert.ToInt32(counttime / (3600 * 24 * 365)) + "年前";
            }
            else
            {
                return "";
            }
        }
        /// <summary>
        /// 时间转换为秒的时间戳
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        private static long ConvertTicks(DateTime time)
        {
            long currentTicks = time.Ticks;
            DateTime dtFrom = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            long currentMillis = (currentTicks - dtFrom.Ticks) / 10000000;  //转换为秒为Ticks/10000000,转换为毫秒Ticks/10000
            return currentMillis;
        }
        #endregion
        #region 清除HTML中指定样式
        /// <summary>
        /// 清除HTML中指定样式
        /// </summary>
        /// <param name="content"></param>
        /// <param name="rule"></param>
        /// <returns></returns>
        public static string ClearHtml(string content, string[] rule)
        {
            if (!rule.Any())
            {
                return content;
            }
            foreach (var item in rule)
            {
                content = Regex.Replace(content, "/" + item + @"\s*=\s*\d+\s*/i", "");
                content = Regex.Replace(content, "/" + item + @"\s*=\s*.+?[""]/i", "");
                content = Regex.Replace(content, "/" + item + @"\s*:\s*\d+\s*px\s*;?/i", "");
            }
            return content;
        }
        #endregion
        #region list随机排序方法
        /// <summary>
        /// list随机排序方法
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="ListT"></param>
        /// <returns></returns>
        public static List<T> RandomSortList<T>(List<T> ListT)
        {
            Random random = new Random();
            List<T> newList = new List<T>();
            foreach (T item in ListT)
            {
                newList.Insert(random.Next(newList.Count + 1), item);
            }
            return newList;
        }
        #endregion
        #region 截前后字符(串)
        ///<summary>
        /// 截前后字符(串)
        ///</summary>
        ///<param name="val">原字符串</param>
        ///<param name="str">要截掉的字符串</param>
        ///<param name="all">是否贪婪</param>
        ///<returns></returns>
        public static string GetCaptureInterceptedText(string val, string str, bool all = false)
        {
            return Regex.Replace(val, @"(^(" + str + ")" + (all ? "*" : "") + "|(" + str + ")" + (all ? "*" : "") + "$)", "");
        }
        #endregion
        #region 密码加密方法
        /// <summary>
        /// 密码加密方法
        /// </summary>
        /// <param name="password">要加密的字符串</param>
        /// <param name="createTime">时间组合</param>
        /// <returns></returns>
        public static string EnPassword(string password, DateTime createTime)
        {
            var dtStr = createTime.ToString("yyyyMMddHHmmss");
            var md5 = Md5For32(password);
            var enPwd = Md5For32(md5 + dtStr);
            return enPwd;
        }
        #endregion
        #region 获取现在是星期几
        /// <summary>
        /// 获取现在是星期几
        /// </summary>
        /// <returns></returns>
        public static string GetWeek()
        {
            string week = string.Empty;
            switch (DateTime.Now.DayOfWeek)
            {
                case DayOfWeek.Monday:
                    week = "周一";
                    break;
                case DayOfWeek.Tuesday:
                    week = "周二";
                    break;
                case DayOfWeek.Wednesday:
                    week = "周三";
                    break;
                case DayOfWeek.Thursday:
                    week = "周四";
                    break;
                case DayOfWeek.Friday:
                    week = "周五";
                    break;
                case DayOfWeek.Saturday:
                    week = "周六";
                    break;
                case DayOfWeek.Sunday:
                    week = "周日";
                    break;
                default:
                    week = "N/A";
                    break;
            }
            return week;
        }
        #endregion
        #region UrlEncode (URL编码)
        /// <summary>
        /// UrlEncode (URL编码)
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static string UrlEncode(string str)
        {
            StringBuilder sb = new StringBuilder();
            byte[] byStr = Encoding.UTF8.GetBytes(str); //默认是System.Text.Encoding.Default.GetBytes(str)
            for (int i = 0; i < byStr.Length; i++)
            {
                sb.Append(@"%" + Convert.ToString(byStr[i], 16));
            }
            return sb.ToString();
        }
        #endregion
        #region 获取10位时间戳
        /// <summary>
        /// 获取10位时间戳
        /// </summary>
        /// <returns></returns>
        public static long GetTimeStampByTotalSeconds()
        {
            TimeSpan ts = DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds);
        }
        #endregion
        #region 获取13位时间戳
        /// <summary>
        /// 获取13位时间戳
        /// </summary>
        /// <returns></returns>
        public static long GetTimeStampByTotalMilliseconds()
        {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalMilliseconds);
        }
        /// <summary>
        /// 获取时间戳
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static int GetDateTimeStamp(DateTime dt)
        {
            DateTime dateStart = new DateTime(1970, 1, 1, 0, 0, 0);
            int timeStamp = Convert.ToInt32((dt.ToUniversalTime() - dateStart).TotalSeconds);
            return timeStamp;
        }
        #endregion
        #region 获取随机字符串
        /// <summary>
        /// 获取随机字符串
        /// </summary>
        /// <returns></returns>
        public static string GetSerialNumber()
        {
            var str = string.Empty;
            Random rand = new Random();
            var charsStr2 = new[] { 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'P', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' };
            var charsLen2 = charsStr2.Length - 1;
            //    shuffle($chars);
            str = "";
            for (int i = 0; i < 16; i++)
            {
                str += charsStr2[rand.Next(0, charsLen2)];
            }
            return str;
        }
        #endregion
        #region Sha1签名
        /// <summary>
        /// Sha1签名
        /// </summary>
        /// <param name="str">内容</param>
        /// <param name="encoding">编码</param>
        /// <returns></returns>
        public static string Sha1Signature(string str, Encoding encoding = null)
        {
            if (encoding == null) encoding = Encoding.UTF8;
            var buffer = encoding.GetBytes(str);
            var data = SHA1.Create().ComputeHash(buffer);
            StringBuilder sub = new StringBuilder();
            foreach (var t in data)
            {
                sub.Append(t.ToString("x2"));
            }
            return sub.ToString();
        }
        #endregion
        /// <summary>
        ///  精确计算base64字符串文件大小(单位:B)
        ///  param base64String
        ///  return double 字节大小
        /// </summary>
        /// <param name="base64String"></param>
        /// <returns></returns>
        public static double Base64FileSize(string base64String)
        {
            //检测是否含有base64,文件头)
            if (base64String.LastIndexOf(",", StringComparison.Ordinal) > -1)
            {
                base64String = base64String[(base64String.LastIndexOf(",", StringComparison.Ordinal) + 1)..];
            }
            //获取base64字符串长度(不含data:audio/wav;base64,文件头)
            var size0 = base64String.Length;
            if (size0 <= 10) return size0 - (double)size0 / 8 * 2;
            //获取字符串的尾巴的最后10个字符,用于判断尾巴是否有等号,正常生成的base64文件'等号'不会超过4个
            var tail = base64String[(size0 - 10)..];
            //找到等号,把等号也去掉,(等号其实是空的意思,不能算在文件大小里面)
            int equalIndex = tail.IndexOf("=", StringComparison.Ordinal);
            if (equalIndex > 0)
            {
                size0 = size0 - (10 - equalIndex);
            }
            //计算后得到的文件流大小,单位为字节
            return size0 - (double)size0 / 8 * 2;
        }
        /// <summary>
        /// 判断文件大小
        /// </summary>
        /// <param name="base64"></param>
        /// <param name="size"></param>
        /// <param name="unit"></param>
        /// <returns></returns>
        public static bool CheckBase64Size(string base64, int size, string unit = "M")
        {
            // 上传文件的大小, 单位为字节.
            var len = Base64FileSize(base64);
            // 准备接收换算后文件大小的容器
            double fileSize = unit.ToUpperInvariant() switch
            {
                "B" => len,
                "K" => (double)len / 1024,
                "M" => (double)len / 1048576,
                "G" => (double)len / 1073741824,
                _ => 0
            };
            // 如果上传文件大于限定的容量
            return !(fileSize > size);
        }
        /// <summary>
        /// 10位时间戳 转化
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static long ConvertDateTimeToInt(DateTime time)
        {
            DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1, 0, 0, 0, 0));
            long t = (time.Ticks - startTime.Ticks) / 10000000;   //除10000000调整为10位
            return t;
        }
        #region 反射相关
        private static List<Assembly>? _allAssemblies = null;
        /// <summary>
        /// 获取所有程序目录下和常用的程序集 包括一些系统引用程序集
        /// </summary>
        /// <returns> 当前工程下的程序集</returns>
        public static List<Assembly> GetAllAssembly()
        {
            if (_allAssemblies == null)
            {
                _allAssemblies = new List<Assembly>();
                string? path = null;
                string singlefile = null;
                try
                {
                    path = Assembly.GetEntryAssembly()?.Location;
                }
                catch { }
                if (string.IsNullOrEmpty(path))
                {
                    singlefile = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
                    path = Path.GetDirectoryName(singlefile);
                }
                if (string.IsNullOrEmpty(path))
                    throw new Exception("获取程序目录出错");
                var dir = new DirectoryInfo(Path.GetDirectoryName(path) ?? "");
                var dlls = dir.GetFiles("*.dll", SearchOption.TopDirectoryOnly);
                string[] systemdll = new string[]
                {
                "Microsoft.",
                "System.",
                "Swashbuckle.",
                "ICSharpCode",
                "Newtonsoft.",
                "Oracle.",
                "Pomelo.",
                "SQLitePCLRaw.",
                "Aliyun.OSS",
                "BouncyCastle.",
                "FreeSql.",
                "Google.Protobuf.dll",
                "Humanizer.dll",
                "IdleBus.dll",
                "K4os.",
                "MySql.Data.",
                "Npgsql.",
                "NPOI.",
                "netstandard",
                "MySqlConnector",
                "VueCliMiddleware"
                };
                var filtered = dlls.Where(x => systemdll.Any(y => x.Name.StartsWith(y)) == false);
                foreach (var dll in filtered)
                {
                    try
                    {
                        AssemblyLoadContext.Default.LoadFromAssemblyPath(dll.FullName);
                    }
                    catch
                    {
                    }
                }
                var dlllist = AssemblyLoadContext.Default.Assemblies.Where(x => systemdll.Any(y => x.FullName.StartsWith(y)) == false).ToList();
                _allAssemblies.AddRange(dlllist);
            }
            return _allAssemblies;
        }
        #endregion
        #region 枚举获取所有项
        /// <summary>
        /// 获取枚举的所有项
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static List<MenmItem> GetEnumItemsWithAttributes<T>() where T : Enum
        {
            var result = new List<MenmItem>();
            foreach (var value in Enum.GetValues(typeof(T)))
            {
                var member = typeof(T).GetMember(value.ToString())[0];
                var descriptionAttribute = member.GetCustomAttribute<DescriptionAttribute>();
                var item = new MenmItem
                {
                    Key = value.ToString(),
                    Value = (int)value, // 或者直接使用 value.ToString() 如果不需要转换为整数字符串
                    Description = descriptionAttribute?.Description ?? string.Empty
                };
                result.Add(item);
            }
            return result;
        }
        /// <summary>
        /// 获取枚举所有的项,返回名称 值 和Description 描述
        /// </summary>
        /// <param name="enumType"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        public static List<MenmItem> EnumerateWithDescriptions(Type enumType)
        {
            if (!enumType.IsEnum)
            {
                throw new ArgumentException(" 这不是枚举");
            }
            var result = new List<MenmItem>();
            foreach (var value in Enum.GetValues(enumType))
            {
                var member = enumType.GetMember(value.ToString())[0];
                var descriptionAttribute = member.GetCustomAttribute<DescriptionAttribute>();
                var item = new MenmItem
                {
                    Key = value.ToString(),
                    Value = (int)value, // 或者直接使用 value.ToString() 如果不需要转换为整数字符串
                    Description = descriptionAttribute?.Description ?? string.Empty
                };
                result.Add(item);
            }
            return result;
        }
        /// <summary>
        /// 是否是系统类型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsCustomType(Type type)
        {
            // 定义一个或多个基础命名空间列表,这些命名空间下的类型通常认为是系统类型
            string[] frameworkNamespaces = { "System", "Microsoft", "mscorlib", "netstandard", "System.Private.CoreLib" };
            // 获取类型所在的命名空间
            string typeNameSpace = type.Namespace;
            // 检查该命名空间是否在基础命名空间列表中,如果不在,则认为是自定义类型
            return frameworkNamespaces.Any(n => typeNameSpace.StartsWith(n, StringComparison.OrdinalIgnoreCase));
        }
        #endregion
        /// <summary>
        /// 根据json字符穿处理
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="jsonString"></param>
        /// <param name="parameter"></param>
        /// <returns></returns>
        private static Expression<Func<T, bool>> CreateWhereExpressionRecursive<T>(string jsonString, ParameterExpression parameter)
        {
            var conditions = new List<Expression>();
            JObject jsonObject = JObject.Parse(jsonString);
            foreach (var property in jsonObject.Properties())
            {
                var propertyName = property.Name;
                var propertyValue = property.Value.ToString();
                if (propertyValue == "") //空值不处理
                    continue;
                var propertyInfo = typeof(T).GetProperty(propertyName);
                if (propertyInfo == null)
                {
                    propertyInfo = typeof(T).GetProperty(propertyName.FirstToCapitalize());
                    if (propertyInfo == null)
                        continue; // 如果属性不存在,跳过
                }
                var propertyType = propertyInfo.PropertyType;
                var TypeName = propertyType.GenericTypeArguments.Length > 0 ? propertyType.GenericTypeArguments[0].Name + "?" : propertyType.Name;
                if (propertyType.IsClass && TypeName != "String")
                {
                    // 递归处理嵌套模型
                    var nestedParameter = Expression.Parameter(propertyType, propertyName);
                    var nestedJsonString = property.Value.ToString();
                    // 使用 MakeGenericType 动态创建泛型方法
                    var genericMethod = typeof(CommonHelper).GetMethod("CreateWhereExpressionRecursive", BindingFlags.Static | BindingFlags.NonPublic)
                        .MakeGenericMethod(propertyType);
                    // 调用泛型方法并获取返回的表达式
                    var nestedConditions = (Expression<Func<object, bool>>)genericMethod.Invoke(null, new object[] { nestedJsonString, nestedParameter });
                    var propExpr = Expression.Property(parameter, propertyName);
                    var lambda = Expression.Lambda(nestedConditions.Body, nestedParameter);
                    var invoke = Expression.Invoke(lambda, propExpr);
                    conditions.Add(invoke);
                }
                else if (propertyType == typeof(string))
                {
                    var propExpr = Expression.Property(parameter, propertyName);
                    var constant = Expression.Constant(propertyValue);
                    var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
                    var containsExpression = Expression.Call(propExpr, containsMethod, constant);
                    conditions.Add(containsExpression);
                }
                else if (TypeName.Contains( "DateTime")) //兼容DateTime?
                {
                    var dateRange = propertyValue.Split('~');
                    if (dateRange.Length == 2)
                    {
                        var startDate = DateTime.Parse(dateRange[0]);
                        var endDate = DateTime.Parse(dateRange[1]);
                        var propExpr = Expression.Property(parameter, propertyName);
                        var startConstant = Expression.Constant(startDate);
                        var endConstant = Expression.Constant(endDate);
                        if (TypeName == "DateTime?")
                        // 将常量表达式转换为 Nullable<DateTime>
                        {
                            var startNullableConstant = Expression.Convert(startConstant, typeof(DateTime?));
                            var endNullableConstant = Expression.Convert(endConstant, typeof(DateTime?));
                            var greaterThanOrEqual = Expression.GreaterThanOrEqual(propExpr, startNullableConstant);
                            var lessThanOrEqual = Expression.LessThanOrEqual(propExpr, endNullableConstant);
                            var andExpression = Expression.AndAlso(greaterThanOrEqual, lessThanOrEqual);
                            conditions.Add(andExpression);
                        }
                        else
                        {
                            var greaterThanOrEqual = Expression.GreaterThanOrEqual(propExpr, startConstant);
                            var lessThanOrEqual = Expression.LessThanOrEqual(propExpr, endConstant);
                            var andExpression = Expression.AndAlso(greaterThanOrEqual, lessThanOrEqual);
                            conditions.Add(andExpression);
                        }
                    }
                    else if (DateTime.TryParse(propertyValue, out var dateTime))
                    {
                        var propExpr = Expression.Property(parameter, propertyName);
                        var constant = Expression.Constant(dateTime);
                        var equalExpression = Expression.Equal(propExpr, constant);
                        conditions.Add(equalExpression);
                    }
                }
                else
                {
                    var propExpr = Expression.Property(parameter, propertyName);
                    var constant = Expression.Constant(Convert.ChangeType(propertyValue, propertyType));
                    var equalExpression = Expression.Equal(propExpr, constant);
                    conditions.Add(equalExpression);
                }
            }
            var body = conditions.Count > 1 ? conditions.Aggregate(Expression.AndAlso) : conditions.FirstOrDefault();
            return Expression.Lambda<Func<T, bool>>(body ?? Expression.Constant(true), parameter);
        }
        /// <summary>
        /// json格式化为表达式树
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="jsonString"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> FormatWhereExpression<T>(string jsonString)
        {
            var parameter = Expression.Parameter(typeof(T), "x");
            return CreateWhereExpressionRecursive<T>(jsonString, parameter);
        }
        /// <summary>
        /// 更具属性名称生成表达式树,如x.id
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static Expression<Func<T, object>> FormatPropertyExpression<T>(string propertyName)
        {
            // 获取参数表达式
            var parameter = Expression.Parameter(typeof(T), "x");
            // 获取属性表达式
            var propertyAccess = Expression.PropertyOrField(parameter, propertyName);
            if (propertyAccess == null)
            {
                ///首字母转大写
                propertyAccess = Expression.PropertyOrField(parameter, propertyName.FirstToCapitalize());
            }
            // 创建 Lambda 表达式,并在返回值中进行类型转换
            return Expression.Lambda<Func<T, object>>(
                Expression.Convert(propertyAccess, typeof(object)),
                parameter);
        }
   }
    /// <summary>
    /// 枚举项属性
    /// </summary>
    [Description("枚举项属性")]
    public class MenmItem
    {
        /// <summary>
        /// 枚举key
        /// </summary>
        [Description("枚举key")]
        public string? Key { get; set; }
        /// <summary>
        /// 枚举值
        /// </summary>
        [Description("枚举值")]
        public int Value { get; set; }
        /// <summary>
        /// 枚举描述
        /// </summary>
        [Description("枚举项属性")]
        public string? Description { get; set; }
    }
}
Admin.NET/cylsg.utility/Extend/EmunEx.cs
New file
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace cylsg.utility.Extend
{
    /// <summary>
    /// 枚举扩展
    /// </summary>
    public static class EmunEx
    {
    }
}
Admin.NET/cylsg.utility/Extend/StringEx.cs
New file
@@ -0,0 +1,251 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace cylsg.utility.Extend
{
    /// <summary>
    /// 字符串扩展
    /// </summary>
    public static class StringEx
    {
        /// <summary>
        /// 首字母小写
        /// </summary>
        /// <param name="self"></param>
        /// <returns></returns>
        public static string FirstToLower(this string self)
        {
            string str1 = self?.Substring(0, 1).ToLower() ?? "";
            string str2 = self?.Substring(1) ?? "";
            //首字母转小写
            return str1 + str2;
        }
        /// <summary>
        /// 带有xxx.xxx结束的的所有字符串 的后面xxx.xxx
        /// </summary>
        /// <param name="strtypr"></param>
        /// <returns></returns>
        public static string GetFileName(this string strtypr)
        {
            return Path.GetFileName(strtypr);
        }
        /// <summary>
        /// 是否是数字序列
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static bool isPureNum(this string strtypr)
        {
            if (strtypr.Length == 0 || strtypr == null)//验证这个字符串是否为空
            {
                return false;
            }
            byte[] strBytes = Encoding.ASCII.GetBytes(strtypr);//获取字符串的byte类型的字符数组,编码方式ASCII
            foreach (byte strByte in strBytes)
            {
                if (strByte < 48 || strByte > 57)     //判断每个字符是否为数字,根据每个字符的ASCII值所在范围判断
                {
                    return false;                     //不是,就返回false
                }
            }
            return true;                              //是,就返回true
        }
        /// <summary>
        /// 是否是数字序列
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static bool isGuid(this string strtypr)
        {
            if (strtypr.Length < 20 || strtypr == null)//验证这个字符串是否为空
            {
                return false;
            }
            try
            {
                new Guid(strtypr);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }
        /// <summary>
        /// 字符串
        /// </summary>
        /// <param name="strtypr"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static int toInt(this string strtypr)
        {
            if (!strtypr.isPureNum())
                throw new Exception("并非完整的数字序列");
            return int.Parse(strtypr);
        }
        /// <summary>
        /// 移除第一个字符串为指定字符串的 如 “axtXXXXX" 如果以"axt"开头的字符串,就移除"axt" 没有则不变
        /// </summary>
        /// <param name="strtypr"></param>
        /// <param name="str">开始字符</param>
        /// <returns></returns>
        public static string RemoveStartWithStr(this string strtypr, string str)
        {
            string strret = strtypr;
            if (strtypr.StartsWith(str))
            {
                strret = strtypr.Remove(0, str.Length);
            }
            return strret;
        }
        /// <summary>
        /// 对字符串进行隐私处理,比如中间的用*显示
        /// </summary>
        /// <param name="strtypr"></param>
        /// <returns></returns>
        public static string PrivacyStr(this string strtypr)
        {
            // 如果是手机号码,仅保留前3位和后4位,中间用星号替换
            if (Regex.IsMatch(strtypr, @"^1[3-9]\d{9}$"))
            {
                return $"{strtypr.Substring(0, 3)}****{strtypr.Substring(7)}";
            }
            // 对于中文名字或者非手机号格式,仅保留首尾各1个字符,中间用星号替换
            else
            {
                if (strtypr.Length <= 2) return $"{strtypr.Substring(0, 1)}*"; // 长度小于等于2时,返回固定的星号
                var masked = $"{strtypr.Substring(0, 1)}*{strtypr.Substring(strtypr.Length - 1, 1)}";
                return masked;
            }
        }
        #region 时间区间字符串转其实和结束时间
        /// <summary>
        /// 解析时间区间字符串
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static (DateTime? StartDate, DateTime? EndDate) TryParseDateRangString(this string input)
        {
            DateTime startDate, endDate;
            var culture = CultureInfo.InvariantCulture;
            // 尝试解析第一种格式 "2023-01-012023-01-31"
            if (DateTime.TryParseExact(input.Substring(0, 10), "yyyy-MM-dd", culture, DateTimeStyles.None, out startDate) &&
                DateTime.TryParseExact(input.Substring(10), "yyyy-MM-dd", culture, DateTimeStyles.None, out endDate))
            {
                return (startDate, endDate);
            }
            // 尝试解析第二种格式 "2023-01-01 08:02:032023-01-31 08:02:03"
            if (DateTime.TryParseExact(input.Substring(0, 19), "yyyy-MM-dd HH:mm:ss", culture, DateTimeStyles.None, out startDate) &&
                DateTime.TryParseExact(input.Substring(19), "yyyy-MM-dd HH:mm:ss", culture, DateTimeStyles.None, out endDate))
            {
                return (startDate, endDate);
            }
            // 尝试解析第二种格式 "2023-01-01T08:02:032023-01-31T08:02:03"
            if (DateTime.TryParseExact(input.Substring(0, 19), "yyyy-MM-ddTHH:mm:ss", culture, DateTimeStyles.None, out startDate) &&
                DateTime.TryParseExact(input.Substring(19), "yyyy-MM-ddTHH:mm:ss", culture, DateTimeStyles.None, out endDate))
            {
                return (startDate, endDate);
            }
            // 如果都不是,返回null
            return (null, null);
        }
        /// <summary>
        /// 移除最后一个字符的?号
        /// </summary>
        /// <param name="input"> </param>
        /// /// <param name="inChar">字符</param>
        /// <returns></returns>
        public static string RemoveCharIfPresent(this string input, char inChar)
        {
            if (string.IsNullOrEmpty(input)) return input;
            // 检查字符串的最后一个字符是否为问号
            if (input[^1] == '?')
            {
                // 使用切片操作符创建不包含问号的新字符串
                return input[..^1];
            }
            else
            {
                // 字符串不以问号结尾,返回原字符串
                return input;
            }
        }
        /// <summary>
        /// 移除最后的字符串
        /// </summary>
        /// <param name="input"></param>
        /// <param name="endString"></param>
        /// <returns></returns>
        public static string RemoveEndStringIfExists(this string input, string endString)
        {
            if (string.IsNullOrEmpty(input) || string.IsNullOrEmpty(endString))
            {
                return input; // 如果输入字符串或结束字符串为空或null,直接返回原字符串
            }
            if (input.EndsWith(endString))
            {
                return input.Substring(0, input.Length - endString.Length); // 移除结束字符串
            }
            return input; // 如果不以指定字符串结束,返回原字符串
        }
        /// <summary>
        /// 首字母转小写
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static string FirstCharToLower(this string input)
        {
            if (string.IsNullOrEmpty(input))
            {
                return input;
            }
            // 使用正则表达式
            return Regex.Replace(input, @"^\p{Lu}", match => match.Value.ToLower());
        }
        #endregion
        /// <summary>
        /// 首字母转大写
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static string FirstToCapitalize(this  string input)
        {
            if (string.IsNullOrEmpty(input))
            {
                return input;
            }
            TextInfo textInfo = new CultureInfo("en-US", false).TextInfo;
            return textInfo.ToTitleCase(input.ToLower()).Substring(0, 1) + input.Substring(1);
        }
    }
}
Admin.NET/cylsg.utility/Extend/TypeAndExpressionEx.cs
New file
@@ -0,0 +1,1021 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using cylsg.utility.Extend;
using Microsoft.Extensions.Primitives;
namespace cylsg.utility.Extend
{
    /// <summary>
    /// 反射扩展函数
    /// </summary>
    public static class TypeAndExpressionEx
    {
        #region 属性扩展 反射
        public static Func<object, object> GetPropertyExpression(Type objtype, string property)
        {
            property = Regex.Replace(property, @"\[[^\]]*\]", string.Empty);
            List<string> level = new List<string>();
            if (property.Contains('.'))
            {
                level.AddRange(property.Split('.'));
            }
            else
            {
                level.Add(property);
            }
            var pe = Expression.Parameter(objtype);
#pragma warning disable 8604
            var member = Expression.Property(pe, objtype.GetSingleProperty(level[0]));
            for (int i = 1; i < level.Count; i++)
            {
                member = Expression.Property(member, member.Type.GetSingleProperty(level[i]));
            }
            return Expression.Lambda<Func<object, object>>(member, pe).Compile();
        }
        /// <summary>
        /// 获取属性名
        /// </summary>
        /// <param name="expression">属性表达式</param>
        /// <param name="getAll">是否获取全部级别名称,比如a.b.c</param>
        /// <returns>属性名</returns>
        public static string GetPropertyName(this Expression expression, bool getAll = true)
        {
            if (expression == null)
            {
                return "";
            }
            MemberExpression me = null;
            LambdaExpression le = null;
            if (expression is MemberExpression)
            {
                me = expression as MemberExpression;
            }
            if (expression is LambdaExpression)
            {
                le = expression as LambdaExpression;
                if (le.Body is MemberExpression)
                {
                    me = le.Body as MemberExpression;
                }
                if (le.Body is UnaryExpression)
                {
                    me = (le.Body as UnaryExpression).Operand as MemberExpression;
                }
            }
            string rv = "";
            if (me != null)
            {
                rv = me.Member.Name;
            }
            while (me != null && getAll && me.NodeType == ExpressionType.MemberAccess)
            {
                Expression exp = me.Expression;
                if (exp is MemberExpression)
                {
                    rv = (exp as MemberExpression).Member.Name + "." + rv;
                    me = exp as MemberExpression;
                }
                else if (exp is MethodCallExpression)
                {
                    var mexp = exp as MethodCallExpression;
                    if (mexp.Method.Name == "get_Item")
                    {
                        object index = 0;
                        if (mexp.Arguments[0] is MemberExpression)
                        {
                            var obj = ((mexp.Arguments[0] as MemberExpression).Expression as ConstantExpression).Value;
                            index = obj.GetType().GetField((mexp.Arguments[0] as MemberExpression).Member.Name).GetValue(obj);
                        }
                        else
                        {
                            index = (mexp.Arguments[0] as ConstantExpression).Value;
                        }
                        rv = (mexp.Object as MemberExpression).Member.Name + "[" + index + "]." + rv;
                        me = mexp.Object as MemberExpression;
                    }
                }
                else
                {
                    break;
                }
            }
            return rv;
        }
        public static Expression GetMemberExp(this ParameterExpression self, Expression member)
        {
            return self.GetMemberExp(member.GetPropertyName());
        }
        public static Expression GetMemberExp(this ParameterExpression self, string memberName)
        {
            var names = memberName.Split(',');
            Expression rv = Expression.PropertyOrField(self, names[0]); ;
            for (int i = 1; i < names.Length; i++)
            {
                rv = Expression.PropertyOrField(rv, names[i]);
            }
            return rv;
        }
        /// <summary>
        /// 获取正则表达式错误
        /// </summary>
        /// <param name="pi">属性信息</param>
        /// <returns>错误文本</returns>
        public static string GetRegexErrorMessage(this MemberInfo pi)
        {
            string rv = "";
            if (pi.GetCustomAttributes(typeof(RegularExpressionAttribute), false).FirstOrDefault() is RegularExpressionAttribute dis && !string.IsNullOrEmpty(dis.ErrorMessage))
            {
                rv = dis.ErrorMessage;
                //if (CoreProgram._localizer != null)
                //{
                //    rv = CoreProgram._localizer[rv];
                //}
            }
            else
            {
                rv = "";
            }
            return rv;
        }
        /// <summary>
        /// 获取属性显示名称
        /// </summary>
        /// <param name="pi">属性信息</param>
        /// <returns>属性名称</returns>
        public static string GetPropertyDisplayName(this MemberInfo pi)
        {
            string rv = "";
            if (pi.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() is DisplayAttribute dis && !string.IsNullOrEmpty(dis.Name))
            {
                rv = dis.Name;
            }
            else
            {
                rv = pi.Name;
            }
            return rv;
        }
        /// <summary>
        /// 获取属性显示名称
        /// </summary>
        /// <param name="expression">属性表达式</param>
        /// <returns>属性显示名称</returns>
        public static string GetPropertyDisplayName(this Expression expression)
        {
            return expression.GetPropertyInfo().GetPropertyDisplayName();
        }
        /// <summary>
        /// 获取属性的 Description 标注名,如果没有,则返回 属性名称
        /// </summary>
        /// <param name="pi"></param>
        /// <returns></returns>
        public static string GetPropertyDescription(this MemberInfo pi)
        {
            string rv = "";
            if (pi.GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault() is DescriptionAttribute dis && !string.IsNullOrEmpty(dis.Description))
            {
                rv = dis.Description;
            }
            else
            {
                rv = pi.Name;
            }
            return rv;
        }
        /// <summary>
        /// 获取属性的 Description 标注名,如果没有,则返回 属性名称
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static string GetPropertyDescription(this Expression expression)
        {
            return expression.GetPropertyInfo().GetPropertyDisplayName();
        }
        /// <summary>
        /// 获取枚举显示名称
        /// </summary>
        /// <param name="value">枚举值</param>
        /// <returns>枚举显示名称</returns>
        public static string? GetEnumDisplayName(this Enum value)
        {
            return GetEnumDisplayName(value.GetType(), value.ToString());
        }
        /// <summary>
        /// 获取属性信息
        /// </summary>
        /// <param name="expression">属性表达式</param>
        /// <returns>属性信息</returns>
        public static PropertyInfo? GetPropertyInfo(this Expression expression)
        {
            MemberExpression me = null;
            LambdaExpression le = null;
            if (expression is MemberExpression)
            {
                me = expression as MemberExpression;
            }
            if (expression is LambdaExpression)
            {
                le = expression as LambdaExpression;
                if (le.Body is MemberExpression)
                {
                    me = le.Body as MemberExpression;
                }
                if (le.Body is UnaryExpression)
                {
                    me = (le.Body as UnaryExpression).Operand as MemberExpression;
                }
            }
            PropertyInfo rv = null;
            if (me != null)
            {
                rv = me.Member.DeclaringType.GetSingleProperty(me.Member.Name);
            }
            return rv;
        }
        /// <summary>
        /// 获取属性值
        /// </summary>
        /// <param name="exp">属性表达式</param>
        /// <param name="obj">属性所在实例</param>
        /// <returns>属性值</returns>
        public static object? GetPropertyValue(this object obj, LambdaExpression exp)
        {
            //获取表达式的值,并过滤单引号
            try
            {
                var expValue = exp.Compile().DynamicInvoke(obj);
                object val = expValue;
                return val;
            }
            catch
            {
                return "";
            }
        }
        /// <summary>
        /// 获取属性值
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="property"></param>
        /// <returns></returns>
        public static object? GetPropertyValue(this object obj, string property)
        {
            //获取表达式的值,并过滤单引号
            try
            {
                return obj.GetType().GetSingleProperty(property).GetValue(obj);
            }
            catch
            {
                return null;
            }
        }
        public static List<string>? GetPropertySiblingValues(this object obj, string propertyName)
        {
            if (obj == null)
            {
                return new List<string>();
            }
            Regex reg = new Regex("(.*?)\\[\\-?\\d?\\]\\.(.*?)$");
            var match = reg.Match(propertyName);
            if (match.Success)
            {
                var name1 = match.Groups[1].Value;
                var name2 = match.Groups[2].Value;
                var levels = name1.Split('.');
                var objtype = obj.GetType();
                var pe = Expression.Parameter(objtype);
                var member = Expression.Property(pe, objtype.GetSingleProperty(levels[0]));
                for (int i = 1; i < levels.Length; i++)
                {
                    member = Expression.Property(member, member.Type.GetSingleProperty(levels[i]));
                }
                var pe2 = Expression.Parameter(member.Type.GetGenericArguments()[0]);
                var cast = Expression.Call(typeof(Enumerable), "Cast", new Type[] { pe2.Type }, member);
                var name2exp = Expression.Property(pe2, pe2.Type.GetSingleProperty(name2));
                var selectexp = Expression.Call(name2exp, "ToString", Type.EmptyTypes);
                Expression select = Expression.Call(
                               typeof(Enumerable),
                               "Select",
                               new Type[] { pe2.Type, typeof(string) },
                               cast,
                               Expression.Lambda(selectexp, pe2));
                var lambda = Expression.Lambda(select, pe);
                var rv = new List<string>();
                try
                {
                    rv = (lambda.Compile().DynamicInvoke(obj) as IEnumerable<string>)?.ToList();
                }
                catch { }
                return rv;
            }
            else
            {
                return new List<string>();
            }
        }
        /// <summary>
        /// 判断属性是否必填
        /// </summary>
        /// <param name="pi">属性信息</param>
        /// <returns>是否必填</returns>
        public static bool IsPropertyRequired(this MemberInfo pi)
        {
            bool isRequired = false;
            if (pi != null)
            {
                //如果需要显示星号,则判断是否是必填项,如果是必填则在内容后面加上星号
                //所有int,float。。。这种Primitive类型的,肯定都是必填
                Type t = pi.GetMemberType();
                if (t != null && (t.IsPrimitive() || t.IsEnum() || t == typeof(decimal) || t == typeof(Guid)))
                {
                    isRequired = true;
                }
                else
                {
                    //对于其他类,检查是否有RequiredAttribute,如果有就是必填
                    if (pi.GetCustomAttributes(typeof(RequiredAttribute), false).FirstOrDefault() is RequiredAttribute required && required.AllowEmptyStrings == false)
                    {
                        isRequired = true;
                    }
                    else if (pi.GetCustomAttributes(typeof(KeyAttribute), false).FirstOrDefault() != null)
                    {
                        isRequired = true;
                    }
                }
            }
            return isRequired;
        }
        /// <summary>
        /// 设置属性值
        /// </summary>
        /// <param name="source">属性所在实例</param>
        /// <param name="property">属性名</param>
        /// <param name="value">要赋的值</param>
        /// <param name="prefix">属性前缀</param>
        /// <param name="stringBasedValue">是否为字符串格式的值</param>
        public static void SetPropertyValue(this object source, string property, object value, string? prefix = null, bool stringBasedValue = false)
        {
            try
            {
                property = Regex.Replace(property, @"\[[^\]]*\]", string.Empty);
                List<string> level = new List<string>();
                if (property.Contains('.'))
                {
                    level.AddRange(property.Split('.'));
                }
                else
                {
                    level.Add(property);
                }
                if (!string.IsNullOrWhiteSpace(prefix))
                {
                    level.Insert(0, prefix);
                }
                object temp = source;
                Type tempType = source.GetType();
                for (int i = 0; i < level.Count - 1; i++)
                {
                    var member = tempType.GetMember(level[i])[0];
                    if (member != null)
                    {
                        var va = member.GetMemberValue(temp);
                        if (va != null)
                        {
                            temp = va;
                        }
                        else
                        {
                            var newInstance = member.GetMemberType().GetConstructor(Type.EmptyTypes).Invoke(null);
                            member.SetMemberValue(temp, newInstance, null);
                            temp = newInstance;
                        }
                        tempType = member.GetMemberType();
                    }
                }
                var memberInfos = tempType.GetMember(level.Last());
                if (!memberInfos.Any())
                {
                    return;
                }
                var fproperty = memberInfos[0];
                if (value == null || value is StringValues s && StringValues.IsNullOrEmpty(s))
                {
                    fproperty.SetMemberValue(temp, null, null);
                    return;
                }
                bool isArray = false;
                if (value != null && value.GetType().IsArray == true)
                {
                    isArray = true;
                }
                if (stringBasedValue == true)
                {
                    Type propertyType = fproperty.GetMemberType();
                    if (propertyType.IsGeneric(typeof(List<>)) == true)
                    {
                        var list = propertyType.GetConstructor(Type.EmptyTypes).Invoke(null) as IList;
                        var gs = propertyType.GenericTypeArguments;
                        try
                        {
                            if (value.GetType() == typeof(StringValues))
                            {
                                var strVals = (StringValues)value;
                                var a = strVals.ToArray();
                                for (int i = 0; i < a.Length; i++)
                                {
                                    list.Add(a[i].ConvertValue(gs[0]));
                                }
                            }
                            else if (isArray)
                            {
                                var a = value as object[];
                                for (int i = 0; i < a.Length; i++)
                                {
                                    list.Add(a[i].ConvertValue(gs[0]));
                                }
                            }
                            else
                            {
                                list = value.ConvertValue(propertyType) as IList;
                            }
                        }
                        catch { }
                        fproperty.SetMemberValue(temp, list, null);
                    }
                    else if (propertyType.IsArray)
                    {
                        try
                        {
                            var strVals = (StringValues)value;
                            var eletype = propertyType.GetElementType();
                            if (eletype != null)
                            {
                                var arr = Array.CreateInstance(eletype, strVals.Count);
                                for (int i = 0; i < arr.Length; i++)
                                {
                                    arr.SetValue(strVals[i].ConvertValue(eletype), i);
                                }
                                fproperty.SetMemberValue(temp, arr, null);
                            }
                        }
                        catch { }
                    }
                    else
                    {
                        if (isArray)
                        {
                            var a = value as object[];
                            if (a.Length == 1)
                            {
                                value = a[0];
                            }
                        }
                        if (value is string)
                        {
                            value = value.ToString().Replace("\\", "/");
                        }
                        fproperty.SetMemberValue(temp, value, null);
                    }
                }
                else
                {
                    if (value is string)
                    {
                        value = value.ToString().Replace("\\", "/");
                    }
                    fproperty.SetMemberValue(temp, value, null);
                }
            }
            catch
            {
            }
        }
        /// <summary>
        /// 根据MemberInfo获取值
        /// </summary>
        /// <param name="mi">MemberInfo</param>
        /// <param name="obj">所在实例</param>
        /// <param name="index">如果是数组,指定数组下标。默认为null</param>
        /// <returns>MemberInfo的值</returns>
        public static object? GetMemberValue(this MemberInfo mi, object obj, object[]? index = null)
        {
            object rv = null;
            if (mi.MemberType == MemberTypes.Property)
            {
                rv = ((PropertyInfo)mi).GetValue(obj, index);
            }
            else if (mi.MemberType == MemberTypes.Field)
            {
                rv = ((FieldInfo)mi).GetValue(obj);
            }
            return rv;
        }
        /// <summary>
        /// 设定MemberInfo的值
        /// </summary>
        /// <param name="mi">MemberInfo</param>
        /// <param name="obj">所在实例</param>
        /// <param name="val">要赋的值</param>
        /// <param name="index">如果是数组,指定数组下标。默认为null</param>
        public static void SetMemberValue(this MemberInfo mi, object obj, object val, object[]? index = null)
        {
            object newval = val;
            if (val is string s)
            {
                if (string.IsNullOrEmpty(s))
                {
                    val = null;
                }
            }
            if (val != null && val.GetType() != mi.GetMemberType())
            {
                newval = val.ConvertValue(mi.GetMemberType());
            }
            if (mi.MemberType == MemberTypes.Property)
            {
                ((PropertyInfo)mi).SetValue(obj, newval, index);
            }
            else if (mi.MemberType == MemberTypes.Field)
            {
                ((FieldInfo)mi).SetValue(obj, newval);
            }
        }
        /// <summary>
        /// 获取某个MemberInfo的类型
        /// </summary>
        /// <param name="mi">MemberInfo</param>
        /// <returns>类型</returns>
        public static Type? GetMemberType(this MemberInfo mi)
        {
            Type? rv = null;
            if (mi != null)
            {
                if (mi.MemberType == MemberTypes.Property)
                {
                    rv = ((PropertyInfo)mi).PropertyType;
                }
                else if (mi.MemberType == MemberTypes.Field)
                {
                    rv = ((FieldInfo)mi).FieldType;
                }
            }
            return rv;
        }
        /// <summary>
        /// 获取枚举显示名称
        /// </summary>
        /// <param name="enumType">枚举类型</param>
        /// <param name="value">枚举值</param>
        /// <returns>枚举显示名称</returns>
        public static string? GetEnumDisplayName(Type enumType, string value)
        {
            string rv = "";
            FieldInfo field = null;
            if (enumType.IsEnum())
            {
                field = enumType.GetField(value);
            }
            //如果是nullable的枚举
            if (enumType.IsGeneric(typeof(Nullable<>)) && enumType.GetGenericArguments()[0].IsEnum())
            {
                field = enumType.GenericTypeArguments[0].GetField(value);
            }
            if (field != null)
            {
                var attribs = field.GetCustomAttributes(typeof(DisplayAttribute), true).ToList();
                if (attribs.Count > 0)
                {
                    rv = ((DisplayAttribute)attribs[0]).GetName();
                    //if (CoreProgram._localizer != null)
                    //{
                    //    rv = CoreProgram._localizer[rv];
                    //}
                }
                else
                {
                    rv = value;
                }
            }
            return rv;
        }
        public static string? GetEnumDisplayName(Type enumType, int value)
        {
            string? rv = "";
            FieldInfo? field = null;
            string? ename = "";
            if (enumType.IsEnum())
            {
                ename = enumType.GetEnumName(value);
                field = enumType.GetField(ename ?? "");
            }
            //如果是nullable的枚举
            if (enumType.IsGeneric(typeof(Nullable<>)) && enumType.GetGenericArguments()[0].IsEnum())
            {
                ename = enumType.GenericTypeArguments[0].GetEnumName(value);
                field = enumType.GenericTypeArguments[0].GetField(ename ?? "");
            }
            if (field != null)
            {
                var attribs = field.GetCustomAttributes(typeof(DisplayAttribute), true).ToList();
                if (attribs.Count > 0)
                {
                    rv = ((DisplayAttribute)attribs[0]).GetName();
                    //if (CoreProgram._localizer != null)
                    //{
                    //    rv = CoreProgram._localizer[rv];
                    //}
                }
                else
                {
                    rv = ename;
                }
            }
            return rv;
        }
        /// <summary>
        /// 转化值
        /// </summary>
        /// <param name="value">要转换的值</param>
        /// <param name="propertyType">转换后的类型</param>
        /// <returns>转换后的值</returns>
        public static object? ConvertValue(this object value, Type propertyType)
        {
            object val = null;
            if (propertyType.IsGeneric(typeof(Nullable<>)) == true)
            {
                var gs = propertyType.GenericTypeArguments;
                try
                {
                    val = value.ConvertValue(gs[0]);
                }
                catch { }
            }
            else if (propertyType.IsEnum())
            {
                val = Enum.Parse(propertyType, value.ToString());
            }
            else if (propertyType == typeof(string))
            {
                val = value?.ToString().Trim();
            }
            else if (propertyType == typeof(Guid))
            {
                bool suc = Guid.TryParse(value?.ToString(), out Guid g);
                if (suc)
                {
                    val = g;
                }
                else
                {
                    val = Guid.Empty;
                }
            }
            else
            {
                try
                {
                    if (value.ToString().StartsWith("`") && value.ToString().EndsWith("`"))
                    {
                        string inner = value.ToString().Trim('`').TrimEnd(',');
                        if (!string.IsNullOrWhiteSpace(inner))
                        {
                            val = propertyType.GetConstructor(Type.EmptyTypes).Invoke(null);
                            string[] pair = inner.Split(',');
                            var gs = propertyType.GetGenericArguments();
                            foreach (var p in pair)
                            {
                                (val as IList).Add(Convert.ChangeType(p, gs[0]));
                            }
                        }
                    }
                    else
                    {
                        val = Convert.ChangeType(value.ToString(), propertyType);
                    }
                }
                catch
                {
                }
            }
            return val;
        }
        public static object MakeList(Type innerType, string propertyName, object[] values)
        {
            object rv = typeof(List<>).MakeGenericType(innerType).GetConstructor(Type.EmptyTypes).Invoke(null);
            var mi = rv.GetType().GetMethod("Add");
            var con = innerType.GetConstructor(Type.EmptyTypes);
            foreach (var item in values)
            {
                var newobj = con.Invoke(null);
                newobj.SetPropertyValue(propertyName, item);
                mi.Invoke(rv, new object[] { newobj });
            }
            return rv;
        }
        /// <summary>
        /// 获取 是否定义该标签属性
        /// </summary>
        /// <param name="pi"></param>
        /// <param name="local"></param>
        /// <returns></returns>
        public static T? GetAttribute<T>(this MemberInfo pi) where T : Attribute
        {
            string rv = "";
            if (pi.GetCustomAttributes(typeof(T), false).FirstOrDefault() is T dis)
            {
                return dis;
            }
            else
            {
                return null;
            }
        }
        public static ImmutableDictionary<string, List<PropertyInfo>> _propertyCache { get; set; } =
          new Dictionary<string, List<PropertyInfo>>().ToImmutableDictionary();
        /// <summary>
        /// 判断是否是泛型
        /// </summary>
        /// <param name="self">Type类</param>
        /// <param name="innerType">泛型类型</param>
        /// <returns>判断结果</returns>
        public static bool IsGeneric(this Type self, Type innerType)
        {
            if (self.GetTypeInfo().IsGenericType && self.GetGenericTypeDefinition() == innerType)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        /// <summary>
        /// 判断是否为Nullable<>类型
        /// </summary>
        /// <param name="self">Type类</param>
        /// <returns>判断结果</returns>
        public static bool IsNullable(this Type self)
        {
            return self.IsGeneric(typeof(Nullable<>));
        }
        /// <summary>
        /// 判断是否为List<>类型
        /// </summary>
        /// <param name="self">Type类</param>
        /// <returns>判断结果</returns>
        public static bool IsList(this Type self)
        {
            return self.IsGeneric(typeof(List<>)) || self.IsGeneric(typeof(IEnumerable<>));
        }
        /// <summary>
        /// 判断是否为List<>类型
        /// </summary>
        /// <param name="self">Type类</param>
        /// <returns>判断结果</returns>
        public static bool IsListOf<T>(this Type self)
        {
            if (self.IsGeneric(typeof(List<>)) && typeof(T).IsAssignableFrom(self.GenericTypeArguments[0]))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        #region 判断是否为枚举
        /// <summary>
        /// 判断是否为枚举
        /// </summary>
        /// <param name="self">Type类</param>
        /// <returns>判断结果</returns>
        public static bool IsEnum(this Type self)
        {
            return self.GetTypeInfo().IsEnum;
        }
        /// <summary>
        /// 判断是否为枚举或者可空枚举
        /// </summary>
        /// <param name="self"></param>
        /// <returns></returns>
        public static bool IsEnumOrNullableEnum(this Type self)
        {
            if (self == null)
            {
                return false;
            }
            if (self.IsEnum)
            {
                return true;
            }
            else
            {
                if (self.IsGenericType && self.GetGenericTypeDefinition() == typeof(Nullable<>) && self.GetGenericArguments()[0].IsEnum)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
        #endregion
        /// <summary>
        /// 判断是否为值类型 基础值类型
        /// </summary>
        /// <param name="self">Type类</param>
        /// <returns>判断结果</returns>
        public static bool IsPrimitive(this Type self)
        {
            return self.GetTypeInfo().IsPrimitive || self == typeof(decimal);
        }
        /// <summary>
        /// 判断值是否是数字基础类型
        /// </summary>
        /// <param name="self"></param>
        /// <returns></returns>
        public static bool IsNumber(this Type self)
        {
            Type checktype = self;
            if (self.IsNullable())
            {
                checktype = self.GetGenericArguments()[0];
            }
            if (checktype == typeof(int) || checktype == typeof(short) || checktype == typeof(long) || checktype == typeof(float) || checktype == typeof(decimal) || checktype == typeof(double))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        #region 判断是否是Bool
        /// <summary>
        /// 是否是bool类型
        /// </summary>
        /// <param name="self"></param>
        /// <returns></returns>
        public static bool IsBool(this Type self)
        {
            return self == typeof(bool);
        }
        /// <summary>
        /// 判断是否是 bool or bool?类型
        /// </summary>
        /// <param name="self"></param>
        /// <returns></returns>
        public static bool IsBoolOrNullableBool(this Type self)
        {
            if (self == null)
            {
                return false;
            }
            if (self == typeof(bool) || self == typeof(bool?))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        #endregion
        /// <summary>
        /// 根据名字获取
        /// </summary>
        /// <param name="self"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static PropertyInfo? GetSingleProperty(this Type self, string name)
        {
            if (self.FullName == null)
                throw new Exception("属性名为空");
            if (_propertyCache.ContainsKey(self.FullName) == false)
            {
                var properties = self.GetProperties().ToList();
                _propertyCache = _propertyCache.Add(self.FullName, properties);
                return properties.Where(x => x.Name == name).FirstOrDefault();
            }
            else
            {
                return _propertyCache[self.FullName].Where(x => x.Name == name).FirstOrDefault();
            }
        }
        /// <summary>
        /// 获取属性列表 并将 列表属性放入缓存
        /// </summary>
        /// <param name="self"></param>
        /// <returns></returns>
        public static PropertyInfo? GetSingleProperty(this Type self, Func<PropertyInfo, bool> where)
        {
            if (self.FullName == null)
                throw new Exception("属性名为空");
            if (_propertyCache.ContainsKey(self.FullName) == false)
            {
                var properties = self.GetProperties().ToList();
                _propertyCache = _propertyCache.Add(self.FullName, properties);
                return properties.Where(where).FirstOrDefault();
            }
            else
            {
                return _propertyCache[self.FullName].Where(where).FirstOrDefault();
            }
        }
        /// <summary>
        /// 获取属性列表 并将 列表属性放入缓存
        /// </summary>
        /// <param name="self"></param>
        /// <returns></returns>
        public static List<PropertyInfo> GetAllProperties(this Type self)
        {
            if (self.FullName == null)
                throw new Exception("属性名为空");
            if (_propertyCache.ContainsKey(self.FullName) == false)
            {
                var properties = self.GetProperties().ToList();
                _propertyCache = _propertyCache.Add(self.FullName, properties);
                return properties;
            }
            else
            {
                return _propertyCache[self.FullName];
            }
        }
        #endregion
    }
}
Admin.NET/cylsg.utility/StaticStringDef.cs
New file
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace cylsg.utility
{
    /// <summary>
    /// 常用字符串定义
    /// </summary>
    public static class StaticStringDef
    {
        /// <summary>
        /// 提现自锁KeY
        /// </summary>
     public   static string TransferMoneyLockKey = "TransferMoneyLockKey:";
        /// <summary>
        /// 提现统计累计锁
        /// </summary>
        public static string TransferMoneyManKey = "TransferMoneyManKey:";
    }
}
Admin.NET/cylsg.utility/cylsg.utility.csproj
New file
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Primitives" Version="8.0.0" />
    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
  </ItemGroup>
</Project>
Admin.NET/cylsg.utility/untilityModels.cs
New file
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace cylsg.utility
{
    /// <summary>
    /// 通用模型
    /// </summary>
    public class untilityModels
    {
    }
}
Web/src/api/Customer/fBS_EnterpriseType.ts
New file
@@ -0,0 +1,52 @@
import {useBaseApi} from '/@/api/base';
// 企业类配置表接口服务
export const useFBS_EnterpriseTypeApi = () => {
    const baseApi = useBaseApi("fBS_EnterpriseType");
    return {
        // 分页查询企业类配置表
        page: baseApi.page,
        // 查看企业类配置表详细
        detail: baseApi.detail,
        // 新增企业类配置表
        add: baseApi.add,
        // 更新企业类配置表
        update: baseApi.update,
        // 删除企业类配置表
        delete: baseApi.delete,
        // 批量删除企业类配置表
        batchDelete: baseApi.batchDelete,
        // 导出企业类配置表数据
        exportData: baseApi.exportData,
        // 导入企业类配置表数据
        importData: baseApi.importData,
        // 下载企业类配置表数据导入模板
        downloadTemplate: baseApi.downloadTemplate,
    }
}
// 企业类配置表实体
export interface FBS_EnterpriseType {
    // 主键Id
    id: number;
    // 名称
    name?: string;
    // 代码
    code?: string;
    // 父
    parentId?: number;
    // 租户Id
    tenantId: number;
    // 创建时间
    createTime: string;
    // 更新时间
    updateTime: string;
    // 创建者Id
    createUserId: number;
    // 创建者姓名
    createUserName: string;
    // 修改者Id
    updateUserId: number;
    // 修改者姓名
    updateUserName: string;
}
Web/src/views/Customer/fBS_EnterpriseType/component/editDialog.vue
New file
@@ -0,0 +1,109 @@
<script lang="ts" name="fBS_EnterpriseType" setup>
import { ref, reactive, onMounted } from "vue";
import { ElMessage } from "element-plus";
import type { FormRules } from "element-plus";
import { formatDate } from '/@/utils/formatTime';
import { useFBS_EnterpriseTypeApi } from '/@/api/Customer/fBS_EnterpriseType';
//父级传递来的函数,用于回调
const emit = defineEmits(["reloadTable"]);
const fBS_EnterpriseTypeApi = useFBS_EnterpriseTypeApi();
const ruleFormRef = ref();
const state = reactive({
    title: '',
    loading: false,
    showDialog: false,
    ruleForm: {} as any,
    stores: {},
    dropdownData: {} as any,
});
// 自行添加其他规则
const rules = ref<FormRules>({
  name: [{required: true, message: '请选择名称!', trigger: 'blur',},],
  code: [{required: true, message: '请选择代码!', trigger: 'blur',},],
  parentId: [{required: true, message: '请选择父!', trigger: 'blur',},],
});
// 页面加载时
onMounted(async () => {
});
// 打开弹窗
const openDialog = async (row: any, title: string) => {
    state.title = title;
    row = row ?? {  };
    state.ruleForm = row.id ? await fBS_EnterpriseTypeApi.detail(row.id).then(res => res.data.result) : JSON.parse(JSON.stringify(row));
    state.showDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
    emit("reloadTable");
    state.showDialog = false;
};
// 提交
const submit = async () => {
    ruleFormRef.value.validate(async (isValid: boolean, fields?: any) => {
        if (isValid) {
            let values = state.ruleForm;
            await fBS_EnterpriseTypeApi[state.ruleForm.id ? 'update' : 'add'](values);
            closeDialog();
        } else {
            ElMessage({
                message: `表单有${Object.keys(fields).length}处验证失败,请修改后再提交`,
                type: "error",
            });
        }
    });
};
//将属性或者函数暴露给父组件
defineExpose({ openDialog });
</script>
<template>
    <div class="fBS_EnterpriseType-container">
        <el-dialog v-model="state.showDialog" :width="800" draggable :close-on-click-modal="false">
            <template #header>
                <div style="color: #fff">
                    <span>{{ state.title }}</span>
                </div>
            </template>
            <el-form :model="state.ruleForm" ref="ruleFormRef" label-width="auto" :rules="rules">
                <el-row :gutter="35">
                    <el-form-item v-show="false">
                        <el-input v-model="state.ruleForm.id" />
                    </el-form-item>
                    <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" >
                        <el-form-item label="名称" prop="name">
                            <el-input v-model="state.ruleForm.name" placeholder="请输入名称" maxlength="256" show-word-limit clearable />
                        </el-form-item>
                    </el-col>
                    <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" >
                        <el-form-item label="代码" prop="code">
                            <el-input v-model="state.ruleForm.code" placeholder="请输入代码" maxlength="36" show-word-limit clearable />
                        </el-form-item>
                    </el-col>
                    <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" >
                        <el-form-item label="父" prop="parentId">
                            <el-input-number v-model="state.ruleForm.parentId" placeholder="请输入父" clearable />
                        </el-form-item>
                    </el-col>
                </el-row>
            </el-form>
            <template #footer>
                <span class="dialog-footer">
                    <el-button @click="() => state.showDialog = false">取 消</el-button>
                    <el-button @click="submit" type="primary" v-reclick="1000">确 定</el-button>
                </span>
            </template>
        </el-dialog>
    </div>
</template>
<style lang="scss" scoped>
:deep(.el-select), :deep(.el-input-number) {
  width: 100%;
}
</style>
Web/src/views/Customer/fBS_EnterpriseType/index.vue
New file
@@ -0,0 +1,194 @@
<script lang="ts" setup name="fBS_EnterpriseType">
import { ref, reactive, onMounted } from "vue";
import { auth } from '/@/utils/authFunction';
import { ElMessageBox, ElMessage } from "element-plus";
import { downloadStreamFile } from "/@/utils/download";
import { useFBS_EnterpriseTypeApi } from '/@/api/Customer/fBS_EnterpriseType';
import editDialog from '/@/views/Customer/fBS_EnterpriseType/component/editDialog.vue'
import printDialog from '/@/views/system/print/component/hiprint/preview.vue'
import ModifyRecord from '/@/components/table/modifyRecord.vue';
import ImportData from "/@/components/table/importData.vue";
const fBS_EnterpriseTypeApi = useFBS_EnterpriseTypeApi();
const printDialogRef = ref();
const editDialogRef = ref();
const importDataRef = ref();
const state = reactive({
  exportLoading: false,
  tableLoading: false,
  stores: {},
  showAdvanceQueryUI: false,
  dropdownData: {} as any,
  selectData: [] as any[],
  tableQueryParams: {} as any,
  tableParams: {
    page: 1,
    pageSize: 20,
    total: 0,
    field: 'createTime', // 默认的排序字段
    order: 'descending', // 排序方向
    descStr: 'descending', // 降序排序的关键字符
  },
  tableData: [],
});
// 页面加载时
onMounted(async () => {
});
// 查询操作
const handleQuery = async (params: any = {}) => {
  state.tableLoading = true;
  state.tableParams = Object.assign(state.tableParams, params);
  const result = await fBS_EnterpriseTypeApi.page(Object.assign(state.tableQueryParams, state.tableParams)).then(res => res.data.result);
  state.tableParams.total = result?.total;
  state.tableData = result?.items ?? [];
  state.tableLoading = false;
};
// 列排序
const sortChange = async (column: any) => {
  state.tableParams.field = column.prop;
  state.tableParams.order = column.order;
  await handleQuery();
};
// 删除
const delFBS_EnterpriseType = (row: any) => {
  ElMessageBox.confirm(`确定要删除吗?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  }).then(async () => {
    await fBS_EnterpriseTypeApi.delete({ id: row.id });
    handleQuery();
    ElMessage.success("删除成功");
  }).catch(() => {});
};
// 批量删除
const batchDelFBS_EnterpriseType = () => {
  ElMessageBox.confirm(`确定要删除${state.selectData.length}条记录吗?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  }).then(async () => {
    await fBS_EnterpriseTypeApi.batchDelete(state.selectData.map(u => ({ id: u.id }) )).then(res => {
      ElMessage.success(`成功批量删除${res.data.result}条记录`);
      handleQuery();
    });
  }).catch(() => {});
};
// 导出数据
const exportFBS_EnterpriseTypeCommand = async (command: string) => {
  try {
    state.exportLoading = true;
    if (command === 'select') {
      const params = Object.assign({}, state.tableQueryParams, state.tableParams, { selectKeyList: state.selectData.map(u => u.id) });
      await fBS_EnterpriseTypeApi.exportData(params).then(res => downloadStreamFile(res));
    } else if (command === 'current') {
      const params = Object.assign({}, state.tableQueryParams, state.tableParams);
      await fBS_EnterpriseTypeApi.exportData(params).then(res => downloadStreamFile(res));
    } else if (command === 'all') {
      const params = Object.assign({}, state.tableQueryParams, state.tableParams, { page: 1, pageSize: 99999999 });
      await fBS_EnterpriseTypeApi.exportData(params).then(res => downloadStreamFile(res));
    }
  } finally {
    state.exportLoading = false;
  }
}
handleQuery();
</script>
<template>
  <div class="fBS_EnterpriseType-container" v-loading="state.exportLoading">
    <el-card shadow="hover" :body-style="{ paddingBottom: '0' }">
      <el-form :model="state.tableQueryParams" ref="queryForm" labelWidth="90">
        <el-row>
          <el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="4" class="mb10">
            <el-form-item label="关键字">
              <el-input v-model="state.tableQueryParams.keyword" clearable placeholder="请输入模糊查询关键字"/>
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="4" class="mb10" v-if="state.showAdvanceQueryUI">
            <el-form-item label="名称">
              <el-input v-model="state.tableQueryParams.name" clearable placeholder="请输入名称"/>
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="4" class="mb10" v-if="state.showAdvanceQueryUI">
            <el-form-item label="代码">
              <el-input v-model="state.tableQueryParams.code" clearable placeholder="请输入代码"/>
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="4" class="mb10" v-if="state.showAdvanceQueryUI">
            <el-form-item label="父">
              <el-input-number v-model="state.tableQueryParams.parentId"  clearable placeholder="请输入父"/>
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="4" class="mb10">
            <el-form-item >
              <el-button-group style="display: flex; align-items: center;">
                <el-button type="primary"  icon="ele-Search" @click="handleQuery" v-auth="'fBS_EnterpriseType:page'" v-reclick="1000"> 查询 </el-button>
                <el-button icon="ele-Refresh" @click="() => state.tableQueryParams = {}"> 重置 </el-button>
                <el-button icon="ele-ZoomIn" @click="() => state.showAdvanceQueryUI = true" v-if="!state.showAdvanceQueryUI" style="margin-left:5px;"> 高级查询 </el-button>
                <el-button icon="ele-ZoomOut" @click="() => state.showAdvanceQueryUI = false" v-if="state.showAdvanceQueryUI" style="margin-left:5px;"> 隐藏 </el-button>
                <el-button type="danger" style="margin-left:5px;" icon="ele-Delete" @click="batchDelFBS_EnterpriseType" :disabled="state.selectData.length == 0" v-auth="'fBS_EnterpriseType:batchDelete'"> 删除 </el-button>
                <el-button type="primary" style="margin-left:5px;" icon="ele-Plus" @click="editDialogRef.openDialog(null, '新增企业类配置表')" v-auth="'fBS_EnterpriseType:add'"> 新增 </el-button>
                <el-dropdown :show-timeout="70" :hide-timeout="50" @command="exportFBS_EnterpriseTypeCommand">
                  <el-button type="primary" style="margin-left:5px;" icon="ele-FolderOpened" v-reclick="20000" v-auth="'fBS_EnterpriseType:export'"> 导出 </el-button>
                  <template #dropdown>
                    <el-dropdown-menu>
                      <el-dropdown-item command="select" :disabled="state.selectData.length == 0">导出选中</el-dropdown-item>
                      <el-dropdown-item command="current">导出本页</el-dropdown-item>
                      <el-dropdown-item command="all">导出全部</el-dropdown-item>
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
                <el-button type="warning" style="margin-left:5px;" icon="ele-MostlyCloudy" @click="importDataRef.openDialog()" v-auth="'fBS_EnterpriseType:import'"> 导入 </el-button>
              </el-button-group>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
    </el-card>
    <el-card class="full-table" shadow="hover" style="margin-top: 5px">
      <el-table :data="state.tableData" @selection-change="(val: any[]) => { state.selectData = val; }" style="width: 100%" v-loading="state.tableLoading" tooltip-effect="light" row-key="id" @sort-change="sortChange" border>
        <el-table-column type="selection" width="40" align="center" v-if="auth('fBS_EnterpriseType:batchDelete') || auth('fBS_EnterpriseType:export')" />
        <el-table-column type="index" label="序号" width="55" align="center"/>
        <el-table-column prop='name' label='名称' show-overflow-tooltip />
        <el-table-column prop='code' label='代码' show-overflow-tooltip />
        <el-table-column prop='parentId' label='父' show-overflow-tooltip />
        <el-table-column label="修改记录" width="100" align="center" show-overflow-tooltip>
          <template #default="scope">
            <ModifyRecord :data="scope.row" />
          </template>
        </el-table-column>
        <el-table-column label="操作" width="140" align="center" fixed="right" show-overflow-tooltip v-if="auth('fBS_EnterpriseType:update') || auth('fBS_EnterpriseType:delete')">
          <template #default="scope">
            <el-button icon="ele-Edit" size="small" text type="primary" @click="editDialogRef.openDialog(scope.row, '编辑企业类配置表')" v-auth="'fBS_EnterpriseType:update'"> 编辑 </el-button>
            <el-button icon="ele-Delete" size="small" text type="primary" @click="delFBS_EnterpriseType(scope.row)" v-auth="'fBS_EnterpriseType:delete'"> 删除 </el-button>
          </template>
        </el-table-column>
      </el-table>
      <el-pagination
              v-model:currentPage="state.tableParams.page"
              v-model:page-size="state.tableParams.pageSize"
              @size-change="(val: any) => handleQuery({ pageSize: val })"
              @current-change="(val: any) => handleQuery({ page: val })"
              layout="total, sizes, prev, pager, next, jumper"
              :page-sizes="[10, 20, 50, 100, 200, 500]"
              :total="state.tableParams.total"
              size="small"
              background />
      <ImportData ref="importDataRef" :import="fBS_EnterpriseTypeApi.importData" :download="fBS_EnterpriseTypeApi.downloadTemplate" v-auth="'fBS_EnterpriseType:import'" @refresh="handleQuery"/>
      <printDialog ref="printDialogRef" :title="'打印企业类配置表'" @reloadTable="handleQuery" />
      <editDialog ref="editDialogRef" @reloadTable="handleQuery" />
    </el-card>
  </div>
</template>
<style scoped>
:deep(.el-input), :deep(.el-select), :deep(.el-input-number) {
  width: 100%;
}
</style>