提交代码

This commit is contained in:
zpc 2024-07-09 04:21:51 +08:00
parent 9276e0e5d4
commit a62b93dc51
56 changed files with 2829 additions and 132 deletions

View File

@ -1,4 +1,4 @@
namespace HuanMeng.DotNetCore.Base
namespace HuanMeng.DotNetCore.Base
{
/// <summary>
/// 响应编码参考,实际的项目使用可以自行定义
@ -21,6 +21,10 @@
/// 用户验证失败
/// </summary>
Unauthorized = 401,
/// <summary>
/// 重复请求
/// </summary>
ManyRequests = 429,
/// <summary>
/// 正在处理中

View File

@ -9,7 +9,9 @@
<ItemGroup>
<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.Logging.Abstractions" Version="8.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

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

@ -18,14 +18,14 @@ namespace HuanMeng.DotNetCore.MultiTenant
/// <summary>
/// 租户信息
/// </summary>
public ITenantInfo TenantInfo { get; set; }
public ITenantInfo? TenantInfo { get; set; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="tenantInfo"></param>
public MultiTenantDbContext(ITenantInfo tenantInfo)
public MultiTenantDbContext(ITenantInfo? tenantInfo)
{
this.TenantInfo = tenantInfo;
}
@ -35,16 +35,16 @@ namespace HuanMeng.DotNetCore.MultiTenant
/// </summary>
/// <param name="tenantInfo"></param>
/// <param name="options"></param>
public MultiTenantDbContext(ITenantInfo tenantInfo, DbContextOptions options)
public MultiTenantDbContext(ITenantInfo? tenantInfo, DbContextOptions options)
: base(options)
{
if (tenantInfo == null)
{
tenantInfo=new TenantInfo()
tenantInfo = new TenantInfo()
{
TenantId = Guid.NewGuid(),
Identifier= "default",
Name="default"
Identifier = "default",
Name = "default"
};
}
this.TenantInfo = tenantInfo;

View File

@ -1,14 +1,32 @@
using AutoMapper;
using HuanMeng.DotNetCore.Base;
using HuanMeng.DotNetCore.JwtInfrastructure;
using HuanMeng.DotNetCore.JwtInfrastructure.Interface;
using HuanMeng.DotNetCore.MultiTenant;
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.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)
@ -31,6 +49,170 @@ namespace HuanMeng.MiaoYu.Code.Base
}
}
#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 (_mapper == 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))
{
}
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
}
}

View File

@ -1,7 +0,0 @@
namespace HuanMeng.MiaoYu.Code
{
public class Class1
{
}
}

View File

@ -1,4 +1,5 @@
using HuanMeng.DotNetCore.Base;
using HuanMeng.DotNetCore.MultiTenant;
using HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
using Microsoft.Extensions.DependencyInjection;
@ -17,25 +18,23 @@ namespace HuanMeng.MiaoYu.Code.DataAccess
public class DAO : DaoBase
{
//private IMultiTenantProvider _multiTenantProvider;
private TenantInfo _tenantInfo;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="serviceProvider"></param>
public DAO(IServiceProvider serviceProvider) : base(serviceProvider)
{
//this._multiTenantProvider = serviceProvider.GetRequiredService<IMultiTenantProvider>();
this._tenantInfo = serviceProvider.GetRequiredService<TenantInfo>();
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="serviceProvider"></param>
/// <param name="channelCode">渠道编号</param>
public DAO(IServiceProvider serviceProvider, string channelCode) : base(serviceProvider)
public DAO(IServiceProvider serviceProvider, TenantInfo tenantInfo) : base(serviceProvider)
{
//this._multiTenantProvider
this._tenantInfo = tenantInfo;
}
@ -52,15 +51,17 @@ namespace HuanMeng.MiaoYu.Code.DataAccess
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>

View File

@ -0,0 +1 @@
global using HuanMeng.MiaoYu.Code.Base;

View File

@ -8,8 +8,23 @@
<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>
@ -17,8 +32,4 @@
<ProjectReference Include="..\HuanMeng.MiaoYu.Model\HuanMeng.MiaoYu.Model.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="MultiTenantUtil\" />
</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,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,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Code.Other
{
/// <summary>
/// 手机号
/// </summary>
public class PhoneNumberValidator
{
// 正则表达式用于匹配手机号码。可以根据需要调整以适应不同的国家或地区。
private static readonly Regex phoneNumberRegex = new Regex(@"^(1[3-9]\d{9})$");
public static bool IsPhoneNumber(string input)
{
if (string.IsNullOrWhiteSpace(input))
{
return false;
}
return phoneNumberRegex.IsMatch(input);
}
}
}

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

@ -14,7 +14,8 @@ namespace HuanMeng.MiaoYu.Code.Users.UserAccount.Contract
/// <summary>
/// 登录
/// </summary>
/// <param name="loginParams">登录参数</param>
/// <returns></returns>
public abstract bool Login();
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

@ -1,26 +0,0 @@
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
{
/// <summary>
/// 手机号登录
/// </summary>
/// <param name="phone">手机号</param>
public class PhoneAccount(string phone) : IUserAccount
{
public Task SendPhone()
{
}
public override bool Login()
{
return false;
}
}
}

View File

@ -0,0 +1,184 @@
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,
};
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 < 60)
{
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

@ -1,9 +1,19 @@
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;
@ -18,9 +28,91 @@ namespace HuanMeng.MiaoYu.Code.Users
{
}
public BaseResponse<object> PhoneLogIn()
/// <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("Name",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

@ -47,7 +47,7 @@ namespace <#= NamespaceHint #>;
}
#>
/// <summary>
/// 妙语实体类
/// 妙语实体类
/// </summary>
public partial class <#= Options.ContextName #> : MultiTenantDbContext//DbContext
{
@ -321,12 +321,8 @@ public partial class <#= Options.ContextName #> : MultiTenantDbContext//DbContex
usings.AddRange(propertyFluentApiCalls.GetRequiredUsings());
#>
j.IndexerProperty<<#= code.Reference(property.ClrType) #>>(<#= code.Literal(property.Name) #>)<#= code.Fragment(propertyFluentApiCalls, indent: 7) #>;
//添加全局筛选器
if (this.TenantInfo != null)
{
entity.HasQueryFilter(it => it.TenantId == this.TenantInfo.TenantId);
}
j.IndexerProperty<<#= code.Reference(property.ClrType) #>>(<#= code.Literal(property.Name) #>)<#= code.Fragment(propertyFluentApiCalls, indent: 7) #>;
<#
}
#>
@ -334,7 +330,13 @@ public partial class <#= Options.ContextName #> : MultiTenantDbContext//DbContex
<#
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

View File

@ -1,14 +1,11 @@
using System;
using System;
using System.Collections.Generic;
using HuanMeng.DotNetCore.MultiTenant.Contract;
using Microsoft.EntityFrameworkCore;
namespace HuanMeng.MiaoYu.Model.DbSqlServer.Db_MiaoYu;
/// <summary>
///
/// 妙语实体类
/// </summary>
public partial class MiaoYuContext : MultiTenantDbContext//DbContext
{
@ -56,8 +53,12 @@ public partial class MiaoYuContext : MultiTenantDbContext//DbContext
/// </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)
//#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
=> optionsBuilder.UseSqlServer("Server=192.168.195.2;Database=MiaoYu;User Id=zpc;Password=zpc;TrustServerCertificate=true;");
protected override void OnModelCreating(ModelBuilder modelBuilder)
@ -75,6 +76,9 @@ public partial class MiaoYuContext : MultiTenantDbContext//DbContext
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("最后一次登录时间")
@ -95,6 +99,11 @@ public partial class MiaoYuContext : MultiTenantDbContext//DbContext
entity.Property(e => e.UserName)
.HasMaxLength(100)
.HasComment("用户姓名");
//添加全局筛选器
if (this.TenantInfo != null)
{
entity.HasQueryFilter(it => it.TenantId == this.TenantInfo.TenantId);
}
});
modelBuilder.Entity<T_User_Data>(entity =>
@ -112,12 +121,8 @@ public partial class MiaoYuContext : MultiTenantDbContext//DbContext
.HasColumnType("datetime")
.HasColumnName("CreatedAt ");
entity.Property(e => e.Currency).HasComment("货币");
entity.Property(e => e.IP)
.HasMaxLength(25)
.IsUnicode(false)
.HasComment("Ip");
entity.Property(e => e.NickName)
.HasMaxLength(50)
.HasMaxLength(100)
.HasComment("用户昵称,需要和主表保持一致");
entity.Property(e => e.UpdatedAt)
.HasComment("更新时间")
@ -126,6 +131,11 @@ public partial class MiaoYuContext : MultiTenantDbContext//DbContext
.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 =>
@ -142,7 +152,7 @@ public partial class MiaoYuContext : MultiTenantDbContext//DbContext
.HasComment("最后一次登录时间")
.HasColumnType("datetime");
entity.Property(e => e.NikeName)
.HasMaxLength(1)
.HasMaxLength(100)
.HasComment("用户昵称");
entity.Property(e => e.PhoneNum)
.HasMaxLength(50)
@ -157,6 +167,43 @@ public partial class MiaoYuContext : MultiTenantDbContext//DbContext
.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);

View File

@ -62,4 +62,9 @@ public partial class T_User: MultiTenantEntity
/// 首次注册方式
/// </summary>
public int RegisterType { get; set; }
/// <summary>
/// Ip地址
/// </summary>
public string? Ip { get; set; }
}

View File

@ -47,9 +47,4 @@ public partial class T_User_Data: MultiTenantEntity
/// 更新时间
/// </summary>
public DateTime UpdatedAt { get; set; }
/// <summary>
/// Ip
/// </summary>
public string? IP { 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,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; }
}
}

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 ResponseAccountLogIn
{
/// <summary>
/// token
/// </summary>
public string token { get; set; }
/// <summary>
/// 昵称
/// </summary>
public string NickName { get; set; }
/// <summary>
/// 用户id
/// </summary>
public int UserId { get; set; }
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Model.Dto
{
/// <summary>
/// 用户信息
/// </summary>
public class RequestUserInfo
{
/// <summary>
/// 昵称
/// </summary>
public string NickName { get; set; }
/// <summary>
/// 用户id
/// </summary>
public int UserId { get; set; }
}
}

View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.MiaoYu.Model.Dto
{
/// <summary>
/// 返回用户信息
/// </summary>
public class ResponseUserInfo
{
/// <summary>
/// 货币
/// </summary>
public int Currency { get; set; }
/// <summary>
/// 用户头像
/// </summary>
public string? UserIconUrl { get; set; }
/// <summary>
/// 用户Id
/// </summary>
public int UserId { get; set; }
/// <summary>
/// 用户昵称,需要和主表保持一致
/// </summary>
public string? NickName { get; set; }
}
}

View File

@ -0,0 +1,52 @@
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace HuanMeng.MiaoYu.WebApi.Base
{
/// <summary>
/// 自定义参数过滤器
/// </summary>
public class LowercaseParameterFilter : IParameterFilter
{
/// <summary>
///
/// </summary>
/// <param name="parameter"></param>
/// <param name="context"></param>
public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
{
// 将参数名称改为小写开头
parameter.Name = Char.ToLower(parameter.Name[0]) + parameter.Name.Substring(1);
}
}
/// <summary>
/// 自定义参数过滤器
/// </summary>
public class LowercaseRequestFilter : IRequestBodyFilter
{
public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext context)
{
if (requestBody.Content != null)
{
foreach (var mediaType in requestBody.Content.Values)
{
if (mediaType.Schema?.Properties != null)
{
var propertiesToRename = new Dictionary<string, OpenApiSchema>(mediaType.Schema.Properties);
// 清空旧的属性
mediaType.Schema.Properties.Clear();
foreach (var property in propertiesToRename)
{
// 创建新的属性,并将名称改为小写开头
var newPropertyName = Char.ToLower(property.Key[0]) + property.Key.Substring(1);
mediaType.Schema.Properties.Add(newPropertyName, property.Value);
}
}
}
}
}
}
}

View File

@ -0,0 +1,51 @@
using AutoMapper;
using HuanMeng.MiaoYu.Code.DataAccess;
using Microsoft.AspNetCore.Mvc;
namespace HuanMeng.MiaoYu.WebApi.Base
{
[ApiController]
public class MiaoYuControllerBase(IServiceProvider _serviceProvider) : ControllerBase
{
/// <summary>
/// 数据库使用
/// </summary>
//[FromServices]
public IServiceProvider ServiceProvider { get; set; } = _serviceProvider;
/// <summary>
/// HttpContextAccessor
/// </summary>
[FromServices]
public required IHttpContextAccessor HttpContextAccessor { get; set; }
/// <summary>
///
/// </summary>
[FromServices]
public required IMapper Mapper { get; set; }
/// <summary>
///
/// </summary>
private DAO? _dao;
/// <summary>
/// 数据库访问类
/// </summary>
public DAO Dao
{
get
{
if (_dao == null)
{
_dao = new DAO(ServiceProvider);
}
return _dao;
}
}
}
}

View File

@ -0,0 +1,72 @@
using Azure;
using HuanMeng.DotNetCore.Base;
using HuanMeng.MiaoYu.Code.Other;
using HuanMeng.MiaoYu.Code.Users;
using HuanMeng.MiaoYu.Model.Dto;
using HuanMeng.MiaoYu.Model.Dto.Account;
using HuanMeng.MiaoYu.WebApi.Base;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Numerics;
using System.Text.RegularExpressions;
namespace HuanMeng.MiaoYu.WebApi.Controllers
{
/// <summary>
/// 账号控制器
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
public class AccountController : MiaoYuControllerBase
{
public AccountController(IServiceProvider _serviceProvider) : base(_serviceProvider)
{
}
/// <summary>
/// 发送手机号验证码
/// </summary>
/// <param name="PhoneNumber">手机号</param>
/// <returns></returns>
[HttpPost]
public async Task<BaseResponse<bool>> SendPhoneNumber([FromBody] RequestPhoneNumberModel phone)
{
if (!PhoneNumberValidator.IsPhoneNumber(phone.PhoneNumber))
{
throw new ArgumentException("请输入正确的手机号");
}
UserBLL userBLL = new UserBLL(ServiceProvider);
return await userBLL.SendPhoneNumber(phone.PhoneNumber);
}
/// <summary>
/// 登录
/// </summary>
/// <param name="requestLoginModel"></param>
/// <returns></returns>
[HttpPost]
[AllowAnonymous]
public async Task<BaseResponse<ResponseAccountLogIn>> AccountLogIn([FromBody] RequestLoginModel requestLoginModel)
{
UserBLL userBLL = new UserBLL(ServiceProvider);
return await userBLL.AccountLogIn(requestLoginModel);
}
/// <summary>
///
/// </summary>
/// <returns></returns>
[Authorize]
[HttpGet]
public async Task<BaseResponse<ResponseUserInfo>> GetUserInfo()
{
UserBLL userBLL = new UserBLL(ServiceProvider);
return await userBLL.GetUserInfo();
}
}
}

View File

@ -1,33 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace HuanMeng.MiaoYu.WebApi.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}

View File

@ -4,9 +4,13 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.6" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>

View File

@ -1,23 +1,133 @@
using System.Diagnostics;
using System.Reflection;
using HuanMeng.MiaoYu.Code.MultiTenantUtil;
using HuanMeng.DotNetCore.MiddlewareExtend;
using HuanMeng.MiaoYu.WebApi.Base;
using Microsoft.OpenApi.Models;
using HuanMeng.MiaoYu.Code.TencentUtile;
using HuanMeng.MiaoYu.Code.Users.UserAccount.VerificationCodeManager;
using HuanMeng.MiaoYu.Code.JwtUtil;
using Microsoft.AspNetCore.Authentication.JwtBearer;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddHttpContextAccessor(); //添加httpContext注入访问
#region
var _myAllowSpecificOrigins = "_myAllowSpecificOrigins";
builder.Services.AddCors(options =>
options.AddPolicy(_myAllowSpecificOrigins,
builder =>
{
//builder.AllowAnyHeader().AllowAnyMethod().
//AllowAnyOrigin();
//builder.AllowAnyHeader()
// .AllowAnyMethod()
// .SetIsOriginAllowed((host) => true)//限制允许跨域请求的特定源
// .AllowCredentials();//允许跨域请求发送凭据,如 Cookies、HTTP 认证信息等。启用该配置可能会增加安全风险,因为浏览器会在请求头中包含凭据信息。因此,在设置 AllowCredentials 时,应该确保服务器端的安全性,并且仅允许受信任的源发送包含凭据的请求。
builder.AllowAnyHeader()
.AllowAnyMethod()
.AllowAnyOrigin();// 许来自任意源的跨域请求
}));
#endregion
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies().Where(it => it.FullName.Contains("HuanMeng") || it.FullName.Contains("XLib.")).ToList());
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSwaggerGen(c =>
{
var securityScheme = new OpenApiSecurityScheme
{
Name = "JWT 身份验证Authentication",
Description = "请输入登录后获取JWT的**token**",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer", //必须小写
BearerFormat = "JWT",
Reference = new OpenApiReference
{
Id = JwtBearerDefaults.AuthenticationScheme,
Type = ReferenceType.SecurityScheme
}
};
c.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme);
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{securityScheme, Array.Empty<string>()}
});
c.SwaggerDoc("v1", new OpenApiInfo { Title = "妙语", Version = "v1" });
foreach (var assemblies in AppDomain.CurrentDomain.GetAssemblies())
{
// 添加 XML 注释文件路径
var xmlFile = $"{assemblies.GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
if (File.Exists(xmlPath))
{
c.IncludeXmlComments(xmlPath);
}
}
c.ParameterFilter<LowercaseParameterFilter>();
c.RequestBodyFilter<LowercaseRequestFilter>();
});
builder.AddMultiTenantMiaoYu();
builder.AddTencent();
builder.AddMemoryVerificationCode();
builder.AddJwtConfig();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
//if (app.Environment.IsDevelopment())
//{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
app.UseSwagger();
app.UseSwaggerUI();
}
c.EnableDeepLinking();
c.DefaultModelsExpandDepth(3);
c.DefaultModelExpandDepth(3);
c.EnableFilter("true");
//c.RoutePrefix = "swagger";
//c.SwaggerEndpoint("/swagger/v1/swagger.json", "Your API V1");
});
//}
app.UseAuthorization();
//自定义初始化
//使用跨域
app.UseCors(_myAllowSpecificOrigins);
app.MapControllers();
//数据库中间件
app.UseMultiTenantMiaoYu();
//异常中间件
app.UseExecutionTimeMiddleware();
//请求耗时中间件
app.UseExceptionMiddleware();
#region
app.MapGet("/", () => "请求成功").WithName("默认请求");
var startDateTime = DateTime.Now;
var InformationalVersion = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
//Console.WriteLine($"version:{InformationalVersion}");
app.MapGet("/system", () =>
{
using Process currentProcess = Process.GetCurrentProcess();
// CPU使用率 (一般是一个0-100之间的值但实际是时间占比需要转换)
double cpuUsage = currentProcess.TotalProcessorTime.TotalMilliseconds / Environment.TickCount * 100;
// 已用内存 (字节)
long memoryUsage = currentProcess.WorkingSet64;
return new
{
msg = $"系统版本:{InformationalVersion},启动时间:{startDateTime.ToString("yyyy-MM-dd HH:mm:ss")},已安全运行时间:{DateTime.Now.Subtract(startDateTime).TotalMinutes.ToString("#.##")}分钟",
InformationalVersion,
startDateTime,
MemoryUsage = $"{memoryUsage / (1024.0 * 1024.0):F2}MB",
CPUUsage = $"{cpuUsage:F2}%"
};
}).WithName("获取系统数据");
#endregion
app.Run();

View File

@ -1,13 +0,0 @@
namespace HuanMeng.MiaoYu.WebApi
{
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}

View File

@ -1,9 +1,50 @@
{
"ConnectionStrings": {
"MiaoYu_SqlServer_Db": "Server=192.168.195.2;Database=MiaoYu;User Id=zpc;Password=zpc;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=True;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
//
"TencentCloud": {
"SecretId": "AKIDLbhdP0Vs57yd7QZWu8A2jFbno8JKBUp6",
"SecretKey": "MlP5tcUG6mdj7TwOpDWnZNFGIrJY8eH4",
"SMSCode": {
//
"ReqMethod": "POST",
//
"Timeout": 30,
//ID:
"SmsSdkAppId": "1400923253",
//
"SignName": "上海寰梦科技发展",
//
"TemplateId": "2209122"
}
},
"JwtTokenConfig": {
//
"secret": "XtrtwJIcxRHWInEMsCyUdwcRKLNHHAcQ",
//
"issuer": "HuanMeng",
//
"audience": "HuanMengApp",
//token,
"accessTokenExpiration": 10080,
//token.
"refreshTokenExpiration": 10100
},
"AllowedHosts": "*",
//
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://*:89"
}
}
}
}