将文件上传至云服务器
文章目录
- 将文件上传至云服务器
- 用户上传头像
- 服务器直传
- 客户端上传
- 客户端将数据提交给云服务器,并等待其响应
- eg:本项目中,用户上传头像时,将表单数据提交给云服务器。
- 服务器直传
- 应用服务器将数据直接提交给云服务器,并等待其响应。
- eg:本项目中,分享时,服务器将自动生成的图片,直接提交给云服务器。
使用七牛云,对象存储服务。
用户上传头像
1、导包
2、自定义配置
配置七牛云的两个密钥,以及七牛云存储空间的名字和域名
# qiniu
qiniu.key.access=6RA-Uus95ZT_1znMrCMD8BpqfjT-K7OKmQTfKB48
qiniu.key.secret=kPNnLFz2_tzztKUVpSLm0lYngtuHWyIq5LzTmLIL
qiniu.bucket.header.name=community_header
quniu.bucket.header.url=http://pvghrij81.bkt.clouddn.com
qiniu.bucket.share.name=community_share
qiniu.bucket.share.url=http://pvghvvuzm.bkt.clouddn.com
3、实现用户上传表单时,直接将表单数据提交给云服务器
A、首先废弃之前的上传模式(之前上传到服务器本机上)
B、生成上传凭证等所需信息,把参数传进模板,用于之后的处理
@LoginRequired
@RequestMapping(path = "/setting", method = RequestMethod.GET)
public String getSettingPage(Model model) {
// 上传文件名称
String fileName = CommunityUtil.generateUUID();
// 设置响应信息
StringMap policy = new StringMap();
policy.put("returnBody", CommunityUtil.getJSONString(0)); // 指定成功时返回json字符串,“code:0”
// 生成上传凭证
Auth auth = Auth.create(accessKey, secretKey);
String uploadToken = auth.uploadToken(headerBucketName, fileName, 3600, policy); // 参数1 存储空间的名字;参数2 上传文件名;参数3 key过期时间;参数4 响应信息
model.addAttribute("uploadToken", uploadToken);
model.addAttribute("fileName", fileName);
return "/site/setting";
}
1)生成上传凭证:用于七牛云对服务器的识别,包括上传文件的名称,以及上传后期待的七牛云响应
2)上传凭证和上传文件名 传给模板。模板利用所需的数据重新构成表单,并将表单以异步的方式提交给七牛云处理
4、更新头像路径
// 更新头像路径
@RequestMapping(path = "/header/url", method = RequestMethod.POST)
@ResponseBody
public String updateHeaderUrl(String fileName) {
if (StringUtils.isBlank(fileName)) {
return CommunityUtil.getJSONString(1, "文件名不能为空!");
}
// 访问七牛云中数据的url
String url = headerBucketUrl + "/" + fileName;
userService.updateHeader(hostHolder.getUser().getId(), url); // 更新头像路径
return CommunityUtil.getJSONString(0);
}
5、前端——表单处理
单击表单提交时,由JQuery异步将数据按照设置信息传给云服务器(即客户端上传到云服务器)。上传成功会更新图像路径
<!--上传到七牛云-->
<form class="mt-5" id="uploadForm">
<div class="form-group row mt-4">
<label for="head-image" class="col-sm-2 col-form-label text-right">选择头像:</label>
<div class="col-sm-10">
<div class="custom-file">
<input type="hidden" name="token" th:value="${uploadToken}"> // name 是七牛云要求的
<input type="hidden" name="key" th:value="${fileName}">
<input type="file" class="custom-file-input" id="head-image" name="file" lang="es" required="">
<label class="custom-file-label" for="head-image" data-browse="文件">选择一张图片</label>
<div class="invalid-feedback">
该账号不存在!
</div>
</div>
</div>
</div>
<div class="form-group row mt-4">
<div class="col-sm-2"></div>
<div class="col-sm-10 text-center">
<button type="submit" class="btn btn-info text-white form-control">立即上传</button>
</div>
</div>
</form>
// 新增一个 js
<script th:src="@{/js/setting.js}"></script>
$(function(){
$("#uploadForm").submit(upload); //点击表单的提交按钮,触发提交事件时,事件由 upload 函数进行处理
});
function upload() {
$.ajax({
url: "http://upload-z1.qiniup.com", // 声明提交路径,由七牛云规定
method: "post", //请求方式
processData: false, // 声明不要把表单内容转换成字符串
contentType: false, // 声明JQuery 不要设置上传的类型,浏览器会自动设置。
data: new FormData($("#uploadForm")[0]), //$("#uploadForm") 是JQuery对象,$("#uploadForm")[0] 是js对象
success: function(data) {
// 操作成功了
if(data && data.code == 0) {
// 更新头像访问路径,仍然是异步处理,只不过是提交给服务器
$.post( // 异步提交给服务器的 更新头像路径
CONTEXT_PATH + "/user/header/url",
{"fileName":$("input[name='key']").val()}, // 传入参数
function(data) { // 服务器的返回值
data = $.parseJSON(data);
if(data.code == 0) {
window.location.reload();
} else {
alert(data.msg);
}
}
);
} else {
alert("上传失败!");
}
}
});
return false; // 声明不要再提交了,上面的逻辑已经对表单进行了处理
}
页面加载完调用此函数。对 form 定义一个事件
服务器直传
重构 share 功能:
1、将 访问路径 shareUrl 修改为 云服务器上对应的储存路径
ShareController.java
@RequestMapping(path = "/share", method = RequestMethod.GET)
@ResponseBody
public String share(String htmlUrl) {
// 文件名
String fileName = CommunityUtil.generateUUID();
// 异步生成长图
Event event = new Event()
.setTopic(TOPIC_SHARE)
.setData("htmlUrl", htmlUrl)
.setData("fileName", fileName)
.setData("suffix", ".png");
eventProducer.fireEvent(event);
// 返回访问路径
Map<String, Object> map = new HashMap<>();
// map.put("shareUrl", domain + contextPath + "/share/image/" + fileName);
map.put("shareUrl", shareBucketUrl + "/" + fileName);
return CommunityUtil.getJSONString(0, null, map);
}
2、废弃 getShareImage() 方法
3、由于 WK 生成长图是异步操作,所以使用定时器,监视该图片,一旦生成了,则上传至七牛云。如果生成失败,或上传失败到一定次数,则中止该定时器。
使用的类:Future、ThreadPoolTaskScheduler (?)
这里没有使用Quartz 是因为, 即使在分布式情况下,每台服务器都部署了consumer,但消费者有个抢占机制,所以只会某个服务器上执行。
(同一消费组下的各个消费者在消费消息是是互斥的,也即是说,同一条消息,只能被同一个消费组下的某个消费者消费,不能被其它组的消费者消费
)
@Value("${wk.image.command}")
private String wkImageCommand;
@Value("${wk.image.storage}")
private String wkImageStorage;
@Value("${qiniu.key.access}")
private String accessKey;
@Value("${qiniu.key.secret}")
private String secretKey;
@Value("${qiniu.bucket.share.name}")
private String shareBucketName;
@Autowired
private ThreadPoolTaskScheduler taskScheduler;
// 消费分享事件
@KafkaListener(topics = TOPIC_SHARE)
public void handleShareMessage(ConsumerRecord record) {
if (record == null || record.value() == null) {
logger.error("消息的内容为空!");
return;
}
Event event = JSONObject.parseObject(record.value().toString(), Event.class);
if (event == null) {
logger.error("消息格式错误!");
return;
}
String htmlUrl = (String) event.getData().get("htmlUrl");
String fileName = (String) event.getData().get("fileName");
String suffix = (String) event.getData().get("suffix");
String cmd = wkImageCommand + " --quality 75 "
+ htmlUrl + " " + wkImageStorage + "/" + fileName + suffix;
try {
Runtime.getRuntime().exec(cmd);
logger.info("生成长图成功: " + cmd);
} catch (IOException e) {
logger.error("生成长图失败: " + e.getMessage());
}
// 启用定时器,监视该图片,一旦生成了,则上传至七牛云.
UploadTask task = new UploadTask(fileName, suffix);
Future future = taskScheduler.scheduleAtFixedRate(task, 500);
task.setFuture(future);
}
class UploadTask implements Runnable {
// 文件名称
private String fileName;
// 文件后缀
private String suffix;
// 启动任务的返回值
private Future future;
// 开始时间
private long startTime;
// 上传次数
private int uploadTimes;
public UploadTask(String fileName, String suffix) {
this.fileName = fileName;
this.suffix = suffix;
this.startTime = System.currentTimeMillis();
}
public void setFuture(Future future) {
this.future = future;
}
@Override
public void run() {
// 生成失败
if (System.currentTimeMillis() - startTime > 30000) {
logger.error("执行时间过长,终止任务:" + fileName);
future.cancel(true);
return;
}
// 上传失败
if (uploadTimes >= 3) {
logger.error("上传次数过多,终止任务:" + fileName);
future.cancel(true);
return;
}
String path = wkImageStorage + "/" + fileName + suffix;
File file = new File(path);
if (file.exists()) {
logger.info(String.format("开始第%d次上传[%s].", ++uploadTimes, fileName));
// 设置响应信息
StringMap policy = new StringMap();
policy.put("returnBody", CommunityUtil.getJSONString(0));
// 生成上传凭证
Auth auth = Auth.create(accessKey, secretKey);
String uploadToken = auth.uploadToken(shareBucketName, fileName, 3600, policy);
// 指定上传机房
UploadManager manager = new UploadManager(new Configuration(Zone.zone1()));
try {
// 开始上传图片
Response response = manager.put(
path, fileName, uploadToken, null, "image/" + suffix, false);
// 处理响应结果
JSONObject json = JSONObject.parseObject(response.bodyString());
if (json == null || json.get("code") == null || !json.get("code").toString().equals("0")) {
logger.info(String.format("第%d次上传失败[%s].", uploadTimes, fileName));
} else {
logger.info(String.format("第%d次上传成功[%s].", uploadTimes, fileName));
future.cancel(true);
}
} catch (QiniuException e) {
logger.info(String.format("第%d次上传失败[%s].", uploadTimes, fileName));
}
} else {
logger.info("等待图片生成[" + fileName + "].");
}
}
}
}