HelloWorld_Shiro Activity

引:记得去年暑假要弄工作流Activity没有弄出来,这次刚好看到了一个基于Shiro和Activity的考勤项目,就想写一下了!

技术点

  1. 前端模板引擎:Velocity(不做重点讲解,它的页面主要使用的BootStrap的Admin开源框架)
  2. 后台:SSM框架
  3. 任务调度框架:Quartz
  4. 工作流引擎:Activity
  5. 权限控制框架:Shiro

项目功能

  1. 利用Shiro实现登录模块并实现权限控制
  2. 实现查看出勤记录
  3. 利用Quartz实现定时任务,每天凌晨扫描没有打卡记录的员工并记录
  4. 利用Activity实现补签流程

项目源码

HelloWorld_Shiro Activity 之 WorkAttendance考勤项目

重点源码说明

利用Shiro实现登录与权限控制

我们这里主要利用了Shiro来进行登录验证,我们可以先看看Shiro架构,如下图:

shiro

  1. Subject主要是存储了访问信息
  2. SecurityManager是Shiro的核心,他主要用于协调Shiro内部各种安全组件
  3. Realm用于连接Shiro和客户系统的用户数据的桥梁。一旦Shiro真正需要访问各种安全相关的数据(比如使用用户账户来做用户身份验证以及权限验证)时,他总是通过调用系统配置的各种Realm来读取数据,可以对比为SpringSecurity的Provider。

Shrio配置文件主要是WorkAttendance/src/main/resources/spring-shiro.xml,在配置文件中,我们除了配置Realm和SecurityManager,还要配置一个ShiroFilter过滤链,这个过滤链主要配置要拦截或者忽略的路径,可以对比为SpringSecurity的配置类WebSecurityConfigurerAdapter。当然我们也可以使用注解来实现路径权限控制,如: @RequiresPermissions(“attend:attendList”)

我们看看登录源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// LoginController的验证登录的方法
public String checkLogin(HttpServletRequest request) throws UnsupportedEncodingException, NoSuchAlgorithmException {
String username = request.getParameter("username");
String pwd = request.getParameter("password");
// 组装成Token给Realm使用
UsernamePasswordToken token = new UsernamePasswordToken(username,pwd);
// 等到登录访问信息
Subject subject = SecurityUtils.getSubject();
try {
// 进入Realm进行认证
subject.login(token);
// 设置session过期时间
SecurityUtils.getSubject().getSession().setTimeout(1800000);
} catch (Exception e) {
return "login_fail";
}
return "login_succ";
}

// 自定义的Realm
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;

// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// PrincipalCollection,可以理解身份上下文
String username = (String) principalCollection.getPrimaryPrincipal();
User user = userService.findUserByUserName(username);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
for(Role role :user.getRoleList()){
authorizationInfo.addRole(role.getRole());
for(Permission permission :role.getPermissionList()){
authorizationInfo.addStringPermission(permission.getPermission());
}
}
return authorizationInfo;
}

// 登入验证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// UsernamePasswordToken封装了用户名和密码
UsernamePasswordToken usernamePasswordToke = (UsernamePasswordToken)authenticationToken;
String username = usernamePasswordToke.getUsername();
User user = userService.findUserByUserName(username);
if(user==null){
return null;
}else {
// 会使用自定义的账号密码校验器进行验证,并返回AuthenticationInfo
AuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
SecurityUtils.getSubject().getSession().setAttribute("userInfo",user);
return info;
}
}
}

// 自定义的密码验证器,配置文件中配置
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {
// 对比用户登入的数据与数据库查询出来的信息
try {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
// 用户登录的密码
String password = String.valueOf(usernamePasswordToken.getPassword());
Object tokenCredentials = MD5Utils.encryptPassword(password);
// getCredentials()和equals都是SimpleCredentialsMatcher自带的方法
Object acountCredentials = getCredentials(authenticationInfo);
return equals(tokenCredentials,acountCredentials);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return false;
}
}

利用Quartz实现定时任务

它的核心元素关系图如下:

quartz

它的配置文件在WorkAttendance/src/main/resources/spring-job.xml,我们也主要就是配置SchedulerFactory,Trigger和Job。里面关于cronExpression可以使用在线Cron表达式生成器来确定或者书写。

我们看看定时任务的源代码:

1
2
3
4
5
6
7
8
9
10
// 其实很简单,因为我们在配置文件中以及配置好要触发的类和方法了
public class AttendCheckTask {

@Autowired
private AttendService attendService;
// JobMethod
public void checkAttend() {
attendService.checkAttend();
}
}

利用Activity实现补签流程

它的配置文件主要在WorkAttendance/src/main/resources/spring-activity.xml,我们主要就是配置processEngineFactory,processEngineConfiguration(会设置自动建表23张以及要部署的流程资源)以及各个服务类。

关于流程资源我们可以利用IDEA的actiBPM插件去画这个流程图,需要注意的是设置好各个ID,因为我们后面需要用到它。

接下来我们看看它的核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 1. 开始流程并提交任务
public void startReAttendFlow(ReAttend reAttend) {
// 从公司组织架构中,查询到此人上级领导人用户名
// 这里手动设置
reAttend.setCurrentHandler("rex666");
reAttend.setStatus(RE_ATTEND_STATUS_ONGOING);
// 插入数据库补签表(ID自增)
reAttendMapper.insertSelective(reAttend);
// 将一些需要的参数放入流程中传递,即Variables
Map<String, Object> map = new HashMap<String, Object>();
map.put(RE_ATTEND_SIGN, reAttend);
// 后面需要根据流程中的变量名来获取
map.put(NEXT_HANDLER, reAttend.getCurrentHandler());
// 获得流程实例
ProcessInstance instance = runtimeService.startProcessInstanceByKey(RE_ATTEND_FLOW_ID, map);
// 获得任务
Task task = taskService.createTaskQuery().processInstanceId(instance.getId()).singleResult();
// 提交用户补签任务
taskService.complete(task.getId(), map);
}

// 2. 当前处理人获得需要处理的任务
public List<ReAttend> listTasks(String userName) {
//转换成页面实体 需要返回的对象
List<ReAttend> reAttendList = new ArrayList<ReAttend>();
List<Task> taskList = taskService.createTaskQuery().processVariableValueEquals(userName).list();
if (CollectionUtils.isNotEmpty(taskList)) {
for (Task task : taskList) {
Map<String, Object> variable = taskService.getVariables(task.getId());
ReAttend reAttend = (ReAttend) variable.get(RE_ATTEND_SIGN);
reAttend.setTaskId(task.getId());
reAttendList.add(reAttend);
}
}
return reAttendList;
}

// 3.当前处理人处理任务
@Transactional
public void approve(ReAttend reAttend) {
// 需要处理的任务
Task task = taskService.createTaskQuery().taskId(reAttend.getTaskId()).singleResult();
// 如果同意
if (("" + RE_ATTEND_STATUS_PSSS).equals(reAttend.getApproveFlag())) {
Attend attend = new Attend();
attend.setId(reAttend.getAttendId());
attend.setAttendStatus(ATTEND_STATUS_NORMAL);
// 将出勤数据的状态从异常变为正常
attendMapper.updateByPrimaryKeySelective(attend);
// 审批通过,修改补签数据状态
reAttend.setStatus(RE_ATTEND_STATUS_PSSS);
reAttendMapper.updateByPrimaryKeySelective(reAttend);
} else if (("" + RE_ATTEND_STATUS_REFUSE.toString()).equals(reAttend.getApproveFlag())) {
reAttend.setStatus(RE_ATTEND_STATUS_REFUSE);
reAttendMapper.updateByPrimaryKeySelective(reAttend);
}
// 完成任务
taskService.complete(task.getId());
}

其他

这个项目关于前端页面前端模板引擎Velocity,SSM架构,Mybatis自动生成插件以及分页方法,这里都不再多说,项目的注释应该算是很全了,认真看就行。

参考与致谢

  1. SSM实战-码码员工考勤系统