新增农户功能升级:身份证OCR/OSS上传、头像采集、同名村检测、Region热度排序及表单体验优化
- Region 表新增热度列,热门省份优先显示 - 区县下拉按地级市分组、已录区县优先、支持全省搜索 - 组选择内置一组至二十组 - 银行卡号独立行+粗体预览,开户行独立行 - 新增农户类型:农户/个体户/合作社/经营集体/公司 - 身份证正反面支持高拍仪/摄像头OCR与手机扫码上传,附件存本地/OSS - 新增农户头像采集组件(摄像头/本地上传),预留人脸识别收购扩展 - 同名村弹窗提醒(防选错乡镇) - 编辑窗体禁止遮罩/Esc误关,整体加宽适配高频操作 - 修复二维码局域网手机无法访问(监听0.0.0.0、局域网IP识别、Vite代理兜底)
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
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 尚未配置授权验证,暂不支持删除");
|
||||
}
|
||||
Reference in New Issue
Block a user