自动构建

This commit is contained in:
2026-07-25 21:15:32 +08:00 Unverified
parent 2ac873c347
commit 1595d6f0c0
12 changed files with 579 additions and 65 deletions
@@ -0,0 +1,165 @@
using System.Text;
namespace Jiaowu.Api.Infrastructure.Configuration;
public static class EnvironmentFile
{
public const string PathVariableName = "JIAOWU_ENV_FILE";
public static string? Load(string? path = null)
{
var configuredPath = path;
if (string.IsNullOrWhiteSpace(configuredPath))
{
configuredPath = Environment.GetEnvironmentVariable(PathVariableName);
}
var isExplicit = !string.IsNullOrWhiteSpace(configuredPath);
var resolvedPath = isExplicit
? Path.GetFullPath(configuredPath!, Environment.CurrentDirectory)
: Path.Combine(AppContext.BaseDirectory, ".env");
if (!File.Exists(resolvedPath))
{
if (isExplicit)
{
throw new FileNotFoundException(
$"环境变量文件不存在:{resolvedPath}",
resolvedPath);
}
return null;
}
var values = Parse(resolvedPath);
foreach (var (key, value) in values)
{
if (Environment.GetEnvironmentVariable(key) is null)
{
Environment.SetEnvironmentVariable(key, value);
}
}
return resolvedPath;
}
private static Dictionary<string, string> Parse(string path)
{
var values = new Dictionary<string, string>(StringComparer.Ordinal);
var lineNumber = 0;
foreach (var sourceLine in File.ReadLines(path, Encoding.UTF8))
{
lineNumber++;
var line = sourceLine.Trim();
if (line.Length == 0 || line.StartsWith('#'))
{
continue;
}
if (line.StartsWith("export ", StringComparison.Ordinal))
{
line = line[7..].TrimStart();
}
var separatorIndex = line.IndexOf('=');
if (separatorIndex <= 0)
{
throw InvalidLine(path, lineNumber, "缺少 KEY=VALUE 分隔符");
}
var key = line[..separatorIndex].Trim();
if (!IsValidKey(key))
{
throw InvalidLine(path, lineNumber, $"变量名无效:{key}");
}
var rawValue = line[(separatorIndex + 1)..].Trim();
values[key] = ParseValue(path, lineNumber, rawValue);
}
return values;
}
private static string ParseValue(string path, int lineNumber, string rawValue)
{
if (rawValue.Length == 0)
{
return string.Empty;
}
if (rawValue[0] == '\'')
{
if (rawValue.Length < 2 || rawValue[^1] != '\'')
{
throw InvalidLine(path, lineNumber, "单引号值没有正确闭合");
}
return rawValue[1..^1];
}
if (rawValue[0] != '"')
{
return rawValue;
}
if (rawValue.Length < 2 || rawValue[^1] != '"')
{
throw InvalidLine(path, lineNumber, "双引号值没有正确闭合");
}
var value = rawValue[1..^1];
var result = new StringBuilder(value.Length);
for (var index = 0; index < value.Length; index++)
{
var current = value[index];
if (current != '\\' || index == value.Length - 1)
{
result.Append(current);
continue;
}
var escaped = value[++index];
switch (escaped)
{
case 'n':
result.Append('\n');
break;
case 'r':
result.Append('\r');
break;
case 't':
result.Append('\t');
break;
case '\\':
result.Append('\\');
break;
case '"':
result.Append('"');
break;
default:
result.Append('\\');
result.Append(escaped);
break;
}
}
return result.ToString();
}
private static bool IsValidKey(string key)
{
if (key.Length == 0 || !(char.IsAsciiLetter(key[0]) || key[0] == '_'))
{
return false;
}
return key.All(character =>
char.IsAsciiLetterOrDigit(character) || character == '_');
}
private static FormatException InvalidLine(
string path,
int lineNumber,
string reason) =>
new($"{path} 第 {lineNumber} 行无效:{reason}。");
}
+4
View File
@@ -13,6 +13,10 @@
<ItemGroup>
<Content Remove="wwwroot\**\*" />
<Content Update="appsettings.Development.json" CopyToPublishDirectory="Never" />
<Content
Include="..\..\.env.example"
Link=".env.example"
CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
+3
View File
@@ -1,6 +1,7 @@
using System.Text;
using System.Text.Json.Serialization;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Configuration;
using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Exams;
using Jiaowu.Api.Infrastructure.Middleware;
@@ -15,6 +16,8 @@ using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using System.Threading.RateLimiting;
EnvironmentFile.Load();
var migrateOnly = args.Contains("--migrate-only", StringComparer.OrdinalIgnoreCase);
var seedDemoData = args.Contains("--seed-demo-data", StringComparer.OrdinalIgnoreCase);
var confirmProductionDemoData = args.Contains(