文章目录
- 一、前言
- 1)背景需求
- 2)重要知识点
- 二、项目设计
- 1)整体架构及设计
- 2)数据库设计
- 3)服务器 API 设计(前后端交互接口设计)
- 4)进行源代码的开发
- 4.1 封装数据库操作
- 4.2 基于Servlet来搭建服务器
- 5)前端页面设计
- 5.1使用HTML模板
- 5.2基于模板进行删减
- 5.3使用Vue.js
- 5.4 实现展示图片
- 5.5实现展示图片
- 5.6 完善功能
- 三、后期加入
- 1)实现基于白名单方式的防盗链
- 2)基于 MD5 实现相同内容图片只存一份:类似于百度网盘的 "秒传" 功能
一、前言
1)背景需求
- 在我们的日常生活或者工作过程中,有时候我们需要在写博客或者写文档的时候需要插入图片,这个时候我们就需要给定一个链接就把图片的内容获取出来。
- 这个服务器可以进行把我们的图片进行上传,下载,查看等等操作,可以适当的作为一个云储存图片的地方。
2)重要知识点
- 简单的Web服务器设计的能力
- 通过JDBC来操作MySQL数据库
- 进行数据库的设计
- Gson库的使用,更好的去理解JSON
- 强化对HTTP协议的理解
- 对Servlet的理解及使用
- 基于Md5对图片进行校验
- 加深对Postman,Filddler,Tomcat工具的使用
- 软件测试的基本理解和方法
二、项目设计
1)整体架构及设计
- 核心就是一个HTTP服务器,提供对图片的增删改查操作,同时搭配简单的前端页面辅助完成图片的操作展示。
2)数据库设计
数据库的属性:
图片编号:imageId --> int型非空且自增
图片的名字:imageName --> varchar(50)
图片的大小:size --> int型
图片的上传时间:uploadTime varchar(50)
图片正文类型:contentType varchar(50)
图片路径:path varchar(1024)
校验和:md5算法:图片的md5校验和 --> varchar(1024)
数据库存储的是图片的属性(元信息),而图片正文是以文件的形式直接存在磁盘上的,当有一个图片上传到服务器上时,数据库就记录一个path对应到磁盘上的这个文件
校验和:通过一个更短的字符串,来验证整体数据是否正确。短的字符串是根据原串内容通过一定的规则来计算出来的 这是一种常见字符串 hash 算法, 具有三个特性: 1.不管源字符串多长, 得到的最终 md5 值都是固定长度 2.源字符串稍微变化一点点内容, md5 值会变化很大(降低冲突概率) 3.通过原字符串很容易计算得到 md5 值, 但是根据 md5 推导出原字符串很难(几乎不可能).
3)服务器 API 设计(前后端交互接口设计)
3.1 Gson的使用
- 这里我们使用JSON来组织数据。Json 是一种常见是数据格式组织方式. 源于 JavaScript, 是一种键值对风格的数据格式.
- Java 中可以使用 Gson 库来完成 Json 的解析和构造.
在Maven 中新增 Gson 的依赖
一般的时候它会是这个样子:
3.2 新增图片
我们先写一个简单的 html 来实现上传图片
3.3 查看所有图片元信息
3.4 查看指定图片元信息
3.5 删除图片
3.6 查看图片内容
4)进行源代码的开发
4.1 封装数据库操作
4.1.1 创建dao包
(1)先创建DBUtil类实现了封装获取数据库连接的操作
- 创建一个单例类DBUtail辅助创建连接,其中URL为我的云服务器的MySQL链接
这个类主要包含3个方法:
创建DateSource的实例:DataSource getDataSource() 获取连接:Connection getConnection() 关闭连接:void close(Connection connection, PreparedStatement statement, ResultSet resultSet) 这里关闭的时候要注意:先创建的对象后关闭,后创建的对象先关闭 close也不是真的销毁连接,只是回收到池子里了,后边还可以用,因为DataSource内置了连接池
(2)创建Image类,每一个image对象对应到一个图片对象(包含图片的相关属性)
(3)创建imageDao类:作为image对象的管理器,借助这个类完成image对象的增删改查操作
(4)使用JDBC的时候是用DataSourse访问数据库的,稍微要比JDBCDriver方式高效一些
(5)当写完一组功能后用单元测试的思想进行测试,也就是将一个类或者方法作为一个单元,然后分开测试,一旦出现了问题,就能及时发现BUG。
4.1.2 了解加回顾
回顾:受查异常与非受查异常
出现异常之后,处理的具体措施: 1)当前接触的大部分都是打印调用栈 2)让程序直接直接终止:及时止损 3)监控报警通知程序猿
了解jar与war包:类似于zip这样的压缩包,也就是将一大堆.class文件放到一起打包成一个文件
使用maven打包成war包放在服务器上的Tomcat的安装目录里的webapps目录下
4.2 基于Servlet来搭建服务器
Servlet负责处理客户端发来的请求,生成服务器所需要生成的响应
4.2.1 创建api包
(1)在包下创建ImageServlet类继承HttpServlet父类并且重写这个父类中的一些方法,完成图片的增删改查
- 这个类的 doPost 对应插入图片, doGet 对应查看图片信息, doDelete 对应删除图片.
- 这里的doGet要分成两种情况, 一个是获取所有图片信息, 一个是获取单个图片信息,根据请求中是否带有 image_id 参数来决定
- 记得将这个类加到web.xml中,其中的类名要写完整的带包的名字
(2)在包下创建ImageShowServlet类继承HttpServlet父类并且重写这个父类中的doGet方法实现展示图片详细内容
4.2.2 了解加回顾
对Servlet的理解:
Servlet相关的代码执行方式和平时写的不太一样,平时的代码是从main方法运行的,而Servlet里面没有main方法,而是靠Tomcat来自动调用到Servlet的代码,Tomcat的工作原理和曾经写的Http服务器是很相似的Tomcat的工作步骤:
(1)启动的时候要绑定端口号(一般是8080)
(2)进入一个循环
(3)在主循环里面,调用accept获取到当前的请求的链接
(4)读取客户端发生的数据(字符串)
(5)把这个字符串按照Http协议来进行解析
(6)解析出的Http请求的方法和URL之后,找到对应的Servlet,并执行对应的doXXX方法
(7)生成响应,回复客户端理解Tomcat中URL与Servlet的映射关系,步骤如下:
(1)Tomcat根据URL查找映射关系表(在那个web.xml文件中),找到api.ImageServlet类
(2)Tomcat根据get方法,决定给你api.ImageServlet类创建一个对象,并且调用其中的doGet方法(这个方法我们一般要进行重写)
(3)执行doGet方法,往resp对象中写入一些内容
(4)Tomcat构造resp对象,根据这个对象生成Http响应报文,再通过socket写回给客户端(浏览器或者其他)
5)前端页面设计
5.1使用HTML模板
因为当前的知识对前端不是很了解,所以我们采用对别人的HTML模板就行删减,下载好一个比较好看的模板之后下载到项目的webapp目录中。
前置知识
HTML:网页的骨架 --骨架
CSS:描述网页上组件的样式(位置,颜色,大小,字体,背景等等) --皮囊
JavaScript:描述前端页面上的一些动作(和用户具体交互的行为) --灵魂
HTML,CSS,JavaScript都可以写到同一个HTML文件中,也可以分开写,浏览器加载这个HTML的时候,就会运行到这些代码
5.2基于模板进行删减
(1)导航栏实现上传
文件上传和提交按钮:在原来的input标签下载增加一个input标签,将type改为file,增加name=“filename”,新的input标签type=“submit”
修改form标签属性,新增method(请求方式)=“POST” enctype(上传到服务器上数据的组织类型)=“multipart/form-data” action(访问的路径)="/java_image_server/image"
小问题:发现“上传按钮”和前面不一样高,用style="height:41px"修饰它
(2)页面主题展示图片预览
5.3使用Vue.js
前置知识
把网页上显示的预览图片替换成我们服务器上保存的图片:将img标签中src改成服务器存的图片的url就可以了,需要获取服务器上所有的图片的url(ImageServlet),需要通过JS先获取到所有图片的属性,在分别加载每一个图片,使用JS来完成。
此处引入Vue JS的框架来帮助我们更方便的编写代码:JS中变量类型都是在初始化的时候自动推导的。
var声明这是一个“变量”,const声明这是一个“常量”。
Vue所做的最核心的工作,就是把页面显示的内容和JS中的代码相互关联在一起,修改JS的变量就能很方便的影响到页面的显示情况
{{author}}称为“差值表达式”:
如果是在标签内部使用Vue对象中的数据,就需要使用插值表达式; 如果是在标签属性中使用Vue对象中的数据就不需要用插值表达式,但是需要搭配Vue的命令 Vue的命令:v-for:循环访问一个数据 v-bind:把数据绑定到html标签上的 v-on:绑定某种事件的处理函数,比如点击鼠标,双击,右键,按下某个键盘,调整窗口大小...
Vue创建对象
示例图片:
5.4 实现展示图片
修改 html 代码, 和数据关联
使用v-bind:src 把图片的src通过imageShow接口获取到
使用 {{image.imageName}} 表示图片标题
从服务器获取数据
在methods中新增获取所有图片的方法
- ajax: JS中构造HTTP请求发送给服务器的一种实现方式.
为了解决浏览器能自动适配图片位置的问题,我们用 $("#app").resize(); 主动触发浏览器 resize 事件即可.
解决小bug
5.5实现展示图片
当前的上传请求会返回一个 JSON 格式的数据. 而我们更需要的是直接能看到上传的效果,解决如下:
- 修改上传接口的响应, 直接返回一个 302 响应, 重定向回主页.
- 修改 ImageServlet.doPost 在上传成功代码最后, 加上一个重定向resp.sendRedirect(“index.html”);
5.6 完善功能
图片下方新增删除按钮
实现事件处理函数
- 浏览器给服务器发送一个DELETE /image?imageld= xxx这样的请求就可以了. (ajax完成)
解决小bug
- 点击删除按钮之后, 会触发预览图片效果:
这是因为 JavaScript 的事件冒泡机制导致的. 一个标签接受到的事件会依次传给父级标签.
此处需要阻止 click 事件冒泡. Vue 中使用 v-on:click.stop 即可.
1)实现基于白名单方式的防盗链
当我们不想让别人在别的网站直接使用我们的图片链接时,可以采用如下方法:
通过 HTTP 中的 refer 字段判定是否是指定网站请求图片.修改 ImageShowServlet.doGet 方法
- refer记录了它的上一个请求页面是哪
2)基于 MD5 实现相同内容图片只存一份:类似于百度网盘的 “秒传” 功能
(1)前置知识
Md5的特点:
1.不管原串多长,得到的MD5值是固定长度.
2.原串哪怕变动一点点(一个字节), MD5值就会变动很大.
3.计算MD5值的过程很简单,但是通过MD5值无法推测出原字符串的. (应用在密码学)
在我们的项目里Md5的应用:
1.如果两个图片内容完全一样, 就在磁盘上只存-份文件就可以了. 通过MD5就能判定两个图片内容是否是一样的.
2.图片文件虽然是二进制数据,但是本质上也是字符串,针对图片内容计算MD5. 如果两个图片内容相同,得到的MD5 一定是相同的.
3.反之,近似的认为MD5相同,原图片内容一定相同. 理论上是有可能两个图片内容不同,
MD5相同,但是实际,上出现概率极低(MD5自身算法设计.上弓|起的特性)
4.通过Md5计算出的字符串是无法在推算出原文件的,即这是一个不可逆的过程
(2)整体思路
1.修改dao层的代码,在imageDao类里边增加selectByMd5方法,再根据Md5来查找数据库中的图片信息
2.修改上传图片的代码,上传文件时先进行判定,如果这个md5对应的文件存在就不会上传并提示用户该图片已经存在
(3)计算md5
1.在pom.xml中引入依赖
2.修改ImageServlet.doPost方法,计算md5
3.修改ImageDao类,新增selectByMd5方法
public static Image selectByMd5(String md5) {
// 1. 获取数据库连接
Connection connection = DBUtil.getConnection();
// 2. 构造 SQL 语句
String sql = "select * from image_table where md5 = ?";
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
// 3. 执行 SQL 语句
statement = connection.prepareStatement(sql);
statement.setString(1, md5);
resultSet = statement.executeQuery();
// 4. 处理结果集
if (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. 关闭链接
DBUtil.close(connection, statement, resultSet);
}
return null;
}
4.根据md5决定是否写入文件
- 修改ImageServlet.doPost方法,如果该md5值的文件存在,则不会存入数据库和磁盘