ERP项目笔记
1. struct2(拦截器,值栈访问,文件下载)
2. Hibernate3(单表数据操作,关联关系数据模型,延迟加载)
3. Spring3(Bean配置与管理,团队开发模式,AOP)
4. Ajax(异步请求获取数据)
5. Jquery(页面脚本动作+页面动态表格操作)
6. Mysql(数据库基本操作)
7. SSH整合
8. 反射(反射的应用)
9. Maven构建项目
10. CRM项目需求与相关业务
其他知识点
1. 反射泛型类型
2. 视图值与真实值的应用
3. 报表工具(java读写Excel,jfreechart工具)
4. Spring计时器任务
5. Spring整合JavaMail(邮件发送)
6. Jquery页面开发实用技术
7. 火狐页面调试功能
技巧
1. 通用类的抽取
2. 代码生成器
3. Long型日期时间处理+页面日期组件
4. 统一的异常处理方案
5. 全局国际化的应用
6. 页面的动态表现
7. 遮罩层的应用
8. 权限校验系统
9. 通用分页组件的制作与使用(自定义标签)
10. 动态数据库表结构
11. 性能优化—二级缓存(备选)
主线流程分析
前置模块:主线流程制作需要满足以下单元模块制作完成为基础条件:
员工管理,权限校验系统,供应商管理,供应商商品类别管理,商品管理
采购流程:
下采购单—>采购审批-->运输任务指派——>判定供应商是否送货——〉运输任务完成——>按采购订单任务入库—>流程结束
销售流程:
下销售单à销售审批à判断库存是否足够à转入分支采购流程à判断商家是否上门提货à转入运输部门à运输任务指派à按销售订单任务出库à运输任务完成à流程结束
采购退货流程
下采够退货单à采购退货审批à判断商家是否上门提货à转入运输部门à运输任务指派à按销售退货单任务入库à流程结束
子线流程分析:
权限管理:
权限管理是所有管理类系统中必不可少的一部分,权限管理通过数据设置完成整体系统的操作能力控制,避免用户进行非法操作。
具体实现
CRM项目对接:
Crm(CustomerRelationship Management)即客户管理系统,用于对客户信息,客户关系进行维护。Crm是一套独立的系统,各种大型企业均拥有自主的客户关系管理系统。使用该系统对老客户进行关系维护,对新客户发展进行实时跟踪,保持良好的发展态势。
本项目中采购对应的供应商和销售对应的客户实际制作均采用CRM项目进行管理。
WebService接口对接
企业级项目中,每个企业管理系统均采用独立控制,独立管理的方式进行。由于各公司间系统存在差异,不能进行良好的数据交互。例如A公司给B公司下了一份采购订单,但是B公司并知情,再B公司的管理系统中也无法呈现对应的数据,造成管理系统只为企业管理完成了一半任务,没有真正意义上的将数据流进行到底。基于此现象,为B公司制作对外的服务接口,A公司给B公司下订单的同时,调用B公司的对外服务接口,这样A公司内部数据管理维护为正确的,同时B公司也接到响应的的信息,只需要将对外接口进行一定的安全管理就可以轻松实现两个系统间数据交互模型。
项目类图分析:
创建Web工程
创建J2EE5.0Web工程,应用名ERP,导入资源/jars目录中所有的Jar包到工程中,
导入资源/项目配置文件中的配置文件到对应的位置(resources目录)
静态页面的导入
将静态页面中所有文件拷贝到webroot目录下
参见:资源/静态页面/page.rar
注意:项目页面最终放入web—inf/jsps目录下,手工创建该项目。
说明:
查看静态页面,使用http://localhost:8080/**/index.jsp
制作项目,使用http://localhost:8080/**
主页设置与跳转
1. 设置项目主导航页index.jsp
2. 设置主导航页跳转到/WEB-INF/jsps/login.jsp
<script type=”text/javascript”>
document.location=”WEB-INF/jsps/login.jsp”
</script>
3. 拷贝login.Jsp页面到/WEB-INF/jsps目录下
4. 测试功能
a)
问题分析:
WEB-INF下的页面资源无法直接读取,因此页面返回404,无法找到该页面
解决方案:
使用action调用的方式访问该页面,配置Action访问
5.设置页面访问格式为action调用格式
<script type=”test/javascript”>
top.document.location=”login.action”
</script>
6.在structs.xml文件中配置对应的Action
登陆Action,采用默认的action类
<action name=”login”>
<result>/WEB-INF/jsps/login.jsp</result>
</action>
说明:由于此处仅仅用于页面跳转,无任何后台操作,可使用默认的Action方式完成,既不定义Action的class属性,使用Struct默认提供的Action即可
7.重启服务器,测试功能。
问题分析:页面中的资源(样式表,图片,脚本)没有加载到,页面的访问路径书写问题。静态页面中资源访问路径写的是相对路径,即../../的格式,此处无法获取。
解决方案:
使用以下格式获取,删除所有的../,使用当前应用目录的格式查看
<linkhref=”../css/index.css” rel=”stylesheet” type=”test/ css”/>
修改为
<linkhref=”css/index.css” rel=”stylesheet” type=”test/css”/>
8. 刷新页面,测试功能:
[知识总结]
WEB-INF目录下的jsp文件无法直接获取,需要经过structs访问方式跳转才可以完成页面访问。
[开发技巧]
进入每个静态页面后,第一件事是将所有的资源路径进行修改,修改完毕后确认没有问题再进行下一步制作。
登陆功能
1. 功能入口为登陆按钮
2. 业务流程分析
a) 点击登陆,将用户输入的用户名,密码,验证码信息传入后台Actiion类
b) Action类将页面收集的数据传入业务层Ebo
c) Ebo类对密码进行MD5加密后,将数据传入数据层Impl
d) Impl类使用Hibernate模板进行查询,将数据库中数据与传递数据进行比对,将查询结果返回Ebo类
e) Ebo类将查询结果返回Action类
f) Action类对查询结果进行判定,如果不为null,则将查询结果(登陆人信息)放入Session范围内,以备后期使用,否则登陆失败,返回登陆界面,告知用户信息输入有误
3. 按定义规则,初始化登录人对应的模块——员工模块所有类
实体模型类:模块名Model
查询模型类:模块名QueryModel(按需求定义)
数据接口:模块名Dao
数据层实现:模块名Impl
业务层接口:模块名Ebi
业务层实现:模块名Ebo
表现层类:Action
补:各个类与接口初始化内容及定义规范
实体模型类:模块名Model
1. 常量真实值定义
2. 常量视图值定义
3. 常量集合定义
4. 常量集合数据初始化
5. 主键字段命名为uuid,long型,数据库端设计自增
6. 所有属性名上方添加注释,标记该字段含义
7. 所有视图值定义
8. 所属关系属性定义
9. 所有属性封装方法为getter/setter(视图值不提供setter方法)
查询模型类:模块名QueryModel
1. 继承实体模型类
2. 实体模型类中范围查询字段二次声明,在变量名后添加数字2
3. 查询字段视图值定义
4. 所有属性封装方法getter/setter(视图值不提供setter方法)
数据层接口:模块名Dao
1. 方法定义前添加public关键字
2. 为方法添加文档注释(基础方法不添加,如save update)
数据层实现:模块名Impl
1. 继承HibernateDaoSupport
2. 实现数据层接口,并实现所有抽象方法
业务层接口:模块名Ebi
2. 方法定义前加public关键字
3. 为方法添加文档注释(所有方法全部添加)
业务层实现:模块名Ebo
1. 实现业务层接口,并实现所有抽象方法
2. 声明私有的数据层接口对象,并进行setter封装(注入)
表现层类:模块名Action
1. 继承ActionSupport
2. 声明公共的实体模型类对象,变量为模块名单词首字母小写+m
public EmployeeModelem=new EmployeeModel();
3. 声明公共实体类模型查询对象,变量为模块单词首字母小写+qm
public EmployeeQueryModel eqm=newEmployeeQueryModel();
4.声明私有的业务接口对象,并进行setter封装(注入)
4.按照定义规则,初始化登录人对应的模块—员工模块所有的配置文件
映射配置文件名:模块名Model.hbm.xml
1. 数据库表名定义规则:tbl_模块名
补:关系表名定义规则为:tbl_主方模块名_从方模块名
2. 只对真实值字段进行定义,视图值字段不进行定义
3. 关系定义时,使用注释描述关系对应方式
4. 关系定义在使用时进行,不要全部定义。
SpringBean配置文件名:applicationContext-模块名首字母小写.xml
1. Action对应Bean定义,scope=“prototype”,名称:模块名Action注入业务层口
2. Ebo对应Bean定义,名称:模块名Ebi
注入数据层接口
3. Impl对应的Bean定义,名称:模块名Dao
注入SessionFactory
Struct.xml添加Action定义
员工
<actionname=”emp_*” class=”empAction” method=”{1}”>
</action>
Action调用名为模块名
5.修改login.jsp页面表单格式为struct2格式
6.设置提交到emp_login
<s:form action=”employee_login”method=”post”>
7.在EmployeeAction类中声明login方法
//登录使用用户传递的用户名密码进行登录
public String login(){
//使用用户名密码到数据库进行校验查询
EmpModelloginEm=empEbi.login(em.getUserName(),em.getPwd());
//判断是否登录成功
If(loginEm==null){
//如果匹配失效了
//跳转到登录页
return “loginFail”;
}else{
ActionContext.getContext().getSession().put(“loginEm”,loginEm);
//跳转到主页
return “loginSuccess”;
}
}
1. 获取用户登录信息,要在业务层对密码进行加密
2. 判断用户信息是否获取成功
2.1判断获取成功,将数据放入Session中,跳转到登录主页
2.2判断获取失败,返回登录界面
8.业务层完成对应的数据传输及加密工作,需要声明MD5加密工具类
接口
@Transactional
Publicinterface EmployeeEbi{
//登录
Public EmployeeModel login(String username,Stringpwd);
}
实现:
Public EmpModellogin(String username,String pwd){
Pwd=MD5Utils.md5(pwd);
Return empDao.getByNameAndPwd(username,pwd);
}
9.数据层完成使用用户名密码获取数据的工作
Publicinterface EmpDao{
Public EmpModel getByNameAndPwd(String username, string password );
}
实现
Public EmpModel getByNameAndPwd(String username,string pwd){
String hql=”fromEmpModel where username = ? and pwd=?”;
List<EmpModel>temp=this.getHibernateTemplate().find(hql,username,pwd);
Returntemp.size>0?temp.get(0):null;
}
10.设置struct.xml跳转页面
<action name=”emp_*”class=”empAction” method=”{1}”>
<result name=”loginSuccess”>/WEB-INF/jsps/main.jsp</result>
<result name=”loginFail”>/WEB-INF/jsps/login.jsp</result>
</action>
11.重启服务器,测试功能
注意1:测试功能前,使用测试数据完成测试任务即可
SQL:INSERTtbl_emp(username,pwd) VALUES(‘admin’,md5(‘admin’))
注意2拷贝main.jsp到指定目录,无需关注该页面内容仅为测试跳转
12修改main.jsp页面资源位置
13.修改main.jsp页面中iframe框架对context.jsp页面的引用格式界面:
<iframeid=”frame-contect” src=”context.action” style=”width:848px;float:right ;height:530px ” scrolling=”no” name=” main” frameborder=”0”></iframe>
Struct.xml
<actionname=”context”>
< result>/WEB-INF/jsps/context.jsp</result>
</action>
14.修改context.jsp页面资源位置
15.修改main.jsp 页面中的登陆人信息
页面获取session范围数据的方式
EL
${sessionScope.loginEm.name}
${sessionScope.loginEm[“name”]}
Structs2:
<s:propertyvalue=”#session[‘loginEm’].name”/>
<s:propertyvalue=”#session.loginEm.name”/>
[知识总结]
1. Action配置方式
如果Action类中没有实际的业务功能,仅仅是为了跳转页面可以配置默认的Action实现;类,只需要配置调用名称
2.request与session数据的存取
3.MD5加密
Md5加密在数据库端通过sql语句完成,在程序端通过工具类完成,所处位置必须在业务层实现类中。
4.HQL语句执行
HQL语句执行依赖Hibernate模板对象完成,根据不同的业务选择是否使用HQL语句来完成任务。当查询条件比较多时,使用QBC查询更合理,当查询任务比较简单时,推荐使用HQL查询完成任务。
[开发技巧]
当定义有关页面跳转的Action类时,因为没有实际业务功能,可以使用通配的格式完成所有页面跳转的一次性定义。
页面调用格式使用page_login.action的格式完成
<scripttype=”text/javascript”>
Document.location=”page_login.action”
</script>
用户登录校验(structs拦截器)
项目中所有页面均放入WEB-INF,所有页面安全性得到了很大程度的提高。但是当用户登录后长时间没有操作时,会造成Session数据过期,如果此时获取Session中的数据,必定引发WEB引用的空指针异常,造成数据错误,程序崩溃。因此需要一种机制保障每次发送请求执行Action中的方法之前校验用户是否处于登录状态。
两种实现方式
1. AOP
AOP思想可以在任意方法执行前进行拦截操作,完成原始方法执行前的操作
2. Structs2拦截器
a) Struct2拦截器可以再Struct2的任意Action执行之前和之后,完成某些任务,其内部原理与AOP极其相似
b) 基于当前业务的需求,需要对每次的Action访问进行拦截,Struct2拦截器更切近业务需求,并且在拦截器中获取Session数据比AO更简便,因此优先选用拦截器。
i. 自定义登陆拦截器
Public class LoginInterceptor extends AbstractInterceptor{
}
3. 添加关于登录校验的业务方法
Public String intercept(ActionInvocation invocation) throwsException{
EmpModelloginEm=ActionContext.getContext().getSession().get(“loginEm”);
If(loginEm==null){
Return “loginFail”;
}else{
Return invocation.invoke();
}
}
声明Struct2自定义拦截器
<interceptorsname=”LoginInterceptor”
Class=”cn.invoice.util.interceptor.LoginInterceptor”></ interceptors>
4. 设置自己定义的拦截器
<interceptors>
<interceptor class=”cn.invoice.util.interceptor.LoginInterceptor”>
</interceptor>
<interceptor-stack>
<interceptor-refname=”loginInterceptor”></interceptor-ref>
<interceptor-refname=”defaultStack”></interceptor-ref>
</interceptor-stack>
</interceptors>
5. 设置默认的调用拦截器栈
<default-interceptor-ref name=”invocationStack”/>
6. 配置全局公共结果集loginFail返回跳转页面
<global-results>
<resultname=”loginSuccess”>/WEB-INF/jsps/main.jsp</result>
<result name=”loginFail”>/WEB-INF/jsps/login.jsp </result>
</ global-results>
7. 重启服务器,测试功能
未登录用户无法访问任何资源,但是正常的登录业务也被拦截到未登陆操作。
问题分析:
执行登陆操作时,由于该操作也属于Action调用,而此时Session中并无登录数据,structs2识别该操作时,无法区分是否登录操作,一次被拦截。
解决方案:
在拦截器中,定义如果该操作是登陆操作,则放行。
StringactionName=invocation.getAction().getClass().getName();
String method=invocation.getProxy().getMethod();
String totalName=actionName+”.”+methodName;
If(totalName.equals(“登录方法名”)){
Returninvocation。Invoke();
}
[知识总结]
1. 自定义拦截器需要为其指定intercept方法,在其中执行拦截器所完成的任务。可以通过其参数ActionInvocation获取Action执行过程中对应的信息
2. 自定义拦截器定义出来需要进行配置才可以使用,该配置在structs.Xml中,声明出的拦截器并不直接被调用,需要配置默认拦截器调用方式,如果直接配置了自定义拦截器为默认拦截器,Struct提供的内置拦截器将失效,因此在使用自定义拦截器时通常配合struct2的默认拦截器组合使用。
3. 适配器设计模式是java常用的设计模式,使用适配器设计模式可以减少无用的接口方法实现。
[开发技巧]
1. 开发自定义拦截器如果不是很专业,可以通过查看structs-default.xml配置文件中的配置方式,模仿开发。
2. 自定义拦截器通常配置在structs.xml中的最前面
3. 定义拦截器有两种实现方式,实现接口,继承抽象类。实现接口时,需要对生命周期初始化与销毁方法进行实现,比较繁琐,推荐使用抽象类。
Public class LoginInterceptor implements Interceptor{
}