热更新包维护接口

This commit is contained in:
Jack 2025-04-22 00:29:57 +08:00
parent d49f3f2d70
commit a4aa10f1bc
7 changed files with 384 additions and 9 deletions

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2025. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
* Vestibulum commodo. Ut rhoncus gravida arcu.
*/
package com.suisung.mall.admin.controller.admin;
import cn.hutool.json.JSONObject;
import com.suisung.mall.admin.service.AdminAppMarketUpdateService;
import com.suisung.mall.common.api.CommonResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@Api(tags = "安卓应用市场 APP 版本检查")
@RestController
@RequestMapping("/admin/admin/app-market-update")
public class AdminAppMarketUpdateController {
@Resource
private AdminAppMarketUpdateService adminAppMarketUpdateService;
@ApiOperation(value = "安卓市场热更新记录搜索列表", notes = "安卓市场热更新记录搜索列表")
@RequestMapping(value = "/search/list", method = RequestMethod.POST)
public CommonResult searchList(@RequestBody JSONObject paramsJSON) {
return adminAppMarketUpdateService.searchListInAdmin(paramsJSON.getInt("marketId"), paramsJSON.getStr("keyword"), paramsJSON.getInt("pageNum"), paramsJSON.getInt("pageSize"));
}
@ApiOperation(value = "新增安卓市场热更新记录", notes = "新增安卓市场热更新记录")
@RequestMapping(value = "/add/new", method = RequestMethod.POST)
public CommonResult addNew(@RequestBody JSONObject paramsJSON) {
return adminAppMarketUpdateService.addNewInAdmin(paramsJSON);
}
@ApiOperation(value = "修改安卓市场热更新记录", notes = "修改安卓市场热更新记录")
@RequestMapping(value = "/modify", method = RequestMethod.POST)
public CommonResult modify(@RequestBody JSONObject paramsJSON) {
return adminAppMarketUpdateService.modifyInAdmin(paramsJSON);
}
@ApiOperation(value = "关闭安卓市场热更新记录", notes = "关闭安卓市场热更新记录")
@RequestMapping(value = "/enable-or-disable", method = RequestMethod.POST)
public CommonResult enableOrDisable(@RequestBody JSONObject paramsJSON) {
return adminAppMarketUpdateService.enableOrDisableInAdmin(paramsJSON.getLong("id"), paramsJSON.getInt("status"));
}
}

View File

@ -24,7 +24,7 @@ import javax.annotation.Resource;
@Api(tags = "安卓应用市场 APP 版本检查")
@RestController
@RequestMapping("/mobile/admin/app-market-update")
public class AdminAppMarketUpdateController {
public class AppMarketUpdateController {
@Resource
private AdminAppMarketUpdateService adminAppMarketUpdateService;

View File

@ -8,7 +8,9 @@
package com.suisung.mall.admin.service;
import cn.hutool.json.JSONObject;
import com.suisung.mall.common.api.CommonResult;
import com.suisung.mall.common.modules.admin.AdminAppMarketUpdate;
public interface AdminAppMarketUpdateService {
@ -23,4 +25,49 @@ public interface AdminAppMarketUpdateService {
CommonResult checkLatestVersion(Integer marketId, String packageName, Integer currVersionKey);
/**
* 后台管理员搜索列表
*
* @param marketId
* @param keyword
* @param pageNum
* @param pageSize
* @return
*/
CommonResult searchListInAdmin(Integer marketId, String keyword, Integer pageNum, Integer pageSize);
/**
* 后台管理员新增
*
* @param paramsJSON
* @return
*/
CommonResult addNewInAdmin(JSONObject paramsJSON);
/**
* 后台管理员修改
*
* @param paramsJSON
* @return
*/
CommonResult modifyInAdmin(JSONObject paramsJSON);
/**
* 后台管理员禁用/启用
*
* @param id
* @param status
* @return
*/
CommonResult enableOrDisableInAdmin(Long id, Integer status);
/**
* 获取一条记录
*
* @param marketId
* @param packageName
* @param versionKey
* @return
*/
AdminAppMarketUpdate getAppMarketUpdate(Integer marketId, String packageName, Integer versionKey);
}

View File

@ -9,14 +9,23 @@
package com.suisung.mall.admin.service.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.suisung.mall.admin.mapper.AdminAppMarketUpdateMapper;
import com.suisung.mall.admin.service.AdminAppMarketUpdateService;
import com.suisung.mall.common.api.CommonResult;
import com.suisung.mall.common.domain.UserDto;
import com.suisung.mall.common.modules.admin.AdminAppMarketUpdate;
import com.suisung.mall.common.pojo.dto.FileInfoDTO;
import com.suisung.mall.common.utils.StringUtils;
import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
import org.springframework.stereotype.Service;
import static com.suisung.mall.common.utils.ContextUtil.getCurrentUser;
@Service
public class AdminAppMarketUpdateServiceImpl extends BaseServiceImpl<AdminAppMarketUpdateMapper, AdminAppMarketUpdate> implements AdminAppMarketUpdateService {
/**
@ -33,20 +42,181 @@ public class AdminAppMarketUpdateServiceImpl extends BaseServiceImpl<AdminAppMar
return CommonResult.failed("缺少必要参数");
}
QueryWrapper<AdminAppMarketUpdate> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("market_id", marketId).eq("package_name", packageName)
.orderByDesc("version_key").last("limit 1");
AdminAppMarketUpdate adminAppMarketUpdate = getOne(queryWrapper);
AdminAppMarketUpdate adminAppMarketUpdate = getAppMarketUpdate(marketId, packageName, currVersionKey);
if (adminAppMarketUpdate == null) {
return CommonResult.failed("当前应用已是最新版本");
return CommonResult.failed("当前已是最新版本");
}
if (!ObjectUtil.isEmpty(currVersionKey) && adminAppMarketUpdate.getVersion_key() <= currVersionKey) {
return CommonResult.failed("当前应用已是最新版本");
return CommonResult.failed("当前已是最新版本");
}
// 更新包后缀处理
if (StrUtil.isNotBlank(adminAppMarketUpdate.getDownload_url()) && StrUtil.isBlank(adminAppMarketUpdate.getFile_ext())) {
adminAppMarketUpdate.setFile_ext(StringUtils.getFileExt(adminAppMarketUpdate.getDownload_url()));
}
return CommonResult.success(adminAppMarketUpdate);
}
/**
* 后台管理员搜索列表
*
* @param marketId
* @param keyword
* @return
*/
@Override
public CommonResult searchListInAdmin(Integer marketId, String keyword, Integer pageNum, Integer pageSize) {
QueryWrapper<AdminAppMarketUpdate> queryWrapper = new QueryWrapper<>();
if (ObjectUtil.isNotEmpty(marketId)) {
queryWrapper.eq("market_id", marketId);
}
if (StrUtil.isNotBlank(keyword)) {
queryWrapper.and(wrapper -> wrapper.like("package_name", keyword)
.or().like("version_name", keyword)
.or().like("description", keyword));
}
queryWrapper.orderByDesc("id");
Page<AdminAppMarketUpdate> list = lists(queryWrapper, pageNum, pageSize);
return CommonResult.success(list);
}
/**
* 后台管理员新增
*
* @param paramsJSON
* @return
*/
@Override
public CommonResult addNewInAdmin(JSONObject paramsJSON) {
String userId = "0";
UserDto user = getCurrentUser();
if (user != null) {
userId = String.valueOf(user.getId());
}
if (ObjectUtil.isEmpty(paramsJSON)) {
return CommonResult.failed("缺少必要参数");
}
AdminAppMarketUpdate adminAppMarketUpdate = JSONUtil.toBean(paramsJSON, AdminAppMarketUpdate.class);
if (ObjectUtil.isEmpty(adminAppMarketUpdate)) {
return CommonResult.failed("参数格式有误");
}
if (ObjectUtil.isEmpty(adminAppMarketUpdate.getMarket_id()) || ObjectUtil.isEmpty(adminAppMarketUpdate.getPackage_name())
|| ObjectUtil.isEmpty(adminAppMarketUpdate.getVersion_key()) || ObjectUtil.isEmpty(adminAppMarketUpdate.getVersion_name())
|| ObjectUtil.isEmpty(adminAppMarketUpdate.getDownload_url())) {
return CommonResult.failed("缺少关键参数");
}
AdminAppMarketUpdate record = getAppMarketUpdate(adminAppMarketUpdate.getMarket_id(), adminAppMarketUpdate.getPackage_name(), adminAppMarketUpdate.getVersion_key());
if (record != null) {
return CommonResult.failed("当前版本升级包已存在");
}
FileInfoDTO fileInfoDTO = StringUtils.getFileInfoFromUrl(adminAppMarketUpdate.getDownload_url());
if (fileInfoDTO != null) {
adminAppMarketUpdate.setPackage_size(fileInfoDTO.getFileSize());
adminAppMarketUpdate.setFile_ext(fileInfoDTO.getFileExtension());
adminAppMarketUpdate.setMd5_checksum(fileInfoDTO.getMd5());
}
adminAppMarketUpdate.setCreated_by(userId);
adminAppMarketUpdate.setUpdated_by(userId);
return add(adminAppMarketUpdate) ? CommonResult.success() : CommonResult.failed("操作失败");
}
/**
* 后台管理员修改
*
* @param paramsJSON
* @return
*/
@Override
public CommonResult modifyInAdmin(JSONObject paramsJSON) {
String userId = "0";
UserDto user = getCurrentUser();
if (user != null) {
userId = String.valueOf(user.getId());
}
if (ObjectUtil.isEmpty(paramsJSON)) {
return CommonResult.failed("缺少必要参数");
}
if (ObjectUtil.isEmpty(paramsJSON.getLong("id"))) {
return CommonResult.failed("缺少id参数");
}
AdminAppMarketUpdate adminAppMarketUpdate = JSONUtil.toBean(paramsJSON, AdminAppMarketUpdate.class);
if (ObjectUtil.isEmpty(adminAppMarketUpdate)) {
return CommonResult.failed("参数格式有误");
}
FileInfoDTO fileInfoDTO = StringUtils.getFileInfoFromUrl(adminAppMarketUpdate.getDownload_url());
if (fileInfoDTO != null) {
adminAppMarketUpdate.setPackage_size(fileInfoDTO.getFileSize());
adminAppMarketUpdate.setFile_ext(fileInfoDTO.getFileExtension());
adminAppMarketUpdate.setMd5_checksum(fileInfoDTO.getMd5());
}
adminAppMarketUpdate.setUpdated_by(userId);
return saveOrUpdate(adminAppMarketUpdate) ? CommonResult.success() : CommonResult.failed("操作失败");
}
/**
* 后台管理员禁用/启用
*
* @param id
* @param status
* @return
*/
@Override
public CommonResult enableOrDisableInAdmin(Long id, Integer status) {
Integer userId = 0;
UserDto user = getCurrentUser();
if (user != null) {
userId = user.getId();
}
if (ObjectUtil.isEmpty(id) || ObjectUtil.isEmpty(status)) {
return CommonResult.failed("缺少必要参数");
}
AdminAppMarketUpdate adminAppMarketUpdate = new AdminAppMarketUpdate();
adminAppMarketUpdate.setId(id);
adminAppMarketUpdate.setStatus(status);
adminAppMarketUpdate.setUpdated_by(String.valueOf(userId));
return saveOrUpdate(adminAppMarketUpdate) ? CommonResult.success() : CommonResult.failed("操作失败");
}
/**
* 获取一条记录
*
* @param marketId
* @param packageName
* @param versionKey
* @return
*/
@Override
public AdminAppMarketUpdate getAppMarketUpdate(Integer marketId, String packageName, Integer versionKey) {
if (ObjectUtil.isEmpty(marketId) || ObjectUtil.isEmpty(packageName) || ObjectUtil.isEmpty(versionKey)) {
return null;
}
QueryWrapper<AdminAppMarketUpdate> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("market_id", marketId).eq("package_name", packageName)
.eq("version_key", versionKey);
return findOne(queryWrapper);
}
}

View File

@ -46,6 +46,9 @@ public class AdminAppMarketUpdate implements Serializable {
@ApiModelProperty(value = "热更新包的下载链接,用户可通过此链接下载更新包完成应用更新")
private String download_url;
@ApiModelProperty(value = "更新包后缀apk wgt")
private String file_ext;
@ApiModelProperty(value = "热更新的发布时间,记录更新包正式发布的时刻")
private Date release_time;

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2025. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
* Vestibulum commodo. Ut rhoncus gravida arcu.
*/
package com.suisung.mall.common.pojo.dto;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value = "文件信息", description = "文件信息")
public class FileInfoDTO {
String md5;
String fileExtension;
long fileSize;
public FileInfoDTO(String md5, String fileExtension, long fileSize) {
this.md5 = md5;
this.fileExtension = fileExtension;
this.fileSize = fileSize;
}
@Override
public String toString() {
return "MD5: " + md5 + ", 文件后缀: " + fileExtension + ", 文件大小: " + fileSize + " 字节";
}
}

View File

@ -1,7 +1,9 @@
package com.suisung.mall.common.utils;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.*;
import com.suisung.mall.common.pojo.dto.FileInfoDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
@ -10,8 +12,12 @@ import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
@ -709,6 +715,64 @@ public final class StringUtils extends org.apache.commons.lang3.StringUtils {
return false;
}
/**
* 根据url文件生成文件MD5文摘签名
*
* @param fileUrl
* @return
*/
public static FileInfoDTO getFileInfoFromUrl(String fileUrl) {
if (StrUtil.isBlank(fileUrl)) {
return null;
}
try {
URL url = new URL(fileUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
// 获取文件大小
long fileSize = connection.getContentLengthLong();
// 获取文件后缀
String path = url.getPath();
String fileExtension = "";
int dotIndex = path.lastIndexOf('.');
if (dotIndex != -1) {
fileExtension = path.substring(dotIndex + 1);
}
try (InputStream inputStream = connection.getInputStream()) {
// 获取 MD5 消息摘要实例
MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] buffer = new byte[8192];
int bytesRead;
// 从输入流读取数据并更新摘要
while ((bytesRead = inputStream.read(buffer)) != -1) {
digest.update(buffer, 0, bytesRead);
}
// 完成摘要计算
byte[] md5Bytes = digest.digest();
// 将字节数组转换为十六进制字符串
StringBuilder md5String = new StringBuilder();
for (byte b : md5Bytes) {
md5String.append(String.format("%02x", b));
}
String md5 = md5String.toString();
return new FileInfoDTO(StrUtil.isBlank(md5) ? "" : md5, fileExtension, fileSize);
} finally {
connection.disconnect();
}
} catch (MalformedURLException e) {
logger.error("错误:提供的 URL 格式不正确,请检查 URL 字符串", e);
} catch (NoSuchAlgorithmException e) {
logger.error("错误:系统不支持 MD5 算法,这可能是 Java 环境的问题", e);
} catch (IOException e) {
logger.error("错误:在连接 URL 或读取文件内容时发生 I/O 异常,可能是网络问题或文件不存在", e);
}
return null;
}
/**
* 生成的随机数类型