pom.xml导包。
<dependency>
<groupId>com.qiniugroupId>
<artifactId>qiniu-java-sdkartifactId>
<version>7.2.23version>
dependency>
配置文件添加相关配置【非必要】,与创建及网站提供的信息一一对应。
#标识用户身份
qiniu.key.access=aaaaaaaaaaaaaa
#上传具体内容为内容加密
qiniu.key.secret=bbbbbbbbbbbbbb
qiniu.bucket.share.name=gerrard-community-header
qiniu.bucket.share.url=http://xxxxxxxx.xxxx.clouddn.com
qiniu.bucket.header.name=gerrard-community-share
quniu.bucket.header.url=http://xxxxxxxx.xxxx.clouddn.com
UserController中添加相关字段,废弃uploadHeader,getHeader方法,修改getSettingPage方法,添加updateHeaderUrl方法。
package com.gerrard.community.controller;
import com.gerrard.community.annotation.LoginRequired;
import com.gerrard.community.entity.User;
import com.gerrard.community.service.FollowerService;
import com.gerrard.community.service.LikeService;
import com.gerrard.community.service.UserService;
import com.gerrard.community.util.CommunityConstant;
import com.gerrard.community.util.CommunityUtil;
import com.gerrard.community.util.HostHolder;
import com.qiniu.util.Auth;
import com.qiniu.util.StringMap;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Map;
@Controller
@RequestMapping("/user")
public class UserController implements CommunityConstant {
private static final Logger logger= LoggerFactory.getLogger(UserController.class);
@Value("${community.path.upload}")
private String uploadPath;
@Value("${community.path.domain}")
private String domain;
@Value("${server.servlet.context-path}")
private String contextPath;
@Autowired
private UserService userService;
@Autowired
private LikeService likeService;
@Autowired
private FollowerService followerService;
@Autowired
private HostHolder hostHolder;
@Value("${qiniu.key.access}")
private String accessKey;
@Value("${qiniu.key.secret}")
private String secretKey;
@Value("${qiniu.bucket.header.name}")
private String headerBucketName;
@Value("${quniu.bucket.header.url}") //七牛云对外暴露的图片链接,不是上传的url
private String headerBucketUrl;
@LoginRequired
@RequestMapping(path="/setting",method = RequestMethod.GET )
public String getSettingPage(Model model){
//上传文件名称
String fileName=CommunityUtil.generateUUID();
//设置响应信息
StringMap policy=new StringMap();
//两件事,确定url(js文件里有)加配置相应规则
//配置七牛云的响应规则
policy.put("returnBody",CommunityUtil.getJSONString(0));
//生成上传凭证
Auth auth=Auth.create(accessKey,secretKey);
String uploadToken=auth.uploadToken(headerBucketName,fileName,3600,policy);
model.addAttribute("uploadToken",uploadToken);
model.addAttribute("fileName",fileName);
return "/site/setting";
}
//更新头像路径
@RequestMapping(path = "/header/url", method = RequestMethod.POST)
@ResponseBody
public String updateHeaderUrl(String fileName) {
if (StringUtils.isBlank(fileName)) {
return CommunityUtil.getJSONString(1, "文件名不能为空!");
}
String url = headerBucketUrl + "/" + fileName;
userService.updateHeader(hostHolder.getUser().getId(), url);
return CommunityUtil.getJSONString(0);
}
//废弃
@LoginRequired
@RequestMapping(path="/upload",method = RequestMethod.POST)
public String uploadHeader(MultipartFile headerImage, Model model){
if(headerImage==null){
model.addAttribute("error","您还没有选择图片!");
return "/site/setting";
}
String fileName=headerImage.getOriginalFilename();
String suffix=fileName.substring(fileName.lastIndexOf("."));
if(StringUtils.isBlank(suffix)){
model.addAttribute("error","文件的格式不正确");
return "/site/setting";
}
//生成随机文件名
fileName= CommunityUtil.generateUUID()+suffix;
//确定文件的存放路径
File dest=new File(uploadPath+"/"+fileName);
try {
//存储文件
headerImage.transferTo(dest);
} catch (IOException e) {
logger.error("上传文件失败:"+e.getMessage());
throw new RuntimeException("上传文件失败,服务器发生异常",e);
}
//更新当前用户的头像的路径(web访问路径)
// http://localhost:8080/community/user/header/xxx.png
User user=hostHolder.getUser();
String headerUrl=domain+contextPath+"/user/header/" + fileName;
userService.updateHeader(user.getId(),headerUrl);
return "redirect:/index";
}
//废弃
//暴露一个访问接口给外部
@RequestMapping(path="header/{fileName}",method=RequestMethod.GET)
public void getHeader(@PathVariable("fileName")String fileName, HttpServletResponse response){
//服务器存放路径
fileName=uploadPath+"/"+fileName;
//文件后缀
String suffix=fileName.substring(fileName.lastIndexOf("."));
//响应图片
response.setContentType("image/"+suffix);
try (
FileInputStream fis = new FileInputStream(fileName);
OutputStream os = response.getOutputStream();
) {
byte[] buffer = new byte[1024];
int b = 0;
while ((b = fis.read(buffer)) != -1) {
os.write(buffer, 0, b);
}
} catch (IOException e) {
logger.error("读取头像失败: " + e.getMessage());
}
}
}
修改setting.html,注释上传至服务器本地的代码,添加上传到七牛云的代码;新建setting.js。
setting.js:
$(function(){
$("#uploadForm").submit(upload);
});
function upload() {
$.ajax({
url: "http://upload-z1.qiniup.com", //z1为华东地区
method: "post",
processData: false,
contentType: false,
data: new FormData($("#uploadForm")[0]),
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;
}
(1)用户发起/setting get请求,
生成上传文件的名称(随机字符串),设置七牛云的响应规则,生成上传至凭证,
model.addAttribute("uploadToken",uploadToken);
model.addAttribute("fileName",fileName);
响应/site/setting页面。
(2)用户点击上传按钮,先向七牛云发送ajax 异步post请求(url:http://upload-z1.qiniup.com),凭证信息也会附带上传;响应七牛云返回的提示信息【凭证信息里包含有响应规则】,上传成供则再次发送 ajax 异步post请求来更新头像的访问路径,更新成功则重新加载/site/setting页面。
更新头像的访问路径:更新数据库中的头像url地址(格式遵循七牛云的规范),返回成功code:0 JSON字符串。
ShareController类中修改share方法,废掉getShareImage方法。
package com.gerrard.community.controller;
import com.gerrard.community.entity.Event;
import com.gerrard.community.event.EventProducer;
import com.gerrard.community.util.CommunityConstant;
import com.gerrard.community.util.CommunityUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
@Controller
public class ShareController implements CommunityConstant {
private static final Logger logger= LoggerFactory.getLogger(ShareController.class);
@Value("${community.path.domain}")
private String domain;
@Value("${server.servlet.context-path}")
private String contextPath;
@Value("${wk.image.storage}")
private String wkImageStorage;
@Value("${qiniu.bucket.share.url}")
private String shareBucketUrl;
@Autowired
private EventProducer eventProducer;
@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);
}
//废弃
//获取长图
@RequestMapping(path="/share/image/{fileName}",method = RequestMethod.GET)
public void getShareImage(@PathVariable("fileName")String fileName, HttpServletResponse response){
if(StringUtils.isBlank(fileName)){
throw new IllegalArgumentException("文件名不能为空!");
}
response.setContentType("image/png");
File file=new File(wkImageStorage+"/"+fileName+".png");
try {
OutputStream os = response.getOutputStream();
FileInputStream fis = new FileInputStream(file);
byte[] buffer = new byte[1024];
int b = 0;
while ((b = fis.read(buffer)) != -1) {
os.write(buffer, 0, b);
}
} catch (IOException e) {
logger.error("获取长图失败: " + e.getMessage());
}
}
}
EventConsumer类中对handleShareMessage方法进行重构。
package com.gerrard.community.event;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gerrard.community.entity.DiscussPost;
import com.gerrard.community.entity.Event;
import com.gerrard.community.entity.Message;
import com.gerrard.community.service.DiscussPostService;
import com.gerrard.community.service.ElasticsearchService;
import com.gerrard.community.service.MessageService;
import com.gerrard.community.util.CommunityConstant;
import com.gerrard.community.util.CommunityUtil;
import com.qiniu.common.QiniuException;
import com.qiniu.common.Zone;
import com.qiniu.http.Response;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.UploadManager;
import com.qiniu.util.Auth;
import com.qiniu.util.StringMap;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;
@Component
public class EventConsumer implements CommunityConstant {
private static final Logger logger= LoggerFactory.getLogger(EventConsumer.class);
@Autowired
private MessageService messageService;
@Autowired
private DiscussPostService discussPostService;
@Autowired
private ElasticsearchService elasticsearchService;
@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.zone0()));
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 + "].");
}
}
}
}
与上传头像功能不同,上传头像功能是在JS中编写,此处用Java编写。
1.用户在浏览器中输入/share?htmlUrl=xxxxx,转到控制器的share方法,发布分享事件,异步生成长图,返回暴露的图片url地址JSON字符串至浏览器【url地址与前版本不一致】。
2.异步生成长图:消费者消费分享事件,使用wkhtmltopdf工具异步生成长图,先在本地生成图片,启用定时器,监视该图片,一旦生成了,则上传至七牛云。
3.用户在浏览器地址栏输入图片url访问地址,访问该图片。
问题:七牛云外链链接一个是直接看到图片,一个是要下载?
上传了一个pdf,可以上传成功,但网页中头像信息显示不出。
上传图片:
在七牛云中查询图片信息:
查看user MySQL数据库信息:
在浏览器地址栏中输入:http://localhost:8080/community/share?htmlUrl=https://www.baidu.com
本次访问未创建成功
再访问一次:
等待一会儿根据响应的地址访问该图片:
在七牛云中查看图片: