Compare commits

..

39 Commits

Author SHA1 Message Date
zpc
2bdd14da94 提交代码 2024-07-15 14:13:56 +08:00
zpc
bdcf89bc35 Merge branch 'dev' of 123.207.203.228:server/HuanMengProject into dev 2024-07-14 15:42:33 +08:00
zpc
8418cfa56e 修改实体类 2024-07-14 15:42:13 +08:00
0a0b80cc1c Merge branch 'dev' of http://123.207.203.228:3000/server/HuanMengProject into dev 2024-07-14 15:18:02 +08:00
ac95d9b438 添加注释 2024-07-14 15:17:02 +08:00
zpc
f82e97b4ef 修改实体类 2024-07-14 15:16:40 +08:00
zpc
2268c115e3 添加角色实体类缓存 2024-07-13 23:00:01 +08:00
zpc
d19afd6e8a 添加日志 2024-07-13 21:42:23 +08:00
zpc
72f84dce05 日志组件 2024-07-13 18:31:17 +08:00
zpc
9222d5d4f4 删除首页角色字段 2024-07-13 18:22:03 +08:00
zpc
af016b57e5 添加缓存 2024-07-13 16:29:27 +08:00
zpc
5263badb1a Merge branch 'dev' of 123.207.203.228:server/HuanMengProject into dev 2024-07-13 15:43:34 +08:00
zpc
14600d3392 修改注释 2024-07-13 15:43:28 +08:00
b7cae0952e 添加缓存 2024-07-13 15:42:36 +08:00
bcdfc25af1 删除聊天 2024-07-13 12:16:53 +08:00
8db46fb36d 删除聊天记录接口 2024-07-13 12:14:22 +08:00
zpc
cb73982ef0 修改验证代码 2024-07-12 16:56:46 +08:00
zpc
02ac4e3961 Merge branch 'dev' of 123.207.203.228:server/HuanMengProject into dev 2024-07-12 16:43:39 +08:00
zpc
b300c7df52 添加注释 2024-07-12 16:43:37 +08:00
4fe894fb6f Merge branch 'dev' of http://123.207.203.228:3000/server/HuanMengProject into dev 2024-07-12 16:42:19 +08:00
03710a37b1 删除聊天记录 2024-07-12 16:41:24 +08:00
zpc
ac181d208d Merge branch 'dev' of 123.207.203.228:server/HuanMengProject into dev 2024-07-12 16:16:00 +08:00
zpc
49e9ded36a 添加Claude对话 2024-07-12 16:15:55 +08:00
72a6b47e99 首页查询聊天记录接口 2024-07-12 11:21:47 +08:00
zpc
bfe3f95055 提交代码 2024-07-11 23:05:41 +08:00
zpc
9282b910ba 添加版本信息 2024-07-10 19:42:20 +08:00
zpc
5f4af8583a 处理异常 2024-07-10 18:37:51 +08:00
zpc
f5d9c53c2c 修改问题 2024-07-10 17:37:02 +08:00
zpc
0107308fec 添加注释 2024-07-10 01:32:10 +08:00
zpc
e080ec72dc 提交代码 2024-07-09 20:22:00 +08:00
zpc
e2a18ad132 修改注释 2024-07-09 04:31:19 +08:00
zpc
a62b93dc51 提交代码 2024-07-09 04:21:51 +08:00
zpc
9276e0e5d4 填充代码 2024-07-05 00:38:22 +08:00
zpc
43a50e0d1b 添加实体类 2024-07-04 21:25:05 +08:00
zpc
796bff8e49 提交代码 2024-06-29 10:43:50 +08:00
zpc
70126fb307 测试项目 2024-06-22 12:38:23 +08:00
zpc
ef1c3c00b7 提交代码 2024-06-14 16:40:25 +08:00
zpc
1f6407c515 调整文件路径 2024-06-11 18:57:50 +08:00
zpc
64c4ed3248 填充项目 2024-06-11 18:19:18 +08:00
164 changed files with 9455 additions and 16 deletions

4
.editorconfig Normal file
View File

@ -0,0 +1,4 @@
root = true
[*.cs]
charset = utf-8 #设置文件字符集为utf-8,在 Linux 系统中,通常推荐使用 UTF-8 而不是 UTF-8 with BOM。添加 BOM 可能会干扰那些不期望在文件开头出现非 ASCII 字节的软件对 UTF-8 的使用。

4
.gitignore vendored
View File

@ -360,4 +360,6 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd
output/
logs/

View File

@ -1,9 +0,0 @@
using System;
namespace ClassLibrary1
{
public class Class1
{
}
}

View File

@ -3,7 +3,48 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.34916.146
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary1", "ClassLibrary1\ClassLibrary1.csproj", "{00597C73-22E6-435E-B6D2-688177A703DC}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{443B77DF-BB15-4DF7-8F45-C54FE6F51AB9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "0-core", "0-core", "{DD14191F-22CE-48D8-A944-B8A41C97ACD4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1-service", "1-service", "{868BF208-7222-482B-B331-1A8482532F1B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2-api", "2-api", "{0C0B6EB5-E41D-46D9-9F60-90D320A2EEF3}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3-client", "3-client", "{23416FDA-19AE-487D-BCAA-5A9D04746500}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5-console", "5-console", "{F34C5BC4-8810-4D6A-B003-AFE12D7ED4BA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "9-test", "9-test", "{8D39E84B-2810-41D7-AFE6-0A58E09E34C3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HuanMeng.DotNetCore", "src\0-core\HuanMeng.DotNetCore\HuanMeng.DotNetCore.csproj", "{B9014C9B-B47D-4900-9EA7-4FE8998C30BE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4-abstractions", "4-abstractions", "{6C2243B0-58DA-4D59-A163-7DDDA7DB01C4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HuanMeng.StableDiffusion.Abstractions", "src\4-abstractions\HuanMeng.StableDiffusion.Abstractions\HuanMeng.StableDiffusion.Abstractions.csproj", "{2A7E25DE-A3B8-4F47-9282-64B8EB2D9953}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HuanMeng.StableDiffusion.WebSocket", "src\0-core\HuanMeng.StableDiffusion.WebSocket\HuanMeng.StableDiffusion.WebSocket.csproj", "{1B659309-ACDB-49FD-8AE0-2097A1BC6D17}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HuanMeng.StableDiffusion.TextGeneration", "src\0-core\HuanMeng.StableDiffusion.TextGeneration\HuanMeng.StableDiffusion.TextGeneration.csproj", "{CFE784F8-3889-4698-8719-55BAB1724486}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HuanMeng.StableDiffusion.TextGenerationTests", "src\9-test\HuanMeng.StableDiffusion.TextGenerationTests\HuanMeng.StableDiffusion.TextGenerationTests.csproj", "{054AFFF5-D25E-4EC7-A135-050EEE7F7FA0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TextGenerationApi", "src\2-api\TextGenerationApi\TextGenerationApi.csproj", "{A9C5939E-D239-4AB8-9380-7E76166326D9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HuanMnegopneaiTest", "src\5-console\HuanMnegopneaiTest\HuanMnegopneaiTest.csproj", "{D2A74CFA-8A03-4272-89C7-C01830D183F1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HuanMeng.MiaoYu.Model", "src\0-core\HuanMeng.MiaoYu.Model\HuanMeng.MiaoYu.Model.csproj", "{B845C884-AD1A-4483-A2F6-B218DB14EA2D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6C59E75C-386D-4325-9752-D7DB8B158BF8}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HuanMeng.MiaoYu.Code", "src\0-core\HuanMeng.MiaoYu.Code\HuanMeng.MiaoYu.Code.csproj", "{6E79742F-1E56-4B7D-94E8-B509D43561FA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HuanMeng.MiaoYu.WebApi", "src\2-api\HuanMeng.MiaoYu.WebApi\HuanMeng.MiaoYu.WebApi.csproj", "{729950F2-71EE-42C0-8B46-295740DE20BA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HuanMeng.Utility", "src\0-core\HuanMeng.Utility\HuanMeng.Utility.csproj", "{48E1532F-8B50-477C-BB78-8AEA89A167CE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -11,14 +52,74 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{00597C73-22E6-435E-B6D2-688177A703DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{00597C73-22E6-435E-B6D2-688177A703DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{00597C73-22E6-435E-B6D2-688177A703DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{00597C73-22E6-435E-B6D2-688177A703DC}.Release|Any CPU.Build.0 = Release|Any CPU
{B9014C9B-B47D-4900-9EA7-4FE8998C30BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9014C9B-B47D-4900-9EA7-4FE8998C30BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9014C9B-B47D-4900-9EA7-4FE8998C30BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9014C9B-B47D-4900-9EA7-4FE8998C30BE}.Release|Any CPU.Build.0 = Release|Any CPU
{2A7E25DE-A3B8-4F47-9282-64B8EB2D9953}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2A7E25DE-A3B8-4F47-9282-64B8EB2D9953}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2A7E25DE-A3B8-4F47-9282-64B8EB2D9953}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A7E25DE-A3B8-4F47-9282-64B8EB2D9953}.Release|Any CPU.Build.0 = Release|Any CPU
{1B659309-ACDB-49FD-8AE0-2097A1BC6D17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B659309-ACDB-49FD-8AE0-2097A1BC6D17}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B659309-ACDB-49FD-8AE0-2097A1BC6D17}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B659309-ACDB-49FD-8AE0-2097A1BC6D17}.Release|Any CPU.Build.0 = Release|Any CPU
{CFE784F8-3889-4698-8719-55BAB1724486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CFE784F8-3889-4698-8719-55BAB1724486}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CFE784F8-3889-4698-8719-55BAB1724486}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CFE784F8-3889-4698-8719-55BAB1724486}.Release|Any CPU.Build.0 = Release|Any CPU
{054AFFF5-D25E-4EC7-A135-050EEE7F7FA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{054AFFF5-D25E-4EC7-A135-050EEE7F7FA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{054AFFF5-D25E-4EC7-A135-050EEE7F7FA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{054AFFF5-D25E-4EC7-A135-050EEE7F7FA0}.Release|Any CPU.Build.0 = Release|Any CPU
{A9C5939E-D239-4AB8-9380-7E76166326D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A9C5939E-D239-4AB8-9380-7E76166326D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9C5939E-D239-4AB8-9380-7E76166326D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9C5939E-D239-4AB8-9380-7E76166326D9}.Release|Any CPU.Build.0 = Release|Any CPU
{D2A74CFA-8A03-4272-89C7-C01830D183F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D2A74CFA-8A03-4272-89C7-C01830D183F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D2A74CFA-8A03-4272-89C7-C01830D183F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D2A74CFA-8A03-4272-89C7-C01830D183F1}.Release|Any CPU.Build.0 = Release|Any CPU
{B845C884-AD1A-4483-A2F6-B218DB14EA2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B845C884-AD1A-4483-A2F6-B218DB14EA2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B845C884-AD1A-4483-A2F6-B218DB14EA2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B845C884-AD1A-4483-A2F6-B218DB14EA2D}.Release|Any CPU.Build.0 = Release|Any CPU
{6E79742F-1E56-4B7D-94E8-B509D43561FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6E79742F-1E56-4B7D-94E8-B509D43561FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6E79742F-1E56-4B7D-94E8-B509D43561FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6E79742F-1E56-4B7D-94E8-B509D43561FA}.Release|Any CPU.Build.0 = Release|Any CPU
{729950F2-71EE-42C0-8B46-295740DE20BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{729950F2-71EE-42C0-8B46-295740DE20BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{729950F2-71EE-42C0-8B46-295740DE20BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{729950F2-71EE-42C0-8B46-295740DE20BA}.Release|Any CPU.Build.0 = Release|Any CPU
{48E1532F-8B50-477C-BB78-8AEA89A167CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{48E1532F-8B50-477C-BB78-8AEA89A167CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{48E1532F-8B50-477C-BB78-8AEA89A167CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{48E1532F-8B50-477C-BB78-8AEA89A167CE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{DD14191F-22CE-48D8-A944-B8A41C97ACD4} = {443B77DF-BB15-4DF7-8F45-C54FE6F51AB9}
{868BF208-7222-482B-B331-1A8482532F1B} = {443B77DF-BB15-4DF7-8F45-C54FE6F51AB9}
{0C0B6EB5-E41D-46D9-9F60-90D320A2EEF3} = {443B77DF-BB15-4DF7-8F45-C54FE6F51AB9}
{23416FDA-19AE-487D-BCAA-5A9D04746500} = {443B77DF-BB15-4DF7-8F45-C54FE6F51AB9}
{F34C5BC4-8810-4D6A-B003-AFE12D7ED4BA} = {443B77DF-BB15-4DF7-8F45-C54FE6F51AB9}
{8D39E84B-2810-41D7-AFE6-0A58E09E34C3} = {443B77DF-BB15-4DF7-8F45-C54FE6F51AB9}
{B9014C9B-B47D-4900-9EA7-4FE8998C30BE} = {DD14191F-22CE-48D8-A944-B8A41C97ACD4}
{6C2243B0-58DA-4D59-A163-7DDDA7DB01C4} = {443B77DF-BB15-4DF7-8F45-C54FE6F51AB9}
{2A7E25DE-A3B8-4F47-9282-64B8EB2D9953} = {6C2243B0-58DA-4D59-A163-7DDDA7DB01C4}
{1B659309-ACDB-49FD-8AE0-2097A1BC6D17} = {DD14191F-22CE-48D8-A944-B8A41C97ACD4}
{CFE784F8-3889-4698-8719-55BAB1724486} = {DD14191F-22CE-48D8-A944-B8A41C97ACD4}
{054AFFF5-D25E-4EC7-A135-050EEE7F7FA0} = {8D39E84B-2810-41D7-AFE6-0A58E09E34C3}
{A9C5939E-D239-4AB8-9380-7E76166326D9} = {0C0B6EB5-E41D-46D9-9F60-90D320A2EEF3}
{D2A74CFA-8A03-4272-89C7-C01830D183F1} = {F34C5BC4-8810-4D6A-B003-AFE12D7ED4BA}
{B845C884-AD1A-4483-A2F6-B218DB14EA2D} = {DD14191F-22CE-48D8-A944-B8A41C97ACD4}
{6E79742F-1E56-4B7D-94E8-B509D43561FA} = {DD14191F-22CE-48D8-A944-B8A41C97ACD4}
{729950F2-71EE-42C0-8B46-295740DE20BA} = {0C0B6EB5-E41D-46D9-9F60-90D320A2EEF3}
{48E1532F-8B50-477C-BB78-8AEA89A167CE} = {DD14191F-22CE-48D8-A944-B8A41C97ACD4}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4A1DC406-AFAA-4884-859C-51B9B26E37FC}
EndGlobalSection

View File

@ -1 +1,13 @@
# HuanMengProject
# HuanMengProject 开发文档
### 项目说明
```sh
# Program.cs 文件说明
# 输出的时候,将请求的参数转换成小写
c.ParameterFilter<LowercaseParameterFilter>();
c.RequestBodyFilter<LowercaseRequestFilter>();
//配置路由选项使URL全部小写
builder.Services.AddRouting(options => options.LowercaseUrls = true);
```

View File

@ -0,0 +1,27 @@
namespace HuanMeng.DotNetCore.Base
{
public abstract class BLLBase<TDao> where TDao : DaoBase
{
/// <summary>
/// _serviceProvider,提供基本依赖注入支持
/// </summary>
protected readonly IServiceProvider _serviceProvider;
/// <summary>
/// _dao,提供数据访问支持
/// </summary>
protected abstract TDao Dao { get; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="serviceProvider"></param>
/// <param name="dao"></param>
public BLLBase(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
}
}

View File

@ -0,0 +1,92 @@
using System.Runtime.Serialization;
using XLib.DotNetCore.Base;
namespace HuanMeng.DotNetCore.Base
{
/// <summary>
/// 接口和服务调用基础响应类
/// </summary>
/// <typeparam name="T"></typeparam>
[DataContract]
[Serializable]
public class BaseResponse<T> : IResponse
{
///// <summary>
///// Http状态码
///// </summary>
//[DataMember]
//public HttpStatusCode StatusCode { get; set; }
/// <summary>
/// 功能执行返回代码
/// </summary>
[DataMember]
public int Code { get; set; }
/// <summary>
/// 消息
/// </summary>
[DataMember]
public string Message { get; set; }
/// <summary>
/// 数据
/// </summary>
[DataMember]
public T? Data { get; set; }
/// <summary>
/// 构造函数
/// </summary>
public BaseResponse()
{
//StatusCode = HttpStatusCode.OK;
Code = 0;
Message = "";
}
/// <summary>
/// 构造函数
/// </summary>
public BaseResponse(int code, string message)
{
Code = code;
Message = message;
Data = default(T);
}
/// <summary>
/// 构造函数
/// </summary>
public BaseResponse(int code, string message, T? data)
{
Code = code;
Message = message;
Data = data;
}
/// <summary>
/// 构造函数
/// </summary>
public BaseResponse(ResonseCode code, string message, T? data)
{
Code = (int)code;
Message = message;
Data = data;
}
/// <summary>
/// ToString
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"Code:{Code};Message:{Message}; Data:{Data}";
}
}
}

View File

@ -0,0 +1,32 @@
namespace HuanMeng.DotNetCore.Base
{
public class DaoBase
{
/// <summary>
/// _serviceProvider,提供基本依赖注入支持
/// </summary>
protected readonly IServiceProvider _serviceProvider;
///// <summary>
///// 构造函数
///// </summary>
//public DaoBase()
//{
// // 创建一个空的ServiceCollection
// var webApplication = WebApplication.Current;
// var services = new ServiceCollection();
// // 创建ServiceProvider实例
// _serviceProvider = services.BuildServiceProvider();
//}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="serviceProvider"></param>
public DaoBase(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
}
}

View File

@ -0,0 +1,195 @@
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.Base
{ /// <summary>
/// 基本数据库操作,需要安装
/// Microsoft.EntityFrameworkCore
/// Microsoft.EntityFrameworkCore.Relational
/// </summary>
/// <typeparam name="TDbContext"></typeparam>
public class EfCoreDaoBase<TDbContext> where TDbContext : DbContext
//, new()
{
private TDbContext _context;
public TDbContext context
{
get
{
//return _context ?? (_context = new TDbContext());
return _context;
}
//set { _context = value; }
}
//public EfCoreDaoBase() { }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="context"></param>
public EfCoreDaoBase(TDbContext context)
{
_context = context;
}
/// <summary>
/// 是否手动提交
/// </summary>
public bool IsManualSubmit { get; set; }
/// <summary>
/// SqlQueryRaw
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public T SqlQuery<T>(string sql, params object[] parameters) where T : class
{
var quiry = context.Database.SqlQueryRaw<T>(sql, parameters);
return quiry.FirstOrDefault();
}
/// <summary>
/// SqlQueryList
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public List<T> SqlQueryList<T>(string sql, params object[] parameters)
{
var quiry = context.Database.SqlQueryRaw<T>(sql, parameters);
return quiry.ToList();
}
/// <summary>
/// ExecuteSql
/// </summary>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public int ExecuteSql(string sql, params object[] parameters)
{
int r = context.Database.ExecuteSqlRaw(sql, parameters);
context.SaveChanges();
return r;
}
/// <summary>
/// 添加实体
/// </summary>
/// <param name="entity"></param>
public void Add<T>(T entity) where T : class
{
context.Set<T>().Add(entity);
if (!IsManualSubmit)
context.SaveChanges();
}
/// <summary>
/// 批量添加实体
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entity"></param>
public void AddRange<T>(List<T> entity) where T : class
{
context.Set<T>().AddRange(entity);
if (!IsManualSubmit)
context.SaveChanges();
}
/// <summary>
/// 删除某个实体
/// </summary>
/// <param name="entity"></param>
public void Delete<T>(T entity) where T : class
{
context.Entry<T>(entity).State = EntityState.Deleted; //整条更新
//context.Set<T>().Remove(entity);
if (!IsManualSubmit)
context.SaveChanges();
}
/// <summary>
/// 更新实体
/// </summary>
/// <param name="entity"></param>
public void Update<T>(T entity) where T : class
{
//一般不需要整条更新,按需更新字段即可
if (context.Entry<T>(entity).State == EntityState.Detached)
{
context.Set<T>().Attach(entity);
context.Entry<T>(entity).State = EntityState.Modified; //整条更新
}
if (!IsManualSubmit)
context.SaveChanges();
}
/// <summary>
/// 清除上下文跟踪(清除缓存) by wyg
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entity"></param>
public void removeTracking<T>(T entity) where T : class
{
if (context.Entry<T>(entity).State != EntityState.Detached)
context.Entry<T>(entity).State = EntityState.Detached;
}
/// <summary>
/// 获取实体,从缓存中,根据键值获取
/// </summary>
/// <param name="keyName"></param>
/// <param name="keyValue"></param>
/// <returns></returns>
public T GetModel<T>(params object[] keyValues) where T : class
{
return context.Set<T>().Find(keyValues);
}
/// <summary>
/// 获取实体,根据条件获取,从数据库获取
/// </summary>
/// <param name="where">筛选条件</param>
/// <param name="isNoTracking">默认false会进行缓存并跟踪(可按需update字段)true不需要缓存和跟踪(一般只读查询使用)</param>
/// <returns></returns>
public T GetModel<T>(System.Linq.Expressions.Expression<Func<T, bool>> where, bool isNoTracking = false) where T : class
{
if (!isNoTracking)
return context.Set<T>().FirstOrDefault(where); //可按需update字段
return context.Set<T>().AsNoTracking<T>().FirstOrDefault(where); //一般只读查询使用
}
/// <summary>
/// 获取列表数据返回IQueryable
/// </summary>
/// <param name="where">筛选条件</param>
/// <param name="isNoTracking">默认false会进行缓存并跟踪(可按需update字段)true不需要缓存和跟踪(一般只读查询使用)</param>
/// <returns></returns>
public IQueryable<T> GetList<T>(System.Linq.Expressions.Expression<Func<T, bool>> where, bool isNoTracking = false) where T : class
{
if (!isNoTracking)
return context.Set<T>().Where(where).AsQueryable();
return context.Set<T>().Where(where).AsNoTracking<T>().AsQueryable();
}
public int GetCount<T>(System.Linq.Expressions.Expression<Func<T, bool>> where) where T : class
{
return context.Set<T>().AsNoTracking<T>().Count(where);
}
public bool Exists<T>(System.Linq.Expressions.Expression<Func<T, bool>> where) where T : class
{
//return context.Set<T>().AsNoTracking<T>().Any(where);
return context.Set<T>().AsNoTracking<T>().Count(where) > 0;
}
}
}

View File

@ -0,0 +1,23 @@
namespace XLib.DotNetCore.Base
{
/// <summary>
/// 接口和服务调用通用响应接口
/// </summary>
public interface IResponse
{
///// <summary>
///// Http状态码
///// </summary>
//HttpStatusCode StatusCode { get; set; }
/// <summary>
/// 功能执行返回代码
/// </summary>
int Code { get; set; }
/// <summary>
/// 消息
/// </summary>
string Message { get; set; }
}
}

View File

@ -0,0 +1,53 @@
namespace HuanMeng.DotNetCore.Base
{
/// <summary>
/// 响应编码参考,实际的项目使用可以自行定义
/// 基本规则:
/// 成功大于等于0
/// 失败小于0
/// </summary>
public enum ResonseCode
{
/// <summary>
/// Sign签名错误
/// </summary>
SignError = -999,
/// <summary>
/// jwt用户签名错误
/// </summary>
TwtError = -998,
/// <summary>
/// 用户验证失败
/// </summary>
Unauthorized = 401,
/// <summary>
/// 重复请求
/// </summary>
ManyRequests = 429,
/// <summary>
/// 正在处理中
/// </summary>
Processing = 102,
/// <summary>
/// 通用错误
/// </summary>
Error = -1,
/// <summary>
/// 参数错误
/// </summary>
ParamError = -2,
/// <summary>
/// 没找到数据记录
/// </summary>
NotFoundRecord = -3,
/// <summary>
/// 成功
/// </summary>
Success = 0,
}
}

View File

@ -0,0 +1,55 @@
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace XLib.DotNetCore.CacheHelper
{
/// <summary>
/// 内存缓存帮助类
/// </summary>
public class MemoryCacheHelper
{
private static MemoryCache cache = new MemoryCache(new MemoryCacheOptions());
/// <summary>
/// 获取缓存
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="cacheName"></param>
/// <returns></returns>
public static T? GetCache<T>(string cacheName) where T : class, new()
{
return cache.TryGetValue(cacheName, out var value) ? value as T : null;
}
/// <summary>
/// 设置缓存
/// </summary>
/// <param name="cacheName"></param>
/// <param name="val"></param>
/// <param name="cacheTime">单位秒默认1小时</param>
public static void SetCache(string cacheName, object val, int cacheTime = 21000)
{
//数据量渐渐大了,每次因为很多都是同时缓存 所以在这里分流一下
if (cacheTime == 21000)
cacheTime = new Random().Next(21000, 43200);
cache.Set(cacheName, val, TimeSpan.FromSeconds(cacheTime));
}
/// <summary>
/// 删除缓存
/// </summary>
/// <param name="cacheName"></param>
public static void DelCache(string? cacheName = null)
{
if (!string.IsNullOrEmpty(cacheName))
cache.Remove(cacheName);
else
cache.Dispose();
}
}
}

View File

@ -0,0 +1,33 @@
using Microsoft.Extensions.DependencyInjection;
namespace HuanMeng.DotNetCore.CustomExtension
{
/// <summary>
/// 添加跨域
/// </summary>
public static class CorsExtension
{
/// <summary>
/// 添加跨域
/// </summary>
/// <param name="services"></param>
/// <param name="policyName"></param>
public static void AddCustomCors(this IServiceCollection services, string policyName)
{
services.AddCors(options =>
{
options.AddPolicy(policyName,
builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
}
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Cors" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.6" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.JwtInfrastructure.Interface
{
/// <summary>
/// jwt帮助类
/// </summary>
public interface IJwtAuthManager
{
/// <summary>
/// 用户刷新令牌只读词典
/// </summary>
IImmutableDictionary<string, JwtRefreshToken> UsersRefreshTokensReadOnlyDictionary { get; }
/// <summary>
/// 生成令牌
/// </summary>
/// <param name="username">用户名</param>
/// <param name="claims">用户的有关信息</param>
/// <param name="now"></param>
/// <returns></returns>
JwtAuthResult GenerateTokens(string username, Claim[] claims, DateTime now);
/// <summary>
/// 刷新令牌
/// </summary>
/// <param name="refreshToken"></param>
/// <param name="accessToken"></param>
/// <param name="now"></param>
/// <returns></returns>
JwtAuthResult Refresh(string refreshToken, string accessToken, DateTime now);
/// <summary>
/// 删除过期的刷新令牌
/// </summary>
/// <param name="now"></param>
void RemoveExpiredRefreshTokens(DateTime now);
/// <summary>
/// 按用户名删除刷新令牌
/// </summary>
/// <param name="userName"></param>
void RemoveRefreshTokenByUserName(string userName);
/// <summary>
/// 解码JwtToken
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
(ClaimsPrincipal, JwtSecurityToken?) DecodeJwtToken(string token);
}
}

View File

@ -0,0 +1,168 @@
using HuanMeng.DotNetCore.JwtInfrastructure.Interface;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.JwtInfrastructure
{
/// <summary>
/// jwt帮助类
/// </summary>
/// <param name="jwtTokenConfig"></param>
public class JwtAuthManager(JwtTokenConfig jwtTokenConfig) : IJwtAuthManager
{
/// <summary>
/// 保存刷新token
/// </summary>
public IImmutableDictionary<string, JwtRefreshToken> UsersRefreshTokensReadOnlyDictionary => _usersRefreshTokens.ToImmutableDictionary();
/// <summary>
/// 后面可以存储在数据库或分布式缓存中
/// </summary>
private readonly ConcurrentDictionary<string, JwtRefreshToken> _usersRefreshTokens = new();
/// <summary>
/// 获取加密字段
/// </summary>
private readonly byte[] _secret = Encoding.UTF8.GetBytes(jwtTokenConfig.Secret);
/// <summary>
/// 删除过期token
/// </summary>
/// <param name="now"></param>
public void RemoveExpiredRefreshTokens(DateTime now)
{
var expiredTokens = _usersRefreshTokens.Where(x => x.Value.ExpireAt < now).ToList();
foreach (var expiredToken in expiredTokens)
{
_usersRefreshTokens.TryRemove(expiredToken.Key, out _);
}
}
/// <summary>
/// 根据用户名删除token
/// </summary>
/// <param name="userName"></param>
public void RemoveRefreshTokenByUserName(string userName)
{
var refreshTokens = _usersRefreshTokens.Where(x => x.Value.UserName == userName).ToList();
foreach (var refreshToken in refreshTokens)
{
_usersRefreshTokens.TryRemove(refreshToken.Key, out _);
}
}
/// <summary>
/// 创建token
/// </summary>
/// <param name="username">用户名</param>
/// <param name="claims">用户项</param>
/// <param name="now">过期时间</param>
/// <returns></returns>
public JwtAuthResult GenerateTokens(string username, Claim[] claims, DateTime now)
{
var shouldAddAudienceClaim = string.IsNullOrWhiteSpace(claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Aud)?.Value);
//创建token
var jwtToken = new JwtSecurityToken(
jwtTokenConfig.Issuer,
shouldAddAudienceClaim ? jwtTokenConfig.Audience : string.Empty,
claims,
expires: now.AddMinutes(jwtTokenConfig.AccessTokenExpiration),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(_secret), SecurityAlgorithms.HmacSha256Signature));
var accessToken = new JwtSecurityTokenHandler().WriteToken(jwtToken);
//创建刷新token
var refreshToken = new JwtRefreshToken
{
UserName = username,
TokenString = GenerateRefreshTokenString(),
ExpireAt = now.AddMinutes(jwtTokenConfig.RefreshTokenExpiration)
};
_usersRefreshTokens.AddOrUpdate(refreshToken.TokenString, refreshToken, (_, _) => refreshToken);
return new JwtAuthResult
{
AccessToken = accessToken,
RefreshToken = refreshToken
};
}
/// <summary>
/// 刷新token
/// </summary>
/// <param name="refreshToken"></param>
/// <param name="accessToken"></param>
/// <param name="now"></param>
/// <returns></returns>
/// <exception cref="SecurityTokenException"></exception>
public JwtAuthResult Refresh(string refreshToken, string accessToken, DateTime now)
{
var (principal, jwtToken) = DecodeJwtToken(accessToken);
if (jwtToken == null || !jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256Signature))
{
throw new SecurityTokenException("无效的token");
}
var userName = principal.Identity?.Name;
if (!_usersRefreshTokens.TryGetValue(refreshToken, out var existingRefreshToken))
{
throw new SecurityTokenException("token已失效");
}
if (existingRefreshToken.UserName != userName || existingRefreshToken.ExpireAt < now)
{
throw new SecurityTokenException("token不匹配");
}
//创建新的token
return GenerateTokens(userName, principal.Claims.ToArray(), now);
}
/// <summary>
/// 解析token
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
/// <exception cref="SecurityTokenException"></exception>
public (ClaimsPrincipal, JwtSecurityToken?) DecodeJwtToken(string token)
{
if (string.IsNullOrWhiteSpace(token))
{
throw new SecurityTokenException("token不能为空");
}
var principal = new JwtSecurityTokenHandler()
.ValidateToken(token,
new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtTokenConfig.Issuer,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(_secret),
ValidAudience = jwtTokenConfig.Audience,
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
},
out var validatedToken);
return (principal, validatedToken as JwtSecurityToken);
}
/// <summary>
/// 获取刷新的token
/// </summary>
/// <returns></returns>
private static string GenerateRefreshTokenString()
{
var randomNumber = new byte[32];
using var randomNumberGenerator = RandomNumberGenerator.Create();
randomNumberGenerator.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.JwtInfrastructure
{
/// <summary>
/// 令牌
/// </summary>
public class JwtAuthResult
{
/// <summary>
/// 当前token
/// </summary>
[JsonPropertyName("accessToken")]
public string AccessToken { get; set; } = string.Empty;
/// <summary>
/// 刷新token
/// </summary>
[JsonPropertyName("refreshToken")]
public JwtRefreshToken RefreshToken { get; set; } = new();
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.JwtInfrastructure
{
/// <summary>
/// 刷新token
/// </summary>
public class JwtRefreshToken
{
/// <summary>
/// 用户名
/// </summary>
[JsonPropertyName("username")]
public string UserName { get; set; } = string.Empty;
/// <summary>
/// token
/// </summary>
[JsonPropertyName("tokenString")]
public string TokenString { get; set; } = string.Empty;
/// <summary>
/// 过期时间
/// </summary>
[JsonPropertyName("expireAt")]
public DateTime ExpireAt { get; set; }
}
}

View File

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.JwtInfrastructure
{
/// <summary>
/// JwtToken 配置文件
/// </summary>
public class JwtTokenConfig
{
/// <summary>
/// 加密值
/// </summary>
[JsonPropertyName("secret")]
public string Secret { get; set; } = string.Empty;
/// <summary>
/// 颁发者
/// </summary>
[JsonPropertyName("issuer")]
public string Issuer { get; set; } = string.Empty;
/// <summary>
/// 受众
/// </summary>
[JsonPropertyName("audience")]
public string Audience { get; set; } = string.Empty;
/// <summary>
/// 令牌过期时间
/// </summary>
[JsonPropertyName("accessTokenExpiration")]
public int AccessTokenExpiration { get; set; }
/// <summary>
/// 刷新令牌过期时间(一般会比令牌过期时间长)
/// </summary>
[JsonPropertyName("refreshTokenExpiration")]
public int RefreshTokenExpiration { get; set; }
}
}

View File

@ -0,0 +1,71 @@
using HuanMeng.DotNetCore.Base;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
namespace HuanMeng.DotNetCore.MiddlewareExtend
{
/// <summary>
/// 异常中间件
/// </summary>
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (ArgumentNullException ex)
{
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
context.Response.StatusCode = StatusCodes.Status200OK;
BaseResponse<object> baseResponse = new BaseResponse<object>(ResonseCode.ParamError, ex.ParamName ?? "参数错误", null)
{
};
context.Response.ContentType = "application/json; charset=utf-8";
// 将异常信息写入 HTTP 响应
await context.Response.WriteAsync(JsonConvert.SerializeObject(baseResponse, settings));
}
catch (Exception ex)
{
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
_logger.LogError(context.Request.Path.ToString(), ex, "异常记录");
// 设置 HTTP 响应状态码为 500
context.Response.ContentType = "application/json; charset=utf-8";
context.Response.StatusCode = StatusCodes.Status200OK;
BaseResponse<object> baseResponse = new BaseResponse<object>(ResonseCode.Error, ex.Message, null);
// 将异常信息写入 HTTP 响应
await context.Response.WriteAsync(JsonConvert.SerializeObject(baseResponse, settings));
}
finally
{
}
}
}
}

View File

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Http;
using System.Diagnostics;
namespace HuanMeng.DotNetCore.MiddlewareExtend
{
/// <summary>
/// 方法执行时间
/// </summary>
public class ExecutionTimeMiddleware(RequestDelegate _next)
{
public async Task Invoke(HttpContext context)
{
// 开始计时
var sw = Stopwatch.StartNew();
//在将响应标头发送到之前添加要调用的委托客户此处注册的回调按相反顺序运行。
context.Response.OnStarting(() =>
{
sw.Stop();
context.Response.Headers.TryAdd("X-Request-Duration", $"{sw.Elapsed.TotalMilliseconds} ms");
return Task.CompletedTask;
});
await _next(context);
}
}
}

View File

@ -0,0 +1,29 @@
using Microsoft.AspNetCore.Builder;
namespace HuanMeng.DotNetCore.MiddlewareExtend
{
/// <summary>
/// 中间库扩展
/// </summary>
public static class MiddlewareExtends
{
/// <summary>
/// 异常中间件
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseExecutionTimeMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ExecutionTimeMiddleware>();
}
/// <summary>
/// 执行时间中间件
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ExceptionMiddleware>();
}
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.MultiTenant.Contract
{
/// <summary>
/// 基本多租户接口
/// </summary>
public interface IMultiTenant
{
/// <summary>
/// 租户ID
/// </summary>
Guid TenantId { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.MultiTenant.Contract
{
/// <summary>
/// 多租户DbContent接口
/// </summary>
public interface IMultiTenantDbContext
{
/// <summary>
/// 租户信息
/// </summary>
ITenantInfo TenantInfo { get; }
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.MultiTenant.Contract
{
/// <summary>
/// 多租户实体类接口
/// </summary>
public interface IMultiTenantEntity : IMultiTenant
{
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.MultiTenant.Contract
{
/// <summary>
/// 租户信息接口
/// </summary>
public interface ITenantInfo : IMultiTenant
{
string? Identifier { get; set; }
/// <summary>
/// Gets or sets a display friendly name for the tenant.
/// </summary>
string? Name { get; set; }
/// <summary>
/// 数据库连接字符串
/// </summary>
string? ConnectionString { get; set; }
}
}

View File

@ -0,0 +1,58 @@
using HuanMeng.DotNetCore.MultiTenant.Contract;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.MultiTenant
{
/// <summary>
/// 基本多租户DbContext
/// </summary>
public class MultiTenantDbContext : DbContext, IMultiTenantDbContext
{
/// <summary>
/// 租户信息
/// </summary>
public ITenantInfo? TenantInfo { get; set; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="tenantInfo"></param>
public MultiTenantDbContext(ITenantInfo? tenantInfo)
{
this.TenantInfo = tenantInfo;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="tenantInfo"></param>
/// <param name="options"></param>
public MultiTenantDbContext(ITenantInfo? tenantInfo, DbContextOptions options)
: base(options)
{
if (tenantInfo == null)
{
tenantInfo = new TenantInfo()
{
TenantId = Guid.NewGuid(),
Identifier = "default",
Name = "default"
};
}
this.TenantInfo = tenantInfo;
}
public void SetTenantInfo(ITenantInfo tenantInfo)
{
this.TenantInfo = tenantInfo;
}
}
}

View File

@ -0,0 +1,22 @@
using HuanMeng.DotNetCore.MultiTenant.Contract;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.MultiTenant
{
/// <summary>
/// 基本多租户实体类
/// </summary>
public class MultiTenantEntity : IMultiTenantEntity
{
/// <summary>
/// 租户ID
/// </summary>
public Guid TenantId { get; set; }
}
}

View File

@ -0,0 +1,41 @@
using HuanMeng.DotNetCore.MultiTenant.Contract;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.MultiTenant
{
/// <summary>
/// 租户信息
/// </summary>
public class TenantInfo : ITenantInfo
{
public TenantInfo()
{
}
/// <summary>
/// 租户ID
/// </summary>
public Guid TenantId { get; set; }
/// <summary>
/// 租户字符串标志
/// </summary>
public string? Identifier { get; set; }
/// <summary>
/// 租户名称(描述说明)
/// </summary>
public string? Name { get; set; }
/// <summary>
/// 租户数据库连接字符串
/// </summary>
public string? ConnectionString { get; set; }
}
}

View File

@ -0,0 +1,58 @@

namespace HuanMeng.DotNetCore.Processors
{
/// <summary>
/// 任务处理器
/// </summary>
public abstract class BaseProcessor : ITaskProcessor
{
/// <summary>
/// 终止内部处理线程的最长等待时间(毫秒)
/// </summary>
protected const int WaitTimeMax_StopProc = 20000;
/// <summary>
/// 构造函数
/// </summary>
public BaseProcessor()
{
//加载配置
LoadSettings();
}
/// <summary>
/// 加载配置
/// </summary>
protected virtual void LoadSettings()
{
//初始化
// LogHelper.Info("BaseProcessor.LoadSettings");
}
/// <summary>
/// Dispose
/// </summary>
public virtual void Dispose()
{
}
/// <summary>
/// 执行任务
/// </summary>
public virtual void Run()
{
//LogHelper.Info("BaseProcessor.Run");
}
/// <summary>
/// 停止执行任务
/// </summary>
public virtual void Stop()
{
// LogHelper.Info("BaseProcessor.Stop");
}
}
}

View File

@ -0,0 +1,18 @@
namespace HuanMeng.DotNetCore.Processors
{
/// <summary>
/// 任务处理器接口
/// </summary>
public interface ITaskProcessor : IDisposable
{
/// <summary>
/// 运行任务
/// </summary>
void Run();
/// <summary>
/// 停止运行
/// </summary>
void Stop();
}
}

View File

@ -0,0 +1,81 @@
namespace HuanMeng.DotNetCore.Processors
{
/// <summary>
/// 包含处理线程的处理器基类
/// </summary>
public abstract class ThreadProcessor : BaseProcessor
{
protected Thread? _thread;
protected bool _isRunning;
protected string? _threadName;
/// <summary>
/// 构造函数
/// </summary>
public ThreadProcessor() { }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="threadName">线程名称</param>
public ThreadProcessor(string threadName)
{
_threadName = threadName;
}
/// <summary>
/// 执行处理
/// </summary>
/// <exception cref="InvalidOperationException">如果处理器已经在运行,抛出异常</exception>
public override void Run()
{
if (_isRunning)
{
throw new InvalidOperationException("重复执行线程");
}
_isRunning = true;
_thread = new Thread(Proc_Do)
{
Name = _threadName
};
_thread.Start();
}
/// <summary>
/// 线程处理函数,需要在子类中实现
/// </summary>
protected abstract void Proc_Do();
/// <summary>
/// 停止处理线程
/// </summary>
public override void Stop()
{
if (!_isRunning)
{
return;
}
_isRunning = false;
if (_thread != null && _thread.IsAlive)
{
_thread.Join(WaitTimeMax_StopProc); // 等待线程结束
_thread = null;
}
}
/// <summary>
/// 释放资源
/// </summary>
public override void Dispose()
{
Stop();
base.Dispose();
}
}
}

View File

@ -0,0 +1,264 @@
using AutoMapper;
using HuanMeng.DotNetCore.Base;
using HuanMeng.DotNetCore.JwtInfrastructure;
using HuanMeng.DotNetCore.JwtInfrastructure.Interface;
using HuanMeng.DotNetCore.MultiTenant;
using HuanMeng.MiaoYu.Code.Cache;
using HuanMeng.MiaoYu.Code.DataAccess;
using HuanMeng.MiaoYu.Code.TencentUtile;
using HuanMeng.MiaoYu.Code.Users.UserAccount;
using HuanMeng.MiaoYu.Code.Users.UserAccount.Contract;
using HuanMeng.MiaoYu.Model.Dto;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Base
{
/// <summary>
/// 默认控制器类
/// </summary>
public class MiaoYuBase : BLLBase<DAO>
{
public MiaoYuBase(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
#region
private DAO _dao;
/// <summary>
/// dao 数据库
/// </summary>
protected override DAO Dao
{
get
{
if (_dao == null)
{
_dao = new DAO(_serviceProvider);
}
return _dao;
}
}
#endregion
#region
/// <summary>
///
/// </summary>
private TenantInfo _tenantInfo;
/// <summary>
/// 租户信息
/// </summary>
public TenantInfo TenantInfo
{
get
{
if (_tenantInfo == null)
{
_tenantInfo = _serviceProvider.GetRequiredService<TenantInfo>();
}
return _tenantInfo;
}
}
#endregion
#region
private IHttpContextAccessor _HttpContextAccessor;
/// <summary>
/// HttpContextAccessor
/// </summary>
public IHttpContextAccessor HttpContextAccessor
{
get
{
if (_HttpContextAccessor == null)
{
_HttpContextAccessor = _serviceProvider.GetRequiredService<IHttpContextAccessor>();
}
return _HttpContextAccessor;
}
}
#endregion
#region
private IMapper _mapper;
/// <summary>
/// dto映射
/// </summary>
//[FromServices]
public IMapper Mapper
{
get
{
if (_mapper == null)
{
_mapper = _serviceProvider.GetRequiredService<IMapper>();
}
return _mapper;
}
set { _mapper = value; }
}
#endregion
#region
private TencentConfig _tencentConfig;
/// <summary>
/// 腾讯云配置
/// </summary>
public TencentConfig TencentConfig
{
get
{
if (_tencentConfig == null)
{
_tencentConfig = _serviceProvider.GetService<TencentConfig>() ?? new TencentConfig();
}
return _tencentConfig;
}
}
#endregion
#region
private IVerificationCodeManager _verificationCodeManager;
/// <summary>
/// 验证码管理
/// </summary>
public IVerificationCodeManager VerificationCodeManager
{
get
{
if (_verificationCodeManager == null)
{
_verificationCodeManager = _serviceProvider.GetService<IVerificationCodeManager>();
}
return _verificationCodeManager;
}
}
#endregion
#region JWT管理
private IJwtAuthManager _jwtAuthManager;
/// <summary>
/// jwt管理
/// </summary>
public IJwtAuthManager JwtAuthManager
{
get
{
if (_jwtAuthManager == null)
{
_jwtAuthManager = _serviceProvider.GetService<IJwtAuthManager>();
}
return _jwtAuthManager;
}
}
#endregion
#region
private RequestUserInfo _userInfo;
/// <summary>
/// 用户信息
/// </summary>
public RequestUserInfo UserInfo
{
get
{
if (_userInfo == null)
{
var accessToken = HttpContextAccessor.HttpContext.GetTokenAsync("Bearer", "access_token").Result;
if (string.IsNullOrEmpty(accessToken))
{
_userInfo = new RequestUserInfo()
{
UserId = 0
};
}
else
{
var (principal, jwtToken) = JwtAuthManager.DecodeJwtToken(accessToken);
if (jwtToken == null || !jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256Signature))
{
throw new SecurityTokenException("无效的token");
}
var userIdStr = principal.FindFirst("UserId")?.Value;
if (string.IsNullOrEmpty(userIdStr))
{
throw new SecurityTokenException("无效的token");
}
var nickName = principal.FindFirst("NickName")?.Value;
var userId = int.Parse(userIdStr);
_userInfo = new RequestUserInfo()
{
UserId = userId,
NickName = nickName
};
}
}
return _userInfo;
}
}
/// <summary>
/// 用户Id
/// </summary>
public int _UserId
{
get
{
return UserInfo?.UserId ?? 0;
}
}
#endregion
#region
private MiaoYuCache? _miaoYuCache; //new MiaoYuCache(Dao, Mapper);
/// <summary>
/// 妙语实现类
/// </summary>
public MiaoYuCache MiaoYuCache
{
get
{
if (_miaoYuCache == null)
{
//new MiaoYuDataEntityCache<T_Character>(Dao).DataList
_miaoYuCache = new MiaoYuCache(Dao, Mapper);
}
return _miaoYuCache;
}
}
#endregion
#region
private Logger<MiaoYuBase>? _logger;
public Logger<MiaoYuBase> _Logger
{
get
{
if (_logger == null)
{
_logger = _serviceProvider.GetRequiredService<Logger<MiaoYuBase>>();
}
return _logger;
}
}
#endregion
}
}

View File

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HuanMeng.MiaoYu.Code.DataAccess;
using HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
using Microsoft.Extensions.Caching.Memory;
namespace HuanMeng.MiaoYu.Code.Cache
{
/// <summary>
/// 人物信息缓存
/// </summary>
public class CharacterInfoBaseCache
{
private IMemoryCache _memoryCache;
private object _cacheLock = new object(); // 用于线程安全的锁
private const string CharactersCacheKey = "Characters";
private DAO _dao;
public CharacterInfoBaseCache(IMemoryCache memoryCache, DAO dao)
{
this._memoryCache = memoryCache;
this._dao = dao;
}
public List<T_Character> GetCharacterFromCache(int userId)
{
lock (_cacheLock) // 加锁以防止并发访问
{
if (!_memoryCache.TryGetValue(CharactersCacheKey , out List<T_Character> cachedCharacter))
{
// 如果缓存中没有数据,则从数据库中获取
//var db = new YourDbContext();
var character = _dao.daoDbMiaoYu.context.T_Character.ToList();
if (character != null && character.Count > 0)
{
// 将数据放入缓存
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(30)) // 设置缓存过期时间
.SetSlidingExpiration(TimeSpan.FromMinutes(5)); // 设置滑动过期时间
_memoryCache.Set(CharactersCacheKey , character, cacheEntryOptions);
cachedCharacter = character;
}
else
{
cachedCharacter = new List<T_Character>();
}
}
return cachedCharacter;
}
}
}
}

View File

@ -0,0 +1,63 @@
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using XLib.DotNetCore.CacheHelper;
namespace HuanMeng.MiaoYu.Code.Cache
{
/// <summary>
///
/// </summary>
public abstract class CommonDataEntityCache<T>(object lockObj, int cacheTime = 36000) where T : class
{
/// <summary>
///
/// </summary>
public abstract string key { get; }
/// <summary>
/// 缓存数据
/// </summary>
protected List<T>? _dataList;
/// <summary>
/// 数据
/// </summary>
public List<T> DataList
{
get
{
if (_dataList == null)
{
var tempDataList = MemoryCacheHelper.GetCache<List<T>>(key);
if (tempDataList == null)
{
lock (lockObj)
{
if (tempDataList == null)
{
tempDataList = GetDataList();
MemoryCacheHelper.SetCache(key, tempDataList, cacheTime);
}
}
}
_dataList = JsonConvert.DeserializeObject<List<T>>(JsonConvert.SerializeObject(tempDataList));
}
return _dataList ?? new List<T>();
}
}
/// <summary>
/// 获取缓存数据
/// </summary>
public abstract List<T> GetDataList();
}
}

View File

@ -0,0 +1,119 @@
using AutoMapper;
using HuanMeng.MiaoYu.Code.Cache.Special;
using HuanMeng.MiaoYu.Model.Dto.Character;
namespace HuanMeng.MiaoYu.Code.Cache
{
/// <summary>
/// 缓存类
/// </summary>
/// <param name="dao"></param>
public partial class MiaoYuCache(DAO dao, IMapper mapper)
{
#region
/// <summary>
/// 对话角色缓存表
/// </summary>
private static object CharacterLock = new object();
/// <summary>
/// 对话角色缓存表
/// </summary>
public MiaoYuDataEntityCache<T_Character>? Character { get; set; }
/// <summary>
/// 角色数据表
/// </summary>
public List<T_Character> CharactersList
{
get
{
if (Character == null)
{
Character = new MiaoYuDataEntityCache<T_Character>(dao, CharacterLock);
}
return Character.DataList ?? new List<T_Character>();
}
}
#endregion
#region
/// <summary>
/// 模型缓存表
/// </summary>
private static object ModelConfigLock = new object();
/// <summary>
/// 模型缓存表
/// </summary>
public MiaoYuDataEntityCache<T_Model_Config>? ModelConfig { get; set; }
/// <summary>
/// 模型
/// </summary>
public List<T_Model_Config> ModelConfigList
{
get
{
if (ModelConfig == null)
{
ModelConfig = new MiaoYuDataEntityCache<T_Model_Config>(dao, ModelConfigLock);
}
return ModelConfig.DataList ?? new List<T_Model_Config>();
}
}
#endregion
#region
/// <summary>
/// 角色缓存表
/// </summary>
public CharacterEntityCache? CharacterCache { get; set; }
/// <summary>
/// 角色
/// </summary>
public List<CharacterCache> CharacterList
{
get
{
if (CharacterCache == null)
{
CharacterCache = new CharacterEntityCache(dao, mapper);
}
return CharacterCache.DataList ?? new List<CharacterCache>();
}
}
#endregion
#region
/// <summary>
/// 图片缓存表
/// </summary>
private static object ImageConfigLock = new object();
/// <summary>
/// 图片缓存表
/// </summary>
public MiaoYuDataEntityCache<T_Image_Config>? ImageConfigCache { get; set; }
/// <summary>
/// 图片
/// </summary>
public List<T_Image_Config> ImageConfigList
{
get
{
if (ImageConfigCache == null)
{
ImageConfigCache = new MiaoYuDataEntityCache<T_Image_Config>(dao, ImageConfigLock);
}
return ImageConfigCache.DataList ?? new List<T_Image_Config>();
}
}
#endregion
}
}

View File

@ -0,0 +1,35 @@
namespace HuanMeng.MiaoYu.Code.Cache
{
/// <summary>
/// 妙语数据库实体类缓存
/// </summary>
public class MiaoYuDataEntityCache<T>(DAO _dao, object lockObj, int cacheTime = 36000)
: CommonDataEntityCache<T>(lockObj, cacheTime) where T : class
{
/// <summary>
/// 缓存的key
/// </summary>
public override string key
{
get
{
return $"{_dao.daoDbMiaoYu.context.TenantInfo?.TenantId}:MiaoYu:{typeof(T).Name}";
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public override List<T> GetDataList()
{
var dbSet = _dao.daoDbMiaoYu.context.Set<T>();
if (dbSet == null)
{
return new List<T>();
}
return dbSet.ToList();
}
}
}

View File

@ -0,0 +1,93 @@
using AutoMapper;
using HuanMeng.MiaoYu.Code.DataAccess;
using HuanMeng.MiaoYu.Code.Other;
using HuanMeng.MiaoYu.Model.Dto.Character;
using HuanMeng.MiaoYu.Model.Dto.Label;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Cache.Special
{
/// <summary>
///
/// </summary>
/// <param name="_dao"></param>
public class CharacterEntityCache(DAO _dao, IMapper mapper) : MiaoYuDataEntityCache<CharacterCache>(_dao, CharacterCacheLock)
{
/// <summary>
/// 锁
/// </summary>
private static object CharacterCacheLock = new object();
private static object ImageLock = new object();
private static object T_Character_LabelLock = new object();
private static object T_Character_Label_RelationLock = new object();
/// <summary>
/// 获取缓存数据
/// </summary>
/// <returns></returns>
public override List<CharacterCache> GetDataList()
{
List<CharacterCache> characterCaches = new List<CharacterCache>();
//读取角色表
var characters = _dao.daoDbMiaoYu.context.T_Character.ToList();
if (characters == null)
{
return characterCaches;
}
characterCaches = mapper.Map<List<CharacterCache>>(characters);
string sqlString = $"select TOP 100 CharacterId,count(DISTINCT UserId ) UserCount from T_User_Char where isdelete=0 and TenantId='{_dao.daoDbMiaoYu.context.TenantInfo?.TenantId}' GROUP BY CharacterId";
//获取查看次数
var characteChatCounts = _dao.daoDbMiaoYu.SqlQueryList<CharacteChatCountModel>(sqlString);
//查询配置表
var modelConfigs = _dao.daoDbMiaoYu.context.T_Model_Config.ToList();
var ImageConfigCache = new MiaoYuDataEntityCache<T_Image_Config>(_dao, ImageLock);
var images = ImageConfigCache.DataList;
//标签
var labelCache = new MiaoYuDataEntityCache<T_Character_Label>(_dao, T_Character_LabelLock);
var labels = labelCache.DataList;
var labelRelationCache = new MiaoYuDataEntityCache<T_Character_Label_Relation>(_dao, T_Character_Label_RelationLock);
var labelRelations = labelRelationCache.DataList;
foreach (var characterCache in characterCaches)
{
var modelConfig = modelConfigs.FirstOrDefault(it => it.Id == characterCache.ModelConfigId);
if (modelConfig != null)
{
characterCache.ModelConfig = modelConfig;
//查询图片
characterCache.IconImage = images.GetImageUrl(characterCache.IconImg ?? 0);
characterCache.BgImage = images.GetImageUrl(characterCache.BgImg ?? 0);
var c = characteChatCounts.FirstOrDefault(it => it.CharacterId == characterCache.Id);
if (c != null)
{
characterCache.LookCount = c.UserCount;
}
var characterLabelIds = labelRelations.Where(it => it.CharacterId == characterCache.Id).Select(it => it.CharacterLabelId).ToList();
if (characterLabelIds.Count > 0)
{
var lab = labels.Where(it => characterLabelIds.Contains(it.Id)).ToList();
var labs = mapper.Map<List<LabelDto>>(lab);
characterCache.Label = labs;
}
else
{
characterCache.Label = new List<LabelDto>();
}
}
}
//移除没有配置模型的类
characterCaches.Where(it => it.ModelConfig == null).ToList().ForEach(characterCache =>
{
characterCaches.Remove(characterCache);
});
return characterCaches;
}
}
}

View File

@ -0,0 +1,99 @@
using AutoMapper;
using HuanMeng.DotNetCore.Base;
using HuanMeng.MiaoYu.Model.Dto.Character;
using HuanMeng.MiaoYu.Model.Dto.Chat;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Character
{
/// <summary>
/// 角色信息类
/// </summary>
public class CharacterBLL : MiaoYuBase
{
public CharacterBLL(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
/// <summary>
/// 首页获取角色列表
/// </summary>
/// <returns></returns>
public async Task<BaseResponse<List<CharacterInfoDto>>> GetHomeCharacter(RequestCharacterInfoPage requestCharacterInfo)
{
var index = requestCharacterInfo.Index ?? 0;
var size = requestCharacterInfo.Size ?? 10;
var charactersList = MiaoYuCache.CharacterList.OrderByDescending(it => it.LookCount)
.Skip((index - 1) * size).Take(size).ToList();
if (charactersList.Count == 0)
{
return new BaseResponse<List<CharacterInfoDto>>();
}
var list = Mapper.Map<List<CharacterInfoDto>>(charactersList);
//不是游客
if (_UserId != 0)
{
List<int> characterIds = list.Select(it => it.CharacterId).ToList();
//获取亲密值
var intimacys = await Dao.daoDbMiaoYu.context.T_Character_User_Intimacy.Where(it => it.UserId == _UserId
&& characterIds.Contains(it.CharacterId)).ToListAsync();
list.ForEach(it =>
{
it.Intimacy = intimacys.FirstOrDefault(item => item.CharacterId == it.CharacterId)?.IntimacyValue ?? 0;
});
}
return new BaseResponse<List<CharacterInfoDto>>(ResonseCode.Success, "", list);
}
/// <summary>
/// 获取角色详情
/// </summary>
/// <param name="requestCharacterInfo"></param>
/// <returns></returns>
public async Task<BaseResponse<CharacterInfoDto>> GetCharacterInfo(RequestCharacterInfo requestCharacterInfo)
{
var charactersinfo = MiaoYuCache.CharacterList.FirstOrDefault(it => it.Id == requestCharacterInfo.CharacterId);
if (charactersinfo == null)
{
return new BaseResponse<CharacterInfoDto>();
}
var info = Mapper.Map<CharacterInfoDto>(charactersinfo);
//不是游客
if (_UserId != 0)
{
//获取亲密值
var intimacys = await Dao.daoDbMiaoYu.context.T_Character_User_Intimacy.Where(it => it.UserId == _UserId
&& it.CharacterId == info.CharacterId).FirstOrDefaultAsync();
info.Intimacy = intimacys?.IntimacyValue ?? 0;
}
return new BaseResponse<CharacterInfoDto>(ResonseCode.Success, "", info);
}
/// <summary>
/// 获取全部角色的Id
/// </summary>
/// <returns></returns>
public BaseResponse<List<int>> GetCharacters()
{
Random rng = new Random();
var charactersIds = MiaoYuCache.CharacterList.Select(it => it.Id).OrderBy(x => rng.Next()).ToList();
if (charactersIds == null)
{
charactersIds = new List<int>();
}
return new BaseResponse<List<int>>(ResonseCode.Success, "", charactersIds);
}
}
}

View File

@ -0,0 +1,67 @@
using HuanMeng.DotNetCore.Base;
using HuanMeng.MiaoYu.Code.Cache;
using HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Chat
{
/// <summary>
/// 聊天类
/// </summary>
public class ChatBLL : MiaoYuBase
{
public ChatBLL(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
/// <summary>
/// 删除聊天记录
/// </summary>
/// <param name="id">聊天id</param>
/// <param name="characterId"></param>
/// <returns></returns>
public async Task<bool> DelChatByIds(List<int> id, int characterId)
{
//var charactersList = MiaoYuCache.CharactersList;
//List<T_Character> list = _characterCache.GetCharacterFromCache(characterId);
//var chatsToDelete = await Dao.daoDbMiaoYu.context.T_Chat.Where(t => id.Contains(t.Id)).ToListAsync();
//if (chatsToDelete.Count <= 0)
//{
// throw new Exception("");
//}
//var chatList = chatsToDelete.Where(t => t.UserId == _UserId && t.CharacterId == characterId && t.Type == 0).ToList();
//if (chatList.Count > 0)
//{
// chatList.ForEach(t => t.Type = 2);
//}
//Dao.daoDbMiaoYu.context.SaveChanges();
return true;
}
/// <summary>
/// 清空聊天记录
/// </summary>
/// <param name="characterId"></param>
/// <returns></returns>
public async Task<bool> DelChat(int characterId)
{
//var tempList = await Dao.daoDbMiaoYu.context.T_Chat.Where(t => t.UserId == _UserId && t.CharacterId == characterId && t.IsDel == false).ToListAsync();
//if (tempList.Count > 0)
//{
// tempList.ForEach(t => t.IsDel = true);
//}
//Dao.daoDbMiaoYu.context.SaveChanges();
return true;
}
}
}

View File

@ -0,0 +1,37 @@
using HuanMeng.MiaoYu.Code.Chat.Contract;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Chat.Claude
{
/// <summary>
/// Claude3 通用参数
/// </summary>
public class BaseClaudeChatChatParams : BaseChatParams
{
/// <summary>
/// 模型
/// </summary>
public string Model { get; set; }
/// <summary>
/// 最大token数
/// </summary>
[JsonProperty("max_tokens")]
public int MaxTokens { get; set; }
/// <summary>
/// 上下文
/// </summary>
public string System { get; set; }
/// <summary>
/// 消息内容
/// </summary>
public ClaudeChatMessage[] Messages { get; set; }
}
}

View File

@ -0,0 +1,130 @@
using HuanMeng.MiaoYu.Code.Chat.Contract;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Serialization;
using HuanMeng.MiaoYu.Code.Chat.Claude.Model;
namespace HuanMeng.MiaoYu.Code.Chat.Claude
{
/// <summary>
/// Claude3 https://docs.anthropic.com/en/api/messages ClaudeChatInfo
/// </summary>
public class ClaudeChat(ClaudeChatConfig claudeChatConfig, IHttpClientFactory factory) : IChat
{
/// <summary>
///
/// </summary>
/// <param name="chatParams"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="NotImplementedException"></exception>
public async Task<BaseChatInfo> MessagesAsync(BaseChatParams chatParams)
{
var claudeChatChatParams = chatParams as ClaudeChatChatParams;
if (claudeChatChatParams == null)
{
throw new ArgumentException("参数异常");
}
//添加默认值
if (claudeChatChatParams.MaxTokens == 0)
{
claudeChatChatParams.MaxTokens = claudeChatConfig.MaxTokens;
}
//添加默认值
if (!string.IsNullOrEmpty(claudeChatChatParams.Model))
{
claudeChatChatParams.Model = claudeChatChatParams.Model;
}
//去线程池里拿http线程
using (var httpClient = factory.CreateClient())
{
// 设置请求头
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.DefaultRequestHeaders.Add("x-api-key", claudeChatConfig.ApiKey);
httpClient.DefaultRequestHeaders.Add("anthropic-version", claudeChatConfig.AnthropicVersion);
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
string json = JsonConvert.SerializeObject(chatParams, settings);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(claudeChatConfig.RequestUrl, content);
if (response.IsSuccessStatusCode)
{
var chatInfo = await response.Content.ReadFromJsonAsync<ClaudeChatResponse?>();
return chatInfo;
}
}
return null;
}
public async IAsyncEnumerable<string> MessagesStreamAsync(BaseChatParams chatParams)
{
var claudeChatChatParams = chatParams as ClaudeChatChatStreamParams;
if (claudeChatChatParams == null)
{
throw new ArgumentException("参数异常");
}
//添加默认值
if (claudeChatChatParams.MaxTokens == 0)
{
claudeChatChatParams.MaxTokens = claudeChatConfig.MaxTokens;
}
claudeChatChatParams.Stream = true;
//添加默认值
if (!string.IsNullOrEmpty(claudeChatChatParams.Model))
{
claudeChatChatParams.Model = claudeChatChatParams.Model;
}
//去线程池里拿http线程
using (var httpClient = factory.CreateClient())
{
// 设置请求头
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.DefaultRequestHeaders.Add("x-api-key", claudeChatConfig.ApiKey);
httpClient.DefaultRequestHeaders.Add("anthropic-version", claudeChatConfig.AnthropicVersion);
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
string json = JsonConvert.SerializeObject(chatParams, settings);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(claudeChatConfig.RequestUrl, content);
if (response.IsSuccessStatusCode)
{
var chatInfo = await response.Content.ReadFromJsonAsync<ClaudeChatStreamResponse?>();
if (chatInfo?.Type == "message_delta")
{
claudeChatChatParams.OutCharResponse.Usage.OutputTokens = chatInfo.Usage?.OutputTokens ?? 0;
}
else if (chatInfo?.Type == "content_block_start")
{
claudeChatChatParams.OutCharResponse = chatInfo?.Message ?? new ClaudeChatResponse();
}
else if (chatInfo?.Type == "content_block_delta")
{
var text = chatInfo?.Delta?.Text ?? "";
if (!string.IsNullOrEmpty(text))
{
yield return text;
}
}
claudeChatChatParams.OutObj.Add(chatInfo);
}
}
}
}
}

View File

@ -0,0 +1,18 @@
using HuanMeng.MiaoYu.Code.Chat.Contract;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Chat.Claude
{
/// <summary>
/// Claude3 请求的参数
/// </summary>
public class ClaudeChatChatParams : BaseClaudeChatChatParams
{
}
}

View File

@ -0,0 +1,31 @@
using HuanMeng.MiaoYu.Code.Chat.Claude.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Chat.Claude
{
/// <summary>
/// 实时流
/// </summary>
public class ClaudeChatChatStreamParams : BaseClaudeChatChatParams
{
/// <summary>
/// 视频流
/// </summary>
public bool Stream { get; set; }
/// <summary>
/// 返回消息内容
/// </summary>
public ClaudeChatResponse OutCharResponse { get; set; }
/// <summary>
///
/// </summary>
public List<object> OutObj { get; set; }
}
}

View File

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Chat.Claude
{
/// <summary>
/// 配置类
/// </summary>
public class ClaudeChatConfig
{
/// <summary>
/// 模型
/// </summary>
public string Model { get; set; }
/// <summary>
/// 最大token数
/// </summary>
public int MaxTokens { get; set; }
/// <summary>
/// api key
/// </summary>
public string ApiKey { get; set; }
/// <summary>
/// 模型版本
/// </summary>
public string AnthropicVersion { get; set; }
/// <summary>
/// 请求地址 = "api.gptsapi.net";
/// </summary>
public string RequestUrl { get; set; }
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Chat.Claude
{
/// <summary>
/// Claude发送消息内容
/// </summary>
public class ClaudeChatMessage
{
/// <summary>
/// 角色
/// </summary>
public string Role { get; set; }
/// <summary>
/// 内容
/// </summary>
public string Content { get; set; }
}
}

View File

@ -0,0 +1,28 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Chat.Claude.Model
{
/// <summary>
/// 消息内容
/// </summary>
public class ClaudeChatContent
{
/// <summary>
/// 内容类型
/// </summary>
[JsonProperty("type")]
public string Type { get; set; }
/// <summary>
/// 文本内容
/// </summary>
[JsonProperty("text")]
public string Text { get; set; }
}
}

View File

@ -0,0 +1,60 @@
using HuanMeng.MiaoYu.Code.Chat.Contract;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Chat.Claude.Model
{
/// <summary>
/// 返回内容
/// </summary>
public class ClaudeChatResponse : BaseChatInfo
{
/// <summary>
/// 消息 ID
/// </summary>
[JsonProperty("id")]
public string Id { get; set; }
/// <summary>
/// 模型
/// </summary>
[JsonProperty("model")]
public string Model { get; set; }
/// <summary>
/// 消息类型
/// </summary>
[JsonProperty("type")]
public string Type { get; set; }
/// <summary>
/// 角色
/// </summary>
[JsonProperty("role")]
public string Role { get; set; }
/// <summary>
/// 消息内容
/// </summary>
[JsonProperty("content")]
public ClaudeChatContent[] Content { get; set; }
/// <summary>
/// 停止原因
/// </summary>
[JsonProperty("stop_reason")]
public string StopReason { get; set; }
/// <summary>
/// 使用情况
/// </summary>
[JsonProperty("usage")]
public ClaudeChatUsage Usage { get; set; }
}
}

View File

@ -0,0 +1,41 @@
using HuanMeng.MiaoYu.Code.Chat.Contract;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Chat.Claude.Model
{
/// <summary>
///
/// </summary>
public class ClaudeChatStreamResponse : BaseChatStream
{
public int? Index { get; set; }
/// <summary>
/// 类型
/// </summary>
public string Type { get; set; }
/// <summary>
/// 内容
/// </summary>
public ClaudeChatContent? Delta { get; set; }
/// <summary>
/// 结尾
/// </summary>
public ClaudeChatResponse? Message { get; set; }
/// <summary>
/// 使用情况
/// </summary>
[JsonProperty("usage")]
public ClaudeChatUsage Usage { get; set; }
}
//delta
}

View File

@ -0,0 +1,28 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Chat.Claude.Model
{
/// <summary>
/// 使用情况
/// </summary>
public class ClaudeChatUsage
{
/// <summary>
/// 输入的 token 数量
/// </summary>
[JsonProperty("input_tokens")]
public int InputTokens { get; set; }
/// <summary>
/// 输出的 token 数量
/// </summary>
[JsonProperty("output_tokens")]
public int OutputTokens { get; set; }
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Chat.Contract
{
/// <summary>
/// 聊天返回的实体类
/// </summary>
public class BaseChatInfo
{
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Chat.Contract
{
/// <summary>
/// 聊天请求参数
/// </summary>
public class BaseChatParams
{
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Chat.Contract
{
/// <summary>
/// 聊天实时流
/// </summary>
public class BaseChatStream
{
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Chat.Contract
{
/// <summary>
/// 聊天接口
/// </summary>
public interface IChat
{
/// <summary>
/// 发送消息
/// </summary>
/// <param name="chatParams"></param>
/// <returns></returns>
Task<BaseChatInfo> MessagesAsync(BaseChatParams chatParams);
/// <summary>
/// 发送消息
/// </summary>
/// <param name="chatParams"></param>
/// <returns></returns>
IAsyncEnumerable<string> MessagesStreamAsync(BaseChatParams chatParams);
}
}

View File

@ -0,0 +1,70 @@
using HuanMeng.DotNetCore.Base;
using HuanMeng.DotNetCore.MultiTenant;
using HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.DataAccess
{
/// <summary>
/// 数据库访问
/// </summary>
public class DAO : DaoBase
{
//private IMultiTenantProvider _multiTenantProvider;
private TenantInfo _tenantInfo;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="serviceProvider"></param>
public DAO(IServiceProvider serviceProvider) : base(serviceProvider)
{
this._tenantInfo = serviceProvider.GetRequiredService<TenantInfo>();
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="serviceProvider"></param>
public DAO(IServiceProvider serviceProvider, TenantInfo tenantInfo) : base(serviceProvider)
{
this._tenantInfo = tenantInfo;
}
private EfCoreDaoBase<MiaoYuContext>? _daoDbMiaoYu;
/// <summary>
/// 数据库[Student]
/// 更新删除尽量只操作本bll实例dao获取到的对象取和存要同一个dao
/// </summary>
public EfCoreDaoBase<MiaoYuContext> daoDbMiaoYu
{
get
{
if (_daoDbMiaoYu == null)
{
var dbContext = _serviceProvider.GetRequiredService<MiaoYuContext>();
if (_tenantInfo == null)
{
this._tenantInfo = _serviceProvider.GetRequiredService<TenantInfo>();
}
dbContext.SetTenantInfo(_tenantInfo);
_daoDbMiaoYu = new EfCoreDaoBase<MiaoYuContext>(dbContext);
}
return _daoDbMiaoYu;
}
}
/// <summary>
/// 租户
/// </summary>
//public IMultiTenantProvider multiTenantProvider { get => _multiTenantProvider; }
}
}

View File

@ -0,0 +1,3 @@
global using HuanMeng.MiaoYu.Code.Base;
global using HuanMeng.MiaoYu.Code.DataAccess;
global using HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;

View File

@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.6" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.2" />
<PackageReference Include="TencentCloudSDK.Common" Version="3.0.1042" />
<PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1042" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HuanMeng.DotNetCore\HuanMeng.DotNetCore.csproj" />
<ProjectReference Include="..\HuanMeng.MiaoYu.Model\HuanMeng.MiaoYu.Model.csproj" />
<ProjectReference Include="..\HuanMeng.Utility\HuanMeng.Utility.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,167 @@
using HuanMeng.DotNetCore.JwtInfrastructure.Interface;
using HuanMeng.DotNetCore.JwtInfrastructure;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.JwtUtil
{
/// <summary>
/// jwt
/// </summary>
/// <param name="jwtTokenConfig"></param>
public class JwtManager(JwtTokenConfig jwtTokenConfig) : IJwtAuthManager
{
/// <summary>
/// 后面可以存储在数据库或分布式缓存中
/// </summary>
private readonly ConcurrentDictionary<string, JwtRefreshToken> _usersRefreshTokens = new();
/// <summary>
/// 获取加密字段
/// </summary>
private readonly byte[] _secret = Encoding.UTF8.GetBytes(jwtTokenConfig.Secret);
/// <summary>
/// 删除过期token
/// </summary>
/// <param name="now"></param>
public void RemoveExpiredRefreshTokens(DateTime now)
{
var expiredTokens = _usersRefreshTokens.Where(x => x.Value.ExpireAt < now).ToList();
foreach (var expiredToken in expiredTokens)
{
_usersRefreshTokens.TryRemove(expiredToken.Key, out _);
}
}
/// <summary>
/// 根据用户名删除token
/// </summary>
/// <param name="userName"></param>
public void RemoveRefreshTokenByUserName(string userName)
{
var refreshTokens = _usersRefreshTokens.Where(x => x.Value.UserName == userName).ToList();
foreach (var refreshToken in refreshTokens)
{
_usersRefreshTokens.TryRemove(refreshToken.Key, out _);
}
}
/// <summary>
/// 创建token
/// </summary>
/// <param name="username">用户名</param>
/// <param name="claims">用户项</param>
/// <param name="now">过期时间</param>
/// <returns></returns>
public JwtAuthResult GenerateTokens(string username, Claim[] claims, DateTime now)
{
var shouldAddAudienceClaim = string.IsNullOrWhiteSpace(claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Aud)?.Value);
//创建token
var jwtToken = new JwtSecurityToken(
jwtTokenConfig.Issuer,
shouldAddAudienceClaim ? jwtTokenConfig.Audience : string.Empty,
claims,
expires: now.AddMinutes(jwtTokenConfig.AccessTokenExpiration),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(_secret), SecurityAlgorithms.HmacSha256Signature));
var accessToken = new JwtSecurityTokenHandler().WriteToken(jwtToken);
//创建刷新token
var refreshToken = new JwtRefreshToken
{
UserName = username,
TokenString = GenerateRefreshTokenString(),
ExpireAt = now.AddMinutes(jwtTokenConfig.RefreshTokenExpiration)
};
//_usersRefreshTokens.AddOrUpdate(refreshToken.TokenString, refreshToken, (_, _) => refreshToken);
return new JwtAuthResult
{
AccessToken = accessToken,
RefreshToken = refreshToken
};
}
/// <summary>
/// 刷新token
/// </summary>
/// <param name="refreshToken"></param>
/// <param name="accessToken"></param>
/// <param name="now"></param>
/// <returns></returns>
/// <exception cref="SecurityTokenException"></exception>
public JwtAuthResult Refresh(string refreshToken, string accessToken, DateTime now)
{
var (principal, jwtToken) = DecodeJwtToken(accessToken);
if (jwtToken == null || !jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256Signature))
{
throw new SecurityTokenException("无效的token");
}
var userName = principal.Identity?.Name;
if (!_usersRefreshTokens.TryGetValue(refreshToken, out var existingRefreshToken))
{
throw new SecurityTokenException("token已失效");
}
if (existingRefreshToken.UserName != userName || existingRefreshToken.ExpireAt < now)
{
throw new SecurityTokenException("token不匹配");
}
//创建新的token
return GenerateTokens(userName, principal.Claims.ToArray(), now);
}
/// <summary>
/// 解析token
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
/// <exception cref="SecurityTokenException"></exception>
public (ClaimsPrincipal, JwtSecurityToken?) DecodeJwtToken(string token)
{
if (string.IsNullOrWhiteSpace(token))
{
throw new SecurityTokenException("token不能为空");
}
var principal = new JwtSecurityTokenHandler()
.ValidateToken(token,
new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtTokenConfig.Issuer,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(_secret),
ValidAudience = jwtTokenConfig.Audience,
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
},
out var validatedToken);
return (principal, validatedToken as JwtSecurityToken);
}
/// <summary>
/// 获取刷新的token
/// </summary>
/// <returns></returns>
private static string GenerateRefreshTokenString()
{
var randomNumber = new byte[32];
using var randomNumberGenerator = RandomNumberGenerator.Create();
randomNumberGenerator.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
public IImmutableDictionary<string, JwtRefreshToken> UsersRefreshTokensReadOnlyDictionary => throw new NotImplementedException();
}
}

View File

@ -0,0 +1,69 @@
using HuanMeng.DotNetCore.JwtInfrastructure.Interface;
using HuanMeng.DotNetCore.JwtInfrastructure;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
namespace HuanMeng.MiaoYu.Code.JwtUtil
{
/// <summary>
///
/// </summary>
public static class JwtTokenManageExtension
{
/// <summary>
/// 添加jwt安全验证配置
/// </summary>
/// <param name="server"></param>
/// <param name="configuration"></param>
/// <returns></returns>
public static void AddJwtConfig(this IHostApplicationBuilder builder)
{
var jwtTokenConfig = builder.Configuration.GetSection("JwtTokenConfig").Get<JwtTokenConfig>()!;
//注册一个jwtTokenConfig的单例服务
builder.Services.AddSingleton(jwtTokenConfig);
//
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.RequireHttpsMetadata = true;
options.SaveToken = true;
//调试使用
//options.Events = new JwtDebugBearerEvents().GetJwtBearerEvents();
options.TokenValidationParameters = new TokenValidationParameters
{
//是否验证颁发者
ValidateIssuer = true,
//是否验证受众
ValidateAudience = true,
//指定是否验证令牌的生存期。设置为 true 表示要进行验证。
ValidateLifetime = true,
//指定是否验证颁发者签名密钥。设置为 true 表示要进行验证。
ValidateIssuerSigningKey = true,
//颁发者
ValidIssuer = jwtTokenConfig.Issuer,
//受众
ValidAudience = jwtTokenConfig.Audience,
//指定用于验证颁发者签名的密钥
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtTokenConfig.Secret)),
//指定允许令牌的时钟偏移。允许令牌的过期时间与实际时间之间存在的时间差。在这里设置为 5 分钟,表示允许令牌的时钟偏移为 5 分钟。
ClockSkew = TimeSpan.FromMinutes(5)
};
});
//注册一个JwtAuthManager的单例服务
builder.Services.AddSingleton<IJwtAuthManager, JwtManager>();
}
}
}

View File

@ -0,0 +1,49 @@
using HuanMeng.DotNetCore.MultiTenant;
using HuanMeng.DotNetCore.MultiTenant.Contract;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.MultiTenantUtil
{
/// <summary>
/// 租户配置项
/// </summary>
public class MiaoYuMultiTenantConfig
{
public MiaoYuMultiTenantConfig(string conn)
{
var tenantInfo = new TenantInfo()
{
};
tenantInfo.ConnectionString = conn ?? "Server=192.168.195.2;Database=MiaoYu;User Id=zpc;Password=zpc;TrustServerCertificate=true;";
tenantInfo.Identifier = "default";
tenantInfo.TenantId = Guid.Empty;
tenantInfo.Name = "default";
TenantInfos.Add(tenantInfo);
}
/// <summary>
///
/// </summary>
public List<TenantInfo> TenantInfos { get; set; } = new List<TenantInfo>();
/// <summary>
/// 获取默认
/// </summary>
/// <returns></returns>
public TenantInfo GetMultiTenantCfgDefault()
{
var config = TenantInfos.FirstOrDefault();
return config;
}
}
}

View File

@ -0,0 +1,76 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
using HuanMeng.DotNetCore.MultiTenant;
using HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
using Microsoft.EntityFrameworkCore;
namespace HuanMeng.MiaoYu.Code.MultiTenantUtil
{
/// <summary>
/// 多租户扩展
/// </summary>
public static class MiaoYuMultiTenantExtension
{
/// <summary>
/// 多租户IServiceCollection扩展
/// </summary>
/// <param name="serviceCollection"></param>
public static void AddMultiTenantMiaoYu(this IHostApplicationBuilder builder)
{
//初始学生数据库
string MiaoYu_SqlServer_Db = builder.Configuration.GetConnectionString("MiaoYu_SqlServer_Db") ?? "";
//添加配置项
//string SunnySports_SqlServer_Db_SunnySport_Admin = builder.Configuration.GetConnectionString("MiaoYu_SqlServer_Db_Admin") ?? "";
MiaoYuMultiTenantConfig miaoYuMultiTenantConfig = new MiaoYuMultiTenantConfig(MiaoYu_SqlServer_Db); builder.Services.AddSingleton<MiaoYuMultiTenantConfig>(miaoYuMultiTenantConfig);
//添加注入全部的多租户配置项
//builder.Services.AddSingleton<MiaoYuMultiTenantConfig>(sunnySportsMultiTenantConfig);
////添加单个租户的配置项
builder.Services.AddScoped<TenantInfo>();
////添加教师端用户
//builder.Services.AddScoped<LoginUserModel>();
//builder.Services.AddScoped<TenantInfo>();
//添加DB
//var iDbLog = LoggerFactory.Create(b => b.AddConsole().AddFilter("", LogLevel.Information));
//添加系统数据库
builder.Services.AddDbContext<MiaoYuContext>((serviceProvider, options) =>
{
var m = serviceProvider.GetRequiredService<TenantInfo>();
string sunnySportConnectionString = "";
if (m != null)
{
sunnySportConnectionString = m.ConnectionString ?? MiaoYu_SqlServer_Db;
}
if (string.IsNullOrEmpty(sunnySportConnectionString))
{
sunnySportConnectionString = MiaoYu_SqlServer_Db;
}
options
.UseSqlServer(sunnySportConnectionString);
//options.UseSqlServer
}, ServiceLifetime.Scoped);
}
/// <summary>
/// 多租户IApplicationBuilder扩展
/// </summary>
/// <param name="app"></param>
/// <returns></returns>
public static IApplicationBuilder UseMultiTenantMiaoYu(this IApplicationBuilder app)
{
return app.UseMiddleware<MiaoYuMultiTenantTenantMiddleware>();
}
}
}

View File

@ -0,0 +1,53 @@
using HuanMeng.DotNetCore.MultiTenant;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.MultiTenantUtil
{
/// <summary>
/// 多租户中间件
/// </summary>
public class MiaoYuMultiTenantTenantMiddleware
{
private readonly RequestDelegate _next;
public MiaoYuMultiTenantTenantMiddleware(RequestDelegate next)
{
_next = next;
}
/// <summary>
/// 根据HttpContext获取并设置当前租户ID
/// </summary>
/// <param name="context"></param>
/// <param name="_serviceProvider"></param>
/// <param name="tenantInfo"></param>
/// <returns></returns>
public virtual async Task Invoke(HttpContext context,
IServiceProvider _serviceProvider,
TenantInfo tenantInfo,
MiaoYuMultiTenantConfig miaoYuMultiTenantConfig
)
{
if (tenantInfo == null)
{
tenantInfo = new TenantInfo();
}
var _ten = miaoYuMultiTenantConfig.GetMultiTenantCfgDefault();
tenantInfo.ConnectionString = _ten.ConnectionString;
tenantInfo.Identifier = _ten.Identifier;
tenantInfo.TenantId = _ten.TenantId;
tenantInfo.Name = _ten.Name;
await _next.Invoke(context);
}
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Other
{
/// <summary>
/// 图片扩展类
/// </summary>
public static class ImageExtend
{
/// <summary>
/// 获取图片地址
/// </summary>
/// <param name="configs"></param>
/// <param name="imageId"></param>
/// <returns></returns>
public static string GetImageUrl(this List<T_Image_Config> configs, int imageId)
{
if (imageId > 0 && configs != null && configs.Count > 0)
{
return configs.FirstOrDefault(it => it.ImageId == imageId)?.Url ?? "";
}
return "";
}
}
}

View File

@ -0,0 +1,23 @@
using HuanMeng.DotNetCore.JwtInfrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HuanMeng.DotNetCore.JwtInfrastructure.Interface;
namespace HuanMeng.MiaoYu.Code.Other
{
/// <summary>
/// jwt接口验证
/// </summary>
public static class JwtTokenManageExtension
{
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.TencentUtile
{
/// <summary>
/// 腾讯云配置
/// </summary>
public class TencentBaseConfig
{
/// <summary>
/// 腾讯云id
/// </summary>
public string SecretId { get; set; }
/// <summary>
/// 密钥
/// </summary>
public string SecretKey { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.TencentUtile
{
/// <summary>
///
/// </summary>
public class TencentConfig : TencentBaseConfig
{
/// <summary>
/// 短信验证码接口
/// </summary>
public TencentSMSConfig SMSCode { get; set; }
}
}

View File

@ -0,0 +1,48 @@
using HuanMeng.MiaoYu.Code.Users.UserAccount.Contract;
using HuanMeng.MiaoYu.Code.Users.UserAccount.VerificationCodeManager;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace HuanMeng.MiaoYu.Code.TencentUtile
{
/// <summary>
/// 腾讯云扩展
/// </summary>
public static class TencentExtension
{
// <summary>
/// 腾讯云
/// </summary>
/// <param name="serviceCollection"></param>
public static void AddTencent(this IHostApplicationBuilder builder)
{
var tencentConfig = builder.Configuration.GetSection("TencentCloud").Get<TencentConfig>();
if (tencentConfig == null)
{
tencentConfig = new TencentConfig();
}
if (tencentConfig.SMSCode == null)
{
tencentConfig.SMSCode = new TencentSMSConfig() { };
}
if (string.IsNullOrEmpty(tencentConfig.SMSCode.SecretId))
{
tencentConfig.SMSCode.SecretId = tencentConfig.SecretId;
}
if (string.IsNullOrEmpty(tencentConfig.SMSCode.SecretKey))
{
tencentConfig.SMSCode.SecretKey = tencentConfig.SecretKey;
}
//注册一个验证码的服务
builder.Services.AddSingleton<TencentConfig>(tencentConfig);
}
}
}

View File

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.TencentUtile
{
/// <summary>
/// 模板短信
/// </summary>
public class TencentSMSConfig : TencentBaseConfig
{
/// <summary>
/// 请求方式
/// </summary>
public string ReqMethod { get; set; }
/// <summary>
/// 超时时间,秒
/// </summary>
public int Timeout { get; set; }
/// <summary>
/// 短信应用ID:
/// </summary>
public string SmsSdkAppId { get; set; }
/// <summary>
/// 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名
/// </summary>
public string SignName { get; set; }
/// <summary>
/// 短信模板Id,必须填写已审核通过的模板
/// </summary>
public string TemplateId { get; set; }
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Users.UserAccount.Contract
{
/// <summary>
/// 登录参数
/// </summary>
public abstract class BaseLoginParams
{
}
/// <summary>
///登录返回参数
/// </summary>
public class LoginAccountInfo
{
/// <summary>
/// 用户Id
/// </summary>
public int UserId { get; set; }
/// <summary>
/// 用户昵称
/// </summary>
public string NickName { get; set; }
/// <summary>
///
/// </summary>
public string Token { get; set; }
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Users.UserAccount.Contract
{
/// <summary>
/// 发送验证码
/// </summary>
public interface ISendVerificationCode
{
/// <summary>
/// 发送验证码
/// </summary>
/// <returns></returns>
public Task<bool> SendVerificationCode(BaseSendVerificationCode baseSendVerificationCode);
}
/// <summary>
/// 发送验证码需要的字段
/// </summary>
public class BaseSendVerificationCode
{
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Users.UserAccount.Contract
{
/// <summary>
/// 登录选项
/// </summary>
public abstract class IUserAccount
{
/// <summary>
/// 登录
/// </summary>
/// <param name="loginParams">登录参数</param>
/// <returns></returns>
public abstract Task<LoginAccountInfo> LoginAsync(BaseLoginParams loginParams);
}
}

View File

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Users.UserAccount.Contract
{
/// <summary>
/// 验证码管理类
/// </summary>
public interface IVerificationCodeManager
{
/// <summary>
/// 判断验证码是否已经过期
/// </summary>
/// <param name="key"></param>
/// <param name="code"></param>
/// <returns></returns>
bool IsVerificationCode(string key);
/// <summary>
/// 判断验证码是否已经过期
/// </summary>
/// <param name="key"></param>
/// <param name="code"></param>
/// <returns></returns>
bool IsExpireVerificationCode(string key, string code);
/// <summary>
/// 获取验证码
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
VerificationCodeResult GetVerificationCode(string key);
/// <summary>
/// 生成验证码
/// </summary>
/// <param name="key"></param>
/// <param name="code"></param>
/// <param name="now"></param>
/// <returns></returns>
VerificationCodeResult GenerateVerificationCode(string key, string code, DateTime now);
/// <summary>
/// 刷新验证码
/// </summary>
/// <param name="key"></param>
/// <param name="code"></param>
/// <param name="now"></param>
/// <returns></returns>
VerificationCodeResult Refresh(string key, string code, DateTime now);
/// <summary>
/// 删除过期的刷新令牌
/// </summary>
/// <param name="now"></param>
void RemoveExpiredRefreshCodes(DateTime now);
/// <summary>
/// 删除单个令牌
/// </summary>
/// <param name="now"></param>
void RemoveExpiredRefreshCodes(string key);
/// <summary>
/// 程序结束时保存
/// </summary>
void SaveVerificationCode();
/// <summary>
/// 程序运行时加载数据
/// </summary>
void LoadVerificationCode();
}
}

View File

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Users.UserAccount.Contract
{
/// <summary>
/// 验证码
/// </summary>
public class VerificationCodeResult
{
/// <summary>
/// key
/// </summary>
public string Key { get; set; }
/// <summary>
/// 验证码
/// </summary>
public string Code { get; set; }
/// <summary>
/// 过期时间
/// </summary>
public DateTime ExpireAt { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateAt { get; set; }
}
}

View File

@ -0,0 +1,185 @@
using HuanMeng.DotNetCore.Base;
using HuanMeng.MiaoYu.Code.DataAccess;
using HuanMeng.MiaoYu.Code.TencentUtile;
using HuanMeng.MiaoYu.Code.Users.UserAccount.Contract;
using HuanMeng.MiaoYu.Code.Users.UserAccount.VerificationCodeManager;
using HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Users.UserAccount.PhoneAccount
{
/// <summary>
/// 手机号登录
/// </summary>
/// <param name="phone">手机号</param>
public class PhoneAccountLogin(IVerificationCodeManager memoryVerificationCodeManager, TencentConfig tencentConfig, DAO dao) : IUserAccount
{
/// <summary>
/// 发送手机验证码
/// </summary>
/// <param name="phone"></param>
/// <returns></returns>
public async Task<BaseResponse<bool>> SendPhone(string phone)
{
if (!memoryVerificationCodeManager.IsVerificationCode(phone))
{
return new BaseResponse<bool>(ResonseCode.ManyRequests, "发送验证码频繁,清稍后再试!", false);
}
Random random = new Random();
var verificationCode = random.Next(100000, 1000000);
//先将验证码放到内存中,防止多次请求
//将验证码放到内存中控制
var verificationCodeModel = new VerificationCodeResult();
try
{
verificationCodeModel = memoryVerificationCodeManager.GenerateVerificationCode(phone, verificationCode.ToString(), DateTime.Now.AddMinutes(5));
}
catch (InvalidOperationException ex)
{
return new BaseResponse<bool>(ResonseCode.ManyRequests, "发送验证码频繁,清稍后再试!", false);
}
catch (Exception ex)
{
return new BaseResponse<bool>(ResonseCode.Error, "出现异常", false);
}
var day = int.Parse(DateTime.Now.ToString("yyyyMMdd"));
var phoneCount = dao.daoDbMiaoYu.context.T_Verification_Code.Where(it => it.Key == phone && it.CreateDay == day).Count();
if (phoneCount >= 5)
{
memoryVerificationCodeManager.RemoveExpiredRefreshCodes(phone);
return new BaseResponse<bool>(ResonseCode.Error, "当日请求次数太多", false);
}
//使用腾讯云短信接口
ISendVerificationCode sendVerificationCode = new TencentSMSSendVerificationCode(tencentConfig.SMSCode);
TencentSMSVerificationCode tencentSMSVerificationCode = new TencentSMSVerificationCode()
{
PhoneNum = phone,
VerificationCode = verificationCode.ToString(),
TimeOutInMinutes = 5.ToString()
};
//发送验证码
var isSend = await sendVerificationCode.SendVerificationCode(tencentSMSVerificationCode);
if (!isSend)
{
memoryVerificationCodeManager.RemoveExpiredRefreshCodes(phone);
return new BaseResponse<bool>(ResonseCode.Error, "验证码发送失败", false);
}
T_Verification_Code t_Verification_Code = new T_Verification_Code()
{
Code = verificationCodeModel.Code,
CreateAt = DateTime.Now,
ExpireAt = verificationCodeModel.ExpireAt,
CreateDay = day,
Key = phone,
Remarks = "登录验证码",
VerificationType = 0,
TenantId = dao.daoDbMiaoYu.context?.TenantInfo?.TenantId ?? Guid.Empty,
};
dao.daoDbMiaoYu.Add(t_Verification_Code);
dao.daoDbMiaoYu.context.SaveChanges();
return new BaseResponse<bool>(ResonseCode.Success, "验证码发送成功", true);
}
public async override Task<LoginAccountInfo> LoginAsync(BaseLoginParams loginParams)
{
var phoneLoginParams = loginParams as PhoneLoginParams;
if (phoneLoginParams == null)
{
throw new ArgumentNullException("登录参数异常");
}
if (string.IsNullOrEmpty(phoneLoginParams.PhoneNumber))
{
throw new ArgumentNullException("请输入手机号码");
}
if (string.IsNullOrEmpty(phoneLoginParams.VerificationCode))
{
throw new ArgumentNullException("请输入验证码");
}
if (!memoryVerificationCodeManager.IsExpireVerificationCode(phoneLoginParams.PhoneNumber, phoneLoginParams.VerificationCode))
{
throw new ArgumentNullException("验证码已失效");
}
var userlogin = dao.daoDbMiaoYu.context.T_User_Phone_Account.Where(it => it.PhoneNum == phoneLoginParams.PhoneNumber).FirstOrDefault();
T_User? user = null;
T_User_Data? userData = null;
if (userlogin != null)
{
user = await dao.daoDbMiaoYu.context.T_User.FirstOrDefaultAsync(it => it.Id == userlogin.UserId);
userData = await dao.daoDbMiaoYu.context.T_User_Data.FirstOrDefaultAsync(it => it.UserId == userlogin.UserId);
}
if (user == null)
{
user = new T_User()
{
UpdatedAt = DateTime.Now,
CreatedAt = DateTime.Now,
IsActive = true,
LastLoginAt = DateTime.Now,
LastLoginTypeAt = 1,
Email = "",
NickName = "新用户",
PhoneNum = phoneLoginParams.PhoneNumber,
RegisterType = 1,
TenantId = dao.daoDbMiaoYu.context.TenantInfo.TenantId,
UserName = phoneLoginParams.PhoneNumber,
};
dao.daoDbMiaoYu.context.T_User.Add(user);
dao.daoDbMiaoYu.context.SaveChanges();
}
if (userData == null)
{
userData = new T_User_Data()
{
CreatedAt_ = DateTime.Now,
Currency = 0,
NickName = user.NickName,
UpdatedAt = DateTime.Now,
VipType = 0,
UserId = user.Id,
TenantId = dao.daoDbMiaoYu.context.TenantInfo.TenantId,
UserIconUrl = "",
};
dao.daoDbMiaoYu.context.Add(userData);
}
if (userlogin == null)
{
userlogin = new T_User_Phone_Account()
{
PhoneNum = phoneLoginParams.PhoneNumber,
UserId = user.Id,
VerificationCode = phoneLoginParams.VerificationCode,
CreatedAt = DateTime.Now,
LastLoginAt = DateTime.Now,
NikeName = user.NickName,
TenantId = dao.daoDbMiaoYu.context.TenantInfo.TenantId,
UpdatedAt = DateTime.Now,
};
dao.daoDbMiaoYu.context.Add(userlogin);
}
user.LastLoginAt = DateTime.Now;
user.LastLoginTypeAt = 1;
user.IsActive = true;
user.Ip = phoneLoginParams.Ip;
dao.daoDbMiaoYu.context.SaveChanges();
LoginAccountInfo loginAccountInfo = new LoginAccountInfo()
{
UserId = user.Id,
NickName = user.NickName,
};
memoryVerificationCodeManager.RemoveExpiredRefreshCodes(phoneLoginParams.PhoneNumber);
return loginAccountInfo;
}
}
}

View File

@ -0,0 +1,31 @@
using HuanMeng.MiaoYu.Code.Users.UserAccount.Contract;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Users.UserAccount.PhoneAccount
{
/// <summary>
/// 登录参数
/// </summary>
public class PhoneLoginParams : BaseLoginParams
{
/// <summary>
/// 手机号码
/// </summary>
public string PhoneNumber { get; set; }
/// <summary>
/// 验证码
/// </summary>
public string VerificationCode { get; set; }
/// <summary>
/// Ip
/// </summary>
public string Ip { get; set; }
}
}

View File

@ -0,0 +1,178 @@
using HuanMeng.MiaoYu.Code.Users.UserAccount.Contract;
using TencentCloud.Common;
using TencentCloud.Common.Profile;
using TencentCloud.Sms.V20210111;
using TencentCloud.Sms.V20210111.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mail;
using System.Text;
using System.Threading.Tasks;
using System.Reflection.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using HuanMeng.MiaoYu.Code.TencentUtile;
namespace HuanMeng.MiaoYu.Code.Users.UserAccount
{
/// <summary>
/// 腾讯云发送短信
/// </summary>
/// <param name="tencentSMSConfig"></param>
public class TencentSMSSendVerificationCode(TencentSMSConfig tencentSMSConfig) : ISendVerificationCode
{
public async Task<bool> SendVerificationCode(BaseSendVerificationCode baseSendVerificationCode)
{
var code = baseSendVerificationCode as TencentSMSVerificationCode;
if (code == null)
{
throw new ArgumentNullException("参数错误");
}
string phoneNum = code.PhoneNum;
string verificationCode = code.VerificationCode;
if (!phoneNum.StartsWith("+86"))
{
phoneNum = "+86" + phoneNum;
}
try
{
// 必要步骤:
// 实例化一个认证对象,入参需要传入腾讯云账户密钥对 SecretIdSecretKey。
// 为了保护密钥安全,建议将密钥设置在环境变量中或者配置文件中。
// 硬编码密钥到代码中有可能随代码泄露而暴露,有安全隐患,并不推荐。
// 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。
// SecretId、SecretKey 查询https://console.cloud.tencent.com/cam/capi
Credential cred = new Credential
{
SecretId = tencentSMSConfig.SecretId,
SecretKey = tencentSMSConfig.SecretKey
};
/* :
* */
ClientProfile clientProfile = new ClientProfile();
/* SDK默认用TC3-HMAC-SHA256进行签名
* */
clientProfile.SignMethod = ClientProfile.SIGN_TC3SHA256;
/*
* */
HttpProfile httpProfile = new HttpProfile();
/* SDK默认使用POST方法
* 使GET方法GET方法无法处理一些较大的请求 */
httpProfile.ReqMethod = tencentSMSConfig.ReqMethod;
httpProfile.Timeout = tencentSMSConfig.Timeout; // 请求连接超时时间,单位为秒(默认60秒)
/* 指定接入地域域名,默认就近地域接入域名为 sms.tencentcloudapi.com ,也支持指定地域域名访问,例如广州地域的域名为 sms.ap-guangzhou.tencentcloudapi.com */
httpProfile.Endpoint = "sms.tencentcloudapi.com";
// 代理服务器,当您的环境下有代理服务器时设定(无需要直接忽略)
// httpProfile.WebProxy = Environment.GetEnvironmentVariable("HTTPS_PROXY");
clientProfile.HttpProfile = httpProfile;
/* (sms为例)client对象
* ap-guangzhou https://cloud.tencent.com/document/api/382/52071#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8 */
SmsClient client = new SmsClient(cred, "ap-nanjing", clientProfile);
/*
* SDK源码确定SendSmsRequest有哪些属性可以设置
*
* 使IDE进行开发便 */
SendSmsRequest req = new SendSmsRequest();
/* :
* SDK采用的是指针风格指定参数使
* SDK提供对基本类型的指针引用封装函数
*
* : https://console.cloud.tencent.com/smsv2
* : https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81 */
/* 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId示例如1400006666 */
// 应用 ID 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看
req.SmsSdkAppId = tencentSMSConfig.SmsSdkAppId;
/* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名 */
// 签名信息可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-sign) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-sign) 的签名管理查看
req.SignName = tencentSMSConfig.SignName;
/* 模板 ID: 必须填写已审核通过的模板 ID */
// 模板 ID 可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-template) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-template) 的正文模板管理查看
req.TemplateId = tencentSMSConfig.TemplateId;
/* 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空 */
req.TemplateParamSet = new String[] { verificationCode, code.TimeOutInMinutes };
/* E.164 +[][]
* +8613711112222 + 8613711112222200*/
req.PhoneNumberSet = new String[] { phoneNum };
/* 用户的 session 内容(无需要可忽略): 可以携带用户侧 ID 等上下文信息server 会原样返回 */
req.SessionContext = "";
/* 短信码号扩展号(无需要可忽略): 默认未开通,如需开通请联系 [腾讯云短信小助手] */
req.ExtendCode = "";
/* 国内短信无需填写该项;国际/港澳台短信已申请独立 SenderId 需要填写该字段,默认使用公共 SenderId无需填写该字段。注月度使用量达到指定量级可申请独立 SenderId 使用,详情请联系 [腾讯云短信小助手](https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81)。 */
req.SenderId = "";
SendSmsResponse resp = await client.SendSms(req);
code.SendSmsResponse = resp;
// 输出json格式的字符串回包
Console.WriteLine(AbstractModel.ToJsonString(resp));
}
catch (Exception e)
{
code.exception = e;
Console.WriteLine(e.ToString());
return false;
}
//Console.Read();
return true;
}
}
/// <summary>
/// 手机号
/// </summary>
public class TencentSMSVerificationCode : BaseSendVerificationCode
{
/// <summary>
/// 手机号
/// </summary>
public string PhoneNum { get; set; }
/// <summary>
/// 验证码
/// </summary>
public string VerificationCode { get; set; }
/// <summary>
/// 超时时间
/// </summary>
public string TimeOutInMinutes { get; set; }
/// <summary>
/// 请求返回内容
/// </summary>
public SendSmsResponse SendSmsResponse { get; set; }
/// <summary>
/// 异常信息
/// </summary>
public Exception exception { get; set; }
}
}

View File

@ -0,0 +1,100 @@
using HuanMeng.DotNetCore.JwtInfrastructure.Interface;
using HuanMeng.MiaoYu.Code.DataAccess;
using HuanMeng.MiaoYu.Code.TencentUtile;
using HuanMeng.MiaoYu.Code.Users.UserAccount.Contract;
using HuanMeng.MiaoYu.Code.Users.UserAccount.PhoneAccount;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Users.UserAccount
{
/// <summary>
/// 使用token
/// </summary>
/// <param name="jwtAuthManager"></param>
/// <param name="dao"></param>
public class TokenAccountLogin(IJwtAuthManager jwtAuthManager, DAO dao) : IUserAccount
{
public override async Task<LoginAccountInfo> LoginAsync(BaseLoginParams loginParams)
{
var tokenLoginParams = loginParams as TokenLoginParams;
if (tokenLoginParams == null)
{
throw new ArgumentNullException("登录参数异常");
}
var (principal, jwtToken) = jwtAuthManager.DecodeJwtToken(tokenLoginParams.Token);
if (jwtToken == null || !jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256Signature))
{
throw new SecurityTokenException("无效的token");
}
var exp = principal.FindFirst("exp")?.Value;
if (string.IsNullOrEmpty(exp))
{
throw new SecurityTokenException("无效的token");
}
var exptime = long.Parse(exp);
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (exptime < timestamp)
{
throw new SecurityTokenException("token已经过期");
}
var userIdStr = principal.FindFirst("UserId")?.Value;
if (string.IsNullOrEmpty(userIdStr))
{
throw new SecurityTokenException("无效的token");
}
var userId = int.Parse(userIdStr);
var user = await dao.daoDbMiaoYu.context.T_User.FirstOrDefaultAsync(it => it.Id == userId);
if (user == null)
{
throw new SecurityTokenException("用户未注册");
}
user.LastLoginAt = DateTime.Now;
user.LastLoginTypeAt = 0;
user.IsActive = true;
user.Ip = tokenLoginParams.Ip;
dao.daoDbMiaoYu.context.SaveChanges();
LoginAccountInfo loginAccountInfo = new LoginAccountInfo()
{
UserId = user.Id,
NickName = user.NickName,
};
DateTime dateTime = DateTimeOffset.FromUnixTimeSeconds(exptime).DateTime;
if (dateTime.Subtract(DateTime.Now).TotalDays > 2)
{
loginAccountInfo.Token = tokenLoginParams.Token;
}
return loginAccountInfo;
}
}
/// <summary>
/// 登录参数
/// </summary>
public class TokenLoginParams : BaseLoginParams
{
/// <summary>
/// token
/// </summary>
public string Token { get; set; }
/// <summary>
/// Ip
/// </summary>
public string Ip { get; set; }
}
}

View File

@ -0,0 +1,32 @@
using HuanMeng.MiaoYu.Code.Users.UserAccount.Contract;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Protocols;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Users.UserAccount.VerificationCodeManager
{
/// <summary>
///
/// </summary>
public static class MemoryVerificationCodeExtension
{
// <summary>
/// 验证码扩展
/// </summary>
/// <param name="serviceCollection"></param>
public static void AddMemoryVerificationCode(this IHostApplicationBuilder builder)
{
//注册一个验证码的服务
builder.Services.AddSingleton<IVerificationCodeManager, MemoryVerificationCodeManager>();
//注册验证码过期的服务器
builder.Services.AddHostedService<MemoryVerificationCodeServer>();
}
}
}

View File

@ -0,0 +1,208 @@
using HuanMeng.MiaoYu.Code.Users.UserAccount.Contract;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Users.UserAccount.VerificationCodeManager
{
/// <summary>
/// 内存 验证码存放 MemoryVerificationCodeExtension
/// </summary>
public class MemoryVerificationCodeManager : IVerificationCodeManager
{
/// <summary>
/// 存放数据
/// </summary>
public ConcurrentDictionary<string, VerificationCodeResult> MemoryVerificationCode { get; set; }
= new ConcurrentDictionary<string, VerificationCodeResult>();
public bool IsVerificationCode(string key)
{
//判断是否存在
if (MemoryVerificationCode.ContainsKey(key))
{
if (DateTime.Now.Subtract(MemoryVerificationCode[key].CreateAt).TotalSeconds < 59)
{
return false;
}
}
return true;
}
/// <summary>
/// 判断验证码是否已经过期
/// </summary>
/// <param name="key">手机号</param>
/// <param name="code">验证码</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException">未找到验证码</exception>
public bool IsExpireVerificationCode(string key, string code)
{
if (!MemoryVerificationCode.TryGetValue(key, out var result))
{
throw new ArgumentNullException("未找到验证码");
}
if (result.Code == code && result.ExpireAt >= DateTime.Now)
{
return true;
}
return false;
}
/// <summary>
/// 获取验证码
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException">未找到验证码</exception>
public VerificationCodeResult GetVerificationCode(string key)
{
if (!MemoryVerificationCode.TryGetValue(key, out var result))
{
throw new ArgumentNullException("未找到验证码");
}
return result;
}
/// <summary>
/// 发送验证码
/// </summary>
/// <param name="key">手机号</param>
/// <param name="code">验证码</param>
/// <param name="now">过期时间</param>
/// <returns></returns>
/// <exception cref="InvalidOperationException">验证码重复发送</exception>
public VerificationCodeResult GenerateVerificationCode(string key, string code, DateTime now)
{
if (!MemoryVerificationCode.TryGetValue(key, out var verificationCodeResult))
{
verificationCodeResult = new VerificationCodeResult()
{
};
MemoryVerificationCode.TryAdd(key, verificationCodeResult);
}
else
{
if (DateTime.Now.Subtract(verificationCodeResult.CreateAt).TotalSeconds < 30)
{
throw new InvalidOperationException("请求发送验证码频繁");
}
}
verificationCodeResult.Key = key;
verificationCodeResult.Code = code;
verificationCodeResult.ExpireAt = now;
verificationCodeResult.CreateAt = DateTime.Now;
return verificationCodeResult;
}
/// <summary>
/// 重新发送验证码
/// </summary>
/// <param name="key"></param>
/// <param name="code"></param>
/// <param name="now"></param>
/// <returns></returns>
public VerificationCodeResult Refresh(string key, string code, DateTime now)
{
if (!MemoryVerificationCode.TryGetValue(key, out var verificationCodeResult))
{
verificationCodeResult = new VerificationCodeResult()
{
};
MemoryVerificationCode.TryAdd(key, verificationCodeResult);
}
verificationCodeResult.Key = key;
verificationCodeResult.Code = code;
verificationCodeResult.ExpireAt = now;
return verificationCodeResult;
}
public void RemoveExpiredRefreshCodes(DateTime now)
{
var expiredTokens = MemoryVerificationCode.Where(x => x.Value.ExpireAt < now).ToList();
foreach (var expiredToken in expiredTokens)
{
MemoryVerificationCode.TryRemove(expiredToken.Key, out _);
}
}
public void RemoveExpiredRefreshCodes(string key)
{
MemoryVerificationCode.TryRemove(key, out _);
}
public void SaveVerificationCode()
{
try
{
string path = Path.GetFullPath("./output/verificationcode/");
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
var json = JsonConvert.SerializeObject(MemoryVerificationCode, Formatting.Indented);
var fileName = path + "verificationcode.json";
using (var file = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
{
file.Position = 0;
file.SetLength(0);
file.Write(System.Text.Encoding.UTF8.GetBytes(json));
file.Flush();
}
}
catch (Exception ex)
{
Console.WriteLine("程序结束时保存验证码出现错误", ex.Message);
}
}
public void LoadVerificationCode()
{
try
{
string path = Path.GetFullPath("./output/verificationcode/");
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
var fileName = path + "verificationcode.json";
if (!File.Exists(fileName))
{
return;
}
using StreamReader streamReader = new StreamReader(fileName);
var str = streamReader.ReadToEnd();
if (!string.IsNullOrEmpty(str))
{
var _memoryVerificationCode = JsonConvert.DeserializeObject<ConcurrentDictionary<string, VerificationCodeResult>>(str);
if (_memoryVerificationCode != null)
{
this.MemoryVerificationCode = _memoryVerificationCode;
}
}
}
catch (Exception ex)
{
Console.WriteLine("加载验证码出现错误", ex.Message);
}
}
}
}

View File

@ -0,0 +1,84 @@
using HuanMeng.MiaoYu.Code.Users.UserAccount.Contract;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Users.UserAccount.VerificationCodeManager
{
/// <summary>
/// 验证码服务
/// </summary>
public class MemoryVerificationCodeServer(IVerificationCodeManager manager) : IHostedService, IDisposable
{
/// <summary>
/// 定时器
/// </summary>
private Timer _timer = null!;
/// <summary>
/// 开始服务
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task StartAsync(CancellationToken cancellationToken)
{
manager.LoadVerificationCode();
// 每分钟从缓存中删除过期的刷新令牌
_timer = new Timer(DoWork!, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
return Task.CompletedTask;
}
/// <summary>
/// 停止服务
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task StopAsync(CancellationToken cancellationToken)
{
manager.SaveVerificationCode();
_timer.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
/// <summary>
/// 删除过期token
/// </summary>
/// <param name="state"></param>
/// <returns></returns>
private void DoWork(object state)
{
manager.RemoveExpiredRefreshCodes(DateTime.Now);
}
private bool disposedValue;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: 释放托管状态(托管对象)
}
_timer.Dispose();
// TODO: 释放未托管的资源(未托管的对象)并重写终结器
// TODO: 将大型字段设置为 null
disposedValue = true;
}
}
public void Dispose()
{
// 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -0,0 +1,118 @@
using HuanMeng.DotNetCore.Base;
using HuanMeng.MiaoYu.Code.Base;
using HuanMeng.MiaoYu.Code.Users.UserAccount;
using HuanMeng.MiaoYu.Code.Users.UserAccount.Contract;
using HuanMeng.MiaoYu.Code.Users.UserAccount.PhoneAccount;
using HuanMeng.MiaoYu.Model.Dto;
using HuanMeng.MiaoYu.Model.Dto.Account;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Users
{
/// <summary>
/// 用户类
/// </summary>
public class UserBLL : MiaoYuBase
{
public UserBLL(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
/// <summary>
/// 发送手机号码
/// </summary>
/// <param name="PhoneNumber"></param>
/// <returns></returns>
public async Task<BaseResponse<bool>> SendPhoneNumber(string PhoneNumber)
{
PhoneAccountLogin phoneAccountLogin = new PhoneAccountLogin(VerificationCodeManager, TencentConfig, Dao);
var msg = await phoneAccountLogin.SendPhone(PhoneNumber);
return msg;
}
/// <summary>
/// 登录
/// </summary>
/// <param name="requestLoginModel"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public async Task<BaseResponse<ResponseAccountLogIn>> AccountLogIn(RequestLoginModel requestLoginModel)
{
IUserAccount userAccount = null;
BaseLoginParams loginParams = null;
string ip = HttpContextAccessor.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "";
if (requestLoginModel.LoginType == 0)
{
userAccount = new TokenAccountLogin(JwtAuthManager, Dao);
loginParams = new TokenLoginParams()
{
Token = requestLoginModel.Token,
Ip = ip
};
}
else if (requestLoginModel.LoginType == 1)
{
userAccount = new PhoneAccountLogin(VerificationCodeManager, TencentConfig, Dao);
loginParams = new PhoneLoginParams()
{
PhoneNumber = requestLoginModel.PhoneNumber,
VerificationCode = requestLoginModel.VerificationCode,
Ip = ip
};
}
else
{
throw new Exception("不支持的登录方式");
}
var accountInfo = await userAccount.LoginAsync(loginParams);
if (string.IsNullOrEmpty(accountInfo.Token))
{
var claims = new[]
{
//new Claim(ClaimTypes.Name,accountInfo.NickName),
new Claim("NickName",accountInfo.NickName),
new Claim("UserId",accountInfo.UserId.ToString()),
};
var jwtAuthResulttre = JwtAuthManager.GenerateTokens(accountInfo.NickName, claims, DateTime.Now);
accountInfo.Token = jwtAuthResulttre.AccessToken;
}
ResponseAccountLogIn responseAccountLogIn = new ResponseAccountLogIn()
{
Token = accountInfo.Token,
NickName = accountInfo.NickName,
UserId = accountInfo.UserId,
};
return new BaseResponse<ResponseAccountLogIn>(ResonseCode.Success, "登录成功", responseAccountLogIn) { };
}
/// <summary>
/// 获取用户信息
/// </summary>
/// <returns></returns>
public async Task<BaseResponse<ResponseUserInfo>> GetUserInfo()
{
var user = await Dao.daoDbMiaoYu.context.T_User.FirstOrDefaultAsync(it => it.Id == _UserId);
var userData = await Dao.daoDbMiaoYu.context.T_User_Data.FirstOrDefaultAsync(it => it.Id == _UserId);
return new BaseResponse<ResponseUserInfo>(ResonseCode.Success, "请求成功", new ResponseUserInfo
{
NickName = user.NickName,
UserId = user.Id,
Currency = userData.Currency,
UserIconUrl = userData.UserIconUrl
});
}
}
}

View File

@ -0,0 +1,387 @@
<#@ template hostSpecific="true" #>
<#@ assembly name="Microsoft.EntityFrameworkCore" #>
<#@ assembly name="Microsoft.EntityFrameworkCore.Design" #>
<#@ assembly name="Microsoft.EntityFrameworkCore.Relational" #>
<#@ assembly name="Microsoft.Extensions.DependencyInjection.Abstractions" #>
<#@ parameter name="Model" type="Microsoft.EntityFrameworkCore.Metadata.IModel" #>
<#@ parameter name="Options" type="Microsoft.EntityFrameworkCore.Scaffolding.ModelCodeGenerationOptions" #>
<#@ parameter name="NamespaceHint" type="System.String" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="Microsoft.EntityFrameworkCore" #>
<#@ import namespace="Microsoft.EntityFrameworkCore.Design" #>
<#@ import namespace="Microsoft.EntityFrameworkCore.Infrastructure" #>
<#@ import namespace="Microsoft.EntityFrameworkCore.Scaffolding" #>
<#@ import namespace="Microsoft.Extensions.DependencyInjection" #>
<#
if (!ProductInfo.GetVersion().StartsWith("8.0"))
{
Warning("Your templates were created using an older version of Entity Framework. Additional features and bug fixes may be available. See https://aka.ms/efcore-docs-updating-templates for more information.");
}
var services = (IServiceProvider)Host;
var providerCode = services.GetRequiredService<IProviderConfigurationCodeGenerator>();
var annotationCodeGenerator = services.GetRequiredService<IAnnotationCodeGenerator>();
var code = services.GetRequiredService<ICSharpHelper>();
var usings = new List<string>
{
"System",
"System.Collections.Generic",
"Microsoft.EntityFrameworkCore"
};
if (NamespaceHint != Options.ModelNamespace
&& !string.IsNullOrEmpty(Options.ModelNamespace))
{
usings.Add(Options.ModelNamespace);
}
if (!string.IsNullOrEmpty(NamespaceHint))
{
#>
namespace <#= NamespaceHint #>;
<#
}
#>
/// <summary>
/// 妙语实体类
/// </summary>
public partial class <#= Options.ContextName #> : MultiTenantDbContext//DbContext
{
<#
if (!Options.SuppressOnConfiguring)
{
#>
public <#= Options.ContextName #>() : base(null)
{
}
<#
}
#>
public <#= Options.ContextName #>(DbContextOptions<<#= Options.ContextName #>> options)
: base(null, options)
{
}
/// <summary>
///
/// </summary>
/// <param name="tenantInfo"></param>
public <#= Options.ContextName #>(ITenantInfo tenantInfo) : base(tenantInfo)
{
}
/// <summary>
///
/// </summary>
/// <param name="tenantInfo"></param>
/// <param name="options"></param>
public <#= Options.ContextName #>(ITenantInfo tenantInfo, DbContextOptions<<#= Options.ContextName #>> options) : base(tenantInfo, options)
{
}
<#
foreach (var entityType in Model.GetEntityTypes().Where(e => !e.IsSimpleManyToManyJoinEntityType()))
{
#>
/// <summary>
/// <#=entityType.GetComment()??"" #>
/// </summary>
public virtual DbSet<<#= entityType.Name #>> <#= entityType.GetDbSetName() #> { get; set; }
<#
}
if (!Options.SuppressOnConfiguring)
{
#>
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
<#
if (!Options.SuppressConnectionStringWarning)
{
#>
<#
}
var useProviderCall = providerCode.GenerateUseProvider(Options.ConnectionString);
usings.AddRange(useProviderCall.GetRequiredUsings());
#>
=> optionsBuilder<#= code.Fragment(useProviderCall, indent: 3) #>;
<#
}
#>
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
<#
var anyConfiguration = false;
var modelFluentApiCalls = Model.GetFluentApiCalls(annotationCodeGenerator);
if (modelFluentApiCalls != null)
{
usings.AddRange(modelFluentApiCalls.GetRequiredUsings());
#>
modelBuilder<#= code.Fragment(modelFluentApiCalls, indent: 3) #>;
<#
anyConfiguration = true;
}
StringBuilder mainEnvironment;
foreach (var entityType in Model.GetEntityTypes().Where(e => !e.IsSimpleManyToManyJoinEntityType()))
{
// Save all previously generated code, and start generating into a new temporary environment
mainEnvironment = GenerationEnvironment;
GenerationEnvironment = new StringBuilder();
if (anyConfiguration)
{
WriteLine("");
}
var anyEntityTypeConfiguration = false;
#>
modelBuilder.Entity<<#= entityType.Name #>>(entity =>
{
<#
var key = entityType.FindPrimaryKey();
if (key != null)
{
var keyFluentApiCalls = key.GetFluentApiCalls(annotationCodeGenerator);
if (keyFluentApiCalls != null
|| (!key.IsHandledByConvention() && !Options.UseDataAnnotations))
{
if (keyFluentApiCalls != null)
{
usings.AddRange(keyFluentApiCalls.GetRequiredUsings());
}
#>
entity.HasKey(<#= code.Lambda(key.Properties, "e") #>)<#= code.Fragment(keyFluentApiCalls, indent: 4) #>;
<#
anyEntityTypeConfiguration = true;
}
}
var entityTypeFluentApiCalls = entityType.GetFluentApiCalls(annotationCodeGenerator)
?.FilterChain(c => !(Options.UseDataAnnotations && c.IsHandledByDataAnnotations));
if (entityTypeFluentApiCalls != null)
{
usings.AddRange(entityTypeFluentApiCalls.GetRequiredUsings());
if (anyEntityTypeConfiguration)
{
WriteLine("");
}
#>
entity<#= code.Fragment(entityTypeFluentApiCalls, indent: 4) #>;
<#
anyEntityTypeConfiguration = true;
}
foreach (var index in entityType.GetIndexes()
.Where(i => !(Options.UseDataAnnotations && i.IsHandledByDataAnnotations(annotationCodeGenerator))))
{
if (anyEntityTypeConfiguration)
{
WriteLine("");
}
var indexFluentApiCalls = index.GetFluentApiCalls(annotationCodeGenerator);
if (indexFluentApiCalls != null)
{
usings.AddRange(indexFluentApiCalls.GetRequiredUsings());
}
#>
entity.HasIndex(<#= code.Lambda(index.Properties, "e") #>, <#= code.Literal(index.GetDatabaseName()) #>)<#= code.Fragment(indexFluentApiCalls, indent: 4) #>;
<#
anyEntityTypeConfiguration = true;
}
var firstProperty = true;
foreach (var property in entityType.GetProperties())
{
var propertyFluentApiCalls = property.GetFluentApiCalls(annotationCodeGenerator)
?.FilterChain(c => !(Options.UseDataAnnotations && c.IsHandledByDataAnnotations)
&& !(c.Method == "IsRequired" && Options.UseNullableReferenceTypes && !property.ClrType.IsValueType));
if (propertyFluentApiCalls == null)
{
continue;
}
usings.AddRange(propertyFluentApiCalls.GetRequiredUsings());
if (anyEntityTypeConfiguration && firstProperty)
{
WriteLine("");
}
#>
entity.Property(e => e.<#= property.Name #>)<#= code.Fragment(propertyFluentApiCalls, indent: 4) #>;
<#
anyEntityTypeConfiguration = true;
firstProperty = false;
}
foreach (var foreignKey in entityType.GetForeignKeys())
{
var foreignKeyFluentApiCalls = foreignKey.GetFluentApiCalls(annotationCodeGenerator)
?.FilterChain(c => !(Options.UseDataAnnotations && c.IsHandledByDataAnnotations));
if (foreignKeyFluentApiCalls == null)
{
continue;
}
usings.AddRange(foreignKeyFluentApiCalls.GetRequiredUsings());
if (anyEntityTypeConfiguration)
{
WriteLine("");
}
#>
entity.HasOne(d => d.<#= foreignKey.DependentToPrincipal.Name #>).<#= foreignKey.IsUnique ? "WithOne" : "WithMany" #>(<#= foreignKey.PrincipalToDependent != null ? $"p => p.{foreignKey.PrincipalToDependent.Name}" : "" #>)<#= code.Fragment(foreignKeyFluentApiCalls, indent: 4) #>;
<#
anyEntityTypeConfiguration = true;
}
foreach (var skipNavigation in entityType.GetSkipNavigations().Where(n => n.IsLeftNavigation()))
{
if (anyEntityTypeConfiguration)
{
WriteLine("");
}
var left = skipNavigation.ForeignKey;
var leftFluentApiCalls = left.GetFluentApiCalls(annotationCodeGenerator, useStrings: true);
var right = skipNavigation.Inverse.ForeignKey;
var rightFluentApiCalls = right.GetFluentApiCalls(annotationCodeGenerator, useStrings: true);
var joinEntityType = skipNavigation.JoinEntityType;
if (leftFluentApiCalls != null)
{
usings.AddRange(leftFluentApiCalls.GetRequiredUsings());
}
if (rightFluentApiCalls != null)
{
usings.AddRange(rightFluentApiCalls.GetRequiredUsings());
}
#>
entity.HasMany(d => d.<#= skipNavigation.Name #>).WithMany(p => p.<#= skipNavigation.Inverse.Name #>)
.UsingEntity<Dictionary<string, object>>(
<#= code.Literal(joinEntityType.Name) #>,
r => r.HasOne<<#= right.PrincipalEntityType.Name #>>().WithMany()<#= code.Fragment(rightFluentApiCalls, indent: 6) #>,
l => l.HasOne<<#= left.PrincipalEntityType.Name #>>().WithMany()<#= code.Fragment(leftFluentApiCalls, indent: 6) #>,
j =>
{
<#
var joinKey = joinEntityType.FindPrimaryKey();
var joinKeyFluentApiCalls = joinKey.GetFluentApiCalls(annotationCodeGenerator);
if (joinKeyFluentApiCalls != null)
{
usings.AddRange(joinKeyFluentApiCalls.GetRequiredUsings());
}
#>
j.HasKey(<#= code.Arguments(joinKey.Properties.Select(e => e.Name)) #>)<#= code.Fragment(joinKeyFluentApiCalls, indent: 7) #>;
<#
var joinEntityTypeFluentApiCalls = joinEntityType.GetFluentApiCalls(annotationCodeGenerator);
if (joinEntityTypeFluentApiCalls != null)
{
usings.AddRange(joinEntityTypeFluentApiCalls.GetRequiredUsings());
#>
j<#= code.Fragment(joinEntityTypeFluentApiCalls, indent: 7) #>;
<#
}
foreach (var index in joinEntityType.GetIndexes())
{
var indexFluentApiCalls = index.GetFluentApiCalls(annotationCodeGenerator);
if (indexFluentApiCalls != null)
{
usings.AddRange(indexFluentApiCalls.GetRequiredUsings());
}
#>
j.HasIndex(<#= code.Literal(index.Properties.Select(e => e.Name).ToArray()) #>, <#= code.Literal(index.GetDatabaseName()) #>)<#= code.Fragment(indexFluentApiCalls, indent: 7) #>;
<#
}
foreach (var property in joinEntityType.GetProperties())
{
var propertyFluentApiCalls = property.GetFluentApiCalls(annotationCodeGenerator);
if (propertyFluentApiCalls == null)
{
continue;
}
usings.AddRange(propertyFluentApiCalls.GetRequiredUsings());
#>
j.IndexerProperty<<#= code.Reference(property.ClrType) #>>(<#= code.Literal(property.Name) #>)<#= code.Fragment(propertyFluentApiCalls, indent: 7) #>;
<#
}
#>
});
<#
anyEntityTypeConfiguration = true;
}
#>
//添加全局筛选器
if (this.TenantInfo != null)
{
entity.HasQueryFilter(it => it.TenantId == this.TenantInfo.TenantId);
}
});
<#
// If any signicant code was generated, append it to the main environment
if (anyEntityTypeConfiguration)
{
mainEnvironment.Append(GenerationEnvironment);
anyConfiguration = true;
}
// Resume generating code into the main environment
GenerationEnvironment = mainEnvironment;
}
foreach (var sequence in Model.GetSequences())
{
var needsType = sequence.Type != typeof(long);
var needsSchema = !string.IsNullOrEmpty(sequence.Schema) && sequence.Schema != sequence.Model.GetDefaultSchema();
var sequenceFluentApiCalls = sequence.GetFluentApiCalls(annotationCodeGenerator);
#>
modelBuilder.HasSequence<#= needsType ? $"<{code.Reference(sequence.Type)}>" : "" #>(<#= code.Literal(sequence.Name) #><#= needsSchema ? $", {code.Literal(sequence.Schema)}" : "" #>)<#= code.Fragment(sequenceFluentApiCalls, indent: 3) #>;
<#
}
if (anyConfiguration)
{
WriteLine("");
}
#>
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
<#
mainEnvironment = GenerationEnvironment;
GenerationEnvironment = new StringBuilder();
foreach (var ns in usings.Distinct().OrderBy(x => x, new NamespaceComparer()))
{
#>
using <#= ns #>;
<#
}
WriteLine("");
GenerationEnvironment.Append(mainEnvironment);
#>

View File

@ -0,0 +1,177 @@
<#@ template hostSpecific="true" #>
<#@ assembly name="Microsoft.EntityFrameworkCore" #>
<#@ assembly name="Microsoft.EntityFrameworkCore.Design" #>
<#@ assembly name="Microsoft.EntityFrameworkCore.Relational" #>
<#@ assembly name="Microsoft.Extensions.DependencyInjection.Abstractions" #>
<#@ parameter name="EntityType" type="Microsoft.EntityFrameworkCore.Metadata.IEntityType" #>
<#@ parameter name="Options" type="Microsoft.EntityFrameworkCore.Scaffolding.ModelCodeGenerationOptions" #>
<#@ parameter name="NamespaceHint" type="System.String" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.ComponentModel.DataAnnotations" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="Microsoft.EntityFrameworkCore" #>
<#@ import namespace="Microsoft.EntityFrameworkCore.Design" #>
<#@ import namespace="Microsoft.Extensions.DependencyInjection" #>
<#
if (EntityType.IsSimpleManyToManyJoinEntityType())
{
// Don't scaffold these
return "";
}
var services = (IServiceProvider)Host;
var annotationCodeGenerator = services.GetRequiredService<IAnnotationCodeGenerator>();
var code = services.GetRequiredService<ICSharpHelper>();
var usings = new List<string>
{
// "System",
//"System.Collections.Generic",
};
if (Options.UseDataAnnotations)
{
usings.Add("System.ComponentModel.DataAnnotations");
usings.Add("System.ComponentModel.DataAnnotations.Schema");
usings.Add("Microsoft.EntityFrameworkCore");
}
if (!string.IsNullOrEmpty(NamespaceHint))
{
#>
namespace <#= NamespaceHint #>;
<#
}
if (!string.IsNullOrEmpty(EntityType.GetComment()))
{
#>
/// <summary>
/// <#= code.XmlComment(EntityType.GetComment()) #>
/// </summary>
<#
}
if (Options.UseDataAnnotations)
{
foreach (var dataAnnotation in EntityType.GetDataAnnotations(annotationCodeGenerator))
{
#>
<#= code.Fragment(dataAnnotation) #>
<#
}
}
#>
public partial class <#= EntityType.Name #>: MultiTenantEntity
{
<#
var firstProperty = true;
foreach (var property in EntityType.GetProperties().OrderBy(p => p.GetColumnOrder() ?? -1))
{
if (!firstProperty)
{
WriteLine("");
}
if(property.Name=="TenantId"){
continue;
}
if (!string.IsNullOrEmpty(property.GetComment()))
{
#>
/// <summary>
/// <#= code.XmlComment(property.GetComment(), indent: 1) #>
/// </summary>
<#
}
if (Options.UseDataAnnotations)
{
var dataAnnotations = property.GetDataAnnotations(annotationCodeGenerator)
.Where(a => !(a.Type == typeof(RequiredAttribute) && Options.UseNullableReferenceTypes && !property.ClrType.IsValueType));
foreach (var dataAnnotation in dataAnnotations)
{
#>
<#= code.Fragment(dataAnnotation) #>
<#
}
}
usings.AddRange(code.GetRequiredUsings(property.ClrType));
var needsNullable = Options.UseNullableReferenceTypes && property.IsNullable && !property.ClrType.IsValueType;
var needsInitializer = Options.UseNullableReferenceTypes && !property.IsNullable && !property.ClrType.IsValueType;
#>
public <#= code.Reference(property.ClrType) #><#= needsNullable ? "?" : "" #> <#= property.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #>
<#
firstProperty = false;
}
foreach (var navigation in EntityType.GetNavigations())
{
WriteLine("");
if (Options.UseDataAnnotations)
{
foreach (var dataAnnotation in navigation.GetDataAnnotations(annotationCodeGenerator))
{
#>
<#= code.Fragment(dataAnnotation) #>
<#
}
}
var targetType = navigation.TargetEntityType.Name;
if (navigation.IsCollection)
{
#>
public virtual ICollection<<#= targetType #>> <#= navigation.Name #> { get; set; } = new List<<#= targetType #>>();
<#
}
else
{
var needsNullable = Options.UseNullableReferenceTypes && !(navigation.ForeignKey.IsRequired && navigation.IsOnDependent);
var needsInitializer = Options.UseNullableReferenceTypes && navigation.ForeignKey.IsRequired && navigation.IsOnDependent;
#>
public virtual <#= targetType #><#= needsNullable ? "?" : "" #> <#= navigation.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #>
<#
}
}
foreach (var skipNavigation in EntityType.GetSkipNavigations())
{
WriteLine("");
if (Options.UseDataAnnotations)
{
foreach (var dataAnnotation in skipNavigation.GetDataAnnotations(annotationCodeGenerator))
{
#>
<#= code.Fragment(dataAnnotation) #>
<#
}
}
#>
public virtual ICollection<<#= skipNavigation.TargetEntityType.Name #>> <#= skipNavigation.Name #> { get; set; } = new List<<#= skipNavigation.TargetEntityType.Name #>>();
<#
}
#>
}
<#
var previousOutput = GenerationEnvironment;
GenerationEnvironment = new StringBuilder();
foreach (var ns in usings.Distinct().OrderBy(x => x, new NamespaceComparer()))
{
#>
using <#= ns #>;
<#
}
WriteLine("");
GenerationEnvironment.Append(previousOutput);
#>

View File

@ -0,0 +1,524 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
namespace HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
/// <summary>
/// 妙语实体类
/// </summary>
public partial class MiaoYuContext : MultiTenantDbContext//DbContext
{
public MiaoYuContext() : base(null)
{
}
public MiaoYuContext(DbContextOptions<MiaoYuContext> options)
: base(null, options)
{
}
/// <summary>
///
/// </summary>
/// <param name="tenantInfo"></param>
public MiaoYuContext(ITenantInfo tenantInfo) : base(tenantInfo)
{
}
/// <summary>
///
/// </summary>
/// <param name="tenantInfo"></param>
/// <param name="options"></param>
public MiaoYuContext(ITenantInfo tenantInfo, DbContextOptions<MiaoYuContext> options) : base(tenantInfo, options)
{
}
/// <summary>
/// 人物表
/// </summary>
public virtual DbSet<T_Character> T_Character { get; set; }
/// <summary>
/// 角色标签表
/// </summary>
public virtual DbSet<T_Character_Label> T_Character_Label { get; set; }
/// <summary>
/// 关联角色和标签
/// </summary>
public virtual DbSet<T_Character_Label_Relation> T_Character_Label_Relation { get; set; }
/// <summary>
/// 发现页类型分类
/// </summary>
public virtual DbSet<T_Character_Type> T_Character_Type { get; set; }
/// <summary>
/// 存储用户和角色之间的亲密值
/// </summary>
public virtual DbSet<T_Character_User_Intimacy> T_Character_User_Intimacy { get; set; }
/// <summary>
/// 聊天记录表
/// </summary>
public virtual DbSet<T_Chat> T_Chat { get; set; }
/// <summary>
/// 图片表
/// </summary>
public virtual DbSet<T_Image_Config> T_Image_Config { get; set; }
/// <summary>
///
/// </summary>
public virtual DbSet<T_Model_Config> T_Model_Config { get; set; }
/// <summary>
/// 用户表
/// </summary>
public virtual DbSet<T_User> T_User { get; set; }
/// <summary>
///
/// </summary>
public virtual DbSet<T_User_Char> T_User_Char { get; set; }
/// <summary>
/// 用户信息表
/// </summary>
public virtual DbSet<T_User_Data> T_User_Data { get; set; }
/// <summary>
/// 手机号登录表
/// </summary>
public virtual DbSet<T_User_Phone_Account> T_User_Phone_Account { get; set; }
/// <summary>
/// 验证码表
/// </summary>
public virtual DbSet<T_Verification_Code> T_Verification_Code { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer("Server=192.168.195.2;Database=MiaoYu;User Id=zpc;Password=zpc;TrustServerCertificate=true;");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<T_Character>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__T_Charac__3214EC070A8D79B3");
entity.ToTable(tb => tb.HasComment("人物表"));
entity.Property(e => e.Id)
.ValueGeneratedNever()
.HasComment("人物id");
entity.Property(e => e.BgImg).HasComment("背景图片");
entity.Property(e => e.Biography)
.HasMaxLength(500)
.HasComment("人物简介");
entity.Property(e => e.CreateTime)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.Gender).HasComment("性别0男1女2其他");
entity.Property(e => e.IconImg).HasComment("角色头像");
entity.Property(e => e.ModelConfigId).HasComment("模型Id");
entity.Property(e => e.Name)
.HasMaxLength(50)
.HasComment("人物名字");
entity.Property(e => e.Prologue)
.HasMaxLength(255)
.HasComment("开场白");
entity.Property(e => e.System)
.HasMaxLength(1000)
.HasComment("人物初始设定");
entity.Property(e => e.TenantId).HasComment("租户Id");
entity.Property(e => e.UpdateTime)
.HasComment("更新时间")
.HasColumnType("datetime");
entity.Property(e => e.Visibility).HasComment("公开/私密 0公开 1私密");
//添加全局筛选器
if (this.TenantInfo != null)
{
entity.HasQueryFilter(it => it.TenantId == this.TenantInfo.TenantId);
}
});
modelBuilder.Entity<T_Character_Label>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__T_Charac__3214EC0777B5F217");
entity.ToTable(tb => tb.HasComment("角色标签表"));
entity.Property(e => e.Id)
.ValueGeneratedNever()
.HasComment("标签id");
entity.Property(e => e.CreateTime)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.LabelName)
.HasMaxLength(50)
.HasComment("标签名称");
entity.Property(e => e.TenantId).HasComment("租户id");
entity.Property(e => e.UpdateTime)
.HasComment("更新时间")
.HasColumnType("datetime");
//添加全局筛选器
if (this.TenantInfo != null)
{
entity.HasQueryFilter(it => it.TenantId == this.TenantInfo.TenantId);
}
});
modelBuilder.Entity<T_Character_Label_Relation>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__T_Charac__3214EC071FC16A53");
entity.ToTable(tb => tb.HasComment("关联角色和标签"));
entity.Property(e => e.Id).HasComment("人物和标签的关联id");
entity.Property(e => e.CharacterId).HasComment("人物Id");
entity.Property(e => e.CharacterLabelId).HasComment("人物标签id");
entity.Property(e => e.CreateTime)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.TenantId).HasComment("租户id");
entity.Property(e => e.UpdateTime)
.HasComment("更新时间")
.HasColumnType("datetime");
//添加全局筛选器
if (this.TenantInfo != null)
{
entity.HasQueryFilter(it => it.TenantId == this.TenantInfo.TenantId);
}
});
modelBuilder.Entity<T_Character_Type>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__T_Charac__3214EC070CC04F82");
entity.ToTable(tb => tb.HasComment("发现页类型分类"));
entity.Property(e => e.Id)
.ValueGeneratedNever()
.HasComment("类型id");
entity.Property(e => e.CreateTime)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.Name)
.HasMaxLength(255)
.HasComment("类型名称");
entity.Property(e => e.TenantId).HasComment("租户id");
entity.Property(e => e.UpdateTime)
.HasComment("更新时间")
.HasColumnType("datetime");
//添加全局筛选器
if (this.TenantInfo != null)
{
entity.HasQueryFilter(it => it.TenantId == this.TenantInfo.TenantId);
}
});
modelBuilder.Entity<T_Character_User_Intimacy>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__T_Charac__3214EC079BEEBDEA");
entity.ToTable(tb => tb.HasComment("存储用户和角色之间的亲密值"));
entity.Property(e => e.Id).HasComment("亲密度id");
entity.Property(e => e.CharacterId).HasComment("人物Id");
entity.Property(e => e.CreateTime)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.IntimacyValue).HasComment("亲密值");
entity.Property(e => e.TenantId).HasComment("租户id");
entity.Property(e => e.UpdateTime)
.HasComment("更新时间")
.HasColumnType("datetime");
entity.Property(e => e.UserId).HasComment("用户Id");
//添加全局筛选器
if (this.TenantInfo != null)
{
entity.HasQueryFilter(it => it.TenantId == this.TenantInfo.TenantId);
}
});
modelBuilder.Entity<T_Chat>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__T_Chat__3214EC079C2C8859");
entity.ToTable(tb => tb.HasComment("聊天记录表"));
entity.Property(e => e.Id)
.ValueGeneratedNever()
.HasComment("聊天id");
entity.Property(e => e.CharacterId).HasComment("人物表Id");
entity.Property(e => e.ClaudeId)
.HasMaxLength(100)
.HasComment("聊天返回的Id");
entity.Property(e => e.ClaudeModel)
.HasMaxLength(50)
.HasComment("人物模型,聊天返回的模型");
entity.Property(e => e.ClaudeType)
.HasMaxLength(20)
.HasComment("聊天返回的消息的类型");
entity.Property(e => e.Content)
.HasMaxLength(1000)
.HasComment("消息内容");
entity.Property(e => e.CreateTime)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.Input_tokens).HasComment("输入token");
entity.Property(e => e.Output_tokens).HasComment("输出token");
entity.Property(e => e.Role)
.HasMaxLength(50)
.HasComment("user/assistant");
entity.Property(e => e.SendDateDay).HasComment("发送消息,天");
entity.Property(e => e.SendMessageDay).HasComment("发送消息时间戳");
entity.Property(e => e.TenantId).HasComment("租户id");
entity.Property(e => e.TimeStamp)
.HasComment("发送时间")
.HasColumnType("datetime");
entity.Property(e => e.Type).HasComment("0正常1重新生成2 删除");
entity.Property(e => e.UpdateTime)
.HasComment("更新时间")
.HasColumnType("datetime");
entity.Property(e => e.UserId).HasComment("聊天内容");
//添加全局筛选器
if (this.TenantInfo != null)
{
entity.HasQueryFilter(it => it.TenantId == this.TenantInfo.TenantId);
}
});
modelBuilder.Entity<T_Image_Config>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__T_Image___3214EC072BCFE4E5");
entity.ToTable(tb => tb.HasComment("图片表"));
entity.Property(e => e.ImageId).HasComment("图片Id");
entity.Property(e => e.Name)
.HasMaxLength(50)
.HasComment("图片名称");
entity.Property(e => e.TenantId).HasComment("租户");
entity.Property(e => e.Url)
.HasMaxLength(500)
.HasComment("图片地址");
//添加全局筛选器
if (this.TenantInfo != null)
{
entity.HasQueryFilter(it => it.TenantId == this.TenantInfo.TenantId);
}
});
modelBuilder.Entity<T_Model_Config>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__T_Model___3214EC074121E040");
entity.Property(e => e.AnthropicVersion)
.HasMaxLength(255)
.HasComment("模型版本 anthropic-version");
entity.Property(e => e.ApiKey)
.HasMaxLength(255)
.HasComment("模型key x-api-key");
entity.Property(e => e.CreateTime)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.MaxTokens).HasComment("模型运行最大的max_tokens");
entity.Property(e => e.Model)
.HasMaxLength(50)
.HasComment("模型model");
entity.Property(e => e.ModelName)
.HasMaxLength(50)
.HasComment("模型名称");
entity.Property(e => e.TenantId).HasComment("租户");
entity.Property(e => e.UpdateTime)
.HasComment("修改时间")
.HasColumnType("datetime");
entity.Property(e => e.Url)
.HasMaxLength(255)
.HasComment("模型请求地址");
//添加全局筛选器
if (this.TenantInfo != null)
{
entity.HasQueryFilter(it => it.TenantId == this.TenantInfo.TenantId);
}
});
modelBuilder.Entity<T_User>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__T_User__3214EC073733108B");
entity.ToTable(tb => tb.HasComment("用户表"));
entity.Property(e => e.Id).HasComment("用户Id");
entity.Property(e => e.CreatedAt)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.Email)
.HasMaxLength(255)
.HasComment("绑定的邮箱");
entity.Property(e => e.Ip)
.HasMaxLength(100)
.HasComment("Ip地址");
entity.Property(e => e.IsActive).HasComment("是否活跃");
entity.Property(e => e.LastLoginAt)
.HasComment("最后一次登录时间")
.HasColumnType("datetime");
entity.Property(e => e.LastLoginTypeAt).HasComment("最后一次登录方式,1手机号");
entity.Property(e => e.NickName)
.HasMaxLength(100)
.HasComment("用户昵称");
entity.Property(e => e.PhoneNum)
.HasMaxLength(50)
.IsUnicode(false)
.HasComment("绑定的手机号");
entity.Property(e => e.RegisterType).HasComment("首次注册方式");
entity.Property(e => e.TenantId).HasComment("租户Id");
entity.Property(e => e.UpdatedAt)
.HasComment("修改时间")
.HasColumnType("datetime");
entity.Property(e => e.UserName)
.HasMaxLength(100)
.HasComment("用户姓名");
//添加全局筛选器
if (this.TenantInfo != null)
{
entity.HasQueryFilter(it => it.TenantId == this.TenantInfo.TenantId);
}
});
modelBuilder.Entity<T_User_Char>(entity =>
{
entity.Property(e => e.CharacterId).HasComment("角色Id");
entity.Property(e => e.CreateAt)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.IsDelete).HasComment("是否删除");
entity.Property(e => e.ModelConfigId).HasComment("使用模型Id");
entity.Property(e => e.SessionId).HasComment("会话Id");
entity.Property(e => e.SessionName)
.HasMaxLength(50)
.HasComment("会话名称");
entity.Property(e => e.TenantId).HasComment("租户");
entity.Property(e => e.UpdateAt)
.HasComment("修改时间")
.HasColumnType("datetime");
entity.Property(e => e.UserId).HasComment("用户Id");
//添加全局筛选器
if (this.TenantInfo != null)
{
entity.HasQueryFilter(it => it.TenantId == this.TenantInfo.TenantId);
}
});
modelBuilder.Entity<T_User_Data>(entity =>
{
entity.HasKey(e => new { e.Id, e.UserId }).HasName("PK__T_User_D__E36C60C3D959FD89");
entity.ToTable(tb => tb.HasComment("用户信息表"));
entity.Property(e => e.Id)
.ValueGeneratedOnAdd()
.HasComment("主键");
entity.Property(e => e.UserId).HasComment("用户Id");
entity.Property(e => e.CreatedAt_)
.HasComment("创建时间")
.HasColumnType("datetime")
.HasColumnName("CreatedAt ");
entity.Property(e => e.Currency).HasComment("货币");
entity.Property(e => e.NickName)
.HasMaxLength(100)
.HasComment("用户昵称,需要和主表保持一致");
entity.Property(e => e.UpdatedAt)
.HasComment("更新时间")
.HasColumnType("datetime");
entity.Property(e => e.UserIconUrl)
.HasMaxLength(300)
.HasComment("用户头像");
entity.Property(e => e.VipType).HasComment("vip类型");
//添加全局筛选器
if (this.TenantInfo != null)
{
entity.HasQueryFilter(it => it.TenantId == this.TenantInfo.TenantId);
}
});
modelBuilder.Entity<T_User_Phone_Account>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__T_User_P__3214EC07987BDDB2");
entity.ToTable(tb => tb.HasComment("手机号登录表"));
entity.Property(e => e.Id).HasComment("主键");
entity.Property(e => e.CreatedAt)
.HasComment("修改时间")
.HasColumnType("datetime");
entity.Property(e => e.LastLoginAt)
.HasComment("最后一次登录时间")
.HasColumnType("datetime");
entity.Property(e => e.NikeName)
.HasMaxLength(100)
.HasComment("用户昵称");
entity.Property(e => e.PhoneNum)
.HasMaxLength(50)
.IsUnicode(false)
.HasComment("手机号");
entity.Property(e => e.TenantId).HasComment("租户Id");
entity.Property(e => e.UpdatedAt)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.UserId).HasComment("用户Id");
entity.Property(e => e.VerificationCode)
.HasMaxLength(10)
.IsUnicode(false)
.HasComment("验证码");
//添加全局筛选器
if (this.TenantInfo != null)
{
entity.HasQueryFilter(it => it.TenantId == this.TenantInfo.TenantId);
}
});
modelBuilder.Entity<T_Verification_Code>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__T_Verifi__3214EC074DE3F41A");
entity.ToTable(tb => tb.HasComment("验证码表"));
entity.Property(e => e.Id).HasComment("主键");
entity.Property(e => e.Code)
.HasMaxLength(10)
.HasComment("验证码");
entity.Property(e => e.CreateAt)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.CreateDay).HasComment("创建天");
entity.Property(e => e.ExpireAt)
.HasComment("过期时间")
.HasColumnType("datetime");
entity.Property(e => e.Key)
.HasMaxLength(100)
.HasComment("手机号或者邮箱");
entity.Property(e => e.Remarks)
.HasMaxLength(100)
.HasComment("备注");
entity.Property(e => e.TenantId).HasComment("租户");
entity.Property(e => e.VerificationType).HasComment("0手机1邮箱");
//添加全局筛选器
if (this.TenantInfo != null)
{
entity.HasQueryFilter(it => it.TenantId == this.TenantInfo.TenantId);
}
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

View File

@ -0,0 +1,70 @@
using System;
namespace HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
/// <summary>
/// 人物表
/// </summary>
public partial class T_Character: MultiTenantEntity
{
/// <summary>
/// 人物id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 人物名字
/// </summary>
public string? Name { get; set; }
/// <summary>
/// 人物简介
/// </summary>
public string? Biography { get; set; }
/// <summary>
/// 开场白
/// </summary>
public string Prologue { get; set; } = null!;
/// <summary>
/// 模型Id
/// </summary>
public int ModelConfigId { get; set; }
/// <summary>
/// 公开/私密 0公开 1私密
/// </summary>
public bool Visibility { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime? CreateTime { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime? UpdateTime { get; set; }
/// <summary>
/// 性别0男1女2其他
/// </summary>
public int Gender { get; set; }
/// <summary>
/// 人物初始设定
/// </summary>
public string? System { get; set; }
/// <summary>
/// 背景图片
/// </summary>
public int? BgImg { get; set; }
/// <summary>
/// 角色头像
/// </summary>
public int? IconImg { get; set; }
}

View File

@ -0,0 +1,30 @@
using System;
namespace HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
/// <summary>
/// 角色标签表
/// </summary>
public partial class T_Character_Label: MultiTenantEntity
{
/// <summary>
/// 标签id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 标签名称
/// </summary>
public string? LabelName { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime? CreateTime { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime? UpdateTime { get; set; }
}

View File

@ -0,0 +1,35 @@
using System;
namespace HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
/// <summary>
/// 关联角色和标签
/// </summary>
public partial class T_Character_Label_Relation: MultiTenantEntity
{
/// <summary>
/// 人物和标签的关联id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 人物Id
/// </summary>
public int CharacterId { get; set; }
/// <summary>
/// 人物标签id
/// </summary>
public int CharacterLabelId { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdateTime { get; set; }
}

View File

@ -0,0 +1,30 @@
using System;
namespace HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
/// <summary>
/// 发现页类型分类
/// </summary>
public partial class T_Character_Type: MultiTenantEntity
{
/// <summary>
/// 类型id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 类型名称
/// </summary>
public string Name { get; set; } = null!;
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdateTime { get; set; }
}

View File

@ -0,0 +1,40 @@
using System;
namespace HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
/// <summary>
/// 存储用户和角色之间的亲密值
/// </summary>
public partial class T_Character_User_Intimacy: MultiTenantEntity
{
/// <summary>
/// 亲密度id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 人物Id
/// </summary>
public int CharacterId { get; set; }
/// <summary>
/// 用户Id
/// </summary>
public int UserId { get; set; }
/// <summary>
/// 亲密值
/// </summary>
public int IntimacyValue { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdateTime { get; set; }
}

View File

@ -0,0 +1,92 @@
using System;
namespace HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
/// <summary>
/// 聊天记录表
/// </summary>
public partial class T_Chat: MultiTenantEntity
{
/// <summary>
/// 聊天id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 聊天内容
/// </summary>
public int UserId { get; set; }
/// <summary>
/// 消息内容
/// </summary>
public string Content { get; set; } = null!;
/// <summary>
/// 发送时间
/// </summary>
public DateTime TimeStamp { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdateTime { get; set; }
/// <summary>
/// 输入token
/// </summary>
public int Input_tokens { get; set; }
/// <summary>
/// 输出token
/// </summary>
public int Output_tokens { get; set; }
/// <summary>
/// 人物表Id
/// </summary>
public int CharacterId { get; set; }
/// <summary>
/// user/assistant
/// </summary>
public string Role { get; set; } = null!;
public Guid SessionId { get; set; }
/// <summary>
/// 发送消息,天
/// </summary>
public long SendDateDay { get; set; }
/// <summary>
/// 发送消息时间戳
/// </summary>
public long SendMessageDay { get; set; }
/// <summary>
/// 0正常1重新生成2 删除
/// </summary>
public int Type { get; set; }
/// <summary>
/// 聊天返回的消息的类型
/// </summary>
public string? ClaudeType { get; set; }
/// <summary>
/// 聊天返回的Id
/// </summary>
public string? ClaudeId { get; set; }
/// <summary>
/// 人物模型,聊天返回的模型
/// </summary>
public string? ClaudeModel { get; set; }
}

View File

@ -0,0 +1,26 @@

namespace HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
/// <summary>
/// 图片表
/// </summary>
public partial class T_Image_Config: MultiTenantEntity
{
public int Id { get; set; }
/// <summary>
/// 图片Id
/// </summary>
public int ImageId { get; set; }
/// <summary>
/// 图片名称
/// </summary>
public string Name { get; set; } = null!;
/// <summary>
/// 图片地址
/// </summary>
public string Url { get; set; } = null!;
}

View File

@ -0,0 +1,49 @@
using System;
namespace HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
public partial class T_Model_Config: MultiTenantEntity
{
public int Id { get; set; }
/// <summary>
/// 模型名称
/// </summary>
public string ModelName { get; set; } = null!;
/// <summary>
/// 模型model
/// </summary>
public string Model { get; set; } = null!;
/// <summary>
/// 模型运行最大的max_tokens
/// </summary>
public int MaxTokens { get; set; }
/// <summary>
/// 模型key x-api-key
/// </summary>
public string ApiKey { get; set; } = null!;
/// <summary>
/// 模型请求地址
/// </summary>
public string Url { get; set; } = null!;
/// <summary>
/// 模型版本 anthropic-version
/// </summary>
public string AnthropicVersion { get; set; } = null!;
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; }
/// <summary>
/// 修改时间
/// </summary>
public DateTime UpdateTime { get; set; }
}

View File

@ -0,0 +1,70 @@
using System;
namespace HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
/// <summary>
/// 用户表
/// </summary>
public partial class T_User: MultiTenantEntity
{
/// <summary>
/// 用户Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 用户昵称
/// </summary>
public string NickName { get; set; } = null!;
/// <summary>
/// 用户姓名
/// </summary>
public string? UserName { get; set; }
/// <summary>
/// 绑定的手机号
/// </summary>
public string? PhoneNum { get; set; }
/// <summary>
/// 绑定的邮箱
/// </summary>
public string? Email { get; set; }
/// <summary>
/// 是否活跃
/// </summary>
public bool IsActive { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 最后一次登录方式,1手机号
/// </summary>
public int LastLoginTypeAt { get; set; }
/// <summary>
/// 最后一次登录时间
/// </summary>
public DateTime LastLoginAt { get; set; }
/// <summary>
/// 修改时间
/// </summary>
public DateTime UpdatedAt { get; set; }
/// <summary>
/// 首次注册方式
/// </summary>
public int RegisterType { get; set; }
/// <summary>
/// Ip地址
/// </summary>
public string? Ip { get; set; }
}

View File

@ -0,0 +1,49 @@
using System;
namespace HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
public partial class T_User_Char: MultiTenantEntity
{
public int Id { get; set; }
/// <summary>
/// 会话Id
/// </summary>
public Guid SessionId { get; set; }
/// <summary>
/// 会话名称
/// </summary>
public string SessionName { get; set; } = null!;
/// <summary>
/// 角色Id
/// </summary>
public int CharacterId { get; set; }
/// <summary>
/// 使用模型Id
/// </summary>
public int ModelConfigId { get; set; }
/// <summary>
/// 用户Id
/// </summary>
public int UserId { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateAt { get; set; }
/// <summary>
/// 修改时间
/// </summary>
public DateTime UpdateAt { get; set; }
/// <summary>
/// 是否删除
/// </summary>
public bool IsDelete { get; set; }
}

View File

@ -0,0 +1,50 @@
using System;
namespace HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
/// <summary>
/// 用户信息表
/// </summary>
public partial class T_User_Data: MultiTenantEntity
{
/// <summary>
/// 主键
/// </summary>
public int Id { get; set; }
/// <summary>
/// 用户Id
/// </summary>
public int UserId { get; set; }
/// <summary>
/// 用户昵称,需要和主表保持一致
/// </summary>
public string? NickName { get; set; }
/// <summary>
/// vip类型
/// </summary>
public int VipType { get; set; }
/// <summary>
/// 货币
/// </summary>
public int Currency { get; set; }
/// <summary>
/// 用户头像
/// </summary>
public string? UserIconUrl { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt_ { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdatedAt { get; set; }
}

View File

@ -0,0 +1,50 @@
using System;
namespace HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
/// <summary>
/// 手机号登录表
/// </summary>
public partial class T_User_Phone_Account: MultiTenantEntity
{
/// <summary>
/// 主键
/// </summary>
public int Id { get; set; }
/// <summary>
/// 用户Id
/// </summary>
public int UserId { get; set; }
/// <summary>
/// 手机号
/// </summary>
public string PhoneNum { get; set; } = null!;
/// <summary>
/// 验证码
/// </summary>
public string VerificationCode { get; set; } = null!;
/// <summary>
/// 最后一次登录时间
/// </summary>
public DateTime LastLoginAt { get; set; }
/// <summary>
/// 修改时间
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime UpdatedAt { get; set; }
/// <summary>
/// 用户昵称
/// </summary>
public string? NikeName { get; set; }
}

View File

@ -0,0 +1,50 @@
using System;
namespace HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
/// <summary>
/// 验证码表
/// </summary>
public partial class T_Verification_Code: MultiTenantEntity
{
/// <summary>
/// 主键
/// </summary>
public int Id { get; set; }
/// <summary>
/// 手机号或者邮箱
/// </summary>
public string Key { get; set; } = null!;
/// <summary>
/// 验证码
/// </summary>
public string Code { get; set; } = null!;
/// <summary>
/// 创建天
/// </summary>
public int CreateDay { get; set; }
/// <summary>
/// 过期时间
/// </summary>
public DateTime ExpireAt { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateAt { get; set; }
/// <summary>
/// 备注
/// </summary>
public string? Remarks { get; set; }
/// <summary>
/// 0手机1邮箱
/// </summary>
public int VerificationType { get; set; }
}

View File

@ -0,0 +1,5 @@

##在API项目里面执行使用连接字符串名称生成代码到model项目
```sh
dotnet ef dbcontext scaffold "Server=192.168.195.2;Database=MiaoYu;User Id=zpc;Password=zpc;TrustServerCertificate=true;" Microsoft.EntityFrameworkCore.SqlServer -o DbSqlServer/Db_MiaoYu/ --use-database-names --no-pluralize --force
```

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Model.Dto.Account
{
/// <summary>
/// 登录接口
/// </summary>
public class RequestLoginModel : RequestPhoneNumberModel
{
/// <summary>
/// 验证码
/// </summary>
public string? VerificationCode { get; set; }
/// <summary>
/// 登录类型0token登录 1手机号2邮箱
/// </summary>
public int LoginType { get; set; }
/// <summary>
/// token登录
/// </summary>
public string? Token { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Model.Dto.Account
{
/// <summary>
/// 发送验证码
/// </summary>
public class RequestPhoneNumberModel
{
/// <summary>
/// 手机号码
/// </summary>
public string? PhoneNumber { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show More