目录
在实际开发中,我们会有很多处理不同功能的服务器。例如:
应用服务器:负责部署我们的应用
数据库服务器:运行我们的数据库
文件服务器:负责存储用户上传文件的服务器
分服务器处理的目的是让服务器各司其职,从而提高我们项目的运行效率。
常见的图片存储方案:
方案一:使用nginx搭建图片服务器
方案二:使用开源的分布式文件存储系统,例如Fastdfs、HDFS等
方案三:使用云存储,例如阿里云、==七牛云==等
一、七牛云存储
七牛云(隶属于上海七牛信息技术有限公司)是国内领先的以视觉智能和数据智能为核心的企业级云计算服务商,同时也是国内知名智能视频云服务商,累计为 70 多万家企业提供服务,覆盖了国内80%网民。围绕富媒体场景推出了对象存储、融合 CDN 加速、容器云、大数据平台、深度学习平台等产品、并提供一站式智能视频云解决方案。为各行业及应用提供可持续发展的智能视频云生态,帮助企业快速上云,创造更广阔的商业价值。
1.注册、登录
要使用七牛云的服务,首先需要注册成为会员。地址:七牛云开发者平台
2.新建存储空间
3.查看存储空间信息
4.鉴权
Java SDK的所有的功能,都需要合法的授权。授权凭证的签算需要七牛账号下的一对==有效的Access Key和Secret Key==,这对密钥可以在七牛云管理控制台的个人中心(七牛云开发者平台)获得,如下图:
二、开发者中心,上传、删除测试
可以通过七牛云提供的开发者中心学习如何操作七牛云服务,代码测试
地址:七牛开发者中心
1.添加依赖
shf-parent添加依赖管理
<!--七牛云服务平台,第三方服务(图片上传)-->
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>7.2.0</version>
</dependency>
common-util引入依赖
<!--七牛云服务平台,第三方服务(图片上传)-->
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
</dependency>
2.代码测试
package com.atguigu.test;
import com.google.gson.Gson;
import com.qiniu.common.QiniuException;
import com.qiniu.common.Zone;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import org.junit.Test;
public class TestQiniu {
// 上传本地文件
@Test
public void uploadFile(){
//构造一个带指定Zone对象的配置类
Configuration cfg = new Configuration(Zone.zone2());
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//...生成上传凭证,然后准备上传
String accessKey = "***";
String secretKey = "***";
String bucket = "shf-img1";
//如果是Windows情况下,格式是 D:\\qiniu\\test.png,可支持中文
String localFilePath = "C:/Users/Plisetsky/Pictures/Saved Pictures/test.jpg";
//默认不指定key的情况下,以文件内容的hash值作为文件名
String key = "test.jpg";
Auth auth = Auth.create(accessKey, secretKey);
String upToken = auth.uploadToken(bucket);
try {
Response response = uploadManager.put(localFilePath, key, upToken);
//解析上传成功的结果
DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
System.out.println(putRet.key);
System.out.println(putRet.hash);
} catch (QiniuException ex) {
Response r = ex.response;
System.err.println(r.toString());
try {
System.err.println(r.bodyString());
} catch (QiniuException e) {
e.printStackTrace();
}
}
}
// 删除空间中的文件
@Test
public void deleteFile() {
//构造一个带指定Zone对象的配置类
Configuration cfg = new Configuration(Zone.zone2());
//...其他参数参考类注释
String accessKey = "***";
String secretKey = "***";
String bucket = "shf-img1";//空间
String key = "test.jpg";//文件名称
Auth auth = Auth.create(accessKey, secretKey);
BucketManager bucketManager = new BucketManager(auth, cfg);
try {
bucketManager.delete(bucket, key);
} catch (QiniuException ex) {
//如果遇到异常,说明删除失败
System.err.println(ex.code());
System.err.println(ex.response.toString());
}
}
}
3. 封装工具类
package com.atguigu.util;
import com.google.gson.Gson;
import com.qiniu.common.QiniuException;
import com.qiniu.common.Zone;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
/**
* 七牛云工具类
*/
public class QiniuUtils {
//设置ak,sk
public static String accessKey = "***";
public static String secretKey = "***";
public static String bucket = "shf-img1";
public static void upload2Qiniu(String filePath,String fileName){
//构造一个带指定Zone对象的配置类
Configuration cfg = new Configuration(Zone.zone2());
UploadManager uploadManager = new UploadManager(cfg);
Auth auth = Auth.create(accessKey, secretKey);
String upToken = auth.uploadToken(bucket);
try {
Response response = uploadManager.put(filePath, fileName, upToken);
//解析上传成功的结果
DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
} catch (QiniuException ex) {
Response r = ex.response;
try {
System.err.println(r.bodyString());
} catch (QiniuException ex2) {
//ignore
}
}
}
//上传文件
public static void upload2Qiniu(byte[] bytes, String fileName){
//构造一个带指定Zone对象的配置类
Configuration cfg = new Configuration(Zone.zone2());
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//默认不指定key的情况下,以文件内容的hash值作为文件名
String key = fileName;
Auth auth = Auth.create(accessKey, secretKey);
String upToken = auth.uploadToken(bucket);
try {
Response response = uploadManager.put(bytes, key, upToken);
//解析上传成功的结果
DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
System.out.println(putRet.key);
System.out.println(putRet.hash);
} catch (QiniuException ex) {
Response r = ex.response;
System.err.println(r.toString());
try {
System.err.println(r.bodyString());
} catch (QiniuException ex2) {
//ignore
}
}
}
//删除文件
public static void deleteFileFromQiniu(String fileName){
//构造一个带指定Zone对象的配置类
Configuration cfg = new Configuration(Zone.zone2());
String key = fileName;
Auth auth = Auth.create(accessKey, secretKey);
BucketManager bucketManager = new BucketManager(auth, cfg);
try {
bucketManager.delete(bucket, key);
} catch (QiniuException ex) {
//如果遇到异常,说明删除失败
System.err.println(ex.code());
System.err.println(ex.response.toString());
}
}
}
三、房源图片上传
1. 需要添加依赖 Common-upload组件
SpringMVC 默认集成 Common-upload,可以直接整合
依赖管理
<commons-fileupload.version>1.3.1</commons-fileupload.version>
<!-- 文件上传组件 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons-fileupload.version}</version>
</dependency>
util 添加依赖
<!-- 文件上传组件 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
2. spring mvc 配置上传支持 文件上传解析器
在spring-mvc添加配置
<!--配置上传解析器
"maxUploadSize":表示文件大小,图片的大小
"maxInMemorySize" :图片加载到内存当中的大小 长 * 宽 * 像素字节数(argb8888,rgb565,argb4444)
"defaultEncoding":UTF-8
优秀程序员:CV战士
-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设定文件上传的最大值为100MB,100*1024*1024 -->
<property name="maxUploadSize" value="104857600" />
<!-- 设定文件上传时写入内存的最大值,如果小于这个参数不会生成临时文件,默认为10240 -->
<property name="maxInMemorySize" value="4096" />
<!-- 设定默认编码 -->
<property name="defaultEncoding" value="UTF-8"/>
</bean>
3. house/show.html页面添加事件
$(function(){
//表示房源图片
$("#upload1").on("click",function(){
opt.openWin('/houseImage/uploadShow/[[${house.id}]]/1','上传房源图片',580,430);
});
//表示房产图片
$("#upload2").on("click",function(){
opt.openWin('/houseImage/uploadShow/[[${house.id}]]/2','上传房产图片',580,430);
});
//上传首页默认图片
$("#upload3").on("click",function(){
opt.openWin('/houseImage/uploadShow/[[${house.id}]]/3','上传首页默认图片',580,430);
});
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h3>房源图片信息</h3>
<a class="btn btn-xs btn-primary" id="upload1">上传房源图片</a>
</div>
<div class="ibox-content">
<a th:each="item,it : ${houseImage1List}" class="fancybox" >
<img alt="image" th:src="${item.imageUrl}"/>
<a th:attr="data-id=${item.id}" class="deleteImages1">删除</a>
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h3>房产图片信息</h3>
<a class="btn btn-xs btn-primary" id="upload2">上传房产图片</a>
</div>
<div class="ibox-content">
<a th:each="item,it : ${houseImage2List}" class="fancybox" >
<img alt="image" th:src="${item.imageUrl}"/>
<a th:attr="data-id=${item.id}" class="deleteImages1">删除</a>
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h3>房源首页图片</h3>
<a class="btn btn-xs btn-primary" id="upload3">上传首页默认图片</a>
</div>
<div class="ibox-content">
<a th:each="item,it : ${houseImage3List}" class="fancybox" >
<img alt="image" th:src="${item.imageUrl}"/>
<a th:attr="data-id=${item.id}" class="deleteImages2">删除</a>
</a>
</div>
</div>
</div>
</div>
4.添加服务器端代码
package com.atguigu.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.atguigu.entity.HouseImage;
import com.atguigu.result.Result;
import com.atguigu.service.HouseImageService;
import com.atguigu.util.QiniuUtils;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
import java.util.UUID;
/**
* @Date 2022/6/22 14:12
* @Author by:Plisetsky
*/
@Controller
@RequestMapping("/houseImage")
public class HouseImageController {
private static final String PAGE_UPLOAD_SHOW = "house/upload";
private static final String ACTION_LIST = "redirect:/house/detail/";
@Reference
HouseImageService houseImageService;
//删除默认展示图片
@RequestMapping("/deleteDefault/{houseId}/{id}")
public String deleteDefault(@PathVariable("houseId")Long houseId,@PathVariable("id") Long id) {
HouseImage houseImage = houseImageService.getById(id);
String imageName = houseImage.getImageName();
houseImageService.delete(id);//删除前查询数据,否则删除后查不到了
//删除house默认图片
houseImageService.updateDefaultImage(houseId,null);
//删除七牛云内图片
QiniuUtils.deleteFileFromQiniu(imageName);
return ACTION_LIST + houseId;
}
//删除房源、房产图片
@RequestMapping("/delete/{houseId}/{id}")
public String delete(@PathVariable("houseId")Long houseId,@PathVariable("id") Long id){
HouseImage houseImage = houseImageService.getById(id);
String imageName = houseImage.getImageName();
houseImageService.delete(id);//删除前查询数据,否则删除后查不到了
//删除七牛云内图片
QiniuUtils.deleteFileFromQiniu(imageName);
return ACTION_LIST + houseId;
}
//上传房源、房产图片,可以不返回值
@RequestMapping("/upload/{houseId}/{type}")
@ResponseBody
public Result upload(@RequestParam("file") MultipartFile[] files, //file是固定的,底层会自动生成表单项.[]接收多个表单项
@PathVariable("houseId") Long houseId,
@PathVariable("type") Integer type) throws Exception { //接收上传文件,再上传到七牛云上
if(files !=null && files.length>0){ //至少上传一个文件
for (MultipartFile file:files){
byte[] bytes = file.getBytes();
//String name = file.getOriginalFilename();//原始文件名称
//QiniuUtils.upload2Qiniu(bytes,name); 不能使用原始文件名称上传,会重名覆盖
String newFileName = UUID.randomUUID().toString();
//将图片上传到七牛云
QiniuUtils.upload2Qiniu(bytes,newFileName);
String imageUrl = "http://rdv0p81lw.hn-bkt.clouddn.com/"+newFileName;
//如果上传的是首页默认图片,将地址更新到house表
if(type==3){
houseImageService.updateDefaultImage(houseId,imageUrl);
}
//将图片路径信息等保存到数据库
HouseImage houseImage = new HouseImage();
houseImage.setHouseId(houseId);
houseImage.setImageName(newFileName);
houseImage.setImageUrl(imageUrl);
houseImage.setType(type);
houseImageService.insert(houseImage);
}
}
return Result.ok();
}
//前往上传页面
@RequestMapping("/uploadShow/{houseId}/{type}")
public String uploadShow(@PathVariable("houseId") Long houseId,
@PathVariable("type") Integer type, Map map){
map.put("houseId",houseId);
map.put("type",type);
return PAGE_UPLOAD_SHOW;
}
}
5. 上传页面 (webuploader上传组件)
注意:修改webuploader-demp.js 中路径地址变量
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:include="common/head :: head"></head>
<!--百度上传组件-->
<link rel="stylesheet" type="text/css" th:href="@{/static/css/plugins/webuploader/webuploader.css}">
<link rel="stylesheet" type="text/css" th:href="@{/static/css/demo/webuploader-demo.css}">
<script th:src="@{/static/js/plugins/webuploader/webuploader.min.js}"></script>
<script th:src="@{/static/js/demo/webuploader-demo.js}"></script>
<script type="text/javascript">
// 添加全局站点信息 swf文件路径
var BASE_URL = '/static/js/plugins/webuploader';
// 自定义请求地址,传入房子id和类型
var BASE_UPLOAD = '/houseImage/upload/[[${houseId}]]/[[${type}]]';
// 会自动生成表单
</script>
<body class="gray-bg">
<div class="row">
<div class="col-sm-9">
<div class="wrapper wrapper-content animated fadeInUp">
<div class="ibox">
<div class="ibox-content">
<div class="row">
<div class="ibox-content">
<div class="page-container" id="uploadShow">
<p>您可以尝试文件拖拽,使用QQ截屏工具,然后激活窗口后粘贴,或者点击添加图片按钮,来体验此demo.</p>
<div id="uploader" class="wu-example">
<div class="queueList">
<div id="dndArea" class="placeholder">
<div id="filePicker"></div>
<p>或将照片拖到这里,单次最多可选300张</p>
</div>
</div>
<div class="statusBar" style="display:none;">
<div class="progress">
<span class="text">0%</span>
<span class="percentage"></span>
</div>
<div class="info"></div>
<div class="btns">
<div id="filePicker2"></div>
<div class="uploadBtn">开始上传</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-primary" type="button" onclick="opt.closeWin(true);">确定</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
6 上传测试
四、用户头像上传
1、用户列表页添加选项
<a class="upload" th:attr="data-id=${item.id}">上传头像</a>
$(".upload").on("click",function(){
var id = $(this).attr("data-id");
opt.openWin('/admin/uploadShow/'+id,'上传头像',580,300);
});
2、添加服务器端代码
//头像上传
@RequestMapping("/upload/{id}")
public String upload(@PathVariable("id") Long id,
@PathVariable("file") MultipartFile file,HttpServletRequest request) throws IOException {
//1.上传到七牛云
byte[] bytes = file.getBytes();
String newFileName = UUID.randomUUID().toString();
QiniuUtils.upload2Qiniu(bytes,newFileName);
//2.地址存储到数据库
String imageUrl = "你的七牛云空间的域名/"+newFileName;
Admin admin = new Admin();
admin.setId(id);
admin.setHeadUrl(imageUrl);
adminService.update(admin);
return this.successPage(this.MESSAGE_SUCCESS, request);
}
//前往头像上传
@RequestMapping("/uploadShow/{id}")
public String uploadShow(@PathVariable("id") Long id,Map map){
map.put("id",id);
return PAGE_UPLOAD;
}
3、上传页面
admin/upload.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:include="common/head :: head"></head>
<body class="gray-bg">
<div class="wrapper wrapper-content animated fadeInRight">
<div class="ibox float-e-margins">
<div class="ibox-content" style="width: 98%;">
<form id="ec" th:action="@{/admin/upload/{id}(id=${id})}" method="post" enctype="multipart/form-data" class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label">上传头像:</label>
<div class="col-sm-10">
<input type="file" name="file" id="file" class="form-control" readonly/>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2 text-right">
<button class="btn btn-primary" type="submit">确定</button>
<button class="btn btn-white" type="button" onclick="javascript:opt.closeWin();" value="取消">取消</button>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
</html>