图片服务器
图片服务器主要的功能是:上传图片,显示图片的功能
写博客的时候,插入的图片,本质上是往文章内插入一个url,图片其实是保存在在另一个服务器上,而我这个项目就是制作一个类似这样的服务器。
核心知识点
1.简单的web服务器设计开发能力(Servlet)
2.jdbc操作数据库
3.数据库设计
4.前后端交互的api的设计
5.json数据格式,使用java中的gson这个库操作json数据
6.使用html,css,javaScript构造一个简单网页。
1.数据库设计及相关操作
1.1数据库的设计
设计一个数据库用来保存图片的相关信息,这样就方便我们后续对图片的调用和存储。数据库中存储的图片的属性(元信息)图片正文,以文件的形式直接存在磁盘上的。数据库的记录一个path就对应到磁盘上的文件
元素 | 类型 | 说明 | 备注 |
---|---|---|---|
id | int | 一张图片的id | 主键 |
imageName | varchar(50) | 文件名 | |
uploadtimel | varchar(50) | 下载时间 | |
contentType | varchar(4096) | 下载格式 | |
path | varchar(4096) | 文件路径 | |
md5 | varchar(4096) | 校验和 |
校验和:通过一个更短的字符串,来验证整体数据的正确,短的字符串时根据原串内容根据一定的柜子来计算出来的。
1.2数据库的相关操作
我们实现对数据库的操作主要依靠JDBC编程。而其中操作的改变就是sql语句的不同,所以我们可以把其他步骤总结成方法。之后根据不同的操作进行调用就好了。
1.引入对应的依赖
2.创建数据源DataSource(指定服务器的地址,用户名,密码等)
3.和数据库建立连接DataSource.getConnection
4.拼装SQL语句
5.遍历结果集(如果是select需要,insert,update,delete不需要)
6.断开连接并回收资源。
//1.引入依赖<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.45</version></dependency>//2.创建数据源public class DBUtil {private static final String URL = "jdbc:mysql://127.0.0.1:3306/java_image_server?characterEncoding=utf8&useSSL=true";private static final String USERNAME = "root";private static final String PASSWORD = "";private static volatile DataSource dataSource = null;public static DataSource getDataSource() {// 通过这个方法来创建 DataSource 的实例if (dataSource == null) {synchronized (DBUtil.class) {if (dataSource == null) {dataSource = new MysqlDataSource();MysqlDataSource tmpDataSource = (MysqlDataSource) dataSource;tmpDataSource.setURL(URL);tmpDataSource.setUser(USERNAME);tmpDataSource.setPassword(PASSWORD);}}}return dataSource;}//3.获取连接public static Connection getConnection() {try {return getDataSource().getConnection();} catch (SQLException e) {e.printStackTrace();}return null;}
//6.关闭资源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();}}
}
为实现当操作出现问题时可以及时反馈,这里我自定义了一个异常方便预警。同时建立了一个实体类,实体类的内容与数据库的字段保持一致。
// 自定义异常:JavaImageServerException
public class JavaImageServerException extends Exception {public JavaImageServerException(String message) {super(message);}
}
// 实体类:image
import lombok.Getter;
import lombok.Setter;@Getter
@Setter
public class Image {private int imageId;private String imageName;private int size;private String uploadTime;private String contentType;private String path;private String md5;@Overridepublic String toString() {return "Image{" +"imageId=" + imageId +", imageName='" + imageName + '\'' +", size=" + size +", uploadTime='" + uploadTime + '\'' +", contentType='" + contentType + '\'' +", path='" + path + '\'' +", md5='" + md5 + '\'' +'}';}
}
1.2.1插入数据
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|JavaImageServerException e) {e.printStackTrace();} finally {// 4. 关闭连接和statement对象DBUtil.close(connection, statement, null);}}
1.2.2删除数据
/*** 根据 imageId 删除指定的图片* @param imageId*/public void delete(int imageId) {// 1. 获取数据库连接Connection connection = DBUtil.getConnection();// 2. 拼装 SQL 语句String sql = "delete from image_table where imageId = ?";PreparedStatement statement = null;// 3. 执行 SQL 语句try {statement = connection.prepareStatement(sql);statement.setInt(1, imageId);int ret = statement.executeUpdate();if (ret != 1) {throw new JavaImageServerException("删除数据库操作失败");}} catch (SQLException | JavaImageServerException e) {e.printStackTrace();} finally {// 4. 关闭连接DBUtil.close(connection, statement, null);}}
1.2.3查找单张图片
/*** 根据 imageId 查找指定的图片信息* @param imageId* @return*/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;try {// 3. 执行 SQL 语句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;}
1.2.4查找全部图片
/*** 查找数据库中的所有图片的信息* @return*/public List<Image> selectAll() {List<Image> 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 {// 5. 关闭连接DBUtil.close(connection, statement, resultSet);}return null;}
2.基于Servlet来搭建服务器
a).Servlet运行方法。
从浏览器发送请求到tomcat上,之后根据url查找映射关系表找到对用的api类
根据对应的方法,决定给api类创建一个对象,并且调用其中的doxxx方法
执行doxxx方法tomcat构造resp对象,根据这个对象生成http响应报文,再通过socket写回给浏览器
2.服务器api设计(前后端接口设计)
a)json一种数据格式,键值对的结构,这里主要采用json格式进行信息交互
b)文件上传操作在前端如何实现(from表单)
1.新增图片
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 1. 获取图片的属性信息, 并且存入数据库// a) 需要创建一个 factory 对象 和 upload 对象, 这是为了获取到图片属性做的准备工作// 固定的逻辑FileItemFactory factory = new DiskFileItemFactory();ServletFileUpload upload = new ServletFileUpload(factory);// b) 通过 upload 对象进一步解析请求(解析HTTP请求中奇怪的 body 中的内容)// FileItem 就代表一个上传的文件对象.// 理论上来说, HTTP 支持一个请求中同时上传多个文件List<FileItem> items = null;try {items = upload.parseRequest(req);} catch (FileUploadException e) {// 出现异常说明解析出错!e.printStackTrace();// 告诉客户端出现的具体的错误是啥resp.setContentType("application/json; charset=utf-8");resp.getWriter().write("{ \"ok\": false, \"reason\": \"请求解析失败\" }");return;}// c) 把 FileItem 中的属性提取出来, 转换成 Image 对象, 才能存到数据库中// 当前只考虑一张图片的情况FileItem fileItem = items.get(0);Image image = new Image();image.setImageName(fileItem.getName());image.setSize((int)fileItem.getSize());// 手动获取一下当前日期, 并转成格式化日期, yyMMdd => 20200218SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");image.setUploadTime(simpleDateFormat.format(new Date()));image.setContentType(fileItem.getContentType());// MD5 image.setMd5(DigestUtils.md5Hex(fileItem.get()));// 利用md5是为了让文件路径能够唯一image.setPath("./image/" + image.getMd5());// 存到数据库中ImageDao imageDao = new ImageDao();// 看看数据库中是否存在相同的 MD5 值的图片, 不存在, 返回 nullImage 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();resp.setContentType("application/json; charset=utf-8");resp.getWriter().write("{ \"ok\": false, \"reason\": \"写磁盘失败\" }");return;}}// 3. 给客户端返回一个结果数据//重定向,上传成功后回到主页resp.sendRedirect("index.html");}
2.查看图片信息(数据库属性)
1.查看所有图片信息
2.查看指定图片信息
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String imageId = req.getParameter("imageId");if (imageId == null || imageId.equals("")) {// 查看所有图片属性selectAll(req, resp);} else {// 查看指定图片selectOne(imageId, resp);}}private void selectAll(HttpServletRequest req, HttpServletResponse resp) throws IOException {resp.setContentType("application/json; charset=utf-8");// 1. 创建一个 ImageDao 对象, 并查找数据库ImageDao imageDao = new ImageDao();List<Image> images = imageDao.selectAll();// 2. 把查找到的结果转成 JSON 格式的字符串, 并且写回给 resp 对象Gson gson = new GsonBuilder().create();// jsonData 就是一个 json 格式的字符串了, 就和之前约定的格式是一样的了.// 只要把之前的相关的字段都约定成统一的命名, 下面的操作就可以一步到位的完成整个转换String jsonData = gson.toJson(images);resp.getWriter().write(jsonData);}private void selectOne(String imageId, HttpServletResponse resp) throws IOException {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);}
4.删除图片
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json; charset=utf-8");// 1. 先获取到请求中的 imageIdString 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\": \"imageId 在数据库中不存在\" }");return;}// 3. 删除数据库中的记录imageDao.delete(Integer.parseInt(imageId));// 4. 删除本地磁盘文件File file = new File(image.getPath());file.delete();resp.setStatus(200);resp.getWriter().write("{ \"ok\": true }");}
5.查看图片具体信息
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 1. 解析出 imageIdString 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();}
3.前端设计
前端主要依靠form表单进行文件的上传
我们这里采用简单的vue框架以及ajax异步请求实现对应的响应。
var app = new Vue({el: '#app',data: {author: "whggg",images: []},methods: {// GET /image,获取照片操作getImages() {$.ajax({url: "image",type: "get",context: this,success: function(data, status) {// 此处的代码在浏览器收到响应之后, 才会执行到// 参数中的 data 这就相当于收到的 HTTP 响应中的 body 部分this.images = data;$('#app').resize();}})},//删除照片操作remove(imageId) {$.ajax({url: "image?imageId=" + imageId,type: "delete",context: this,success: function(data, status) {this.getImages();// 弹出对话框alert("删除成功!");}})}}})app.getImages();