From a9582f4fcd8073680f968dfc64859ac7d4114591 Mon Sep 17 00:00:00 2001 From: zpc Date: Tue, 23 Sep 2025 16:11:54 +0800 Subject: [PATCH] 321 --- .../Business/OdfAppUpdatesController.cs | 130 +++++++++++++ .../Controllers/CommonController.cs | 2 +- .../Controllers/WebApiController.cs | 39 ++++ ZR.Admin.WebApi/appsettings.json | 2 +- .../wwwroot/2025/0923/341f5ae608caea22.xlsx | Bin 0 -> 10867 bytes .../wwwroot/2025/0923/603d60c6c874695e.xlsx | Bin 0 -> 10867 bytes .../wwwroot/2025/0923/efd4757512a8be29.xlsx | Bin 0 -> 10867 bytes ZR.Model/Business/Dto/OdfAppUpdatesDto.cs | 93 +++++++++ ZR.Model/Business/OdfAppUpdates.cs | 42 +++++ .../IBusinessService/IOdfAppUpdatesService.cs | 33 ++++ ZR.Service/Business/OdfAppUpdatesService.cs | 176 ++++++++++++++++++ 11 files changed, 515 insertions(+), 2 deletions(-) create mode 100644 ZR.Admin.WebApi/Controllers/Business/OdfAppUpdatesController.cs create mode 100644 ZR.Admin.WebApi/wwwroot/2025/0923/341f5ae608caea22.xlsx create mode 100644 ZR.Admin.WebApi/wwwroot/2025/0923/603d60c6c874695e.xlsx create mode 100644 ZR.Admin.WebApi/wwwroot/2025/0923/efd4757512a8be29.xlsx create mode 100644 ZR.Model/Business/Dto/OdfAppUpdatesDto.cs create mode 100644 ZR.Model/Business/OdfAppUpdates.cs create mode 100644 ZR.Service/Business/IBusinessService/IOdfAppUpdatesService.cs create mode 100644 ZR.Service/Business/OdfAppUpdatesService.cs diff --git a/ZR.Admin.WebApi/Controllers/Business/OdfAppUpdatesController.cs b/ZR.Admin.WebApi/Controllers/Business/OdfAppUpdatesController.cs new file mode 100644 index 0000000..c5c4c8a --- /dev/null +++ b/ZR.Admin.WebApi/Controllers/Business/OdfAppUpdatesController.cs @@ -0,0 +1,130 @@ +using Microsoft.AspNetCore.Mvc; + +using System.Linq.Expressions; +using System.Threading.Tasks; + +using ZR.Model.Business; +using ZR.Model.Business.Dto; +using ZR.Service.Business; +using ZR.Service.Business.IBusinessService; + +//创建时间:2025-09-23 +namespace ZR.Admin.WebApi.Controllers.Business +{ + /// + /// App 更新表 + /// + [Route("business/OdfAppUpdates")] + public class OdfAppUpdatesController : BaseController + { + /// + /// App 更新表接口 + /// + private readonly IOdfAppUpdatesService _OdfAppUpdatesService; + + public OdfAppUpdatesController(IOdfAppUpdatesService OdfAppUpdatesService) + { + _OdfAppUpdatesService = OdfAppUpdatesService; + } + + /// + /// 查询App 更新表列表 + /// + /// + /// + [HttpGet("list")] + [ActionPermissionFilter(Permission = "odfappupdates:list")] + public IActionResult QueryOdfAppUpdates([FromQuery] OdfAppUpdatesQueryDto parm) + { + var response = _OdfAppUpdatesService.GetList(parm); + return SUCCESS(response); + } + + + /// + /// 查询App 更新表详情 + /// + /// + /// + [HttpGet("{Id}")] + [ActionPermissionFilter(Permission = "odfappupdates:query")] + public IActionResult GetOdfAppUpdates(int Id) + { + var response = _OdfAppUpdatesService.GetInfo(Id); + + var info = response.Adapt(); + return SUCCESS(info); + } + + /// + /// 修改状态 + /// + /// + [HttpPost("status")] + [ActionPermissionFilter(Permission = "odfappupdates:add")] + [Log(Title = "App 更新表", BusinessType = BusinessType.INSERT)] + public IActionResult OdfAppStatus([FromBody] OdfAppUpdatesStatusDto parm) + { + var modal = _OdfAppUpdatesService.GetById(parm.Id); + if (modal != null) + { + if (parm.IsActive) + { + // 先将所有激活状态设置为未激活 + _OdfAppUpdatesService.UpdateAllActiveToInactive(); + } + modal.IsActive = parm.IsActive; + var response = _OdfAppUpdatesService.UpdateOdfAppUpdates(modal); + + return SUCCESS(response); + } + return SUCCESS(null); + } + + /// + /// 添加App 更新表 + /// + /// + [HttpPost("add")] + [ActionPermissionFilter(Permission = "odfappupdates:add")] + [Log(Title = "App 更新表", BusinessType = BusinessType.INSERT)] + public IActionResult AddOdfAppUpdates([FromBody] OdfAppUpdatesDto parm) + { + var modal = parm.Adapt().ToCreate(HttpContext); + parm.CreateTime = DateTime.Now; + var response = _OdfAppUpdatesService.AddOdfAppUpdates(modal); + + return SUCCESS(response); + } + + /// + /// 更新App 更新表 + /// + /// + [HttpPut] + [ActionPermissionFilter(Permission = "odfappupdates:edit")] + [Log(Title = "App 更新表", BusinessType = BusinessType.UPDATE)] + public IActionResult UpdateOdfAppUpdates([FromBody] OdfAppUpdatesDto parm) + { + var modal = parm.Adapt().ToUpdate(HttpContext); + var response = _OdfAppUpdatesService.UpdateOdfAppUpdates(modal); + + return ToResponse(response); + } + + /// + /// 删除App 更新表 + /// + /// + [HttpPost("delete/{ids}")] + [ActionPermissionFilter(Permission = "odfappupdates:delete")] + [Log(Title = "App 更新表", BusinessType = BusinessType.DELETE)] + public IActionResult DeleteOdfAppUpdates([FromRoute] string ids) + { + var idArr = Tools.SplitAndConvert(ids); + + return ToResponse(_OdfAppUpdatesService.Delete(idArr)); + } + + } +} \ No newline at end of file diff --git a/ZR.Admin.WebApi/Controllers/CommonController.cs b/ZR.Admin.WebApi/Controllers/CommonController.cs index ede239c..8b693d2 100644 --- a/ZR.Admin.WebApi/Controllers/CommonController.cs +++ b/ZR.Admin.WebApi/Controllers/CommonController.cs @@ -103,7 +103,7 @@ namespace ZR.Admin.WebApi.Controllers /// [HttpPost] [ActionPermissionFilter(Permission = "common")] - public async Task UploadFile([FromForm] UploadDto uploadDto, IFormFile file, StoreType storeType = StoreType.ALIYUN) + public async Task UploadFile([FromForm] UploadDto uploadDto, IFormFile file, StoreType storeType = StoreType.LOCAL) { if (file == null) throw new CustomException(ResultCode.PARAM_ERROR, "上传文件不能为空"); SysFile sysfile = new(); diff --git a/ZR.Admin.WebApi/Controllers/WebApiController.cs b/ZR.Admin.WebApi/Controllers/WebApiController.cs index e30af06..0e2d1ec 100644 --- a/ZR.Admin.WebApi/Controllers/WebApiController.cs +++ b/ZR.Admin.WebApi/Controllers/WebApiController.cs @@ -21,5 +21,44 @@ namespace ZR.Admin.WebApi.Controllers public class WebApiController : BaseController { private readonly WechatApiClient _client; + private readonly IOdfAppUpdatesService _odfAppUpdatesService; + + public WebApiController(WechatApiClient client, IOdfAppUpdatesService odfAppUpdatesService) + { + _client = client; + _odfAppUpdatesService = odfAppUpdatesService; + } + + /// + /// 检查App版本更新 + /// + /// 版本 + /// + [HttpGet] + public IActionResult CheckAppVersion([FromQuery] string version) + { + if (version == null || string.IsNullOrEmpty(version)) + { + return ToResponse(ResultCode.CUSTOM_ERROR, "版本号不能为空"); + } + + try + { + var response = _odfAppUpdatesService.CheckAppVersion(version); + + if (response.NeedUpdate) + { + return SUCCESS(response, "发现新版本"); + } + else + { + return SUCCESS(new { }, "当前已是最新版本"); + } + } + catch (Exception ex) + { + return ToResponse(ResultCode.CUSTOM_ERROR, $"版本检查失败: {ex.Message}"); + } + } } } diff --git a/ZR.Admin.WebApi/appsettings.json b/ZR.Admin.WebApi/appsettings.json index 5b9fbf0..4d25b1d 100644 --- a/ZR.Admin.WebApi/appsettings.json +++ b/ZR.Admin.WebApi/appsettings.json @@ -49,7 +49,7 @@ "sqlExecutionTime": 5, //Sql执行时间超过多少秒记录日志并警报 "Upload": { "uploadUrl": "http://localhost:8888", //本地存储资源访问路径 - "localSavePath": "", //本地上传默认文件存储目录 wwwroot + "localSavePath": "/file/", //本地上传默认文件存储目录 wwwroot "maxSize": 15, //上传文件大小限制 15M "notAllowedExt": [ ".bat", ".exe", ".jar", ".js" ], "requestLimitSize": 50 //请求body大小限制 diff --git a/ZR.Admin.WebApi/wwwroot/2025/0923/341f5ae608caea22.xlsx b/ZR.Admin.WebApi/wwwroot/2025/0923/341f5ae608caea22.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..02aad158601697b77c33812f45d3abd3ff6c53a1 GIT binary patch literal 10867 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 + /// App 更新表查询对象 + /// + public class OdfAppUpdatesQueryDto : PagerInfo + { + public int? VersionCode { get; set; } + } + + /// + /// App 更新表输入输出对象 + /// + public class OdfAppUpdatesDto + { + public int? Id { get; set; } + + [Required(ErrorMessage = "版本号不能为空")] + public int VersionCode { get; set; } + + [Required(ErrorMessage = "更新类型不能为空")] + public string UpdateType { get; set; } + + [Required(ErrorMessage = "下载地址不能为空")] + public string DownloadUrl { get; set; } + + [Required(ErrorMessage = "是否启用不能为空")] + public bool IsActive { get; set; } + + public DateTime? CreateTime { get; set; } + + + + [ExcelColumn(Name = "更新类型")] + public string UpdateTypeLabel { get; set; } + } + + /// + /// App 更新表输入输出对象 + /// + public class OdfAppUpdatesStatusDto + { + public int? Id { get; set; } + + [Required(ErrorMessage = "是否启用不能为空")] + public bool IsActive { get; set; } + + } + + /// + /// App版本检查请求对象 + /// + public class AppVersionCheckRequestDto + { + /// + /// 当前App版本号 (如: 1.0.1) + /// + [Required(ErrorMessage = "版本号不能为空")] + public string Version { get; set; } + } + + /// + /// App版本检查响应对象 + /// + public class AppVersionCheckResponseDto + { + /// + /// 是否需要更新 + /// + public bool NeedUpdate { get; set; } + + /// + /// 是否强制更新 + /// + public bool ForceUpdate { get; set; } + + /// + /// 最新版本号 + /// + public string LatestVersion { get; set; } + + /// + /// 下载地址 + /// + public string DownloadUrl { get; set; } + + /// + /// 更新说明 + /// + public string UpdateDescription { get; set; } + } +} \ No newline at end of file diff --git a/ZR.Model/Business/OdfAppUpdates.cs b/ZR.Model/Business/OdfAppUpdates.cs new file mode 100644 index 0000000..1e4d47d --- /dev/null +++ b/ZR.Model/Business/OdfAppUpdates.cs @@ -0,0 +1,42 @@ + +namespace ZR.Model.Business +{ + /// + /// App 更新表 + /// + [SugarTable("odf_app_updates")] + public class OdfAppUpdates + { + /// + /// Id + /// + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public int? Id { get; set; } + + /// + /// 版本号 + /// + public int VersionCode { get; set; } + + /// + /// 更新类型 + /// + public string UpdateType { get; set; } + + /// + /// 下载地址 + /// + public string DownloadUrl { get; set; } + + /// + /// 是否启用 + /// + public bool IsActive { get; set; } + + /// + /// 发布时间 + /// + public DateTime? CreateTime { get; set; } + + } +} \ No newline at end of file diff --git a/ZR.Service/Business/IBusinessService/IOdfAppUpdatesService.cs b/ZR.Service/Business/IBusinessService/IOdfAppUpdatesService.cs new file mode 100644 index 0000000..c07021a --- /dev/null +++ b/ZR.Service/Business/IBusinessService/IOdfAppUpdatesService.cs @@ -0,0 +1,33 @@ +using ZR.Model.Business.Dto; +using ZR.Model.Business; + +namespace ZR.Service.Business.IBusinessService +{ + /// + /// App 更新表service接口 + /// + public interface IOdfAppUpdatesService : IBaseService + { + PagedInfo GetList(OdfAppUpdatesQueryDto parm); + + OdfAppUpdates GetInfo(int Id); + + + OdfAppUpdates AddOdfAppUpdates(OdfAppUpdates parm); + int UpdateOdfAppUpdates(OdfAppUpdates parm); + + /// + /// 批量更新所有激活状态为未激活 + /// + /// + int UpdateAllActiveToInactive(); + + /// + /// 检查App版本是否需要更新 + /// + /// 当前App版本号 + /// + AppVersionCheckResponseDto CheckAppVersion(string currentVersion); + + } +} diff --git a/ZR.Service/Business/OdfAppUpdatesService.cs b/ZR.Service/Business/OdfAppUpdatesService.cs new file mode 100644 index 0000000..bea0e5c --- /dev/null +++ b/ZR.Service/Business/OdfAppUpdatesService.cs @@ -0,0 +1,176 @@ +using Infrastructure.Attribute; +using Infrastructure.Extensions; +using ZR.Model.Business.Dto; +using ZR.Model.Business; +using ZR.Repository; +using ZR.Service.Business.IBusinessService; + +namespace ZR.Service.Business +{ + /// + /// App 更新表Service业务层处理 + /// + [AppService(ServiceType = typeof(IOdfAppUpdatesService), ServiceLifetime = LifeTime.Transient)] + public class OdfAppUpdatesService : BaseService, IOdfAppUpdatesService + { + /// + /// 查询App 更新表列表 + /// + /// + /// + public PagedInfo GetList(OdfAppUpdatesQueryDto parm) + { + var predicate = QueryExp(parm); + + var response = Queryable() + //.OrderBy("IsActive desc") + .Where(predicate.ToExpression()) + .ToPage(parm); + + return response; + } + + + /// + /// 获取详情 + /// + /// + /// + public OdfAppUpdates GetInfo(int Id) + { + var response = Queryable() + .Where(x => x.Id == Id) + .First(); + + return response; + } + + /// + /// 添加App 更新表 + /// + /// + /// + public OdfAppUpdates AddOdfAppUpdates(OdfAppUpdates model) + { + return Insertable(model).ExecuteReturnEntity(); + } + + /// + /// 修改App 更新表 + /// + /// + /// + public int UpdateOdfAppUpdates(OdfAppUpdates model) + { + return Update(model, true); + } + + /// + /// 批量更新所有激活状态为未激活 + /// + /// + public int UpdateAllActiveToInactive() + { + return Update(x => x.IsActive == true, x => new OdfAppUpdates { IsActive = false }); + } + + /// + /// 检查App版本是否需要更新 + /// + /// 当前App版本号 + /// + public AppVersionCheckResponseDto CheckAppVersion(string currentVersion) + { + var response = new AppVersionCheckResponseDto + { + NeedUpdate = false, + ForceUpdate = false + }; + + // 获取当前启用的最新版本 + var latestUpdate = Queryable() + .Where(x => x.IsActive == true) + .OrderByDescending(x => x.VersionCode) + .First(); + + if (latestUpdate == null) + { + return response; // 没有启用的版本,不需要更新 + } + + // 将版本号转换为可比较的格式 (如: 1.0.1 -> 1001) + var currentVersionCode = ConvertVersionToCode(currentVersion); + var latestVersionCode = latestUpdate.VersionCode; + + // 如果数据库中的版本号大于当前版本号,则需要更新 + if (latestVersionCode > currentVersionCode) + { + response.NeedUpdate = true; + response.LatestVersion = latestVersionCode.ToString(); + response.DownloadUrl = latestUpdate.DownloadUrl; + response.ForceUpdate = latestUpdate.UpdateType == "1"; // 假设UpdateType为"force"表示强制更新 + response.UpdateDescription = $"发现新版本 {response.LatestVersion},请及时更新"; + } + + return response; + } + + /// + /// 将版本号字符串转换为数字代码 (如: 1.0.1 -> 1001) + /// + /// 版本号字符串 + /// + private int ConvertVersionToCode(string version) + { + if (string.IsNullOrEmpty(version)) + return 0; + var newCode = version.Replace(".", ""); + if (int.TryParse(newCode, out int result)) + { + return result; + } + return 100; + //var parts = version.Split('.'); + //var code = 0; + //var multiplier = 100; // 支持到百位 + + //foreach (var part in parts) + //{ + // if (int.TryParse(part, out int num)) + // { + // code += num * multiplier; + // multiplier /= 100; // 每段占2位 + // } + //} + + //return code; + } + + /// + /// 将数字代码转换为版本号字符串 (如: 1001 -> 1.0.1) + /// + /// 数字代码 + /// + private string ConvertCodeToVersion(int code) + { + var major = code / 10000; + var minor = (code % 10000) / 100; + var patch = code % 100; + + return $"{major}.{minor}.{patch}"; + } + + /// + /// 查询导出表达式 + /// + /// + /// + private static Expressionable QueryExp(OdfAppUpdatesQueryDto parm) + { + var predicate = Expressionable.Create(); + + predicate = predicate.AndIF(parm.VersionCode != null, it => it.VersionCode == parm.VersionCode); + return predicate; + } + } +} \ No newline at end of file