All checks were successful
continuous-integration/drone/push Build is passing
207 lines
8.0 KiB
C#
207 lines
8.0 KiB
C#
using CampusErrand.Data;
|
||
using CampusErrand.Models;
|
||
using CampusErrand.Models.Dtos;
|
||
using Microsoft.EntityFrameworkCore;
|
||
|
||
namespace CampusErrand.Endpoints;
|
||
|
||
public static class ConfigEndpoints
|
||
{
|
||
public static void MapConfigEndpoints(this WebApplication app)
|
||
{
|
||
// 获取页面顶图配置
|
||
app.MapGet("/api/config/page-banner/{page}", async (string page, AppDbContext db) =>
|
||
{
|
||
var key = $"page_banner_{page}";
|
||
var config = await db.SystemConfigs.FirstOrDefaultAsync(c => c.Key == key);
|
||
return Results.Ok(new ConfigResponse
|
||
{
|
||
Key = key,
|
||
Value = config?.Value ?? "",
|
||
UpdatedAt = config?.UpdatedAt ?? DateTime.MinValue
|
||
});
|
||
});
|
||
|
||
// 获取客服二维码
|
||
app.MapGet("/api/config/qrcode", async (AppDbContext db) =>
|
||
{
|
||
var config = await db.SystemConfigs.FirstOrDefaultAsync(c => c.Key == "qrcode");
|
||
return Results.Ok(new ConfigResponse
|
||
{
|
||
Key = "qrcode",
|
||
Value = config?.Value ?? "",
|
||
UpdatedAt = config?.UpdatedAt ?? DateTime.MinValue
|
||
});
|
||
});
|
||
|
||
// 获取用户协议
|
||
app.MapGet("/api/config/agreement", async (AppDbContext db) =>
|
||
{
|
||
var config = await db.SystemConfigs.FirstOrDefaultAsync(c => c.Key == "agreement");
|
||
return Results.Ok(new ConfigResponse
|
||
{
|
||
Key = "agreement",
|
||
Value = config?.Value ?? "",
|
||
UpdatedAt = config?.UpdatedAt ?? DateTime.MinValue
|
||
});
|
||
});
|
||
|
||
// 获取隐私政策
|
||
app.MapGet("/api/config/privacy", async (AppDbContext db) =>
|
||
{
|
||
var config = await db.SystemConfigs.FirstOrDefaultAsync(c => c.Key == "privacy");
|
||
return Results.Ok(new ConfigResponse
|
||
{
|
||
Key = "privacy",
|
||
Value = config?.Value ?? "",
|
||
UpdatedAt = config?.UpdatedAt ?? DateTime.MinValue
|
||
});
|
||
});
|
||
|
||
// 获取跑腿协议
|
||
app.MapGet("/api/config/runner-agreement", async (AppDbContext db) =>
|
||
{
|
||
var config = await db.SystemConfigs.FirstOrDefaultAsync(c => c.Key == "runner_agreement");
|
||
return Results.Ok(new ConfigResponse
|
||
{
|
||
Key = "runner_agreement",
|
||
Value = config?.Value ?? "",
|
||
UpdatedAt = config?.UpdatedAt ?? DateTime.MinValue
|
||
});
|
||
});
|
||
|
||
// 获取提现说明
|
||
app.MapGet("/api/config/withdrawal-guide", async (AppDbContext db) =>
|
||
{
|
||
var config = await db.SystemConfigs.FirstOrDefaultAsync(c => c.Key == "withdrawal_guide");
|
||
return Results.Ok(new ConfigResponse
|
||
{
|
||
Key = "withdrawal_guide",
|
||
Value = config?.Value ?? "",
|
||
UpdatedAt = config?.UpdatedAt ?? DateTime.MinValue
|
||
});
|
||
});
|
||
|
||
// 获取最低佣金配置
|
||
app.MapGet("/api/config/min-commission", async (AppDbContext db) =>
|
||
{
|
||
var config = await db.SystemConfigs.FirstOrDefaultAsync(c => c.Key == "min_commission");
|
||
return Results.Ok(new ConfigResponse
|
||
{
|
||
Key = "min_commission",
|
||
Value = config?.Value ?? "1.0",
|
||
UpdatedAt = config?.UpdatedAt ?? DateTime.MinValue
|
||
});
|
||
});
|
||
|
||
// 管理端获取指定配置
|
||
app.MapGet("/api/admin/config/{key}", async (string key, AppDbContext db) =>
|
||
{
|
||
var config = await db.SystemConfigs.FirstOrDefaultAsync(c => c.Key == key);
|
||
return Results.Ok(new ConfigResponse
|
||
{
|
||
Key = key,
|
||
Value = config?.Value ?? "",
|
||
UpdatedAt = config?.UpdatedAt ?? DateTime.MinValue
|
||
});
|
||
}).RequireAuthorization("AdminOnly");
|
||
|
||
// 管理端更新系统配置
|
||
app.MapPut("/api/admin/config/{key}", async (string key, UpdateConfigRequest request, AppDbContext db) =>
|
||
{
|
||
// 允许的配置键白名单
|
||
var allowedKeys = new HashSet<string>
|
||
{
|
||
"qrcode", "agreement", "privacy", "runner_agreement", "withdrawal_guide", "freeze_days",
|
||
"min_commission",
|
||
"page_banner_pickup", "page_banner_delivery", "page_banner_help", "page_banner_purchase", "page_banner_food",
|
||
"page_banner_order-hall"
|
||
};
|
||
|
||
if (!allowedKeys.Contains(key))
|
||
return Results.BadRequest(new { code = 400, message = $"不支持的配置键: {key}" });
|
||
|
||
var config = await db.SystemConfigs.FirstOrDefaultAsync(c => c.Key == key);
|
||
if (config == null)
|
||
{
|
||
config = new SystemConfig
|
||
{
|
||
Key = key,
|
||
Value = request.Value,
|
||
UpdatedAt = DateTime.UtcNow
|
||
};
|
||
db.SystemConfigs.Add(config);
|
||
}
|
||
else
|
||
{
|
||
config.Value = request.Value;
|
||
config.UpdatedAt = DateTime.UtcNow;
|
||
}
|
||
|
||
await db.SaveChangesAsync();
|
||
|
||
return Results.Ok(new ConfigResponse
|
||
{
|
||
Key = config.Key,
|
||
Value = config.Value,
|
||
UpdatedAt = config.UpdatedAt
|
||
});
|
||
}).RequireAuthorization("AdminOnly");
|
||
|
||
// 图片上传接口
|
||
app.MapPost("/api/upload/image", async (IFormFile file, IConfiguration config) =>
|
||
{
|
||
if (file == null || file.Length == 0)
|
||
return Results.BadRequest(new { code = 400, message = "请选择要上传的图片" });
|
||
|
||
// 文件大小校验(默认 5MB)
|
||
var maxSize = config.GetValue<long>("Upload:MaxFileSizeBytes", 5242880);
|
||
if (file.Length > maxSize)
|
||
return Results.BadRequest(new { code = 400, message = $"图片大小不能超过 {maxSize / 1024 / 1024}MB" });
|
||
|
||
// 文件扩展名校验
|
||
var allowedExtensions = config.GetSection("Upload:AllowedExtensions").Get<string[]>()
|
||
?? new[] { ".jpg", ".jpeg", ".png", ".gif", ".webp" };
|
||
var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
|
||
if (!allowedExtensions.Contains(ext))
|
||
return Results.BadRequest(new { code = 400, message = $"不支持的图片格式,仅支持 {string.Join(", ", allowedExtensions)}" });
|
||
|
||
// 上传到腾讯云 COS
|
||
var cosConfig = new COSXML.CosXmlConfig.Builder()
|
||
.IsHttps(true)
|
||
.SetRegion(config["COS:Region"])
|
||
.Build();
|
||
var credential = new COSXML.Auth.DefaultQCloudCredentialProvider(
|
||
config["COS:SecretId"], config["COS:SecretKey"], 600);
|
||
var cosXml = new COSXML.CosXmlServer(cosConfig, credential);
|
||
|
||
var bucket = config["COS:Bucket"]!;
|
||
var cosKey = $"uploads/{DateTime.UtcNow:yyyyMMdd}/{Guid.NewGuid()}{ext}";
|
||
|
||
// 将上传文件写入临时文件
|
||
var tempPath = Path.GetTempFileName();
|
||
try
|
||
{
|
||
using (var stream = new FileStream(tempPath, FileMode.Create))
|
||
{
|
||
await file.CopyToAsync(stream);
|
||
}
|
||
|
||
var putRequest = new COSXML.Model.Object.PutObjectRequest(bucket, cosKey, tempPath);
|
||
var putResult = cosXml.PutObject(putRequest);
|
||
|
||
if (putResult.httpCode != 200)
|
||
return Results.BadRequest(new { code = 400, message = "图片上传失败" });
|
||
|
||
var url = $"{config["COS:BaseUrl"]}/{cosKey}";
|
||
return Results.Ok(new UploadImageResponse { Url = url });
|
||
}
|
||
finally
|
||
{
|
||
if (File.Exists(tempPath)) File.Delete(tempPath);
|
||
}
|
||
}).RequireAuthorization()
|
||
.DisableAntiforgery();
|
||
}
|
||
}
|