主要分为三大模块,上传模块,删除模块,展示模块。其中展示模块又可分为展示多张图片与放大图片。进入页面之后就可进行上传操作、删除操作。
1.2核心功能:本项目主要分为两部分,数据存储部分与服务器模块,使用MySQL数据库存储图片属性,将图片内容保存在本次磁盘,服务器提供上传图片,获取图片属性,根据图片URL获取图片内容和删除图片等接口。
- 简单的Web服务器设计开发能力(Servlet 的使用)
- 使用数据库(MySQL)JDBC 操作 MySQL
- 数据库设计(根据实际场景设计数据库表结构)
- 前后端交互的 API 的设计(基于HTTP协议)
- 认识 JSON 数据格式,学习使用 Java 中的 Gson 这个库操作 JSON 数据
- 强化 HTTP 协议的理解,学习测试 HTTP 服务器,Postman 工具的使用
- 基于 md5 进行校验
- 使用 HTML CSS Javascript 技术构建一个简单的网页
代码展示:
DBUtil.java
package dao;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DBUtil {
private static final String URL="jdbc:mysql://127.0.0.1:3306/image_server";
private static final String USERNAME="root";
private static final String PASSWORD="1231";
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;
}
public static Connection getConnection() {
try {
return getDataSource().getConnection();
} catch (SQLException exc) {
exc.printStackTrace();
}
return null;
}
public static void close(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet){
try {
if(resultSet!=null){
resultSet.close();
}
if(preparedStatement!=null){
preparedStatement.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException exc) {
exc.printStackTrace();
}
}
}
Image.java
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 + ''' +
'}';
}
}
ImageDao.java
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 {
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 exc) {
exc.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 exc) {
exc.printStackTrace();
}finally {
//5.关闭连接
DBUtil.close(connection,statement,resultSet);
}
return null;
}
public Image selectOne(int imageId){
//1.获取数据库连接
Connection connection=DBUtil.getConnection();
//2.构造sql语句
String sql="select * from image_table where imageId = ? ";
//3.执行sql语句
PreparedStatement statement=null;
ResultSet resultSet=null;
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 exc) {
exc.printStackTrace();
}finally {
//5.关闭连接
DBUtil.close(connection,statement,resultSet);
}
return null;
}
public void delete(int imageId){
//1.获取数据库连接
Connection connection=DBUtil.getConnection();
//2.拼装sql语句
String sql="delete from image_table where imageId=?";
//3.执行sql语句
PreparedStatement statement=null;
ResultSet resultSet=null;
try {
statement=connection.prepareStatement(sql);
statement.setInt(1,imageId);
int ret=statement.executeUpdate();
if(ret!=1){
throw new JavaImageServerException("删除图片失败!");
}
} catch (SQLException exc) {
exc.printStackTrace();
} catch (JavaImageServerException e) {
e.printStackTrace();
} finally {
//4.关闭连接
DBUtil.close(connection,statement,resultSet);
}
}
public Image selectByMd5(String md5){
//1.获取数据库连接
Connection connection=DBUtil.getConnection();
//2.构造sql语句
String sql="select * from image_table where md5 = ? ";
//3.执行sql语句
PreparedStatement statement=null;
ResultSet resultSet=null;
try {
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 exc) {
exc.printStackTrace();
}finally {
//5.关闭连接
DBUtil.close(connection,statement,resultSet);
}
return null;
}
}
ImageServlet.java
package api;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import dao.Image;
import dao.ImageDao;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
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;
import java.util.SimpleTimeZone;
@WebServlet("/image")
public class ImageServlet extends HttpServlet{
@Override
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 selectOne(String imageId, HttpServletResponse resp) throws IOException {
resp.setContentType("application/json; charset=utf-8");
ImageDao imageDao=new ImageDao();
Image image=imageDao.selectOne(Integer.parseInt(imageId));
Gson gson=new GsonBuilder().create();
String jsonData=gson.toJson(image);
resp.getWriter().write(jsonData);
}
private void selectAll(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("application/json; charset=utf-8");
ImageDao imageDao=new ImageDao();
List images=imageDao.selectAll();
Gson gson=new GsonBuilder().create();
String jsonData=gson.toJson(images);
resp.getWriter().write(jsonData);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取图片的属性信息,并且存入数据库
//a.固定格式
FileItemFactory factory=new DiskFileItemFactory();
ServletFileUpload upload=new ServletFileUpload(factory);
//b.通过upload对象进一步解析请求(解析HTTP请求中奇怪的body中的内容)
//FileItem代表一个上传文件的对象
List items=null;
try {
//理论上来说,http支持一个请求同时上传多个文件
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());
//手动获取当前时间,并格式化日期,yyyyMMdd
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyyMMdd");
image.setUploadTime(simpleDateFormat.format(new Date()));
image.setContentType(fileItem.getContentType());
image.setMd5(DigestUtils.md5Hex(fileItem.get()));
//自己构造一个路径来保存
image.setPath("./image/"+image.getMd5());
ImageDao imageDao=new ImageDao();
Image 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");
}
@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){
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}");
}
}
ImageShow.java
package api;
import dao.Image;
import dao.ImageDao;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
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;
@WebServlet("/imageShow")
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中
outputStream.write(buffer);
}
fileInputStream.close();
outputStream.close();
}
}
index.html
CY 图片列表 CY 图片列表
![]()
{{image.imageName}}
pom.xml
3.界面展示4.0.0 war picture_Server org.example picture_Server 1.0-SNAPSHOT org.mortbay.jetty maven-jetty-plugin 6.1.7 8888 30000 ${project.build.directory}/${pom.artifactId}-${pom.version} / mysql mysql-connector-java 5.1.49 javax.servlet javax.servlet-api 3.1.0 provided commons-fileupload commons-fileupload 1.4 com.google.code.gson gson 2.8.5 commons-codec commons-codec 1.10 commons-io commons-io 2.4 junit junit 4.11 test
框架:junit框架
测试方法:白盒测试,主要对代码的路径覆盖、错误处理等进行测试。
步骤:
1.添加junit依赖
2.创建测试类
package dao;
import java.util.List;
import static org.junit.Assert.*;
public class ImageDaoTest {
@org.junit.Test
public void insert() {
ImageDao imageDao=new ImageDao();
Image image=new Image();
image.setSize(195083);
image.setUploadTime("20211014");
image.setContentType("image/jpeg");
image.setMd5("8a5a0eb24dd33c14f12e58fcae53d7f5");
image.setImageName("test.jpg");
image.setImageId(37);
image.setPath("./image/8a5a0eb24dd33c14f12e58fcae53d7f5");
imageDao.insert(image);
}
@org.junit.Test
public void selectAll() {
ImageDao imageDao = new ImageDao();
List lists = imageDao.selectAll();
System.out.println(lists.size());
System.out.println();
for (Image image:lists) {
System.out.println("imageId = " + image.getImageId()+" "+"imageName = "+image.getImageName());
}
}
@org.junit.Test
public void selectOne() {
ImageDao imageDao = new ImageDao();
try {
Image image = imageDao.selectOne(2);
System.out.println(image.getImageName());
System.out.println(image.getImageId());
System.out.println(image.getSize());
System.out.println(image.getMd5());
}catch (Exception e){
System.out.println("查询失败,imageId 不存在!!!");
}
}
@org.junit.Test
public void delete() {
ImageDao imageDao = new ImageDao();
List lists1 = imageDao.selectAll();
System.out.println("删除前图片容量" + lists1.size());
imageDao.delete(54);
List lists2 = imageDao.selectAll();
System.out.println("删除后图片容量" + lists2.size());
if(lists1.size()>lists2.size()) {
System.out.println("删除成功");
}else {
System.out.println("删除失败,imageId 不存在!!!");
}
}
@org.junit.Test
public void selectByMd5() {
ImageDao imageDao = new ImageDao();
try {
Image image = imageDao.selectByMd5("fbab242a577efdf0c530a27a92e0c255");
System.out.println(image.getImageId());
System.out.println(image.getImageName());
System.out.println(image.getPath());
} catch (Exception e) {
System.out.println("查询失败,Md5 不存在!!!");
}
}
}
2.系统功能测试
编写测试用例
黑盒测试
3.自动化测试编写自动化测试脚本,测试上传,删除功能
unit框架
from selenium import webdriver
import unittest
import time
class imageTest(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Chrome(executable_path=r"C:UsersAdministratorAppDataLocalGoogleChromeApplicationchromedriver.exe")
self.driver.get("http://localhost:8080/picture_Server/index.html")
self.driver.maximize_window()
def tearDown(self):
self.driver.quit()
def test_upload(self):
driver=self.driver
driver.find_element_by_name("upload").send_keys("C:/Users/Administrator/Pictures/背景/yy.jpg")
driver.find_element_by_xpath("/html/body/nav/div/form/div[2]/input").click()
time.sleep(6)
@unittest.skip("skipping")
def test_delete(self):
driver=self.driver
driver.find_element_by_xpath("/html/body/div[1]/figure/div/div[6]/button").click()
time.sleep(6)
alert=driver.switch_to.alert
alert.accept()
time.sleep(6)
if __name__ == '__main__':
unittest.main()
4.性能测试
使用loadRunner进行性能测试
录制脚本(上传图片功能)
进行测试(设置3个用户并发,每5s退出一个)
生成测试报告
我们以测试场景“上传图片”为例,对测试报告进行说明
1.测试说明
| 功能 | 上传图片 |
|---|---|
| 测试目的 | 进行上传图片操作,评估服务器承受能力 |
| 场景说明 | 打开首页,点击登录,选择照片,点击上传 |
| 事务说明 | 上传图片 |
| 参数设置 | 通过HP LoadRunner11.00压力测试工具,导入录制脚本,设置测试1台控制主机,1台压力测试机被控制机,设置参数如下: Start vusers:3,100Vusers every 00:00:05 Duration:Run 3 minute Stop vusers:3Vusers every 00:00:05 All user:3 |
2.结果分析
| 指标 | 最大 | 平均 | |
|---|---|---|---|
| 并发用户数 | 3 | ||
| TPS | 14 | 9 | |
| 响应时间 | 上传 | 0.500 | 0.014 |
| 关闭上传 | 0.250 | 0.014 | |
| 点击率 | 210 | 108 |
1.统计信息摘要
下图说明本次测试运行的最大并发数为3,总吞吐量为2,481,485,491字节,平均每秒吞吐量为11,650,167字节,总的请求数为23,016,平均每秒的请求为108.056。对于吞吐量,单位时间内吞吐量越大,说明服务器的处理能越好,而请求数仅表示客户端向服务器发出的请求数,与吞吐量一般是成正比关系。
2.事务摘要
这部分给出了场景执行结束后相关Action的平均响应时间、通过率等情况,如下图所示,从该图中我们可以得到每个Action的平均响应时间与业务成功率。
3.HTTP响应摘要
该部分显示在场景执行中,每次发送HTTP请求所得到的状态。本次测试其中“HTTP200”有23,016,000次,基本所有的请求都能成功。
4.每秒点击数
“Hits per Second(每秒点击数)”反映了每秒向服务器提交的请求数量。下图显示的是“Hits per Second”与“Throughput”的复合图。如果两种图像的曲线都正常且基本一致,说明服务器能及时接受客户端的请求,并能返回结果。从图中可以看出,整体表现一般。
5.业务成功率
业务成功率就是事务成功率,用户一般把一个Action当做一笔业务,在“Transaction Summary”中我们可以很明确地看到每个事务的执行状态
1.删除图片后刷新页面图片依旧存在
原因:删除图片未删除干净(上传时本地还有一份图片数据,删除时未删除)
2.上传已删除过的图片显示写失败,再次刷新页面才会显图片
原因:删除图片未删除干净(上传时本地还有一份图片数据,删除时未删除)
3.上传非图片的文件上传成功
建议:上传时进行判断
4.上传空文件上传成功
建议:上传时进行判断
5.全部图片在同一个页面,图片过多时,对服务器压力过大
原因:未分页
6.图片名称过长时未折行展示,影响美观
建议:折行展示



