主体架构
主体采用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