提交代码
This commit is contained in:
parent
9276e0e5d4
commit
a62b93dc51
|
|
@ -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>
|
||||
/// 正在处理中
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
namespace HuanMeng.MiaoYu.Code
|
||||
{
|
||||
public class Class1
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
1
src/0-core/HuanMeng.MiaoYu.Code/GlobalUsings.cs
Normal file
1
src/0-core/HuanMeng.MiaoYu.Code/GlobalUsings.cs
Normal file
|
|
@ -0,0 +1 @@
|
|||
global using HuanMeng.MiaoYu.Code.Base;
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
167
src/0-core/HuanMeng.MiaoYu.Code/JwtUtil/JwtManager.cs
Normal file
167
src/0-core/HuanMeng.MiaoYu.Code/JwtUtil/JwtManager.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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>();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
// 必要步骤:
|
||||
// 实例化一个认证对象,入参需要传入腾讯云账户密钥对 SecretId,SecretKey。
|
||||
// 为了保护密钥安全,建议将密钥设置在环境变量中或者配置文件中。
|
||||
// 硬编码密钥到代码中有可能随代码泄露而暴露,有安全隐患,并不推荐。
|
||||
// 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。
|
||||
// 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, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号*/
|
||||
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; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -62,4 +62,9 @@ public partial class T_User: MultiTenantEntity
|
|||
/// 首次注册方式
|
||||
/// </summary>
|
||||
public int RegisterType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ip地址
|
||||
/// </summary>
|
||||
public string? Ip { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
24
src/0-core/HuanMeng.MiaoYu.Model/Dto/RequestUserInfo.cs
Normal file
24
src/0-core/HuanMeng.MiaoYu.Model/Dto/RequestUserInfo.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
35
src/0-core/HuanMeng.MiaoYu.Model/Dto/ResponseUserInfo.cs
Normal file
35
src/0-core/HuanMeng.MiaoYu.Model/Dto/ResponseUserInfo.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user