初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
|
||||
namespace PCL.Core.Utils.Validate;
|
||||
|
||||
public class BlacklistValidator(List<string> contains) : AbstractValidator<string>
|
||||
{
|
||||
public List<string> Blacklist { get; set; } = contains;
|
||||
|
||||
public BlacklistValidator() : this([])
|
||||
{
|
||||
}
|
||||
|
||||
private void _BuildRules()
|
||||
{
|
||||
RuleFor(x => x)
|
||||
.Custom((input, context) =>
|
||||
{
|
||||
foreach (var items in Blacklist)
|
||||
{
|
||||
if (input.Contains(items))
|
||||
{
|
||||
context.AddFailure($"输入内容不能包含 {items}!");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override bool PreValidate(ValidationContext<string> context, ValidationResult result)
|
||||
{
|
||||
_BuildRules();
|
||||
return base.PreValidate(context, result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using PCL.Core.Utils.Exts;
|
||||
|
||||
namespace PCL.Core.Utils.Validate;
|
||||
|
||||
public class FileNameValidator(
|
||||
string? parentFolder = null,
|
||||
bool ignoreCase = true,
|
||||
bool useMinecraftCharCheck = true,
|
||||
bool requireParentFolderExists = true)
|
||||
: FileSystemValidator
|
||||
{
|
||||
public bool UseMinecraftCharCheck { get; set; } = useMinecraftCharCheck;
|
||||
public bool IgnoreCase { get; set; } = ignoreCase;
|
||||
public string? ParentFolder { get; set; } = parentFolder;
|
||||
public bool RequireParentFolderExists { get; set; } = requireParentFolderExists;
|
||||
|
||||
private bool? _isParentFolderExists;
|
||||
|
||||
public FileNameValidator() : this(null)
|
||||
{
|
||||
}
|
||||
|
||||
private void _BuildRules()
|
||||
{
|
||||
RuleFor(x => x)
|
||||
.Must(x => !string.IsNullOrWhiteSpace(x)).WithMessage("输入内容不能为空!")
|
||||
.Must(x => !x.StartsWith(' ')).WithMessage("文件名不能以空格开头!")
|
||||
.Must(x => !x.EndsWith(' ')).WithMessage("文件名不能以空格结尾!")
|
||||
.Must(x => !x.EndsWith('.')).WithMessage("文件名不能以小数点结尾!")
|
||||
.Custom((fileName, context) =>
|
||||
{
|
||||
var invalidChar = CheckInvalidStrings(fileName, UseMinecraftCharCheck ? ["!;"] : []);
|
||||
if (invalidChar is not null)
|
||||
{
|
||||
context.AddFailure($"文件名不可包含 {invalidChar} 字符!");
|
||||
}
|
||||
})
|
||||
.Custom((fileName, context) =>
|
||||
{
|
||||
var reservedWord = CheckReservedWord(fileName, []);
|
||||
if (reservedWord is not null)
|
||||
{
|
||||
context.AddFailure($"文件名不可为 {reservedWord}!");
|
||||
}
|
||||
})
|
||||
.Must(x => !x.IsMatch(RegexPatterns.Ntfs83FileName)).WithMessage("文件名不能包含这一特殊格式!")
|
||||
.Must(x =>
|
||||
{
|
||||
if (ParentFolder is null) return true;
|
||||
|
||||
var dirInfo = new DirectoryInfo(ParentFolder);
|
||||
if (dirInfo.Exists)
|
||||
{
|
||||
return !dirInfo.EnumerateFiles().Select(f => f.Name).Contains(x,
|
||||
IgnoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
_isParentFolderExists = false;
|
||||
return !RequireParentFolderExists;
|
||||
|
||||
}).WithMessage(_isParentFolderExists is not null ? $"父文件夹不存在:{ParentFolder}" : "不可与现有文件重名!");
|
||||
}
|
||||
|
||||
protected override bool PreValidate(ValidationContext<string> context, ValidationResult result)
|
||||
{
|
||||
_BuildRules();
|
||||
return base.PreValidate(context, result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FluentValidation;
|
||||
|
||||
namespace PCL.Core.Utils.Validate;
|
||||
|
||||
public abstract class FileSystemValidator : AbstractValidator<string>
|
||||
{
|
||||
protected static string? CheckInvalidStrings(string input, string[] extraInvalidStrings)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input)) return null;
|
||||
|
||||
var invalidStrings = Path.GetInvalidFileNameChars().Select(c => c.ToString());
|
||||
invalidStrings = invalidStrings.Concat(extraInvalidStrings);
|
||||
|
||||
// 找出字符串中包含的非法字符
|
||||
var found = invalidStrings
|
||||
.Where(input.Contains)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
return found.Length != 0 ? string.Join(" ", found) : null;
|
||||
}
|
||||
|
||||
protected static string? CheckReservedWord(string input, string[] extraReservedWords)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input)) return null;
|
||||
|
||||
var nameWithoutExtension = Path.GetFileNameWithoutExtension(input);
|
||||
|
||||
IEnumerable<string> reserved =
|
||||
[
|
||||
"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7",
|
||||
"COM8", "COM9", "COM¹", "COM²", "COM³", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7",
|
||||
"LPT8", "LPT9", "LPT¹", "LPT²", "LPT³"
|
||||
];
|
||||
reserved = reserved.Concat(extraReservedWords);
|
||||
|
||||
// 找出匹配的保留字
|
||||
var matched = reserved.FirstOrDefault(r => r.Equals(nameWithoutExtension));
|
||||
|
||||
return matched ?? null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using PCL.Core.Utils.Exts;
|
||||
|
||||
namespace PCL.Core.Utils.Validate;
|
||||
|
||||
public class FolderNameValidator(
|
||||
string? parentFolder = null,
|
||||
bool useMinecraftCharCheck = true,
|
||||
bool ignoreCase = true,
|
||||
bool ignoreSameNameInParentFolder = false)
|
||||
: FileSystemValidator
|
||||
{
|
||||
public bool UseMinecraftCharCheck { get; set; } = useMinecraftCharCheck;
|
||||
public bool IgnoreCase { get; set; } = ignoreCase;
|
||||
public bool IgnoreSameNameInParentFolder { get; set; } = ignoreSameNameInParentFolder;
|
||||
public string? ParentFolder { get; set; } = parentFolder;
|
||||
|
||||
public FolderNameValidator() : this(null)
|
||||
{
|
||||
}
|
||||
|
||||
private void _BuildRules()
|
||||
{
|
||||
RuleFor(x => x)
|
||||
.Must(x => !string.IsNullOrWhiteSpace(x)).WithMessage("输入内容不能为空!")
|
||||
.Must(x => !x.StartsWith(' ')).WithMessage("文件名不能以空格开头!")
|
||||
.Must(x => !x.EndsWith(' ')).WithMessage("文件名不能以空格结尾!")
|
||||
.Must(x => !x.EndsWith('.')).WithMessage("文件名不能以小数点结尾!")
|
||||
.Custom((fileName, context) =>
|
||||
{
|
||||
var invalidChar = CheckInvalidStrings(fileName, UseMinecraftCharCheck ? ["!;"] : []);
|
||||
if (invalidChar is not null)
|
||||
{
|
||||
context.AddFailure($"文件名不可包含 {invalidChar} 字符!");
|
||||
}
|
||||
})
|
||||
.Custom((fileName, context) =>
|
||||
{
|
||||
var reservedWord = CheckReservedWord(fileName, []);
|
||||
if (reservedWord is not null)
|
||||
{
|
||||
context.AddFailure($"文件名不可为 {reservedWord}!");
|
||||
}
|
||||
})
|
||||
.Must(x => !x.IsMatch(RegexPatterns.Ntfs83FileName)).WithMessage("文件名不能包含这一特殊格式!")
|
||||
.Must(x =>
|
||||
{
|
||||
if (ParentFolder is null) return true;
|
||||
|
||||
var dirInfo = new DirectoryInfo(ParentFolder);
|
||||
if (!dirInfo.Exists) return true;
|
||||
if (IgnoreSameNameInParentFolder) return true;
|
||||
|
||||
return !dirInfo.EnumerateDirectories().Select(f => f.Name).Contains(x,
|
||||
IgnoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
|
||||
|
||||
}).WithMessage("不可与现有文件夹重名!");
|
||||
}
|
||||
|
||||
protected override bool PreValidate(ValidationContext<string> context, ValidationResult result)
|
||||
{
|
||||
_BuildRules();
|
||||
return base.PreValidate(context, result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using PCL.Core.Utils.Exts;
|
||||
|
||||
namespace PCL.Core.Utils.Validate;
|
||||
|
||||
public class FolderPathValidator(bool useMinecraftCharCheck) : FileSystemValidator
|
||||
{
|
||||
public bool UseMinecraftCharCheck { get; set; } = useMinecraftCharCheck;
|
||||
|
||||
public FolderPathValidator() : this(true)
|
||||
{
|
||||
}
|
||||
|
||||
private void _BuildRules()
|
||||
{
|
||||
RuleFor(x => x)
|
||||
.NotEmpty().WithMessage("输入内容不能为空!")
|
||||
.Must(x => !x.EndsWith(' ')).WithMessage("文件夹名不能以空格结尾!")
|
||||
.Must(x => !x.EndsWith('.')).WithMessage("文件夹名不能以小数点结尾!");
|
||||
|
||||
RuleForEach(x => _GetSubPaths(x))
|
||||
.Must(x => !string.IsNullOrWhiteSpace(x)).WithMessage("文件夹路径存在错误!")
|
||||
.Must(x => !x.StartsWith(' ')).WithMessage("文件夹名不能以空格开头!")
|
||||
.Must(x => !x.EndsWith(' ')).WithMessage("文件夹名不能以空格结尾!")
|
||||
.Must(x => !x.EndsWith('.')).WithMessage("文件夹名不能以小数点结尾!")
|
||||
.Custom((fileName, context) =>
|
||||
{
|
||||
var invalidChar = CheckInvalidStrings(fileName, UseMinecraftCharCheck ? ["!;"] : []);
|
||||
if (invalidChar is not null)
|
||||
{
|
||||
context.AddFailure($"文件夹名不可包含 {invalidChar} 字符!");
|
||||
}
|
||||
})
|
||||
.Custom((fileName, context) =>
|
||||
{
|
||||
var reservedWord = CheckReservedWord(fileName, []);
|
||||
if (reservedWord is not null)
|
||||
{
|
||||
context.AddFailure($"文件夹名不可为 {reservedWord}!");
|
||||
}
|
||||
})
|
||||
.Must(x => !x.IsMatch(RegexPatterns.Ntfs83FileName)).WithMessage("文件夹名不能包含这一特殊格式!")
|
||||
.OverridePropertyName("PathSegments");
|
||||
}
|
||||
|
||||
protected override bool PreValidate(ValidationContext<string> context, ValidationResult result)
|
||||
{
|
||||
_BuildRules();
|
||||
return base.PreValidate(context, result);
|
||||
}
|
||||
|
||||
private static string[] _GetSubPaths(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var fullPath = new DirectoryInfo(path).FullName;
|
||||
return fullPath[Path.GetPathRoot(fullPath)!.Length..]
|
||||
.TrimEnd(Path.DirectorySeparatorChar)
|
||||
.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using PCL.Core.Utils.Exts;
|
||||
|
||||
namespace PCL.Core.Utils.Validate;
|
||||
|
||||
public class HttpAndUncValidator(bool allowNullOrEmpty) : AbstractValidator<string>
|
||||
{
|
||||
public bool AllowsNullOrEmpty { get; set; } = allowNullOrEmpty;
|
||||
|
||||
public HttpAndUncValidator() : this(false)
|
||||
{
|
||||
}
|
||||
|
||||
private void _BuildRules()
|
||||
{
|
||||
RuleFor(x => x)
|
||||
.Must(x =>
|
||||
{
|
||||
if (AllowsNullOrEmpty && string.IsNullOrEmpty(x))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return x.IsMatch(RegexPatterns.HttpUri) || x.IsMatch(RegexPatterns.UncPath);
|
||||
}).WithMessage("输入的网址无效!");
|
||||
}
|
||||
|
||||
protected override bool PreValidate(ValidationContext<string> context, ValidationResult result)
|
||||
{
|
||||
_BuildRules();
|
||||
return base.PreValidate(context, result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using PCL.Core.Utils.Exts;
|
||||
|
||||
namespace PCL.Core.Utils.Validate;
|
||||
|
||||
public class HttpValidator(bool allowNullOrEmpty) : AbstractValidator<string>
|
||||
{
|
||||
public bool AllowsNullOrEmpty { get; set; } = allowNullOrEmpty;
|
||||
|
||||
public HttpValidator() : this(false)
|
||||
{
|
||||
}
|
||||
|
||||
private void _BuildRules()
|
||||
{
|
||||
RuleFor(x => x)
|
||||
.Must(x =>
|
||||
{
|
||||
if (AllowsNullOrEmpty && string.IsNullOrEmpty(x))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return x.IsMatch(RegexPatterns.HttpUri);
|
||||
}).WithMessage("输入的网址无效!");
|
||||
}
|
||||
|
||||
protected override bool PreValidate(ValidationContext<string> context, ValidationResult result)
|
||||
{
|
||||
_BuildRules();
|
||||
return base.PreValidate(context, result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
|
||||
namespace PCL.Core.Utils.Validate;
|
||||
|
||||
public class IntValidator(int max = int.MaxValue, int min = int.MinValue) : AbstractValidator<string>
|
||||
{
|
||||
public int Max { get; set; } = max;
|
||||
public int Min { get; set; } = min;
|
||||
|
||||
public IntValidator() : this(int.MaxValue)
|
||||
{
|
||||
}
|
||||
|
||||
private void _BuildRules()
|
||||
{
|
||||
RuleFor(x => x)
|
||||
.Must(x => x.Length < 9).WithMessage("请输入一个大小合理的数字!")
|
||||
.Must(x => int.TryParse(x, out _)).WithMessage("请输入一个整数!")
|
||||
.Must(x => int.TryParse(x, out var value) && value <= Max).WithMessage($"不可超过 {Max}!")
|
||||
.Must(x => int.TryParse(x, out var value) && value >= Min).WithMessage($"不可低于 {Min}!");
|
||||
}
|
||||
|
||||
protected override bool PreValidate(ValidationContext<string> context, ValidationResult result)
|
||||
{
|
||||
_BuildRules();
|
||||
return base.PreValidate(context, result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace PCL.Core.Utils.Validate;
|
||||
|
||||
public class NullOrEmptyValidator : AbstractValidator<string>
|
||||
{
|
||||
public NullOrEmptyValidator()
|
||||
{
|
||||
RuleFor(x => x)
|
||||
.Must(x => !string.IsNullOrEmpty(x)).WithMessage("输入内容不能为空!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace PCL.Core.Utils.Validate;
|
||||
|
||||
public class NullOrWhiteSpaceValidator : AbstractValidator<string>
|
||||
{
|
||||
public NullOrWhiteSpaceValidator()
|
||||
{
|
||||
RuleFor(x => x)
|
||||
.Must(x => !string.IsNullOrWhiteSpace(x)).WithMessage("输入内容不能为空!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using PCL.Core.App.Localization;
|
||||
|
||||
namespace PCL.Core.Utils.Validate;
|
||||
|
||||
public class RegexValidator(string pattern = "", string errorMessage = "正则检查失败!") : AbstractValidator<string>
|
||||
{
|
||||
public RegexValidator() : this(string.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
public string Pattern { get; set; } = pattern;
|
||||
public string ErrorMessage { get; set; } = errorMessage;
|
||||
public string ErrorKey { get; set; } = "";
|
||||
|
||||
private void _BuildRules()
|
||||
{
|
||||
var message = string.IsNullOrEmpty(ErrorKey) ? ErrorMessage : Lang.Text(ErrorKey);
|
||||
RuleFor(x => x)
|
||||
.Must(x => Regex.IsMatch(x, Pattern)).WithMessage(message);
|
||||
}
|
||||
|
||||
protected override bool PreValidate(ValidationContext<string> context, ValidationResult result)
|
||||
{
|
||||
_BuildRules();
|
||||
return base.PreValidate(context, result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
|
||||
namespace PCL.Core.Utils.Validate;
|
||||
|
||||
public class StringLengthValidator(int min = 0, int max = int.MaxValue) : AbstractValidator<string>
|
||||
{
|
||||
public int Min { get; set; } = min;
|
||||
public int Max { get; set; } = max;
|
||||
|
||||
public StringLengthValidator() : this(0)
|
||||
{
|
||||
}
|
||||
|
||||
private void _BuildRules()
|
||||
{
|
||||
RuleFor(x => x)
|
||||
.Must(x => x.Length != Max || Max == Min).WithMessage($"长度必须为 {Max} 个字符!")
|
||||
.Must(x => x.Length >= Min).WithMessage($"长度至少为 {Min} 个字符!")
|
||||
.Must(x => x.Length <= Max).WithMessage($"长度最长为 {Max} 个字符!");
|
||||
}
|
||||
|
||||
protected override bool PreValidate(ValidationContext<string> context, ValidationResult result)
|
||||
{
|
||||
_BuildRules();
|
||||
return base.PreValidate(context, result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user