oss板块
This commit is contained in:
+4
-1
@@ -49,7 +49,10 @@ public class OnboardingContractDocumentService : IOnboardingContractDocumentServ
|
||||
sourceBytes.ToArray(),
|
||||
variables.ToDictionary(x => x.VariableCode, x => x.VariableValue ?? string.Empty));
|
||||
var name = $"{task.TaskNo}_{contract.ContractName}.docx";
|
||||
var fileId = await _fileService.StoreGeneratedFile(source.Engine, name, rendered);
|
||||
var fileId = await _fileService.StoreGeneratedFile(
|
||||
SysDictConst.FILE_ENGINE_ALIYUN_OSS,
|
||||
name,
|
||||
rendered);
|
||||
|
||||
contract.GeneratedDocxFileId = fileId;
|
||||
contract.GeneratedAt = DateTime.Now;
|
||||
|
||||
+3
@@ -262,6 +262,9 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
|
||||
|
||||
private async Task<ObOnboardingTask> GetEditableTask(string token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
throw Oops.Bah("入职链接 Token 不能为空");
|
||||
|
||||
var hash = Convert.ToHexString(
|
||||
global::System.Security.Cryptography.SHA256.HashData(
|
||||
global::System.Text.Encoding.UTF8.GetBytes(token)));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
@@ -68,6 +68,11 @@ public class SysDictConst
|
||||
/// </summary>
|
||||
public const string FILE_ENGINE_MINIO = "MINIO";
|
||||
|
||||
/// <summary>
|
||||
/// 阿里云 OSS,当前仅供入职业务文件使用。
|
||||
/// </summary>
|
||||
public const string FILE_ENGINE_ALIYUN_OSS = "ALIYUN_OSS";
|
||||
|
||||
#endregion 上传文件引擎
|
||||
|
||||
#region 系统字典分类
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 阿里云 OSS 及文档预览服务配置。
|
||||
/// </summary>
|
||||
public class OssSettingOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// OSS 访问密钥 Id。
|
||||
/// </summary>
|
||||
public string AccessKeyID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OSS 访问密钥。
|
||||
/// </summary>
|
||||
public string AccessKeySecret { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用于换取临时凭证的 RAM 角色 ARN。
|
||||
/// </summary>
|
||||
public string RoleArn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// STS 角色会话名称。
|
||||
/// </summary>
|
||||
public string RoleSessionName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// STS 服务端点。
|
||||
/// </summary>
|
||||
public string StsEndpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OSS Bucket 名称。
|
||||
/// </summary>
|
||||
public string Bucket { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OSS 服务端点。
|
||||
/// </summary>
|
||||
public string EndPoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// IMM 服务端点。
|
||||
/// </summary>
|
||||
public string IMMEndpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// IMM 项目名称。
|
||||
/// </summary>
|
||||
public string IMMProject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OSS 自定义访问域名。
|
||||
/// </summary>
|
||||
public string OSSUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 阿里云地域 Id。
|
||||
/// </summary>
|
||||
public string RegionId { get; set; }
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
using Aliyun.OSS;
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
@@ -95,6 +97,8 @@ public class FileService : DbRepository<SysFile>, IFileService
|
||||
return GetFileStreamResult(devFile.StoragePath, devFile.Name);
|
||||
if (devFile.Engine == SysDictConst.FILE_ENGINE_MINIO)
|
||||
return await GetFileStreamResultFromMinio(devFile.ObjName, devFile.Name);
|
||||
if (devFile.Engine == SysDictConst.FILE_ENGINE_ALIYUN_OSS)
|
||||
return await GetFileStreamResultFromAliyunOss(devFile);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
@@ -142,6 +146,9 @@ public class FileService : DbRepository<SysFile>, IFileService
|
||||
}
|
||||
break;
|
||||
|
||||
case SysDictConst.FILE_ENGINE_ALIYUN_OSS:
|
||||
(bucketName, objName, storageUrl) = await StorageAliyunOss(objectId, file);
|
||||
break;
|
||||
default:
|
||||
|
||||
throw Oops.Bah("不支持的文件引擎");
|
||||
@@ -154,13 +161,13 @@ public class FileService : DbRepository<SysFile>, IFileService
|
||||
Engine = engine,
|
||||
Bucket = bucketName,
|
||||
Name = file.FileName,
|
||||
Suffix = fileSuffix.Split(".")[1],
|
||||
Suffix = fileSuffix.TrimStart('.'),
|
||||
ObjName = objName,
|
||||
SizeKb = fileSizeKb,
|
||||
SizeInfo = GetSizeInfo(fileSizeKb),
|
||||
StoragePath = storageUrl
|
||||
};
|
||||
if (engine != CateGoryConst.CONFIG_FILE_LOCAL)//如果不是本地,设置下载地址
|
||||
if (engine != SysDictConst.FILE_ENGINE_LOCAL)//如果不是本地,设置下载地址
|
||||
{
|
||||
devFile.DownloadPath = storageUrl;
|
||||
}
|
||||
@@ -236,6 +243,111 @@ public class FileService : DbRepository<SysFile>, IFileService
|
||||
return (fileObjectName, downloadUrl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将入职业务文件存储到阿里云 OSS。
|
||||
/// </summary>
|
||||
private async Task<(string bucketName, string objName, string downloadUrl)> StorageAliyunOss(
|
||||
long fileId,
|
||||
IFormFile file)
|
||||
{
|
||||
var setting = GetOssSetting();
|
||||
var suffix = Path.GetExtension(file.FileName).ToLowerInvariant();
|
||||
var objectName = $"onboarding/{DateTime.Now:yyyy/MM/dd}/{fileId}{suffix}";
|
||||
var metadata = new ObjectMetadata();
|
||||
if (!string.IsNullOrWhiteSpace(file.ContentType))
|
||||
metadata.ContentType = file.ContentType;
|
||||
|
||||
await using var stream = file.OpenReadStream();
|
||||
var client = CreateOssClient(setting);
|
||||
client.PutObject(setting.Bucket, objectName, stream, metadata);
|
||||
|
||||
var downloadUrl = string.IsNullOrWhiteSpace(setting.OSSUrl)
|
||||
? $"https://{setting.Bucket}.{setting.EndPoint.Trim().TrimEnd('/')}/{objectName}"
|
||||
: $"{setting.OSSUrl.Trim().TrimEnd('/')}/{objectName}";
|
||||
|
||||
return (setting.Bucket, objectName, downloadUrl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从阿里云 OSS 读取业务文件并通过统一下载接口返回。
|
||||
/// </summary>
|
||||
private async Task<FileStreamResult> GetFileStreamResultFromAliyunOss(SysFile file)
|
||||
{
|
||||
var setting = GetOssSetting();
|
||||
var client = CreateOssClient(setting);
|
||||
var ossObject = client.GetObject(file.Bucket, file.ObjName);
|
||||
var memory = new MemoryStream();
|
||||
await using (ossObject.Content)
|
||||
{
|
||||
await ossObject.Content.CopyToAsync(memory);
|
||||
}
|
||||
|
||||
memory.Position = 0;
|
||||
var fileName = HttpUtility.UrlEncode(file.Name, Encoding.GetEncoding("UTF-8"));
|
||||
return new FileStreamResult(memory, "application/octet-stream")
|
||||
{
|
||||
FileDownloadName = fileName
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取并校验阿里云 OSS 配置。
|
||||
/// </summary>
|
||||
private static OssSettingOptions GetOssSetting()
|
||||
{
|
||||
var setting = App.GetConfig<OssSettingOptions>("OssSetting", true);
|
||||
var configurationIncomplete = setting == null
|
||||
|| string.IsNullOrWhiteSpace(setting.AccessKeyID)
|
||||
|| string.IsNullOrWhiteSpace(setting.AccessKeySecret)
|
||||
|| string.IsNullOrWhiteSpace(setting.Bucket)
|
||||
|| string.IsNullOrWhiteSpace(setting.EndPoint);
|
||||
if (configurationIncomplete)
|
||||
throw Oops.Bah("阿里云 OSS 配置不完整");
|
||||
|
||||
return setting;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过 STS 临时凭证创建阿里云 OSS 客户端。
|
||||
/// </summary>
|
||||
private static OssClient CreateOssClient(OssSettingOptions setting)
|
||||
{
|
||||
var endpoint = setting.EndPoint.Trim();
|
||||
if (!endpoint.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
|
||||
&& !endpoint.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
endpoint = "https://" + endpoint;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(setting.RoleArn))
|
||||
return new OssClient(endpoint, setting.AccessKeyID, setting.AccessKeySecret);
|
||||
|
||||
var stsConfig = new AlibabaCloud.OpenApiClient.Models.Config
|
||||
{
|
||||
AccessKeyId = setting.AccessKeyID,
|
||||
AccessKeySecret = setting.AccessKeySecret,
|
||||
Endpoint = setting.StsEndpoint
|
||||
};
|
||||
var stsClient = new AlibabaCloud.SDK.Sts20150401.Client(stsConfig);
|
||||
var request = new AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleRequest
|
||||
{
|
||||
RoleArn = setting.RoleArn,
|
||||
RoleSessionName = string.IsNullOrWhiteSpace(setting.RoleSessionName)
|
||||
? "onboardx-server"
|
||||
: setting.RoleSessionName,
|
||||
DurationSeconds = 3600
|
||||
};
|
||||
var credentials = stsClient.AssumeRole(request).Body.Credentials;
|
||||
if (credentials == null)
|
||||
throw Oops.Bah("获取阿里云 OSS 临时凭证失败");
|
||||
|
||||
return new OssClient(
|
||||
endpoint,
|
||||
credentials.AccessKeyId,
|
||||
credentials.AccessKeySecret,
|
||||
credentials.SecurityToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件大小
|
||||
/// </summary>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.2.0"/>
|
||||
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.0"/>
|
||||
<PackageReference Include="IP2Region.Net" Version="2.0.2"/>
|
||||
<PackageReference Include="UAParser" Version="3.1.47"/>
|
||||
<PackageReference Include="Magicodes.IE.Excel" Version="2.7.5.1"/>
|
||||
|
||||
@@ -506,6 +506,11 @@
|
||||
MINIO
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:SimpleAdmin.System.SysDictConst.FILE_ENGINE_ALIYUN_OSS">
|
||||
<summary>
|
||||
阿里云 OSS,当前仅供入职业务文件使用。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:SimpleAdmin.System.SysDictConst.DICT_CATEGORY_FRM">
|
||||
<summary>
|
||||
系统
|
||||
@@ -2670,6 +2675,66 @@
|
||||
对象映射
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:SimpleAdmin.System.OssSettingOptions">
|
||||
<summary>
|
||||
阿里云 OSS 及文档预览服务配置。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:SimpleAdmin.System.OssSettingOptions.AccessKeyID">
|
||||
<summary>
|
||||
OSS 访问密钥 Id。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:SimpleAdmin.System.OssSettingOptions.AccessKeySecret">
|
||||
<summary>
|
||||
OSS 访问密钥。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:SimpleAdmin.System.OssSettingOptions.RoleArn">
|
||||
<summary>
|
||||
用于换取临时凭证的 RAM 角色 ARN。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:SimpleAdmin.System.OssSettingOptions.RoleSessionName">
|
||||
<summary>
|
||||
STS 角色会话名称。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:SimpleAdmin.System.OssSettingOptions.StsEndpoint">
|
||||
<summary>
|
||||
STS 服务端点。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:SimpleAdmin.System.OssSettingOptions.Bucket">
|
||||
<summary>
|
||||
OSS Bucket 名称。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:SimpleAdmin.System.OssSettingOptions.EndPoint">
|
||||
<summary>
|
||||
OSS 服务端点。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:SimpleAdmin.System.OssSettingOptions.IMMEndpoint">
|
||||
<summary>
|
||||
IMM 服务端点。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:SimpleAdmin.System.OssSettingOptions.IMMProject">
|
||||
<summary>
|
||||
IMM 项目名称。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:SimpleAdmin.System.OssSettingOptions.OSSUrl">
|
||||
<summary>
|
||||
OSS 自定义访问域名。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:SimpleAdmin.System.OssSettingOptions.RegionId">
|
||||
<summary>
|
||||
阿里云地域 Id。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:SimpleAdmin.System.SystemSettingsOptions">
|
||||
<summary>
|
||||
系统配置
|
||||
@@ -3588,6 +3653,26 @@
|
||||
<param name="file"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:SimpleAdmin.System.FileService.StorageAliyunOss(System.Int64,Microsoft.AspNetCore.Http.IFormFile)">
|
||||
<summary>
|
||||
将入职业务文件存储到阿里云 OSS。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:SimpleAdmin.System.FileService.GetFileStreamResultFromAliyunOss(SimpleAdmin.System.SysFile)">
|
||||
<summary>
|
||||
从阿里云 OSS 读取业务文件并通过统一下载接口返回。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:SimpleAdmin.System.FileService.GetOssSetting">
|
||||
<summary>
|
||||
读取并校验阿里云 OSS 配置。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:SimpleAdmin.System.FileService.CreateOssClient(SimpleAdmin.System.OssSettingOptions)">
|
||||
<summary>
|
||||
通过 STS 临时凭证创建阿里云 OSS 客户端。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:SimpleAdmin.System.FileService.GetSizeInfo(System.Int64)">
|
||||
<summary>
|
||||
获取文件大小
|
||||
|
||||
@@ -24,5 +24,18 @@
|
||||
"InitSeedData": false, //是否初始化种子数据
|
||||
"SuperAdminViewAllData": true, //是否超级管理员可以查看所有数据
|
||||
"UseMessageCenter": false //是否启用通知中心
|
||||
},
|
||||
"OssSetting": {
|
||||
"AccessKeyID": "LTAI5t9VQvLERXtMHC9BK31f",
|
||||
"AccessKeySecret": "YomhludvaOmLZo1K0vUDoeBhWRjxWL",
|
||||
"RoleArn": "acs:ram::1436008104961462:role/onboardx-sts",
|
||||
"RoleSessionName": "onboardx-server",
|
||||
"StsEndpoint": "sts.cn-beijing.aliyuncs.com",
|
||||
"Bucket": "onboardx",
|
||||
"EndPoint": "oss-cn-beijing.aliyuncs.com",
|
||||
"IMMEndpoint": "",
|
||||
"IMMProject": "",
|
||||
"OSSUrl": "https://onboardx.oss-cn-beijing.aliyuncs.com",
|
||||
"RegionId": "oss-cn-beijing"
|
||||
}
|
||||
}
|
||||
@@ -24,5 +24,18 @@
|
||||
"InitSeedData": false, //是否初始化种子数据
|
||||
"SuperAdminViewAllData": true, //是否超级管理员可以查看所有数据
|
||||
"UseMessageCenter": false //是否启用通知中心
|
||||
},
|
||||
"OssSetting": {
|
||||
"AccessKeyID": "LTAI5t9VQvLERXtMHC9BK31f",
|
||||
"AccessKeySecret": "YomhludvaOmLZo1K0vUDoeBhWRjxWL",
|
||||
"RoleArn": "acs:ram::1436008104961462:role/onboardx-sts",
|
||||
"RoleSessionName": "onboardx-server",
|
||||
"StsEndpoint": "sts.cn-beijing.aliyuncs.com",
|
||||
"Bucket": "onboardx",
|
||||
"EndPoint": "oss-cn-beijing.aliyuncs.com",
|
||||
"IMMEndpoint": "",
|
||||
"IMMProject": "",
|
||||
"OSSUrl": "https://onboardx.oss-cn-beijing.aliyuncs.com",
|
||||
"RegionId": "oss-cn-beijing"
|
||||
}
|
||||
}
|
||||
+23
-1
@@ -9,10 +9,32 @@ namespace SimpleAdmin.Web.Core;
|
||||
public class OnboardingContractTemplateController
|
||||
{
|
||||
private readonly IOnboardingContractTemplateService _service;
|
||||
private readonly IFileService _fileService;
|
||||
|
||||
public OnboardingContractTemplateController(IOnboardingContractTemplateService service)
|
||||
public OnboardingContractTemplateController(
|
||||
IOnboardingContractTemplateService service,
|
||||
IFileService fileService)
|
||||
{
|
||||
_service = service;
|
||||
_fileService = fileService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将入职合同 DOCX 模板上传到阿里云 OSS。
|
||||
/// </summary>
|
||||
[HttpPost("file/upload-docx")]
|
||||
[Consumes("multipart/form-data")]
|
||||
[RequestSizeLimit(20 * 1024 * 1024)]
|
||||
public async Task<long> UploadDocx([FromForm] IFormFile file)
|
||||
{
|
||||
if (file == null || file.Length == 0)
|
||||
throw Oops.Bah("请选择需要上传的 DOCX 文件");
|
||||
|
||||
var suffix = Path.GetExtension(file.FileName);
|
||||
if (!string.Equals(suffix, ".docx", StringComparison.OrdinalIgnoreCase))
|
||||
throw Oops.Bah("合同模板仅支持 DOCX 文件");
|
||||
|
||||
return await _fileService.UploadFile(SysDictConst.FILE_ENGINE_ALIYUN_OSS, file);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+6
-3
@@ -59,11 +59,14 @@ public class OnboardingPublicSubmissionController
|
||||
[HttpPost("upload-material")]
|
||||
[Consumes("multipart/form-data")]
|
||||
[RequestSizeLimit(20 * 1024 * 1024)]
|
||||
public async Task UploadMaterial([FromForm] string token, [FromForm] long materialTypeId, IFormFile file)
|
||||
public async Task UploadMaterial(
|
||||
[FromQuery] string token,
|
||||
[FromQuery] long materialTypeId,
|
||||
[FromForm] IFormFile file)
|
||||
{
|
||||
if (file == null || file.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("请选择需要上传的文件");
|
||||
throw Oops.Bah("请选择需要上传的文件");
|
||||
}
|
||||
|
||||
await _service.ValidateMaterialUpload(new OnboardingMaterialUploadValidationInput
|
||||
@@ -74,7 +77,7 @@ public class OnboardingPublicSubmissionController
|
||||
FileLength = file.Length
|
||||
});
|
||||
|
||||
var fileId = await _fileService.UploadFile(SysDictConst.FILE_ENGINE_LOCAL, file);
|
||||
var fileId = await _fileService.UploadFile(SysDictConst.FILE_ENGINE_ALIYUN_OSS, file);
|
||||
await _service.SaveMaterial(new OnboardingSaveMaterialInput
|
||||
{
|
||||
Token = token,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
global using MoYu;
|
||||
global using MoYu.DynamicApiController;
|
||||
global using MoYu.FriendlyException;
|
||||
global using MoYu.Logging;
|
||||
global using Microsoft.AspNetCore.Authorization;
|
||||
global using Microsoft.AspNetCore.Builder;
|
||||
|
||||
@@ -91,6 +91,11 @@
|
||||
提供合同模板、版本、变量和适用范围相关 HTTP 接口。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:SimpleAdmin.Web.Core.OnboardingContractTemplateController.UploadDocx(Microsoft.AspNetCore.Http.IFormFile)">
|
||||
<summary>
|
||||
将入职合同 DOCX 模板上传到阿里云 OSS。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:SimpleAdmin.Web.Core.OnboardingContractTemplateController.Page(SimpleAdmin.Application.OnboardingContractTemplatePageInput)">
|
||||
<summary>
|
||||
分页查询合同模板。
|
||||
|
||||
Reference in New Issue
Block a user