一、引言
先描述一下需求:
目前手中的项目是一个比较老的平台系统,一个单体应用,即所有的模块都打包在一个War包中发布到Tomcat。由于是国企单位的内网环境,公司没有独立的文件服务器,对于以前的老系统,文件的存储和访问,在一台服务器上,完全不是问题。
但现在要将老系统的各个模块抽取出来,做成各个独立的微服务。在这种情况下,文件的存储和访问就会有问题,一个节点的资源,如何让其他节点访问?由于我对客观环境不是很清楚,给的建议是提供一个公共的文件服务,但领导说,条件不允许。因为在项目实施的时候,每个省公司都是内网环境,难道要让每个省公司都搭建一个文件服务器?成本太高……
So,将文件存储数据库,就是讨论的结果。每次涉及文件上传,就将文件存到数据库,若其他节点发现自己本节点没有该文件,就去数据库中把文件拉去下来。用这种方法来实现文件共享存储。
这种方式我是排斥的,但条件所限,没有办法……
二、具体代码 1、几点说明平台数据库:Oracle
数据库字段:file_content为存储文件的字段
public class ResourceDao {
private static final org.slf4j.Logger LOG =
LoggerFactory.getLogger(ResourceDao.class);
/**
* 读取数据表中资源
* @param fileSrcMd 文件路径的MD5值
* @return boolean 读取成功/失败
*/
public boolean readResourceFromDB(String fileSrcMd) {
FileOutputStream fos = null;
String fileSrc = "";
InputStream in = null;
try{
//获得数据库连接
Connection con = PoolManager.getPoolConnection();
con.setAutoCommit(false);
String querySql = "select file_src, file_content from "
+ "t_file_content where file_src_md = ?";
PreparedStatement ps = con.prepareStatement(querySql);
ps.setString(1,fileSrcMd);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
fileSrc = rs.getString("file_src");
java.sql.Blob blob = rs.getBlob("file_content");
in = blob.getBinaryStream();
} else {
LOG.warn("no resource by md5: {}", fileSrcMd);
return false;
}
File file = new File(fileSrc);
if (!file.getParentFile().exists()) {
boolean succ = file.getParentFile().mkdirs();
if (!succ) {
throw new Exception("mkdir failed: "
+ file.getParentFile().getAbsolutePath());
}
}
fos = new FileOutputStream(file);
int len = 0;
byte[] buf = new byte[1024];
while ((len = in.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.flush();
con.commit();
return true;
} catch (Exception e) {
LOG.error("readResourceFromDB异常:", e);
return false;
} finally {
LOG.debug("readResourceFromDB() enter finally");
if (null != fos) {
try {
fos.close();
} catch (IOException e) {
}
}
if (null != in) {
try {
in.close();
} catch (IOException e) {
}
}
}
}
/**
* 将资源保存到数据库中
* @param fileTid 文件id
* @param fileSrc 文件路径
* @throws Exception
*/
public void saveResourceToDB(String fileTid, String fileSrc)
throws Exception {
OutputStream os = null;
FileInputStream fis = null;
// 文件路径MD5值
String fileSrcMd = Md5Tool.getMd5(fileSrc);
try {
//获得数据库连接
Connection con = PoolManager.getPoolConnection();
con.setAutoCommit(false);
String sql = "insert into t_file_content (file_tid, file_src_md,"
+ "file_src,file_content) values (?, ?, ?, empty_blob())";
PreparedStatement ps = con.prepareStatement(sql);
ps.setString(1,fileTid);
ps.setString(2,fileSrcMd);
ps.setString(3,fileSrc);
//插入一个空对象empty_blob()
ps.executeUpdate();
ps.close();
//锁定数据行进行更新,用for update加锁
String querySql = "select file_content from t_file_content "
+ "where file_src_md = ? for update";
ps = con.prepareStatement(querySql);
ps.setString(1,fileSrcMd);
ResultSet rs = ps.executeQuery();
File file = new File(fileSrc);
if(!file.exists()) {
return;
}
fis = new FileInputStream(file);
byte[] buffer = new byte[1024];
if (rs.next()) {
//得到java.sql.Blob对象后强制转换为oracle.sql.BLOB
oracle.sql.BLOB blob = (oracle.sql.BLOB)
rs.getBlob("file_content");
os = blob.getBinaryOutputStream();
int len = 0;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
}
os.flush();
con.commit();
}catch (Exception e){
LOG.error("saveResourceToDB异常:", e);
throw e;
}finally {
LOG.debug("saveResourceToDB enter finally");
if (null != os) {
try {
os.close();
} catch (IOException e) {
}
}
if (null != fis) {
try {
fis.close();
} catch (IOException e) {
}
}
}
}
/**
* 解决资源文件生成相同的MD5值,导致数据库插入失败。
* 解决办法:重新生成新的文件名,最多MD5碰撞3次。
* 此处要把文件的绝对路径返回,要不然,t_media_cotent、t_file_content 中
* 路径不一致
* @param fileTid
* @param fileSrc
* @throws Exception
*/
public String saveResourceToDBWithNoCollision(String fileTid,
String fileSrc) throws Exception {
String fileUUid = "";
Exception saveResourceException = null;
File file = new File(fileSrc);
for (int i = 0; i 0){
renameDestFile = new File(fileDir + "/"+ fileName);
}else{
renameDestFile = new File(fileDir + "\\" + fileName);
}
file.renameTo(renameDestFile);
file = renameDestFile;
continue;
}
}
// 循环尝试插库3次不成功,抛出插库异常
throw saveResourceException;
}
// public static Connection getConnection(){
//
// Connection conn = null;
// try {
// Class.forName("oracle.jdbc.driver.OracleDriver");
// String url="jdbc:oracle:thin:@192.168.200.124:1521:dev";
// conn = DriverManager.getConnection(url,"aaa","111111 ");
// } catch (Exception e) {
// e.printStackTrace();
// }
// return conn;
// }
//
// //测试
// public static void main(String[] args) {
//
// ResourceDao dao = new ResourceDao();
// 入库
// String fileTid = Utility.newUUID();
// String fileSrc = "F:\\pic\\pic\\pc\\addr\\logo.png";
// String fileSrcMd = Md5Tool.getMd5(fileSrc);
//
// System.out.println("fileTid:"+fileTid);
// System.out.println("fileSrc:"+fileSrc);
// System.out.println("fileSrcMd:"+fileSrcMd);
//
//
// try {
// dao.saveResourceToDBWithNoCollision(fileTid,fileSrc);
dao.saveResourceToDB(fileTid,fileSrc);
// }catch (Exception e){
// System.out.println("EMPException:" + e);
// }
// --------------------------------------------------
// 从库中取资源
// String fileSrc = "F:\\pic\\pic\\pc\\addr\\logo.png";
// String fileSrcMd = "9270d863bbbe6f13114fc9e2ce544c93";
//
// System.out.println("fileSrc after md5 :" + Md5Tool.getMd5(fileSrc));
// System.out.println("fileSrcMd :" + fileSrcMd);
//
// try {
// dao.readResourceFromDB(fileSrcMd);
// }catch (Exception e){
// System.out.println("EMPException:" + e);
// }
// }
}
三、小结
这里还要对上面的file_src_md的字段做一下说明:因为老系统和前端静态资源交互方式用的是路径,而不是统一的fileId,所以去数据库中查资源的时候,只能拿路径去查。因为路径长度不一,而且查询效率不高,所以就设计了将路径的MD5放入数据库中,作为查询字段。