目录
一、引言
在当前的移动互联网时代,APP的版本更新已经成为常态。如何快速、稳定地提供新版本的下载,是每个APP开发者和运营者需要考虑的问题。阿里云OSS(Object Storage Service)提供了一种解决方案,它可以帮助我们轻松实现APP版本包的存储和分发。
二、阿里云OSS简介
阿里云OSS是一种对象存储服务,它提供了海量、安全、低成本、高可靠的云存储服务。我们可以将APP的版本包上传到OSS,然后通过OSS提供的URL进行下载。详情可以阅读这位博主讲解的基础使用,首先,我们需要在阿里云控制台创建一个OSS存储空间,用于存储APP的版本包。
阿里云对象存储OSS简介和使用http://t.csdnimg.cn/3mmh4
三、代码实现
以下是使用阿里云SDK for Java上传APP版本包并下载的示例代码:
1.数据库
CREATE TABLE `app_version` (
`app_version_id` bigint(20) NOT NULL COMMENT '版本Id',
`version_number` varchar(50) DEFAULT NULL COMMENT '版本号',
`release_date` varchar(50) DEFAULT NULL COMMENT '发布日期',
`description` varchar(255) DEFAULT NULL COMMENT '版本的描述',
`force_update` tinyint(1) DEFAULT 0 COMMENT '是否需要强制更新',
`download_link` varchar(255) DEFAULT NULL COMMENT '版本的下载链接',
`is_deprecated` tinyint(1) DEFAULT NULL COMMENT '版本是否已被停用',
PRIMARY KEY (`app_version_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.实体类
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
@Data
public class AppVersion implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 版本Id
*/
@TableId
private Long appVersionId;
/**
* 版本号
*/
private String versionNumber;
/**
* 发布日期
*/
private String releaseDate;
/**
* 版本的描述
*/
private String description;
/**
* 是否需要强制更新
*/
private Integer forceUpdate;
/**
* 版本的下载链接
*/
private String downloadLink;
/**
* 版本是否已被停用
*/
private Integer isDeprecated;
}
3.控制层
@Api(tags = "版本检查更新")
@RestController
@RequestMapping("/appVersion")
public class AppVersionController {
@Resource
private AppVersionService appVersionService;
@ApiOperation(value = "上传安卓apk和修改版本号")
@ApiImplicitParams({
@ApiImplicitParam(name = "newAppVersion", value = "版本Id", required = true, dataTypeClass = String.class),
@ApiImplicitParam(name = "isForceUpdate", value = "是否强制更新", required = true, dataTypeClass = String.class)
})
@PostMapping("/update/android")
public @ResponseBody
Result<Object> updateAndroidVersion(@RequestParam("file") MultipartFile file,
@RequestParam("newAppVersion") String newAppVersion,
@RequestParam("isForceUpdate") String isForceUpdate) {
//ResponseUtil类是一个简单的封装返回结果工具类,重要的是service业务层
return ResponseUtil.getServiceResponseResult(appVersionService.updateAndroidVersion(file, newAppVersion, Integer.valueOf(isForceUpdate)));
}
@ApiOperation(value = "下载安卓apk")
@GetMapping("/download")
public String downloadApk(HttpServletResponse response) {
String resMsg = "";
try {
appVersionService.downloadApk(response);
} catch (Exception me) {
resMsg = me.getMessage();
}
return resMsg;
}}
4.业务层
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public interface AppVersionService extends IService<AppVersion> {
/**
* 上传安卓apk和修改版本号
*
* @param file 新版本软件包
* @param newAppVersion 新版本的版本号
* @param isForceUpdate 是否强制更新
* @return 是否成功
*/
BaseResult<Object> updateAndroidVersion(MultipartFile file, String newAppVersion, Integer isForceUpdate);
/**
* 下载安卓apk
*
* @param response 客户端响应对象
* @throws IOException IO异常
*/
void downloadApk(HttpServletResponse response) throws IOException;
}
@Service
@RefreshScope
public class AppVersionServiceImpl extends ServiceImpl<AppVersionMapper, AppVersion> implements AppVersionService {
@Resource
private AppVersionMapper appVersionMapper;
@Override
public BaseResult<Object> updateAndroidVersion(MultipartFile file, String newAppVersion, Integer isForceUpdate) {
// 文件是否是apk包
if (Objects.requireNonNull(file.getOriginalFilename()).endsWith(".exe") || Objects.requireNonNull(file.getOriginalFilename()).endsWith(".bat")) {
throw new MyException(MessageUtilsNew.get("subject.oss.file.upload.format.error"));
}
// 保留每个版本的apk包
String uploadFileName;
if (!file.isEmpty()) {
String originalFileName = file.getOriginalFilename();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
//给上传的文件加一个标识,作为下载时的文件key
uploadFileName = simpleDateFormat.format(new Date()) + originalFileName;
try (InputStream stream = file.getInputStream()) {
OssUtil.uploadFile(uploadFileName, stream);
} catch (Exception e) {
throw new MyException(MessageUtilsNew.get("subject.oss.file.save.error"));
}
} else {
throw new MyException(MessageUtilsNew.get("subject.oss.file.upload.empty"));
}
// 更新当前apk包的版本信息
AppVersion appVersion = appVersionMapper.selectByAppVersionId(1L);
appVersion.setVersionNumber(newAppVersion);
appVersion.setDownloadLink(uploadFileName);
appVersion.setForceUpdate(isForceUpdate);
appVersionMapper.updateByAppVersionId(appVersion);
return BaseResult.success();
}
/**
* @param response 客户端响应对象
* downloadlink 下载地址或者oss文件地址
*/
@Override
public void downloadApk(HttpServletResponse response) throws IOException {
// 下载所需的apk包
AppVersion appVersion = appVersionMapper.selectByAppVersionId(1L);
if (appVersion != null && appVersion.getDownloadLink() != null && StringUtils.isNotEmpty(appVersion.getDownloadLink())) {
OssUtil.downloadFile(response, appVersion.getDownloadLink(), appVersion.getDownloadLink().substring(17));
}
}
}
5.数据层
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Update;
public interface AppVersionMapper extends BaseMapper<AppVersion> {
@Update("update app_version set version_number = #{versionNumber},download_link = #{downloadLink},force_update = #{forceUpdate},description = #{description}, is_deprecated = #{isDeprecated} where app_version_id = #{appVersionId}")
void updateByAppVersionId(AppVersion appVersion);
@Select("select * from app_version where app_version_id = #{appVersionId}")
AppVersion selectByAppVersionId(Long appVersionId);
}
6.ossUtil工具类
import com.aliyun.oss.ClientBuilderConfiguration;
@Component
public class OssUtil {
//这些配置参数设为自己的即可
private static String bucket;
private static String endpoint;
private static String accessKeyId;
private static String accessKeySecret;
private static boolean sldEnabled;
/**
* 上传文件(fileKey-InputStream)
*/
public static void uploadFile(String fileKey, InputStream stream) throws IOException {
ClientBuilderConfiguration conf = new ClientBuilderConfiguration();
conf.setSLDEnabled(sldEnabled);
OSS client = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret, conf);
try {
client.putObject(bucket, fileKey, stream);
} finally {
stream.close();
if (client != null) {
client.shutdown();
}
IOUtils.closeQuietly(stream);
}
}
/**
* 文件下载
*/
public static void downloadFile(HttpServletResponse response, String fileKey, String fileName) throws IOException {
InputStream stream = null;
BufferedInputStream bufferedInputStream = null;
ClientBuilderConfiguration conf = new ClientBuilderConfiguration();
conf.setSLDEnabled(sldEnabled);
OSS client = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret, conf);
try {
stream = client.getObject(bucket, fileKey).getObjectContent();
response.reset();
response.setContentType("application/octet-stream");
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")
.replace("+", "%20"));
OutputStream outputStream = response.getOutputStream();
bufferedInputStream = new BufferedInputStream(stream);
byte[] buf = new byte[16384];
int bytesRead;
while ((bytesRead = stream.read(buf, 0, buf.length)) >= 0) {
outputStream.write(buf, 0, bytesRead);
outputStream.flush();
}
} finally {
if (client != null) {
client.shutdown();
}
if (bufferedInputStream != null || stream != null) {
try {
bufferedInputStream.close();
stream.close();
} catch (IOException e) {
log.error("Oss Download IOException,fileKey:" + fileKey, e);
}
}
}
}
}
四、总结
通过阿里云OSS,我们可以方便地实现APP版本包的存储和下载,大大提高了APP更新的效率和用户体验。