主體架構
主體採用Struts2 Spring3 Hibernate3.3架構。
Spring和Hibernate都採用Annotation方式省去了大量Bean的配置和ORM映射文件。
界面展示用JSP結合 Struts2 tags的方式。
主要模塊的頁面採用form,和list兩個頁面,前者負責新增、編輯、查看,後者負責數據列表展現。
權限管理
平臺採用基於角色的訪問控制RBAC模型,通過給角色授予訪問某資源(模塊,URL),再將角色分配給用戶,使用戶獲得訪問某些資源的權限。
對應的領域模型有“用戶”User,“角色”Role,“模塊”Module,“部門”Department。
權限採用Spring Security。
郵件發送組件
系統已在com.epro.crm.util.message.EmailHelper工具類中集成了JavaMail, 並在EmailService內置在了CommonBaseService中。如果業務需要發送郵件,只需在將郵件內容寫在EmailServiceImpl中,然後在Service中直接調用即可。系統將異步調用EmailHelper.sendEmail(…)方法,將郵件發出。
參考:com.epro.crm.service.system.impl.UserServiceImpl.add(User)
註冊用戶時,系統將註冊成功信息,異步的發送至用戶的註冊郵箱中。
protected void sendEmail(final List<String> recipientAccounts,final String message, final String subject){
//判斷 系統設置 (是否已開啓 發送郵件模塊)
if(Constants.YES.equals(Configurations.SEND_EMAIL)){
//異步發送
taskExecutor.execute(new Runnable() {
public void run() {
EmailHelper.sendEmail(recipientAccounts,message, subject);
}
});
} else {
log.info("系統郵件發送功能未開啓,無法發送短信");
}
}
界面表單驗證
使用了jQuery validate 表單驗證框架
參考 /page/user/user_form.jsp
jQuery(function(){
$("#submitForm").validate({
rules: {
name: "required",
username: {
required: true,
minlength: 4
//remote:"UserAction!validateUsername"
},
phone:"digits",
birthDay: "dateISO",
email: {
required: true,
email: true
}
},
success: function(label) {
label.text('').addClass("success");
},
messages: {
name: "請輸入用戶的姓名",
phone:"電話號碼必須是數字",
username: {
required: "請輸入該用戶的登錄名(登錄賬號)",
minlength: "登錄名最短4位"
},
birthDay: "請輸入合法日期格式 如:2011-3-11",
email: "請輸入合法email格式"
}
});
});
增刪改查封裝
普通的數據訪問對象(Dao)接口應繼承於com.epro.crm.dao.base.CommonDaoInterface<T>,其默認已有增刪改查的接口。其實現類應繼承於com.epro.crm.dao.base.CommonBaseDao<T>,默認已有增刪改查等基本操作的實現,並可通過HibernateTemplate擴展其他的操作。如果需要按自定義的條件查詢,可以重寫CommonBaseDao類的String appendConditionHQL(T t)方法。
參考:com.epro.crm.dao.system.impl.UserDaoImpl
增加
public void add(T t) {
String stateStr = null;
try {
if (t instanceof DeletedByLogic ) {
stateStr = BeanUtils.getProperty(t, "state");
if(stateStr == null){
BeanUtils.setProperty(t, "state",Constants.STATE_VALID);
}
hibernateTemplate.save(t);
} else if (t instanceof DeletedByPhysics) {
hibernateTemplate.save(t);
} else {
hibernateTemplate.save(t);
}
} catch (Exception e) {
log.error(e.getMessage(),e);
throw new CrmSystemException("新增數據出錯",e);
}
}
刪除public void delete(T t) {
try {
String idStr = BeanUtils.getProperty(t, "id");
long id = Long.parseLong(idStr);
Object delObject = hibernateTemplate.get(t.getClass(), id);
if (t instanceof DeletedByLogic) {
BeanUtils.setProperty(delObject, "state",
Constants.STATE_DELETED);
} else if (t instanceof DeletedByPhysics) {
hibernateTemplate.delete(delObject);
} else {
throw new CrmSystemException("實體類 " + t.getClass().getName()
+ " 既不是物理刪除,也不是邏輯刪除");
}
} catch (Exception e) {
log.error(e.getMessage(),e);
throw new CrmSystemException("刪除對象" + t + "時,系統異常");
}
}
查詢
public List<T> getList(T t, final int offset, final int length) {
final String hql = getListHQL(t);
List<T> list = getHibernateTemplate().executeFind(
new HibernateCallback() {
public Object doInHibernate(Session session)
throws HibernateException, SQLException {
Query query = session.createQuery(hql);
query.setFirstResult(offset);
query.setMaxResults(length);
List<T> list = query.list();
return list;
}
});
return list;
}
/**
* 子類需要重寫HQL時,可繼承重寫該方法
* @param t
* @return
*/
protected String getListHQL(T t){
String className = t.getClass().getSimpleName();
StringBuffer hqlBuffer = new StringBuffer("from " + className + " as t " + (appendJoinHQL(t)==null?"":appendJoinHQL(t)) + " where 1 = 1 ");
String appender = appendConditionHQL(t);
if(appender != null){
hqlBuffer.append(appender);
}
if(t instanceof DeletedByLogic){
hqlBuffer.append(" and t.state != '" + Constants.STATE_DELETED + "'");
}
hqlBuffer.append(" order by " + getOrderByHQL()+ " " );
String hql = hqlBuffer.toString();
return hql;
}
public int getCount(T t, int offset, int length) {
String className = t.getClass().getSimpleName();
StringBuffer hqlBuffer = new StringBuffer("select count(*) from " + className + " as t where 1=1 ");
String appender = appendConditionHQL(t);
if(appender != null){
hqlBuffer.append(appender);
}
if(t instanceof DeletedByLogic){
hqlBuffer.append(" and t.state != '" + Constants.STATE_DELETED + "'");
}
final String hql = hqlBuffer.toString();
Object uniqueResult = createQuery(hql).uniqueResult();
return Integer.parseInt(uniqueResult.toString());
}
普通的業務處理對象(Service)接口應繼承於com.epro.crm.service.base.CommonServiceInterface<T>,其默認已有增刪改查的接口。其實現類應繼承於com.epro.crm.service.base.CommonBaseService<T>,該類有增刪改查,日誌等功能。
參考:com.epro.crm.service.system.impl.UserServiceImpl
public PageModel<T> getPageModel(T sample, PageModel<T> pageModel){
if(sample == null)
sample = getNewEntity();
List<T> dataList = getCommonDao().getList(sample, pageModel.getOffset(), pageModel.getPageSize());
Integer total = getCommonDao().getCount(sample, pageModel.getOffset(), pageModel.getPageSize());
pageModel.setDataList(dataList);
pageModel.setTotalItemNumber(total);
return pageModel;
}
分頁模型
頁面上採用pager-taglib 標籤進行分頁頁碼顯示及偏移量計算,由分頁模型對象com.epro.crm.model.util.PageModel<T>接收偏移量offset,頁大小,在DAO層由com.epro.crm.dao.base.CommonBaseDao.getList(T,int, int)方法將分頁查詢的結果放在PageModel對象中,在頁面通過Struts-tag顯示。
public class PageModel<T> {
/**
* 總記錄數
*/
private Integer totalItemNumber;
/**
* 當前頁結果集
*/
private List<T> dataList;
/**
* 偏移量:當前頁第一條記錄,在總記錄數中的序號
*/
private Integer offset = 0;
/**
* 每頁顯示記錄數
*/
private Integer pageSize = Configurations.DEFAULT_PAGE_SIZE;
/**
* 頁碼
*/
private Integer pageNumber = null;
/**
* 總頁碼
*/
private Integer totalPageNumber = null;
/** getters and setters **/
}
數據字典
數據字典由“數據字典類型”com.epro.crm.model.system.DataDictType以及“數據字典項”com.epro.crm.model.system.DataDictItem組成。
在數據庫中添加了數據字典類型,及對應的數據字典項後。在Action層中,通過DataDictTypeService準備數據字典內容。在界面中可通過
<s:select list="allTaskTypes" name="taskType.id" headerKey="" headerValue="請選擇.." listKey="id" listValue="name" value="taskType.id" cssClass="required"></s:select>
的Struts標籤,即可顯示數據字典列表。
系統日誌/操作日誌
平臺提供兩種日誌方式。
系統日誌:系統日誌由log4j記錄,輸出至/crm_log.html文件中。程序中可通過以下方式獲得日誌記錄對象。
protected final Log log = LogFactory.getLog(getClass());
操作日誌:操作日誌是由平臺提供用來將用戶的重要操作記錄到數據庫中,以及日誌的顯示模塊。記錄的內容包括操作時間,操作者用戶名,IP,以及操作描述。見com.epro.crm.model.system.Log
已經在BaseAction中封裝,所以可以在Action中直接使用logService.log(“”)記錄。
參考:
public void log(String summary, String description){
Log log = new Log((User)sessionMap.get(Constants.SESSION_USER),getIpAddr(),
summary, description);
logService.add(log);
}
平臺中的領域對象刪除後的狀態支持兩種模式。
邏輯刪除是採用一個標記字段表示該記錄的狀態,如已刪除,則修改標記字段爲刪除狀態,查詢時將其忽略。物理刪除即爲普通delete刪除。
採用物理刪除的類中實現com.epro.crm.model.base.DeletedByPhysics接口即可。
採用邏輯刪除的類中實現com.epro.crm.model.base.DeletedByLogic接口即可。
CommonBaseDao在刪除和查詢對象時,會根據類所表明的模式,進行判斷。
參考:com.epro.crm.model.system.User
上傳下載
Struts 2 的常用方式,有待封裝
見 ResumeAction:111
Json支持
由於使用Struts2 Json plugin 時,response默認content type爲”text/json”,部分瀏覽器對該格式解析不正確,產生下載頁面。
所以本平臺採用另外一種手動設置的方式
this.getResponse().setContentType("text/html");
this.getResponse().setCharacterEncoding("GBK");
PrintWriter out = this.getResponse().getWriter();
ajaxResult = "{error: '簡歷上傳成功!但無法提取信息,請手動填寫表單!',msg:'',filename:'"+ resumeFileFileName + "',filepath : ' " + targetDirectory+ "/" + targetFileName + " '}";
out.write(ajaxResult);
out.flush();
見 ResumeAction:111