From b883f24cc1eab8b376f5ce28c967e1ddb2d4ec64 Mon Sep 17 00:00:00 2001 From: zhaoyf <1783123481@qq.com> Date: Wed, 12 Aug 2026 14:21:09 +0800 Subject: [PATCH] =?UTF-8?q?add:pda=E6=96=B0=E7=89=88=E6=9C=AC=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E5=9C=A8=E7=BA=BF=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../appversion/AppVersionController.java | 69 +++++++ .../appversion/IAppVersionService.java | 20 ++ .../service/appversion/dao/AppVersion.java | 35 ++++ .../dao/mapper/AppVersionMapper.java | 9 + .../impl/AppVersionServiceImpl.java | 172 +++++++++++++++++ .../src/views/system/appVersion/index.vue | 180 ++++++++++++++++++ sql/sys_app_version.sql | 14 ++ 7 files changed, 499 insertions(+) create mode 100644 nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/controller/appversion/AppVersionController.java create mode 100644 nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/service/appversion/IAppVersionService.java create mode 100644 nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/service/appversion/dao/AppVersion.java create mode 100644 nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/service/appversion/dao/mapper/AppVersionMapper.java create mode 100644 nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/service/appversion/impl/AppVersionServiceImpl.java create mode 100644 nladmin-ui/src/views/system/appVersion/index.vue create mode 100644 sql/sys_app_version.sql diff --git a/nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/controller/appversion/AppVersionController.java b/nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/controller/appversion/AppVersionController.java new file mode 100644 index 0000000..c5b5797 --- /dev/null +++ b/nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/controller/appversion/AppVersionController.java @@ -0,0 +1,69 @@ +package org.nl.wms.system_manage.controller.appversion; + +import cn.dev33.satoken.annotation.SaIgnore; +import org.nl.common.base.ResponseData; +import org.nl.common.domain.query.PageQuery; +import org.nl.wms.system_manage.service.appversion.IAppVersionService; +import org.nl.wms.system_manage.service.appversion.dao.AppVersion; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.StreamUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +@RestController +@RequestMapping("/api/appVersion") +public class AppVersionController { + + @Autowired + private IAppVersionService appVersionService; + + @GetMapping + public ResponseEntity query(@RequestParam(required = false, name = "app_name") String appName, PageQuery page) { + return ResponseData.build(appVersionService.pageQuery(appName, page), HttpStatus.OK); + } + + @PostMapping("/publish") + public ResponseEntity publish(@RequestParam("app_name") String appName, + @RequestParam("version_name") String versionName, + @RequestParam("file") MultipartFile file) { + return ResponseData.build(appVersionService.publish(appName, versionName, file), HttpStatus.CREATED); + } + + @SaIgnore + @GetMapping("/check") + public ResponseEntity check(@RequestParam("app_name") String appName, + @RequestParam(required = false, name = "version_name") String versionName) { + return ResponseData.build(appVersionService.checkLatest(appName, versionName), HttpStatus.OK); + } + + @SaIgnore + @GetMapping("/download/{appVersionId}") + public void download(@PathVariable String appVersionId, HttpServletResponse response) throws IOException { + AppVersion version = appVersionService.getDownloadVersion(appVersionId); + File apk = new File(version.getApk_path()); + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + response.setContentLengthLong(apk.length()); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + + URLEncoder.encode(version.getApk_name(), StandardCharsets.UTF_8.name()).replace("+", "%20")); + try (FileInputStream input = new FileInputStream(apk)) { + StreamUtils.copy(input, response.getOutputStream()); + response.flushBuffer(); + } + } +} diff --git a/nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/service/appversion/IAppVersionService.java b/nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/service/appversion/IAppVersionService.java new file mode 100644 index 0000000..a17962a --- /dev/null +++ b/nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/service/appversion/IAppVersionService.java @@ -0,0 +1,20 @@ +package org.nl.wms.system_manage.service.appversion; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import org.nl.common.domain.query.PageQuery; +import org.nl.wms.system_manage.service.appversion.dao.AppVersion; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Map; + +public interface IAppVersionService extends IService { + + IPage pageQuery(String appName, PageQuery page); + + AppVersion publish(String appName, String versionName, MultipartFile apk); + + Map checkLatest(String appName, String versionName); + + AppVersion getDownloadVersion(String appVersionId); +} diff --git a/nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/service/appversion/dao/AppVersion.java b/nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/service/appversion/dao/AppVersion.java new file mode 100644 index 0000000..9940eeb --- /dev/null +++ b/nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/service/appversion/dao/AppVersion.java @@ -0,0 +1,35 @@ +package org.nl.wms.system_manage.service.appversion.dao; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; + +import java.io.Serializable; + +@Data +@TableName("sys_app_version") +public class AppVersion implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId("app_version_id") + private String app_version_id; + + private String app_name; + + private String version_name; + + private String apk_name; + + @JsonIgnore + private String apk_path; + + private Long apk_size; + + private String create_id; + + private String create_name; + + private String create_time; +} diff --git a/nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/service/appversion/dao/mapper/AppVersionMapper.java b/nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/service/appversion/dao/mapper/AppVersionMapper.java new file mode 100644 index 0000000..b401e7b --- /dev/null +++ b/nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/service/appversion/dao/mapper/AppVersionMapper.java @@ -0,0 +1,9 @@ +package org.nl.wms.system_manage.service.appversion.dao.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.nl.wms.system_manage.service.appversion.dao.AppVersion; + +@Mapper +public interface AppVersionMapper extends BaseMapper { +} diff --git a/nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/service/appversion/impl/AppVersionServiceImpl.java b/nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/service/appversion/impl/AppVersionServiceImpl.java new file mode 100644 index 0000000..86d06f2 --- /dev/null +++ b/nladmin-system/wms-server/src/main/java/org/nl/wms/system_manage/service/appversion/impl/AppVersionServiceImpl.java @@ -0,0 +1,172 @@ +package org.nl.wms.system_manage.service.appversion.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.nl.common.domain.query.PageQuery; +import org.nl.common.exception.BadRequestException; +import org.nl.common.utils.SecurityUtils; +import org.nl.config.FileProperties; +import org.nl.wms.system_manage.service.appversion.IAppVersionService; +import org.nl.wms.system_manage.service.appversion.dao.AppVersion; +import org.nl.wms.system_manage.service.appversion.dao.mapper.AppVersionMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class AppVersionServiceImpl extends ServiceImpl implements IAppVersionService { + + @Autowired + private FileProperties fileProperties; + + @Override + public IPage pageQuery(String appName, PageQuery page) { + return page(page.build(AppVersion.class), new LambdaQueryWrapper() + .like(StrUtil.isNotBlank(appName), AppVersion::getApp_name, appName) + .orderByDesc(AppVersion::getCreate_time)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public AppVersion publish(String appName, String versionName, MultipartFile apk) { + if (StrUtil.isBlank(appName) || StrUtil.isBlank(versionName)) { + throw new BadRequestException("应用名称和版本号不能为空"); + } + if (apk == null || apk.isEmpty() || !"apk".equalsIgnoreCase(getExtension(apk.getOriginalFilename()))) { + throw new BadRequestException("只能上传 APK 文件"); + } + if (apk.getSize() > fileProperties.getMaxSize() * 1024 * 1024) { + throw new BadRequestException("APK 文件超过允许大小"); + } + if (count(new LambdaQueryWrapper() + .eq(AppVersion::getApp_name, appName) + .eq(AppVersion::getVersion_name, versionName)) > 0) { + throw new BadRequestException("该应用版本已发布"); + } + + String apkName = safeFilePart(appName) + "-" + safeFilePart(versionName) + ".apk"; + File directory = new File(fileProperties.getPath().getPath(), "apk"); + File destination = new File(directory, apkName); + if (destination.exists()) { + throw new BadRequestException("同名 APK 文件已存在"); + } + if (!directory.exists() && !directory.mkdirs()) { + throw new BadRequestException("APK 存储目录创建失败"); + } + + try { + apk.transferTo(destination); + AppVersion appVersion = new AppVersion(); + appVersion.setApp_version_id(IdUtil.getSnowflake(1, 1).nextIdStr()); + appVersion.setApp_name(appName.trim()); + appVersion.setVersion_name(versionName.trim()); + appVersion.setApk_name(apkName); + appVersion.setApk_path(destination.getCanonicalPath()); + appVersion.setApk_size(apk.getSize()); + appVersion.setCreate_id(SecurityUtils.getCurrentUserId()); + appVersion.setCreate_name(SecurityUtils.getCurrentNickName()); + appVersion.setCreate_time(DateUtil.now()); + save(appVersion); + return appVersion; + } catch (Exception e) { + if (destination.exists()) { + destination.delete(); + } + if (e instanceof BadRequestException) { + throw (BadRequestException) e; + } + throw new BadRequestException("APK 保存失败"); + } + } + + @Override + public Map checkLatest(String appName, String versionName) { + if (StrUtil.isBlank(appName)) { + throw new BadRequestException("应用名称不能为空"); + } + List versions = list(new LambdaQueryWrapper() + .eq(AppVersion::getApp_name, appName.trim())); + AppVersion latest = null; + for (AppVersion version : versions) { + if (latest == null || compareVersion(version.getVersion_name(), latest.getVersion_name()) > 0) { + latest = version; + } + } + Map result = new HashMap<>(); + result.put("has_update", latest != null && compareVersion(latest.getVersion_name(), versionName) > 0); + if (latest != null) { + Map latestInfo = new HashMap<>(); + latestInfo.put("app_version_id", latest.getApp_version_id()); + latestInfo.put("app_name", latest.getApp_name()); + latestInfo.put("version_name", latest.getVersion_name()); + latestInfo.put("apk_name", latest.getApk_name()); + latestInfo.put("apk_size", latest.getApk_size()); + latestInfo.put("create_time", latest.getCreate_time()); + result.put("latest", latestInfo); + } else { + result.put("latest", null); + } + result.put("download_url", latest == null ? null : "/api/appVersion/download/" + latest.getApp_version_id()); + return result; + } + + @Override + public AppVersion getDownloadVersion(String appVersionId) { + AppVersion version = getById(appVersionId); + if (version == null || !new File(version.getApk_path()).isFile()) { + throw new BadRequestException("APK 文件不存在"); + } + return version; + } + + private String getExtension(String name) { + if (name == null || name.lastIndexOf('.') < 0) { + return ""; + } + return name.substring(name.lastIndexOf('.') + 1); + } + + private String safeFilePart(String value) { + String safeValue = value.trim().replaceAll("[\\\\/:*?\"<>|\\s]+", "_"); + if (safeValue.isEmpty() || ".".equals(safeValue) || "..".equals(safeValue)) { + throw new BadRequestException("应用名称或版本号不能用于文件名"); + } + return safeValue; + } + + private int compareVersion(String first, String second) { + String[] firstParts = normalizeVersion(first).split("\\."); + String[] secondParts = normalizeVersion(second).split("\\."); + int length = Math.max(firstParts.length, secondParts.length); + for (int index = 0; index < length; index++) { + int firstPart = index < firstParts.length ? parseVersionPart(firstParts[index]) : 0; + int secondPart = index < secondParts.length ? parseVersionPart(secondParts[index]) : 0; + if (firstPart != secondPart) { + return firstPart > secondPart ? 1 : -1; + } + } + return 0; + } + + private String normalizeVersion(String version) { + return version == null ? "0" : version.trim().replaceFirst("^[vV]", ""); + } + + private int parseVersionPart(String value) { + try { + return Integer.parseInt(value.replaceAll("\\D.*", "")); + } catch (NumberFormatException e) { + return 0; + } + } +} diff --git a/nladmin-ui/src/views/system/appVersion/index.vue b/nladmin-ui/src/views/system/appVersion/index.vue new file mode 100644 index 0000000..16f9da9 --- /dev/null +++ b/nladmin-ui/src/views/system/appVersion/index.vue @@ -0,0 +1,180 @@ + + + diff --git a/sql/sys_app_version.sql b/sql/sys_app_version.sql new file mode 100644 index 0000000..ef740e9 --- /dev/null +++ b/sql/sys_app_version.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS `sys_app_version` ( + `app_version_id` VARCHAR(32) NOT NULL COMMENT '版本记录标识', + `app_name` VARCHAR(100) NOT NULL COMMENT '应用名称', + `version_name` VARCHAR(50) NOT NULL COMMENT '版本号', + `apk_name` VARCHAR(255) NOT NULL COMMENT 'APK 存储文件名,格式:应用名称-版本号.apk', + `apk_path` VARCHAR(500) NOT NULL COMMENT 'APK 绝对存储路径', + `apk_size` BIGINT NOT NULL COMMENT 'APK 文件字节数', + `create_id` VARCHAR(32) DEFAULT NULL COMMENT '发布人标识', + `create_name` VARCHAR(100) DEFAULT NULL COMMENT '发布人名称', + `create_time` VARCHAR(25) NOT NULL COMMENT '发布时间', + PRIMARY KEY (`app_version_id`), + UNIQUE KEY `uk_sys_app_version_name_version` (`app_name`, `version_name`), + KEY `idx_sys_app_version_name` (`app_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='APP 版本发布记录';