7.23将文件上传至云服务器

在这里插入图片描述
客户端上传例子:用户上传头像;

服务器直传在上节课中也讲到过,服务器将自动生成的图片,直接交给云服务器。

我们选择 qiniu云服务器,因为免费。
官网:
在这里插入图片描述

注册会比较麻烦,因此我们直接截图吧

创建云服务器 08:23

新建存储空间,利用域名来访问:
在这里插入图片描述
现在什么都没有
在这里插入图片描述
又新建一个存储空间,域名为:
在这里插入图片描述
在porm中导入qiniu所需的包
在这里插入图片描述
接下来在 application Properties中
做一些配置

AK用来标识用户身份,不是谁都可以往空间中传东西;
SK用来上传具体内容时为内容加密
需要将这两个在配置文件中设置。
在这里插入图片描述

# 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

接下来处理上传头像的逻辑:
上传头像的请求在UserController中:

@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}")
private String headerBucketUrl;
@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字符串,告诉我们有没有成功即可(成功的时候返回0),这样更方便,如果是同步的方式,响应个html,就乱套了
    // 生成上传凭证(提供上传文件的名称),使得qiniu可以识别身份
    Auth auth = Auth.create(accessKey, secretKey);//利用  与身份有关的accessKey,与内容有关的secretKey 生成的auth对象,以生成上传凭证uploadToken
    String uploadToken = auth.uploadToken(headerBucketName, fileName, 3600, policy);//headerBucketName上传空间名字;fileName文件名;key过期时间1h。policy响应信息

    model.addAttribute("uploadToken", uploadToken);
    model.addAttribute("fileName", fileName);

    return "/site/setting";
}
// 更新头像路径,当我们把数据提交给qiniu云以后,qiniu给我们返回一个消息0,代表成功,成功后要把user表里面的headurl做一个更新,更新为qinniu云的路径,如果不更的话,表里的路径没变,就白传了,网页还是显示的之前的头像,而不是传到qinniu云里面的
@RequestMapping(path = "/header/url", method = RequestMethod.POST)//因为要提交数据,更新数据,所以是POST
@ResponseBody
public String updateHeaderUrl(String fileName) {
    if (StringUtils.isBlank(fileName)) {
        return CommunityUtil.getJSONString(1, "文件名不能为空!");//1表示错误
    }

    //不为空 则需要拼URL
    String url = headerBucketUrl + "/" + fileName;//头像有效的访问路径
    userService.updateHeader(hostHolder.getUser().getId(), url);

    return CommunityUtil.getJSONString(0);
}

重构好user controller了,因为废弃了原有的几个方法,对现有的方法进行改进

接下来处理对应的表单 setting.html
注释掉:
在这里插入图片描述
修改为:

<!--上传到七牛云-->
	<form class="mt-5" id="uploadForm">  <!--异步上传不需要method="post" enctype="multipart/form-data" th:action="@{/user/upload}"-->
		<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}">
					<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>

新建setting.js

$(function(){  <!--页面加载完 后调用此function-->
    $("#uploadForm").submit(upload);<!--当点击提交按钮,触发表单的提交事件时,该事件由upload处理。Form id是uploadForm,当其触发提交事件时-->
});

function upload() {
    $.ajax({
        url: "http://upload-z1.qiniup.com",<!-- 表示请求要提交给谁-->
        method: "post",<!-- 请求方式是post-->
        processData: false,<!-- 不要把表单内容转为字符串,默认情况下,浏览器会把表单内容转为字符串 提交给服务器-->
        contentType: false,<!-- contentType按理说应该是 html、json。即数据类型,false的意思是不让 jqury设置上传类型,浏览器自动设置。文件和别的数据不一样,是二进制的,当其和别的数据混编在一起时,边界如何确定呢;浏览器会给其加一个随机边界字符串,好去拆分 -->
        data: new FormData($("#uploadForm")[0]),
        success: function(data) {<!--成功的时候处理:-->
            if(data && data.code == 0) {<!--data存在且data.code = 0-->
                // 更新头像访问路径(异步)
                $.post(
                    CONTEXT_PATH + "/user/header/url",
                    {"fileName":$("input[name='key']").val()},<!--要传数据的名字-->
                    function(data) {
                        data = $.parseJSON(data);//将data解析为JSON
                        if(data.code == 0) {//表示成功
                            window.location.reload();//刷新页面,看看头像是否改变
                        } else {
                            alert(data.msg);//失败则给出提示
                        }
                    }
                );
            } else {
                alert("上传失败!");
            }
        }
    });
    return false;  <!--false表示事件到此为止-->
}

测试:
访问首页、登录:
其头像是:
在这里插入图片描述
表里的数据:
在这里插入图片描述
现在的路径是本地路径,一回儿要改成 qiniu云的路径

接下来修改头像,上传新的头像。
在这里插入图片描述
头像为:
在这里插入图片描述
此时 url变为qiniu云的
在这里插入图片描述

接下来在 qiniu中查看
在这里插入图片描述
可以看到有一条数据
在这里插入图片描述
预览没有问题
在这里插入图片描述

于是上传头像便解决了。

这只是上传至云服务器的一种方案,通过客户端上传。

接下来演示通过服务器直传。

打开share controller

@Value("${qiniu.bucket.share.url}")//application properties中  qiniu.bucket.share.url
private String shareBucketUrl;

对share函数

文件名(随机,每次都要变,不然很容易重名)
异步生成长图
最终返回浏览器的路径 需要改动
一开始:
在这里插入图片描述
改为:

       // 生成长图以后,如何访问呢---返回访问路径
        Map<String, Object> map = new HashMap<>();
//        map.put("shareUrl", domain + contextPath + "/share/image/" + fileName);//domain项目域名
        map.put("shareUrl", shareBucketUrl + "/" + fileName);

打开EventConsumer

@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;//执行定时任务的线程池,因为要考虑分布式部署的问题

在handleShareMessage中

  String htmlUrl = (String) event.getData().get("htmlUrl");//event.getData()是map
    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);//传入cmd,执行
        logger.info("生成长图成功: " + cmd);
    } catch (IOException e) {
        logger.error("生成长图失败: " + e.getMessage());
    }

    //注意,logger.info("生成长图成功: " + cmd); 先执行,因为 Runtime.getRuntime().exec(cmd);//传入cmd,执行  因为图片大,所以慢,所以要等
    // 启用定时器,监视该图片,每隔一段时间 检测是否生成图片,一旦生成了,则上传至七牛云.
    UploadTask task = new UploadTask(fileName, suffix);//哪个服务器抢到这个消息,则只有该服务器会开启定时
    Future future = taskScheduler.scheduleAtFixedRate(task, 500);//taskScheduler.scheduleAtFixedRate(task, 500)触发定时器的执行,定时器启动后有一个返回值 叫future,里面封装了用户的状态
    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) {//时间超过30s
            logger.error("执行时间过长,终止任务:" + fileName);
            future.cancel(true);//停止定时器
            return;
        }
        // 上传图片至qiniu云失败
        if (uploadTimes >= 3) {
            logger.error("上传次数过多,终止任务:" + fileName);
            future.cancel(true);//停止定时器
            return;
        }

        String path = wkImageStorage + "/" + fileName + suffix;//本地存放文件完整 路径
        File file = new File(path);//将path传入以实例化文件file
        if (file.exists()) {//如果文件存在
            logger.info(String.format("开始第%d次上传[%s].", ++uploadTimes, fileName));//%d 位置用++uploadTimes替换;%s用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);//shareBucketName为空间名;fileName为文件名;3600为过期时间
            // 指定上传机房
            UploadManager manager = new UploadManager(new Configuration(Zone.zone1()));//zone1为华北机房
            try {
                // 开始上传图片
                Response response = manager.put(
                        path, fileName, uploadToken, null, "image/" + suffix, false);
                // 处理响应结果,得到json数据
                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 + "].");
        }
    }
}

测试:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

图片越大,越容易失败,百度截图 图片小,第一次就成功了
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值