文章目录
- 前言
- 项目展示
- 技能要求
- 一、开始前的准备
- 1、OA系统是什么?
- 2、人员权利与报销流程
- 3、数据库设计
- 4、创建项目及作用说明
- 5、包与全局变量配置
- 6、编写过滤器
- 7、静态资源的复制与请求处理
- 8、常量类
- 二、功能实现
- 1、部门管理
- 部门信息的增删改查
- (1)、实体类
- (2)、dao接口与Sql映射文件
- (3)、biz接口与其实现类
- (4)、控制器
- (5)、页面
- 2、员工管理
- 员工信息的增删改查
- (1)、Sql映射文件
- (2)、biz接口与实体类
- (3)、控制器
- (4)、页面
- 3、登录及个人中心
- (1)、登录的biz接口与实现类
- (2)、控制器
- (3)、登录拦截器
- (4)、退出
- (5)、修改密码
- 4、报销单处理
- (1)、报销单的持久层处理
- (2)、填写报销单与报销单详情
- (3)、个人报销单与待处理报销单
- (4)、修改报销单
- (5)、提交报销单
- (6)、审核报销单与打款
- 三、总结与源码
前言
SSM(Spring+SpringMVC+MyBatis)框架集由Spring、SpringMVC、MyBatis三个开源框架整合而成,常作为数据源较简单的web项目的框架。
其中Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。SpringMVC分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让它们更容易进行定制。
MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架。
页面发送请求给控制器,控制器调用业务层处理逻辑,逻辑层向持久层发送请求,持久层与数据库交互,后将结果返回给业务层,业务层将处理逻辑发送给控制器,控制器再调用视图展现数据。
写作动机:
这个项目是本小白学习完SSM后跟着视频做的第一个项目,但当时跟着做时出现了很多的bug无法更改,而且所提供的源码也是错误的,当时就直接把我搞得劝退了,而现在因为接触、学习了很多其他知识后,并且做了很多大大小小的项目有了些许经验,想着毕竟是第一个项目,要完成好,所以就靠自己写了出来,自己写出来后发现原来视频提供的源码是真的错了,难怪当时用不了,因为是自己敲的,所以会有很多缺陷,请见谅,下面便开始我的创作历程吧,希望对你有帮助(づ ̄3 ̄)づ╭~
相关博客:
Spring微总结
MyBatis微总结
SpringMVC微总结
SpringBoot+Mybatis实现用户账号CRUD系统+前后端交互(含源码)
项目展示
技能要求
前置条件:
MyBatis、Spring、Spring MVC、MySQL
主要技术:
Spring IOC、MyBatis+Spring整合、声明式事务、Spring标签库、Spring拦截器
一、开始前的准备
1、OA系统是什么?
OA系统(Office Automation),即办公自动化系统,是一种应用于办公领域的新型无纸化办公系统。
它利用计算机、通信等现代化技术来数字化地创建、收集、存储、处理办公任务所需的各种信息,代替办公人员传统的部分手动或重复性业务活动,极大地优化了以往复杂、低效的办公室工作过程,可以最大限度地提高工作效率和质量、改善工作环境。
2、人员权利与报销流程
报销流程:
3、数据库设计
4、创建项目及作用说明
oa:
父moudle
全局定义与组织
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.oa</groupId>
<artifactId>oa</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>oa_dao</module>
<module>oa_biz</module>
<module>oa_web</module>
</modules>
<properties>
<spring.version>4.0.2.RELEASE</spring.version>
</properties>
</project>
oa_dao:
持久层:持久化操作相关
NyBatis依赖、Spring依赖、MyBatis-Spring整合
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>oa</artifactId>
<groupId>com.oa</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>oa_dao</artifactId>
<dependencies>
<--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<--MyBatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.4</version>
</dependency>
<--Spring基本的依赖包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<--Spring与MyBtis整合依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
</project>
oa_biz:
业务层:业务功能处理、AOP依赖、Aspectj依赖
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>oa</artifactId>
<groupId>com.oa</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>oa_biz</artifactId>
<dependencies>
<--依赖持久层-->
<dependency>
<groupId>com.oa</groupId>
<artifactId>oa_dao</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<--声明式事务的依赖包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<--AOP依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<--Aspectj依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.0</version>
</dependency>
</dependencies>
</project>
oa_web:
表现层:与用户进行交互
Servlet依赖、Spring MVC依赖
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>oa</artifactId>
<groupId>com.oa</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>oa_web</artifactId>
<packaging>war</packaging>
<name>oa_web Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<--依赖业务层-->
<dependency>
<groupId>com.oa</groupId>
<artifactId>oa_biz</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<--Servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<--spring相关依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<--SpringMVC依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
<build>
<finalName>oa_web</finalName>
</build>
</project>
业务层、持久层、表现层所用到的模板:
5、包与全局变量配置
oa_dao:
dao、entity、global
数据源:指数据库应用程序所使用的数据库或者数据库服务器
Session(称域对象)工厂、映射器接口
resources/spring_dao.xml:
oa_biz:
biz
事务
resources/spring_biz.xml:
oa_web:
controller、dto、global
静态资源处理、视图转换器
Spring MVC加载
resources/spring_web.xml:
Spring MVC加载器:resources/webapp/WEB-INF/web.xml:
6、编写过滤器
oa_web/…/com/oa/global/EncodingFilter.java:
public class EncodingFilter implements Filter {
private String encoding = "utf-8";
public void init(FilterConfig filterConfig) throws ServletException {
if(filterConfig.getInitParameter("encoding")!=null){ //获取encoding初始化参数
encoding = filterConfig.getInitParameter("encoding");
}
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
//设定字符集
request.setCharacterEncoding(encoding);
response.setCharacterEncoding(encoding);
//调用拦拦截器链
filterChain.doFilter(request,response);
}
public void destroy() {
}
}
配置过滤器:resources/webapp/WEB-INF/web.xml:
<filter>
<filter-name>encoding</filter-name>
<filter-class>com.oa.global.EncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
7、静态资源的复制与请求处理
静态资源复制后,因为如果所有的请求都交给了SpringMVC处理的话,效率就太慢了,所以写一个让这些静态资源默认给servlet进行处理(静态资源以放在GitHub里)。
都交给默认的servlet进行请求处理(因为在web中都会有一个default的servlet,所以就直接写了servlet-mapping):resources/webapp/WEB-INF/web.xml:
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/assets/*</url-pattern>
<url-pattern>/js/*</url-pattern>
<url-pattern>/vendor/*</url-pattern>
<url-pattern>*.js</url-pattern>
<url-pattern>*.jpg</url-pattern>
<url-pattern>*.gif</url-pattern>
<url-pattern>*.png</url-pattern>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
8、常量类
都放在oa_dao层的com.oa.global.Contant.java:
报销单状态
//报销单状态
public static final String CLAIMVOUCHER_CREATED="新创建";
public static final String CLAIMVOUCHER_SUBMIT="已提交";
public static final String CLAIMVOUCHER_APPROVED="已审核";
public static final String CLAIMVOUCHER_BACK="已打回";
public static final String CLAIMVOUCHER_TERMINATED="已终止";
public static final String CLAIMVOUCHER_RECHECK="待复审";
public static final String CLAIMVOUCHER_PAID="已打款";
处理方式
//处理方式
public static final String DEAL_CREATE="创建";
public static final String DEAL_SUBMIT="提交";
public static final String DEAL_UPDATe="修改";
public static final String DEAL_BACK="打回";
public static final String DEAL_REJECT="拒绝";
public static final String DEAL_PASS="通过";
public static final String DEAL_PAID="打款";
职务、职务集合
//职务
public static final String POST_STAFF="员工";
public static final String POST_FM="部门经理";
public static final String POST_GM="总经理";
public static final String POST_CASHIER="财务";
public static List<String> getPosts(){
List<String> list = new ArrayList<String>();
list.add(POST_STAFF);
list.add(POST_FM);
list.add(POST_GM);
list.add(POST_CASHIER);
return list;
}
需复审额度
//审核额度
public static final double LIMIT_CHECK=5000.00;
费用类别集合
//费用类别
public static List<String> getItems(){
List<String> list = new ArrayList<String>();
list.add("交通");
list.add("餐饮");
list.add("住宿");
list.add("办公");
return list;
}
二、功能实现
1、部门管理
部门信息的增删改查
步骤:
(1)、实体类
根据数据库创建相对应的实体类:(都放在oa_dao层的com.oa.entity)
ClaimVoucher.java:(get、set)
public class ClaimVoucher {
private Integer id; //编号
private String cause; //说明
private String createSn; //创建者编号
@DateTimeFormat(pattern = "yyyy-MM-dd hh:mm") //时间
private Date createTime;
private String nextDealSn; //待处理者编号
private Double totalAmount; //金额
private String status; //状态
}
ClaimVoucherItem.java:(get、set)
public class ClaimVoucherItem {
private Integer id; //编号
private Integer claimVoucherId; //报销单编号
private String item; //类型
private Double amount; //金额
private String comment; //说明
}
DealRecord.java:(get、set)
public class DealRecord {
private Integer id; //编号
private Integer claimVoucherId; //报销单编号
private String dealSn; //处理人编号
@DateTimeFormat(pattern = "yyyy-MM-dd hh:mm")
private Date dealTime; //处理时间
private String dealWay; //处理方式
private String dealResult; //处理结果
private String comment; //处理后的意见或备注
}
Department.java:(get、set)
public class Department {
private String sn;
private String name;
private String address;
}
Employee.java:(get、set)
public class Employee {
private String sn;
private String password;
private String name;
private String departmentSn;
private String post;
}
(2)、dao接口与Sql映射文件
dao接口:(都放在oa_dao层的com.oa.dao)
@Repository:这个注解是具有类似用途和功能的 @Component 注解的特化。它为 DAO 提供了额外的好处。它将 DAO 导入 IoC 容器,并使未经检查的异常有资格转换为 Spring DataAccessException。
ClaimVoucherDao:
@Repository("claimVoucherDao")
public interface ClaimVoucherDao {
void insert(ClaimVoucher claimVoucher);
void update(ClaimVoucher claimVoucher);
void delete(int id);
ClaimVoucher select(int id);
List<ClaimVoucher> selectByCreateSn(String csn); //根据创建者编号查询
List<ClaimVoucher> selectByNextDealSn(String ndsn); //根据待处理人编号查询
}
ClaimVoucherItemDao:
@Repository("claimVoucherItemDao")
public interface ClaimVoucherItemDao {
void insert(ClaimVoucherItem claimVoucherItem);
void update(ClaimVoucherItem claimVoucherItem);
void delete(int id);
List<ClaimVoucherItem> selectByClaimVoucher(int cvid); //根据报销单编号进行查询
}
DealRecordDao:
@Repository("dealRecordDao")
public interface DealRecordDao {
void insert(DealRecord dealRecord);
List<DealRecord> selectByClaimVoucher(int cvid);
}
DepartmentDao:
@Repository("departmentDao")
public interface DepartmentDao {
void insert(Department department);
void update(Department department);
void delete(String sn);
Department select(String sn);
List<Department> selectAll();
}
EmployeeDao:
@Repository("employeeDao")
public interface EmployeeDao {
void insert(Employee employee);
void update(Employee employee);
void delete(String sn);
Employee select(String sn);
List<Employee> selectAll();
List<Employee> selectByDepartmentAndPost(@Param("dsn") String dsn, @Param("post") String post);
}
Sql映射文件:
在全局配置文件中使用package标签统一配置dao包下接口对应的mapper.xml文件时,映射文件须与对应接口处在相同路径下且映射文件名与对应接口名须相同
使用mapper接口方式必须满足:
1.映射文件的namespace的值必须是接口的全路径名称 比如:com.oa.dao.XXXXXX
2.接口中的方法名在映射文件中必须有一个id值与之对应。
3.映射文件的名称必须和接口的名称一致
DepartmentDao.xml:
若对MySQL的一些简单操作不了解可以看看这个:一文解决MySQL基础的 ’ 增删改查 ’
(3)、biz接口与其实现类
biz接口:(放在oa_biz层的com.oa.dao)
DepartmentBiz:
public interface DepartmentBiz {
void add(Department department);
void edit(Department department);
void remove(String sn);
Department get(String sn);
List<Department> getAll();
}
可以发现上面两个接口基本上是一一对应的,那它又有什么存在的必要呢,其实呀,因为这个部门管理没什么业务功能,也没什么特殊的业务流程,所以这个接口会与持久层功能匹配,在后面完成登录功能时便会介绍比较特殊的业务功能。
实现类:(放在oa_biz层的com.oa.dao.impl)
简单的增删改查的操作:DepartmentBizImpl.java:
@Service("departmentBiz")
public class DepartmentBizImpl implements DepartmentBiz {
@Autowired
private DepartmentDao departmentDao;
public void add(Department department) {
departmentDao.insert(department);
}
public void edit(Department department) {
departmentDao.update(department);
}
public void remove(String sn) {
departmentDao.delete(sn);
}
public Department get(String sn) {
return departmentDao.select(sn);
}
public List<Department> getAll() {
return departmentDao.selectAll();
}
}
可以发现这个模块是很简单的,当然也就这个模块比较简单ヾ(。`Д´。)ノ彡
上面已经完成了持久层和业务层的程序,下面我们来完成表现层┗(•ω•;)┛
(4)、控制器
DepartmentController.java:(展现员工列表)[oa_web/…/com.oa.controller]
@Controller("departmentController")
@RequestMapping("/department")
public class DepartmentController {
@Autowired
private DepartmentBiz departmentBiz;
@RequestMapping("/list")
public String list(Map<String,Object> map){
map.put("list",departmentBiz.getAll());
return "department_list";
}
}
(5)、页面
实现相对应的JSP:
department_list.jsp和公用的jsp:top.jsp、botton.jsp请看最后源码展示
对于部门增删改的控制器如下,department_add.jsp、department_update.jsp就看源码吧:
简单介绍一下:
redirect(重定向):
是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL.
@RequestMapping("/to_add")
public String toAdd(Map<String,Object> map){
map.put("department",new Department());
return "department_add";
}
@RequestMapping("/add")
public String add(Department department){
departmentBiz.add(department);
return "redirect:list";
}
@RequestMapping(value = "/to_update",params = "sn") //在RequestMapping进行过滤,用属性params强制要求传sn这个参数
public String toUpdate(String sn,Map<String,Object> map){
map.put("department",departmentBiz.get(sn));
return "department_update";
}
@RequestMapping("/update")
public String update(Department department){
departmentBiz.edit(department);
return "redirect:list";
}
@RequestMapping(value = "/remove",params = "sn") //在RequestMapping进行过滤,用属性params强制要求传sn这个参数
public String remove(String sn){
departmentBiz.remove(sn);
return "redirect:list";
}
以上便是部门管理的功能实现,效果展现:
下面便进行员工管理。
2、员工管理
员工信息的增删改查
(1)、Sql映射文件
在上面的部门管理功能实现时就已经完成了实体类、dao接口,下面来编写SQL的映射文件:
EmployeeDao.xml:
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.4//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.oa.dao.EmployeeDao">
<resultMap id="employee" type="Employee">
<id property="sn" column="sn" javaType="String"/>
<result property="password" column="password" javaType="String"/>
<result property="name" column="name" javaType="String"/>
<result property="departmentSn" column="department_sn" javaType="String"/>
<result property="post" column="post" javaType="String"/>
<association property="department" column="department_sn" javaType="Department" >
<id property="sn" column="dsn" javaType="String"/>
<result property="name" column="dname" javaType="String"/>
</association>
</resultMap>
<insert id="insert" parameterType="Employee">
insert into employee values(#{sn},#{password},#{name},#{departmentSn},#{post})
</insert>
<update id="update" parameterType="Employee">
update employee set name=#{name},password=#{password},department_sn=#{departmentSn},post=#{post} where sn=#{sn}
</update>
<delete id="delete" parameterType="String">
delete from employee where sn=#{sn}
</delete>
<select id="select" parameterType="String" resultMap="employee">
select e.*,d.sn dsn,d.name dname from employee e left join department d on d.sn=e.department_sn
where e.sn=#{sn}
</select>
<select id="selectAll" resultMap="employee">
select e.*,d.sn dsn,d.name dname from employee e left join department d on d.sn=e.department_sn
</select>
<select id="selectByDepartmentAndPost" resultMap="employee">
select e.*,d.sn dsn,d.name dname from employee e left join department d on d.sn=e.department_sn
where e.sn is not NULL
<if test="dsn!=null">
and e.department_sn=#{dsn}
</if>
<if test="post!=null">
and e.post=#{post}
</if>
</select>
</mapper>
员工与部门的关联关系
下面便实现biz接口与其实现类
(2)、biz接口与实体类
(3)、控制器
EmployeeController.java:(放在oa_web/…/com.oa.controller,这些都比较简单就不详细解释了)
@Controller("employeeController")
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private DepartmentBiz departmentBiz;
@Autowired
private EmployeeBiz employeeBiz;
@RequestMapping("/list")
public String list(Map<String,Object> map){
map.put("list",employeeBiz.getAll());
return "employee_list";
}
@RequestMapping("/to_add")
public String toAdd(Map<String,Object> map){
map.put("employee",new Employee());
map.put("dlist",departmentBiz.getAll()); //传递所有部门到添加页面
map.put("plist", Contant.getPosts()); //传递所有职位到添加页面
return "employee_add";
}
@RequestMapping("/add")
public String add(Employee employee){
employeeBiz.add(employee);
return "redirect:list";
}
@RequestMapping(value = "/to_update",params = "sn")
public String toUpdate(String sn,Map<String,Object> map){
map.put("employee",employeeBiz.get(sn));
map.put("dlist",departmentBiz.getAll());
map.put("plist", Contant.getPosts());
return "employee_update";
}
@RequestMapping("/update")
public String update(Employee employee){
employeeBiz.edit(employee);
return "redirect:list";
}
@RequestMapping(value = "/remove",params = "sn")
public String remove(String sn){
employeeBiz.remove(sn);
return "redirect:list";
}
}
(4)、页面
页面的JSP请看最后的源码,这里我就不展示了
效果展示:
3、登录及个人中心
(1)、登录的biz接口与实现类
GlobalBiz:
public interface GlobalBiz {
Employee login(String sn, String password); //登录账户
void changePassword(Employee employee); //修改密码
}
GlobalBizImpl.java:
@Service("globalBiz")
public class GlobalBizImpl implements GlobalBiz {
@Autowired
private EmployeeDao employeeDao;
public Employee login(String sn, String password) {
Employee employee = employeeDao.select(sn);
if(employee != null && employee.getPassword().equals(password)){
return employee;
}
return null;
}
public void changePassword(Employee employee) {
employeeDao.update(employee);
}
}
便完成了biz业务层的登录程序,下面继续编写web表现层的登录程序。
(2)、控制器
简单了解:
@RequestParam从请求中提取查询参数,表单参数甚至文件.
转载一下这位大佬博主解释得很透彻了, 点我:彻底弄清楚session是什么?
GloablController.java:
@Controller("globalController")
public class GloablController {
@Autowired
private GlobalBiz globalBiz;
@RequestMapping("/to_login")
public String toLogin(){
return "login";
}
@RequestMapping("/login")
public String login(HttpSession session, @RequestParam String sn, @RequestParam String password){ //从jsp获取sn和password
Employee employee = globalBiz.login(sn,password); //调用login方法判断用户是否存在
if (employee == null) {
return "redirect:to_login";
}
session.setAttribute("employee",employee);
return "redirect:self";
}
@RequestMapping("/self")
public String self(){
return "self";
}
}
(3)、登录拦截器
oa_web/…/com.oa.global.LoginInterceptor.java:
public class LoginInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
//获取URL判断是否拦截
String url = httpServletRequest.getRequestURI();
if(url.toLowerCase().indexOf("login") >= 0){
return true;
}
//判断当前状态是否登录
HttpSession session = httpServletRequest.getSession();
if(session.getAttribute("employee") != null){
return true;
}
httpServletResponse.sendRedirect("/to_login");
return false;
}
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
拦截器的程序就是这样,但要想其生效就需要在spring中进行配置并进行所有拦截(spring-web.xml):
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.oa.global.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
登录页面login.jsp、self.jsp便不在这里展示了,请到最后看源码
(4)、退出
控制器:oa_web/…/com.oa.global.LoginInterceptor.java:
@RequestMapping("/quit")
public String quit(HttpSession session){
session.setAttribute("employee",null);
return "redirect:to_login";
}
(5)、修改密码
控制器:oa_web/…/com.oa.global.LoginInterceptor.java:
@RequestMapping("/to_change_password")
public String toChangePassword(){
return "change_password";
}
@RequestMapping("/change_password")
public String changePassword(HttpSession session, @RequestParam String old, @RequestParam String new1 ,@RequestParam String new2){
Employee employee = (Employee)session.getAttribute("employee");
if(employee.getPassword().equals(old)){
if(new1.equals(new2)){
employee.setPassword(new1);
globalBiz.changePassword(employee);
return "redirect:self";
}
}
return "redirect:to_change_password";
}
修改密码的页面change_password.jsp便不展示了,最后请参考源码,效果展示:
终于到了最后一个模块也是最核心最复杂的模块报销单处理,下面我们便开始吧。
4、报销单处理
(1)、报销单的持久层处理
先从持久层入手,上面我们关于持久层的大部分都已经实现了,但还需要添加一些关联对象的属性:
oa_dao/…/com.oa.entity.ClaimVoucher.java:(创建者、处理人)(get、set)
private Employee creater; //创建者
private Employee dealer; //处理人
DealRecord.java的dealSn是处理人编号,但处理人在表现层呈现的时候不能为编号,所以Employee dealer进行传递(get、set):
private Employee dealer;
dao接口已经在上面实现了,下面便是关于Sql映射文件:
ClaimVoucherDao.xml:
下面这两个就没有什么特殊性,所以就不解释了
下面是报销单条目的Sql映射文件ClaimVoucherItemDao.xml:
再下面是处理记录的Sql映射文件DealRecordDao.xml:
在这里关于oa_dao持久层的程序实现就完成了,下面便开始报销单的功能实现,业务层与表现层交际的实行
(2)、填写报销单与报销单详情
首先是实现oa_biz业务层的开发:
biz接口:
ClaimVoucherBiz:
public interface ClaimVoucherBiz {
//保存报销单
void save(ClaimVoucher claimVoucher, List<ClaimVoucherItem> items);
//获取报销单对象
ClaimVoucher get(int id);
//获取报销单条目
List<ClaimVoucherItem> getItems(int cvid);
//审核记录
List<DealRecord> getRecords(int cvid);
}
还有实现类:
@Qualifier 注释:
可能会有这样一种情况,当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。
ClaimVoucherBizImpl.java:
@Service("cliamVoucherBiz")
public class ClaimVoucherBizImpl implements ClaimVoucherBiz {
@Qualifier("claimVoucherDao")
@Autowired
private ClaimVoucherDao claimVoucherDao;
@Qualifier("claimVoucherItemDao")
@Autowired
private ClaimVoucherItemDao claimVoucherItemDao;
@Qualifier("dealRecordDao")
@Autowired
private DealRecordDao dealRecordDao;
public void save(ClaimVoucher claimVoucher, List<ClaimVoucherItem> items) {
claimVoucher.setCreateTime(new Date()); //设置创建时间
claimVoucher.setNextDealSn(claimVoucher.getCreateSn()); //设置待处理人(创建者)
claimVoucher.setStatus(Contant.CLAIMVOUCHER_CREATED); //设置状态(新创建)
//保存到数据库
claimVoucherDao.insert(claimVoucher);
for(ClaimVoucherItem item:items){
item.setClaimVoucherId(claimVoucher.getId());
claimVoucherItemDao.insert(item);
}
}
public ClaimVoucher get(int id) {
return claimVoucherDao.select(id);
}
public List<ClaimVoucherItem> getItems(int cvid) {
return claimVoucherItemDao.selectByClaimVoucher(cvid);
}
public List<DealRecord> getRecords(int cvid) {
return dealRecordDao.selectByClaimVoucher(cvid);
}
}
关于填写报销单与报销单详情业务层就完成啦,下面便进行表现层的编写:
如何获取用户填写的报销单信息呢???
oa_web/…/com.oa.dto.ClaimVoucherInfo.java:(get、set)
public class ClaimVoucherInfo {
//用一下的方式获取用户提交的信息
private ClaimVoucher claimVoucher; //报销单对象
private List<ClaimVoucherItem> items; //items集合
}
控制器:oa_web/…/com.oa.controller.ClaimVoucherController.java:
@Controller("claimVoucherController")
@RequestMapping("/claim_voucher")
public class ClaimVoucherController {
@Autowired
private ClaimVoucherBiz claimVoucherBiz;
//去填写
@RequestMapping("/to_add")
public String toAdd(Map<String,Object> map){
map.put("items", Contant.getItems()); //传递常量类所有定义的类型
map.put("info",new ClaimVoucherInfo()); //以info为键,创建ClaimVoucherInfo()
return "claim_voucher_add";
}
//填写完后保存
@RequestMapping("/add")
public String add(HttpSession session, ClaimVoucherInfo info){ //info是要与上面的创建ClaimVoucherInfo()的键想对应
Employee employee = (Employee)session.getAttribute("employee"); //创建者编号,表现层通过session拿出employee
info.getClaimVoucher().setCreateSn(employee.getSn());
claimVoucherBiz.save(info.getClaimVoucher(),info.getItems());
return "redirect:detail?id=" + info.getClaimVoucher().getId();
}
//保存完后跳转到详情界面
@RequestMapping("/detail")
public String detail(int id, Map<String,Object> map){
map.put("claimVoucher",claimVoucherBiz.get(id));
map.put("items",claimVoucherBiz.getItems(id));
map.put("records",claimVoucherBiz.getRecords(id));
return "claim_voucher_detail";
}
}
至于页面claim_voucher_add.jsp、claim_voucher_detail.jsp请参考源码。
看看视频效果:
(3)、个人报销单与待处理报销单
与上面的填写报销单与报销单详情相同,首先是实现oa_biz业务层的开发:
biz接口:
ClaimVoucherBiz:
//获取个人报销单
List<ClaimVoucher> getForSelf(String sn);
//获取待处理报销单
List<ClaimVoucher> getForDeal(String sn);
还有实现类:
ClaimVoucherBizImpl.java:
public List<ClaimVoucher> getForSelf(String sn) {
return claimVoucherDao.selectByCreateSn(sn);
}
public List<ClaimVoucher> getForDeal(String sn) {
return claimVoucherDao.selectByNextDealSn(sn);
}
关于个人报销单与待处理报销单业务层就完成啦,下面便进行表现层的编写:
控制器:oa_web/…/com.oa.controller.ClaimVoucherController.java:
//个人报销单
@RequestMapping("/self")
public String self(HttpSession session, Map<String,Object> map){
Employee employee = (Employee)session.getAttribute("employee"); //获取当前用户的编号
map.put("list",claimVoucherBiz.getForSelf(employee.getSn()));
return "claim_voucher_self";
}
//待处理报销单
@RequestMapping("/deal")
public String deal(HttpSession session, Map<String,Object> map){
Employee employee = (Employee)session.getAttribute("employee"); //获取当前用户的编号
map.put("list",claimVoucherBiz.getForDeal(employee.getSn()));
return "claim_voucher_deal";
}
至于页面claim_voucher_self.jsp、claim_voucher_deal.jsp请参考源码。
看看视频效果:
(4)、修改报销单
与上面相同,首先是实现oa_biz业务层的开发:
biz接口:
ClaimVoucherBiz:
//修改报销单
void update(ClaimVoucher claimVoucher, List<ClaimVoucherItem> items);
还有实现类:
ClaimVoucherBizImpl.java:
public void update(ClaimVoucher claimVoucher, List<ClaimVoucherItem> items) {
claimVoucher.setNextDealSn(claimVoucher.getCreateSn());
claimVoucher.setStatus(Contant.CLAIMVOUCHER_CREATED);
claimVoucherDao.update(claimVoucher);
List<ClaimVoucherItem> olds = claimVoucherItemDao.selectByClaimVoucher(claimVoucher.getId()); //获取数据库已有的条目集合
//删除我不要的条目
for(ClaimVoucherItem old : olds){ //迭代其集合
boolean isHave = false;
for(ClaimVoucherItem item : items){ //寻找条目集合是否存在
if(item.getId() == old.getId()){
isHave = true;
break;
}
}
if(!isHave){
claimVoucherItemDao.delete(old.getId()); //在迭代和新的集合都没有找到,所以删掉这个条目
}
}
for(ClaimVoucherItem item : items){
item.setClaimVoucherId(claimVoucher.getId());
if(item.getId() != null && item.getId() > 0){
//更新已经有了的条目
claimVoucherItemDao.update(item);
}else{
//插入之前不存在的新条目
claimVoucherItemDao.insert(item);
}
}
}
关于个人报销单与待处理报销单业务层就完成啦,下面便进行表现层的编写:
控制器:oa_web/…/com.oa.controller.ClaimVoucherController.java:
@RequestMapping("/to_update")
public String toUpdate(int id,Map<String,Object> map){
map.put("items", Contant.getItems());
ClaimVoucherInfo info =new ClaimVoucherInfo();
info.setClaimVoucher(claimVoucherBiz.get(id)); //通过业务处理对象使用get(id)来获取报销单
info.setItems(claimVoucherBiz.getItems(id)); //获取条目信息
map.put("info",info);
return "claim_voucher_update";
}
@RequestMapping("/update")
public String update(HttpSession session, ClaimVoucherInfo info){
Employee employee = (Employee)session.getAttribute("employee");
info.getClaimVoucher().setCreateSn(employee.getSn());
claimVoucherBiz.update(info.getClaimVoucher(),info.getItems());
return "redirect:deal";
}
至于页面claim_voucher_update.jsp请参考源码,效果展示:
(5)、提交报销单
提交报销单的持久层的操作在部门管理和员工管理就已经完成了,下面我们来进行oa_biz业务层:
biz接口:
ClaimVoucherBiz:
//提交报销单
void submit(int id);
还有实现类:
ClaimVoucherBizImpl.java:
@Qualifier("employeeDao")
@Autowired
private EmployeeDao employeeDao;
public void submit(int id) {
ClaimVoucher claimVoucher = claimVoucherDao.select(id);
Employee employee = employeeDao.select(claimVoucher.getCreateSn());
//报销单状态更新
claimVoucher.setStatus(Contant.CLAIMVOUCHER_SUBMIT);
//通过部门(同部门)与职位(部门经理),设置待处理人编号
claimVoucher.setNextDealSn(employeeDao.selectByDepartmentAndPost(employee.getDepartmentSn(),Contant.POST_FM).get(0).getSn());
//更新
claimVoucherDao.update(claimVoucher);
//进行记录保存
DealRecord dealRecord = new DealRecord();
dealRecord.setDealWay(Contant.DEAL_SUBMIT); //设置提交参数
dealRecord.setDealSn(employee.getSn()); //设置处理人(当前员工)
dealRecord.setClaimVoucherId(id); //报销单编号
dealRecord.setDealResult(Contant.CLAIMVOUCHER_SUBMIT); //设置提交后的状态
dealRecord.setDealTime(new Date()); //设置当前时间
dealRecord.setComment("无"); //备注设置为无
dealRecordDao.insert(dealRecord); //保存
}
关于个人报销单与待处理报销单业务层就完成啦,下面便进行表现层的编写:
控制器:oa_web/…/com.oa.controller.ClaimVoucherController.java:
@RequestMapping("/submit")
public String submit(int id){
claimVoucherBiz.submit(id);
return "redirect:deal";
}
效果展示:
(6)、审核报销单与打款
与上面相同,首先是实现oa_biz业务层的开发:
biz接口:
ClaimVoucherBiz:
//审核报销单
void deal(DealRecord dealRecord);
还有实现类:
ClaimVoucherBizImpl.java:
public void deal(DealRecord dealRecord) {
ClaimVoucher claimVoucher = claimVoucherDao.select(dealRecord.getClaimVoucherId());
Employee employee = employeeDao.select(dealRecord.getDealSn());
//设置处理时间
dealRecord.setDealTime(new Date());
if(dealRecord.getDealWay().equals(Contant.DEAL_PASS)){
//金额小于5000或者处理报销单的人是总经理就不需要复审
if(claimVoucher.getTotalAmount()<=Contant.LIMIT_CHECK || employee.getPost().equals(Contant.POST_GM)){
//修改报销单状态:已审核
claimVoucher.setStatus(Contant.CLAIMVOUCHER_APPROVED);
//设置待处理人为财务
claimVoucher.setNextDealSn(employeeDao.selectByDepartmentAndPost(null,Contant.POST_CASHIER).get(0).getSn());
//设置处理结果
dealRecord.setDealResult(Contant.CLAIMVOUCHER_APPROVED);
}else{ //需要复审的
claimVoucher.setStatus(Contant.CLAIMVOUCHER_RECHECK);
claimVoucher.setNextDealSn(employeeDao.selectByDepartmentAndPost(null,Contant.POST_GM).get(0).getSn());
dealRecord.setDealResult(Contant.CLAIMVOUCHER_RECHECK);
}
}else if(dealRecord.getDealWay().equals(Contant.DEAL_BACK)){ //打回的操作
claimVoucher.setStatus(Contant.CLAIMVOUCHER_BACK);
claimVoucher.setNextDealSn(claimVoucher.getCreateSn());
dealRecord.setDealResult(Contant.CLAIMVOUCHER_BACK);
}else if(dealRecord.getDealWay().equals(Contant.DEAL_REJECT)){ //拒绝的操作
claimVoucher.setStatus(Contant.CLAIMVOUCHER_TERMINATED);
claimVoucher.setNextDealSn(null);
dealRecord.setDealResult(Contant.CLAIMVOUCHER_TERMINATED);
}else if(dealRecord.getDealWay().equals(Contant.DEAL_PAID)){ //打款的操作
claimVoucher.setStatus(Contant.CLAIMVOUCHER_PAID);
claimVoucher.setNextDealSn(null);
dealRecord.setDealResult(Contant.CLAIMVOUCHER_PAID);
}
//报销单更新
claimVoucherDao.update(claimVoucher);
//审核记录插入
dealRecordDao.insert(dealRecord);
}
关于个人报销单与待处理报销单业务层就完成啦,下面便进行表现层的编写:
控制器:oa_web/…/com.oa.controller.ClaimVoucherController.java:
@RequestMapping("/to_check")
public String toCheck(int id,Map<String,Object> map){
map.put("claimVoucher",claimVoucherBiz.get(id));
map.put("items",claimVoucherBiz.getItems(id));
map.put("records",claimVoucherBiz.getRecords(id));
DealRecord dealRecord = new DealRecord();
dealRecord.setClaimVoucherId(id);
map.put("record",dealRecord);
return "claim_voucher_check";
}
@RequestMapping("/check")
public String check(HttpSession session, DealRecord dealRecord){
Employee employee = (Employee)session.getAttribute("employee");
dealRecord.setDealSn(employee.getSn()); //设置当前处理人
claimVoucherBiz.deal(dealRecord); //调用处理方法审核报销单
return "redirect:deal";
}
至于页面claim_voucher_check.jsp请参考源码,效果展示:
在此所以的业务功能就完成啦ヾ(o´∀`o)ノ
三、总结与源码
总结:
开发过程中遇到的bug:
严重: Servlet.service() for servlet [SpringMVC] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
这个报错是在开发修改报销单的时候出现的,百度了很多都没有找到问题所在,不知道为什么会报一个空指针异常,不断的调试和观看日志后,最后发现原来是ClaimVoucherBizImpl的update方法中判断条目是否存在时少了一个条件,原:if(item.getId() > 0),改正后:if(item.getId() != null && item.getId() > 0),害,是自己考虑不周全
难易的bug有很多这里就记录这一个(其实就是其它的bug忘记记录了)┭┮﹏┭┮
通过这个项目的实现操作对于MVC架构有了更深层的了解,在本项目中,V即是JSP为视图,C即是Controller控制器,M即是oa_dao持久层、oa_biz业务层就是对用户的请求进行实际处理,不过JSP已经很老了,日后可以用SpringBoot+Trymeleaf+Vue进行代替。
在此项目中的功能有很多不足,如对于部门、员工的添加、删除、修改没有特指谁可以进行,所以导致只要是该公司人员就可进行操作,还有很多方面可以完善项目功能,请自由发挥ヾ(◍°∇°◍)ノ゙
源码:(*ω)
点个赞呗,创作不易٩( ‘ω’ )و 蟹蟹!