コード例 #1
0
        protected virtual async Task CheckFile(FileDto input)
        {
            if (input.Bytes.IsNullOrEmpty())
            {
                throw new AbpValidationException("Bytes can not be null or empty!",
                                                 new List <ValidationResult>
                {
                    new ValidationResult("Bytes can not be null or empty!", new[] { "Bytes" })
                });
            }

            var allowedMaxFileSize = await SettingProvider.GetAsync <int>(FileManagementSettings.AllowedMaxFileSize);//kb

            var allowedUploadFormats = (await SettingProvider.GetOrNullAsync(FileManagementSettings.AllowedUploadFormats))
                                       ?.Split(",", StringSplitOptions.RemoveEmptyEntries);

            if (input.Bytes.Length > allowedMaxFileSize * 1024)
            {
                throw new UserFriendlyException(L["FileManagement.ExceedsTheMaximumSize", allowedMaxFileSize]);
            }

            if (allowedUploadFormats == null || !allowedUploadFormats.Contains(Path.GetExtension(input.FileName)))
            {
                throw new UserFriendlyException(L["FileManagement.NotValidFormat"]);
            }
        }
コード例 #2
0
        public virtual async Task SendChangePhoneNumberCodeAsync(SendChangePhoneNumberCodeDto input)
        {
            var securityTokenCacheKey  = SmsSecurityTokenCacheItem.CalculateCacheKey(input.NewPhoneNumber, "SmsChangePhoneNumber");
            var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey);

            var interval = await SettingProvider.GetAsync(Settings.IdentitySettingNames.User.SmsRepetInterval, 1);

            if (securityTokenCacheItem != null)
            {
                throw new UserFriendlyException(L["SendRepeatPhoneVerifyCode", interval]);
            }

            // 是否已有用户使用手机号绑定
            if (await UserRepository.IsPhoneNumberConfirmedAsync(input.NewPhoneNumber))
            {
                throw new BusinessException(IdentityErrorCodes.DuplicatePhoneNumber);
            }
            var user = await UserManager.GetByIdAsync(CurrentUser.GetId());

            var template = await SettingProvider.GetOrNullAsync(Settings.IdentitySettingNames.User.SmsPhoneNumberConfirmed);

            var token = await UserManager.GenerateChangePhoneNumberTokenAsync(user, input.NewPhoneNumber);

            // 发送验证码
            await SecurityCodeSender.SendPhoneConfirmedCodeAsync(input.NewPhoneNumber, token, template);

            securityTokenCacheItem = new SmsSecurityTokenCacheItem(token, user.ConcurrencyStamp);
            await SecurityTokenCache
            .SetAsync(securityTokenCacheKey, securityTokenCacheItem,
                      new DistributedCacheEntryOptions
            {
                AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(interval)
            });
        }
        public virtual async Task SendPhoneSigninCodeAsync(SendPhoneSigninCodeDto input)
        {
            var securityTokenCacheKey  = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode");
            var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey);

            var interval = await SettingProvider.GetAsync(IdentitySettingNames.User.SmsRepetInterval, 1);

            if (securityTokenCacheItem != null)
            {
                throw new UserFriendlyException(L["SendRepeatSmsVerifyCode", interval]);
            }
            // 传递 isConfirmed 验证过的用户才允许通过手机登录
            var user = await GetUserByPhoneNumberAsync(input.PhoneNumber, isConfirmed : true);

            var code = await UserManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider);

            var template = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsUserSignin);

            // 发送登录验证码短信
            await SecurityCodeSender.SendPhoneConfirmedCodeAsync(input.PhoneNumber, code, template);

            // 缓存登录验证码状态,防止同一手机号重复发送
            securityTokenCacheItem = new SmsSecurityTokenCacheItem(code, user.SecurityStamp);
            await SecurityTokenCache
            .SetAsync(securityTokenCacheKey, securityTokenCacheItem,
                      new DistributedCacheEntryOptions
            {
                AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(interval)
            });
        }
        public virtual async Task SendPhoneRegisterCodeAsync(SendPhoneRegisterCodeDto input)
        {
            await CheckSelfRegistrationAsync();
            await CheckNewUserPhoneNumberNotBeUsedAsync(input.PhoneNumber);

            var securityTokenCacheKey  = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode");
            var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey);

            var interval = await SettingProvider.GetAsync(IdentitySettingNames.User.SmsRepetInterval, 1);

            if (securityTokenCacheItem != null)
            {
                throw new UserFriendlyException(L["SendRepeatSmsVerifyCode", interval]);
            }

            var template = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsNewUserRegister);

            // 安全令牌
            var securityToken = GuidGenerator.Create().ToString("N");

            var code = TotpService.GenerateCode(Encoding.Unicode.GetBytes(securityToken), securityTokenCacheKey);

            securityTokenCacheItem = new SmsSecurityTokenCacheItem(code.ToString(), securityToken);

            await SecurityCodeSender.SendPhoneConfirmedCodeAsync(
                input.PhoneNumber, securityTokenCacheItem.Token, template);

            await SecurityTokenCache
            .SetAsync(securityTokenCacheKey, securityTokenCacheItem,
                      new DistributedCacheEntryOptions
            {
                AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(interval)
            });
        }
コード例 #5
0
        private async Task CheckMaxUserOrganizationUnitMembershipCountAsync(int requestedCount)
        {
            var maxCount = await SettingProvider.GetAsync <int>(IdentitySettingNames.OrganizationUnit.MaxUserMembershipCount);

            if (requestedCount > maxCount)
            {
                throw new BusinessException(IdentityErrorCodes.MaxAllowedOuMembership)
                      .WithData("MaxUserMembershipCount", maxCount);
            }
        }
コード例 #6
0
ファイル: EditModal.cshtml.cs プロジェクト: EasyAbp/EShop
        public virtual async Task OnGetAsync()
        {
            ServerSide = await SettingProvider.GetAsync <bool>("EasyAbp.EShop.Plugins.Baskets.EnableServerSideBaskets") &&
                         await AuthorizationService.IsGrantedAsync(BasketsPermissions.BasketItem.Default);

            if (!ServerSide)
            {
                return;
            }

            var dto = await _service.GetAsync(Id);

            ViewModel = ObjectMapper.Map <BasketItemDto, EditBasketItemViewModel>(dto);
        }
コード例 #7
0
        public virtual async Task BeforeSendAsync(string type, string receiver)
        {
            var exist = await CaptchaStore.FindAsync(type, receiver, CurrentTenant.Id);

            if (exist == null)
            {
                return;
            }
            int expireSeconds = await SettingProvider.GetAsync(AbpCaptchaSettings.CaptchaFrequencyLimitSeconds, 60);

            if (exist.CreationTime.AddSeconds(expireSeconds) > Clock.Now)//检查是否超过请求频率限制
            {
                throw new BusinessException(CaptchaErrorCodes.FrequencyLimit);
            }
        }
        public virtual async Task SendPhoneResetPasswordCodeAsync(SendPhoneResetPasswordCodeDto input)
        {
            /*
             * 注解: 微软的重置密码方法通过 UserManager.GeneratePasswordResetTokenAsync 接口生成密码重置Token
             *       而这个Token设计的意义就是用户通过链接来重置密码,所以不适合短信验证
             *       某些企业是把链接生成一个短链发送短信的,不过这种方式不是很推荐,因为现在是真没几个人敢随便点短信链接的
             *
             *  此处设计方式为:
             *
             *  step1: 例行检查是否重复发送,这一点是很有必要的
             *  step2: 通过已确认的手机号来查询用户,如果用户未确认手机号,那就不能发送,这一点也是很有必要的
             *  step3(重点): 通过 UserManager.GenerateTwoFactorTokenAsync 接口来生成二次认证码,这就相当于伪验证码,只是用于确认用户传递的验证码是否通过
             *               比起自己生成随机数,这个验证码利用了TOTP算法,有时间限制的
             *  step4(重点): 用户传递验证码后,通过 UserManager.VerifyTwoFactorTokenAsync 接口来校验验证码
             *               验证通过后,再利用 UserManager.GeneratePasswordResetTokenAsync 接口来生成真正的用于重置密码的Token
             */

            var securityTokenCacheKey  = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode");
            var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey);

            var interval = await SettingProvider.GetAsync(IdentitySettingNames.User.SmsRepetInterval, 1);

            // 传递 isConfirmed 用户必须是已确认过手机号的
            var user = await GetUserByPhoneNumberAsync(input.PhoneNumber, isConfirmed : true);

            // 能查询到缓存就是重复发送
            if (securityTokenCacheItem != null)
            {
                throw new UserFriendlyException(L["SendRepeatSmsVerifyCode", interval]);
            }

            var template = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsResetPassword);

            // 生成二次认证码
            var code = await UserManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider);

            // 发送短信验证码
            await SecurityCodeSender.SendPhoneConfirmedCodeAsync(input.PhoneNumber, code, template);

            // 缓存这个手机号的记录,防重复
            securityTokenCacheItem = new SmsSecurityTokenCacheItem(code, user.SecurityStamp);
            await SecurityTokenCache
            .SetAsync(securityTokenCacheKey, securityTokenCacheItem,
                      new DistributedCacheEntryOptions
            {
                AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(interval)
            });
        }
        /// <summary>
        /// 验证手机号码
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        /// <remarks>
        /// 用户传递手机号码及认证类型
        /// 1、如果认证类型为注册:
        ///     先查询是否存在此手机号的缓存验证码信息,如果存在,抛出不能重复发送验证码异常
        ///     随机生成6位纯数字验证码,通过短信接口服务发送到用户手机,并缓存验证码,设定一个有效时长
        ///
        /// 2、如果认证类型为登录:
        ///     先查询是否存在此手机号的缓存验证码信息,如果存在,抛出不能重复发送验证码异常
        ///     通过手机号查询用户信息,如果用户不存在,抛出手机号未注册异常
        ///     调用PhoneNumberTokenProvider接口生成6位手机验证码,用途为 phone_verify
        ///     发送手机验证码到用户手机,并缓存验证码,设定一个有效时长
        ///
        ///     用户调用 IdentityServer4/connect/token 登录系统(需要引用LINGYUN.Abp.IdentityServer.SmsValidator模块)
        ///         参数1:grant_type=phone_verify
        ///         参数2:phone_number=手机号码
        ///         参数3:phone_verify_code=手机验证码
        ///         参数4:client_id=客户端标识
        ///         参数5:client_secret=客户端密钥
        /// </remarks>
        public virtual async Task VerifyPhoneNumberAsync(VerifyDto input)
        {
            // TODO: 借用TOTP算法生成6位动态验证码

            var verifyCodeExpiration = await SettingProvider.GetAsync <int>(AccountSettingNames.PhoneVerifyCodeExpiration);

            var phoneVerifyCacheKey = NormalizeCacheKey(input.PhoneNumber);
            var verifyCacheItem     = await Cache.GetAsync(phoneVerifyCacheKey);

            if (verifyCacheItem != null)
            {
                throw new UserFriendlyException(L["PhoneVerifyCodeNotRepeatSend", verifyCodeExpiration]);
            }
            verifyCacheItem = new AccountRegisterVerifyCacheItem
            {
                PhoneNumber = input.PhoneNumber,
            };
            switch (input.VerifyType)
            {
            case PhoneNumberVerifyType.Register:
                var phoneVerifyCode = new Random().Next(100000, 999999);
                verifyCacheItem.VerifyCode = phoneVerifyCode.ToString();
                var templateCode = await SettingProvider.GetOrDefaultAsync(AccountSettingNames.SmsRegisterTemplateCode, ServiceProvider);
                await SendPhoneVerifyMessageAsync(templateCode, input.PhoneNumber, phoneVerifyCode.ToString());

                break;

            case PhoneNumberVerifyType.Signin:
                var phoneSigninCode = await SendSigninVerifyCodeAsync(input.PhoneNumber);

                verifyCacheItem.VerifyCode = phoneSigninCode;
                break;

            case PhoneNumberVerifyType.ResetPassword:
                var resetPasswordCode = new Random().Next(100000, 999999);
                verifyCacheItem.VerifyCode = resetPasswordCode.ToString();
                var resetPasswordToken = await SendResetPasswordVerifyCodeAsync(input.PhoneNumber, verifyCacheItem.VerifyCode);

                verifyCacheItem.VerifyToken = resetPasswordToken;
                break;
            }

            var cacheOptions = new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(verifyCodeExpiration)
            };
            await Cache.SetAsync(phoneVerifyCacheKey, verifyCacheItem, cacheOptions);
        }
        protected virtual async Task <AliyunBasicSessionCredentialsCacheItem> GetCacheItemAsync(string accessKeyId, string accessKeySecret, string regionId)
        {
            var cacheItem = await Cache.GetAsync(AliyunBasicSessionCredentialsCacheItem.CacheKey);

            if (cacheItem == null)
            {
                var roleArn = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Authorization.RamRoleArn);

                var roleSession = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Authorization.RoleSessionName);

                Check.NotNullOrWhiteSpace(roleArn, AliyunSettingNames.Authorization.RamRoleArn);

                var policy = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Authorization.Policy);

                var durationSeconds = await SettingProvider.GetAsync(AliyunSettingNames.Authorization.DurationSeconds, 3000);

                var profile = DefaultProfile.GetProfile(regionId, accessKeyId, accessKeySecret);
                var request = new AssumeRoleRequest
                {
                    AcceptFormat    = FormatType.JSON,
                    RoleArn         = roleArn,
                    RoleSessionName = roleSession,
                    DurationSeconds = durationSeconds,
                    Policy          = policy.IsNullOrWhiteSpace() ? null : policy
                };

                var client   = new DefaultAcsClient(profile);
                var response = client.GetAcsResponse(request);

                cacheItem = new AliyunBasicSessionCredentialsCacheItem(
                    response.Credentials.AccessKeyId,
                    response.Credentials.AccessKeySecret,
                    response.Credentials.SecurityToken);

                await Cache.SetAsync(
                    AliyunBasicSessionCredentialsCacheItem.CacheKey,
                    cacheItem,
                    new DistributedCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(durationSeconds - 10)
                });
            }

            return(cacheItem);
        }
コード例 #11
0
        public virtual async Task <OssObjectDto> CreateAsync(CreateOssObjectInput input)
        {
            if (!input.Content.IsNullOrEmpty())
            {
                // 检查文件大小
                var fileSizeLimited = await SettingProvider
                                      .GetAsync(
                    AbpOssManagementSettingNames.FileLimitLength,
                    AbpOssManagementSettingNames.DefaultFileLimitLength);

                if (fileSizeLimited * 1024 * 1024 < input.Content.Length)
                {
                    ThrowValidationException(L["UploadFileSizeBeyondLimit", fileSizeLimited], nameof(input.Content));
                }

                // 文件扩展名
                var fileExtensionName  = FileHelper.GetExtension(input.Object);
                var fileAllowExtension = await SettingProvider.GetOrNullAsync(AbpOssManagementSettingNames.AllowFileExtensions);

                // 检查文件扩展名
                if (!fileAllowExtension.Split(',')
                    .Any(fe => fe.Equals(fileExtensionName, StringComparison.CurrentCultureIgnoreCase)))
                {
                    ThrowValidationException(L["NotAllowedFileExtensionName", fileExtensionName], "FileName");
                }
            }

            var oss = CreateOssContainer();

            var createOssObjectRequest = new CreateOssObjectRequest(
                input.Bucket,
                input.Object,
                input.Content,
                input.Path,
                input.ExpirationTime)
            {
                Overwrite = input.Overwrite
            };
            var ossObject = await oss.CreateObjectAsync(createOssObjectRequest);

            return(ObjectMapper.Map <OssObject, OssObjectDto>(ossObject));
        }
コード例 #12
0
        public virtual async Task <string> CreateAsync(FileUploadInputDto input)
        {
            if (input.Bytes.IsNullOrEmpty())
            {
                throw new AbpValidationException("Bytes can not be null or empty!",
                                                 new List <ValidationResult>
                {
                    new ValidationResult("Bytes can not be null or empty!", new[] { "Bytes" })
                });
            }

            var allowedMaxFileSize = await SettingProvider.GetAsync <int>(FileManagementSettings.AllowedMaxFileSize);//kb

            var allowedUploadFormats = (await SettingProvider.GetOrNullAsync(FileManagementSettings.AllowedUploadFormats))
                                       ?.Split(",", StringSplitOptions.RemoveEmptyEntries);

            if (input.Bytes.Length > allowedMaxFileSize * 1024)
            {
                throw new UserFriendlyException(L["FileManagement.ExceedsTheMaximumSize", allowedMaxFileSize]);
            }

            if (allowedUploadFormats == null || !allowedUploadFormats.Contains(Path.GetExtension(input.Name)))
            {
                throw new UserFriendlyException(L["FileManagement.NotValidFormat"]);
            }

            var fileName = Guid.NewGuid().ToString("N") + Path.GetExtension(input.Name);
            var filePath = Path.Combine(FileOptions.FileUploadLocalFolder, fileName);

            if (!Directory.Exists(FileOptions.FileUploadLocalFolder))
            {
                Directory.CreateDirectory(FileOptions.FileUploadLocalFolder);
            }

            File.WriteAllBytes(filePath, input.Bytes);

            return(fileName);
        }
コード例 #13
0
        public virtual async Task SendAsync(string type, string receiver)
        {
            await BeforeSendAsync(type, receiver);

            var code = await CaptchaGenerator.CreateAsync();

            var receiverType = receiver.Contains("@") ? EnumReceiverType.Email : EnumReceiverType.PhoneNumber;

            string expireSecondSettingName = receiverType == EnumReceiverType.Email ? AbpCaptchaSettings.EmailCaptchaExpireSeconds : AbpCaptchaSettings.SmsCaptchaExpireSeconds;
            int    expireSeconds           = await SettingProvider.GetAsync(expireSecondSettingName, 60);

            var captcha = new Captcha(type, code, receiver, receiverType, Clock.Now, expireSeconds, CurrentTenant.Id);

            bool sendResult = receiverType == EnumReceiverType.Email
                ? await EmailCaptchaSender.SendAsync(captcha)
                : await SmsCaptchaSender.SendAsync(captcha);

            if (!sendResult)
            {
                throw new BusinessException(CaptchaErrorCodes.SendFailed);
            }
            await CaptchaStore.CreateAsync(captcha);
        }
コード例 #14
0
        public virtual async Task CreateFileAsync([FromForm] FileUploadDto input)
        {
            // 检查文件大小
            var fileSizeLimited = await SettingProvider
                                  .GetAsync(
                AbpFileManagementSettingNames.FileLimitLength,
                AbpFileManagementSettingNames.DefaultFileLimitLength);

            if (fileSizeLimited * 1024 * 1024 < input.TotalSize)
            {
                throw new UserFriendlyException(L["UploadFileSizeBeyondLimit", fileSizeLimited]);
            }
            // 采用分块模式上传文件

            // 保存分块到临时目录
            var fileName = input.FileName;
            // 文件扩展名
            var fileExtensionName  = FileHelper.GetExtension(fileName);
            var fileAllowExtension = await SettingProvider
                                     .GetOrDefaultAsync(AbpFileManagementSettingNames.AllowFileExtensions, ServiceProvider);

            // 检查文件扩展名
            if (!fileAllowExtension.Split(',')
                .Any(fe => fe.Equals(fileExtensionName, StringComparison.CurrentCultureIgnoreCase)))
            {
                throw new UserFriendlyException(L["NotAllowedFileExtensionName", fileExtensionName]);
            }
            // 以上传的文件名创建一个临时目录
            var tempFilePath = Path.Combine(
                Path.GetTempPath(),
                "lingyun-abp-file-management",
                "upload",
                string.Concat(input.Path ?? "", input.FileName).ToMd5());

            DirectoryHelper.CreateIfNotExists(tempFilePath);
            // 以上传的分片索引创建临时文件
            var tempSavedFile = Path.Combine(tempFilePath, $"{input.ChunkNumber}.{fileExtensionName}");

            try
            {
                if (HttpContext.RequestAborted.IsCancellationRequested)
                {
                    // 如果取消请求,删除临时目录
                    Directory.Delete(tempFilePath, true);
                    return;
                }

                if (input.File != null)
                {
                    // 保存临时文件
                    using (var fs = new FileStream(tempSavedFile, FileMode.Create, FileAccess.Write))
                    {
                        // 写入当前分片文件
                        await input.File.CopyToAsync(fs);
                    }
                }

                if (input.ChunkNumber == input.TotalChunks)
                {
                    // 合并文件
                    var mergeSavedFile = Path.Combine(tempFilePath, $"{fileName}");
                    // 获取并排序所有分片文件
                    var mergeFiles = Directory.GetFiles(tempFilePath).OrderBy(f => f.Length).ThenBy(f => f);
                    // 创建临时合并文件
                    input.Data = new byte[0];
                    foreach (var mergeFile in mergeFiles)
                    {
                        // 读取当前文件字节
                        var mergeFileBytes = await FileHelper.ReadAllBytesAsync(mergeFile);

                        // 写入到合并文件流
                        input.Data = input.Data.Concat(mergeFileBytes).ToArray();
                        Array.Clear(mergeFileBytes, 0, mergeFileBytes.Length);
                        // 删除已参与合并的临时文件分片
                        FileHelper.DeleteIfExists(mergeFile);
                    }
                    await FileSystemAppService.CreateFileAsync(input);

                    // 文件保存之后删除临时文件目录
                    Directory.Delete(tempFilePath, true);
                }
            }
            catch
            {
                // 发生异常删除临时文件目录
                Directory.Delete(tempFilePath, true);
                throw;
            }
        }
コード例 #15
0
        public virtual async Task AppendFileAsync([FromQuery] VersionFileCreateDto versionFileCreate)
        {
            // 检查文件大小
            var fileSizeLimited = await SettingProvider
                                  .GetAsync(PlatformSettingNames.AppVersion.VersionFileLimitLength, AppVersionConsts.DefaultVersionFileLimitLength);

            if (fileSizeLimited * 1024 * 1024 < versionFileCreate.TotalByte)
            {
                throw new UserFriendlyException(L["UploadFileSizeBeyondLimit", fileSizeLimited]);
            }
            // 采用分块模式上传文件

            // 保存分块到临时目录
            var fileName = versionFileCreate.FileName;
            // 文件扩展名
            var fileExtensionName  = FileHelper.GetExtension(fileName);
            var fileAllowExtension = await SettingProvider
                                     .GetOrNullAsync(PlatformSettingNames.AppVersion.AllowVersionFileExtensions);

            if (fileAllowExtension.IsNullOrWhiteSpace())
            {
                fileAllowExtension = AppVersionConsts.DefaultAllowVersionFileExtensions;
            }
            // 检查文件扩展名
            if (!fileAllowExtension.Split(',').Any(fe => fe.Equals(fileExtensionName, StringComparison.CurrentCultureIgnoreCase)))
            {
                throw new UserFriendlyException(L["NotAllowedFileExtensionName", fileExtensionName]);
            }
            // 当前计算机临时目录
            var tempFilePath = Environment.GetFolderPath(Environment.SpecialFolder.Templates);

            // 以上传的文件名创建一个临时目录
            tempFilePath = Path.Combine(tempFilePath, "lingyun-platform", Path.GetFileNameWithoutExtension(fileName));
            // 以上传的分片索引创建临时文件
            var tempSavedFile = Path.Combine(tempFilePath, $"{versionFileCreate.CurrentByte}.{fileExtensionName}");

            if (!Directory.Exists(tempFilePath))
            {
                // 临时目录不存在则创建
                Directory.CreateDirectory(tempFilePath);
            }
            try
            {
                if (HttpContext.RequestAborted.IsCancellationRequested)
                {
                    // 如果取消请求,删除临时目录
                    Directory.Delete(tempFilePath, true);
                    return;
                }
                // 保存临时文件
                using (var fs = new FileStream(tempSavedFile, FileMode.Create, FileAccess.Write))
                {
                    // 写入当前分片文件
                    await Request.Body.CopyToAsync(fs);
                }

                if (versionFileCreate.CurrentByte == versionFileCreate.TotalByte)
                {
                    // 合并文件
                    var mergeSavedFile = Path.Combine(tempFilePath, $"{fileName}");
                    // 获取并排序所有分片文件
                    var mergeFiles = Directory.GetFiles(tempFilePath).OrderBy(f => f.Length).ThenBy(f => f);
                    // 创建临时合并文件
                    using (var mergeSavedFileStream = new FileStream(mergeSavedFile, FileMode.Create))
                    {
                        foreach (var mergeFile in mergeFiles)
                        {
                            // 读取当前文件字节
                            var mergeFileBytes = await FileHelper.ReadAllBytesAsync(mergeFile);

                            // 写入到合并文件流
                            await mergeSavedFileStream.WriteAsync(mergeFileBytes, 0, mergeFileBytes.Length);

                            // 删除已参与合并的临时文件分片
                            FileHelper.DeleteIfExists(mergeFile);
                        }
                        // 上传最终合并的文件并取得SHA256指纹
                        var fileData = await mergeSavedFileStream.GetAllBytesAsync();

                        versionFileCreate.SHA256 = await _versionFileManager.SaveFileAsync(versionFileCreate.Version,
                                                                                           versionFileCreate.FilePath, versionFileCreate.FileName, versionFileCreate.FileVersion, fileData);
                    }
                    // 添加到版本信息
                    await _versionAppService.AppendFileAsync(versionFileCreate);

                    // 文件保存之后删除临时文件目录
                    Directory.Delete(tempFilePath, true);
                }
            }
            catch
            {
                // 发生异常删除临时文件目录
                Directory.Delete(tempFilePath, true);
                throw;
            }
        }