diff --git a/NPin.sln b/NPin.sln index 4ca8e51..bd3bfd2 100644 --- a/NPin.sln +++ b/NPin.sln @@ -46,8 +46,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.SqlSugarCore", "src\NP EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "audit-logging", "audit-logging", "{29EA07EB-E1D2-4BCA-9EA4-D69E28F14978}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "upms", "upms", "{CA0606BE-4146-4390-86CD-AD92FC161A9E}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tenant-management", "tenant-management", "{FFB03EF5-B008-4F84-83D8-2F4B5D821251}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Framework.AuditLogging.Domain", "module\audit-logging\NPin.Framework.AuditLogging.Domain\NPin.Framework.AuditLogging.Domain.csproj", "{734EF33E-028A-44C3-B83F-13DBFA8AC117}" @@ -64,6 +62,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Framework.TenantManage EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Framework.TenantManagement.SqlSugarCore", "module\tenant-management\NPin.Framework.TenantManagement.SqlSugarCore\NPin.Framework.TenantManagement.SqlSugarCore.csproj", "{9B6CAF84-D53C-4595-93B7-231E771FAED3}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "upms", "upms", "{39C0CC7B-2185-48C6-8D22-F54212CA36D4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "code-gen", "code-gen", "{0FBD3B80-E61E-4797-9599-98E8884E5798}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Framework.Upms.Domain.Shared", "module\upms\NPin.Framework.Upms.Domain.Shared\NPin.Framework.Upms.Domain.Shared.csproj", "{23E194C3-B1E2-49C9-A145-DFD3CDE724AD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Framework.Upms.Domain", "module\upms\NPin.Framework.Upms.Domain\NPin.Framework.Upms.Domain.csproj", "{C159A479-5020-4E8D-9377-6E2F6EA06396}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Framework.Upms.SqlSugarCore", "module\upms\NPin.Framework.Upms.SqlSugarCore\NPin.Framework.Upms.SqlSugarCore.csproj", "{786A1422-C4BD-4742-B33D-EBB82762CC63}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Framework.Upms.Application.Contracts", "module\upms\NPin.Framework.Upms.Application.Contracts\NPin.Framework.Upms.Application.Contracts.csproj", "{028A9FD0-3C94-45D6-9A4D-68BD4985B176}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Framework.Upms.Application", "module\upms\NPin.Framework.Upms.Application\NPin.Framework.Upms.Application.csproj", "{A48B3BDD-5996-438B-951A-09C52E595FCB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -158,6 +170,26 @@ Global {9B6CAF84-D53C-4595-93B7-231E771FAED3}.Debug|Any CPU.Build.0 = Debug|Any CPU {9B6CAF84-D53C-4595-93B7-231E771FAED3}.Release|Any CPU.ActiveCfg = Release|Any CPU {9B6CAF84-D53C-4595-93B7-231E771FAED3}.Release|Any CPU.Build.0 = Release|Any CPU + {23E194C3-B1E2-49C9-A145-DFD3CDE724AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23E194C3-B1E2-49C9-A145-DFD3CDE724AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23E194C3-B1E2-49C9-A145-DFD3CDE724AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23E194C3-B1E2-49C9-A145-DFD3CDE724AD}.Release|Any CPU.Build.0 = Release|Any CPU + {C159A479-5020-4E8D-9377-6E2F6EA06396}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C159A479-5020-4E8D-9377-6E2F6EA06396}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C159A479-5020-4E8D-9377-6E2F6EA06396}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C159A479-5020-4E8D-9377-6E2F6EA06396}.Release|Any CPU.Build.0 = Release|Any CPU + {786A1422-C4BD-4742-B33D-EBB82762CC63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {786A1422-C4BD-4742-B33D-EBB82762CC63}.Debug|Any CPU.Build.0 = Debug|Any CPU + {786A1422-C4BD-4742-B33D-EBB82762CC63}.Release|Any CPU.ActiveCfg = Release|Any CPU + {786A1422-C4BD-4742-B33D-EBB82762CC63}.Release|Any CPU.Build.0 = Release|Any CPU + {028A9FD0-3C94-45D6-9A4D-68BD4985B176}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {028A9FD0-3C94-45D6-9A4D-68BD4985B176}.Debug|Any CPU.Build.0 = Debug|Any CPU + {028A9FD0-3C94-45D6-9A4D-68BD4985B176}.Release|Any CPU.ActiveCfg = Release|Any CPU + {028A9FD0-3C94-45D6-9A4D-68BD4985B176}.Release|Any CPU.Build.0 = Release|Any CPU + {A48B3BDD-5996-438B-951A-09C52E595FCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A48B3BDD-5996-438B-951A-09C52E595FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A48B3BDD-5996-438B-951A-09C52E595FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A48B3BDD-5996-438B-951A-09C52E595FCB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {088B4948-AE5B-45B3-A9FA-B853671CFA05} = {F2A0A89E-A2F9-48CF-AD38-0318B5ACD11C} @@ -176,7 +208,6 @@ Global {C4D324BB-01B6-436F-9AA6-136C7D5438F6} = {86F61EBB-4ACC-459C-AB3C-C8D486C3017D} {DC8E2E59-589F-4521-95E2-9CE7E1DD5541} = {86F61EBB-4ACC-459C-AB3C-C8D486C3017D} {29EA07EB-E1D2-4BCA-9EA4-D69E28F14978} = {EEAD0AD4-0F90-46D9-A775-D88AE07E2869} - {CA0606BE-4146-4390-86CD-AD92FC161A9E} = {EEAD0AD4-0F90-46D9-A775-D88AE07E2869} {FFB03EF5-B008-4F84-83D8-2F4B5D821251} = {EEAD0AD4-0F90-46D9-A775-D88AE07E2869} {734EF33E-028A-44C3-B83F-13DBFA8AC117} = {29EA07EB-E1D2-4BCA-9EA4-D69E28F14978} {70685213-B0C0-4589-B9ED-A67838227705} = {29EA07EB-E1D2-4BCA-9EA4-D69E28F14978} @@ -185,5 +216,12 @@ Global {FAB3D7AE-2260-4DC1-B2BB-E26C540056BF} = {FFB03EF5-B008-4F84-83D8-2F4B5D821251} {6F3DF341-F4BB-416A-8772-F68B4626C0C8} = {FFB03EF5-B008-4F84-83D8-2F4B5D821251} {9B6CAF84-D53C-4595-93B7-231E771FAED3} = {FFB03EF5-B008-4F84-83D8-2F4B5D821251} + {39C0CC7B-2185-48C6-8D22-F54212CA36D4} = {EEAD0AD4-0F90-46D9-A775-D88AE07E2869} + {0FBD3B80-E61E-4797-9599-98E8884E5798} = {EEAD0AD4-0F90-46D9-A775-D88AE07E2869} + {23E194C3-B1E2-49C9-A145-DFD3CDE724AD} = {39C0CC7B-2185-48C6-8D22-F54212CA36D4} + {C159A479-5020-4E8D-9377-6E2F6EA06396} = {39C0CC7B-2185-48C6-8D22-F54212CA36D4} + {786A1422-C4BD-4742-B33D-EBB82762CC63} = {39C0CC7B-2185-48C6-8D22-F54212CA36D4} + {028A9FD0-3C94-45D6-9A4D-68BD4985B176} = {39C0CC7B-2185-48C6-8D22-F54212CA36D4} + {A48B3BDD-5996-438B-951A-09C52E595FCB} = {39C0CC7B-2185-48C6-8D22-F54212CA36D4} EndGlobalSection EndGlobal diff --git a/framework/NPin.Framework.Core/Crypt/AES/AesCrypt.cs b/framework/NPin.Framework.Core/Crypt/AES/AesCrypt.cs new file mode 100644 index 0000000..2150c25 --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/AES/AesCrypt.cs @@ -0,0 +1,84 @@ +using System.Security.Cryptography; +using System.Text; + +namespace NPin.Framework.Core.Crypt.AES; + +public static class AesCrypt +{ + /// + /// 盐 + /// + private const string Slat = "Q+OFqu]luparUP;Xn^_ktHX^FoWiK4C#;daRV(b1bbT_;HrrAL"; + + /// + /// 处理key + /// + /// 输入的密码 + /// Key和IV模式 + /// + private static Tuple GetAesKey(string pwd, AesKeyEnum model = AesKeyEnum.AES256) + { + var hash1 = $"{pwd}-{Slat}".Md5(); + switch (model) + { + case AesKeyEnum.AES256: + { + var hash2 = $"{hash1}-{Slat}".Md5(); + var hash3 = $"{hash2}-{Slat}".To16Md5(); + var key = Encoding.UTF8.GetBytes($"{hash1}{hash2}".Md5()); + var iv = Encoding.UTF8.GetBytes(hash3); + return new(key, iv); + } + case AesKeyEnum.AES128: + { + var hash2 = $"{hash1}-{Slat}".To16Md5(); + var key = Encoding.UTF8.GetBytes(hash1); + var iv = Encoding.UTF8.GetBytes(hash2); + return new(key, iv); + } + default: throw new("不支持的类型"); + } + } + + /// + /// AES加密 + /// + /// 待加密数据 + /// 密钥 + /// Aes密钥模式 + /// 加密模式 + /// 填充模式 + /// + public static byte[] Encrypt(byte[] content, string pwd, AesKeyEnum model, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7) + { + var (Key, IV) = GetAesKey(pwd, model); + using var aes = Aes.Create(); + aes.Key = Key; + aes.IV = IV; + aes.Mode = mode; + aes.Padding = padding; + var cTransform = aes.CreateEncryptor(); + return cTransform.TransformFinalBlock(content, 0, content.Length); + } + + /// + /// AES解密 + /// + /// 待解密数据 + /// 密钥 + /// Aes密钥模式 + /// 加密模式 + /// 填充模式 + /// + public static byte[] Decrypt(byte[] secret, string pwd, AesKeyEnum model, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7) + { + var (key, iv) = GetAesKey(pwd, model); + using var aes = Aes.Create(); + aes.Key = key; + aes.IV = iv; + aes.Mode = mode; + aes.Padding = padding; + var cTransform = aes.CreateDecryptor(); + return cTransform.TransformFinalBlock(secret, 0, secret.Length); + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/AES/AesKeyEnum.cs b/framework/NPin.Framework.Core/Crypt/AES/AesKeyEnum.cs new file mode 100644 index 0000000..f038e1d --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/AES/AesKeyEnum.cs @@ -0,0 +1,17 @@ +namespace NPin.Framework.Core.Crypt.AES; + +/// +/// AES加密的密钥算法模式 +/// +public enum AesKeyEnum +{ + /// + /// AES128 + /// + AES128, + + /// + /// AES256 + /// + AES256 +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/BCrypt/BCrypt.cs b/framework/NPin.Framework.Core/Crypt/BCrypt/BCrypt.cs new file mode 100644 index 0000000..ac6e9f6 --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/BCrypt/BCrypt.cs @@ -0,0 +1,14 @@ +using System.Text; + +namespace NPin.Framework.Core.Crypt.BCrypt; + +public static class BCrypt +{ + public static byte[] Generate(string password, string salt, int cost) + { + var passBytes = Encoding.UTF8.GetBytes(password); + var saltBytes = Encoding.UTF8.GetBytes(salt); + + return Org.BouncyCastle.Crypto.Generators.BCrypt.Generate(passBytes, saltBytes, cost); + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/DES/DesCrypt.cs b/framework/NPin.Framework.Core/Crypt/DES/DesCrypt.cs new file mode 100644 index 0000000..09de010 --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/DES/DesCrypt.cs @@ -0,0 +1,77 @@ +using System.Security.Cryptography; +using System.Text; + +namespace NPin.Framework.Core.Crypt.DES; + +/// +/// DES加密解密(由于本库对密钥进行了hash算法处理.使用本库加密仅能用本库解密) +/// +public static class DesCrypt +{ + /// + /// 盐 + /// + private const string Slat = "Fo~@Ymf3w-!K+hYYoI^emXJeNt79pv@Sy,rpl0vXyIa-^jI{fU"; + + /// + /// 处理key + /// + /// 输入的密码 + /// + private static Tuple GetEesKey(string pwd) + { + var hash1 = $"{pwd}-{Slat}".Md5(); + var hash2 = $"{hash1}-{Slat}".Md5(); + var hash3 = $"{hash2}-{Slat}".To16Md5(); + var key = Encoding.UTF8.GetBytes($"{hash1}{hash2}".To16Md5()[..8]); + var iv = Encoding.UTF8.GetBytes(hash3[..8]); + return new(key, iv); + } + + /// + /// DES加密 + /// + /// 待加密数据 + /// 密钥 + /// 加密模式 + /// 填充模式 + /// 加密后的数据 + public static byte[] Encrypt(byte[] content, string pwd, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7) + { + var (key, iv) = GetEesKey(pwd); + + var des = System.Security.Cryptography.DES.Create(); + des.Key = key; + des.IV = iv; + des.Mode = mode; + des.Padding = padding; + using var ms = new MemoryStream(); + using var cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write); + cs.Write(content, 0, content.Length); + cs.FlushFinalBlock(); + return ms.ToArray(); + } + + /// + /// DES解密字符串 + /// + /// 待解密数据 + /// 密钥 + /// 加密模式 + /// 填充模式 + /// 解密后的字符串 + public static byte[] Decrypt(byte[] secret, string pwd, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7) + { + var (key, iv) = GetEesKey(pwd); + var des = System.Security.Cryptography.DES.Create(); + des.Key = key; + des.IV = iv; + des.Mode = mode; + des.Padding = padding; + using var ms = new MemoryStream(); + using var cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write); + cs.Write(secret, 0, secret.Length); + cs.FlushFinalBlock(); + return ms.ToArray(); + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/DES/TripleDes.cs b/framework/NPin.Framework.Core/Crypt/DES/TripleDes.cs new file mode 100644 index 0000000..d1c3da3 --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/DES/TripleDes.cs @@ -0,0 +1,73 @@ +using System.Security.Cryptography; +using System.Text; + +namespace NPin.Framework.Core.Crypt.DES; + +public static class TripleDes +{ + /// + /// 盐 + /// + private const string slat = "HosW[A1]ew0sVtVzf[DfQ~x%hk2+ifMlg;)Wsf[9@Fh{_z$jNC"; + + /// + /// 处理key + /// + /// 输入的密码 + /// + private static Tuple GetEesKey(string pwd) + { + var hash1 = $"{pwd}-{slat}".Md5(); + var hash2 = $"{hash1}-{slat}".Md5(); + var hash3 = $"{hash2}-{slat}".To16Md5(); + var Key = Encoding.UTF8.GetBytes($"{hash1}{hash2}".Md5()[..24]); + var IV = Encoding.UTF8.GetBytes(hash3[..8]); + return new(Key, IV); + } + + /// + /// 使用给定密钥加密 + /// + /// 待加密数据 + /// 密钥 + /// 加密模式 + /// 填充模式 + /// 加密后的数据 + public static byte[] Encrypt(byte[] content, string pwd, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7) + { + var (Key, IV) = GetEesKey(pwd); + var des = TripleDES.Create(); + des.Key = Key; + des.IV = IV; + des.Mode = mode; + des.Padding = padding; + using var ms = new MemoryStream(); + using var cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write); + cs.Write(content, 0, content.Length); + cs.FlushFinalBlock(); + return ms.ToArray(); + } + + /// + /// 使用给定密钥解密数据 + /// + /// 待解密数据 + /// 密钥 + /// 加密模式 + /// 填充模式 + /// 解密后的数据 + public static byte[] Decrypt(byte[] secret, string pwd, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7) + { + var (Key, IV) = GetEesKey(pwd); + var des = TripleDES.Create(); + des.Key = Key; + des.IV = IV; + des.Mode = mode; + des.Padding = padding; + using var ms = new MemoryStream(); + using var cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write); + cs.Write(secret, 0, secret.Length); + cs.FlushFinalBlock(); + return ms.ToArray(); + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/Md5Extensions.cs b/framework/NPin.Framework.Core/Crypt/Md5Extensions.cs new file mode 100644 index 0000000..6f38b5e --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/Md5Extensions.cs @@ -0,0 +1,36 @@ +using System.Security.Cryptography; +using System.Text; + +namespace NPin.Framework.Core.Crypt; + +public static class Md5Extensions +{ + /// + /// 获取16位长度的MD5大写字符串 + /// 截取第 9~25 位 + /// + /// + /// + public static string To16Md5(this string value) => value.Md5().Substring(8, 16); + + /// + /// 获取32位长度的MD5大写字符串 + /// + /// + /// + public static string Md5(this string value) + { + using var md5 = MD5.Create(); + + var inputBytes = Encoding.UTF8.GetBytes(value); + var hashBytes = md5.ComputeHash(inputBytes); + + var sb = new StringBuilder(); + foreach (var hash in hashBytes) + { + sb.Append(hash.ToString("X2")); + } + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/RC4/Rc4Crypt.cs b/framework/NPin.Framework.Core/Crypt/RC4/Rc4Crypt.cs new file mode 100644 index 0000000..19ed027 --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/RC4/Rc4Crypt.cs @@ -0,0 +1,48 @@ +namespace NPin.Framework.Core.Crypt.RC4; + +/// +/// RC4 加密解密 +/// +public static class Rc4Crypt +{ + /// + /// RC4解密 + /// + /// 待解密数据 + /// 密钥 + /// + public static byte[] Decrypt(IEnumerable data, byte[] key) => Encrypt(data, key); + + /// + /// RC4加密 + /// + /// 待加密数据 + /// 密钥 + /// + public static byte[] Encrypt(IEnumerable data, byte[] key) + { + var s = EncryptInit(key); + var i = 0; + var j = 0; + return data.Select(b => + { + i = (i + 1) & 255; + j = (j + s[i]) & 255; + Swap(s, i, j); + return (byte)(b ^ s[(s[i] + s[j]) & 255]); + }).ToArray(); + } + + private static byte[] EncryptInit(byte[] key) + { + var s = Enumerable.Range(0, 256).Select(i => (byte)i).ToArray(); + for (int i = 0, j = 0; i < 256; i++) + { + j = (j + key[i % key.Length] + s[i]) & 255; + Swap(s, i, j); + } + return s; + } + + private static void Swap(byte[] s, int i, int j) => (s[i], s[j]) = (s[j], s[i]); +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/README.md b/framework/NPin.Framework.Core/Crypt/README.md new file mode 100644 index 0000000..cc782ad --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/README.md @@ -0,0 +1,14 @@ +> 来自于[EasilyNET.Security](https://github.com/EasilyNET/EasilyNET/blob/main/src/EasilyNET.Security/README.md)的加密库封装 + +一个.Net 中常用的加密算法的封装.降低加密解密的使用复杂度. + +- 目前有的算法:AES,DES,RC4,TripleDES,RSA,SM2,SM3,SM4 +- 支持 RSA XML 结构的 SecurityKey 和 Base64 格式的互转. +- 本库不是去实现加密算法,而是基于.Net 提供的接口封装,为了方便使用 +- 未经测试的预测,若是遇到了解密乱码,可能是需要引入一个包. +- 在主项目中添加 System.Text.Encoding.CodePages 库,并在程序入口处添加注册代码. Programe.cs + +```c# +var builder = WebApplication.CreateBuilder(args); +System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); +``` \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/RSA/RsaCrypt.cs b/framework/NPin.Framework.Core/Crypt/RSA/RsaCrypt.cs new file mode 100644 index 0000000..5f93e94 --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/RSA/RsaCrypt.cs @@ -0,0 +1,178 @@ +using System.Security.Cryptography; +using System.Text; + +namespace NPin.Framework.Core.Crypt.RSA; + +/// +/// RSA算法 +/// +public static class RsaCrypt +{ + /// + /// 创建RSA密钥对 + /// + /// 密钥的大小,必须384位到16384位,增量为8 + /// + public static RsaSecretKey GenerateKey(int keySize) + { + if (keySize is > 16384 or < 384 && keySize % 8 is not 0) + { + throw new ArgumentException("密钥的大小,必须384位到16384位,增量为8", nameof(keySize)); + } + + var rsa = new RSACryptoServiceProvider(keySize) + { + KeySize = keySize, + PersistKeyInCsp = false + }; + return new() + { + PrivateKey = rsa.ToXmlString(true), + PublicKey = rsa.ToXmlString(false) + }; + } + + /// + /// 创建RSA密钥对 + /// + /// 密钥的大小,提供常用长度枚举 + /// + public static RsaSecretKey GenerateKey(RsaKeyLength keySize) => GenerateKey((int)keySize); + + /// + /// 使用RSA的加密byte[](该方法存在长度限制) + /// + /// 当前RSA对象的密匙XML字符串(不包括专用参数)--公钥 + /// 需要进行加密的字节数组 + /// 加密后的数据 + public static byte[] Encrypt(string xmlPublicKey, byte[] content) + { + using var rsa = new RSACryptoServiceProvider(); + rsa.FromXmlString(xmlPublicKey); + return rsa.Encrypt(content, false); + } + + /// + /// RSA解密byte[](该方法存在长度限制) + /// + /// 当前RSA对象的密匙XML字符串(包括专用参数)--私钥 + /// 需要进行解密的字节数组 + /// 解密后的字符串 + public static byte[] Decrypt(string xmlPrivateKey, byte[] secret) + { + using var rsa = new RSACryptoServiceProvider(); + rsa.FromXmlString(xmlPrivateKey); + return rsa.Decrypt(secret, false); + } + + /// + /// RSA加密 不限长度的加密版本 + /// + /// 公匙 + /// 需要进行加密的数据 + /// 加密后的数据 + public static void Encrypt(string xmlPublicKey, byte[] content, out byte[] secret) + { + if (content.Length is 0) throw new("加密字符串不能为空."); + ArgumentException.ThrowIfNullOrEmpty(xmlPublicKey, nameof(xmlPublicKey)); + using var rsaProvider = new RSACryptoServiceProvider(); + rsaProvider.FromXmlString(xmlPublicKey); //载入公钥 + var bufferSize = (rsaProvider.KeySize >> 3) - 11; //单块最大长度 + var buffer = new byte[bufferSize]; + using MemoryStream ms = new(content), os = new(); + while (true) + { + //分段加密 + var readSize = ms.Read(buffer, 0, bufferSize); + if (readSize <= 0) break; + var temp = new byte[readSize]; + Array.Copy(buffer, 0, temp, 0, readSize); + var encryptedBytes = rsaProvider.Encrypt(temp, false); + os.Write(encryptedBytes, 0, encryptedBytes.Length); + } + + secret = os.ToArray(); //转化为字节流方便传输 + } + + /// + /// RSA解密 不限长度的解密版本 + /// + /// 私匙 + /// 需要进行解密的数据 + /// 解密后的数据 + public static void Decrypt(string xmlPrivateKey, byte[] secret, out byte[] context) + { + if (secret.Length is 0) throw new("解密字符串不能为空."); + ArgumentException.ThrowIfNullOrEmpty(xmlPrivateKey, nameof(xmlPrivateKey)); + using var rsaProvider = new RSACryptoServiceProvider(); + rsaProvider.FromXmlString(xmlPrivateKey); + var bufferSize = rsaProvider.KeySize >> 3; + var buffer = new byte[bufferSize]; + using MemoryStream ms = new(secret), os = new(); + while (true) + { + var readSize = ms.Read(buffer, 0, bufferSize); + if (readSize <= 0) break; + var temp = new byte[readSize]; + Array.Copy(buffer, 0, temp, 0, readSize); + var rawBytes = rsaProvider.Decrypt(temp, false); + os.Write(rawBytes, 0, rawBytes.Length); + } + + context = os.ToArray(); + } + + /// + /// 从文件中取得SHA256描述信息 + /// + /// + /// + public static string GetFileSHA256(FileStream objFile) + { + ArgumentNullException.ThrowIfNull(objFile, nameof(objFile)); + using var stream = new MemoryStream(); + objFile.CopyTo(stream); + var bytes = stream.ToArray(); + var array = SHA256.HashData(bytes); + objFile.Close(); + return Encoding.UTF8.GetString(array); + } + + #region RSA签名与签名验证 + + /// + /// RSA签名 + /// + /// 当前RSA对象的密匙XML字符串(包括专用参数)--私钥 + /// 需要签名的数据 + /// 签名数据 + public static byte[] Signature(string xmlPrivateKey, byte[] context) + { + using var rsa = new RSACryptoServiceProvider(); + rsa.FromXmlString(xmlPrivateKey); + var rsaFormatter = new RSAPKCS1SignatureFormatter(rsa); + //设置签名的算法为SHA256 + rsaFormatter.SetHashAlgorithm("SHA256"); + //执行签名 + return rsaFormatter.CreateSignature(context); + } + + /// + /// RSA 签名验证 + /// + /// 当前RSA对象的密匙XML字符串(不包括专用参数)--公钥 + /// 用RSA签名的数据[俗称:Hash描述字符串,即:MD5或者SHA256这种.本库使用SHA256] + /// 要为该数据验证的已签名数据 + /// 如果 Verification 与使用指定的哈希算法和密钥在 signature 上计算出的签名匹配,则为 ;否则为 . + public static bool Verification(string xmlPublicKey, byte[] secret, byte[] signature) + { + using var rsa = new RSACryptoServiceProvider(); + rsa.FromXmlString(xmlPublicKey); + var formatter = new RSAPKCS1SignatureDeformatter(rsa); + //指定解密的时候HASH算法为SHA256 + formatter.SetHashAlgorithm("SHA256"); + return formatter.VerifySignature(secret, signature); + } + + #endregion +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/RSA/RsaKeyConverter.cs b/framework/NPin.Framework.Core/Crypt/RSA/RsaKeyConverter.cs new file mode 100644 index 0000000..7bb3008 --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/RSA/RsaKeyConverter.cs @@ -0,0 +1,89 @@ +using System.Security.Cryptography; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509; + +namespace NPin.Framework.Core.Crypt.RSA; + +/// +/// RSAKey转化扩展类,用于将XML格式和Base64这种互转.如C#和Java的编码就不一样. +/// +public static class RsaKeyConverter +{ + /// + /// XML私钥 👉 Base64私钥 + /// + /// XML私钥 + /// Base64私钥 + public static string ToBase64PrivateKey(string xmlPrivate) + { + using var rsa = new RSACryptoServiceProvider(); + rsa.FromXmlString(xmlPrivate); + var param = rsa.ExportParameters(true); + var privateKeyParam = new RsaPrivateCrtKeyParameters(new(1, param.Modulus), new(1, param.Exponent), + new(1, param.D), new(1, param.P), + new(1, param.Q), new(1, param.DP), + new(1, param.DQ), new(1, param.InverseQ)); + var privateKey = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKeyParam); + return Convert.ToBase64String(privateKey.ToAsn1Object().GetEncoded()); + } + + /// + /// XML公钥 👉 Base64公钥 + /// + /// XML公钥 + /// Base64公钥 + public static string ToBase64PublicKey(string xmlPublic) + { + using var rsa = new RSACryptoServiceProvider(); + rsa.FromXmlString(xmlPublic); + var p = rsa.ExportParameters(false); + var keyParams = new RsaKeyParameters(false, new(1, p.Modulus), new(1, p.Exponent)); + var publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyParams); + return Convert.ToBase64String(publicKeyInfo.ToAsn1Object().GetEncoded()); + } + + /// + /// Base64私钥 👉 XML私钥 + /// + /// Base64私钥 + /// XML私钥 + public static string ToXmlPrivateKey(string base64Private) + { + var privateKeyParams = + (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(base64Private)); + using var rsa = new RSACryptoServiceProvider(); + var rsaParams = new RSAParameters + { + Modulus = privateKeyParams.Modulus.ToByteArrayUnsigned(), + Exponent = privateKeyParams.PublicExponent.ToByteArrayUnsigned(), + D = privateKeyParams.Exponent.ToByteArrayUnsigned(), + DP = privateKeyParams.DP.ToByteArrayUnsigned(), + DQ = privateKeyParams.DQ.ToByteArrayUnsigned(), + P = privateKeyParams.P.ToByteArrayUnsigned(), + Q = privateKeyParams.Q.ToByteArrayUnsigned(), + InverseQ = privateKeyParams.QInv.ToByteArrayUnsigned() + }; + rsa.ImportParameters(rsaParams); + return rsa.ToXmlString(true); + } + + /// + /// Base64公钥 👉 XML公钥 + /// + /// Base64公钥 + /// XML公钥 + public static string ToXmlPublicKey(string base64Public) + { + var p = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(base64Public)); + using var rsa = new RSACryptoServiceProvider(); + var rsaParams = new RSAParameters + { + Modulus = p.Modulus.ToByteArrayUnsigned(), + Exponent = p.Exponent.ToByteArrayUnsigned() + }; + rsa.ImportParameters(rsaParams); + return rsa.ToXmlString(false); + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/RSA/RsaKeyLength.cs b/framework/NPin.Framework.Core/Crypt/RSA/RsaKeyLength.cs new file mode 100644 index 0000000..280b361 --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/RSA/RsaKeyLength.cs @@ -0,0 +1,27 @@ +namespace NPin.Framework.Core.Crypt.RSA; + +/// +/// RSA 密钥长度 +/// +public enum RsaKeyLength +{ + /// + /// 512 bit + /// + Bit512 = 512, + + /// + /// 1024 bit + /// + Bit1024 = 1024, + + /// + /// 2048 bit + /// + Bit2048 = 2048, + + /// + /// 4096 bit + /// + Bit4096 = 4096 +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/RSA/RsaSecretKey.cs b/framework/NPin.Framework.Core/Crypt/RSA/RsaSecretKey.cs new file mode 100644 index 0000000..3c60fe6 --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/RSA/RsaSecretKey.cs @@ -0,0 +1,49 @@ +namespace NPin.Framework.Core.Crypt.RSA; + +/// +/// RSA密钥 结构体 +/// +/// +/// +public struct RsaSecretKey(string privateKey, string publicKey) +{ + /// + /// 公钥 + /// + public string PublicKey { get; set; } = publicKey; + + /// + /// 私钥 + /// + public string PrivateKey { get; set; } = privateKey; + + /// + /// 得到XML格式的私钥和公钥 + /// + /// + public readonly string ToXmlString() => + $""" + -----BEGIN RSA XML PRIVATE KEY----- + {PrivateKey} + -----END RSA XML PRIVATE KEY----- + + -----BEGIN RSA XML PUBLIC KEY----- + {PublicKey} + -----END RSA XML PUBLIC KEY----- + """; + + /// + /// 得到Base64格式的私钥和公钥 + /// + /// + public readonly string ToBase64String() => + $""" + -----BEGIN RSA BASE64 PRIVATE KEY----- + {RsaKeyConverter.ToBase64PrivateKey(PrivateKey)} + -----END RSA BASE64 PRIVATE KEY----- + + -----BEGIN RSA BASE64 PUBLIC KEY----- + {RsaKeyConverter.ToBase64PublicKey(PublicKey)} + -----END RSA BASE64 PUBLIC KEY----- + """; +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/SM2/Sm2Crypt.cs b/framework/NPin.Framework.Core/Crypt/SM2/Sm2Crypt.cs new file mode 100644 index 0000000..fd4bd0f --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/SM2/Sm2Crypt.cs @@ -0,0 +1,157 @@ +using Org.BouncyCastle.Asn1.GM; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; + +namespace NPin.Framework.Core.Crypt.SM2; + +/// +/// BouncyCastle(BC) 实现SM2国密加解密、签名、验签 +/// +public static class Sm2Crypt +{ + private static readonly X9ECParameters X9 = GMNamedCurves.GetByName("SM2P256V1"); + + /// + /// 构建公钥和私钥 + /// + /// + /// + public static void GenerateKey(out byte[] publicKey, out byte[] privateKey) + { + var g = new ECKeyPairGenerator(); + g.Init(new ECKeyGenerationParameters(new ECDomainParameters(X9), new())); + var k = g.GenerateKeyPair(); + publicKey = ((ECPublicKeyParameters)k.Public).Q.GetEncoded(false); + privateKey = ((ECPrivateKeyParameters)k.Private).D.ToByteArray(); + } + + /// + /// SM2加密,默认:C1C3C2 + /// + /// 公钥 + /// 需要加密的数据 + /// 模式 + /// 用户ID + /// + public static byte[] Encrypt(byte[] publicKey, byte[] data, byte[]? userId = null, + SM2Engine.Mode model = SM2Engine.Mode.C1C3C2) + { + var sm2 = new SM2Engine(new SM3Digest(), SM2Engine.Mode.C1C3C2); + ICipherParameters cp = + new ParametersWithRandom(new ECPublicKeyParameters(X9.Curve.DecodePoint(publicKey), new(X9))); + if (userId is not null) + { + cp = new ParametersWithID(cp, userId); + } + + sm2.Init(true, cp); + data = sm2.ProcessBlock(data, 0, data.Length); + if (model == SM2Engine.Mode.C1C2C3) data = C132ToC123(data); + return data; + } + + /// + /// SM2解密,默认:C1C3C2 + /// + /// 私钥 + /// 需要解密的数据 + /// 模式 + /// 用户ID + /// + public static byte[] Decrypt(byte[] privateKey, byte[] data, byte[]? userId = null, + SM2Engine.Mode model = SM2Engine.Mode.C1C3C2) + { + if (model == SM2Engine.Mode.C1C2C3) data = C123ToC132(data); + var sm2 = new SM2Engine(new SM3Digest(), SM2Engine.Mode.C1C3C2); + ICipherParameters cp = new ECPrivateKeyParameters(new(1, privateKey), new(X9)); + if (userId is not null) + { + cp = new ParametersWithID(cp, userId); + } + + sm2.Init(false, cp); + return sm2.ProcessBlock(data, 0, data.Length); + } + + /// + /// SM2签名 + /// + /// 私钥 + /// 数据 + /// 用户ID + /// + public static byte[] Signature(byte[] privateKey, byte[] msg, byte[]? userId = null) + { + var sm2 = new SM2Signer(new SM3Digest()); + // var sm2 = new SM2Signer(StandardDsaEncoding.Instance, new SM3Digest()); + // var sm2 = new SM2Signer(PlainDsaEncoding.Instance, new SM3Digest()); + ICipherParameters cp = new ParametersWithRandom(new ECPrivateKeyParameters(new(1, privateKey), new(X9))); + if (userId is not null) + { + cp = new ParametersWithID(cp, userId); + } + + sm2.Init(true, cp); + sm2.BlockUpdate(msg, 0, msg.Length); + return sm2.GenerateSignature(); + } + + /// + /// SM2验签 + /// + /// 公钥 + /// 数据 + /// 签名数据 + /// 用户ID + /// + public static bool Verify(byte[] publicKey, byte[] msg, byte[] signature, byte[]? userId = null) + { + var sm2 = new SM2Signer(new SM3Digest()); + ICipherParameters cp = new ECPublicKeyParameters(X9.Curve.DecodePoint(publicKey), new(X9)); + if (userId is not null) + { + cp = new ParametersWithID(cp, userId); + } + + sm2.Init(false, cp); + sm2.BlockUpdate(msg, 0, msg.Length); + return sm2.VerifySignature(signature); + } + + /// + /// C1C2C3转成C1C3C2 + /// + /// + /// + private static byte[] C123ToC132(byte[] c1c2c3) + { + var c1Len = (((X9.Curve.FieldSize + 7) >> 3) << 1) + 1; //sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。 + const int c3Len = 32; + var result = new byte[c1c2c3.Length]; + Array.Copy(c1c2c3, 0, result, 0, c1Len); //c1 + Array.Copy(c1c2c3, c1c2c3.Length - c3Len, result, c1Len, c3Len); //c3 + Array.Copy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.Length - c1Len - c3Len); //c2 + return result; + } + + /// + /// C1C3C2转成C1C2C3 + /// + /// + /// + private static byte[] C132ToC123(byte[] c1c3c2) + { + var c1Len = (((X9.Curve.FieldSize + 7) >> 3) << 1) + 1; + const int c3Len = 32; + var result = new byte[c1c3c2.Length]; + Array.Copy(c1c3c2, 0, result, 0, c1Len); //c1: 0->65 + Array.Copy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.Length - c1Len - c3Len); //c2 + Array.Copy(c1c3c2, c1Len, result, c1c3c2.Length - c3Len, c3Len); //c3 + return result; + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/SM3/Sm3Crypt.cs b/framework/NPin.Framework.Core/Crypt/SM3/Sm3Crypt.cs new file mode 100644 index 0000000..70392e2 --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/SM3/Sm3Crypt.cs @@ -0,0 +1,37 @@ +using System.Text; +using Org.BouncyCastle.Crypto.Digests; + +namespace NPin.Framework.Core.Crypt.SM3; + +/// +/// SM3算法(10进制的ASCII) +/// 在SHA-256基础上改进实现的一种算法 +/// 对标国际MD5算法和SHA算法 +/// +public static class Sm3Crypt +{ + /// + /// 获取字符串的SM3签名 + /// + /// + /// + public static byte[] Signature(string data) + { + var msg = Encoding.UTF8.GetBytes(data); + return Signature(msg); + } + + /// + /// 获取字节数组的SM3签名 + /// + /// + /// + public static byte[] Signature(byte[] data) + { + var md = new byte[32]; + var sm3 = new SM3Digest(); + sm3.BlockUpdate(data, 0, data.Length); + sm3.DoFinal(md, 0); + return md; + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/SM4/Sm4.cs b/framework/NPin.Framework.Core/Crypt/SM4/Sm4.cs new file mode 100644 index 0000000..0769a62 --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/SM4/Sm4.cs @@ -0,0 +1,323 @@ +namespace NPin.Framework.Core.Crypt.SM4; + +internal sealed class Sm4 +{ + /// + /// 固定参数CK + /// + private readonly uint[] CK = + [ + 0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269, + 0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9, + 0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249, + 0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9, + 0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229, + 0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299, + 0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209, + 0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279 + ]; + + /// + /// 系统参数FK + /// + private readonly uint[] FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc]; + + /// + /// S盒 + /// + private readonly byte[] SBoxTable = + [ + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f*/ + /*0*/ 0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05, + /*1*/ 0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99, + /*2*/ 0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62, + /*3*/ 0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6, + /*4*/ 0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8, + /*5*/ 0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35, + /*6*/ 0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87, + /*7*/ 0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e, + /*8*/ 0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1, + /*9*/ 0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3, + /*a*/ 0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f, + /*b*/ 0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51, + /*c*/ 0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8, + /*d*/ 0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0, + /*e*/ 0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84, + /*f*/ 0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48 + ]; + + /// + /// 加密 非线性τ函数B=τ(A) + /// + /// + /// + /// + private static long GetULongByBe(byte[] b, int i) => ((long)(b[i] & 0xff) << 24) | (uint)((b[i + 1] & 0xff) << 16) | (uint)((b[i + 2] & 0xff) << 8) | (b[i + 3] & 0xff & 0xffffffffL); + + /// + /// 解密 非线性τ函数B=τ(A) + /// + /// + /// + /// + private static void PutULongToBe(long n, byte[] b, int i) + { + b[i] = (byte)(int)(0xFF & (n >> 24)); + b[i + 1] = (byte)(int)(0xFF & (n >> 16)); + b[i + 2] = (byte)(int)(0xFF & (n >> 8)); + b[i + 3] = (byte)(int)(0xFF & n); + } + + private static long SHL(long x, int n) => (x & 0xFFFFFFFF) << n; + + /// + /// 循环移位,为32位的x循环左移n位 + /// + /// + /// + /// + private static long RotL(long x, int n) => SHL(x, n) | (x >> (32 - n)); + + /// + /// 将密钥逆序 + /// + /// + /// + private static void Swap(long[] sk, int i) => (sk[i], sk[31 - i]) = (sk[31 - i], sk[i]); + + /// + /// Sm4的S盒取值 + /// + /// + /// + private byte SBox(byte inch) => SBoxTable[inch & 0xFF]; + + /// + /// 线性变换 L + /// + /// + /// + private long Lt(long ka) + { + var a = new byte[4]; + var b = new byte[4]; + PutULongToBe(ka, a, 0); + b[0] = SBox(a[0]); + b[1] = SBox(a[1]); + b[2] = SBox(a[2]); + b[3] = SBox(a[3]); + var bb = GetULongByBe(b, 0); + return bb ^ RotL(bb, 2) ^ RotL(bb, 10) ^ RotL(bb, 18) ^ RotL(bb, 24); + } + + /// + /// 轮函数 F + /// + /// + /// + /// + /// + /// + /// + private long F(long x0, long x1, long x2, long x3, long rk) => x0 ^ Lt(x1 ^ x2 ^ x3 ^ rk); + + /// + /// 轮密钥rk + /// + /// + /// + private long CalcRK(long ka) + { + var a = new byte[4]; + var b = new byte[4]; + PutULongToBe(ka, a, 0); + b[0] = SBox(a[0]); + b[1] = SBox(a[1]); + b[2] = SBox(a[2]); + b[3] = SBox(a[3]); + var bb = GetULongByBe(b, 0); + return bb ^ RotL(bb, 13) ^ RotL(bb, 23); + } + + /// + /// 加密密钥 + /// + /// + /// + private void SetKey(long[] SK, byte[] key) + { + var MK = new long[4]; + var k = new long[36]; + var i = 0; + MK[0] = GetULongByBe(key, 0); + MK[1] = GetULongByBe(key, 4); + MK[2] = GetULongByBe(key, 8); + MK[3] = GetULongByBe(key, 12); + k[0] = MK[0] ^ FK[0]; + k[1] = MK[1] ^ FK[1]; + k[2] = MK[2] ^ FK[2]; + k[3] = MK[3] ^ FK[3]; + for (; i < 32; i++) + { + k[i + 4] = k[i] ^ CalcRK(k[i + 1] ^ k[i + 2] ^ k[i + 3] ^ CK[i]); + SK[i] = k[i + 4]; + } + } + + /// + /// 解密函数 + /// + /// 轮密钥 + /// 输入分组的密文 + /// 输出的对应的分组明文 + private void OneRound(long[] sk, byte[] input, byte[] output) + { + var i = 0; + var ul_buf = new long[36]; + ul_buf[0] = GetULongByBe(input, 0); + ul_buf[1] = GetULongByBe(input, 4); + ul_buf[2] = GetULongByBe(input, 8); + ul_buf[3] = GetULongByBe(input, 12); + while (i < 32) + { + ul_buf[i + 4] = F(ul_buf[i], ul_buf[i + 1], ul_buf[i + 2], ul_buf[i + 3], sk[i]); + i++; + } + PutULongToBe(ul_buf[35], output, 0); + PutULongToBe(ul_buf[34], output, 4); + PutULongToBe(ul_buf[33], output, 8); + PutULongToBe(ul_buf[32], output, 12); + } + + /// + /// 补足 16 进制字符串的 0 字符,返回不带 0x 的16进制字符串 + /// + /// + /// 1表示加密,0表示解密 + /// + private static byte[] Padding(byte[] input, Sm4Mode mode) + { + byte[] ret; + if (mode is Sm4Mode.Encrypt) + { + var p = 16 - input.Length % 16; + ret = new byte[input.Length + p]; + Array.Copy(input, 0, ret, 0, input.Length); + for (var i = 0; i < p; i++) + { + ret[input.Length + i] = (byte)p; + } + } + else + { + int p = input[^1]; + ret = new byte[input.Length - p]; + Array.Copy(input, 0, ret, 0, input.Length - p); + } + return ret; + } + + /// + /// 设置加密的key + /// + /// + /// + internal void SetKeyEnc(Sm4Context ctx, byte[] key) + { + ctx.Mode = Sm4Mode.Encrypt; + SetKey(ctx.Key, key); + } + + /// + /// 设置解密的key + /// + /// + /// + internal void SetKeyDec(Sm4Context ctx, byte[] key) + { + ctx.Mode = Sm4Mode.Decrypt; + SetKey(ctx.Key, key); + for (var i = 0; i < 16; i++) + { + Swap(ctx.Key, i); + } + } + + internal byte[] ECB(Sm4Context ctx, byte[] input) + { + if (ctx is { IsPadding: true, Mode: Sm4Mode.Encrypt }) + { + input = Padding(input, Sm4Mode.Encrypt); + } + var length = input.Length; + var bins = new byte[length]; + Array.Copy(input, 0, bins, 0, length); + var bous = new byte[length]; + for (var i = 0; length > 0; length -= 16, i++) + { + var inBytes = new byte[16]; + var outBytes = new byte[16]; + Array.Copy(bins, i * 16, inBytes, 0, length > 16 ? 16 : length); + OneRound(ctx.Key, inBytes, outBytes); + Array.Copy(outBytes, 0, bous, i * 16, length > 16 ? 16 : length); + } + if (ctx is { IsPadding: true, Mode: Sm4Mode.Decrypt }) + { + bous = Padding(bous, Sm4Mode.Decrypt); + } + return bous; + } + + internal byte[] CBC(Sm4Context ctx, byte[] iv, byte[] input) + { + if (ctx is { IsPadding: true, Mode: Sm4Mode.Encrypt }) input = Padding(input, Sm4Mode.Encrypt); + var length = input.Length; + var bins = new byte[length]; + Array.Copy(input, 0, bins, 0, length); + var bousList = new List(); + int i; + if (ctx.Mode is Sm4Mode.Encrypt) + { + for (var j = 0; length > 0; length -= 16, j++) + { + var inBytes = new byte[16]; + var outBytes = new byte[16]; + var out1 = new byte[16]; + Array.Copy(bins, j * 16, inBytes, 0, length > 16 ? 16 : length); + for (i = 0; i < 16; i++) + { + outBytes[i] = (byte)(inBytes[i] ^ iv[i]); + } + OneRound(ctx.Key, outBytes, out1); + Array.Copy(out1, 0, iv, 0, 16); + for (var k = 0; k < 16; k++) + { + bousList.Add(out1[k]); + } + } + } + else + { + var temp = new byte[16]; + for (var j = 0; length > 0; length -= 16, j++) + { + var inBytes = new byte[16]; + var outBytes = new byte[16]; + var out1 = new byte[16]; + Array.Copy(bins, j * 16, inBytes, 0, length > 16 ? 16 : length); + Array.Copy(inBytes, 0, temp, 0, 16); + OneRound(ctx.Key, inBytes, outBytes); + for (i = 0; i < 16; i++) + { + out1[i] = (byte)(outBytes[i] ^ iv[i]); + } + Array.Copy(temp, 0, iv, 0, 16); + for (var k = 0; k < 16; k++) + { + bousList.Add(out1[k]); + } + } + } + return ctx is { IsPadding: true, Mode: Sm4Mode.Decrypt } ? Padding(bousList.ToArray(), Sm4Mode.Decrypt) : bousList.ToArray(); + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/SM4/Sm4Context.cs b/framework/NPin.Framework.Core/Crypt/SM4/Sm4Context.cs new file mode 100644 index 0000000..d234d0d --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/SM4/Sm4Context.cs @@ -0,0 +1,19 @@ +namespace NPin.Framework.Core.Crypt.SM4; + +internal sealed class Sm4Context +{ + /// + /// 是否补足16进制字符串 + /// + internal bool IsPadding { get; set; } = true; + + /// + /// 加密或者解密 + /// + internal Sm4Mode Mode { get; set; } = Sm4Mode.Encrypt; + + /// + /// 密钥 + /// + internal long[] Key { get; } = new long[32]; +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/SM4/Sm4Crypt.cs b/framework/NPin.Framework.Core/Crypt/SM4/Sm4Crypt.cs new file mode 100644 index 0000000..f6b0ce4 --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/SM4/Sm4Crypt.cs @@ -0,0 +1,116 @@ +using System.Text; +using Org.BouncyCastle.Utilities.Encoders; + +namespace NPin.Framework.Core.Crypt.SM4; + +/// +/// SM4 加解密 +/// +public static class Sm4Crypt +{ + /// + /// 加密ECB模式 + /// + /// 密钥 + /// 密钥是否是十六进制 + /// 二进制格式加密的内容 + /// 返回二进制格式密文 + public static byte[] EncryptECB(string secretKey, bool hexString, byte[] plainText) + { + var ctx = new Sm4Context + { + IsPadding = true, + Mode = Sm4Mode.Encrypt + }; + var keyBytes = hexString ? Hex.Decode(secretKey) : Encoding.UTF8.GetBytes(secretKey); + var sm4 = new Sm4(); + sm4.SetKeyEnc(ctx, keyBytes); + return sm4.ECB(ctx, plainText); + } + + /// + /// 解密ECB模式 + /// + /// 密钥 + /// 密钥是否是十六进制 + /// 二进制格式密文 + /// 二进制格式明文 + public static byte[] DecryptECB(string secretKey, bool hexString, byte[] cipherBytes) + { + var ctx = new Sm4Context + { + IsPadding = true, + Mode = Sm4Mode.Decrypt + }; + var keyBytes = hexString ? Hex.Decode(secretKey) : Encoding.UTF8.GetBytes(secretKey); + var sm4 = new Sm4(); + sm4.SetKeyDec(ctx, keyBytes); + return sm4.ECB(ctx, cipherBytes); + } + + /// + /// 加密CBC模式 + /// + /// 密钥 + /// 密钥和IV是否是十六进制 + /// + /// 二进制格式明文 + /// 返回二进制密文数组 + public static byte[] EncryptCBC(string secretKey, bool hexString, string iv, byte[] plainText) + { + var ctx = new Sm4Context + { + IsPadding = true, + Mode = Sm4Mode.Encrypt + }; + byte[] keyBytes; + byte[] ivBytes; + if (hexString) + { + keyBytes = Hex.Decode(secretKey); + ivBytes = Hex.Decode(iv); + } + else + { + keyBytes = Encoding.UTF8.GetBytes(secretKey); + ivBytes = Encoding.UTF8.GetBytes(iv); + } + + var sm4 = new Sm4(); + sm4.SetKeyEnc(ctx, keyBytes); + return sm4.CBC(ctx, ivBytes, plainText); + } + + /// + /// 解密CBC模式 + /// + /// 16进制密钥 + /// 密钥和IV是否是十六进制 + /// + /// 二进制格式密文 + /// 返回二进制格式明文 + public static byte[] DecryptCBC(string secretKey, bool hexString, string iv, byte[] cipherText) + { + var ctx = new Sm4Context + { + IsPadding = true, + Mode = Sm4Mode.Decrypt + }; + byte[] keyBytes; + byte[] ivBytes; + if (hexString) + { + keyBytes = Hex.Decode(secretKey); + ivBytes = Hex.Decode(iv); + } + else + { + keyBytes = Encoding.UTF8.GetBytes(secretKey); + ivBytes = Encoding.UTF8.GetBytes(iv); + } + + var sm4 = new Sm4(); + sm4.SetKeyDec(ctx, keyBytes); + return sm4.CBC(ctx, ivBytes, cipherText); + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/Crypt/SM4/Sm4Mode.cs b/framework/NPin.Framework.Core/Crypt/SM4/Sm4Mode.cs new file mode 100644 index 0000000..5f4818c --- /dev/null +++ b/framework/NPin.Framework.Core/Crypt/SM4/Sm4Mode.cs @@ -0,0 +1,7 @@ +namespace NPin.Framework.Core.Crypt.SM4; + +public enum Sm4Mode +{ + Encrypt, + Decrypt +} \ No newline at end of file diff --git a/framework/NPin.Framework.Core/NPin.Framework.Core.csproj b/framework/NPin.Framework.Core/NPin.Framework.Core.csproj index 0a3e1af..4bc59a2 100644 --- a/framework/NPin.Framework.Core/NPin.Framework.Core.csproj +++ b/framework/NPin.Framework.Core/NPin.Framework.Core.csproj @@ -2,6 +2,7 @@ + diff --git a/framework/NPin.Framework.SqlSugarCore.Abstractions/Data/IEnabled.cs b/framework/NPin.Framework.SqlSugarCore.Abstractions/Data/IEnabled.cs new file mode 100644 index 0000000..0a565b5 --- /dev/null +++ b/framework/NPin.Framework.SqlSugarCore.Abstractions/Data/IEnabled.cs @@ -0,0 +1,6 @@ +namespace NPin.Framework.SqlSugarCore.Abstractions.Data; + +public interface IEnabled +{ + bool IsEnabled { get; set; } +} \ No newline at end of file diff --git a/framework/NPin.Framework.SqlSugarCore.Abstractions/Data/IOrderNum.cs b/framework/NPin.Framework.SqlSugarCore.Abstractions/Data/IOrderNum.cs new file mode 100644 index 0000000..e69cb6a --- /dev/null +++ b/framework/NPin.Framework.SqlSugarCore.Abstractions/Data/IOrderNum.cs @@ -0,0 +1,9 @@ +namespace NPin.Framework.SqlSugarCore.Abstractions.Data; + +public interface IOrderNum +{ + /// + /// 排序号 + /// + int OrderNum { get; set; } +} \ No newline at end of file diff --git a/framework/NPin.Framework.SqlSugarCore.Abstractions/ISqlSugarDbConnectionCreator.cs b/framework/NPin.Framework.SqlSugarCore.Abstractions/ISqlSugarDbConnectionCreator.cs index 3e35021..9109b64 100644 --- a/framework/NPin.Framework.SqlSugarCore.Abstractions/ISqlSugarDbConnectionCreator.cs +++ b/framework/NPin.Framework.SqlSugarCore.Abstractions/ISqlSugarDbConnectionCreator.cs @@ -13,6 +13,7 @@ public interface ISqlSugarDbConnectionCreator Action OnLogExecuting { get; set; } Action OnLogExecuted { get; set; } Action EntityService { get; set; } + Action EntityNameService { get; set; } ConnectionConfig Build(Action? action = null); void SetDbAop(ISqlSugarClient currentDb); diff --git a/framework/NPin.Framework.SqlSugarCore/SqlSugarDbConnectionCreator.cs b/framework/NPin.Framework.SqlSugarCore/SqlSugarDbConnectionCreator.cs index 8bd7056..a88b6a5 100644 --- a/framework/NPin.Framework.SqlSugarCore/SqlSugarDbConnectionCreator.cs +++ b/framework/NPin.Framework.SqlSugarCore/SqlSugarDbConnectionCreator.cs @@ -20,6 +20,8 @@ public class SqlSugarDbConnectionCreator : ISqlSugarDbConnectionCreator, ITransi [DisablePropertyInjection] public Action OnLogExecuted { get; set; } [DisablePropertyInjection] public Action EntityService { get; set; } + + [DisablePropertyInjection] public Action EntityNameService { get; set; } public DbConnOptions Options { get; } @@ -87,6 +89,10 @@ public class SqlSugarDbConnectionCreator : ISqlSugarDbConnectionCreator, ITransi } EntityService(c, p); + }, + EntityNameService = (t, e) => + { + EntityNameService(t, e); } }, // Aop diff --git a/framework/NPin.Framework.SqlSugarCore/SqlSugarDbContext.cs b/framework/NPin.Framework.SqlSugarCore/SqlSugarDbContext.cs index 98846e8..f411de9 100644 --- a/framework/NPin.Framework.SqlSugarCore/SqlSugarDbContext.cs +++ b/framework/NPin.Framework.SqlSugarCore/SqlSugarDbContext.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NPin.Framework.SqlSugarCore.Abstractions; using SqlSugar; -using Volo.Abp; using Volo.Abp.Auditing; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; @@ -47,6 +46,7 @@ public class SqlSugarDbContext : ISqlSugarDbContext var connectionCreator = LazyServiceProvider.LazyGetRequiredService(); connectionCreator.OnSqlSugarClientConfig = OnSqlSugarClientConfig; connectionCreator.EntityService = EntityService; + connectionCreator.EntityNameService = EntityNameService; connectionCreator.DataExecuting = DataExecuting; connectionCreator.DataExecuted = DataExecuted; connectionCreator.OnLogExecuting = OnLogExecuting; @@ -204,6 +204,9 @@ public class SqlSugarDbContext : ISqlSugarDbContext /// /// 实体配置 + /// 自动主键 + /// 自动Ignore关联配置(导航) + /// 开启下划线 /// /// /// @@ -213,10 +216,25 @@ public class SqlSugarDbContext : ISqlSugarDbContext { column.IsIgnore = true; } + if (property.Name == nameof(Entity.Id)) { column.IsPrimarykey = true; } + + column.DbColumnName = UtilMethods.ToUnderLine(column.DbColumnName); + } + + /// + /// EntityName 配置 + /// 开启下划线 + /// + /// + /// + protected virtual void EntityNameService(Type type, EntityInfo entity) + { + // 开启下划线 + entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName); } public void Backup() diff --git a/module/upms/NPin.Framework.Upms.Application.Contracts/NPin.Framework.Upms.Application.Contracts.csproj b/module/upms/NPin.Framework.Upms.Application.Contracts/NPin.Framework.Upms.Application.Contracts.csproj new file mode 100644 index 0000000..c22cc34 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Application.Contracts/NPin.Framework.Upms.Application.Contracts.csproj @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/module/upms/NPin.Framework.Upms.Application.Contracts/NPinFrameworkUpmsApplicationContractsModule.cs b/module/upms/NPin.Framework.Upms.Application.Contracts/NPinFrameworkUpmsApplicationContractsModule.cs new file mode 100644 index 0000000..c7bdfa2 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Application.Contracts/NPinFrameworkUpmsApplicationContractsModule.cs @@ -0,0 +1,12 @@ +using NPin.Framework.Ddd.Application.Contracts; +using NPin.Framework.Upms.Domain.Shared; + +namespace NPin.Framework.Upms.Application.Contracts; + +[DependsOn( + typeof(NPinFrameworkUpmsDomainSharedModule), + typeof(NPinFrameworkDddApplicationContractsModule) +)] +public class NPinFrameworkUpmsApplicationContractsModule : AbpModule +{ +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Application/NPin.Framework.Upms.Application.csproj b/module/upms/NPin.Framework.Upms.Application/NPin.Framework.Upms.Application.csproj new file mode 100644 index 0000000..5d4dd8c --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Application/NPin.Framework.Upms.Application.csproj @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/module/upms/NPin.Framework.Upms.Application/NPinFrameworkUpmsApplicationModule.cs b/module/upms/NPin.Framework.Upms.Application/NPinFrameworkUpmsApplicationModule.cs new file mode 100644 index 0000000..2528c4f --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Application/NPinFrameworkUpmsApplicationModule.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.DependencyInjection; +using NPin.Framework.Ddd.Application; +using NPin.Framework.Upms.Application.Contracts; +using NPin.Framework.Upms.Domain; +using Volo.Abp.BackgroundWorkers.Quartz; + +namespace NPin.Framework.Upms.Application; + +[DependsOn( + typeof(NPinFrameworkUpmsApplicationContractsModule), + typeof(NPinFrameworkUpmsDomainModule), + // framework + typeof(NPinFrameworkDddApplicationModule), + typeof(AbpBackgroundWorkersQuartzModule) +)] +public class NPinFrameworkUpmsApplicationModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddCaptcha(); + } +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Caches/CaptchaPhoneCacheItem.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Caches/CaptchaPhoneCacheItem.cs new file mode 100644 index 0000000..2f52e8b --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Caches/CaptchaPhoneCacheItem.cs @@ -0,0 +1,26 @@ +namespace NPin.Framework.Upms.Domain.Shared.Caches; + +public class CaptchaPhoneCacheItem +{ + public string Code { get; set; } + + public CaptchaPhoneCacheItem(string code) + { + Code = code; + } +} + +public class CaptchaPhoneCacheKey +{ + public string Phone { get; set; } + + public CaptchaPhoneCacheKey(string phone) + { + Phone = phone; + } + + public override string ToString() + { + return $"Phone: {Phone}"; + } +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Consts/UserConst.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Consts/UserConst.cs new file mode 100644 index 0000000..abe3527 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Consts/UserConst.cs @@ -0,0 +1,16 @@ +namespace NPin.Framework.Upms.Domain.Shared.Consts; + +public class UserConst +{ + // 子租户管理员 + public const string Admin = "admin"; + public const string AdminRoleCode = "Admin"; + public const string AdminPermissionCode = "*:*:*"; + + // 租户管理员 + public const string TenantAdmin = "tadmin"; + public const string TenantAdminPermissionCode = "*"; + + // 角色 + public const string DefaultRoleCode = "Default"; +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Enums/DataScopeEnum.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Enums/DataScopeEnum.cs new file mode 100644 index 0000000..c8f2b4d --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Enums/DataScopeEnum.cs @@ -0,0 +1,32 @@ +namespace NPin.Framework.Upms.Domain.Shared.Enums; + +/// +/// 数据范围枚举 +/// +public enum DataScopeEnum +{ + /// + /// 所有数据 + /// + All = 0, + + /// + /// 自定义 + /// + Custom = 1, + + /// + /// 本部门 + /// + Dept = 2, + + /// + /// 部门以及子部门 + /// + DeptFollow = 3, + + /// + /// 只有自己 + /// + User = 4 +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Enums/GenderEnum.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Enums/GenderEnum.cs new file mode 100644 index 0000000..1816858 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Enums/GenderEnum.cs @@ -0,0 +1,19 @@ +namespace NPin.Framework.Upms.Domain.Shared.Enums; + +public enum GenderEnum +{ + /// + /// 保密 + /// + Secrecy = 0, + + /// + /// 男性 + /// + Male = 1, + + /// + /// 女性 + /// + Female = 2 +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Enums/JobTypeEnum.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Enums/JobTypeEnum.cs new file mode 100644 index 0000000..93389fb --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Enums/JobTypeEnum.cs @@ -0,0 +1,14 @@ +namespace NPin.Framework.Upms.Domain.Shared.Enums; + +public enum JobTypeEnum +{ + /// + /// Cron表达式 + /// + Cron, + + /// + /// 基于毫秒 + /// + Milliseconds +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/NPin.Framework.Upms.Domain.Shared.csproj b/module/upms/NPin.Framework.Upms.Domain.Shared/NPin.Framework.Upms.Domain.Shared.csproj new file mode 100644 index 0000000..b68603d --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/NPin.Framework.Upms.Domain.Shared.csproj @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/NPinFrameworkUpmsDomainSharedModule.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/NPinFrameworkUpmsDomainSharedModule.cs new file mode 100644 index 0000000..57a5513 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/NPinFrameworkUpmsDomainSharedModule.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.DependencyInjection; +using NPin.Framework.Mapster; +using NPin.Framework.Upms.Domain.Shared.Options; +using Volo.Abp.Domain; + +namespace NPin.Framework.Upms.Domain.Shared; + +[DependsOn( + typeof(AbpDddDomainSharedModule), + typeof(NPinFrameworkMapsterModule) +)] +public class NPinFrameworkUpmsDomainSharedModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + // 配置文件读取 + Configure(configuration.GetSection(nameof(JwtOptions))); + Configure(configuration.GetSection(nameof(RefreshJwtOptions))); + Configure(configuration.GetSection(nameof(UpmsOptions))); + } +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/OperLog/OperLogAttribute.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/OperLog/OperLogAttribute.cs new file mode 100644 index 0000000..585185b --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/OperLog/OperLogAttribute.cs @@ -0,0 +1,31 @@ +namespace NPin.Framework.Upms.Domain.Shared.OperLog; + +[AttributeUsage(AttributeTargets.Method)] +public class OperLogAttribute: Attribute +{ + /// + /// 操作类型 + /// + public OperTypeEnum OperType { get; set; } + + /// + /// 日志标题 + /// + public string Title { get; set; } + + /// + /// 是否保存请求数据 + /// + public bool IsSaveRequestData { get; set; } = true; + + /// + /// 是否保存返回数据 + /// + public bool IsSaveResponseData { get; set; } = true; + + public OperLogAttribute(string title, OperTypeEnum operationType) + { + Title = title; + OperType = operationType; + } +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/OperLog/OperTypeEnum.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/OperLog/OperTypeEnum.cs new file mode 100644 index 0000000..ce26fb8 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/OperLog/OperTypeEnum.cs @@ -0,0 +1,19 @@ +namespace NPin.Framework.Upms.Domain.Shared.OperLog; + +/// +/// 操作类型枚举 +/// +public enum OperTypeEnum +{ + Insert = 1, + Update = 2, + Delete = 3, + + // + Auth = 4, + Export = 5, + Import = 6, + ForcedOut = 7, + GenerateCode = 8, + ClearData = 9 +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Options/AliyunOptions.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Options/AliyunOptions.cs new file mode 100644 index 0000000..24de15c --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Options/AliyunOptions.cs @@ -0,0 +1,17 @@ +namespace NPin.Framework.Upms.Domain.Shared.Options; + +/// +/// 阿里云SDK相关配置 +/// +public class AliyunOptions +{ + public string AccessKeyId { get; set; } + public string AccessKeySecret { get; set; } + public AliyunSms Sms { get; set; } +} + +public class AliyunSms +{ + public string SignName { get; set; } + public string TemplateCode { get; set; } +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Options/JwtOptions.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Options/JwtOptions.cs new file mode 100644 index 0000000..9328b13 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Options/JwtOptions.cs @@ -0,0 +1,15 @@ +namespace NPin.Framework.Upms.Domain.Shared.Options; + +/// +/// JWT 配置 +/// +public class JwtOptions +{ + public string Issuer { get; set; } = "NoahLan"; + + public string Audience { get; set; } = "NoahLan"; + + public string SecurityKey { get; set; } = "892u4j1803qj23jro0fjkf8bmsdf9nb9mf92834u23jdf923jrnmvasbceqwt347562tgdhdnsv9wevbnop"; + + public long ExpiresMinuteTime { get; set; } = 120; +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Options/RefreshJwtOptions.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Options/RefreshJwtOptions.cs new file mode 100644 index 0000000..a05a726 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Options/RefreshJwtOptions.cs @@ -0,0 +1,8 @@ +namespace NPin.Framework.Upms.Domain.Shared.Options; + +/// +/// 刷新Token 配置 +/// +public class RefreshJwtOptions : JwtOptions +{ +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Options/UpmsOptions.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Options/UpmsOptions.cs new file mode 100644 index 0000000..ba14c39 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Options/UpmsOptions.cs @@ -0,0 +1,32 @@ +namespace NPin.Framework.Upms.Domain.Shared.Options; + +/// +/// Upms模块的总体配置 +/// +public class UpmsOptions +{ + /// + /// 超级管理员默认密码 + /// + public string AdminPassword { get; set; } = "a123456"; + + /// + /// 租户超级管理员默认密码 + /// + public string TenantAdminPassword { get; set; } = "a123456"; + + /// + /// 是否开启登录图形验证码 + /// + public bool EnableCaptcha { get; set; } = false; + + /// + /// 是否开启注册功能 + /// + public bool EnableRegister { get; set; } = false; + + /// + /// 是否开启数据库自动o备份功能 + /// + public bool EnableDataBaseBackup { get; set; } = false; +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain/Authorization/IDataPermission.cs b/module/upms/NPin.Framework.Upms.Domain/Authorization/IDataPermission.cs new file mode 100644 index 0000000..2139b24 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain/Authorization/IDataPermission.cs @@ -0,0 +1,8 @@ +namespace NPin.Framework.Upms.Domain.Authorization; + +/// +/// 数据权限过滤接口,可通过DataFilter配置关闭 +/// +public interface IDataPermission +{ +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain/Entities/UserEntity.cs b/module/upms/NPin.Framework.Upms.Domain/Entities/UserEntity.cs new file mode 100644 index 0000000..82c16aa --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain/Entities/UserEntity.cs @@ -0,0 +1,88 @@ +using Microsoft.Win32.SafeHandles; +using NPin.Framework.SqlSugarCore.Abstractions.Data; +using NPin.Framework.Upms.Domain.Shared.Enums; +using SqlSugar; +using Volo.Abp.Auditing; +using Volo.Abp.Domain.Entities; + +namespace NPin.Framework.Upms.Domain.Entities; + +[SugarTable("User")] +public class UserEntity : Entity, ISoftDelete, IAuditedObject, IEnabled, IOrderNum +{ + #region User + + [SugarColumn(IsPrimaryKey = true)] public override Guid Id { get; protected set; } + + [SugarColumn(ColumnDescription = "用户名")] + public string Username { get; set; } = string.Empty; + + [SugarColumn(ColumnDescription = "手机号码")] + public string PhoneNumber { get; set; } + + [SugarColumn(ColumnDescription = "邮箱")] + public string? Email { get; set; } + + [SugarColumn(ColumnDescription = "昵称")] + public string? Nickname { get; set; } + + [SugarColumn(ColumnDescription = "密码")] + public string Password { get; set; } = string.Empty; + + [SugarColumn(ColumnDescription = "简介")] + public string? Introduction { get; set; } + + [SugarColumn(ColumnDescription = "性别")] + public GenderEnum Gender { get; set; } = GenderEnum.Secrecy; + + [SugarColumn(ColumnDescription = "注册IP地址")] + public string? IpAddr { get; set; } + + [SugarColumn(ColumnDescription = "头像")] + public string? Avatar { get; set; } + + #endregion + + #region Implements + + /// + /// 逻辑删除 + /// + public bool IsDeleted { get; } + + public DateTime CreationTime { get; } + public Guid? CreatorId { get; } + public DateTime? LastModificationTime { get; } + public Guid? LastModifierId { get; } + + /// + /// 是否启用 + /// + public bool IsEnabled { get; set; } + + public int OrderNum { get; set; } + + #endregion + + #region 关联关系(导航) + + #endregion + + public UserEntity() + { + } + + public UserEntity(string username, string phoneNumber, string password, string? nickname) + { + Username = username; + PhoneNumber = phoneNumber; + if (username.IsNullOrEmpty()) + { + username = $"用户{phoneNumber}"; + } + Nickname = nickname.IsNullOrWhiteSpace() ? username : nickname; + + // Password + // SafeNCryptHandle + } +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain/NPin.Framework.Upms.Domain.csproj b/module/upms/NPin.Framework.Upms.Domain/NPin.Framework.Upms.Domain.csproj new file mode 100644 index 0000000..0d49405 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain/NPin.Framework.Upms.Domain.csproj @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/module/upms/NPin.Framework.Upms.Domain/NPinFrameworkUpmsDomainModule.cs b/module/upms/NPin.Framework.Upms.Domain/NPinFrameworkUpmsDomainModule.cs new file mode 100644 index 0000000..9163450 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain/NPinFrameworkUpmsDomainModule.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.DependencyInjection; +using NPin.Framework.Caching.FreeRedis; +using NPin.Framework.Upms.Domain.Shared; +using Volo.Abp.AspNetCore.SignalR; +using Volo.Abp.Caching; +using Volo.Abp.Domain; + +namespace NPin.Framework.Upms.Domain; + +[DependsOn( + typeof(NPinFrameworkUpmsDomainSharedModule), + typeof(NPinFrameworkCachingFreeRedisModule), + // Abp + typeof(AbpAspNetCoreSignalRModule), + typeof(AbpDddDomainModule), + typeof(AbpCachingModule) +)] +public class NPinFrameworkUpmsDomainModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + var services = context.Services; + var configuration = services.GetConfiguration(); + services.AddControllers(opts => + { + // opts.Filters.Add() + }); + + // 配置短信 + // Configure(); + } +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.SqlSugarCore/NPin.Framework.Upms.SqlSugarCore.csproj b/module/upms/NPin.Framework.Upms.SqlSugarCore/NPin.Framework.Upms.SqlSugarCore.csproj new file mode 100644 index 0000000..38f9347 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.SqlSugarCore/NPin.Framework.Upms.SqlSugarCore.csproj @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/module/upms/NPin.Framework.Upms.SqlSugarCore/NPinFrameworkUpmsSqlSugarCoreModule.cs b/module/upms/NPin.Framework.Upms.SqlSugarCore/NPinFrameworkUpmsSqlSugarCoreModule.cs new file mode 100644 index 0000000..6dec149 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.SqlSugarCore/NPinFrameworkUpmsSqlSugarCoreModule.cs @@ -0,0 +1,19 @@ +using NPin.Framework.Mapster; +using NPin.Framework.SqlSugarCore; +using NPin.Framework.Upms.Domain; + +namespace NPin.Framework.Upms.SqlSugarCore; + +[DependsOn( + typeof(NPinFrameworkUpmsDomainModule), + // framework + typeof(NPinFrameworkMapsterModule), + typeof(NPinFrameworkSqlSugarCoreModule) +)] +public class NPinFrameworkUpmsSqlSugarCoreModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + + } +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.SqlSugarCore/NPinUpmsDbContext.cs b/module/upms/NPin.Framework.Upms.SqlSugarCore/NPinUpmsDbContext.cs new file mode 100644 index 0000000..de4af47 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.SqlSugarCore/NPinUpmsDbContext.cs @@ -0,0 +1,49 @@ +using NPin.Framework.SqlSugarCore; +using NPin.Framework.Upms.Domain.Authorization; +using NPin.Framework.Upms.Domain.Shared.Consts; +using SqlSugar; +using Volo.Abp.DependencyInjection; + +namespace NPin.Framework.Upms.SqlSugarCore; + +public class NPinUpmsDbContext : SqlSugarDbContext +{ + public NPinUpmsDbContext(IAbpLazyServiceProvider lazyServiceProvider) : base(lazyServiceProvider) + { + } + + protected override void CustomDataFilter(ISqlSugarClient sqlSugarClient) + { + if (DataFilter.IsEnabled()) + { + DataPermissionFilter(sqlSugarClient); + } + + base.CustomDataFilter(sqlSugarClient); + } + + /// + /// 数据权限过滤 + /// + /// + protected void DataPermissionFilter(ISqlSugarClient sqlSugarClient) + { + // 当前无登录用户,无需过滤 + if (CurrentUser.Id == null) return; + + // 管理员角色 不进行过滤 + if (CurrentUser.UserName.Equals(UserConst.Admin) || + CurrentUser.Roles.Any(r => r.Equals(UserConst.AdminRoleCode))) + { + return; + } + + // var expUser = Expressionable.Create<>(); + // var expRole = Expressionable.Create<>(); + + // 无岗位 or 无角色 只能看自己 + + + // sqlSugarClient.QueryFilter.AddTableFilter() + } +} \ No newline at end of file