主体架构
主体采用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
-
- },
- 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;
- }
-
-
-
-
-
-
-
-
- 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;
-
-
- }
数据字典
数据字典由“数据字典类型”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”,部分浏览器对该格式解析不正确,产生下载页面。
所以本平台采用另外一种手动设置的方式
- <span style="white-space:pre"> </span> 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