人事信息管理系統中,需要管理用戶的個人身份照片。通常這種格式的照片只有幾
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>