- Region 表新增热度列,热门省份优先显示 - 区县下拉按地级市分组、已录区县优先、支持全省搜索 - 组选择内置一组至二十组 - 银行卡号独立行+粗体预览,开户行独立行 - 新增农户类型:农户/个体户/合作社/经营集体/公司 - 身份证正反面支持高拍仪/摄像头OCR与手机扫码上传,附件存本地/OSS - 新增农户头像采集组件(摄像头/本地上传),预留人脸识别收购扩展 - 同名村弹窗提醒(防选错乡镇) - 编辑窗体禁止遮罩/Esc误关,整体加宽适配高频操作 - 修复二维码局域网手机无法访问(监听0.0.0.0、局域网IP识别、Vite代理兜底)
80 lines
3.2 KiB
C#
80 lines
3.2 KiB
C#
namespace AgriculturalPlatform.Api.Services;
|
|
|
|
/// <summary>已保存文件的存储 key 与访问地址</summary>
|
|
public record StoredFile(string Key, string Url);
|
|
|
|
/// <summary>
|
|
/// 附件文件存储抽象。
|
|
/// 当前默认实现为本地存储(wwwroot/uploads);
|
|
/// 阿里云 OSS 授权验证配置好后(appsettings.json 的 Oss 节点),
|
|
/// 将 Storage:UseOss 设为 true 即可切换到 OSS。
|
|
/// </summary>
|
|
public interface IFileStorage
|
|
{
|
|
/// <summary>保存文件,返回存储 key 与访问 URL</summary>
|
|
Task<StoredFile> SaveAsync(string subDir, Stream stream, string fileName, string contentType);
|
|
|
|
/// <summary>按 key 删除文件(本地/OSS 通用)</summary>
|
|
Task DeleteAsync(string key);
|
|
}
|
|
|
|
/// <summary>本地磁盘存储(wwwroot/uploads),URL 即相对路径,由静态文件中间件直接访问</summary>
|
|
public sealed class LocalFileStorage(IWebHostEnvironment env) : IFileStorage
|
|
{
|
|
private readonly string _root = Path.Combine(
|
|
env.WebRootPath ?? Path.Combine(env.ContentRootPath, "wwwroot"), "uploads");
|
|
|
|
public async Task<StoredFile> SaveAsync(string subDir, Stream stream, string fileName, string contentType)
|
|
{
|
|
var dateDir = DateTime.Now.ToString("yyyyMM");
|
|
var dir = Path.Combine(_root, subDir, dateDir);
|
|
Directory.CreateDirectory(dir);
|
|
|
|
var ext = Path.GetExtension(fileName);
|
|
if (string.IsNullOrWhiteSpace(ext)) ext = ".jpg";
|
|
if (ext.Length > 10) ext = ".jpg";
|
|
var name = $"{Guid.NewGuid():N}{ext}";
|
|
var full = Path.Combine(dir, name);
|
|
|
|
await using var fs = File.Create(full);
|
|
await stream.CopyToAsync(fs);
|
|
|
|
var key = $"/uploads/{subDir}/{dateDir}/{name}";
|
|
return new StoredFile(key, key);
|
|
}
|
|
|
|
public Task DeleteAsync(string key)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(key) || !key.StartsWith("/uploads/")) return Task.CompletedTask;
|
|
try
|
|
{
|
|
var full = Path.Combine(_root, key.TrimStart('/').Replace('/', Path.DirectorySeparatorChar));
|
|
if (File.Exists(full)) File.Delete(full);
|
|
}
|
|
catch
|
|
{
|
|
// 忽略删除失败
|
|
}
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 阿里云 OSS 存储。授权验证(AccessKeyId/Secret、Endpoint、Bucket)后期配置提供;
|
|
/// 未配置前调用会抛出 InvalidOperationException,由接口统一转为友好提示。
|
|
/// </summary>
|
|
public sealed class OssFileStorage(IConfiguration cfg) : IFileStorage
|
|
{
|
|
public bool IsConfigured =>
|
|
!string.IsNullOrWhiteSpace(cfg["Oss:AccessKeyId"])
|
|
&& !string.IsNullOrWhiteSpace(cfg["Oss:AccessKeySecret"])
|
|
&& !string.IsNullOrWhiteSpace(cfg["Oss:Bucket"]);
|
|
|
|
public Task<StoredFile> SaveAsync(string subDir, Stream stream, string fileName, string contentType)
|
|
=> throw new InvalidOperationException(
|
|
"阿里云 OSS 尚未配置授权验证,请在 appsettings.json 的 Oss 节点填写 AccessKeyId / AccessKeySecret / Endpoint / Bucket 后重启服务");
|
|
|
|
public Task DeleteAsync(string key)
|
|
=> throw new InvalidOperationException("阿里云 OSS 尚未配置授权验证,暂不支持删除");
|
|
}
|