图片服务器--->服务器(图床)
比如:编写博客时我们会插入图片,本质上是往文章中放了一个链接(URL),这个URL资源在另外一个服务器上。
(1)简单的Web服务器设计开发能力(Servlet)
Web服务器--->HTTP服务器
Servlet是Tomcat这个HTTP服务器所提供的一组编程接口
(2)使用数据库来进行存储(MySQL)JDBC操作MySql
(3)数据库设计(根据实际场景设计数据库表结构)
(4)前后端交互的API的设计(基于HTTP协议)
(5)认识JSON数据格式,学习使用Java中的Gson这个库操作JSON数据
(6)学习测试一个HTTP服务器(Postman)
(7)使用HTML CSS JavaScript技术构建一个简单的网页
数据库中存储的是图片的属性(元信息);图片正文是以文件的形式直接存在磁盘上的;数据库中就记录一个path,path就对应到磁盘上的一个文件。
(1)JSON
一种数据组织的格式,格式键值对的结构。JSON只是一种数据格式,和编程语言无关。
JSON结构展示:
{
"name":"曹操",
"skill1":"剑气",
"skill2":"三段跳",
"skill3":"加攻击和吸血",
"skill4":"加攻速",
}
使用JSON完成数据的序列化方便完成网络传输。
(2)Gson:google搞得一个开源的JSON解析库
(3)文件上传操作在HTML中是如何完成的(用到form表单)
文件上传在HTTP协议中是如何完成的
1>新增图片
请求:使用POST,POST到/image这个路径上
Content-Type:multipart/from-data;
(正文内容包含图片自身的一些信息)
(图片正文的二进制内容)
响应:(1)上传成功
①HTTP/1.1 200 OK
②{
"ok":true,
}
(2)上传失败
①HTTP/1.1 200 OK
②{
"ok":false,
"reason":"具体的失败原因"
}
2>查看所有图片信息(数据库里面存的属性信息)
请求:GET/image
响应:①成功:HTTP/1.1 200 OK
成功返回的数组:
[
{
imageid:1,
imageName:"1.png",
contentType:"image/png",
size:1000,
uploadTime:"20200222",
path:"./data/1.png",
md5:"11223344",
},
{
.........
}
]
②:失败:HTTP/1.1 200 OK
失败返回的数组:
[](返回一个空数组)
3>查看指定图片属性
请求:GET/image?imageid=[具体的数值]
响应:
①:成功
HTTP/1.1 200 OK
{
imageid:1,
imageName:"1.png",
contentType:"image/png",
size:1000,
uploadTime:"20200222",
path:"./data/1.png",
md5:"11223344",
}
②:失败
HTTP/1.1 200 OK
{
ok:false,
reason:"具体出错的原因"
}
4>删除指定图片
请求:DELETE/image?image=[具体的图片id]
响应:
①:成功
HTTP/1.1 200 OK
{
ok:true,
}
②:失败
HTTP/1.1 200 OK
{
ok:false,
reason:"具体的出错原因",
}
服务器实现代码的时候就可以判断方法,如果是DELETE方法就执行删除操作
删除不一定非要用DELETE方法,也可以用:GET/image?image=xxx&delete=1
5>查看指定图片内容
请求:GET/imageShow?imageid=[具体的图片id]
响应:
①:成功:
HTTP/1.1 200 OK
Content-Tpye:image/png
[图片的二进制内容]
②:失败
HTTP/1.1 200 OK
{
ok:false,
reason:"[具体的出错原因]"
}
1.数据库操作
先创建DBUtil,封装一下获取数据库链接的过程
dao--->数据访问层,这里面的类围绕着数据操作展开
ctrl+alt+t--->选中一些代码,在这些代码外面包上一些其他代码
package dao;
public class Image {
private int imageId;
private String imageName;
private int size;
private String uploadTime;
private String contentType;
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 + '\'' +
'}';
}
}
DBUtil封装获取数据库链接的操作;
Image对应到一个图片对象(包含图片的相关属性);
ImageDao Image对象管理器,借助这个类完成Image对象的增删改查操作;
ImageDao有一个selectAll方法,查找出所有数据库中的数据,但是如果有上亿条数据这样查找很低效,更科学的方法可以指定一些其他筛选条件(分页);
(JAR:类似于zip这样的压缩包,一大堆.class文件放在一起,打包成一个文件)
ImageDao:
package dao;
import common.JavaImageServerException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class ImageDao {
//把image对象插入到数据库中
public void insert(Image image) {
//1.获取数据库连接
Connection connection=DBUtil.getConnection();
//2.创建并拼装SQL语句
String sql="insert into image_table values(null,?,?,?,?,?)";
PreparedStatement statement=null;
try {
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 JavaImageServerException("插入大户巨款出错");
}
} catch (SQLException e) {
e.printStackTrace();
}catch (JavaImageServerException e){
e.printStackTrace();
}finally{
//4.关闭连接和statement对象
DBUtil.close(connection,statement,null);
}
}
//查找数据库中的所有图片的信息
public List selectAll(){
List images=new ArrayList<>();
//1、获取数据库连接
Connection connection=DBUtil.getConnection();
//2.构造SQL语句
String sql="select * from image_table";
PreparedStatement statement=null;
ResultSet resultSet=null;
try {
//3.执行sql语句
statement=connection.prepareStatement(sql);
resultSet=statement.executeQuery();
//4.处理结果集
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{
//关闭连接
DBUtil.close(connection,statement,resultSet);
}
return null;
}
//根据imageId查找指定的图片信息
public Image selectOne(int imageId){
//1.获取数据库连接
Connection connection=DBUtil.getConnection();
//2.构造SQL语句
String sql="select * from image_table where imageId = ?";
PreparedStatement statement=null;
ResultSet resultSet=null;
//3.执行SQL语句
try {
statement=connection.prepareStatement(sql);
statement.setInt(1,imageId);
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;
}
//根据imageId删除指定图片
public void delete(int imageId){
//1、获取数据库连接
Connection connection=DBUtil.getConnection();
//2.拼装SQL语句
String sql="delete * from image_table where imageId = ?";
PreparedStatement statement=null;
// ResultSet resultSet=null;
//3.执行SQL语句
try {
statement=connection.prepareStatement(sql);
statement.setInt(1,imageId);
int ret =statement.executeUpdate();
//4.处理结果集
if(ret!=1){
throw new JavaImageServerException("删除数据库操作失败")
}
} catch (SQLException | JavaImageServerException e) {
e.printStackTrace();
}finally{
//4.关闭连接
DBUtil.close(connection,statement,null);
}
}
public static void main(String[] args) {
//用于进行简单的测试
//1.测试插入数据
// Image image=new Image();
// image.setImageName("1.png");
// image.setSize(100);
// image.setUploadTime("2023");
// image.setContentType("image/png");
// image.setPath("./date/1.png");
// image.setMd5("112233");
// ImageDao imageDao=new ImageDao();
// imageDao.insert(image);
//2.测试查找所有图片信息
// ImageDao imageDao=new ImageDao();
// List images=imageDao.selectAll();
// System.out.println(images);
//3.测试查找指定图片信息
ImageDao imageDao=new ImageDao();
Image image=imageDao.selectOne(1);
System.out.println(image);
//4.测试查找指定图片信息
ImageDao imageDao=new ImageDao();
imageDao.delete(1);
}
}
(1)安装Servlet
(2)创建一个类,继承HttpServlet父类(重写父类中的一些重要方法)
如果服务器收到的是GET方法,就会自动调用HttpServlet的doGet方法;
如果服务器收到的是POST方法,就会自动调用HttpServlet的doPost方法;
如果服务器收到的是DELETE方法,就会自动调用HttpServlet的doDelete方法;
HttpServletRequest req--->请求(方法、url、各种header、body) HttpServletResponse resp--->响应(状态码、各种header、body)
给网页上显示一个hello world,修改请求还是修改响应--->修改响应;当代码写完之后,还需要修改webapp/WEB-INF/web.xml把新创建的Servlet注册进去:
Hello
HelloServlet
servlet >
Hello servlet-name>
/ hello-servlet
servlet-mapping>
部署:
(1)使用maven打包(war) maven package
(2)把war包拷贝到tomcat的webapps目录中
部署完之后,就可以在浏览器(http客户端)中访问服务器了。
http://47.98.116.42:8080/java_image_server/image
java_image_server----->war包的名字;image---->web.xml配置的内容
(1)上传图片
需要用到第三方库
对于JSON格式的响应,需要把content-type设定成指定格式
如果有两个图片,内容不同,但是名字相同,此时会出现上传失败的情况;应该让每次上传图片对应的路径都不相同(在路径上加上时间戳)
(2)查看所有图片信息
(3)查看所有指定图片信息
(4)删除指定图片
代码:
package api;
import dao.Image;
import dao.ImageDao;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
public class ImageServlet extends HttpServlet {
//查看图片属性,既能查看所有,也能查看指定
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws SecurityException, ServletException, IOException {
//考虑到查看所有图片属性和查看指定图片属性
//通过是否URL中带有imageId参数来进行区分
//存在imageId查看指定图片属性,否则就查看所有图片属性
//例如:URL/image?imageId=100
//imageId的值就是"100"
//如果URL中不存在imageId那么返回null
String imageId =req.getParameter("imageId");
if(imageId==null || imageId.equals("")){
//查看所有图片属性
selectAll(req,resp);
}else{
//查看指定图片
selectOne(imageId,resp);
}
// //req对象中包含了请求中的所有信息
// //resp对象要生成的结果就放到里面去
// //当前这个doGet方法就是要根据请求,生成响应
// resp.setStatus(200);
// //这个代码就是把hello这个字符串放到http响应的body中了
// resp.getWriter().write("hello");
}
private void selectAll(HttpServletRequest req,HttpServletResponse resp){
//1.创建一个ImageDao对象,并查找数据库
ImageDao imageDao=new ImageDao();
List images=imageDao.selectAll();
//2.把查找到的结果转成JSON格式的字符串,并且写回到resp对象
Gson gson=new GsonBuilder().create();
//jsonData就是一个json格式的字符串,和之前约定的格式是一样的
String jsonDate=gson.toJson(images);
resp.getWriter().write(jsonDate);
}
private void selectOne(String imageId,HttpServletResponse resp){
resp.setContentType("application/json;charset=utf-8");
//1.创建ImageDao对象
ImageDao imageDao=new ImageDao();
Image image=imageDao.selectOne(Integer.parseInt(imageId));
//2.使用gson把查看到的数据转成json格式,并写回给响应对象
Gson gson=new GsonBuilder().create;
String jsonData=gson.toJson(image);
resp.getWriter().write(jsonData);
}
//上传图片
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取图片的属性信息,并且存入数据库
//1>需要创建一个factory对象,和upload对象,为了获取到图片属性做的准备工作
FileItemFactory factory=new DiskFileItemFactory();
ServletFileUpload upload=new ServletFileUpload(factory);
//2>通过upload对象进一步解析请求
List items=null;
try{
items=upload.paresRequest(req);
}catch(FileUploadException e){
//出现异常说明解析出错
e.printStackTrace();
//告诉客户端出现的具体的错误是什么
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().write("{\"ok\":false,\"reason\",\"请求解析失败\"}");
return;
}
//3>把FileItem中的属性提取出来,转换成Image对象,才能存到数据库中
//当前只考虑一张图片的情况
FileItem fileItem=item.get(0);
Image image=new Image();
image.setImageName(fileItem.getName());
image.setSize((int)fileItem.getSize());
//手动获取一下当前日期,并转换成格式化日期
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyyMMdd");
image.setUploadTime(simpleDateFormat.format(new Date()));
image.setContentType(fileItem.getContentType());
//自己构造一个路径来保存,引入时间戳为了让文件能够唯一
image.setPath("./image/"+System.currentTimeMillis()+"_"+image.getImageName());
//TODO MD5(先不管,写死)
image.setMD5("112233");
//存到数据库中
ImageDao imageDao=new ImageDao();
imageDao.insert(image);
//2.获取图片的内容信息,并且写入磁盘文件
File file=new File(image.getPath());
try{
fileItem.write(file);
}catch(Exception e){
e.printStackTrace();
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().write("{\"ok\":false,\"reason\",\"写磁盘失败\"}");
return;
}
//3.给客户端返回一个结果数据
//resp.setContentType("application/json;charset=utf-8");
// resp.getWriter().write("{\"ok\":true}");
resp.sendRedirect("index.html");
}
//删除指定图片
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json;charset=utf-8");
//1.先获取到请求中的imageId
String imageId=req.getParameter("imageId");
if(imageId==null || imageId.equals("")){
resp.setStatus(200);
resp.getWriter().write("{\"ok\":false,\"reason\",\"解析请求失败\"}");
return;
}
//2.传教imageDao对象,查看到该图片对象对应的相关属性(为了知道这个图片对应的文件路径)
ImageDao imageDao=new ImageDao();
Image image=imageDao.selectOne(Integer.parseInt(ImageId));
if(image==null){
//此时请求中传入的id在数据库中不存在
resp.setStatus(200);
resp.getWriter().write("{\"ok\":false,\"reason\",\"image在数据库中不存在\"}");
return;
}
//3.删除数据库中的记录
imageDao.delete(Integer.parseInt(imageId));
//4.删除本地磁盘文件
File fule=new File(image.getPath());
file.delete();
resp.setStatus(200);
resp.getWriter().write("{\"ok\":true}");
}
}
代码:
package api;
import dao.Image;
import dao.ImageDao;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
public class ImageShowServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.解析出imageId
String imageId=req.getParameter("imageId");
if(imageId==null || imageId.equals("")){
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().write("{\"ok\":false,\"reason\",\"imageId解析失败\"}");
return;
}
//2.根据imageId查找数据库,得到对应的图片属性信息(需要知道图片存储的路径
ImageDao imageDao=new ImageDao();
Image image=imageDao.selectOne(Integer.parseInt(ImageId));
//3.根据路径打开文件,读取其中的内容,写入到响应对象中
resp.setContentType(image.getContentType());
File file=new File(image.getPath());
//由于图片是二进制文件,应该使用字节流的方式读取文件
OutputStream outputStream=resp.getOutputStream();
FileInputStream fileInputStream=new FileInputStream(file);
byte[] buffer=new byte[1024];
while(true){
int len=fileInputStream.read(buffer);
if(len==-1){
//文件读取结束
break;
}
//此时语句读到一部分数据,放到buffer里面,把buffer中的内容写到响应对象中
outputStream.write(buffer);
}
fileInputStream.close();
outputStream.close();
}
}
(1)HTML-->网页的骨架;
(2)CSS--->描述网页上组件的样式(位置、大小、颜色等);
(3)JavaScript:描述前端页面上的一些动作(和用户具体交互的行为)
(HTML、CSS、JavaScript可以写到一个HTML文件中,也可以分开写)
把网页上显示的这些预览图片替换成服务器上保存的图片,img标签中scr改成服务器上存在的图片的url就可以了;
需要获取到服务器上所有图片的url(ImageServlet),需要通过JS先获取到所有图片的属性,再分别加载每一个图片;使用JS来完成。
(1)Vue
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
(2)React(facebook)
(3)Auglar(google)
JS中变量类型都是在初始化的时候自动推导的
var声明这是一个变量;
Vue所做的最核心的工作,把页面显示的内容和JS中的代码相互关联到一起;
修改js中的变量就能很方便的影响的页面的显示情况。
var app = new Vue({
el:'#app',
data: {
images: [
{
imageId: 1,
imageName: "1.png",
contentType: "image/png",
md5: "aabbccdd",
},
{
imageId: 2,
imageName: "2.png",
contentType: "image/png",
md5: "aabbccdd",
}
}
},
methods: {
},
});
Vue对象中构造一组写死的数据,先去借助这组数据来渲染页面
Vue中的命令:
1>v-for--->>循环访问一个数据
2>v-bind-->>把数据绑定到html标签上的某个属性
3>v-on-->>绑定某种事件的处理函数(点击鼠标、双击、右键等)
remove(image_id) {
$.ajax({
url:"image?image_id=" + image_id,
type:"delete",
context: this,
success: function(data, status) {
this.getImages();
alert("删除成功");
}
})
}
如果是在标签内部使用Vue对象中的数据,就需要使用插值表达式;
如果是在标签属性中使用Vue对象中的数据,步需要使用插值表达式,但要搭配Vue的命令;
images这个数组里面的内容是写死的,接下里通过浏览器中JS代码请求服务器,获取到服务器上都有那些图片,把这个数据作为Vue渲染的依据(Vue对象中的images数组)
getImages() {
$.ajax({
url: "image",
type: "get",
context: this,
success: function(data, status) {
this.images = data;
// 这个代码用来触发 resize 事件.
$("#app").resize();
}
})
},
ajax:JSz中构造HTTP请求发送给服务器的一种实现方式
原来页面的渲染过程:
1>先加载图片
2>再根据图片大小设定图片的位置,设定显示图片的空间大小
使用ajax来渲染:
1>页面先尝试获取页面的大小,并设定显示图片的空间(当前图片还没有获取到,不知道图片的大小)
2>通过ajax获取图片内容
改进上传操作,上传成功之后,自动跳转到主页index.html HTTP重定向
实现删除图片,浏览器给服务器发送一个DELETE/image?imageId=xxx这样的请求就可以了(ajax完成)
拓展:
(1)简单的防盗链机制(通过一定的机制来限制其他人来使用图片)
可以判定当前请求的referer爱段(HTTP请求协议中的header部分),是不是在我们代码指定的白名单中,如果是,就可以访问。
referer-->表示当前请求的上一个页面的地址
再代码中用一个hashSet存一下允许访问的Referer就可以了,展示图片的时候判断一下是否再hashSet中存在即可。
(2)优化磁盘空间
应用到MD5,如果两个图片内容完全一样,就在磁盘上存一份就可以了。通过MD5可以判断两个图片内容是否一样。
图片文件虽然是二进制数据,但是本质上也是字符串,针对图片内容计算MD5。
如果两个图片内容相同,得到的MD5一定是相同的,反之,近似认为MD5相同,原图内容一定相同。
理论上使是有可能两个图片的内容不同,MD5相同,但实际上出现概率极低(MD5自身算法设计上引起的特性)
实现思路:
上传图片的时候,先判定新图片的MD5在数据库中是否存在,如果已经存在了,就不用把图片内容写到磁盘上,如果不存在就写到磁盘上。
1>该上传代码的逻辑,磁盘文件名用md5值来表示
2>修改ImageDao新增一个接口,能按照MD5查找数据库内容
3>修改上传图片的逻辑,根据MD5判定当前图片是是否要写磁盘