人事信息管理系统中,需要管理用户的个人身份照片。通常这种格式的照片只有几
K
到几十
K
大小,保存在数据库中易于进行管理和维护(如果放在文件夹下容易发生误操作而引起数据被修改或丢失)。
功能设计:
给用户提供一个上传的界面,并设定上传文件的尺寸上限。用户上传的照片先统一保存在一个临时文件夹中,之后可以用
<img>
指向临时文件夹中的这个图片,让用户可以预览自己上传的照片。当所有的用户信息都收集完成后,将图片和其他信息一并提交,保存到数据库中。保存成功以后,删除临时文件夹中的图片。
实现步骤:
我使用的是从
struts
主页上下载的
struts-1.2.8-src
,其中
web/examples/
目录下有一个
upload
的例子,稍微修改了一下就直接拿过来用了。这是一个
JSP
页面、
ActionForm
和
Action
的组合。下面分别列出各自的代码。
upload.jsp
的部分源代码:
<html:form action="/UploadSubmit" enctype="multipart/form-data">
请选择需要上传的照片
:
<html:file property="theFile"/>
<html:submit value="
上传
"/>
</html:form>
接下来需要在
ActionForm
中声明这个属性,并设置
getter
和
setter
方法,这部分源代码如下:
public class UploadForm extends ActionForm {
protected FormFile theFile;
public FormFile getTheFile() {
return theFile;
}
public void setTheFile(FormFile theFile) {
this.theFile = theFile;
}
}
这个表单的
theFile
属性不是
String
或
boolean
,而是
org.apache.struts.upload.FormFile
。因为用户上传的是一个二进制文件,而
HTTP
协议是以文本形式传输数据的,这就需要进行转换。打个比方,一辆汽车需要从甲地送到乙地,但是两地之间只有一条索道,汽车没法开,所以就想个办法在甲地把汽车先拆了,把零件送到乙地再重新组装成一辆汽车。
FormFile
起的就是拆卸和组装的作用,只不过它把拆卸、传输和组装的过程都封装起来了,我们看到的是一辆汽车从甲地开进
FormFile
,过一会它就从乙地开出来了
J
我们要决定的只是把它停到什么地方,这就是
Action
的活了。
按照功能设计,
Action
要把这部车停到一个临时文件夹下面,这部分源代码如下:
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
if (form instanceof UploadForm) {
UploadForm theForm = (UploadForm) form;
//
获取上传的数据文件
FormFile file = theForm.getTheFile();
//
获取文件名
String filename= file.getFileName();
//
设置图片文件临时存放的路径
HttpSession session = request.getSession();
String path = session.getServletContext().getRealPath("/") + "temp\\" + filename;
try {
//
读取文件中的数据,获取二进制的数据流
InputStream stream = file.getInputStream();
//
把数据写到指定路径
OutputStream bos = new FileOutputStream(path);
int bytesRead = 0;
byte[] buffer = new byte[8192];
while ((bytesRead = stream.read(buffer, 0, 8192)) != -1) {
bos.write(buffer, 0, bytesRead);
}
bos.close();
logger.info("The file has been written to \""
+ path + "\"");
//
设计一个标记,说明用户已经上传过照片了。
session.setAttribute("imageuploaded","true");
session.setAttribute("filename",filename);
// close the stream
stream.close();
bos.flush();
bos.close();
}catch (FileNotFoundException fnfe) {
return null;
}catch (IOException ioe) {
return null;
}
//destroy the temporary file created
file.destroy();
//
转向下一个页面
return mapping.findForward("next");
}
//this shouldn't happen in this example
return null;
}
这样图片就被放在
temp
的临时文件夹下,显示的时候,只需要先检查一下标记,看看用户是否上传了照片,如果已经上传,就用一个
<img src=””>
引用这个图片。还有一个小地方需要修改,因为限定上传的是身份照片,需要限定一个尺寸上限,这个在
struts
的
upload
里面有现成的例子。先在
struts-config.xml
中配置这个
ActionForm
和
Action
:
<form-bean name="uploadForm"
type="org.apache.struts.webapp.upload.UploadForm"/>
……
<action input="/pages/hr/error.jsp" name="uploadForm"
path="/UploadSubmit" scope="request"
type="org.apache.struts.webapp.upload.UploadAction" validate="true">
<forward name="next" path="/pages/hr/input.jsp"/>
</action>
……
<controller maxFileSize="2M" inputForward="true" />
在配置文件中已经看到
<action>
的
validate
属性被设置成“
true
”,这就是说表单提交之前先要对其内容进行验证,这里我们要验证的就是
theFile
是否超出了
controller
中设定的最大尺寸
”2M”
。这个验证是通过
ActionForm
的
validate
方法来实现的:
/**
* Check to make sure the client hasn't exceeded the maximum allowed upload size inside of this validate method.
**/
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request) {
ActionErrors errors = null;
//has the maximum length been exceeded?
Boolean maxLengthExceeded =
(Boolean) request.getAttribute(
MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED);
if ((maxLengthExceeded != null) && (maxLengthExceeded.booleanValue())) {
errors = new ActionErrors();
errors.add(
ActionMessages.GLOBAL_MESSAGE ,
new ActionMessage("maxLengthExceeded"));
errors.add(
ActionMessages.GLOBAL_MESSAGE ,
new ActionMessage("maxLengthExplanation"));
}
return errors;
}
这里我估计有个
hook
之类的东西先截获了表单(应该和
controller
有关),对
theFile
的尺寸进行校验,然后把结果保存在
request scope
中。
Validate
方法只要检查一下这个结果就可以了,如果尺寸超标,表单就不会被提交给
Action
。
以上算是完成了第一步,从用户那里拿到照片,并保存在临时文件夹当中。接下来要做的就是把照片保存到
MySQL
数据库中,这个字段我用的是
MEDIUMBLOB
,因为
BLOB
最大长度是
216-1
字节,大约
64K
;
MEDIUMBLOB
是
224-1
字节,约
16M
,足够用了。保存图片的主要代码如下:
/**
*
将用户的照片保存在数据表中,添加成功后,删除临时文件夹中的图片。
* @param id
用户的身份号,作为图片的标识码
* @param path
图片存放的路径。一般存放在一个临时文件夹中。
* @return
*/
public static void saveImage(int id, String path) throws SQLException{
String time = new java.util.Date().toString();
Connection conn = null;
PreparedStatement pstmt = null;
boolean flag = false;
//
获取连接
try {
conn = DbManager.getConnection();
logger.info(time + ":saveImage()
从
DbManager
数据库连接池获取一个连接。
");
} catch (SQLException e) {
//
如果没有能够从
DbManager
获取连接,此次查询操作失败
logger.error(time + ": saveImage()
不能获取数据库连接,无法保存图片!
");
throw new SQLException(":saveImage()
不能获取数据库连接,无法保存图片!
");
}
//
执行查询
try {
pstmt = conn.prepareStatement("UPDATE hr01 SET hr01_photo=? where hr01_id=?");
FileInputStream in = new FileInputStream(path);
pstmt.setBinaryStream(1,in,in.available());
pstmt.setInt(2,id);
pstmt.executeUpdate();
pstmt.executeUpdate("COMMIT");
logger.info("
图片
" + path + "
被添加到数据库中!
");
flag = true;
}catch(IOException e){
logger.error("
图片
" + path + "
文件读写错误!请检查文件路径是否正确
");
throw new SQLException("
无法保存图片!
");
}catch (SQLException ex) {
logger.error(new java.util.Date() + "Error:Insert into table."
+ ex.getMessage());
logger.error("
图片
" + path +"
没有被保存到数据库
.");
throw new SQLException("
图片
" + path +"
没有被保存到数据库
.");
} finally {
try {
pstmt.close();
conn.close();
logger.info("DbHrinfo saveImage() closed the connection created at " + time);
} catch (SQLException e) {
}
}
>
//
图片添加成功以后就删除临时文件夹中的图片数据
if(flag == true){
File file = new File(path);
if(file.exists()){
file.delete();
}
}
}
需要注意的是
pstmt.executeUpdate("COMMIT");
这行代码,最初我并没有写这行,程序能顺利执行,日志中也显示“图片××被添加到数据库中”,但是到库里一查询,什么都没有。
SQL
语句被提交,但是数据库里面没有即时的显示,估计是缓冲区的作用,它把我的
SQL
语句缓存起来而不是立即提交给数据库。后来我在
textpad
里面单独执行这段代码,发现不用“
COMMIT
”
SQL
语句就立即被提交了。这个地方还没有弄清楚,以后需要继续研究。
功能上做一点小改进,用户提交了照片以后,浏览了一下觉得不满意,只要还没有最终提交数据,当然允许他重新上传一个照片。我们只需要在
<img src=””>
下面提供一个“重新提交”的链接就可以了。这个链接指向一个
AlterImageAction
,它的功能就是清除
session
中用户已经上传照片的标记,并把刚才保存到临时文件夹中的照片删掉,等待用户重新上传。这部分代码如下:
public ActionForward execute(ActionMapping mapping,
ActionForm form,HttpServletRequest request,
HttpServletResponse response)
throws IOException,ServletException{
HttpSession session = request.getSession();
//1.
从临时文件夹中删除图片
String filename = (String)session.getAttribute("filename");
String path = session.getServletContext().getRealPath("/")
+ "temp\\" + filename;
File file = new File(path);
if(file.exists()){
file.delete();
logger.info("
文件
" + path + "
已经被删除
");
}
//2.
从
session
中清除上传图片的标记
session.removeAttribute("imageuploaded");
session.removeAttribute("filename");
return mapping.findForward("next");
}
提交和保存到此功德圆满。下次用户想要查询自己的信息的时候,因为临时文件夹中已经没有用户照片,需要从数据库中读取。用一个
ShowImageAction
来实现这个功能:
public ActionForward execute(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response)
throws IOException,ServletException{
//
需要的情况下设置数据源
if (!DbManager.hasSetDataSource()) {
javax.sql.DataSource dataSource;
try {
dataSource = getDataSource(request);
DbManager.setDataSource(dataSource);
} catch (Exception e) {
logger.error(e.getMessage());
mapping.findForward("error");
}
}
String photo_no = request.getParameter("photo_no");
Connection conn = null;
Statement stmt = null;
//
获取连接
try {
conn = DbManager.getConnection();
logger.info("showimage.jsp
从
DbManager
数据库连接池获取一个连接。
");
} catch (SQLException e) {
//
如果没有能够从
DbManager
获取连接,此次查询操作失败
logger.error(" showimage.jsp
不能获取数据库连接,无法读取图片!
");
}
try {
//
准备语句执行对象
stmt = conn.createStatement();
String sql = " SELECT hr01_photo FROM hr01 WHERE hr01_id='" + photo_no + "'";
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
InputStream in = rs.getBinaryStream("hr01_photo");
int bytesRead = 0;
byte[] buffer = new byte[8192];
response.setContentType("image/jpeg");
response.setContentLength(in.available());
OutputStream outs = response.getOutputStream();
while ((bytesRead = in.read(buffer, 0, 8192)) != -1) {
outs.write(buffer, 0, bytesRead);
}
outs.flush();
in.close();
rs.close();
} else {
rs.close();
response.sendRedirect("error.jsp");
}
}catch(SQLException e){
}finally {
try{
stmt.close();
conn.close();
}catch(SQLException ex){
}
}
return null;
}
以前一直不清楚
execute
方法中的
response
参数的用法,因为处理完以后总要
redirect
到另外一个页面,所以用的最多的就是把数据保存在
session
中,在另一个页面里再取出来用。这次纯粹是试验性地在
response
中写入
image/jpeg
内容,然后返回一个
null
值。最后的执行结果跟我预期的一样,在浏览器中直接显示从数据库中读出的图片。那么接下来就很好做了,只需要在
JSP
页面中设置一个
<image>
标签,指向这个
Action
就可以,当然,在这之前需要在
struts-config.xml
中先部署这个
Action
:
<action path="/ShowImage"
type="software.action.ShowImageAction"></action>
然后在
JSP
页面中引用这个
Action
来显示图像
:
<img src="/tibet/ShowImage.do?photo_no=666542">
< p>