目录
一. 项目背景
- 现在很多网页都可以见到上传图片的功能,假如我们上传一张本地图片后,网页就会显示我们刚刚所上传的图片,比如博客上传一张图、个人信息提交头像页面等等。那么这背后的原理是什么呢?
- 其实当我们浏览网页的时候,本质上是从 对端服务器 获取资源,浏览器在获得这些资源之后进行解析和渲染,呈现在我们用户面前的就是绚丽多彩的页面了。
- 一般来讲这些资源文档主要有三种格式:HTML、CSS、JS。HTML相当于网页的骨架,CSS相当于网页的衣服,用来规定网页的样式,比如字体大小以及排版等等,而JavaScript则主要负责一些动态的逻辑,比如在网页上按下一个按键后会显示什么等等。
二.项目使用技术栈与环境
所用技术:
- JDBC(Java操作MySQL)
- 数据库设计(MySQL建库建表,增删查)
- Gson(反序列化成json字符串)
- Servlet(服务器端API设计,前后端交互)
- 基于md5进行校验(决定文件是否要写入磁盘)
平台与环境:
- Windows
- IDEA
- Maven
三.项目功能
- 核心就是实现一个 HTTP 服务器,实现对图片的增删查功能,同时搭配简单的页面辅助完成对图片的上传,显示,阅览功能。(实现一个 HTTP 服务器,然后用这个服务器来存储图片,针对每个图片提供一个唯一的 url,有了这个 url 之后就可以借助它把图片展示到其他网页上。)
- 本项目的结构主要分为两个部分,数据存储模块和服务器模块。使用MySQL存储图片的属性信息,将图片内容保存到本地磁盘,服务器向外提供诸如上传图片、获取单个图片的属性信息、获取所有图片的属性信息、和删除图片等API接口。
四.项目具体实现
4.1:数据库、表的设计
(1)imageId:图片的 id ,设置为自增主键
(2)imageName:图片名称
(3)size:图片大小
(4)uploadTime:图片的上传时间
(5)contentType:图片的类型(image/jpg,image/png等)
(6)path:图片在磁盘上的存储路径
(7)md5:一种计算校验和的算法(主要用来判断两张图片是否内容相同,即是否两张图片一样)
知识扫盲:什么是md5
这是一种常见的字符串 hash 算法, 具有三个特性:
- 不管源字符串多长, 得到的最终 md5 值都是固定长度
- 源字符串稍微变化一点点内容, md5 值就会变化很大(降低冲突概率)
- 通过原字符串很容易计算得到 md5 值,但是根据 md5 推导出原字符串很难(几乎不可能)
- md5 常用于哈希算法,加密算法,校验和
4.2:pom.xml文件中引入依赖
注:JSON 是一种常见的数据格式组织方式,源于 JavaScript , 是一种键值对风格的数据格式。Java 中可以使用 Gson 库来完成 JSON 的解析和构造. 在 Maven 中新增 对 Gson 库的依赖就可以使用 JSON 了
<!-- 打包方式是 war 包,一种用于 web 应用的包,原理类似 jar 包 -->
<packaging>war</packaging>
<!-- 指定属性信息 -->
<properties>
<encoding>UTF-8</encoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- 加入 servlet 依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<!-- servlet 版本和 tomcat 版本有对应关系,切记 -->
<version>3.1.0</version>
<!-- 这个意思是我们只在开发阶段需要这个依赖,部署到 tomcat 上时就不需要了 -->
<scope>provided</scope>
</dependency>
<!--单元测试矿浆junit依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!--JSON库,gson-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<!--对应上传图片操作 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.14</version>
</dependency>
</dependencies>
<build>
<!-- 指定最终 war 包的名称 -->
<finalName>image_server</finalName>
4.3:服务器端封装数据库的的设计
4.3.1:前期准备工作,首先创建一个maven项目
(1)Maven 是什么?
Maven是 Apache 下的一个纯 Java 开发的开源项目,是一个项目构建和管理的工具;它提供了帮助管理、 构建、文档、报告、依赖、发布、分发的方法。可以方便的编译代码、进行依赖管理、管理二进制库等等。Maven是专门用于构建和管理Java相关项目的工具
(2)使用 Maven 的好处?
maven 的目标是完成项目构建解决一切繁琐事宜。我们具体关注它的以下功能:
- 提供一个标准的项目工程目录
- 提供项目描述
- 提供强大的版本管理工具
- 可以分阶段的进行构建过程
- 提供了丰富的插件库使用
4.3.2:dao 包下封装对数据库的相关操作
具体代码实现:
DBUtil工具类:
/**
* 创建一个单例类来辅助创建连接,关闭连接
*/
public class DBUtil {
private static volatile DataSource dataSource = null;//管理数据源
// 通过这个方法来创建 DataSource 的实例
public static DataSource getDataSource() {
//单例模式(双重校验+锁+volatile的饿汉模式)
//加锁实际上会牺牲效率,所以不要每次都加锁,只要判断datasource不为空,就直接返回dataSource对象,保证效率
if (dataSource == null) {
synchronized (DBUtil.class) {
if (dataSource == null) {
dataSource = new MysqlDataSource();
MysqlDataSource tmpDataSource = (MysqlDataSource) dataSource;
tmpDataSource.setURL("jdbc:mysql://localhost:3306/image_server_project?user=root&password=1450618603&useSSL=false&characterEncoding=UTF-8");
}
}
}
return dataSource;
}
//建立数据库连接方法
public static Connection getConnection() {
try {
return getDataSource().getConnection();
} catch (SQLException e) {
throw new RuntimeException("数据库连接失败");
}
}
//关闭数据库连接方法
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Image图片类:
/**
* 一个 Image 对象:和数据库表的设计相对应
*/
public class Image {
private int imageId;
private String imageName;
private int size;
private String uploadTime; //图片上传时间
private String contentType; //图片的格式:image.jpg/image.png 等等
private String path; //图片应该往磁盘写的位置
private String md5; //校验和机制(用于判断两张图片的内容是否相同)
public int getImageId() {
return imageId;
}
public void setImageId(int imageId) {
this.imageId = imageId;
}
public String getImageName() {
return imageName;
}
public void setImageName(String imageName) {
this.imageName = imageName;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getUploadTime() {
return uploadTime;
}
public void setUploadTime(String uploadTime) {
this.uploadTime = uploadTime;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getMd5() {
return md5;
}
public void setMd5(String md5) {
this.md5 = md5;
}
@Override
public String toString() {
return "Image{" +
"imageId=" + imageId +
", imageName='" + imageName + '\'' +
", size=" + size +
", uploadTime='" + uploadTime + '\'' +
", contentType='" + contentType + '\'' +
", path='" + path + '\'' +
", md5='" + md5 + '\'' +
'}';
}
}
数据库操作类:
/**
* 用来对数据库的增删改查的一个类
* JDBC的操作流程
* 1.获取数据库连接
* 2.创建并拼装SQL语句
* 3.执行SQL语句
* 4.关闭结果集resultSet对象、关闭statement对象,关闭连接
*/
public class ImageDao {
/**
* 把一个 Image 对象插入到数据库中
*/
public boolean insert(Image image){
Connection connection = null;
PreparedStatement statement = null;
try {
//1.获取数据库连接
connection = DBUtil.getConnection();
//2.创建并拼接sql语句
String sql = "insert into image_table values(null,?,?,?,?,?,?)";
statement = connection.prepareStatement(sql);
statement.setString(1,image.getImageName());
statement.setInt(2,image.getSize());
statement.setString(3,image.getUploadTime());
statement.setString(4,image.getContentType());
statement.setString(5,image.getPath());
statement.setString(6,image.getMd5());
//3.执行sql语句
int ret = statement.executeUpdate();
if (ret!=1) {
throw new RuntimeException("插入数据库出错");
}else {
return true;
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//4.关闭资源
DBUtil.close(connection,statement,null);
}
return false;
}
/**
* 查找数据库中所有图片的属性信息,并存储在 List 列表中
* @return 所有图片信息
*/
public List<Image> selectAll(){
List<Image> images = new ArrayList<>();
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet= null;
try {
connection = DBUtil.getConnection();
String sql = "select * from image_table";
statement = connection.prepareStatement(sql);
resultSet = statement.executeQuery();
while (resultSet.next()) {
Image image = new Image();
image.setImageId(resultSet.getInt("imageId"));
image.setImageName(resultSet.getString("imageName"));
image.setSize(resultSet.getInt("size"));
image.setUploadTime(resultSet.getString("uploadTime"));
image.setContentType(resultSet.getString("contentType"));
image.setPath(resultSet.getString("path"));
image.setMd5(resultSet.getString("md5"));
images.add(image);
}
return images;
} catch (SQLException e) {
e.printStackTrace();
}finally {
//关闭结果集,statement对象和连接
DBUtil.close(connection,statement,resultSet);
}
return null;//出错的话返回空的结果集
}
/**
* 根据 imageId 查找指定图片的属性信息
* @param imageId
* @return 返回根据id查找到的图片信息
*/
public Image selectById(int imageId){
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = DBUtil.getConnection();
String sql = "select * from image_table where imageId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1,imageId);
resultSet = statement.executeQuery();
while (resultSet.next()) {
Image image = new Image();
image.setImageId(resultSet.getInt("imageId"));
image.setImageName(resultSet.getString("imageName"));
image.setSize(resultSet.getInt("size"));
image.setUploadTime(resultSet.getString("uploadTime"));
image.setContentType(resultSet.getString("contentType"));
image.setPath(resultSet.getString("path"));
image.setMd5(resultSet.getString("md5"));
return image;
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
// 5.关闭连接和statement对象
DBUtil.close(connection,statement,resultSet);
}
return null; //如果失败的话返回null
}
/**
* 根据 imageId 删除指定图片
* @param imageId
*/
public void delete(int imageId){
Connection connection = null;
String sql = "delete from image_table where imageId = ?";
PreparedStatement statement = null;
try {
connection = DBUtil.getConnection();
statement = connection.prepareStatement(sql);
statement.setInt(1,imageId);
int ret = statement.executeUpdate();
if (ret != 1) {
throw new RuntimeException("删除数据库异常");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtil.close(connection,statement,null);
}
}
//根据MD5的值在数据库中查找数据
public Image selectByMD5(String md5) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = DBUtil.getConnection();
String sql = "select * from image_table where md5 = ?";
statement = connection.prepareStatement(sql);
statement.setString(1,md5);
resultSet = statement.executeQuery();
while (resultSet.next()) {
Image image = new Image();
image.setImageId(resultSet.getInt("imageId"));
image.setImageName(resultSet.getString("imageName"));
image.setSize(resultSet.getInt("size"));
image.setUploadTime(resultSet.getString("uploadTime"));
image.setContentType(resultSet.getString("contentType"));
image.setPath(resultSet.getString("path"));
image.setMd5(resultSet.getString("md5"));
return image;
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
// 5.关闭连接和statement对象
DBUtil.close(connection,statement,resultSet);
}
return null; //如果失败的话返回null
}
}
4.4:服务器端servlet接口的设计
首先创建一个 api 包,在这个包中创建两个 Servlet 类,这两个Servlet 类都继承自 HttpServlet类,然后重写了对应的 doxxx() 方法。
一个用来完成图片的增删查操作(ImageServlet类)
一个用来展示图片的详细内容(ImageShowServlet类)
注意:要把这两个Servlet 类都要配置到 web.xml 文件中,其中类名要写完整的带包名的名字
<servlet>
<servlet-name>ImageServlet</servlet-name>
<servlet-class>api.ImageServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ImageServlet</servlet-name>
<url-pattern>/image</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>ImageShowServlet</servlet-name>
<servlet-class>api.ImageShowServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ImageShowServlet</servlet-name>
<url-pattern>/imageShow</url-pattern>
</servlet-mapping>
imageServlet类:
public class ImageServlet extends HttpServlet {
/**
* 查询所有图片/指定图片的内容
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//通过URL中是否带有imageId参数来区分是查看所有图片还是指定图片
//例如:URL为 /image/imageId=100 name则是查看imageId为100的图片信息
// 如果URL后面的键值对都不存在或者只存在键无值,就查询所有图片信息
String imageId = request.getParameter("imageId");
if (imageId == null || imageId.equals("")) {
//查看所有图片信息
selectAll(request,response);
}else {
//查看指定图片信息
selectById(imageId,response);
}
}
/**
* 查看指定图片信息
* @param imageId
* @param response
*/
private void selectById(String imageId, HttpServletResponse response) throws IOException {
response.setCharacterEncoding("utf-8");
response.setContentType("application/json");
//1.创建一个ImageDao对象,根据指定id在数据库中查找
ImageDao imageDao = new ImageDao();
Image image = imageDao.selectById(Integer.parseInt(imageId));
//2.创建Gson对象,将查到的数据转换成JSON字符串格式,并且写给响应response对象
Gson gson = new GsonBuilder().create();
String jsonData = gson.toJson(image);
//发送响应给前端
PrintWriter writer = response.getWriter();
writer.println(jsonData);
}
private void selectAll(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setCharacterEncoding("utf-8");
response.setContentType("application/json");
ImageDao imageDao = new ImageDao();
List<Image> images = imageDao.selectAll();
Gson gson = new GsonBuilder().create();
String jsonData = gson.toJson(images);
PrintWriter writer = response.getWriter();
writer.println(jsonData);
}
/**
* 上传图片
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.获取图片信息,并存入数据库
// 1.1需要创建一个factory对象和upload对象,为了获取到图片属性做的准备工作
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
// 1.2通过upload对象进一步解析请求(解析HTTP请求中的 body 部分)
// 理论上来说,HTTP 支持一个请求中同时上传多个文件,FileItem 就代表上传的一个文件对象,多个文件用 List 组织
List<FileItem> items = null;
try {
items = upload.parseRequest(request);
} catch (FileUploadException e) {
//出现异常说明解析出错
e.printStackTrace();
//告诉客户端出现的具体错误信息
PrintWriter writer = response.getWriter();
response.setCharacterEncoding("utf-8");
response.setContentType("application/json");
writer.write("{\"ok\":false \"reason\":\"上传图片请求解析失败\"}");
return;
}
// 1.3将FileItem中的对象提取出来转换成Image对象,才能存到数据库中
FileItem fileItem = items.get(0);
Image image = new Image();
image.setImageName(fileItem.getName());
image.setSize((int)fileItem.getSize());
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
String time = simpleDateFormat.format(date);
image.setUploadTime(time);
image.setContentType(fileItem.getContentType());
//计算md5的值
image.setMd5(DigestUtils.md5Hex(fileItem.get()));// md5 值是十六进制的字符串 图片内容相同时则md5值相同,图片内容不同时,md5值不同
//自定义存储路径,加上数据保证不会出现连续插入两张图片报错,因为每时每刻时间戳是唯一的
image.setPath("./image/"+image.getMd5());
//存入数据库
ImageDao imageDao = new ImageDao();
//看看数据库中是否存在相同的md5值的图片,不存在则返回null
Image existImage = imageDao.selectByMD5(image.getMd5());
imageDao.insert(image);
//2.获取图片的内容信息,如果相册中不存在该图片,写入磁盘文件中
if (existImage==null) {
File file = new File(image.getPath());
try {
fileItem.write(file);
} catch (Exception e) {
e.printStackTrace();
//出现异常说明写入文件失败
PrintWriter writer = response.getWriter();
response.setCharacterEncoding("utf-8");
response.setContentType("application/json");
writer.write("{\"ok\":false \"reason\":\"图片内容写入磁盘失败\"}");
return;
}
}
/* PrintWriter writer = response.getWriter();
writer.write("{\"ok\":true}");*/
response.sendRedirect("index.html");
}
/**
* 删除指定图片
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.先获取请求中的imageId
String imageId = req.getParameter("imageId");
if (imageId==null || imageId.equals("")) {
resp.setStatus(200);
resp.setCharacterEncoding("utf-8");
resp.setContentType("application/json");
PrintWriter writer = resp.getWriter();
writer.write("{\"ok\":\"false\" \"reason\":\"解析请求失败\"}");
return;
}
//2.创建ImageDao对象,查看到该图片对象对应的相关属性,若存在则要删除
// 既要删除数据库中的内容,也要删除磁盘中存储的对应图片,因此要知道该 imageId 对应的图片对象的存储路径
ImageDao imageDao = new ImageDao();
Image image = imageDao.selectById(Integer.parseInt(imageId));
if (image == null) {
//说明请求中传入的id在数据库中不存在
resp.setStatus(200);
resp.setCharacterEncoding("utf-8");
resp.setContentType("application/json");
PrintWriter writer = resp.getWriter();
writer.write("{\"ok\":\"false\" \"reason\":\"数据库中不存在该数据\"}");
return;
}
//3.删除数据库中的记录
imageDao.delete(Integer.parseInt(imageId));
// 4.删除本地磁盘文件(注意:数据库中可能有多张相同的图片,但是磁盘中只写入了一张图片,删除要注意)
// 刚刚已经从数据库中删除了那张图片,若此时数据库中还存有相同的图片,那么不应该删除磁盘图片文件
// 相同图片的 md5 值是相同的
Image existImage = imageDao.selectByMD5(image.getMd5());
// 如果删除完毕后,数据库中没有相同的图片了,那么就删除磁盘文件,否则就不删除
if (existImage == null) {
File file = new File(image.getPath());
file.delete();
resp.setStatus(200);
}
}
}
imageShowServlet类:
public class ImageShowServlet extends HttpServlet {
//基于白名单的防盗链机制
static private HashSet<String> whiteList = new HashSet<>();
//白名单
static {
whiteList.add("http://localhost:8080/index.html");
whiteList.add("http://localhost:8080/image_server/index.html");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//防盗链功能,因为服务器同时访问人太多容易崩溃
String referer = request.getHeader("Referer");
if (!whiteList.contains(referer)) {
response.setContentType("application/json; charset=utf-8");
response.getWriter().write("{ \"ok\":false,\"reason\":\"该网站未授权访问\" }");
return;
}
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
response.setContentType("application/json");
//1.解析出imageId
String imageId = request.getParameter("imageId");
if (imageId==null || imageId.equals("")) {
PrintWriter writer = response.getWriter();
writer.write("{\"ok\":\"false\" \"reason\":\"数据解析失败\"}");
return;
}
//2.根据imageId查找数据库,得到对应的图片属性信息(需要知道图片存储的路径)
ImageDao imageDao = new ImageDao();
Image image = imageDao.selectById(Integer.parseInt(imageId));
//3.根据路径打开文件,读取其中的内容,写入到响应体中
response.setContentType(image.getContentType());
File file = new File(image.getPath());
//由于图片是二进制文件,因此使用字节流方式读取
FileInputStream fileInputStream = new FileInputStream(file);
ServletOutputStream outputStream = response.getOutputStream();
byte[] buffer = new byte[1024];
while (true) {
int len = fileInputStream.read(buffer);
if (len == -1) {
//文件读取完毕
break;
}
//此时已经读到一部分数据,在buffer中存着,应该写到响应对象中
outputStream.write(buffer);
}
fileInputStream.close();
outputStream.close();
}
}
至此,后端代码告一段落
4.5:前端代码实现:
有句话说的好,下坡易上坡难,我们不会做加法,就学着去做减法,前端不太
了解的情况我们可以在网上下载一些前端模板,对其删减已达到我们想要的效果
基于模板进行删减修改:
(1)title 标签的修改
(2)页面的选择文件和提交按钮
注意:为了让 “提交按钮” 和前面的选择文件一样高, 这里加了一个style=“height:41px”
此处我们直接使用 style 属性调整样式, 这是一个简单粗暴的做法. 事实上专业的前端开发很少这样做, 因为代码的可维护性不好
(3)页面 Logo 的修改
(4)使用 Vue
Vue 是 JS 的框架,他做的核心工作是把页面显示的内容和 JS 中的代码相互关联到一起,修改 JS 中的变量就能很方便的影响到页面的显示情况。
Vue的基本用法:
var 声明这是一个变量,构造方法是使用了 JSON 形式的{} ,el 属性表示我要把当前这个对象关联上某一个 html 的标签(把 app 对象关联到 html 中一个 id=“app” 的标签)
主要JS代码:
第一步: id=“app” 把 JS 代码和 该标签绑定
第二步:使用 v-bind:src 把图片的内容 通过 ImageShowServlet 接口获取到.
第三步:使用 {{image.imageName}} 表示图片的名称
第四步:v-for 能够循环访问一个数据
注:只要访问 index.html 文件就要加载服务器中的所有图片,所以只要访问 index.html 文件,就要调用该方法
<script>
var app=new Vue({
// e1 属性表示我要把当前这个对象关联到某一个 html 的标签上(把 app 对象关联到 html 中的一个 id="app 的标签)
el: '#app',
data: {
images: [ //一个集合数组
]
},
methods:{
// 请求格式: GET /image
getImages(){
$.ajax({ // ajax 是在JS中构造 http 请求发送给服务器的一种实现方式
url:"image",
type:"get",
context:this,
success:function (data,status) {
//此处的代码在浏览器收到响应之后,才会执行到
//参数中的 data 就相当于收到的 http 响应中的 body 部分
this.images=data;
$('#app').resize(); //触发浏览器,让它自动调整大小,让图片正确显示
}
})
},
// 请求格式: DELETE /image?imageId=
remove(imageId){
$.ajax({
url:"image?imageId="+imageId,
type:"delete",
context:this,
success:function (data,status) {
this.getImages(); //只要删除图片,就重新获取一下服务器上剩余的图片,保证页面显示的图片数量正常
alert("删除图片成功!");//弹出一个对话框
}
})
}
}
})
app.getImages(); //项目只要访问 index.html 就自动调用该方法
</script>
(5)在返回完后端代码完善上传功能
当前的上传请求成功后会返回一个 JSON 格式的数据. 而我们需要的是直接能看到上传的效果,因此应该修改响应,直接重定向到 index.html 页面观察即可。
修改上传接口中的响应, 直接返回一个 302 响应, 重定向回主页即可。
(6)实现删除图片
此程序的事件冒泡机制:div 标签中有 3 个字标签
①点击 div 就会触发一个点击事件,div 就会处理这个点击事件并显示预览图
②点击 button 删除按钮的时候也会触发一个点击事件,触发删除操作
③点击 cilck 就会先被 button 处理,进行删除操作,然后顺着 button 的父标签,传递给 div 标签,然后 div 再处理一次,触发预览图效果
4.6:项目的两个拓展点:
4.6.1:防盗链机制:增加安全性
通过 HTTP 请求中的 Refer 字段判定是否是指定网站请求服务器图片,未授权网站就无法访问图片内容。
4.6.2:md5校验和机制:实现相同内容的图片在磁盘上只能存一份
整体思路:
- 修改 dao 层代码,在 dao 层实现一个selectByMd5()的方法,根据 Md5 值来查找数据库中的图片信息(是否存在多张相同图片).
- 修改上传图片代码,在磁盘上存储文件时先判定,该 md5 对应的图片是否已经存在于磁盘上了,若存在就不必再往磁盘写了.
- 修改删除图片代码,先删除数据库记录,删除完毕后,看数据库中是否还存在刚刚相同的 md5 的记录.如果不存在,就删除磁盘文件.
五:项目效果展示
效果图:
此处我们发现,数据库和浏览器都存有7张图片,然而本地磁盘只存有六张图片,这正是由于我们用了md5实现了相同内容的图片在磁盘上只能存一份,并加入了时间戳,保证每时每刻的时间是不相同的,所以即使两张图片的内容相同,但是由于时间是不同的,所以图片数据插入数据库不会报错,至此整个项目基本就告一段落了,由于本人能力有限,些许地方还有实现不当,希望各位小伙伴发现了可以不吝批评