1)在我们真正进行WEB开发的时候,编写代码之前,我们都要做两件非常重要的设计:数据库设计+前后端交互API接口的设计+JDBC操作+WEB服务器开发能力,我们还要实现一个简单的页面来进行展示当前的图片
2)学习测试服务器---->PostMan
1.项目背景:首先它是一个服务器(图床)
1)核心功能:上传图片,展示图片
2)我们通常会写博客,githup,可能我们也是会穿插图片或者其他资源,我们写博客的时候,插入的图片,本质上就是一个url,这个链接所对应的图片资源在另一个服务器上面,本质上CSDN已经内置了这个图片服务器功能
3)当我们在浏览器上面输入图片的时候,我们点击这个图片,复制链接地址,然后再来打开一个浏览器的窗口,输入刚才的这个链接地址,展示搜索结果,我们就可以看到这个图片了
2.核心知识点:
1)简单的Web服务器(HTTP服务器)设计开发能力(Servlet)
2)Servlet是Tomact这个服务器提供的一组编程接口,我们就可以开发出我们自己所实现的一套逻辑,这一套逻辑我们就可以部署到Tomact里面,就可以完成一个我们自己的服务器的搭建
3)使用数据库进行存储
4)学会设数据库设计,根据实际场景设计数据库表结构
5)前后端接口的设计,前后端交互的API的设计
6)认识JSON数据格式,使用Gson这个库来进行操作JSON
7)学习测试一个服务器(postman),使用HTML,CSS,javascript来进行构建一个简单的网页
3.服务器设计和数据库设计
1)mysql -uroot连接数据库,就是启动了MYSQL客户端,我们之前安装数据库,即安装了服务器又安装了客户端(命令行程序),是同过网络来进行通信的
2)MYSQL服务器就是我们的本体,在这个服务器里面包含着数据库和表;
3)MariaDB和MYSQL有什么关系?
4)创建表结构:
描述图片相关的内容(属性信息)
1)ImageID 图片ID primary key auto_increment----图片的唯一身份标识
2)ImageName varchar(50)------图片的名字
3)ImageSize int------图片的大小
4)ImageTime varchar(50)------图片的上传时间
5)ContentType varchar(50)---->image/jpg
(ContentType(HTTP数据类型)=image/png)-----表示图片类型
6)ContentPath varchar(50)--------路径,服务器本地的目录
7)md5 varchar(1024)------校验和(在应用层进行校验)
咱们的数据库中存储的是图片的属性和路径;
图片正文,是以文件的形式直接存储在磁盘里面的,数据库中的一个Contentpath就是对应咱们磁盘上面的文件
md5:字符串哈希算法,密码学领域也能用到
1)图片的MD5校验和,应用层进行数据校验,通过一个更短的字符串来进行验证整体数据是否正确,短的字符串是根据源字符串的内容按照一定的规则来计算出来的;
2)src循环冗余校验算法
3)哈希算法就是把key经过一系列的数学变换,得到一个整数,对应到数组下标上面,把hello字符串计算出一个哈希值(123456),把这个哈希值去%数组的长度从而得到数组下标
4.服务器API设计:
JSON是一种数据组织的格式,格式是键值对的结构,gson既可以解析哈希表也是可以进行解析类的,还可以解析数组;
第一种用法:把哈希表转化成JSON格式的数据
对于对象来说,转化成JSON格式的数据键就是字段名,Value就是这个字段对应的值
引入2.8.6的库 import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.util.HashMap; public class TestJson { public static void main(String[] args) { HashMap<String,Object> hashMap=new HashMap<>(); //1.创建hashmap hashMap.put("name","曹操"); hashMap.put("代码大神","李佳伟"); //2创建Json对象 Gson gson=new GsonBuilder().create(); //3.使用ToGson方法转成字符串 String str= gson.toJson(hashMap);//里面的参数也可以是一个对象 System.out.println(str); } }
第二种用法:使用数组
import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class TestJson { static class Student{ public String username; public int age; public String password; } public static void main(String[] args) { Student student1=new Student(); student1.age=12; student1.username="李佳伟"; student1.password="12503487"; Student student2=new Student(); student2.age=12; student2.username="周云刚"; student2.password="778896"; List<Student> list=new ArrayList<>(); list.add(student1); list.add(student2); Gson gson=new GsonBuilder().create(); String str= gson.toJson(list); System.out.println(str); } }
回顾:文件上传操作在HTML中是如何完成的
1)我们直接在前端使用form表单
<form action="upload" method="POST" enctype="multipart/form-data"> <input type="file" name="MyImage"> <input type="submit" value="提交图片"> </form> 1)这里面的ID就表示唯一的身份标识 2)action:提交图片的时候,产生的HTTP请求产生的Path是怎么样的,url 3)method:GET方法上传还是POST方法上传 4)enctype:表示请求的格式,文件的传输格式
我们开始正式设计前后端进行交互的API:HTTP协议具体要构造成什么样子
1)新增图片:
请求: POST/Image ContentType:multipart/form-data; 正文内容:包含图片自身的一些信息,图片正文的二进制内容 响应: 1)上传成功的情况: HTTP/1.1 200 OK { OK:"1", reason:"上传成功" } 2)上传失败的情况: HTTP/1.1 200 OK { OK:"0", reason:"上传图片失败" }
2)查看所有图片信息(数据库存放的属性信息)
请求: GET/Image 响应: HTTP/1.1 200 OK [ { ImageID:1, ImageName:"代码.png", ImageSize:1000, ImageTime:"2022/12/2", ContentType:"image/png", ContentPath:"./data/1.png", md5:"1122334455" }, { ImageID:2, ImageName:"板书.png", ImageSize:1050, ImageTime:"2022/10/2", ContentType:"image1/png", ContentPath:"./data/2.png", md5:"778896" } ] 获取失败: HTTP/1.1 200 OK []
3)查看指定图片信息
请求: GET/Image?ImageID=7 响应:HTTP/1.1 200 OK { ImageID:1, ImageName:"代码.png", ImageSize:1000, ImageTime:"2022/12/2", ContentType:"image/png", ContentPath:"./data/1.png", md5:"1122334455" } 响应失败: HTTP/1.1 200 OK { OK:"0", reason:"失败的原因" }
4)删除操作(删除指定图片)
请求: DELETE/Image?ImageID=具体的图片ID 响应: HTTP/1.1 200 OK { OK:"200", reason:"" } 响应失败: HTTP/1.1 200 OK { OK:"200", reason:"出错原因", }
6)查看指定图片内容(咱们的图片内容到底展示的是小猫小狗,还是其他呢?)
请求:GET/ImageShow?ImageID=1 响应:HTTP/1.1 200 OK ContentType:image/jpg [图片的二进制内容] 返回错误: HTTP/1.1 200 OK { OK:"200", reason:"出错的原因" }
我们总共拆分成了两个Servlet,一个路径请求对应着一个Servlet
源码开发:
一:封装获取到数据库连接操作
create table Image( ImageID int primary key auto_increment, ImageName varchar(100), ImageSize int, ImageTime dateTime, ContentType varchar(50), ContentPath varchar(100), md5 varchar(100)); 此时咱们的数据库是在linux上面进行运行的,咱们要在数据库中进行创建表
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 ConnectionMysql { private static volatile DataSource dataSource=null; private static String url="jdbc:mysql://127.0.0.1:3306/picturemaven?characterEncoding=utf-8&userSSL=true"; private static String username="root"; private static String password="12503487"; private static DataSource GetDataSource() { if(dataSource==null) { synchronized(Object.class) { if(dataSource==null) { dataSource=new MysqlDataSource(); ((MysqlDataSource)dataSource).setPassword(password); ((MysqlDataSource)dataSource).setUser(username); ((MysqlDataSource)dataSource).setURL(url); } } } return dataSource; } public static Connection GetConnection() throws SQLException { return GetDataSource().getConnection(); } public static void Close(ResultSet resultSet, PreparedStatement preparedStatement,Connection connection) throws SQLException { if(resultSet!=null) { resultSet.close(); } if(preparedStatement!=null) { preparedStatement.close(); } if(connection!=null) { connection.close(); } } }
1)多个线程同时操作一个数据的时候,可能会发生线程安全问题
1.1)当我们进行创建datasource实例的时候,进入if语句之后,可能就是说第一个线程在进行new datasource()实例的时候(datasource实例,马上就要进行创建了),可能线程2在这个时候会抢占CPU,到就绪队列上面执行;
1.2)当第二个线程进行读取(进入到if语句判断里面)发现datasource是空(此时线程1还没有创建DataSource实例,就被线程2抢走了,线程2会立即执行创建dataSource实例),就会创建第一份datasource实例,当我们的线程1被调度上CPU执行之后(if语句因为调度被线程2抢走之前已经判断为空了,PCB的上下文此时就会发生作用),又会进行创建datasource对象,从而导致线程不安全问题
2)资源关闭顺序一定要正确(后创建的先进行释放)
3)咱们的ctrl+alt+t surround功能是选中一部分代码之后,在这个代码外边加上一些其他代码
4)加上volatile关键字之后就可以防止线程1创建DataSource实例之后,线程2也想创建DataSource,无法感知线程1已经创建了DataSource实例,还认为DataSource为空呢,进行外层if判断的时候可能会导致判断错误
这个程序会报错,数据库会无法连接
二:封装数据库操作数据
受查异常:向上抛出或者是就地处理
出现异常之后的处理措施:
1)直接用catch进行捕获,里面直接打印信息调用栈
2)直接抛出异常,让我们的程序进行终止
3)监控报警直接通知程序员,这个一般适用于服务器,我们就去专门写一个程序,它是用来感知服务器是否出现了问题,一旦我们的程序发生了问题,那么直接发送短信/电话/微信(比如说充钱操作)
Java代码封装数据库:
1)创建一个Image对象,对应一个图片对象(包括图片的相关属性),这一个类就对应着一张数据库的表(里面的path属性就表示我们的图片位置在我们的计算机硬盘上面的哪一个位置),咱们的数据库只是存放图片的属性,而咱们具体的图片内容是存放在一个个的文件里面;
package Dao; public class Image { private int ImageID; private String ImageName; private int ImageSize; private String ImageTime; private String ConteneType; private String ContentPath; private String md5; public int getImageID() { return ImageID; } public void setImageID(int imageID) { ImageID = imageID; } public String getImageName() { return ImageName; } public void setImageName(String imageName) { ImageName = imageName; } public int getImageSize() { return ImageSize; } public void setImageSize(int imageSize) { ImageSize = imageSize; } public String getImageTime() { return ImageTime; } public void setImageTime(String imageTime) { ImageTime = imageTime; } public String getConteneType() { return ConteneType; } public void setConteneType(String conteneType) { ConteneType = conteneType; } public String getContentPath() { return ContentPath; } public void setContentPath(String contentPath) { ContentPath = contentPath; } public String getMd5() { return md5; } public void setMd5(String md5) { this.md5 = md5; } @Override public String toString() { return "Image{" + "ImageID=" + ImageID + ", ImageName='" + ImageName + '\'' + ", ImageSize=" + ImageSize + ", ImageTime='" + ImageTime + '\'' + ", ConteneType='" + ConteneType + '\'' + ", ContentPath='" + ContentPath + '\'' + ", md5='" + md5 + '\'' + '}'; } }
2)我们在进行创建一个包叫做ImageDemo,在进行创建一个类叫做OperateImage对象的管理器,我们希望借助这个类完成对对象的增删改查
2.1)当我们进行新增一个Image对象的时候,实现这个方法的时候,我们在方法里面直接传入一个Image对象,进行插入数据库里面
2.2)进行查找的时候,我们要分成两种情况,一种是查询所有的图片(List<Image>,一种是查找指定ID的图片(Image)
2.3)删除操作,是指定图片的ID进行删除,ImageID:
package Dao; 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 OperateImage { public void InsertImage(Image image) throws SQLException, JavaOperateMysql { //1与数据库建立连接 Connection connection=ConnectionMysql.GetConnection(); //2.创建并拼装SQL语句 String Sql="insert into Image values(null,?,?,?,?,?,?)"; PreparedStatement preparedStatement=connection.prepareStatement(Sql); preparedStatement.setString(1,image.getImageName()); preparedStatement.setInt(2,image.getImageSize()); preparedStatement.setString(3,image.getImageTime()); preparedStatement.setString(4,image.getConteneType()); preparedStatement.setString(5,image.getContentPath()); preparedStatement.setString(6,image.getMd5()); //3.执行SQL语句 int len=preparedStatement.executeUpdate(); if(len==1) { System.out.println("插入图片的操作成功"); }else{ System.out.println("插入图片的操作失败"); // throw new UnsupportedOperationException("当前数据库执行插入操作失败"); throw new JavaOperateMysql("当前数据库插入操作失败"); 当我们的程序写到这里的时候,还是会发生问题,当我们上面的任意一个操作抛出异常的时候 就会直接自己跳转到Catch语句或者是程序异常终止 所谓一我们后面的关闭资源的操作就有可能执行失败,我们根本的目的就是无论程序是否出现异常,我们都要进行最终的一个资源关闭操作 所以最好好把关闭资源的操作放到finally里面,程序的代码可能会爆红,我们就把代码字段变成全局变量 } //4.关闭连接 ConnectionMysql.Close(null,preparedStatement,connection); } public List<Image> SelectAll() throws SQLException { //1.与数据库建立连接 Connection connection=ConnectionMysql.GetConnection(); //2.拼装SQL语句 String SQL="select * from Image"; //3.执行SQL语句 PreparedStatement preparedStatement= connection.prepareStatement(SQL); ResultSet resultSet= preparedStatement.executeQuery(); List<Image> list=new ArrayList<>(); //4.遍历结果集,返回结果 while(resultSet.next()) { Image image=new Image(); image.setImageID(resultSet.getInt("ImageID")); image.setContentPath(resultSet.getString("ContentPath")); image.setImageTime(resultSet.getString("ImageTime")); image.setMd5(resultSet.getString("md5")); image.setImageSize(resultSet.getInt("ImageSize")); image.setImageName(resultSet.getString("ImageName")); image.setConteneType(resultSet.getString("ContentType")); System.out.println(image); list.add(image); } //5.关闭连接 ConnectionMysql.Close(resultSet,preparedStatement,connection); return list; } public Image SelectOne(int ImageID) throws SQLException { //1.与数据库建立连接 Connection connection=ConnectionMysql.GetConnection(); //2.拼装SQL语句 String sql="select * from Image where ImageID=?"; //3.执行SQL语句 PreparedStatement preparedStatement= connection.prepareStatement(sql); preparedStatement.setInt(1,ImageID); ResultSet resultSet= preparedStatement.executeQuery(); //4.处理结果集 Image image=new Image(); while(resultSet.next()) { image.setImageID(resultSet.getInt("ImageID")); image.setContentPath(resultSet.getString("ContentPath")); image.setImageTime(resultSet.getString("ImageTime")); image.setMd5(resultSet.getString("md5")); image.setImageSize(resultSet.getInt("ImageSize")); image.setImageName(resultSet.getString("ImageName")); image.setConteneType(resultSet.getString("ContentType")); } ConnectionMysql.Close(resultSet,preparedStatement,connection); return image; } public void Delete(int ImageID) throws SQLException { //1.与数据库建立连接 Connection connection=ConnectionMysql.GetConnection(); //2.拼装SQL语句 String SQL ="delete from Image where ImageID=?"; //3.执行SQL语句 PreparedStatement preparedStatement=connection.prepareStatement(SQL); int len= preparedStatement.executeUpdate(); if(len==1) { System.out.println("删除成功"); }else{ System.out.println("删除失败"); } //4.关闭资源 ConnectionMysql.Close(null,preparedStatement,connection); } }
如何进行打JAR包:
1.普通方法:
1)注意:当前我们的数据库是在阿里云服务器上面,也就是在咱们的linux上面,不是在本地的windows系统上面,我们直接通过上述代码是不可以进行直接在本地访问咱们的数据库,所以我们需要把这些程序部署到我们的云服务器上面才可以看到效果
2)我们的解决方法就是直接打一个jar包,我们直接把这个jar包直接拷贝到我们的云服务器上面就可以了
3)我们直接点击File目录中的Project Structure会显示Project Setting,然后我们直接点击Artifacts,然后在进行点击+,点击jar,在进行点击mainclass,选择我们的入口类(表示要从哪一个类开始执行)(这个入口类必须有main方法)
4)JAR包:本质上来说就是一个zip压缩包,本质上来说就是一大堆的.class文件,我们会把一大堆的.class文件放在一起,打包成一个文件
5)最后我们点击Build点击BuildAtifacts就可以进行打包操作了,打包之后,就会自动生成jar包
6)先把jar包拖到linux上面,运行我们的jar包:java -jar +jar包名字,我们就可以进行运行jar包了,我们按下回车之后,系统虽然没有给任何的提示,但是如果说最后咱们的jar包运行错误,那么一定会抛出一个异常,会打印异常调用栈的信息
下面是我写代码的时候出现的两个错误
while(resultSet!=null) { Image image=new Image(); image.setImageID(image.getImageID()); image.setContentPath(image.getContentPath()); image.setImageTime(image.getImageTime()); image.setMd5(image.getMd5()); image.setConteneType(image.getConteneType()); } //1.第一个判定条件是result.next //2.第二个写的啥也不是,具体的值应该从result中获取
我们可以通过单元测试的方法对程序进行验证,所谓的单元测试,就是把一个类或者是一个方法作为一个单元,一旦出现问题,我们就可以尽早地发现BUG,BUG发现的越早,我们的解决成本就越低