add:pda新版本发布在线更新

This commit is contained in:
zhaoyf
2026-08-12 14:21:09 +08:00
parent b40e11daf2
commit b883f24cc1
7 changed files with 499 additions and 0 deletions

View File

@@ -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<Object> query(@RequestParam(required = false, name = "app_name") String appName, PageQuery page) {
return ResponseData.build(appVersionService.pageQuery(appName, page), HttpStatus.OK);
}
@PostMapping("/publish")
public ResponseEntity<Object> 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<Object> 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();
}
}
}

View File

@@ -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<AppVersion> {
IPage<AppVersion> pageQuery(String appName, PageQuery page);
AppVersion publish(String appName, String versionName, MultipartFile apk);
Map<String, Object> checkLatest(String appName, String versionName);
AppVersion getDownloadVersion(String appVersionId);
}

View File

@@ -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;
}

View File

@@ -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<AppVersion> {
}

View File

@@ -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<AppVersionMapper, AppVersion> implements IAppVersionService {
@Autowired
private FileProperties fileProperties;
@Override
public IPage<AppVersion> pageQuery(String appName, PageQuery page) {
return page(page.build(AppVersion.class), new LambdaQueryWrapper<AppVersion>()
.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<AppVersion>()
.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<String, Object> checkLatest(String appName, String versionName) {
if (StrUtil.isBlank(appName)) {
throw new BadRequestException("应用名称不能为空");
}
List<AppVersion> versions = list(new LambdaQueryWrapper<AppVersion>()
.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<String, Object> result = new HashMap<>();
result.put("has_update", latest != null && compareVersion(latest.getVersion_name(), versionName) > 0);
if (latest != null) {
Map<String, Object> 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;
}
}
}