前言
實(shí)現(xiàn)一個(gè)簡(jiǎn)易的頁(yè)面編輯器是大家在學(xué)習(xí)jsp的時(shí)候經(jīng)常會(huì)遇到的一個(gè)需求,發(fā)現(xiàn)網(wǎng)上這方便的資料不多,所以想著自己總結(jié)下,本文詳細(xì)介紹了JSP簡(jiǎn)易頁(yè)面編輯器的實(shí)現(xiàn)方法,下面話不多說,來一起看看詳細(xì)的介紹:
需求
提供一頁(yè)面,放置“幫助”、“版權(quán)”文字內(nèi)容,特點(diǎn):靜態(tài)頁(yè)面,無(wú)須讀數(shù)據(jù)庫(kù),只是應(yīng)付字眼上頻繁的修改;沒有復(fù)雜的交互,無(wú)須 JavaScript;沒有圖片,不需要文件上傳。
給出的方案:提供一頁(yè)面和簡(jiǎn)易的后臺(tái)管理,功能單一,只是編輯頁(yè)面(只是修改字體、大小、粗體、斜體等的功能)。
實(shí)現(xiàn)思路:純 JSP 展示,管理界面用 HTTP Basic 登入,通過一個(gè) js 寫成 HTML 編輯器修改頁(yè)面內(nèi)容。直接修改服務(wù)器磁盤文件。
界面如下,右圖是后臺(tái)編輯。
值得一提的是,Tomcat 7 下 JSP 默認(rèn)的 Java 語(yǔ)法仍舊是 1.6 的。在 JSP 里面嵌入 Java 1.7 特性的代碼會(huì)拋出“Resource specification not allowed here for source level below 1.7”的異常。于是需要修改 Tomcat/conf/web.xml 里面的配置文件,找到 <servlet> 節(jié)點(diǎn)( <servlet-name>jsp</servlet-name>
的才是),加入下面最后兩個(gè) init-param 節(jié)點(diǎn)部分。注意是 <servlet-name>jsp</servlet-name>
節(jié)點(diǎn)才可以,不是 default 節(jié)點(diǎn)(很相似)。
<servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>fork</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>xpoweredBy</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>compilerSourceVM</param-name> <param-value>1.7</param-value> </init-param> <init-param> <param-name>compilerTargetVM</param-name> <param-value>1.7</param-value> </init-param> <load-on-startup>3</load-on-startup> </servlet>
訪問的 jsp 其實(shí)只有兩個(gè) /index.jsp 和 /admin/index.jsp,分別是靜態(tài)頁(yè)面和后臺(tái)編輯頁(yè)面。/admin/action.jsp 用于接收保存的 action,數(shù)據(jù)由表單 POST 過來。functions.jsp 就是全部的業(yè)務(wù)邏輯代碼,通過 %@include file="functions.jsp"%
,它不能單獨(dú)給外界 url 訪問。
我們先看看 /index.jsp。
<%@page pageEncoding="UTF-8"%> <html> <head> <title>幫助</title> <meta charset="utf-8" /> <!--寬度 320px --> <meta name="viewport" content="width=320,user-scalable=0,initial-scale=1.0,maximum-scale=1.0, minimum-scale=1.0" /> <style> html { font-size: 15px; } body { padding: 0; margin: 0 auto; max-width: 600px; -webkit-font-smoothing: antialiased; font-family: "Microsoft YaHei", "ff-tisa-web-pro-1", "ff-tisa-web-pro-2", "Lucida Grande", "Hiragino Sans GB", "Hiragino Sans GB W3", Arial; background-color: #ebebeb; } h1 { text-align: center; font-size: 1.5rem; letter-spacing: 2px; color: #864c24; border-bottom: #e0c494 solid 1px; padding: 2% 0; } h2 { font-size: 1rem; letter-spacing: 1px; color: #4c4c4c; padding-bottom:0; margin: 0; } p { text-align: justify; font-size: 1rem; color: #818181; margin: 1% 0; margin-top:0; } ol { padding: 0; margin: 0; } ol { } ol>li>:first-child { /* Make Firefox put the list marker inside */ /* https://bugzilla.mozilla.org/show_bug.cgi?id=36854 "if list-style-position is inside, bullet takes own line" */ display: inline; } ol>li>:first-child:after { /* Add the margin that was lost w/ display: inline */ /* Firefox 10 displays this as block */ /* Safari 5.1.2 and Chrome 17.0.963.56 don't */ content: ""; display: block; } li { padding: 5% 2%; list-style-position: inside; border-bottom: 1px solid #dddddb; } .text { color: #a8a8a8; font-size: 1rem; font-weight: bold; padding: 2%; } </style> </head> <body> <!-- Editable AREA|START --> <h1>幫助</h1> <div class="text">常見問題</div> <ol> <li> <h2>Power TV的資費(fèi)是怎樣收取的?</h2> <p>12元Power TV手機(jī)電視包月,產(chǎn)品代碼88888888,12元/月;省內(nèi)用戶省內(nèi)使用配送3G/月定向流量,流量?jī)H用于使用Power TV,超過定向流量部分按標(biāo)準(zhǔn)資費(fèi)收取; </p> </li> <li> <h2>Power TV的資費(fèi)是怎樣收取的?</h2> <p>12元Power TV手機(jī)電視包月,產(chǎn)品代碼88888888,12元/月;省內(nèi)用戶省內(nèi)使用配送3G/月定向流量,流量?jī)H用于使用Power TV,超過定向流量部分按標(biāo)準(zhǔn)資費(fèi)收取; </p> </li> <li> <h2>Power TV的資費(fèi)是怎樣收取的?</h2> <p>12元Power TV手機(jī)電視包月,產(chǎn)品代碼88888888,12元/月;省內(nèi)用戶省內(nèi)使用配送3G/月定向流量,流量?jī)H用于使用Power TV,超過定向流量部分按標(biāo)準(zhǔn)資費(fèi)收取; </p> </li> <li> <h2>Power TV的資費(fèi)是怎樣收取的?</h2> <p>12元Power TV手機(jī)電視包月,產(chǎn)品代碼88888888,12元/月;省內(nèi)用戶省內(nèi)使用配送3G/月定向流量,流量?jī)H用于使用Power TV,超過定向流量部分按標(biāo)準(zhǔn)資費(fèi)收取; </p> </li> <li> <h2>Power TV的資費(fèi)是怎樣收取的?</h2> <p>12元Power TV手機(jī)電視包月,產(chǎn)品代碼88888888,12元/月;省內(nèi)用戶省內(nèi)使用配送3G/月定向流量,流量?jī)H用于使用Power TV,超過定向流量部分按標(biāo)準(zhǔn)資費(fèi)收取; </p> </li> <li> <h2>Power TV的資費(fèi)是怎樣收取的?</h2> <p>12元Power TV手機(jī)電視包月,產(chǎn)品代碼88888888,12元/月;省內(nèi)用戶省內(nèi)使用配送3G/月定向流量,流量?jī)H用于使用Power TV,超過定向流量部分按標(biāo)準(zhǔn)資費(fèi)收取; </p> </li> <li> <h2>Power TV的資費(fèi)是怎樣收取的?</h2> <p>12元Power TV手機(jī)電視包月,產(chǎn)品代碼88888888,12元/月;省內(nèi)用戶省內(nèi)使用配送3G/月定向流量,流量?jī)H用于使用Power TV,超過定向流量部分按標(biāo)準(zhǔn)資費(fèi)收取; </p> </li> </ol> <!-- Editable AREA|END --> </body> </html>
這份 JSP 與一般 JSP 并無(wú)特異,只不過大家有沒有留意到兩段注釋: <!-- Editable AREA|START -->
和 <!-- Editable AREA|END -->
——這就是我們約定的“可編輯”范圍。當(dāng)然,使用自定義的 HTML Tag 也可以,只要定義了一個(gè)范圍即可。一份網(wǎng)頁(yè),無(wú)非是 HTML。對(duì)于其中欲編輯的東西,我們定義一個(gè)范圍指明哪些地方需要編輯,就可以了。至于為什么不讓全部的頁(yè)面可以編輯?是因?yàn)槲覀儾幌胗脩魧?duì)頁(yè)面其它部分進(jìn)行編輯,萬(wàn)一修改了的關(guān)鍵地方造成了錯(cuò)誤,那可不好。
好了,怎么讓這個(gè) /index.jsp 編輯呢?就是利用 Java 讀取磁盤的方法來做的。在這個(gè)之前,得先登錄到 /admin/index.jsp。這里我們通過 HTTP Basic Authorization 來做用戶認(rèn)證,無(wú)須數(shù)據(jù)庫(kù)。如果需要修改 賬號(hào)密碼,打開 admin/functions.jsp,編輯頭部分即可:
<%! public static final String userid = "admin", pwd = "123123"; ....%>
不過筆者調(diào)試 HTTP Basic Authorization 遇到了個(gè)小問題,就是瀏覽器彈出的對(duì)話框,不知怎么修改其中的提示文字,試過幾種方法,要么不顯示,要么亂碼。如果知道的童鞋還請(qǐng)告知一二!
action.jsp 也要作認(rèn)證的限制,不然等于是個(gè)漏洞可以讓別人 POST 任何數(shù)據(jù)到頁(yè)面。
<%@page pageEncoding="UTF-8"%> <%@include file="functions.jsp"%> <% if (checkAuth(request.getHeader("Authorization"), userid, pwd)) { request.setCharacterEncoding("utf-8"); if (request.getMethod().equalsIgnoreCase("POST")) { String contentBody = request.getParameter("contentBody"), path = Mappath(getEditJSP(request)); System.out.println("path:::" + path); save_jsp_fileContent(path, contentBody); out.println("<script>alert('修改成功!');window.location = document.referrer;</script>"); } else { out.println("method error"); } } else { %> <html> <body> 非法登錄! </body> </html> <% } %>
修改下頁(yè)面,點(diǎn)擊保存就可以修改頁(yè)面了。
至于 HTML 如何編輯?這個(gè)答案想必大家都清楚,使用 HTML 可視化編輯器即可,在線的哦,而不是什么 Dreamweaver、FrontPage、VS Web 之類啦。老人們用過的就是有 FCKEditror 呀、TinyMCE Editor,近幾年好像喜歡用國(guó)產(chǎn)了,我就不知道了。現(xiàn)在這個(gè)用的是我自己寫,功能比較單一的。
核心邏輯是通過下面的代碼搞定的。
<%@page pageEncoding="UTF-8" import="sun.misc.BASE64Decoder, java.io.*"%> <%! public static final String userid = "admin", pwd = "86006966"; // 檢查 HTTP Basic 認(rèn)證 /** * 是否空字符串 * * @param str * @return */ public static boolean isEmptyString(String str) { return str == null || str.trim().isEmpty(); } /** * 是否不合法的數(shù)組 * * @param arr * @return */ public static boolean isBadArray(String[] arr) { return arr == null || arr.length != 2; } /** * * @param authorization * 認(rèn)證后每次HTTP請(qǐng)求都會(huì)附帶上 Authorization 頭信息 * @param username * 用戶名 * @param password * 密碼 * @return true = 認(rèn)證成功/ false = 需要認(rèn)證 */ public static boolean checkAuth(String authorization, String username, String password) { if (isEmptyString(authorization)) return false; String[] basicArray = authorization.split("//s+"); if (isBadArray(basicArray)) return false; String idpass = null; try { byte[] buf = new BASE64Decoder().decodeBuffer(basicArray[1]); idpass = new String(buf, "UTF-8"); } catch (IOException e) { e.printStackTrace(); return false; } if (isEmptyString(idpass)) return false; String[] idpassArray = idpass.split(":"); if (isBadArray(idpassArray)) return false; return username.equalsIgnoreCase(idpassArray[0]) && password.equalsIgnoreCase(idpassArray[1]); } /** * 可編輯標(biāo)識(shí)開始 */ private final static String startToken = "<!-- Editable AREA|START -->"; /** * 可編輯標(biāo)識(shí)結(jié)束 */ private final static String endToken = "<!-- Editable AREA|END -->"; /** * 根據(jù) 頁(yè)面中可編輯區(qū)域之標(biāo)識(shí),取出來。 * * @param fullFilePath * 完整的 jsp 文件路徑 * @return 可編輯內(nèi)容 * @throws IOException */ public static String read_jsp_fileContent(String fullFilePath) throws IOException { String jsp_fileContent = readFile(fullFilePath); int start = jsp_fileContent.indexOf(startToken), end = jsp_fileContent.indexOf(endToken); try { jsp_fileContent = jsp_fileContent.substring(start + startToken.length(), end); } catch (StringIndexOutOfBoundsException e) { jsp_fileContent = null; String msg = "頁(yè)面文件" + fullFilePath + "中沒有標(biāo)記可編輯區(qū)域之標(biāo)識(shí)。請(qǐng)參考:" + startToken + "/" + endToken; throw new IOException(msg); } return jsp_fileContent; } /** * 請(qǐng)求附帶文件參數(shù),將其轉(zhuǎn)換真實(shí)的磁盤文件路徑 * * @param rawFullFilePath * URL 提交過來的磁盤文件路徑,可能未包含文件名或加了很多 url 參數(shù) * @return 完整的磁盤文件路徑 */ static String getFullPathByRequestUrl(String rawFullFilePath) { if (rawFullFilePath.indexOf(".jsp") == -1) rawFullFilePath += "/index.jsp"; // 加上 擴(kuò)展名 if (rawFullFilePath.indexOf("?") != -1) // 去掉 url 參數(shù) rawFullFilePath = rawFullFilePath.replaceAll("//?.*$", ""); return rawFullFilePath; } /** * 保存要修改的頁(yè)面 * * @param rawFullFilePath * 真實(shí)的磁盤文件路徑 * @param newContent * 新提交的內(nèi)容 * @throws IOException */ public static void save_jsp_fileContent(String rawFullFilePath, String newContent) throws IOException { String fullFilePath = getFullPathByRequestUrl(rawFullFilePath); // 真實(shí)的磁盤文件路徑 String jsp_fileContent = readFile(fullFilePath), toDel_fileContent = read_jsp_fileContent(fullFilePath);// 讀取舊內(nèi)容 //System.out.println(jsp_fileContent); //System.out.println(toDel_fileContent); if (toDel_fileContent != null) { jsp_fileContent = jsp_fileContent.replace(toDel_fileContent, newContent); save2file(fullFilePath, jsp_fileContent); // 保存新內(nèi)容 } else { throw new IOException("頁(yè)面文件中沒有標(biāo)記可編輯區(qū)域之標(biāo)識(shí)。請(qǐng)參考: startToken/endTpoken"); } } /** * 讀取文件 * * @param filename * @return * @throws IOException */ public static String readFile(String filename) throws IOException { File file = new File(filename); if (!file.exists()) throw new FileNotFoundException(filename + " 不存在!"); try (FileInputStream is = new FileInputStream(file);) { String line = null; StringBuilder result = new StringBuilder(); try (InputStreamReader isReader = new InputStreamReader(is, "UTF-8"); BufferedReader reader = new BufferedReader(isReader);) { while ((line = reader.readLine()) != null) { result.append(line); result.append('/n'); } } catch (IOException e) { System.err.println(e); } return result.toString(); } catch (IOException e) { System.err.println("讀取文件流出錯(cuò)!" + filename); throw e; } } /** * 寫文件不能用 FileWriter,原因是會(huì)中文亂碼 * * @param filename * @param content * @throws IOException */ public static void save2file(String filename, String content) throws IOException { try (FileOutputStream out = new FileOutputStream(filename); // OutputStreramWriter將輸出的字符流轉(zhuǎn)化為字節(jié)流輸出(字符流已帶緩沖) OutputStreamWriter writer = new OutputStreamWriter(out, "UTF8");) { writer.write(content); } catch (IOException e) { System.err.println("寫入文件" + filename + "失敗"); throw e; } } /** * 輸入一個(gè)相對(duì)地址,補(bǔ)充成為絕對(duì)地址 相對(duì)地址轉(zhuǎn)換為絕對(duì)地址,并轉(zhuǎn)換斜杠 * * @param relativePath * 相對(duì)地址 * @return 絕對(duì)地址 */ public String Mappath(String relativePath) { String absoluteAddress = getServletContext().getRealPath(relativePath); // 絕對(duì)地址 if (absoluteAddress != null) absoluteAddress = absoluteAddress.replace('//', '/'); return absoluteAddress; } public String getEditJSP(HttpServletRequest request) { String uri = request.getRequestURI().replaceAll("admin///w+", "index"); uri = uri.replace(request.getContextPath(), ""); return uri; } %>
用戶憑賬號(hào)密碼登入簡(jiǎn)易的后臺(tái),通過可視化編輯器即可修改頁(yè)面內(nèi)容,立刻修改,立刻產(chǎn)生效果,簡(jiǎn)單快捷——把頁(yè)面開放出來允許自主編輯這樣會(huì)提高效率——減少來回修改的次數(shù)。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對(duì)VeVb武林網(wǎng)的支持。
|
新聞熱點(diǎn)
疑難解答
圖片精選