標籤:ssh 系統架構 流程式控制制 架構 架構
一、項目總體介紹
前一段時間的工作中,筆者大概用了兩三個月開發了一個Web管理資訊系統,使用的框架組為Struts2.3.1+Spring3.0+Hibernate3+Jquery EasyUI1.3.5,系統商務邏輯並不複雜,完成資料的採集(以問卷的形式)、計算處理和形成報告發布。EasyUI是一個很優秀的JS UI架構,使用簡單方便,效果也還可以,是UI效果和頻寬速度之間的一個折中之選。系統中還有新聞發布模組,用到了富文字編輯器,在比較了很多外掛程式之後,選擇了kindeditor,原因很簡單,因為它有網上現成的和EasyUI結合的例子。另外生產圖表時用到了JFreechart外掛程式。系統開發運行環境為windows7下Myeclipse8.5+JDK1.6+Tomcat6.0+SqlServer2008R2。
二、關於EasyUI
EasyUI很方便使用,一般只需要在一個頁面中匯入一些檔案之後,其他的頁面都是嵌在當前頁買中。EasyUI提供了很多組件,如layout、panel、tabs、datagrid等,只需要初始化這些組件,並提供相應的資料即可。每個組件都有兩種方式聲明或者建立,即使用標記元素的形式和使用js的形式。具體引用參見項目源碼或者EasyUI官網提供的API。
EasyUI官網:http://www.jeasyui.com/index.php
EasyUI官方API:http://www.jeasyui.com/documentation/index.php
三、關於系統架構
系統應用了常規的“三層架構”,即視圖展示層、商務邏輯層和資料處理層。其中EasyUI+Struts2的Action負責頁面展示、資料抓取和推送、視圖流程式控制制,Service層負責商務邏輯的實現,Dao層與Hibernate結合,負責資料的處理,與資料庫打交道。而Spring則扮演一個管家的角色,其功能有IOC容器、依賴注入、整合其他外掛程式等,負責將整個系統融合起來,合理調度。
下面對Action、Service、Dao的設計簡單小結一下。其基本的流程是Action調用Service,Service調用Dao,且各層都使用Spring的依賴注入來執行個體化各層屬性。
1、 Action層
Action負責接收前台提交的請求,擷取請求資料,然後調用Service完成業務處理,然後將請求轉寄到下一流程(可能返回到某個頁面或者另一個Action),並將相應的資料隨之一併轉寄。
本項目中首先寫了一個BaseAction,繼承了Struts2的ActionSupport。因為EasyUI接收的資料格式都是json格式,所以這裡用到了一個json外掛程式fastjson並且提供了一個writeJSON(Object o)方法,用來在需要的時候,將資料寫到前台頁面,供EasyUI的組件讀取。
其他的Action只需繼承此Action即可。項目中擷取請求參數時採用了模型驅動的方式,即Action實現ModelDriven介面,該介面接受一個泛型T,這裡T一般是一個資料模型類,這樣前台就可以直接寫某個類的屬性,後台便能抓取到它們並自動封裝到一個對象中。
項目中異常處理在Action層中完成,Service和Dao只負責捕捉,並往上一層拋,在Action才處理。這也並不是絕對的,看具體的要求。
2、 Service
Service層完成具體的商務邏輯。如根據不同的條件調用不同的Dao等。
3、Dao
Dao結合Hibernate與具體的資料庫打交道,即完成資料的持久化,或者載入持久化資料對象。這裡Dao的設計採用了泛型和介面的結合,首先寫了一個帶泛型的公用介面BaseDaoI<T>,介面中定義了一些常用的,通用的方法,這些方法接受的或者返回的類型都是泛型。然後實現了該介面,其泛型資訊依然保留。這樣一來,具體的和某一表相關聯的Dao,只需在編寫介面時繼承該通用介面,並將泛型替換為實體類型,然後編寫實現時去實現剛才的介面,並繼承通用介面的實現,即可。這樣的好處是,通用的功能,都在公用介面中定義了,而且利用泛型,完成了類型的良好過度和檢查,而不必用Object來強制轉換。
舉個例子,以使用者實體相關操作為例。
(1)、公用介面
import java.io.Serializable;import java.util.List;import java.util.Map;/** * @author Administrator * * @param <T> */public interface BaseDaoI<T> {/** * 儲存實體 * * @param o * @throws Exception */void save(T o) throws Exception;/** * 刪除實體 * * @param o * @throws Exception */void delete(T o) throws Exception;/** * 以sql形式刪除資料 * @param sql 刪除sql * @throws Exception */void delete(String sql) throws Exception;/** * 更新實體 * * @param o * @throws Exception */void update(T o) throws Exception;/** * 儲存或更新 * * @param o * @throws Exception */void saveOrUpdate(T o) throws Exception;/** * @param c * @param id * @return * @throws Exception */T get(Class<T> c,Serializable id)throws Exception;/** * 不帶參數的hql查詢 * * @param hql * 完整的hql語句 * @return 結果集合 * @throws Exception */List<T> get(String hql) throws Exception;/** * 帶參數的hql查詢,該方式使用問號預留位置,並按順序傳遞參數(不建議使用) * * @param hql * 帶參數的hql語句 * @param params * 順序良好的參數列表 * @return 結果集合 * @throws Exception */List<T> get(String hql, Object[] params) throws Exception;/** * 帶參數的hql查詢,該方式使用【:參數名】預留位置,並使用Map傳遞參數(建議使用) * * @param hql * 帶參數的hql語句 * @param params * Map參數列表 * @return 結果集合 * @throws Exception */List<T> get(String hql, Map<String, Object> params) throws Exception;/** * 帶參數的分頁查詢語句,可查詢指定條數的記錄 * * @param hql * 查詢hql * @param params * 參數列表 * @param starRow * 查詢起始行(Hibernate行數從0開始) * @param size * 查詢數量 * @return 結果集合 * @throws Exception */List<T> get(String hql, Map<String, Object> params, int starRow, int size)throws Exception;/** * 不帶參數的分頁查詢語句,可查詢指定條數的記錄 * * @param hql * 查詢hql * @param starRow * 查詢起始行(Hibernate行數從0開始) * @param size * 查詢數量 * @return 結果集合 * @throws Exception */List<T> get(String hql, int starRow, int size) throws Exception;/** * 不帶參數查詢記錄總數 * @param hql 查詢hql(例如"select count(*) from XXXX") * @return 記錄總數 * @throws Exception */Long getTotalAmount(String hql) throws Exception;/** * 帶參數查詢記錄總數 * @param hql 查詢hql (例如"select count(*) from XXXX where ...") * @param params 參數列表 * @return 記錄總數 * @throws Exception */Long getTotalAmount(String hql, Map<String, Object> params) throws Exception;/** * 執行特定的hql語句 * @param hql * @return * @throws Exception */int executeHql(String hql)throws Exception;}
(2)、公用介面的實現
import java.io.Serializable;import java.util.List;import java.util.Map;import org.hibernate.Query;import org.hibernate.SQLQuery;import org.hibernate.Session;import org.springframework.orm.hibernate3.support.HibernateDaoSupport;import com.pmi.dao.BaseDaoI;public class BaseDaoImpl<T> extends HibernateDaoSupport implements BaseDaoI<T> {public void save(T o) throws Exception {getHibernateTemplate().save(o);}public List<T> get(String hql) throws Exception {List<T> list = getHibernateTemplate().find(hql);return list;}public List<T> get(String hql, Object[] params) throws Exception {Session session = getHibernateTemplate().getSessionFactory().getCurrentSession();Query q = session.createQuery(hql);if (params != null && params.length > 0) {for (int i = 0; i < params.length; i++) {q.setParameter(i, params[i]);}}List<T> list = q.list();return list;}public List<T> get(String hql, Map<String, Object> params) throws Exception {Session session = getHibernateTemplate().getSessionFactory().getCurrentSession();Query q = session.createQuery(hql);for (String key : params.keySet()) {q.setParameter(key, params.get(key));}List<T> list = q.list();return list;}public void delete(T o) throws Exception {getHibernateTemplate().delete(o);}public void saveOrUpdate(T o) throws Exception {getHibernateTemplate().saveOrUpdate(o);}public void update(T o) throws Exception {getHibernateTemplate().update(o);}public List<T> get(String hql, Map<String, Object> params, int starRow,int size) throws Exception {Session session = getHibernateTemplate().getSessionFactory().getCurrentSession();Query q = session.createQuery(hql);for (String key : params.keySet()) {q.setParameter(key, params.get(key));}q.setFirstResult(starRow);q.setMaxResults(size);List<T> list = q.list();return list;}public List<T> get(String hql, int starRow, int size) throws Exception {Session session = getHibernateTemplate().getSessionFactory().getCurrentSession();Query q = session.createQuery(hql);q.setFirstResult(starRow);q.setMaxResults(size);List<T> list = q.list();return list;}public Long getTotalAmount(String hql) throws Exception {Session session = getHibernateTemplate().getSessionFactory().getCurrentSession();Query q = session.createQuery(hql);Long count = (Long) q.uniqueResult();return count;}public Long getTotalAmount(String hql, Map<String, Object> params)throws Exception {Session session = getHibernateTemplate().getSessionFactory().getCurrentSession();Query q = session.createQuery(hql);for (String key : params.keySet()) {q.setParameter(key, params.get(key));}Long count = (Long) q.uniqueResult();return count;}public void delete(String sql) throws Exception {Session session = getHibernateTemplate().getSessionFactory().getCurrentSession();SQLQuery q = session.createSQLQuery(sql);q.executeUpdate();}public T get(Class<T> c, Serializable id) throws Exception {return getHibernateTemplate().get(c, id);}public int executeHql(String hql) throws Exception {Session session = getHibernateTemplate().getSessionFactory().getCurrentSession();Query q = session.createQuery(hql);int i = q.executeUpdate();return i;}}
(3)、User實體介面
import com.pmi.model.database.TbYhxx;public interface UserDaoI extends BaseDaoI<TbYhxx> {}
(4)、User實體介面實現
</pre><pre name="code" class="java">import com.pmi.dao.UserDaoI;import com.pmi.model.database.TbYhxx;public class UserDaoImpl extends BaseDaoImpl<TbYhxx> implements UserDaoI {……(專屬的方法)}
4、許可權控制
設計思路:項目採用基於角色的許可權控制,即使用者的許可權是以角色來區分的。使用者權限包括菜單資源許可權、增刪改查操作許可權。使用者登入時,系統根據使用者的角色,載入使用者所具有的菜單資源和操作許可權,匹配的操作才能被完成。
菜單資源許可權的實現:這個邏輯比較簡單,為角色指派的菜單存放在資料表中,使用者登入時,根據角色載入功能表項目即可。
操作許可權的實現:操作許可權是指具體的增刪改查操作,比如查看新聞、添加新聞、刪除使用者等操作。這裡採用Struts2的攔截器實現操作許可權控制,針對Action層過濾。攔截到請求的action和方法之後,首先判斷需不需要進行許可權檢查,因為不是所有的操作都需要許可權控制,比如查看新聞。預設所有的aciton請求都需要檢查,如果有例外,通過struts.xml中通過ignoreActions配置。檢查流程為,先檢查是否登入,如果未登入,阻止請求,並給出提示,若已登入,則檢查當前請求的操作許可權與使用者所具有的是否匹配,匹配則允許存取,否則阻止請求,給出提示。
5、實體模型
一般在SSH應用中,一個資料表對應一個實體模型,這個Model既完成持久化的相關工作,也是往視圖層傳遞的資料載體,這樣比較簡單,但同時也帶來一定的弊端,比如前台頁面中,除了需要該Model對應的資料表中的欄位之外,還需要一些額外的參數(分頁參數、自訂其他參數)作為屬性,又比如在使用Hibernate映射時,一般外鍵會映射成持有外鍵表對應Model的對象,而在前台頁面,可能並不需要這個對象的所有資訊而只是需要一些特定的屬性值,這時如果都寫在一個Model時,在完成持久化時比較不清晰,對於開發維護人員來說可能會造成邏輯混亂,這裡採用的是雙Model機制,即一張表對應兩個Model實體,一個是完全的資料表欄位作為屬性,另一個則是與之對應的專門與視圖層關聯的Model複製體,而這個複製體可能有更多的視圖層需要的資訊,同時在Service層完成這兩個Model的資料複製,利用Spring的BeanUtils類提供的靜態copyProperties()方法。以使用者資訊表為例,使用者資訊表有如下欄位
Model1:Hibernate反向映射自動產生的
public class TbYhxx implements java.io.Serializable {// Fieldsprivate Integer yhId;private TbDyxx tbDyxx;private TbYbxx tbYbxx;private TbJsxx tbJsxx;private String yhName;private String yhPwd;private String yhXm;private String phone;private String email;private Integer yhState;private Integer extend1;private String extend2;private String extend3;private Set tbKzwjtbs = new HashSet(0);private Set tbWjtbs = new HashSet(0);private Set tbGgxxes = new HashSet(0);private Set tbFxbgs = new HashSet(0);private Set tbYbkzs = new HashSet(0);private Set tbTsxxes = new HashSet(0);private Set tbTjxxes = new HashSet(0);private Set tbZhzses = new HashSet(0);private Set tbXwggs = new HashSet(0);private Set tbTjjgs = new HashSet(0);......setters and getters}
Model2:與視圖層關聯的,前台頁面相關聯的
public class User extends BaseModel {// Fields// datagrid需要的欄位名private Integer yhId;private Integer dyId;private String dyName;private Integer qyId;private String qyName;private Integer jsId;private String jsName;private String yhName;private String yhPwd;//使用者修改密碼資訊時使用private String newPwd;private String yhXm;private String phone;private String email;private Integer yhState;private Integer extend1;private String extend2;private String extend3;........setters and getters}
從例子中可以看出,這兩個Model還是有一定區別的,且是有實際意義的。
6、日誌記錄
日誌資訊可以檢測系統的日常運營資訊,如使用者操作和異常或者錯誤情境,一個設計良好的管理資訊系統應該具有日誌記錄功能。本系統採用的是常用的log4j外掛程式作為日誌工具。log4j使用相對簡單,匯入jar包後,增加一個設定檔即可。本項目的配置見下文。
log4j.properties:
log4j.rootCategory=INFO, stdout , Rlog4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern=[PMI] %p [%d] %C.%M(%L) | %m%nlog4j.appender.R=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.R.File=D\:/pmi_log/pmi_daily.loglog4j.appender.R.layout=org.apache.log4j.PatternLayoutlog4j.appender.R.layout.ConversionPattern=[PMI] %p \: %m - %c | %t | %d %n