Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24d67baf65 | ||
|
|
2306fbd36b | ||
|
|
a027bf5d9e |
@@ -366,3 +366,4 @@ MigrationBackup/
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
/IOT_API/appsettings.json
|
||||
/IOT_API/wwwroot
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model;
|
||||
using Model.Dto.Asset;
|
||||
using Model.Entity.Asset;
|
||||
using Model.Mapper;
|
||||
using Service.Interface;
|
||||
using SqlSugar;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IOT_API.Controllers.Asset
|
||||
@@ -11,16 +14,21 @@ namespace IOT_API.Controllers.Asset
|
||||
/// <summary>
|
||||
/// 资产管理 - 设备台账控制器
|
||||
/// </summary>
|
||||
[Route("api/Equipment")] //资产管理模块下的设备管理接口
|
||||
[ApiController]
|
||||
[Route("api/asset/equipment")]
|
||||
[Route("api/asset/equipment")] //资产管理模块下的设备管理接口
|
||||
public class EquipmentController : ControllerBase
|
||||
{
|
||||
private readonly IEquipmentService _equipmentService;
|
||||
private readonly IEquipmentAttachmentService _attachmentService;
|
||||
private readonly IWebHostEnvironment _webHostEnvironment;
|
||||
|
||||
public EquipmentController(IEquipmentService equipmentService)
|
||||
public EquipmentController(IEquipmentService equipmentService,
|
||||
IEquipmentAttachmentService attachmentService,
|
||||
IWebHostEnvironment webHostEnvironment)
|
||||
{
|
||||
_equipmentService = equipmentService;
|
||||
_attachmentService = attachmentService;
|
||||
_webHostEnvironment = webHostEnvironment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -32,22 +40,27 @@ namespace IOT_API.Controllers.Asset
|
||||
/// <param name="categoryId">设备分类Id(0表示不过滤)</param>
|
||||
/// <param name="status">设备状态(不传表示不过滤)</param>
|
||||
[HttpGet("list")]
|
||||
public async Task<Result<List<EquipmentEntity>>> GetList(int pageIndex = 1, int pageSize = 10,
|
||||
public async Task<Result<List<EquipmentDto>>> GetList(int pageIndex = 1, int pageSize = 10,
|
||||
string? keyword = null, long categoryId = 0, EquipmentStatusEnum? status = null)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var result = await _equipmentService.GetPagedAsync(pageIndex, pageSize, total, keyword, categoryId, status);
|
||||
Response.Headers["X-Total-Count"] = total.Value.ToString();
|
||||
return result;
|
||||
return result.IsSuccess
|
||||
? Result<List<EquipmentDto>>.Success(result.Data.ToDtoList())
|
||||
: Result<List<EquipmentDto>>.Error(result.Msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询全部设备(不分页,供下拉选择等场景使用)
|
||||
/// </summary>
|
||||
[HttpGet("all")]
|
||||
public async Task<Result<List<EquipmentEntity>>> GetAll()
|
||||
public async Task<Result<List<EquipmentDto>>> GetAll()
|
||||
{
|
||||
return await _equipmentService.GetAllAsync();
|
||||
var result = await _equipmentService.GetAllAsync();
|
||||
return result.IsSuccess
|
||||
? Result<List<EquipmentDto>>.Success(result.Data.ToDtoList())
|
||||
: Result<List<EquipmentDto>>.Error(result.Msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -55,29 +68,33 @@ namespace IOT_API.Controllers.Asset
|
||||
/// </summary>
|
||||
/// <param name="id">设备主键 Id</param>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<Result<EquipmentEntity>> GetById(long id)
|
||||
public async Task<Result<EquipmentDto>> GetById(long id)
|
||||
{
|
||||
return await _equipmentService.GetByIdAsync(id);
|
||||
var res = await _equipmentService.GetByIdAsync(id);
|
||||
return res.IsSuccess
|
||||
? Result<EquipmentDto>.Success(res.Data.ToDto())
|
||||
: Result<EquipmentDto>.Error(res.Msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增设备
|
||||
/// </summary>
|
||||
/// <param name="entity">设备实体</param>
|
||||
/// <param name="dto">设备 DTO(IsDel/CreateTime 等服务端字段不接受入参)</param>
|
||||
[HttpPost]
|
||||
public async Task<Result<bool>> Add([FromBody] EquipmentEntity entity)
|
||||
public async Task<Result<bool>> Add([FromBody] EquipmentDto dto)
|
||||
{
|
||||
return await _equipmentService.InsertAsync(entity);
|
||||
return await _equipmentService.InsertAsync(dto.ToEntity());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改设备
|
||||
/// </summary>
|
||||
/// <param name="id">设备主键 Id</param>
|
||||
/// <param name="entity">设备实体</param>
|
||||
/// <param name="id">设备主键 Id(以路由参数为准,忽略请求体中的 Id)</param>
|
||||
/// <param name="dto">设备 DTO(IsDel/CreateTime 等服务端字段不接受入参)</param>
|
||||
[HttpPut("{id}")]
|
||||
public async Task<Result<bool>> Update(long id, [FromBody] EquipmentEntity entity)
|
||||
public async Task<Result<bool>> Update(long id, [FromBody] EquipmentDto dto)
|
||||
{
|
||||
var entity = dto.ToEntity();
|
||||
entity.Id = id;
|
||||
return await _equipmentService.UpdateAsync(entity);
|
||||
}
|
||||
@@ -96,11 +113,11 @@ namespace IOT_API.Controllers.Asset
|
||||
/// 变更设备状态(同步记录状态变更历史)
|
||||
/// </summary>
|
||||
/// <param name="id">设备主键 Id</param>
|
||||
/// <param name="request">状态变更请求</param>
|
||||
/// <param name="dto">状态变更请求(目标状态/操作人/备注)</param>
|
||||
[HttpPost("{id}/status")]
|
||||
public async Task<Result<bool>> ChangeStatus(long id, [FromBody] ChangeStatusRequest request)
|
||||
public async Task<Result<bool>> ChangeStatus(long id, [FromBody] EquipmentStatusChangeDto dto)
|
||||
{
|
||||
return await _equipmentService.ChangeStatusAsync(id, request.Status, request.Operator, request.Remark);
|
||||
return await _equipmentService.ChangeStatusAsync(id, dto.Status, dto.Operator, dto.Remark);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -108,30 +125,65 @@ namespace IOT_API.Controllers.Asset
|
||||
/// </summary>
|
||||
/// <param name="id">设备主键 Id</param>
|
||||
[HttpGet("{id}/status-records")]
|
||||
public async Task<Result<List<EquipmentStatusRecordEntity>>> GetStatusRecords(long id)
|
||||
public async Task<Result<List<EquipmentStatusRecordDto>>> GetStatusRecords(long id)
|
||||
{
|
||||
return await _equipmentService.GetStatusRecordsAsync(id);
|
||||
var res = await _equipmentService.GetStatusRecordsAsync(id);
|
||||
return res.IsSuccess
|
||||
? Result<List<EquipmentStatusRecordDto>>.Success(res.Data.ToDtoList())
|
||||
: Result<List<EquipmentStatusRecordDto>>.Error(res.Msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询设备附件列表(资产附件管理)
|
||||
/// </summary>
|
||||
/// <param name="id">设备主键 Id</param>
|
||||
[HttpGet("{id}/attachments")]
|
||||
public async Task<Result<List<EquipmentAttachmentDto>>> GetAttachments(long id)
|
||||
{
|
||||
var res = await _attachmentService.GetByEquipmentAsync(id);
|
||||
return res.IsSuccess
|
||||
? Result<List<EquipmentAttachmentDto>>.Success(res.Data.ToDtoList())
|
||||
: Result<List<EquipmentAttachmentDto>>.Error(res.Msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传设备附件(multipart/form-data,文件字段名 file)
|
||||
/// </summary>
|
||||
/// <param name="id">设备主键 Id</param>
|
||||
/// <param name="file">上传的文件</param>
|
||||
/// <param name="fileType">文件类型(1使用说明书/2合格证明/3校准证书/4保养卡/5合同扫描件/6验收照片/99其他)</param>
|
||||
/// <param name="uploader">上传人</param>
|
||||
/// <param name="remark">备注</param>
|
||||
[HttpPost("{id}/attachments")]
|
||||
[RequestSizeLimit(50 * 1024 * 1024)]
|
||||
public async Task<Result<EquipmentAttachmentDto>> UploadAttachment(long id, IFormFile file,
|
||||
[FromForm] AttachmentTypeEnum fileType = AttachmentTypeEnum.Other,
|
||||
[FromForm] string? uploader = null, [FromForm] string? remark = null)
|
||||
{
|
||||
if (file == null || file.Length == 0)
|
||||
{
|
||||
return Result<EquipmentAttachmentDto>.Error("请选择要上传的文件");
|
||||
}
|
||||
|
||||
// wwwroot 不存在时退回运行目录下的 wwwroot(上传时会自动创建)
|
||||
string webRootPath = _webHostEnvironment.WebRootPath
|
||||
?? Path.Combine(_webHostEnvironment.ContentRootPath, "wwwroot");
|
||||
|
||||
using var stream = file.OpenReadStream();
|
||||
var res = await _attachmentService.UploadAsync(id, stream, file.FileName, file.Length, fileType, uploader, remark, webRootPath);
|
||||
return res.IsSuccess
|
||||
? Result<EquipmentAttachmentDto>.Success(res.Data.ToDto())
|
||||
: Result<EquipmentAttachmentDto>.Error(res.Msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除设备附件
|
||||
/// </summary>
|
||||
/// <param name="attachmentId">附件主键 Id</param>
|
||||
[HttpDelete("attachments/{attachmentId}")]
|
||||
public async Task<Result<bool>> DeleteAttachment(long attachmentId)
|
||||
{
|
||||
return await _attachmentService.DeleteAsync(attachmentId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态变更请求体
|
||||
/// </summary>
|
||||
public class ChangeStatusRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 目标状态
|
||||
/// </summary>
|
||||
public EquipmentStatusEnum Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作人
|
||||
/// </summary>
|
||||
public string? Operator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变更原因/备注
|
||||
/// </summary>
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,4 +17,8 @@
|
||||
<ProjectReference Include="..\Service\Service.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Converters\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace WebAPI
|
||||
options.JsonSerializerOptions.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString;
|
||||
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = null;
|
||||
|
||||
});
|
||||
// 自动注册业务服务(Service.Interface -> Service.Implement)
|
||||
builder.Services.AddBusinessServices();
|
||||
@@ -63,6 +64,9 @@ namespace WebAPI
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
// 静态文件:设备附件上传后通过 /uploads/... 访问(存储于 wwwroot)
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
|
||||
@@ -16,8 +16,8 @@ namespace Model.Dto.Asset
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime? CreateTime { get; set; }
|
||||
|
||||
/// <summary>设备Id(关联 EquipmentEntity.Id)</summary>
|
||||
public long EquipmentId { get; set; }
|
||||
/// <summary>设备Id(关联 EquipmentEntity.Id,雪花 Id 转 string 防精度丢失)</summary>
|
||||
public string EquipmentId { get; set; }
|
||||
|
||||
/// <summary>文件名称</summary>
|
||||
public string? FileName { get; set; }
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace Model.Dto.Asset
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime? CreateTime { get; set; }
|
||||
|
||||
/// <summary>父级分类Id(0表示顶级)</summary>
|
||||
public long ParentId { get; set; }
|
||||
/// <summary>父级分类Id(0表示顶级,雪花 Id 转 string 防精度丢失)</summary>
|
||||
public string ParentId { get; set; }
|
||||
|
||||
/// <summary>分类名称</summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace Model.Dto.Asset
|
||||
/// </summary>
|
||||
public class EquipmentDto
|
||||
{
|
||||
/// <summary>主键 Id(long → string)</summary>
|
||||
public string Id { get; set; }
|
||||
/// <summary>主键 Id(long → string;入参可空,新增时无需传递)</summary>
|
||||
public string? Id { get; set; }
|
||||
|
||||
/// <summary>删除状态(0、未删除;1、已删除)</summary>
|
||||
public byte IsDel { get; set; }
|
||||
@@ -22,8 +22,8 @@ namespace Model.Dto.Asset
|
||||
/// <summary>设备名称</summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>设备分类Id(关联 EquipmentCategoryEntity.Id)</summary>
|
||||
public long CategoryId { get; set; }
|
||||
/// <summary>设备分类Id(关联 EquipmentCategoryEntity.Id,雪花 Id 转 string 防精度丢失;可空,未分类时为 "0" 或不传)</summary>
|
||||
public string? CategoryId { get; set; }
|
||||
|
||||
/// <summary>设备类型</summary>
|
||||
public string? Type { get; set; }
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using Model.Entity.Asset;
|
||||
|
||||
namespace Model.Dto.Asset
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备状态变更请求 DTO(POST api/asset/equipment/{id}/status 入参)
|
||||
/// </summary>
|
||||
public class EquipmentStatusChangeDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 目标状态(必填)
|
||||
/// </summary>
|
||||
public EquipmentStatusEnum Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作人
|
||||
/// </summary>
|
||||
public string? Operator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变更原因/备注
|
||||
/// </summary>
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,8 @@ namespace Model.Dto.Asset
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime? CreateTime { get; set; }
|
||||
|
||||
/// <summary>设备Id(关联 EquipmentEntity.Id)</summary>
|
||||
public long EquipmentId { get; set; }
|
||||
/// <summary>设备Id(关联 EquipmentEntity.Id,雪花 Id 转 string 防精度丢失)</summary>
|
||||
public string EquipmentId { get; set; }
|
||||
|
||||
/// <summary>变更前状态</summary>
|
||||
public EquipmentStatusEnum FromStatus { get; set; }
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 文件名称
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 200)]
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string? FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -26,13 +26,13 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 文件扩展名(如 .pdf、.jpg)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 20)]
|
||||
[SugarColumn(Length = 20, IsNullable = true)]
|
||||
public string? FileExt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件存储地址(上传后存储的相对路径)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 200)]
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string? FileUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -43,13 +43,13 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 上传人
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? Uploader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 500)]
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 分类名称
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100)]
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分类编码
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -37,7 +37,7 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 500)]
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 设备编号(唯一)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备名称
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100)]
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -28,55 +28,55 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 设备类型(实验室/楼层/部门/位置/产品/自定义等分类维度下的类型名称,冗余便于展示)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100)]
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备品牌
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100)]
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? Brand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备型号
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100)]
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? Model { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 规格参数
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 200)]
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string? Specifications { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 制造商(设备制造商)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100)]
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? Manufacturer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 供应商(资产供应商)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100)]
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? Supplier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备图片地址(图片上传后存储的相对路径)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 200)]
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string? ImageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 二维码编号(一物一码)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? QrCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备描述
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDataType = "text")]
|
||||
[SugarColumn(ColumnDataType = "text", IsNullable = true)]
|
||||
public string? Description { get; set; }
|
||||
#endregion
|
||||
|
||||
@@ -84,31 +84,31 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 使用部门名称(资产使用部门)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 100)]
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? Department { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 存放位置(实验室/楼层/房间等位置信息)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 200)]
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string? Location { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 责任人(资产责任人)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? ResponsiblePerson { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 责任人联系电话(资产责任人电话)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 30)]
|
||||
[SugarColumn(Length = 30, IsNullable = true)]
|
||||
public string? ContactPhone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 保管人
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? Custodian { get; set; }
|
||||
#endregion
|
||||
|
||||
@@ -116,17 +116,19 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 购置日期
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? PurchaseDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 购置价格(资产购置价格)
|
||||
/// </summary>
|
||||
[SugarColumn(DecimalDigits = 2, Length = 18)]
|
||||
[SugarColumn(DecimalDigits = 2, Length = 18, IsNullable = true)]
|
||||
public decimal? PurchasePrice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 保修到期日期(资产保修到期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? WarrantyExpireDate { get; set; }
|
||||
#endregion
|
||||
|
||||
@@ -134,21 +136,25 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 验收日期(资产验收日期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? AcceptanceDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启用日期(资产启用日期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? EnableDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 使用年限(资产使用年限,单位:年)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public int? ServiceLifeYears { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 报废日期(资产报废日期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? ScrapDate { get; set; }
|
||||
#endregion
|
||||
|
||||
@@ -168,21 +174,25 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 上次校准日期(校准日期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LastCalibrationDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下次校准到期日期(用于判断是否超期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? NextCalibrationDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 检定日期
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? InspectionDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下次检定到期日期(用于判断是否超期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? NextInspectionDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -193,11 +203,13 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 上次保养日期(保养日期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LastMaintenanceDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下次保养到期日期(保养周期到期,用于判断是否超期)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? NextMaintenanceDate { get; set; }
|
||||
#endregion
|
||||
|
||||
@@ -205,24 +217,25 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 500)]
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建人
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? CreateBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新人(修改人)
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? UpdateBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新时间(修改时间)
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? UpdateTime { get; set; }
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -30,13 +30,13 @@ namespace Model.Entity.Asset
|
||||
/// <summary>
|
||||
/// 操作人
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50)]
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string? Operator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变更原因/备注
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 500)]
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Model.Mapper
|
||||
CreateTime = entity.CreateTime,
|
||||
Code = entity.Code,
|
||||
Name = entity.Name,
|
||||
CategoryId = entity.CategoryId,
|
||||
CategoryId = entity.CategoryId.ToString(),
|
||||
Type = entity.Type,
|
||||
Brand = entity.Brand,
|
||||
Model = entity.Model,
|
||||
@@ -71,6 +71,54 @@ namespace Model.Mapper
|
||||
{
|
||||
return entities?.Select(e => e.ToDto()).ToList() ?? new List<EquipmentDto>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EquipmentDto → EquipmentEntity(入参映射,用于新增/修改)
|
||||
/// 安全约定:IsDel/CreateTime/UpdateTime 等服务端管控字段刻意不映射,防止过度绑定(over-binding);
|
||||
/// Id 解析失败时为 0(新增时由雪花算法自动生成)
|
||||
/// </summary>
|
||||
public static EquipmentEntity ToEntity(this EquipmentDto dto)
|
||||
{
|
||||
if (dto == null) return null;
|
||||
return new EquipmentEntity
|
||||
{
|
||||
Id = long.TryParse(dto.Id, out var id) ? id : 0,
|
||||
Code = dto.Code,
|
||||
Name = dto.Name,
|
||||
CategoryId = long.TryParse(dto.CategoryId, out var categoryId) ? categoryId : 0,
|
||||
Type = dto.Type,
|
||||
Brand = dto.Brand,
|
||||
Model = dto.Model,
|
||||
Specifications = dto.Specifications,
|
||||
Manufacturer = dto.Manufacturer,
|
||||
Supplier = dto.Supplier,
|
||||
ImageUrl = dto.ImageUrl,
|
||||
QrCode = dto.QrCode,
|
||||
Description = dto.Description,
|
||||
Department = dto.Department,
|
||||
Location = dto.Location,
|
||||
ResponsiblePerson = dto.ResponsiblePerson,
|
||||
ContactPhone = dto.ContactPhone,
|
||||
Custodian = dto.Custodian,
|
||||
PurchaseDate = dto.PurchaseDate,
|
||||
PurchasePrice = dto.PurchasePrice,
|
||||
WarrantyExpireDate = dto.WarrantyExpireDate,
|
||||
AcceptanceDate = dto.AcceptanceDate,
|
||||
EnableDate = dto.EnableDate,
|
||||
ServiceLifeYears = dto.ServiceLifeYears,
|
||||
ScrapDate = dto.ScrapDate,
|
||||
Status = dto.Status,
|
||||
CalibrationStatus = dto.CalibrationStatus,
|
||||
LastCalibrationDate = dto.LastCalibrationDate,
|
||||
NextCalibrationDate = dto.NextCalibrationDate,
|
||||
InspectionDate = dto.InspectionDate,
|
||||
NextInspectionDate = dto.NextInspectionDate,
|
||||
MaintenanceStatus = dto.MaintenanceStatus,
|
||||
LastMaintenanceDate = dto.LastMaintenanceDate,
|
||||
NextMaintenanceDate = dto.NextMaintenanceDate,
|
||||
Remark = dto.Remark
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 设备附件
|
||||
@@ -85,7 +133,7 @@ namespace Model.Mapper
|
||||
Id = entity.Id.ToString(),
|
||||
IsDel = entity.IsDel,
|
||||
CreateTime = entity.CreateTime,
|
||||
EquipmentId = entity.EquipmentId,
|
||||
EquipmentId = entity.EquipmentId.ToString(),
|
||||
FileName = entity.FileName,
|
||||
FileType = entity.FileType,
|
||||
FileExt = entity.FileExt,
|
||||
@@ -117,7 +165,7 @@ namespace Model.Mapper
|
||||
Id = entity.Id.ToString(),
|
||||
IsDel = entity.IsDel,
|
||||
CreateTime = entity.CreateTime,
|
||||
ParentId = entity.ParentId,
|
||||
ParentId = entity.ParentId.ToString(),
|
||||
Name = entity.Name,
|
||||
Code = entity.Code,
|
||||
Level = entity.Level,
|
||||
@@ -147,7 +195,7 @@ namespace Model.Mapper
|
||||
Id = entity.Id.ToString(),
|
||||
IsDel = entity.IsDel,
|
||||
CreateTime = entity.CreateTime,
|
||||
EquipmentId = entity.EquipmentId,
|
||||
EquipmentId = entity.EquipmentId.ToString(),
|
||||
FromStatus = entity.FromStatus,
|
||||
ToStatus = entity.ToStatus,
|
||||
ChangeTime = entity.ChangeTime,
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
using Model;
|
||||
using Model.Entity.Asset;
|
||||
using ORM;
|
||||
using Service.Interface;
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Service.Implement
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备附件服务实现(资产附件管理)
|
||||
/// </summary>
|
||||
public class EquipmentAttachmentService : IEquipmentAttachmentService
|
||||
{
|
||||
private readonly SqlSugarRepository<EquipmentAttachmentEntity> _repository;
|
||||
|
||||
public EquipmentAttachmentService(SqlSugarRepository<EquipmentAttachmentEntity> repository)
|
||||
{
|
||||
_repository = repository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定设备的附件列表(按上传时间倒序)
|
||||
/// </summary>
|
||||
public async Task<Result<List<EquipmentAttachmentEntity>>> GetByEquipmentAsync(long equipmentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var list = await _repository.Entities
|
||||
.Where(x => x.EquipmentId == equipmentId && x.IsDel == 0)
|
||||
.OrderBy(x => x.CreateTime, OrderByType.Desc)
|
||||
.ToListAsync();
|
||||
return Result<List<EquipmentAttachmentEntity>>.Success(list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<List<EquipmentAttachmentEntity>>.Error("查询附件列表失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传附件(保存文件到 uploads 目录并写入附件记录)
|
||||
/// </summary>
|
||||
public async Task<Result<EquipmentAttachmentEntity>> UploadAsync(long equipmentId, Stream stream, string originalFileName, long fileSize,
|
||||
AttachmentTypeEnum fileType, string? uploader, string? remark, string webRootPath)
|
||||
{
|
||||
if (equipmentId <= 0)
|
||||
{
|
||||
return Result<EquipmentAttachmentEntity>.Error("设备 Id 无效");
|
||||
}
|
||||
if (stream == null || fileSize <= 0)
|
||||
{
|
||||
return Result<EquipmentAttachmentEntity>.Error("上传文件为空");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 按设备分目录存储:uploads/equipment/{equipmentId}/
|
||||
string relativeDir = Path.Combine("uploads", "equipment", equipmentId.ToString());
|
||||
string physicalDir = Path.Combine(webRootPath, relativeDir);
|
||||
if (!Directory.Exists(physicalDir))
|
||||
{
|
||||
Directory.CreateDirectory(physicalDir);
|
||||
}
|
||||
|
||||
// 存储文件名:时间戳 + 原扩展名,避免重名与中文路径问题
|
||||
string ext = Path.GetExtension(originalFileName);
|
||||
string storedName = $"{DateTime.Now:yyyyMMddHHmmssfff}{ext}";
|
||||
string physicalPath = Path.Combine(physicalDir, storedName);
|
||||
|
||||
using (var fileStream = new FileStream(physicalPath, FileMode.Create))
|
||||
{
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
var entity = new EquipmentAttachmentEntity
|
||||
{
|
||||
EquipmentId = equipmentId,
|
||||
FileName = originalFileName,
|
||||
FileType = fileType,
|
||||
FileExt = ext,
|
||||
FileUrl = $"/{relativeDir.Replace('\\', '/')}/{storedName}",
|
||||
FileSize = fileSize,
|
||||
Uploader = uploader,
|
||||
Remark = remark,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
|
||||
await _repository.Context.Insertable(entity).ExecuteCommandAsync();
|
||||
return Result<EquipmentAttachmentEntity>.Success(entity);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<EquipmentAttachmentEntity>.Error("上传附件失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除附件(软删除记录并删除物理文件)
|
||||
/// </summary>
|
||||
public async Task<Result<bool>> DeleteAsync(long id)
|
||||
{
|
||||
if (id <= 0)
|
||||
{
|
||||
return Result<bool>.Error("附件 Id 无效");
|
||||
}
|
||||
try
|
||||
{
|
||||
var attachment = await _repository.Entities
|
||||
.Where(x => x.Id == id && x.IsDel == 0)
|
||||
.FirstAsync();
|
||||
if (attachment == null)
|
||||
{
|
||||
return Result<bool>.Error("附件不存在或已被删除");
|
||||
}
|
||||
|
||||
var result = await _repository.Context.Updateable<EquipmentAttachmentEntity>()
|
||||
.SetColumns(x => x.IsDel == 1)
|
||||
.Where(x => x.Id == id)
|
||||
.ExecuteCommandAsync();
|
||||
|
||||
// 删除物理文件(失败不影响记录删除结果)
|
||||
try
|
||||
{
|
||||
string webRoot = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot");
|
||||
string physicalPath = Path.Combine(webRoot, (attachment.FileUrl ?? string.Empty).TrimStart('/').Replace('/', Path.DirectorySeparatorChar));
|
||||
if (File.Exists(physicalPath))
|
||||
{
|
||||
File.Delete(physicalPath);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 物理文件删除失败忽略
|
||||
}
|
||||
|
||||
return Result<bool>.Success(result > 0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Error("删除附件失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Model;
|
||||
using Model.Entity.Asset;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Service.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备附件服务接口(资产附件管理:分类存储、在线预览)
|
||||
/// </summary>
|
||||
public interface IEquipmentAttachmentService
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询指定设备的附件列表(按上传时间倒序)
|
||||
/// </summary>
|
||||
/// <param name="equipmentId">设备 Id</param>
|
||||
/// <returns>返回包含附件列表的 Result</returns>
|
||||
Task<Result<List<EquipmentAttachmentEntity>>> GetByEquipmentAsync(long equipmentId);
|
||||
|
||||
/// <summary>
|
||||
/// 上传附件(保存文件到 uploads 目录并写入附件记录)
|
||||
/// </summary>
|
||||
/// <param name="equipmentId">设备 Id</param>
|
||||
/// <param name="stream">文件流</param>
|
||||
/// <param name="originalFileName">原始文件名</param>
|
||||
/// <param name="fileSize">文件大小(字节)</param>
|
||||
/// <param name="fileType">文件类型(说明书/合格证/校准证书等)</param>
|
||||
/// <param name="uploader">上传人</param>
|
||||
/// <param name="remark">备注</param>
|
||||
/// <param name="webRootPath">站点静态文件根目录物理路径</param>
|
||||
/// <returns>返回新增附件记录的 Result</returns>
|
||||
Task<Result<EquipmentAttachmentEntity>> UploadAsync(long equipmentId, Stream stream, string originalFileName, long fileSize,
|
||||
AttachmentTypeEnum fileType, string? uploader, string? remark, string webRootPath);
|
||||
|
||||
/// <summary>
|
||||
/// 删除附件(软删除记录并删除物理文件)
|
||||
/// </summary>
|
||||
/// <param name="id">附件 Id</param>
|
||||
/// <returns>返回操作是否成功的 Result</returns>
|
||||
Task<Result<bool>> DeleteAsync(long id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user