您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關如何使用SSM實現簡單工作流系統,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
HRSystem: https://github.com/sysuKinthon/HRSystem (包含項目初始項目和原始SSH項目)
HRSystem_Dev: https://github.com/sysuKinthon/HRSystem_Dev (包含完整的SSM項目代碼)
上面的項目都是idea項目,下載到相應的項目后,運行步驟如下:
在IDEA中打開
建立下數據庫,數據庫使用MySQL,sql文件在src/main/resource目錄下,叫做schema.sql。
修改下項目中的spring-mybatis.xml里面關于數據庫的配置,主要修改用戶名和密碼。
上述都配置好后,再配置一下tomcat就可以了。
1)應用背景
項目實現的是一個簡單的工作流系統,該系統包含公司日常的事務:日常考勤,工資結算及簽核申請等。該簽核系統完成了考勤改變申請的送簽,以及對申請的簽核,這是一種簡單的工作流,,可以提高企業的生產效率,另外,本應用額外的打卡系統,自動工資結算等,也可以在一定程度上提高企業的生產效率。
2)系統功能介紹
系統的用戶主要分為兩種:普通員工和經理。
普通員工的功能包括
員工通過打卡完成每天上下班的考勤記錄,考勤記錄包括遲到,早退,曠工等;
員工也可以查看本人最近3天的考勤情況;
如果發現考勤情況與實際不符,可提出申請,申請將由系統自動轉發給員工經理,如果經理通過核準,則此申請自動生效,系統將考勤必為實際的情況;
員工也可以查看自己的工資記錄
經理的功能包括
包含上述普通員工的功能,但是經理的考勤不能提出申請;
簽核員工申請
新增員工
查看管理的全部員工
查看員工的上月工資
3)系統結構
表現層:由JSP頁面組成
MVC層:使用MVC框架技術(Spring MVC)
業務邏輯層:主要由Spring IoC容器管理的業務邏輯組件組成(門面模式)
DAO層:由6個DAO組件組成
領域對象層:由7個持久化對象組成(使用貧血模式設計)
數據庫服務層:使用MySQL數據庫存儲持久化數據
4)系統功能模塊
本系統可以大致分為兩個模塊:經理模塊和員工模塊,其主要業務邏輯通過EmpManagerService和MgrManagerService兩個業務邏輯組件來實現,因此可以使用這兩個業務組件來封裝DAO組件。
系統以業務邏輯組件作為DAO組件的門面,封裝這些DAO組件,業務邏輯組件底層依賴于這些DAO組件,向上實現系統的業務邏輯功能
本系統主要有如下6個DAO對象
ApplicationDao:提供對application_inf表的基本操作
AttendDao:提供對attend_inf表的基本操作
AttendTypeDao:提供對attend_type_inf表的基本操作
CheckbackDao:提供對checkback_inf表的基本操作
EmployeeDao:提供對employee_inf表的基本操作
PaymentDao:提供對payment_inf表的基本操作
系統提供了兩個業務邏輯組件:
EmpManagerSerivce:提供Employee角色所需業務邏輯功能的實現
MgrManagerService:提供Manager角色所需業務邏輯功能的實現
1)設計持久化實體
面向對象分析,是指根據系統需求提取應用中的對象,將這些對象抽象成類,再抽取出需需要持久化保存的類,這些需要持久化保持的類就是持久化對象(PO)。
本項目一共設計了7個持久化類
Application: 對應普通員工的考勤提出申請,包含申請理由,是否被批復及申請改變的類型等屬性
Attend: 對應每天的考勤,包含考勤時間,考勤員工,是否上班及考勤類別等信息
AttendType: 對應考勤的類別,包含考勤的名稱,如遲到,早退等
Checkback: 對應批復,包含該批復對應的申請,是否通過申請,由哪個經理完成批復等屬性
Employee: 對應系統的員工信息,包含員工的用戶名,密碼,工資以及對應的經理等屬性
Manager: 對應系統的經理信息,公包含經理管理的部門名。實際上,Manager繼承了Employee類,因此該類同樣包含Employee的所有屬性
Payment: 對應每月所發的薪水信息,包含發薪月份,領薪員工和薪資數等信息
本應用采用貧血模式來設計它們,所以持久化類中不提供業務邏輯方法,而是將所有的業務邏輯方法放到業務邏輯組件中實現。
當采用貧血模式的架構模型時,系統中的領域對象十分簡潔,它們都是單純的數據類,不需要考慮到底應該包含哪些業務邏輯方法,因此開發起來非常便捷;而系統的所有的業務邏輯都由業務邏輯組件負責實現,可以將業務邏輯的變化限制在業務邏輯層內,從而避免擴散到兩個層,因此降低了系統的開發難度。
7個PO的關系如下:
Employee是Manager的父類,同時Manager和Employee之間存在 1-N的關系,即一個Manager對應多個Employee,但每個Employee只能對應一個Manager
Employee和Payment之間是1-N的關系,即每個員工可以多次領取薪水
Employee和Attend之間存在1-N的關系,即每個員工可以參與多次考勤,但每次考勤只對應一個員工
Manager繼承了Employee類,因此具有Employee的全部屬性,另外Manager還不慌不忙 Checkback之間存在1-N的關系
Application與Attend之間存在N-1的關系,即每個Attend可以被對應多次申請。
Application與AttendType之間存在N-1的關系,即每次申請都有明確的考勤類型,而一個考勤類型可以對應多個申請
Attend與AttendType之間存在N-1的關系,即每個Attend只屬于一個AttendType。
根據上面書寫如下的schema.sql,也就是數據庫文件
CREATE DATABASE IF NOT EXISTS hrSystem COLLATE = 'utf8_general_ci' CHARACTER SET = 'utf8'; use hrSystem; create table attend_type_inf ( type_id int auto_increment, amerce_amount double not null, type_name varchar(50) not null, primary key(type_id) ); create table employee_inf ( emp_id int auto_increment, emp_type int, emp_name varchar(50) not null, emp_pass varchar(50) not null, emp_salary double not null, mgr_id int, dept_name varchar(50), primary key(emp_id), unique key(emp_name), foreign key(mgr_id) references employee_inf(emp_id) ); create table attend_inf ( attend_id int auto_increment, duty_day varchar(50) not null, punch_time datetime, is_come boolean not null, type_id int not null, emp_id int not null, primary key(attend_id), foreign key(type_id) references attend_type_inf(type_id), foreign key(emp_id) references employee_inf(emp_id) ); create table application_inf ( app_id int auto_increment, attend_id int not null, app_reason varchar(255), app_result boolean, type_id int not null, primary key(app_id), foreign key(type_id) references attend_type_inf(type_id), foreign key(attend_id) references attend_inf(attend_id) ); create table payment_inf ( pay_id int auto_increment, pay_month varchar(50) not null, pay_amount double not null, emp_id int not null, primary key(pay_id), foreign key(emp_id) references employee_inf(emp_id) ); create table checkback_inf ( check_id int auto_increment, app_id int not null, check_result boolean not null, check_reason varchar(255), mgr_id int not null, primary key(check_id), foreign key(app_id) references application_inf(app_id), foreign key(mgr_id) references employee_inf(emp_id) ); INSERT INTO `test_user` VALUES ('1', '123455@qq.com','12345', 'test'); insert into attend_type_inf ( type_name , amerce_amount) values ( '正常', 0); insert into attend_type_inf ( type_name , amerce_amount) values ( '事假', -20); insert into attend_type_inf ( type_name , amerce_amount) values ( '病假', -10); insert into attend_type_inf ( type_name , amerce_amount) values ( '遲到', -10); insert into attend_type_inf ( type_name , amerce_amount) values ( '早退', -10); insert into attend_type_inf ( type_name , amerce_amount) values ( '曠工', -30); insert into attend_type_inf ( type_name , amerce_amount) values ( '出差', 10); # 插入經理 insert into employee_inf (emp_type , emp_name , emp_pass , emp_salary , mgr_id , dept_name) values (2, 'oracle', 'oracle' , 5000 , null , 'DB部'); insert into employee_inf (emp_type , emp_name , emp_pass , emp_salary , mgr_id , dept_name) values (2, 'weblogic', 'weblogic' , 6000 , null , 'Server部'); # 員工 insert into employee_inf (emp_type , emp_name , emp_pass , emp_salary , mgr_id) values (1 , 'mysql', 'mysql' , 3000 , 1); insert into employee_inf (emp_type , emp_name , emp_pass , emp_salary , mgr_id) values (1 , 'hsql', 'hsql' , 3200 , 1); insert into employee_inf (emp_type , emp_name , emp_pass , emp_salary , mgr_id) values (1 , 'tomcat', 'tomcat' , 2800 , 2); insert into employee_inf (emp_type , emp_name , emp_pass , emp_salary , mgr_id) values (1 , 'jetty', 'jetty' , 2560 , 2);
上面已經陳述了全部的設計,剩下的一些細節將在下面的代碼實現中說明。
為了節約篇幅,下面的所有實現都只針對Employee類來說明,如有必要會針對其他進行說明
持久化實體類的存放目錄是com.kevin.HRSystem.model,下面是Employee持久化類的源碼:
package com.kevin.HRSystem.model; public class Employee { private long id; private int type; private String name; private String password; private double salary; private Manager manager; public long getId() { return id; } public void setId(long id) { this.id = id; } public int getType() { return type; } public void setType(int type) { this.type = type; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } public Manager getManager() { return manager; } public void setManager(Manager manager) { this.manager = manager; } @Override public String toString() { String rsl = "Employee [id=" + this.id + ", name=" + this.name + ", password=" + this.password + ", type=" + this.type + ", salary=" + this.salary; if(this.manager != null) { rsl += ", managerId=" + this.manager.getId() + ", managerName=" + this.manager.getName() + "]"; } return rsl; } }
在這里針對Employee.java和Manager.java進行一點說明,這兩個的數據庫表都是employee_inf,但是Employee沒有dept_name(部門名稱),而Manager沒有mgr_id(所屬部門經理id)
其他的model對象這里就不多說,直接參考源碼即可。
1)DAO組件的定義
在持久層之上,可以使用DAO組件再次封裝數據庫操作,這也是Java EE應用里常用的DAO模式。當使用DAO模式時,既體現了業務邏輯組件封裝DAO組件的門面模式,也可分離業務邏輯組件和DAO組件的功能:業務邏輯組件負責業務邏輯的變化,而DAO組件負責持久化技術的變化,這也正是橋接模式的使用。
引入DAO模式后,每個DAO組件包含了數據庫的訪問邏輯;每個DAO組件可對一個數據庫表完成基本的CURD等操作。
DAO模式是一種更符合軟件工程的開發方式,使用DAO模式有如下理由:
DAO模式抽象出數據訪問方式,業務邏輯組件無須理會底層的數據庫訪問細節,而只專注于業務邏輯的實現,業務邏輯組件只負責業務功能的變化
DAO將數據訪問集中在獨立的一層,所有的數據訪問都由DAO對象完成,這層獨立的DAO分離了數據訪問的實現與其他業務邏輯,使得系統更具可維護性
DAO還有助于提升系統的可移植性,獨立的DAO層使得系統能在不同的數據庫之間輕易切換,底層的數據庫實現對于業務邏輯組件是透明的。數據庫移植時僅僅影響DAO層,不同數據庫的切換也不會影響業務邏輯組件,因此提供了系統的可復用性
DAO組件提供了各持久化對象的基本的CRUD操作,而在DAO接口里則對DAO組件包含的各種CRUD方法提供了聲明。使用DAO接口的原因是:避免業務邏輯組件與特定的DAO組件耦合
盡管很多的DAO組件方法需要根據業務邏輯需求的變化而變化,但是還是有一些通用的方法,在代碼實現中,BaseDao接口包含了幾個通用的方法,其定義如下:
package com.kevin.HRSystem.dao; import java.util.List; public interface BaseDao<T> { void save(T entity); //保持持久化實例 void delete(long id); //根據主鍵刪除持久化實例 void update(T entity); //更新持久化實例 T findById(long id); //根據主鍵加載持久化實例 List<T> findAll(); //獲取數據表中全部的持久化實例 }
DAO接口無須給出任何實現,僅僅是DAO組件包含的CRUD方法的定義,這些方法定義的實現取決于底層的持久化技術,DAO組件的實現既可以使用傳統的JDBC,也可以采用Hibernate,MyBatis等技術。
如下是我們的EmployeeDao組件接口的源碼
package com.kevin.HRSystem.dao; import com.kevin.HRSystem.model.Employee; import com.kevin.HRSystem.model.Manager; import org.apache.ibatis.annotations.Param; import java.util.List; public interface EmployeeDao extends BaseDao<Employee>{ /** * 根據用戶名和密碼查詢員工 * @param name 用戶名 * password 密碼 * @return 返回員工 */ public Employee findByNameAndPass(@Param("name")String name, @Param("password") String password); public Employee findByName(@Param("name")String name); public List<Employee> findEmployeesByMgrId(@Param("id") long id); public void saveAsEmployee(Employee employee); public void saveAsManager(Manager manager); }
其他的DAO組件接口的源碼請直接查看代碼;這里有一個要明確的,就是持久化對象里面有Employee和Manager,但是DAO組件里面只有EmployeeDAO,因為這兩個持久化對象對應的其實是同一個數據庫表,在實現的過程中,我將兩個持化對象的CRUD整合到同一個DAO組件里面了。
DAO接口只定義了DAO組件應該實現的方法,但如何實現這些DAO方法則沒有任何限制,程序可以使用任何持久化技術來實現它們,這樣就可以讓DAO組件來負責持久化技術這個維度的變化,當系統需要在不同的持久化技術之間遷移時,應用只需要提供不同的DAO實現類即可,程序的其他部分無須進行任何改變,這就很好地提高了系統的可擴展性。
2)DAO組件的實現
在本項目中,DAO層的實現是使用了MyBatis技術,在基礎項目中, 我們已經在Spring中引入了MyBatis,其配置文件(spring-mybatis.xml)如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!--掃描service包下所有注解的類型--> <context:component-scan base-package="com.kevin.HRSystem.service"/> <!--配置數據庫相關參數properties的屬性--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--配置數據源--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="maxPoolSize" value="${c3p0.maxPoolSize}"/> <property name="minPoolSize" value="${c3p0.minPoolSize}"/> <property name="autoCommitOnClose" value="${c3p0.autoCommitOnClose}"/> <property name="checkoutTimeout" value="${c3p0.checkoutTimeout}"/> <property name="acquireRetryAttempts" value="${c3p0.acquireRetryAttempts}"/> </bean> <!--配置sqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!--掃描Model包,使用別名--> <property name="typeAliasesPackage" value="com.kevin.HRSystem.model"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <!--配置掃描DAO接口包,動態實現Dao接口,注入到spring容器中--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <property name="basePackage" value="com.kevin.HRSystem.dao"/> </bean> <!--配置事務管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入數據庫連接池--> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
根據上面的配置文件可以看出,我們的DAO層實現類是放在了classpath:mapper路徑下面的,這里我們一樣只給出EmployeeDao組件的實現,
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kevin.HRSystem.dao.EmployeeDao"> <insert id="save" parameterType="Employee" useGeneratedKeys="true" keyProperty="id"> INSERT INTO employee_inf(emp_type, emp_name, emp_pass, emp_salary, mgr_id, dept_name) VALUES (#{type}, #{name}, #{password}, #{salary}, #{manager.id}, #{departmentName}) </insert> <insert id="saveAsEmployee" parameterType="Employee" useGeneratedKeys="true" keyProperty="id"> INSERT INTO employee_inf(emp_type, emp_name, emp_pass, emp_salary, mgr_id) VALUES (#{type}, #{name}, #{password}, #{salary}, #{manager.id}) </insert> <insert id="saveAsManager" parameterType="Manager" useGeneratedKeys="true" keyProperty="id"> INSERT INTO employee_inf(emp_type, emp_name, emp_pass, emp_salary, dept_name) VALUES (#{type}, #{name}, #{password}, #{salary}, #{departmentName}) </insert> <delete id="delete" parameterType="long"> DELETE FROM employee_inf WHERE emp_id=#{id} </delete> <update id="update" parameterType="Employee"> UPDATE employee_inf SET emp_name=#{name}, emp_pass=#{password}, emp_salary=#{saraly}, emp_type=#{type}, mgr_id=#{manager.id} WHERE emp_id=#{id} </update> <!--查詢語句 start--> <select id="findAll" resultMap="employeeResultMap"> SELECT * FROM employee_inf </select> <select id="findById" parameterType="long" resultMap="employeeResultMap"> SELECT * FROM employee_inf WHERE emp_id=#{id} </select> <select id="findByNameAndPass" resultMap="employeeResultMap"> SELECT * FROM employee_inf WHERE emp_name=#{name} and emp_pass=#{password} </select> <select id="findByName" resultMap="employeeResultMap"> SELECT * FROM employee_inf WHERE emp_name=#{name} </select> <!--根據mgr_id查找員工--> <select id="findEmployeesByMgrId" parameterType="long" resultMap="employeeResultMap"> SELECT * FROM employee_inf WHERE mgr_id=#{id} </select> <!--查詢語句 end --> <!-- resultMap設置,使用鑒別器來區分employee和manager--> <resultMap id ="employeeResultMap" type="com.kevin.HRSystem.model.Employee"> <id property="id" column="emp_id"/> <result property="type" column="emp_type"/> <result property="name" column="emp_name"/> <result property="password" column="emp_pass"/> <result property="salary" column="emp_salary"/> <discriminator javaType="int" column="emp_type"> <case value="1" resultMap="originalEmployeeResultMap"/> <case value="2" resultMap="managerResultMap"/> </discriminator> </resultMap> <resultMap id="originalEmployeeResultMap" type="com.kevin.HRSystem.model.Employee" extends="employeeResultMap"> <association property="manager" javaType="com.kevin.HRSystem.model.Manager"> <id property="id" column="mgr_id"/> </association> </resultMap> <resultMap id="managerResultMap" type="com.kevin.HRSystem.model.Manager" extends="employeeResultMap"> <result property="departmentName" column="dept_name"/> <collection property="employees" column="emp_id" ofType="com.kevin.HRSystem.model.Employee" select="com.kevin.HRSystem.dao.EmployeeDao.findEmployeesByMgrId"/> </resultMap> <!--基礎resultMap end--> <select id="selectByIdWithForeign" parameterType="long" resultMap="employeeWithForeignResultMap"> SELECT e.emp_id, e.emp_name, e.emp_pass, e.emp_type, e.emp_salary, m.emp_id mgr_id, m.emp_name mgr_name, m.dept_name FROM employee_inf e, employee_inf m WHERE e.mgr_id = m.emp_id and e.emp_id = #{id} </select> <resultMap id ="employeeWithForeignResultMap" type="com.kevin.HRSystem.model.Employee"> <id property="id" column="emp_id"/> <result property="type" column="emp_type"/> <result property="name" column="emp_name"/> <result property="password" column="emp_pass"/> <result property="salary" column="emp_salary"/> <association property="manager" javaType="com.kevin.HRSystem.model.Manager"> <id property="id" column="mgr_id"/> <result property="name" column="mgr_name"/> <result property="departmentName" column="dept_name"/> </association> </resultMap> </mapper>
這個xml文件就是我們EmployeeDao組件的實現類了。關于這個類的鑒別器使用可以參考 SSM項目問題與解決(還沒有出)
1)業務邏輯組件的設計
業務邏輯組件是DAO組件的門面,所以可以理解為業務邏輯組件需要依賴于DAO組件。EmpManagerService接口(業務邏輯組件之一)里定義了大量的業務方法,這些方法的實現依賴于DAO組件,由于每個業務都要涉及多個DAO操作,其DAO操作是單條數據記錄的操作,而業務邏輯方法的訪問,則需要設計多個DAO操作,因此每個業務邏輯方法可能需要涉及多條記錄的訪問
業務邏輯組件面向DAO接口編程,可以讓業務邏輯組件從DAO組件的實現中分離,因此業務邏輯組件只關系業務邏輯的實現,無須關心數據訪問邏輯的實現
EmpManagerService接口的代碼實現如下:
package com.kevin.HRSystem.service; import com.kevin.HRSystem.model.AttendType; import com.kevin.HRSystem.model.Employee; import com.kevin.HRSystem.model.Manager; import com.kevin.HRSystem.vo.AttendVo; import com.kevin.HRSystem.vo.PaymentVo; import java.util.List; public interface EmpManagerService { /** * 驗證登錄 * @param employee 登錄的身份 * @return * 登錄后的身份確認:0為登錄失敗,1為登錄emp,2為登錄mgr */ int validLogin(Employee employee); /** * 自動打卡,周一到周五,早上7點為每個員工插入曠工記錄 */ void autoPunch(); /** * 自動結算工資,每月1號,結算上個月工資 */ void autoPay(); /** * 驗證某個員工是否可以打卡,以及打卡的類型,上班打卡,還是下班打卡 * @param user 員工名 * @param dutyDay 日期 * @return 可打卡的類別 */ int validPunch(String user, String dutyDay); /** * 實現普通員工的打卡 * @param user 員工名 * @param dutyDay 打卡日期 * @param isCome 是否是上班打卡 * @return 打卡結果 */ int punch(String user, String dutyDay, boolean isCome); /** * 根據員工瀏覽自己的工資 * @param employeeName 員工名 * @return 該員工的工資列表 */ List<PaymentVo> employeeSalary(String employeeName); /** * 員工查看自己的最近三天的非正常打卡 * @param employeeName 員工名 * @return 該員工最近三天的非正常打卡 */ List<AttendVo> getUnAttend(String employeeName); /** * 返回全部的出勤類別 * @return 全部的出勤類別 */ List<AttendType> getAllType(); /** * 添加申請 * @param attId 申請的出勤ID * @param typeId 申請的類型ID * @param reason 申請的理由 * @return 添加的結果 */ boolean addApplication(int attId, int typeId, String reason); }
2)實現業務邏輯組件
業務邏輯組件負責實現系統所需的業務方法,系統有多少個業務需求,業務邏輯組件就提供多少個對應方法,本應用采用的貧血模式的架構模型,因此業務邏輯方法完全由業務邏輯組件負責實現。
業務邏輯組件只負責業務邏輯上的變化,而持久層的變化則交給DAO層負責,因此業務邏輯組件必須依賴于DAO組件。
下面是EmpManagerServiceImpl的代碼實現:
package com.kevin.HRSystem.service.impl; import com.kevin.HRSystem.dao.*; import com.kevin.HRSystem.model.*; import com.kevin.HRSystem.service.EmpManagerService; import com.kevin.HRSystem.vo.AttendVo; import com.kevin.HRSystem.vo.PaymentVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import static com.kevin.HRSystem.constant.ConstantManager.*; @Service("EmpManagerService") public class EmpManagerServiceImpl implements EmpManagerService { @Resource private ApplicationDao applicationDao; @Resource private AttendDao attendDao; @Resource private AttendTypeDao attendTypeDao; @Resource private CheckbackDao checkbackDao; @Resource private EmployeeDao employeeDao; @Resource private PaymentDao paymentDao; public int validLogin(Employee employee) { Employee employee1 = employeeDao.findByNameAndPass(employee.getName(), employee.getPassword()); if(null != employee1) { if(employee1 instanceof Manager) { System.out.println("EmpManagerService: manager"); System.out.println(employee); return LOGIN_MGR; } else { System.out.println("EmpManagerService:employee"); System.out.println(employee); return LOGIN_EMP; } } return LOGIN_FALT; } public void autoPunch() { List<Employee> employees = employeeDao.findAll(); System.out.println(employees.size()); //獲取當前時間 String dutyDay = new java.sql.Date(System.currentTimeMillis()).toString(); for(Employee employee : employees) { System.out.println(employee); //先設置出勤類型為曠工,然后真正出勤的時候由員工去修改出勤的狀態 //6表示曠工;已經插入數據庫了; 這里最好弄一個常量管理類;但為了方便先這樣吧 AttendType attendType = attendTypeDao.findById(6); Attend a = new Attend(); a.setAttendType(attendType); a.setDutyDay(dutyDay); //如果是早上,則對應上班打卡 if(Calendar.getInstance().get(Calendar.HOUR_OF_DAY) < AM_LIMIT) { a.setCome(true); } else { //下班打卡 a.setCome(false); } a.setEmployee(employee); attendDao.save(a); } } public void autoPay() { List<Employee> employees = employeeDao.findAll(); //獲取上個月時間 Calendar c = Calendar.getInstance(); c.add(Calendar.DAY_OF_MONTH, -15); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM"); String payMonth = sdf.format(c.getTime()); for(Employee employee : employees) { Payment pay = new Payment(); double amount = employee.getSalary(); //獲取出勤記錄 List<Attend> attends = attendDao.findByEmpAndMonth(employee, payMonth); //計算工資 for(Attend attend : attends) { amount += attend.getAttendType().getAmerceAmount(); } pay.setPayMonth(payMonth); pay.setEmployee(employee); pay.setPayAmount(amount); paymentDao.save(pay); } } @Override public int validPunch(String user, String dutyDay) { //不能查找到對應的用戶,返回無法打卡 Employee employee = employeeDao.findByName(user); if(null == employee) { return NO_PUNCH; } //打到員工指定日期的出勤記錄 List<Attend> attends = attendDao.findByEmpAndDutyDay(employee, dutyDay); //系統沒有為用戶在當前插入空打卡記錄,無法打卡 if(null == attends || attends.size() == 0) { return NO_PUNCH; } //開始上班打卡 if(attends.size() == 1 && attends.get(0).isCome() && null == attends.get(0).getPunchTime()) { return COME_PUNCH; } if(attends.size() == 1 && null == attends.get(0).getPunchTime()) { return LEAVE_PUNCH; } if(attends.size() == 2) { if(null == attends.get(0).getPunchTime() && null == attends.get(0).getPunchTime()) { return BOTH_PUNCH; } else if(null == attends.get(1).getPunchTime()) { return LEAVE_PUNCH; } else { return NO_PUNCH; } } return NO_PUNCH; } public int punch(String user, String dutyDay, boolean isCome) { Employee employee = employeeDao.findByName(user); if(null == employee) { return PUNCH_FALT; } //找到員工本次打卡對應的出勤記錄 Attend attend = attendDao.findByEmpAndDutyDayAndCome(employee, dutyDay, isCome); if(null == attend) { return PUNCH_FALT; } //如果已經打卡 if(null != attend.getPunchTime()) { return PUNCHED; } int punchHour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY); attend.setPunchTime(new Date()); //上班打卡 if(isCome) { //9點之前算正常 if(punchHour < COME_LIMIT) { //1表示正常 attend.setAttendType(attendTypeDao.findById(1)); } else if (punchHour < LATE_LIMIT) { //9點到11點之間算遲到 attend.setAttendType(attendTypeDao.findById(4)); } //11點之后算曠工,不用管,本來初始就是曠工 } else { //下班打卡 //18點之后算正常 if(punchHour >= LEAVE_LIMIT) { attend.setAttendType(attendTypeDao.findById(1)); } else if(punchHour >= LEAVE_LIMIT) { //16到18點算正常 attend.setAttendType(attendTypeDao.findById(5)); } } attendDao.update(attend); return PUNCH_SUCC; } public List<PaymentVo> employeeSalary(String employeeName) { Employee employee = employeeDao.findByName(employeeName); List<Payment> payments = paymentDao.findByEmp(employee); System.out.println(payments); List<PaymentVo> result = new ArrayList<PaymentVo>(); for(Payment payment : payments) { result.add(new PaymentVo(payment.getPayMonth(), payment.getPayAmount())); } return result; } /** * 查看自己最近三天的非正常打卡 * @param employeeName 員工名 * @return 該員工的最近三天的非正常打卡 */ public List<AttendVo> getUnAttend(String employeeName) { AttendType type = attendTypeDao.findById(1); Employee employee = employeeDao.findByName(employeeName); //獲取最近三天非正常的出勤記錄 List<Attend> attends = attendDao.findByEmpUnAttend(employee, type); List<AttendVo> result = new ArrayList<AttendVo>(); for(Attend attend : attends) { result.add(new AttendVo(attend.getId(), attend.getDutyDay(), attend.getAttendType().getTypeName(), attend.getPunchTime())); } return result; } public List<AttendType> getAllType() { return attendTypeDao.findAll(); } @Override public boolean addApplication(int attId, int typeId, String reason) { Application application = new Application(); //獲取申請需要改變的出勤記錄 Attend attend = attendDao.findById(attId); AttendType type = attendTypeDao.findById(typeId); application.setAttend(attend); application.setAttendType(type); if(reason != null) { application.setApplicationReason(reason); } applicationDao.save(application); return true; } }
這里需要介紹一下上面業務邏輯組件的一些方法,首先是autoPunch和autoPay,這兩個方法并不由客戶端直接調用,而是由任務調度來執行.
系統會在每個工作日的早上7點和下午12點時自動調用autoPunch,這個方法負責為每個員工插入一條曠工考勤記錄,所以一天就會插入兩條曠工記錄了。這樣做的原因在于,先插入記錄為曠工,然后在員工上班打卡時,就將早上7點插入的記錄的出勤類型根據出勤時間進行修改為正常或是遲到等,如果沒有打卡就是曠工了。在員工下班打卡的時候,根據下班打卡的時間將12點插入的記錄進行修改其出勤類型。
系統同樣會在每月3日為所有員工完成工資結算,這個是通過autoPay來實現的。
系統中常常有些需要自動執行的任務,這些任務可能每隔一段時間就要執行一次,也可能需要在指定時間點自動執行,這些任務的自動執行必須使用任務的自動調度。通過使用開源框架Quartz,借助于它的支持,既可以實現簡單的任務調度,也可以實現復雜的任務調度。
1)引入Quartz
為了在Spring項目中引入Quartz, 我們需要在pom.xml中加入如下的包依賴,
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.1</version> </dependency>
2)實現任務調度
Quartz允許提供一個名為quartz.properties的配置文件,通過該配置文件,可以修改框架運行時的環境,其配置如下:
# 配置主調度器屬性 org.quartz.scheduler.instanceName = QuartzScheduler org.quartz.scheduler.instanceId = AUTO # 配置線程池 # Quartz線程池的實現類 org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool # 線程池的線程數量 org.quartz.threadPool.threadCount = 1 # 線程池里線程的優先級 org.quartz.threadPool.threadPriority = 10 # 配置作業存儲 org.quartz.jobStore.misfireThreshold = 60000 org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
接下來就實現我們的兩個作業類了,這兩個作業類都需要去繼承QuartzJobBean,PunchJob的實現如下:
package com.kevin.HRSystem.schedule; import com.kevin.HRSystem.service.EmpManagerService; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; import javax.annotation.Resource; public class PunchJob extends QuartzJobBean { //判斷作業是否執行的標志 private boolean isRunning = false; @Resource private EmpManagerService empManagerService; public void setEmpManagerService(EmpManagerService empManagerService) { this.empManagerService = empManagerService; } public void executeInternal(JobExecutionContext ctx) throws JobExecutionException { if(!isRunning) { System.out.println("開始調度自動打卡"); isRunning = true; empManagerService.autoPunch(); System.out.println("打卡結束"); isRunning = false; } } }
定義完上面的工作類后,就可以在配置文件中進行定義Bean了,新建一個spring-quartz.xml文件,其配置如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--croExpression指定cron表達式:每月3日2時啟動--> <bean id="cronTriggerPay" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean" p:cronExpression="0 0 2 3 * ? *"> <property name="jobDetail"> <!--使用嵌套Bean的方法來定義任務Bean,jobClass指定任務bean的實現類--> <bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean" p:jobClass="com.kevin.HRSystem.schedule.PayJob" p:durability="true" > <property name="jobDataAsMap"> <map> <entry key="empManagerService" value-ref="EmpManagerService"/> </map> </property> </bean> </property> </bean> <!--定義觸發器來管理任務 Bean cronExpression指定Cron表達式: 周一到周五7點和12點執行調度--> <bean id="cronTriggerPunch" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean" p:cronExpression="0 0 7,12 ? * MON-FRI"> <property name="jobDetail"> <bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean" p:jobClass="com.kevin.HRSystem.schedule.PunchJob" p:durability="true"> <property name="jobDataAsMap"> <map> <entry key="empManagerService" value-ref="EmpManagerService"/> </map> </property> </bean> </property> </bean> <!--執行實際的調度--> <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTriggerPay"/> <ref bean="cronTriggerPunch"/> </list> </property> </bean> </beans>
關于Cron表達式的使用請自行Google,其配置還是比較直觀的。
在這一層,我們使用的是SpringMVC,在初始項目中,已經配置好了相關的文件,主要是配置下spring-mvc.xml和web.xml,兩個文件的配置如下:
spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> <!-- 掃描web相關的bean --> <context:component-scan base-package="com.kevin.HRSystem.controller"/> <!-- 開啟SpringMVC注解模式 --> <mvc:annotation-driven/> <!-- 靜態資源默認servlet配置 --> <mvc:default-servlet-handler/> <!-- 配置jsp 顯示ViewResolver --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
web.xml
<?xml version="1.0" encoding="utf-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <display-name>Archetype Created Web Application</display-name> <!--編碼過濾器--> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--配置SpringMVC中心控制器 DispatcherServlet--> <!-- 配置DispatcherServlet --> <servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置springMVC需要加載的配置文件--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-*.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <!--<async-supported>true</async-supported>--> </servlet> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <!-- 匹配所有請求,此處也可以配置成 *.do 形式 --> <url-pattern>/</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
配置好上面的文件之后,我們的所有請求就會被SpringMVC所攔截進行分發了。為了節約篇幅,我們這里只陳述實現登錄業務的整體過程,其他的業務大致類似。
因為到Service層我們都已經實現了,只要定義好前端和Controller層的數據就可以了,我們先開發jsp頁面。在這里有一個要注意的,在tomcat項目中,放在應用程序目錄下的任何資源,用戶都可以通過輸入該資源的URL而直接進行訪問,如果你希望某個資源可以被Servlet訪問,但是不能被用戶訪問,那么應用把它放在WEB-INF目錄下面; 所以我們的jsp頁面是放在webapp/WEB-INF下面的,而圖片等靜態資源是放在了webapp/resources下。
為了在我們的jsp中使用jstl,我們還需要引入如下的依賴
<dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency>
首先們來看下main.jsp的源碼,當index.jsp被訪問時(歡迎頁面),請求就會被轉發到這個頁面
<%-- Created by IntelliJ IDEA. User: kevin Date: 18-3-3 Time: 下午5:34 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Java EE簡單工作流系統</title> </head> <body> <%@include file="header.jsp"%> <table width="960" align="center" background="${pageContext.request.contextPath}/resources/images/bodybg.jpg"> <tr> <td colspan="3">請單擊后面鏈接開始使用系統<a href="loginPage" rel="external nofollow" >登錄系統</a></td> </tr> <tr> <td colspan="3"> <br> <p align="center"><span class="pt11">這僅僅是一個Java EE的框架程序。應用模擬一個簡單的工作流系統。系統包含兩個角色,<br> 普通員工的功能包括員工出勤打卡,員工的人事異動申請,員工查看工資單。<br> 經理的功能包括管理部門員工,簽核申請,每月工資自動結算等。</span></p> <p align="center" class="pt11">應用模擬了簡單的工作流,使用的輕量級Java EE架構。技術包括:Struts 2.3、Spring 4.0、Hibernate 4.3、Quartz 2.2,整個應用使用Spring提供的DAO支持操作數據庫,同時利用Spring的聲明式事務。<br> 程序中的權限檢查使用Spring的AOP框架支持,也利用了Spring的任務調度抽象<br> Hibernate為底層的數據庫訪問提供支持,作為O/R Mapping框架使用。</p> <p align="center" class="pt11">本程序的源代碼隨程序一起發布,版權屬于李剛,<a href="http://www.crazyit.org" rel="external nofollow" >http://www.crazyit.org</a><br> 任何個人可用來參考學習Java EE架構,規范,但請勿在本程序的基礎上修改,用做任何商業用途。<br> 本人保留依法追究相關責任的權利。轉載和學習請保留此信息。 <br> </p> </td> </tr> </table> <%@include file="footer.jsp"%> </body> </html>
所以當我們點擊了登錄系統按鈕之后,就會發起/loginPage的請求,我們需要定義這個方法,其定義如下(在CommonController.java中):
@RequestMapping(value = "/loginPage", method = RequestMethod.GET) public ModelAndView showLoginPage() { return new ModelAndView("login", "employee", new Employee()); }
關于SpringMVC的基本使用可以參考:?鏈接?
通過上面的邏輯我們知道,我們的SpringMVC經過處理請求后,會發送login.jsp給前端,login.jsp的定義如下:
<%-- Created by IntelliJ IDEA. User: kevin Date: 18-3-3 Time: 下午5:55 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <title>登錄系統</title> </head> <body> <%@include file="header.jsp"%> <table width="960" align="center" background="${pageContext.request.contextPath}/resources/images/bodybg.jpg"> <tr> <td> 請輸入用戶名和密碼來登錄<br /> <c:if test="${error.length() > 0}"> <div class="error"> <c:out value="${error}"></c:out> </div> </c:if> <div class="center"> <form:form method="POST" action="processLogin" modelAttribute="employee"> <table> <tr> <td><form:label path="name">Name</form:label></td> <td><form:input path="name"/></td> </tr> <tr> <td><form:label path="password">Password</form:label></td> <td><form:password path="password"/></td> </tr> <tr> <td><input type="submit" value="登錄"></td> <td><input type="reset" value="重填"></td> </tr> </table> </form:form> </div> </td> </tr> </table> <%@include file="footer.jsp"%> </body> </html>
當我們輸入用戶名和密碼,并點擊登錄之后,會發起/processLogin的請求,其定義如下:
@RequestMapping(value = "/processLogin", method = RequestMethod.POST) public ModelAndView processLogin(@ModelAttribute("employee") Employee employee, HttpServletRequest request) { System.out.println(employee); System.out.println(request.getProtocol()); ModelAndView modelAndView; int result = empManagerService.validLogin(employee); String message; //登錄結果為普通員工 System.out.println(result); if(result == ConstantManager.LOGIN_EMP) { //設置Session request.getSession().setAttribute(WebConstant.USER, employee.getName()); request.getSession().setAttribute(WebConstant.LEVEL, WebConstant.EMP_LEVEL); message = "您已成功登錄系統,您的身份是普通員工"; System.out.println(message); // modelAndView = new ModelAndView("success"); modelAndView = new ModelAndView("employee/index"); modelAndView.addObject("message", message); return modelAndView; } else if(result == ConstantManager.LOGIN_MGR){ request.getSession().setAttribute(WebConstant.USER, employee.getName()); request.getSession().setAttribute(WebConstant.LEVEL, WebConstant.MGR_LEVEL); message = "您已成功登錄系統,您的身份是經理"; System.out.println(message); // modelAndView = new ModelAndView("success"); modelAndView = new ModelAndView("manager/index"); modelAndView.addObject("message", message); return modelAndView; } else { message = "用戶名與密碼不匹配,登錄失敗"; System.out.println(message); modelAndView = new ModelAndView("error"); modelAndView.addObject("message", message); return modelAndView; } }
根據校驗結果的不同,而有不同的返回頁面,假設校驗成功是一個普通員工的話,我們就返回了employee/index.jsp,并設置了message變量供jsp頁面獲取,employee/index.jsp如下:
<%-- Created by IntelliJ IDEA. User: kevin Date: 18-3-6 Time: 下午4:36 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@taglib prefix="c" uri="https://cache.yisu.com/upload/information/20200623/121/100077"> <tr height="60"> <td> </td> </tr> <tr> <td> <c:if test="${message.length()>0}"> <div class="error"> <c:out value="${message}"></c:out> </div> </c:if> </td> </tr> <tr height="80"> <td> </td> </tr> <tr> <td><%=request.getSession().getAttribute("user")%> ,歡迎您使用JavaEE簡單工作流系統,您是普通員工</td> </tr> <tr height="60"> <td> </td> </tr> </table> <%@include file="../footer.jsp"%> </body> </html>
其他頁面的開發邏輯也類似如上。
關于“如何使用SSM實現簡單工作流系統”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。