committed by
GitHub
97 changed files with 1409 additions and 167 deletions
@ -0,0 +1,24 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.Auditing; |
|||
using Volo.Abp.Identity; |
|||
|
|||
namespace LINGYUN.Abp.Account |
|||
{ |
|||
public class PasswordResetDto |
|||
{ |
|||
[Required] |
|||
[Phone] |
|||
[StringLength(IdentityUserConsts.MaxPhoneNumberLength)] |
|||
public string PhoneNumber { get; set; } |
|||
|
|||
[Required] |
|||
[StringLength(IdentityUserConsts.MaxPasswordLength)] |
|||
[DataType(DataType.Password)] |
|||
[DisableAuditing] |
|||
public string NewPassword { get; set; } |
|||
|
|||
[Required] |
|||
[StringLength(6)] |
|||
public string VerifyCode { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
using LINGYUN.Abp.Account.Localization; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Localization; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Identity; |
|||
using IIdentityUserRepository = LINGYUN.Abp.Account.IIdentityUserRepository; |
|||
|
|||
namespace Microsoft.AspNetCore.Identity |
|||
{ |
|||
[Dependency(ServiceLifetime.Scoped, ReplaceServices = true)] |
|||
[ExposeServices(typeof(IUserValidator<IdentityUser>))] |
|||
public class PhoneNumberUserValidator : UserValidator<IdentityUser> |
|||
{ |
|||
private readonly IStringLocalizer<AccountResource> _stringLocalizer; |
|||
private readonly IIdentityUserRepository _identityUserRepository; |
|||
|
|||
public PhoneNumberUserValidator( |
|||
IStringLocalizer<AccountResource> stringLocalizer, |
|||
IIdentityUserRepository identityUserRepository) |
|||
{ |
|||
_stringLocalizer = stringLocalizer; |
|||
_identityUserRepository = identityUserRepository; |
|||
} |
|||
public override async Task<IdentityResult> ValidateAsync(UserManager<IdentityUser> manager, IdentityUser user) |
|||
{ |
|||
await ValidatePhoneNumberAsync(manager, user); |
|||
return await base.ValidateAsync(manager, user); |
|||
} |
|||
|
|||
protected virtual async Task ValidatePhoneNumberAsync(UserManager<IdentityUser> manager, IdentityUser user) |
|||
{ |
|||
var phoneNumber = await manager.GetPhoneNumberAsync(user); |
|||
if (phoneNumber.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new UserFriendlyException(_stringLocalizer["InvalidPhoneNumber"].Value, "InvalidPhoneNumber"); |
|||
} |
|||
|
|||
var phoneNumberHasRegisted = await _identityUserRepository.PhoneNumberHasRegistedAsync(phoneNumber); |
|||
if (phoneNumberHasRegisted) |
|||
{ |
|||
throw new UserFriendlyException(_stringLocalizer["DuplicatePhoneNumber", phoneNumber].Value, "DuplicatePhoneNumber"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Qiniu.Shared" Version="7.2.15" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.FileStorage\LINGYUN.Abp.FileStorage.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<Folder Include="Qiniu\Http\" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,10 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.FileStorage.Qiniu |
|||
{ |
|||
[DependsOn(typeof(AbpFileStorageModule))] |
|||
public class AbpQiniuFileStorageModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
namespace LINGYUN.Abp.FileStorage.Qiniu |
|||
{ |
|||
public class QiniuFileStorageOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Api授权码
|
|||
/// </summary>
|
|||
public string AccessKey { get; set; } |
|||
/// <summary>
|
|||
/// Api密钥
|
|||
/// </summary>
|
|||
public string SecretKey { get; set; } |
|||
/// <summary>
|
|||
/// 默认自动删除该文件天数
|
|||
/// 默认 0,不删除
|
|||
/// </summary>
|
|||
public int DeleteAfterDays { get; set; } |
|||
/// <summary>
|
|||
/// 上传成功后,七牛云向业务服务器发送 POST 请求的 URL。
|
|||
/// 必须是公网上可以正常进行 POST 请求并能响应 HTTP/1.1 200 OK 的有效 URL
|
|||
/// </summary>
|
|||
public string UploadCallbackUrl { get; set; } |
|||
/// <summary>
|
|||
/// 上传成功后,七牛云向业务服务器发送回调通知时的 Host 值。
|
|||
/// 与 callbackUrl 配合使用,仅当设置了 callbackUrl 时才有效。
|
|||
/// </summary>
|
|||
public string UploadCallbackHost { get; set; } |
|||
/// <summary>
|
|||
/// 上传成功后,七牛云向业务服务器发送回调通知 callbackBody 的 Content-Type。
|
|||
/// 默认为 application/x-www-form-urlencoded,也可设置为 application/json。
|
|||
/// </summary>
|
|||
public string UploadCallbackBodyType { get; set; } |
|||
/// <summary>
|
|||
/// 上传成功后,自定义七牛云最终返回給上传端(在指定 returnUrl 时是携带在跳转路径参数中)的数据。
|
|||
/// 支持魔法变量和自定义变量。returnBody 要求是合法的 JSON 文本。
|
|||
/// 例如 {"key": $(key), "hash": $(etag), "w": $(imageInfo.width), "h": $(imageInfo.height)}。
|
|||
/// </summary>
|
|||
public string UploadCallbackBody { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,111 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Qiniu.IO; |
|||
using Qiniu.IO.Model; |
|||
using Qiniu.RS; |
|||
using Qiniu.Util; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.FileStorage.Qiniu |
|||
{ |
|||
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)] |
|||
[ExposeServices(typeof(IFileStorageProvider), typeof(FileStorageProvider))] |
|||
public class QiniuFileStorageProvider : FileStorageProvider |
|||
{ |
|||
protected QiniuFileStorageOptions Options { get; } |
|||
public QiniuFileStorageProvider( |
|||
IFileStore store, |
|||
IOptions<QiniuFileStorageOptions> options) |
|||
: base(store) |
|||
{ |
|||
Options = options.Value; |
|||
} |
|||
|
|||
protected override async Task<FileInfo> DownloadFileAsync(FileInfo fileInfo, string saveLocalPath) |
|||
{ |
|||
Mac mac = new Mac(Options.AccessKey, Options.SecretKey); |
|||
|
|||
int expireInSeconds = 3600; |
|||
string accUrl = DownloadManager.CreateSignedUrl(mac, fileInfo.Url, expireInSeconds); |
|||
|
|||
var saveLocalFile = Path.Combine(saveLocalPath, fileInfo.Name); |
|||
var httpResult = await DownloadManager.DownloadAsync(accUrl, saveLocalFile); |
|||
if(httpResult.Code == 200) |
|||
{ |
|||
using (var fs = new FileStream(saveLocalFile, FileMode.Open, FileAccess.Read)) |
|||
{ |
|||
fileInfo.Data = new byte[fs.Length]; |
|||
|
|||
await fs.ReadAsync(fileInfo.Data, 0, fileInfo.Data.Length).ConfigureAwait(false); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// TODO: 处理响应代码
|
|||
|
|||
Console.WriteLine(httpResult.Code); |
|||
} |
|||
|
|||
return fileInfo; |
|||
} |
|||
|
|||
protected override async Task RemoveFileAsync(FileInfo fileInfo, CancellationToken cancellationToken = default) |
|||
{ |
|||
Mac mac = new Mac(Options.AccessKey, Options.SecretKey); |
|||
|
|||
var bucket = fileInfo.Directory + ":" + fileInfo.Name; |
|||
var backetManager = new BucketManager(mac); |
|||
await backetManager.DeleteAsync(bucket, fileInfo.Name); |
|||
|
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
protected override async Task UploadFileAsync(FileInfo fileInfo, int? expireIn = null, CancellationToken cancellationToken = default) |
|||
{ |
|||
Mac mac = new Mac(Options.AccessKey, Options.SecretKey); |
|||
|
|||
PutPolicy putPolicy = new PutPolicy |
|||
{ |
|||
Scope = fileInfo.Directory + ":" + fileInfo.Name, |
|||
CallbackBody = Options.UploadCallbackBody, |
|||
CallbackBodyType = Options.UploadCallbackBodyType, |
|||
CallbackHost = Options.UploadCallbackHost, |
|||
CallbackUrl = Options.UploadCallbackUrl |
|||
}; |
|||
if (expireIn.HasValue) |
|||
{ |
|||
putPolicy.SetExpires(expireIn.Value); |
|||
} |
|||
if (Options.DeleteAfterDays > 0) |
|||
{ |
|||
putPolicy.DeleteAfterDays = Options.DeleteAfterDays; |
|||
} |
|||
|
|||
|
|||
string jstr = putPolicy.ToJsonString(); |
|||
string token = Auth.CreateUploadToken(mac, jstr); |
|||
|
|||
UploadProgressHandler handler = (uploadByte, totalByte) => |
|||
{ |
|||
OnFileUploadProgressChanged(uploadByte, totalByte); |
|||
}; |
|||
|
|||
// 带进度的上传
|
|||
ResumableUploader uploader = new ResumableUploader(); |
|||
var httpResult = await uploader.UploadDataAsync(fileInfo.Data, fileInfo.Name, token, handler); |
|||
|
|||
// 普通上传
|
|||
//FormUploader fu = new FormUploader();
|
|||
//var httpResult = await fu.UploadDataAsync(fileInfo.Data, fileInfo.Name, token);
|
|||
|
|||
// TODO: 处理响应代码
|
|||
|
|||
Console.WriteLine(httpResult.Code); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Core" Version="2.9.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.FileStorage |
|||
{ |
|||
public class AbpFileStorageModule : AbpModule |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.FileStorage |
|||
{ |
|||
public class FileDownloadCompletedEventArges : EventArgs |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace LINGYUN.Abp.FileStorage |
|||
{ |
|||
public class FileDownloadProgressEventArges : EventArgs |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.FileStorage |
|||
{ |
|||
/// <summary>
|
|||
/// 文件信息
|
|||
/// </summary>
|
|||
public class FileInfo |
|||
{ |
|||
/// <summary>
|
|||
/// 名称
|
|||
/// </summary>
|
|||
public string Name { get; set; } |
|||
/// <summary>
|
|||
/// 大小
|
|||
/// </summary>
|
|||
public long Size { get; set; } |
|||
/// <summary>
|
|||
/// 文件路径
|
|||
/// </summary>
|
|||
public string Directory { get; set; } |
|||
/// <summary>
|
|||
/// 文件扩展名
|
|||
/// </summary>
|
|||
public string Extension { get; set; } |
|||
/// <summary>
|
|||
/// 文件哈希码,用于唯一标识
|
|||
/// </summary>
|
|||
public string Hash { get; set; } |
|||
/// <summary>
|
|||
/// 文件链接
|
|||
/// </summary>
|
|||
public string Url { get; set; } |
|||
/// <summary>
|
|||
/// 文件数据
|
|||
/// </summary>
|
|||
public byte[] Data { get; set; } |
|||
/// <summary>
|
|||
/// 媒体类型
|
|||
/// </summary>
|
|||
public MediaType MediaType { get; set; } |
|||
/// <summary>
|
|||
/// 创建时间
|
|||
/// </summary>
|
|||
public DateTime CreationTime { get; set; } |
|||
/// <summary>
|
|||
/// 创建人
|
|||
/// </summary>
|
|||
public Guid? CreatorId { get; set; } |
|||
/// <summary>
|
|||
/// 上次变更时间
|
|||
/// </summary>
|
|||
public DateTime? LastModificationTime { get; set; } |
|||
/// <summary>
|
|||
/// 上次变更人
|
|||
/// </summary>
|
|||
public Guid? LastModifierId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
using System; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.FileStorage |
|||
{ |
|||
public abstract class FileStorageProvider : IFileStorageProvider |
|||
{ |
|||
public event EventHandler<FileDownloadProgressEventArges> FileDownloadProgressChanged; |
|||
public event EventHandler<FileDownloadCompletedEventArges> FileDownloadCompleted; |
|||
public event EventHandler<FileUploadProgressEventArges> FileUploadProgressChanged; |
|||
public event EventHandler<FileUploadCompletedEventArges> FileUploadCompleted; |
|||
|
|||
protected IFileStore Store { get; } |
|||
|
|||
public FileStorageProvider(IFileStore store) |
|||
{ |
|||
Store = store; |
|||
} |
|||
|
|||
public async Task DeleteFileAsync(string hash, CancellationToken cancellationToken = default) |
|||
{ |
|||
// 获取文件信息
|
|||
var file = await Store.GetFileAsync(hash); |
|||
// 删除文件
|
|||
await RemoveFileAsync(file, cancellationToken); |
|||
// 删除文件信息
|
|||
await Store.DeleteFileAsync(hash, cancellationToken); |
|||
} |
|||
|
|||
public async Task<FileInfo> GetFileAsync(string hash, string saveLocalPath) |
|||
{ |
|||
// 获取文件信息
|
|||
var file = await Store.GetFileAsync(hash); |
|||
// 下载文件
|
|||
return await DownloadFileAsync(file, saveLocalPath); |
|||
} |
|||
|
|||
public async Task StorageAsync(FileInfo fileInfo, int? expireIn = null, CancellationToken cancellationToken = default) |
|||
{ |
|||
// step1 上传文件
|
|||
await UploadFileAsync(fileInfo, expireIn, cancellationToken); |
|||
// step2 保存文件信息
|
|||
await Store.StorageAsync(fileInfo, expireIn, cancellationToken); |
|||
} |
|||
|
|||
protected abstract Task UploadFileAsync(FileInfo fileInfo, int? expireIn = null, CancellationToken cancellationToken = default); |
|||
|
|||
protected abstract Task<FileInfo> DownloadFileAsync(FileInfo fileInfo, string saveLocalPath); |
|||
|
|||
protected abstract Task RemoveFileAsync(FileInfo fileInfo, CancellationToken cancellationToken = default); |
|||
|
|||
protected virtual void OnFileUploadProgressChanged(long sent, long total) |
|||
{ |
|||
FileUploadProgressChanged?.Invoke(this, new FileUploadProgressEventArges(sent, total)); |
|||
} |
|||
|
|||
protected virtual void OnFileUploadConpleted() |
|||
{ |
|||
FileUploadCompleted?.Invoke(this, new FileUploadCompletedEventArges()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.FileStorage |
|||
{ |
|||
public class FileUploadCompletedEventArges : EventArgs |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.FileStorage |
|||
{ |
|||
public class FileUploadProgressEventArges : EventArgs |
|||
{ |
|||
/// <summary>
|
|||
/// 上传数据大小
|
|||
/// </summary>
|
|||
public long BytesSent { get; } |
|||
/// <summary>
|
|||
/// 总数据大小
|
|||
/// </summary>
|
|||
public long TotalBytesSent { get; } |
|||
public FileUploadProgressEventArges(long sent, long total) |
|||
{ |
|||
BytesSent = sent; |
|||
TotalBytesSent = total; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
using System; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.FileStorage |
|||
{ |
|||
/// <summary>
|
|||
/// 文件存储提供者
|
|||
/// </summary>
|
|||
public interface IFileStorageProvider |
|||
{ |
|||
event EventHandler<FileDownloadProgressEventArges> FileDownloadProgressChanged; |
|||
event EventHandler<FileDownloadCompletedEventArges> FileDownloadCompleted; |
|||
|
|||
event EventHandler<FileUploadProgressEventArges> FileUploadProgressChanged; |
|||
event EventHandler<FileUploadCompletedEventArges> FileUploadCompleted; |
|||
|
|||
/// <summary>
|
|||
/// 存储文件
|
|||
/// </summary>
|
|||
/// <param name="fileInfo">文件信息</param>
|
|||
/// <param name="expireIn">过期时间,单位(s)</param>
|
|||
/// <param name="cancellationToken"></param>
|
|||
/// <returns></returns>
|
|||
Task StorageAsync(FileInfo fileInfo, int? expireIn = null, CancellationToken cancellationToken = default); |
|||
/// <summary>
|
|||
/// 获取文件
|
|||
/// </summary>
|
|||
/// <param name="hash">文件唯一标识</param>
|
|||
/// <param name="saveLocalPath">保存到本地路径</param>
|
|||
/// <returns></returns>
|
|||
Task<FileInfo> GetFileAsync(string hash, string saveLocalPath); |
|||
/// <summary>
|
|||
/// 删除文件
|
|||
/// </summary>
|
|||
/// <param name="hash">文件唯一标识</param>
|
|||
/// <param name="cancellationToken"></param>
|
|||
/// <returns></returns>
|
|||
Task DeleteFileAsync(string hash, CancellationToken cancellationToken = default); |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.FileStorage |
|||
{ |
|||
/// <summary>
|
|||
/// 文件存储接口
|
|||
/// </summary>
|
|||
public interface IFileStore |
|||
{ |
|||
/// <summary>
|
|||
/// 存储文件
|
|||
/// </summary>
|
|||
/// <param name="fileInfo">文件信息</param>
|
|||
/// <param name="expireIn">过期时间,单位(s)</param>
|
|||
/// <param name="cancellationToken"></param>
|
|||
/// <returns></returns>
|
|||
Task StorageAsync(FileInfo fileInfo, int? expireIn = null, CancellationToken cancellationToken = default); |
|||
/// <summary>
|
|||
/// 获取文件
|
|||
/// </summary>
|
|||
/// <param name="hash">文件唯一标识</param>
|
|||
/// <returns></returns>
|
|||
Task<FileInfo> GetFileAsync(string hash); |
|||
/// <summary>
|
|||
/// 文件是否存在
|
|||
/// </summary>
|
|||
/// <param name="hash">文件唯一标识</param>
|
|||
/// <returns></returns>
|
|||
Task<bool> FileHasExistsAsync(string hash); |
|||
/// <summary>
|
|||
/// 删除文件
|
|||
/// </summary>
|
|||
/// <param name="hash">文件唯一标识</param>
|
|||
/// <param name="cancellationToken"></param>
|
|||
/// <returns></returns>
|
|||
Task DeleteFileAsync(string hash, CancellationToken cancellationToken = default); |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
namespace LINGYUN.Abp.FileStorage |
|||
{ |
|||
/// <summary>
|
|||
/// 媒体类型
|
|||
/// </summary>
|
|||
public enum MediaType |
|||
{ |
|||
/// <summary>
|
|||
/// 文档
|
|||
/// </summary>
|
|||
Document = 0, |
|||
/// <summary>
|
|||
/// 图像
|
|||
/// </summary>
|
|||
Image = 2, |
|||
/// <summary>
|
|||
/// 影像
|
|||
/// </summary>
|
|||
Video = 3, |
|||
/// <summary>
|
|||
/// 音乐
|
|||
/// </summary>
|
|||
Music = 4 |
|||
} |
|||
} |
|||
@ -1,12 +1,18 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> |
|||
<Version>2.9.0</Version> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> |
|||
<OutputPath>D:\LocalNuget</OutputPath> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Auditing" Version="2.8.0" /> |
|||
<PackageReference Include="Volo.Abp.Auditing" Version="2.9.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
|
|||
@ -0,0 +1,8 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.IdentityServer.WeChatValidator |
|||
{ |
|||
public class Class1 |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netcoreapp3.1</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.IdentityServer.Domain" Version="2.9.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,35 @@ |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
using Volo.Abp.Identity.EntityFrameworkCore; |
|||
using Volo.Abp.IdentityServer.EntityFrameworkCore; |
|||
using Volo.Abp.PermissionManagement.EntityFrameworkCore; |
|||
using Volo.Abp.SettingManagement.EntityFrameworkCore; |
|||
using Volo.Abp.TenantManagement.EntityFrameworkCore; |
|||
|
|||
namespace LINGYUN.Platform.EntityFrameworkCore |
|||
{ |
|||
public class PlatformHttpApiHostMigrationsDbContext : AbpDbContext<PlatformHttpApiHostMigrationsDbContext> |
|||
{ |
|||
public PlatformHttpApiHostMigrationsDbContext(DbContextOptions<PlatformHttpApiHostMigrationsDbContext> options) |
|||
: base(options) |
|||
{ |
|||
|
|||
} |
|||
|
|||
protected override void OnModelCreating(ModelBuilder modelBuilder) |
|||
{ |
|||
base.OnModelCreating(modelBuilder); |
|||
|
|||
modelBuilder.ConfigureIdentity(); |
|||
modelBuilder.ConfigureIdentityServer(options => |
|||
{ |
|||
options.TablePrefix = "IdentityServer"; |
|||
options.Schema = null; |
|||
options.DatabaseProvider = EfCoreDatabaseProvider.MySql; |
|||
}); |
|||
modelBuilder.ConfigureTenantManagement(); |
|||
modelBuilder.ConfigureSettingManagement(); |
|||
modelBuilder.ConfigurePermissionManagement(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
using System.IO; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore.Design; |
|||
using Microsoft.Extensions.Configuration; |
|||
|
|||
namespace LINGYUN.Platform.EntityFrameworkCore |
|||
{ |
|||
public class PlatformHttpApiHostMigrationsDbContextFactory : IDesignTimeDbContextFactory<PlatformHttpApiHostMigrationsDbContext> |
|||
{ |
|||
public PlatformHttpApiHostMigrationsDbContext CreateDbContext(string[] args) |
|||
{ |
|||
var configuration = BuildConfiguration(); |
|||
|
|||
var builder = new DbContextOptionsBuilder<PlatformHttpApiHostMigrationsDbContext>() |
|||
.UseMySql(configuration.GetConnectionString("Default")); |
|||
|
|||
return new PlatformHttpApiHostMigrationsDbContext(builder.Options); |
|||
} |
|||
|
|||
private static IConfigurationRoot BuildConfiguration() |
|||
{ |
|||
var builder = new ConfigurationBuilder() |
|||
.SetBasePath(Directory.GetCurrentDirectory()) |
|||
.AddJsonFile("appsettings.Development.json", optional: false); |
|||
|
|||
return builder.Build(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Platform.EventBus.Handlers |
|||
{ |
|||
public class TenantConnectionStringCreateEventHandler |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,339 @@ |
|||
<template> |
|||
<div class="login-container"> |
|||
<el-form |
|||
ref="formResetPassword" |
|||
:model="resetPasswordForm" |
|||
:rules="resetPasswordFormRules" |
|||
label-position="left" |
|||
label-width="0px" |
|||
class="demo-ruleForm login-page" |
|||
> |
|||
<div class="title-container"> |
|||
<h3 class="title"> |
|||
{{ $t('login.resetpassword') }} |
|||
</h3> |
|||
<lang-select class="set-language" /> |
|||
</div> |
|||
<el-form-item label-width="0px"> |
|||
<tenant-box |
|||
v-if="isMultiEnabled" |
|||
v-model="resetPasswordForm.tenantName" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item |
|||
prop="phoneNumber" |
|||
> |
|||
<el-input |
|||
v-model="resetPasswordForm.phoneNumber" |
|||
prefix-icon="el-icon-mobile-phone" |
|||
type="text" |
|||
maxlength="11" |
|||
auto-complete="off" |
|||
:placeholder="$t('global.pleaseInputBy', {key: $t('login.phoneNumber')})" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item |
|||
prop="verifyCode" |
|||
> |
|||
<el-row> |
|||
<el-col :span="16"> |
|||
<el-input |
|||
v-model="resetPasswordForm.verifyCode" |
|||
auto-complete="off" |
|||
:placeholder="$t('global.pleaseInputBy', {key: $t('login.phoneVerifyCode')})" |
|||
prefix-icon="el-icon-key" |
|||
style="margin:-right: 10px;" |
|||
/> |
|||
</el-col> |
|||
<el-col :span="8"> |
|||
<el-button |
|||
ref="sendButton" |
|||
style="margin-left: 10px;width: 132px;" |
|||
:disabled="sending" |
|||
@click="handleSendPhoneVerifyCode" |
|||
> |
|||
{{ sendButtonName }} |
|||
</el-button> |
|||
</el-col> |
|||
</el-row> |
|||
</el-form-item> |
|||
<el-form-item |
|||
prop="newPassword" |
|||
> |
|||
<el-input |
|||
:key="passwordType" |
|||
ref="newPassword" |
|||
v-model="resetPasswordForm.newPassword" |
|||
prefix-icon="el-icon-lock" |
|||
:type="passwordType" |
|||
:placeholder="$t('global.pleaseInputBy', {key: $t('login.password')})" |
|||
name="newPassword" |
|||
tabindex="2" |
|||
@keyup.enter.native="handleResetPassword" |
|||
/> |
|||
<span |
|||
class="show-pwd" |
|||
@click="showPwd" |
|||
> |
|||
<svg-icon :name="passwordType === 'password' ? 'eye-off' : 'eye-on'" /> |
|||
</span> |
|||
</el-form-item> |
|||
|
|||
<el-form-item |
|||
label-width="100px" |
|||
:label="$t('login.existsAccount')" |
|||
> |
|||
<el-link |
|||
type="success" |
|||
@click="handleRedirectLogin" |
|||
> |
|||
{{ $t('login.logIn') }} |
|||
</el-link> |
|||
</el-form-item> |
|||
|
|||
<el-form-item style="width:100%;"> |
|||
<el-button |
|||
type="primary" |
|||
style="width:100%;" |
|||
:loading="reseting" |
|||
@click="handleResetPassword" |
|||
> |
|||
{{ $t('login.resetpassword') }} |
|||
</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { Input } from 'element-ui' |
|||
import { Route } from 'vue-router' |
|||
import { Dictionary } from 'vue-router/types/router' |
|||
import TenantBox from '@/components/TenantBox/index.vue' |
|||
import LangSelect from '@/components/LangSelect/index.vue' |
|||
import { Component, Vue, Watch } from 'vue-property-decorator' |
|||
import UserService, { PhoneVerify, VerifyType, UserResetPasswordData } from '@/api/users' |
|||
import { AbpConfigurationModule } from '@/store/modules/abp' |
|||
|
|||
@Component({ |
|||
name: 'Register', |
|||
components: { |
|||
LangSelect, |
|||
TenantBox |
|||
} |
|||
}) |
|||
export default class extends Vue { |
|||
private passwordType = 'password' |
|||
private redirect?: string |
|||
|
|||
private sendTimer: any |
|||
private sending = false |
|||
private sendButtonName = this.l('login.sendVerifyCode') |
|||
private reseting = false |
|||
private resetPasswordForm = { |
|||
tenantName: '', |
|||
newPassword: '', |
|||
phoneNumber: '', |
|||
verifyCode: '' |
|||
} |
|||
|
|||
get isMultiEnabled() { |
|||
return AbpConfigurationModule.configuration?.multiTenancy?.isEnabled |
|||
} |
|||
|
|||
private validatePhoneNumberValue = (rule: any, value: string, callback: any) => { |
|||
const phoneReg = /^1[34578]\d{9}$/ |
|||
if (!value || !phoneReg.test(value)) { |
|||
callback(new Error(this.l('global.pleaseInputBy', { key: this.l('global.correctPhoneNumber') }))) |
|||
} else { |
|||
callback() |
|||
} |
|||
} |
|||
|
|||
private resetPasswordFormRules = { |
|||
newPassword: [ |
|||
{ |
|||
required: true, message: this.l('global.pleaseInputBy', { key: this.l('login.password') }), trigger: 'blur' |
|||
} |
|||
], |
|||
phoneNumber: [ |
|||
{ |
|||
required: true, validator: this.validatePhoneNumberValue, trigger: 'blur' |
|||
} |
|||
], |
|||
verifyCode: [ |
|||
{ |
|||
required: true, message: this.l('global.pleaseInputBy', { key: this.l('login.phoneVerifyCode') }), trigger: 'blur' |
|||
} |
|||
] |
|||
} |
|||
|
|||
destroyed() { |
|||
if (this.sendTimer) { |
|||
clearInterval(this.sendTimer) |
|||
} |
|||
} |
|||
|
|||
@Watch('$route', { immediate: true }) |
|||
private onRouteChange(route: Route) { |
|||
// TODO: remove the "as Dictionary<string>" hack after v4 release for vue-router |
|||
// See https://github.com/vuejs/vue-router/pull/2050 for details |
|||
const query = route.query as Dictionary<string> |
|||
if (query) { |
|||
this.redirect = query.redirect |
|||
} |
|||
} |
|||
|
|||
private showPwd() { |
|||
if (this.passwordType === 'password') { |
|||
this.passwordType = '' |
|||
} else { |
|||
this.passwordType = 'password' |
|||
} |
|||
this.$nextTick(() => { |
|||
(this.$refs.newPassword as Input).focus() |
|||
}) |
|||
} |
|||
|
|||
private handleRedirectLogin() { |
|||
this.$router.replace('login') |
|||
} |
|||
|
|||
private handleResetPassword() { |
|||
const frmResetPassword = this.$refs.formResetPassword as any |
|||
frmResetPassword.validate(async(valid: boolean) => { |
|||
if (valid) { |
|||
this.reseting = true |
|||
try { |
|||
const userReserPassword = new UserResetPasswordData() |
|||
userReserPassword.phoneNumber = this.resetPasswordForm.phoneNumber |
|||
userReserPassword.verifyCode = this.resetPasswordForm.verifyCode |
|||
userReserPassword.newPassword = this.resetPasswordForm.newPassword |
|||
UserService.resetPassword(userReserPassword).then(() => { |
|||
this.handleRedirectLogin() |
|||
}).finally(() => { |
|||
this.resetLoginButton() |
|||
}) |
|||
} catch { |
|||
this.resetLoginButton() |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
|
|||
private handleSendPhoneVerifyCode() { |
|||
const frmResetPassword = this.$refs.formResetPassword as any |
|||
frmResetPassword.validateField('phoneNumber', (errorMsg: string) => { |
|||
if (!errorMsg) { |
|||
this.sending = true |
|||
const phoneVerify = new PhoneVerify() |
|||
phoneVerify.phoneNumber = this.resetPasswordForm.phoneNumber |
|||
phoneVerify.verifyType = VerifyType.ResetPassword |
|||
UserService.sendPhoneVerifyCode(phoneVerify).then(() => { |
|||
let interValTime = 60 |
|||
const sendingName = this.l('login.afterSendVerifyCode') |
|||
const sendedName = this.l('login.sendVerifyCode') |
|||
this.sendTimer = setInterval(() => { |
|||
this.sendButtonName = interValTime + sendingName |
|||
--interValTime |
|||
if (interValTime < 0) { |
|||
this.sendButtonName = sendedName |
|||
this.sending = false |
|||
clearInterval(this.sendTimer) |
|||
} |
|||
}, 1000) |
|||
}).catch(() => { |
|||
this.sending = false |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
private l(name: string, values?: any[] | { [key: string]: any }) { |
|||
return this.$t(name, values).toString() |
|||
} |
|||
|
|||
private resetLoginButton() { |
|||
setTimeout(() => { |
|||
this.reseting = false |
|||
}, 0.5 * 1000) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.login-container { |
|||
width: 100%; |
|||
height: 100%; |
|||
overflow: hidden; |
|||
background-color: $loginBg; |
|||
|
|||
.svg-container { |
|||
padding: 6px 5px 6px 15px; |
|||
color: $darkGray; |
|||
vertical-align: middle; |
|||
width: 30px; |
|||
display: inline-block; |
|||
} |
|||
|
|||
.title-container { |
|||
position: relative; |
|||
|
|||
.title { |
|||
font-size: 26px; |
|||
margin: 0px auto 20px auto; |
|||
text-align: center; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.tips { |
|||
font-size: 14px; |
|||
color: #fff; |
|||
margin-bottom: 10px; |
|||
|
|||
span { |
|||
&:first-of-type { |
|||
margin-right: 16px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.set-language { |
|||
position: absolute; |
|||
top: 3px; |
|||
font-size: 18px; |
|||
right: 0px; |
|||
cursor: pointer; |
|||
} |
|||
} |
|||
|
|||
.show-pwd { |
|||
position: absolute; |
|||
right: 10px; |
|||
font-size: 16px; |
|||
color: $darkGray; |
|||
cursor: pointer; |
|||
user-select: none; |
|||
} |
|||
} |
|||
|
|||
.login-page { |
|||
-webkit-border-radius: 5px; |
|||
border-radius: 5px; |
|||
margin: 130px auto; |
|||
width: 500px; |
|||
padding: 35px 35px 15px; |
|||
border: 1px solid #8c9494; |
|||
box-shadow: 0 0 25px #454646; |
|||
background-color:rgb(247, 255, 255); |
|||
|
|||
.loginTab.el-tabs__item { |
|||
width: 180px; |
|||
} |
|||
} |
|||
|
|||
label.el-checkbox.rememberme { |
|||
margin: 0px 0px 15px; |
|||
text-align: left; |
|||
} |
|||
</style> |
|||
Loading…
Reference in new issue