问答平台(7),文件上传至云服务器

七牛云

1
2
- 注册账号,实名认证。
- 对象存储:新建存储空间`community-header-my`和`community-share-my`(测试域名有效期:30天)

客户端上传

1
2
- 客户端将数据提交给云服务器,并等待其响应。
- 用户上传头像时,将表单数据提交给云服务器。

导入依赖

1
2
3
4
5
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>7.2.23</version>
</dependency>

配置文件

1
2
3
4
5
6
7
# qiniu
qiniu.key.access=xxx
qiniu.key.secret=xxx
qiniu.bucket.header.name=community-header-my
qiniu.bucket.header.url=http://qbykqrf0g.bkt.clouddn.com
qiniu.bucket.share.name=community-share-my
qiniu.bucket.share.url=http://qbykzk6js.bkt.clouddn.com

表现层

  • UserController: 重构
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    @Value("${qiniu.key.access}")
    private String accessKey;

    @Value("${qiniu.key.secret}")
    private String secretKey;

    @Value("${qiniu.bucket.header.name}")
    private String headerBucketName;

    @Value("${qiniu.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));
    // 生成上传凭证
    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);
    }

页面

  • setting.html: 修改
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!-- 内容 -->
    <!-- 上传到七牛云 -->
    <form class="mt-5" id="uploadForm">
    <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>
    </form>
  • setting.js: 新增
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    $(function () {
    $("#uploadForm").submit(upload);
    });

    function upload() {
    $.ajax({
    url: "http://upload-z1.qiniup.com",
    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
2
- 应用服务器将数据直接提交给云服务器,并等待其响应。
- 分享时,服务端将自动生成的图片,直接提交给云服务器。

表现层

  • ShareController: 重构
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    @Value("${qiniu.bucket.share.url}")
    private String shareBucketUrl;

    @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) {

    }

消费事件

  • EventConsumer: 修改
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    @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.info("生成长图失败: " + 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() {
    // 生成图片失败(30s)
    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 + "].");
    }
    }
    }

结果展示

服务器直传-图示
服务器直传2-图示


参考资料


问答平台(7),文件上传至云服务器
https://lcf163.github.io/2020/06/19/问答平台(7),文件上传至云服务器/
作者
乘风的小站
发布于
2020年6月19日
许可协议