图片服务器

news2025/1/6 19:23:49

文章目录

  • 一、项目简介
  • 二、功能及场景
  • 三、业务设计
  • 四、数据库设计
    • 准备图片表
    • 准备实体类
  • 五、API设计
    • 常用功能封装
    • 文件上传
      • 文件上传
    • 获取图片列表接口
    • 获取图片内容
    • 删除图片接口
  • 六、项目优化
  • 七、测试
    • 自动化测试
    • 测试用例

一、项目简介

图片服务器:解决项目中插入图片的问题

在这里插入图片描述

二、功能及场景

1.功能/接口

  • 显示图片列表
  • 显示图片内容
  • 上传图片
  • 删除图片

模拟的html要展示的图片列表:

因为存在上传和删除操作,所以列表是动态变化=>需要是动态网页

(1) servlet返回Java字符串拼接的html内容
(2) 模板技术
(3) ajax 根据响应来动态生成html内容(本项目以此方式实现)

在这里插入图片描述

2.图片服务器的应用场景:

常见的:专门提供图片下载浏览的网站,图床
写博客文章,可以上传图片

三、业务设计

系统设计
数据库设计
接口设计:考虑请求方法,请求路径,请求数据(格式)﹔响应数据(json,图片格式)
基础设施搭建:
maven项目

前端技术: ajax,vue(前端js框架),jquery (只用了这个框架提供的ajax函数来发请求)
后端技术: Servlet, jdbc,commons-fileupload, commons–codec(唯一性验证的框架,通过上传的图片生成md5来验证), jackson,lombok

四、数据库设计

准备图片表

-- 准备表:
-- 注意:图片表的字段,转为实体类的成员变量(名称会关联前后端接口)
create table image_info (
    image_id int primary key auto_increment comment '主键id',
    image_name varchar(50) comment '图片名称',
    size bigint comment '图片大小',
    upload_time datetime comment '图片上传日期',
    md5 varchar(128) comment 'md5值,用于校验图片唯一',
    content_type varchar(50) comment '数据类型,上传图片时,请求数据就包含Content-Type',
    path varchar(1024) comment '图片的路径: 相对路径'
);

准备实体类

数据库jdbc操作(插入,修改,查询),返回http响应数据,都需要使用实体类。

把数据库的表转为类,字段转为成员变量。

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class ImageInfo {
    //主键id
    private Integer imageId;
    //图片名称
    private String imageName;
    //图片大小
    private Long size;
    //图片上传日期
    private java.util.Date uploadTime;
    //md5校验码:通过一段数据(字符串,数值,二进制)生成
    private String md5;
    //数据格式:http请求上传form-data数据格式时,图片字段还可以包含Content-Type
    private String contentType;
    //图片路径:相对路径
    private String path;
}

五、API设计

常用功能封装

  • 封装数据库连接池
public class DBUtil {
    //封装数据库连接池(双重校验锁的线程安全的单例模式)
    private static volatile DataSource DS;

    private static DataSource getDataSource(){
        if(DS == null){
            synchronized (DBUtil.class){
                if(DS == null){
                    MysqlDataSource dataSource = new MysqlDataSource();
                    dataSource.setURL("jdbc:mysql://localhost:3306/image_system");
                    dataSource.setUser("root");
                    dataSource.setPassword("123456");
                    dataSource.setUseSSL(false);
                    dataSource.setCharacterEncoding("utf8");
                    DS = dataSource;
                }
            }
        }
        return DS;
    }

    public static Connection getConnection(){
        try {
            return getDataSource().getConnection();
        } catch (SQLException e) {
            throw new RuntimeException("获取数据库连接失败", e);
        }
    }

    @Test
    public void testGetConnection(){
        System.out.println(getConnection());
    }

    public static void close(Connection c, Statement s, ResultSet rs){
        try {
            if(rs != null) rs.close();
            if(s != null) s.close();
            if(c != null) c.close();
        } catch (SQLException e) {
            throw new RuntimeException("释放数据库资源出错", e);
        }
    }
}
  • 序列化与反序列化
public class WebUtil {

    private static final ObjectMapper M = new ObjectMapper();

    static {
        //设置序列化的日期格式
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        M.setDateFormat(df);
    }

    //json序列化
    public static void serialize(HttpServletResponse resp, Object o){
        resp.setContentType("application/json");
        resp.setCharacterEncoding("UTF-8");
        try {
            String json = M.writeValueAsString(o);
            resp.getWriter().write(json);
        } catch (IOException e) {
            //这里是序列化就返回响应了,捕获到异常,就自行处理
            e.printStackTrace();
            resp.setStatus(500);
        }
    }

    //反序列化: 请求的json格式数据,转换为Java对象
    public static <T> T deserialize(HttpServletRequest req, Class<T> clazz){
        try {
            return M.readValue(req.getInputStream(), clazz);
        } catch (IOException e) {
            throw new RuntimeException("反序列化失败", e);
        }
    }
}

文件上传

上传需要考虑到的图片路径的问题:上传到数据库时,需要保存Path;图片本身是要保存在本地硬盘上,也涉及到路径;显示文件内容时,前端需要路径为< img src=“xxxx”>;因此需要分析这些路径该如何设置。

  • 对于上传到数据库中保存的路径信息字段名为path(是服务端自定义的,这里是一个md5值),但是数据库中并不保存完整路径——为自定义后缀,完整路径=本地路径前缀(服务端本地路径前缀)+自定义后缀
  • 前端显示的路径由 <img v-bind:src="'imageShow?imageId=' + image.imageId"> 决定
  • 后端servlet提供/imageShow的接口:通过解析imageId, 找到文件在服务端本地的路径(完整路径),然后把二进制数据写入响应体。其中解析方法是:1,通过Id在数据库中找到对应的数据(包含path字段)2.拼接上前缀就可以找到图片在本地的真实路径;

文件上传

文件上传接口请求如下:

在这里插入图片描述

针对接口的请求,后端servlet进行处理返回响应,整体步骤分为以下操作:

  • 获取请求数据:uploadImage=图片数据 。获取图片Part对象
  • 保存图片完整路径在服务端本地硬盘: 完整路径为路径前缀+后缀(/MD5值)
  • 保存图片信息在数据库
  • 返回响应: {ok: boolean, msg: String} ,是根据接口所需响应设置此两个字段
//图片上传接口
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //请求数据:uploadImage=图片数据
        Part p = req.getPart("uploadImage");

        //1.保存在服务端本地硬盘: 完整路径为路径前缀+后缀(自己约定规则)
        //我们这里简单的,用md5值作为文件名(/md5值作为后缀)
        //md5操作,可以基于byte[],String,InputStream
        //先根据上传的图片,生成md5值
        String md5 = DigestUtils.md5Hex(p.getInputStream());
        

        //保存在服务端本地硬盘:路径前缀(常量)+后缀(/md5值)
        p.write(LOCAL_PATH_PREFIX+"/"+md5);
        //2.保存在数据库
        //先构造一个ImageInfo对象,来保存要插入数据库的数据
        ImageInfo image = new ImageInfo();
        //设置图片名称:上传的文件名
        image.setImageName(p.getSubmittedFileName());
        //设置图片大小:上传的文件大小
        image.setSize(p.getSize());
        //设置上传日期:当前日期
        image.setUploadTime(new java.util.Date());
        //设置md5
        image.setMd5(md5);
        //设置数据格式/类型: 上传的文件格式(注意,不是请求的数据格式,是form-data上传的图片字段的格式)
        image.setContentType(p.getContentType());
        //设置路径:设置为路径后缀(/md5值)
        image.setPath("/"+md5);

        //插入数据库图片数据 jdbc操作
        int n = ImageDao.insert(image);

        //返回响应: {ok: boolean, msg: String}
        Map<String, Object> data = new HashMap<>();
        data.put("ok", true);//我们不返回错误,出错就让tomcat返回500状态码
        WebUtil.serialize(resp, data);
    }

获取图片列表接口

返回:[{imageId: 1, imageName: “”}]
查询数据库所有图片,并返回(List),设置到响应体中

//获取图片列表接口:返回[{imageId: 1, imageName: ""}]
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //查询数据库所有图片,并返回(List<ImageInfo>)
        List<ImageInfo> images = ImageDao.selectAll();
        //返回响应
        WebUtil.serialize(resp, images);
    }

此时运行后,图片列表展示效果如下:

在这里插入图片描述

获取图片内容

在这里插入图片描述

  • 请求:GET /imageShow?imageld=1
    其中, imageld是获取图片列表接口响应的数据,来填充进去的
  • 响应体为图片的二进制数据(响应的数据格式,可以设置,也可以不设置)

后端servlet处理get请求步骤如下

  • 获取请求数据:图片id
  • 根据图片id,在数据库查询图片数据(path字段)
  • 根据图片完整路径,读取本地图片,把二进制数据设置到响应体
//获取图片内容接口:GET /imageShow?imageId=1
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //1.获取请求数据:获取图片id,获取queryString,是getParameter获取
        String imageId = req.getParameter("imageId");
        //2.根据图片id,在数据库查询图片数据(包含path字段:路径后缀)
        ImageInfo imageInfo = ImageDao.selectOne(Integer.parseInt(imageId));
        //3.返回响应:读取本地图片文件,把二进制数据设置到响应体
        //读取本地图片:完整路径=路径前缀+路径后缀(path字段)
        String path = ImageServlet.LOCAL_PATH_PREFIX+imageInfo.getPath();
        //读取这个路径的文件
        //读取方式一:通过FileInputStream文件输入字节流来读取(参考io操作的代码)
        //读取方式二:byte[] b = Files.readAllBytes(path) 是工具方法,读取一个路径的所有数据,
        //Path对象,可以通过File对象转换
        File pic = new File(path);
        byte[] data = Files.readAllBytes(pic.toPath());

        //把图片二进制数据,写入到响应正文
        //严格来说要设置响应数据格式Content-Type,但前端是<img>使用,所以没有也可以
        resp.getOutputStream().write(data);
    }

删除图片接口

在这里插入图片描述

后端处理delete请求步骤如下

  • 获取请求数据: 获取图片id
  • 根据图片id查询到数据(包含path),拼接完整路径在本地删除
  • 删除数据库图片数据
  • 返回响应数据

六、项目优化

目前存在的问题:
1.重复图片上传:目前还是会接收并保存(降低效率)
解决方法:使用md5值,在数据库查询是否存在,如果存在就返回报错信息:上传重复图片

//md5操作,可以基于byte[],String,InputStream
        //先根据上传的图片,生成md5值
        String md5 = DigestUtils.md5Hex(p.getInputStream());
        // 可以先验证md5值,如果存在,就说明是已经存在(重复),不保存
        //先根据md5值,在数据库查询是否存在,如果存在就返回报错信息:上传重复图片
        ImageInfo imageInfo = ImageDao.selectByMd5(md5);
        if(imageInfo != null){//已经存在这个图片,说明重复
            Map<String, Object> data = new HashMap<>();
            data.put("ok", false);
            data.put("msg", "上传图片重复");
            WebUtil.serialize(resp, data);
            return;
        }

2.图片防盗链
只提供给授权(允许)的网站使用=>防盗链
解决方法:通过http请求报文中,Referer这个请求头,可以知道,当前这个http请求,是从哪个页面发起的(上一个页面是哪个)。我们就可以根据Referer的值,来判断是否允许访问。

白名单:提供一个数组/列表,在范围内的,才允许访问

/白名单列表:本机访问本机时,是以下路径,如果放在云服务器,需要改
    private static final List<String> WHITE_LIST = Arrays.asList(
            "http://localhost:8080/java_image_server/"
            ,"http://localhost:8080/java_image_server/index.html"
            ,"http://localhost:8080/java_image_server/index2.html"
    );

获取图片内容接口,先校验Referer请求头,在白名单列表中,才允许访问:

 //获取图片内容接口:GET /imageShow?imageId=1
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //防盗链:先获取Referer请求头,在白名单列表中,才允许访问,否则返回403
        String referer = req.getHeader("Referer");
        //不在白名单中
        if(!WHITE_LIST.contains(referer)){
            //返回403
            resp.setStatus(403);
            return;
        }

七、测试

自动化测试

采用selenium和unittest框架完成了自动化测试,对项目中图片上传功能和删除图片部分编写简单的脚本,并生成测试报告。

from HTMLTestRunner import HTMLTestRunner

from selenium import webdriver
from selenium.webdriver.common.by import By
import time
import unittest
import testImageServer1
from ddt import ddt


@ddt
class testImageServer(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Chrome()
        self.url = "http://localhost:8080/java_image_server"
        self.driver.get(self.url)
        self.driver.maximize_window()
        time.sleep(3)

    def tearDown(self):
        self.driver.quit()

    # 上传图片
    def test_upload(self):
        driver = self.driver
        driver.get(self.url)
        time.sleep(3)
        driver.find_element(By.XPATH, "//*[@id='blog-collapse']/form/div[1]/input").send_keys(
            r"C:\Users\xyy\Pictures\zhinengxinxi\Denoised.jpg")
        time.sleep(3)
        driver.find_element(By.XPATH, "//*[@id='blog-collapse']/form/div[2]/input").click()
        time.sleep(20)

    # 删除图片
    def test_delete(self):
        driver = self.driver
        driver.get(self.url)
        time.sleep(3)
        driver.find_element(By.XPATH, "//*[@id='container']/div[1]/button").click()
        time.sleep(3)
        driver.switch_to.alert.accept()
        time.sleep(3)


if __name__ == '__main__':
    unittest.main(verbosity=0)
import os
import sys
import time
import unittest
from HTMLTestRunner import HTMLTestRunner

import testImageServer1
def createsuit():
    testima = unittest.TestSuite()
    # 向测试套件添加测试用例
    # testima.addTest(TestBaiDu("test_search_set"))
    # testima.addTest(TestBaiDu("test_baidu_search"))
    # 把一个类里面所有测试用例添加进去unittest.makeSuite(testbaidu1.Baidu1)
    testima.addTest(unittest.makeSuite(testImageServer1.testImageServer))
    return testima


if __name__ == '__main__':
    # 1.创建一个文件夹
    # 当前路径
    curpath = sys.path[0]
    # 当前路径下resultreport 文件夹不存在就创建一个
    if not os.path.exists(curpath+'/resultreport'):
            os.mkdir(curpath+'/resultreport')
    # 2.解决重复命名的问题 当前时间命名
    now = time.strftime("%Y-%m-%d-%H %M %S", time.localtime(time.time()))
    print(time.time())
    print(time.localtime(time.time()))
    # 准备HTML报告输出的文件
    filename = curpath+'/resultreport/'+now+'resultreport.html'

    # 创建测试报告 HTML格式的测试执行报告
    fp = open(filename, "wb")
    # 创建执行对象
    runner = HTMLTestRunner(stream=fp, title="图片服务器测试上传删除", description="用例执行情况:", verbosity=2)

    suit = createsuit()
    runner.run(suit)
    fp.close()

测试报告

测试用例

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/386100.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java 运算符与类型转化

Java 运算符与类型转化 1 算术运算符 Java中的算术运算符主要有&#xff08;加&#xff09;、-&#xff08;减&#xff09;、*&#xff08;乘&#xff09;、/&#xff08;除&#xff09;、%&#xff08;求余&#xff09;&#xff0c;它们都是二元运算符。 2 自增和自减运算…

Day906.grant语句 -MySQL实战

grant语句 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于grant语句的内容。 在 MySQL 里面&#xff0c;grant 语句是用来给用户赋权的。 不知道有没有见过一些操作文档里面提到&#xff0c;grant 之后要马上跟着执行一个 flush privileges 命令&#xff0c;才能使…

搞懂它,带你学会高效配置交换机!

想了解更多IT学习资料&#xff0c;可关注公众号“IT运维大本营” 不同网段的用户想要进行三层通信&#xff0c;必须借助于路由表项&#xff0c;而VLANIF接口只能生成直连路由&#xff0c;实现不同网段间通过同一台设备互通&#xff0c;对于不同网段间跨设备…

【Linux】虚拟机设置ISO镜像、配置CentOS 7、设置快照

目录 一、设置ISO镜像 1.设置或编辑 2.配置光驱&#xff08;DVD&#xff09; 3.虚拟机快捷键设置 4.启动虚拟机 二、配置CentOS 7 三、设置快照 四、​​​​​​​虚拟网卡不显示怎么办&#xff1f; &#x1f49f; 创作不易&#xff0c;不妨点赞&#x1f49a;评论❤️收…

安装打印机驱动程序的操作步骤,详细方法介绍

安装打印机驱动程序是使用打印机的前提条件&#xff0c;因此学会正确的安装方法是非常重要的。下面是安装打印机驱动程序的详细步骤分析&#xff0c;为你全面的讲解安装和出现故障如何处理等问题。 一.安装打印机的准备工作 在安装打印机驱动程序之前&#xff0c;需要先准备好…

【Java集合框架】篇六:Collections工具类

Collections 是一个操作 Set、List 和 Map 等集合的工具类。 1。 常用方法 Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作&#xff0c;还提供了对集合对象设置不可变、对集合对象实现同步控制等方法&#xff08;均为static方法&#xff09;&…

软件测试6

一 css书写位置&#xff0c;引入方式 1.内嵌式&#xff1a;css写在style标签中&#xff0c;放在title标签的后面&#xff0c;因为html代码和css代码混在同一个文件中&#xff0c;所以叫内嵌式。 2.外链式&#xff1a;css代码和html代码分离&#xff0c;使用link标签设置href属…

UE 蓝图上帝视角

UE 蓝图上帝视角 UE5蓝图实现相机上帝视角控制&#xff0c;包括绕点旋转&#xff0c;水平平移&#xff0c;远近缩放 实现效果 绕点旋转 水平平移 实现功能 左键鼠标平移&#xff08;相对于场景水平面平移&#xff09;右键绕点旋转中间键视角前后移动&#xff08;可以理解成…

linux网络管理、测试网络连通性

一、网络管理 在rhel7上&#xff0c;同时支持network.service和NetworkMananger.service&#xff08;简称NM&#xff09;。在rhel8上&#xff0c;已经废弃network.service&#xff0c;因此只能通过NM进行网络配置&#xff0c;包括静态ip和动态ip。在rhel8上&#xff0c;必须开启…

安卓短信自动填充踩坑

安卓短信自动填充踩坑 前言 最近弄了个短信自动填充功能&#xff0c;一开始觉得很简单&#xff0c;不就是动态注册个广播接收器去监听短信消息不就可以了吗&#xff1f;结果没这么简单&#xff0c;问题就出在机型的适配上。小米的短信权限、荣耀的短信监听都是坑&#xff0c;…

LabVIEW绘制带有两个不同标尺的波形图/图表

LabVIEW绘制带有两个不同标尺的波形图/图表拥有多组不同标尺的数据&#xff0c;想要在LabVIEW中显示这些数据。能否在同一张波形图/图表中使用多个不同的标尺绘制这些数据&#xff1f;通过在同一波形图上使用多个轴&#xff0c;可以使用不同的标尺绘制数据。请按照以下步骤操作…

【网络】-- 网络基础

&#xff08;本文是网络的宏观的概念铺垫&#xff09; 目录 计算机网络背景 网络发展 认识 "协议" 网络协议初识 协议分层 OSI七层模型 TCP/IP 五层(或四层)模型 报头 以太网 碰撞 路由器 IP地址和MAC地址 IP地址与MAC地址总结 IP地址 MAC地址 计算机…

混合图像python旗舰版

仔细看这个图像。然后后退几米再看。你看到了什么&#xff1f;混合图像是指将一张图片的低频与另一张图片的高频相结合的图片。根据观看距离的不同&#xff0c;所得到的图像有两种解释。在上面的图片中&#xff0c;你可以看到阿尔伯特爱因斯坦&#xff0c;一旦你离开屏幕或缩小…

UG曲面造型-搭建轮廓和曲面创建(通过网格曲面,填充曲面,割面补面,曲面合格性分析)

先在xy平面创建一段圆弧&#xff0c;圆弧两端固定在坐标轴上在yz平面创建一段圆弧&#xff0c;圆弧两端固定在坐标轴上最后在xz平面创建圆弧&#xff0c;圆弧两端与之前的两个圆弧端点重合完成之后的草图如下&#xff1a;接下来构造曲面&#xff1a;方法1&#xff1a;采用通过曲…

HTML、CSS学习笔记5(移动端基础知识、Flex布局)

一、移动端基础知识 1.PC端和移动端区别 移动端&#xff1a;手机版网页&#xff0c;手机屏幕小&#xff0c;网页宽度多数为100%&#xff0c;没有版心 PC端&#xff1a;电脑版网页&#xff0c;屏幕大&#xff0c;网页固定版心 PC端和移动端不是同一个网页 2.如何在电脑里面…

pytorch-模型训练常用的torchvision包。关于数据、模型、数据增强、优化器、损失函数。用官方的实现,自定义模型训练

pytoch关于图像数据的部分 一般情况下处理图像、文本、音频和视频数据时&#xff0c;可以使用标准的Python包来加载数据到一个numpy数组中。 然后把这个数组转换成 torch.*Tensor。 图像可以使用 Pillow, OpenCV 音频可以使用 scipy, librosa 文本可以使用原始Python和Cython…

3D立体视觉成像原理介绍【一 】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言什么是基线&#xff1f;基线是如何影响3D图像质量激光三角测量飞行时间结构光相机时间编码结构光前言 本文将介绍3D立体视觉的成像原理&#xff0c;包括【激光三…

【JUC2022】第五章 ThreadLocal

【JUC2022】第五章 ThreadLocal 文章目录【JUC2022】第五章 ThreadLocal一、是什么二、案例三、使用规范四、源码分析五、内存泄漏问题六、实际应用 Demo一、是什么 ThreadLocal 提供线程局部变量&#xff0c;这些变量与正常的变量不同&#xff0c;因为每一个线程在访问 Threa…

Qt Widget之QMainWindow

目录 一 QMainWindow整体概况 二 菜单栏 三 工具栏 四 状态栏 五 铆接部件 六 核心部件 七 总体效果 一 QMainWindow整体概况 QMainWindow是主窗口程序&#xff0c;由以下几个部分构成&#xff1a;菜单栏&#xff0c;状态栏&#xff0c;工具栏&#xff0c;铆接部件和核…

Nginx优化服务和防盗链

Nginx优化服务和防盗链一、长连接1、修改主配置文件2、测试3、在主配置文件添加4、验证二、Nginx第三方模块1、开源的echo模块2、查看是否成功3、加echo模块步骤4、网页测试验证三、搭建虚拟主机1、编译安装好nginx后&#xff0c;对主配置文件进行修改2、创建文件3、验证四、防…