图片服务器

这篇具有很好参考价值的文章主要介绍了图片服务器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、项目简介

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

图片服务器

二、功能及场景

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()

图片服务器

测试用例

图片服务器文章来源地址https://www.toymoban.com/news/detail-466165.html

到了这里,关于图片服务器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 体验版小程序为何无法访问云端服务器后端接口(请求失败...(已完美解决附加图片))?

    体验版小程序访问不到后端,接口请求失败,这个及其头疼的今天这个坑被我踩到了,呜呜呜~ 今天再发体验版小程序时,在微信开发者工具上面是可以正常访问后端的,但是线上体验版本就访问不到,废话不多说,下面我总结了几个 解决的方案 ,如果你的和我不一样,记得

    2024年02月15日
    浏览(44)
  • VisualSVN Server下载安装和使用方法、服务器搭建、使用TortoiseSvn将项目上传到云端服务器、各种错误解决方法

    请大家多多指正,欢迎大家关注! 以A台电脑作为服务器,B台电脑从服务器上拉代码下来到B电脑本地。 截止2024.01.19为最新版本 写这篇文章主要是总是忘记如何将写好的项目推送到服务器里这个操作,其他都是捎带,写下来,再忘就看这篇文章。一写就太多了,但没有相应的

    2024年02月21日
    浏览(49)
  • 将前端vue项目部署到服务器上(详细教程&各种bug解决办法)

    1、修改文件中的配置(解决打包后点击index.html空白页的情况) 将vue.config.js下的publicPath从 \\\'/\\\' 改成 \\\'./\\\' 2、将写好的项目打包 正常的命令是npm run build, 如果是使用vue-admin-template则使用npm run build:prod 3、使用Xftp将打包后的dist传到服务器上,最好传到root目录下 4、 在服务器上安

    2024年02月13日
    浏览(42)
  • 解决前后端分离Vue项目部署到服务器后出现的302重定向问题

    最近发现自己开发的vue前后端分离项目因为使用了spring security 安全框架,即使在登录认证成功之后再调用一些正常的接口总是会莫名奇妙地出现302重定向的问题,导致接口数据出不来。奇怪的是这个问题在本地开发环境并没有,而是部署到了服务器之后才会有。 接口无法加

    2024年02月13日
    浏览(45)
  • vue项目访问服务器时:WebSocket connection to ‘wss://XXXX/‘的解决方法

    关于WebSocke的介绍:新手入门:websocket 简单来说,WebSocket 为web应用程序客户端和服务端之间(客户端服务端)提供了一种全双工通信机制,报错是因为发送报文的过程出现问题。 我的报错如下: 程序可以正常运行使用,但一直打印报错太难受了,问了一下同事,也查了一下

    2024年02月11日
    浏览(46)
  • [小程序]向服务器上传图片和从服务器下载图片

    本例的服务器基于flask,配置flask可以参见 [Flask]上传多个文件到服务器 https://blog.csdn.net/weixin_37878740/article/details/128435136?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522170581653516800185854860%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257Drequest_id=170581653516800185854860biz_id=0utm_m

    2024年02月20日
    浏览(45)
  • BOA服务器(一):简介

    在嵌入式设备的管理与交互中,基于Web方式的应用成为目前的主流,这种程序结构也就是大家非常熟悉的B/S结构,即在嵌入式设备上运行一个支持脚本或CGI功能的Web服务器,能够生成动态页面,在用户端只需要通过Web浏览器就可以对嵌入式设备进行管理和监控,非常方便实用

    2024年02月05日
    浏览(39)
  • Nuxt3 全栈 项目服务器部署 全网最全最细保姆级教程 解决各种小坑 时光小灶

    服务器:Nginx 面板:宝塔面板 个人博客:https://timebk.cn/ 原文地址:原文 第一步肯定是打包咯,前提是项目已开发好了以及没有啥Bug了哈 打包好后,你会在项目根目录下发现多了个 .output 文件夹 打开这个文件夹,将里面的所有文件压缩成压缩包 压缩包格式推荐 .rar ,其它格

    2024年02月12日
    浏览(39)
  • 图片、文件资源服务器(minio服务器)

    1.MinioConfig配置类 2.MinioUtil工具类 3.对应使用方法

    2024年02月11日
    浏览(52)
  • 云计算入门——云服务器:简介

    前些天发现了一个人工智能学习网站,通俗易懂,风趣幽默,最重要的屌图甚多,忍不住分享一下给大家。点击跳转到网站。 什么是云服务器? 云服务器 是远程向用户提供计算资源的互联网基础设施。我们可以将云服务器视为一台私人计算机,可以像本地计算机(例如笔记

    2024年01月18日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包