权限系统
一、部门管理
1、部门条件查询
这个部门列表不需要分页,依据我们刚才的接口分析,我们直接来编码
1)定义接口
新增类DeptController中定义新的方法
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.zzyl.base.ResponseResult;
import com.zzyl.dto.DeptDto;
import com.zzyl.service.DeptService;
import com.zzyl.vo.DeptVo;
import com.zzyl.vo.TreeVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author : like
* @date : 2024/3/23 17:59
* @Version: 1.0
*/
@Api(tags = "部门管理")
@RestController
@RequestMapping("/dept")
public class DeptController {
@Autowired
private DeptService deptService;
@ApiOperation("部门列表")
@PostMapping("/list")
@ApiOperationSupport(includeParameters = {
"deptDto.dataState","deptDto.deptName","deptDto.parentDeptNo"
}) // 设置显示的字段
public ResponseResult<List<DeptVo>> deptList(@RequestBody DeptDto deptDto){
List<DeptVo> deptVoList = deptService.getList(deptDto);
return ResponseResult.success(deptVoList);
}
@PostMapping("/tree")
public ResponseResult<TreeVo> deptTree() {
TreeVo treeVo = deptService.deptTree();
return ResponseResult.success(treeVo);
}
}
其中的ApiOperationSupport是可以在swagger接口文档中说明参数包含了哪些字段,比如你定义的dto是10个字段,但是当前接口只需要6个字段,就可以把这6个字段声明出来,这样做的好处就是dto可以在多个接口中复用
2)持久层mapper
在DeptMapper中新增方法
```java
package com.zzyl.mapper;
/**
* @author sjqn
*/
@Mapper
public interface DeptMapper {
/**
* 查询列表
* @param deptDto
* @return
*/
List<DeptVo> selectList(DeptDto deptDto);
}
对应的xml映射文件
<?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.zzyl.mapper.DeptMapper">
<resultMap id="BaseResultMap" type="com.zzyl.entity.Dept">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="parent_dept_no" jdbcType="VARCHAR" property="parentDeptNo"/>
<result column="dept_no" jdbcType="VARCHAR" property="deptNo"/>
<result column="dept_name" jdbcType="VARCHAR" property="deptName"/>
<result column="sort_no" jdbcType="INTEGER" property="sortNo"/>
<result column="data_state" jdbcType="CHAR" property="dataState"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
<result column="create_by" jdbcType="BIGINT" property="createBy"/>
<result column="update_by" jdbcType="BIGINT" property="updateBy"/>
<result column="leader_id" jdbcType="BIGINT" property="leaderId"/>
<result column="leader_name" jdbcType="BIGINT" property="leaderName"/>
</resultMap>
<!--java 不能识别 ’_‘ 所以需要映射 resultmap -->
<resultMap id="BaseResultVoMap" type="com.zzyl.vo.DeptVo">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="parent_dept_no" jdbcType="VARCHAR" property="parentDeptNo"/>
<result column="dept_no" jdbcType="VARCHAR" property="deptNo"/>
<result column="dept_name" jdbcType="VARCHAR" property="deptName"/>
<result column="sort_no" jdbcType="INTEGER" property="sortNo"/>
<result column="data_state" jdbcType="CHAR" property="dataState"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
<result column="create_by" jdbcType="BIGINT" property="createBy"/>
<result column="update_by" jdbcType="BIGINT" property="updateBy"/>
<result column="leader_id" jdbcType="BIGINT" property="leaderId"/>
<result column="role_id" jdbcType="BIGINT" property="roleId"/>
<result column="create_day" jdbcType="BIGINT" property="createDay"/>
</resultMap>
<sql id="Base_Column_List">
id
, parent_dept_no, dept_no, dept_name, sort_no, data_state, create_time, update_time, create_by, update_by, remark, leader_id </sql>
<!--
例如,like concat('%',#{deptName},'%') 可以用来匹配包含特定部门名称的记录,因为 % 表示匹配任意字符。
而 like concat(#{parentDeptNo},'%') 则可以用来匹配以特定父部门编号开头的记录。
-->
<select id="selectList" parameterType="com.zzyl.dto.DeptDto" resultMap="BaseResultVoMap">
select
d.id, d.parent_dept_no, d.dept_no, d.dept_name, d.sort_no, d.data_state, d.create_time, d.update_time, d.create_by, d.update_by, d.remark, d.leader_id , u.real_name as leader_name,DATE_FORMAT(d.create_time,'%Y-%m-%d') as create_day from sys_dept d left join sys_user u on u.id = leader_id <where>
<if test="deptName!=null and deptName!=''">
and d.dept_name like concat('%',#{deptName},'%')
</if>
<if test="parentDeptNo!=null and parentDeptNo!=''">
and d.parent_dept_no like concat(#{parentDeptNo},'%')
</if>
<if test="dataState!=null and dataState!=''">
and d.data_state=#{dataState}
</if>
</where>
order by d.sort_no asc, d.create_time desc
</select>
</mapper>
由于在部门列表的展示中,需要展示部门的负责人,所以关联了sys_user用户表来查询负责人姓名,虽然现在我们还没有开发用户模块,但是用户表已经有了,在这里可以进行关联查询
-
在sys_dept表和实体类中并没有一个叫做leader_name的字段,我们需要扩展一下,只需要在Dept实体类中新增一个leaderName字段即可,并且把对应的xml映射文件中的BaseResultVoMap中定义这个字段
-
在返回的日期中,前端要求返回的是创建的天,比如是:2023-10-31,我们需要使用mysql的日期函数单独处理
-
格式化
-
select DATE_FORMAT(new(),'%Y-%m-%d %H:%i:%s')--->返回字符串:2023-10-31 01:21:00
-
select str_to_DATE('2023-10-31 01:21:00','%Y-%m-%d %H:%i:%s')--->返回日期类型
-
-
3)业务层
在DeptService中定义新的方法
```java
package com.zzyl.service;
public interface DeptService {
// 多条件
List<DeptVo> deptList(DeptDto deptDto);
}
实现类
package com.zzyl.service.impl;
/**
* @author sjqn
* @date 2023/10/30
*/
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
/**
* 多条件查询部门
* @param deptDto
* @return
*/
@Override
public List<DeptVo> deptList(DeptDto deptDto) {
return deptMapper.selectList(deptDto);
}
}
2、部门树形结构
controller
@ApiOperation("部门树形结构")
@PostMapping("/tree")
public ResponseResult<TreeVo> deptTree() {
TreeVo treeVo = deptService.deptTree();
return ResponseResult.success(treeVo);
}
封装结构
{
// TreeVo
"items": [
// TreeItemVo
{
"id": "0",
"label": "Parent",
"children": [
{
"id": "1",
"label": "Child 1",
"children": []
},
{
"id": "2",
"label": "Child 2",
"children": []
}
]
}
]
}
- 需要的vo类
/**
* 资源树显示类
*/
@Data
@NoArgsConstructor
public class TreeVo implements Serializable {
@ApiModelProperty(value = "tree数据")
private List<TreeItemVo> items;
@Builder
public TreeVo(List<TreeItemVo> items) {
this.items = items;
}
}
/**
* 资源树结构体
*/
@Data
@NoArgsConstructor
public class TreeItemVo implements Serializable {
@ApiModelProperty(value = "节点ID")
public String id;
@ApiModelProperty(value = "显示内容")
public String label;
@ApiModelProperty(value = "显示内容")
public List<TreeItemVo> children = new ArrayList<>();
@Builder
public TreeItemVo(String id, String label, List<TreeItemVo> children) {
this.id = id;
this.label = label;
this.children = children;
}
}
实现方法并且递归
// 部门树
@Override
public TreeVo deptTree() {
// 将parentDeptNo 赋值,查询条件封装
String rootDeptParentId = SuperConstant.ROOT_DEPT_PARENT_ID;
DeptDto deptDto = DeptDto.builder()
.parentDeptNo(NoProcessing.processString(rootDeptParentId))
.build();
// 调用查询方法
List<DeptVo> deptVoList = getList(deptDto);
// 查找根节点
List<TreeItemVo> items = new ArrayList<>();
DeptVo rootDept = deptVoList.stream()
.filter(d -> d.getParentDeptNo().equals(rootDeptParentId))
.collect(Collectors.toList())
.get(0);
// 递归调用 (空集合,根节点,数据)
recursionTreeItem(items,rootDept,deptVoList);
return TreeVo.builder().items(items).build();
}
// 递归
private void recursionTreeItem(List<TreeItemVo> items, DeptVo rootDept, List<DeptVo> deptVoList) {
// 根部门,封装TreeItemVo,后期向里面set子对象
TreeItemVo treeItemVo = TreeItemVo.builder()
.id(rootDept.getDeptNo())
.label(rootDept.getDeptName())
.build();
// 找子级
List<DeptVo> childrenList = deptVoList.stream()
.filter(d -> d.getParentDeptNo().equals(rootDept.getDeptNo()))
.collect(Collectors.toList());
// 不为空,执行递归
if (!EmptyUtil.isNullOrEmpty(childrenList)){
List<TreeItemVo> listChildrenList = new ArrayList<>();
childrenList.forEach(dept -> {
// 空集合,父级,父节点=子节点的父节点的数据
recursionTreeItem(listChildrenList,dept,deptVoList);
});
treeItemVo.setChildren(listChildrenList);
}
items.add(treeItemVo);
}
3、Spring Cache
对于 getList(DeptDto deptDto)
方法:
@Cacheable(value = DeptCacheConstant.LIST, key = "#deptDto.hashCode()")
表示使用名为DeptCacheConstant.LIST
的缓存区域,并且使用#deptDto.hashCode()
作为缓存的键值。- 在方法调用时,系统会首先根据
deptDto
对象的哈希值来检查缓存中是否存在对应的结果,如果存在则直接返回缓存结果,否则执行实际的方法逻辑,并将结果放入缓存中。
对于 deptTree()
方法:
@Cacheable(value = DeptCacheConstant.TREE)
表示使用名为DeptCacheConstant.TREE
的缓存区域,但未指定具体的键值生成策略。- 同样,系统会根据默认的键值生成策略来检查缓存中是否存在结果,并根据需要执行实际的方法逻辑。
对于 createDept()
表示了在 createDept
方法执行之后,会触发两个缓存区域的清除操作。具体来说:
@CacheEvict(value = DeptCacheConstant.LIST, allEntries = true)
:清除名为DeptCacheConstant.LIST
的缓存区域中的所有条目。@CacheEvict(value = DeptCacheConstant.TREE, allEntries = true)
:清除名为DeptCacheConstant.TREE
的缓存区域中的所有条目。
这样的设计可以确保在执行创建部门操作后,相关的缓存数据得到及时更新,避免脏数据或不一致的情况发生。
// 部门条件查询
@Cacheable(value = DeptCacheConstant.LIST,key = "#deptDto.hashCode()")
@Override
public List<DeptVo> getList(DeptDto deptDto) {
return null;
}
// 部门树
@Cacheable(value = DeptCacheConstant.TREE)
@Override
public TreeVo deptTree() {
return null;
}
@Transactional
@Override
@Caching(evict = {
@CacheEvict(value = DeptCacheConstant.LIST,allEntries = true),
@CacheEvict(value = DeptCacheConstant.TREE,allEntries = true)
})
public Boolean createDept(DeptDto deptDto) {
return null;
}
四、Spring Security JWT
集成
-
引入所需依赖
jwt
-
实现
UserDetail
,对应需要的数据, 实现UserDetailsService
数据库认证 -
SecurityConfig
注入自定义认证管理器AuthenticationManager
,登录接口放行 -
登录接口,所有认证管理器(会调用
UserDetailsService
),认证成功生成jwt -
jwt授权管理器
一、配置
1-1、引入依赖
<!--JWT-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!--工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.0.M3</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
1-2、视图vo类
import com.zzyl.base.BaseVo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Set;
/**
* 用户表
*/
@Data
@NoArgsConstructor
public class UserVo extends BaseVo {
@ApiModelProperty(value = "用户账号")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "用户类型(0:系统用户,1:客户)")
private String userType;
@ApiModelProperty(value = "用户昵称")
private String nickName;
@ApiModelProperty(value = "用户职位")
private String post;
@ApiModelProperty(value = "用户部门")
private String dept;
@ApiModelProperty(value = "用户邮箱")
private String email;
@ApiModelProperty(value = "真实姓名")
private String realName;
@ApiModelProperty(value = "手机号码")
private String mobile;
@ApiModelProperty(value = "用户性别(0男 1女 2未知)")
private String sex;
@ApiModelProperty(value = "备注")
private String remark;
@ApiModelProperty(value = "选中节点")
private String[] checkedIds;
@ApiModelProperty(value = "三方openId")
private String openId;
@ApiModelProperty(value = "查询用户:用户角色Ids")
private Set<String> roleVoIds;
@ApiModelProperty(value = "构建令牌:用户角色标识")
private Set<String> roleLabels;
@ApiModelProperty(value = "角色列表")
private List<RoleVo> roleList;
@ApiModelProperty(value = "构建令牌:用户权限路径")
private Set<String> resourceRequestPaths;
@ApiModelProperty(value = "部门编号【当前】")
private String deptNo;
@ApiModelProperty(value = "职位编号【当前】")
private String postNo;
@ApiModelProperty(value = "角色Id【当前】")
private Long roleId;
@ApiModelProperty(value = "用户令牌")
private String userToken;
private String dataState;
@Builder
public UserVo(Long id, String dataState, String username, String password, String userType, String nickName, String post, String dept, String email, String realName, String mobile, String sex, String remark, String[] checkedIds, String openId, Set<String> roleVoIds, Set<String> roleLabels, List<RoleVo> roleList, Set<String> resourceRequestPaths, String deptNo, String postNo, Long roleId, String userToken, String dataState1) {
super(id, dataState);
this.username = username;
this.password = password;
this.userType = userType;
this.nickName = nickName;
this.post = post;
this.dept = dept;
this.email = email;
this.realName = realName;
this.mobile = mobile;
this.sex = sex;
this.remark = remark;
this.checkedIds = checkedIds;
this.openId = openId;
this.roleVoIds = roleVoIds;
this.roleLabels = roleLabels;
this.roleList = roleList;
this.resourceRequestPaths = resourceRequestPaths;
this.deptNo = deptNo;
this.postNo = postNo;
this.roleId = roleId;
this.userToken = userToken;
this.dataState = dataState1;
}
}
1-3、JWT工具类
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
public class JwtUtil {
/**
* 生成jwt
* 使用Hs256算法, 私匙使用固定秘钥
*
* @param secretKey jwt秘钥
* @param dateOffset jwt过期时间(小时)
* @param claims 设置的信息
* @return
*/
public static String createJWT(String secretKey , int dateOffset, Map<String, Object> claims) {
// 指定签名的时候使用的签名算法,也就是header那部分
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 设置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
// 设置过期时间
.setExpiration(DateUtil.offset(new Date(), DateField.HOUR_OF_DAY, dateOffset));
return builder.compact();
}
/**
* Token解密
*
* @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
* @param token 加密后的token
* @return
*/
public static Claims parseJWT(String secretKey, String token) {
try {
// 得到DefaultJwtParser
Claims claims = Jwts.parser()
// 设置签名的秘钥
.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
// 设置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
} catch (Exception e) {
// throw new AccessDeniedException("没有权限,请登录");
throw new RuntimeException("没有权限,请登录");
}
}
}
二、登录实现
2-1、配置类添加自定义认证管理器
import com.zzyl.properties.SecurityConfigProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
/**
* 权限核心配置类
*/
@Configuration
@EnableConfigurationProperties(SecurityConfigProperties.class)
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests()
.antMatchers("/**")
.permitAll();
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS );//关闭session
http.headers().cacheControl().disable();//关闭缓存
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
/**
* BCrypt密码编码
* @return
*/
@Bean
public BCryptPasswordEncoder bcryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
2-2、自定义UserDetails
import com.zzyl.utils.EmptyUtil;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
/**
* 自定认证用户
*/
@Data
@NoArgsConstructor
public class UserAuth implements UserDetails {
private String id;
/**
* 用户账号
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 权限内置
*/
private Collection<SimpleGrantedAuthority> authorities;
/**
* 用户类型(00系统用户)
*/
private String userType;
/**
* 用户昵称
*/
private String nickName;
/**
* 用户邮箱
*/
private String email;
/**
* 真实姓名
*/
private String realName;
/**
* 手机号码
*/
private String mobile;
/**
* 用户性别(0男 1女 2未知)
*/
private String sex;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 备注
*/
private String remark;
/**
* 部门编号【当前】
*/
private String deptNo;
/**
* 职位编号【当前】
*/
private String postNo;
public UserAuth(UserVo userVo) {
this.setId(userVo.getId().toString());
this.setUsername(userVo.getUsername());
this.setPassword(userVo.getPassword());
if (!EmptyUtil.isNullOrEmpty(userVo.getResourceRequestPaths())) {
authorities = new ArrayList<>();
userVo.getResourceRequestPaths().forEach(resourceRequestPath -> authorities.add(new SimpleGrantedAuthority(resourceRequestPath)));
}
this.setUserType(userVo.getUserType());
this.setNickName(userVo.getNickName());
this.setEmail(userVo.getEmail());
this.setRealName(userVo.getRealName());
this.setMobile(userVo.getMobile());
this.setSex(userVo.getSex());
this.setCreateTime(userVo.getCreateTime());
this.setCreateBy(userVo.getCreateBy());
this.setUpdateTime(userVo.getUpdateTime());
this.setUpdateBy(userVo.getUpdateBy());
this.setRemark(userVo.getRemark());
this.setDeptNo(userVo.getDeptNo());
this.setPostNo(userVo.getPostNo());
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
2-3、配置数据库认证
import com.zzyl.service.UserService;
import com.zzyl.vo.UserAuth;
import com.zzyl.vo.UserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
/**
* @author : like
* @date : 2024/3/24 19:37
* @Version: 1.0
*/@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//这里我们默认使用账号密码登录,对于多种登录方式如何处理-->字符串分割
UserVo userVo = userService.findUserVoForLogin(username);
return new UserAuth(userVo);
}
}
2-4、jwt配置文件
zzyl:
framework:
jwt:
base64-encoded-secret-key: $2a$10$PVtHnkj86mJgf6li/yron.LRx/cQAlaiZkBJ9BeogCNTryXJRT1YC
ttl: 3600000
读取配置文件
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.io.Serializable;
/**
* jw配置文件
*/
@Setter
@Getter
@NoArgsConstructor
@ToString
@Configuration
@ConfigurationProperties(prefix = "zzyl.framework.jwt")
public class JwtTokenManagerProperties implements Serializable {
/**
* 签名密码
*/
private String base64EncodedSecretKey;
/**
* 有效时间
*/
private Integer ttl;
}
2-5、登录实现
-
使用认证管理器,认证
-
获取用户数据
-
获取用户资源列表 获取用户角色列表
-
生成token,参数定义在 配置文件中 (敏感数据处理 )
-
存redis,使用uuid,不用将jwt传到前端,需要校验时,用uuid获取jwt
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.zzyl.constant.UserCacheConstant;
import com.zzyl.dto.LoginDto;
import com.zzyl.exception.BaseException;
import com.zzyl.properties.JwtTokenManagerProperties;
import com.zzyl.service.LoginService;
import com.zzyl.service.ResourceService;
import com.zzyl.service.RoleService;
import com.zzyl.utils.JwtUtil;
import com.zzyl.vo.ResourceVo;
import com.zzyl.vo.RoleVo;
import com.zzyl.vo.UserAuth;
import com.zzyl.vo.UserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author : like
* @date : 2024/3/24 19:33
* @Version: 1.0
*/
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private ResourceService resourceService;
@Autowired
private RoleService roleService;
@Autowired
private JwtTokenManagerProperties jwtTokenManagerProperties;
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public UserVo login(LoginDto loginDto) {
// 1.使用认证管理器,认证
UsernamePasswordAuthenticationToken upat
= new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
Authentication authenticate = authenticationManager.authenticate(upat);
if (!authenticate.isAuthenticated()){
throw new BaseException("用户登录失败");
}
// 2.获取用户数据
UserAuth userAuth = (UserAuth) authenticate.getPrincipal();
UserVo userVo = BeanUtil.toBean(userAuth, UserVo.class); // hutool 工具包
// 3.获取用户资源列表
List<ResourceVo> resourceVoList = resourceService.findResourceVoListByUserId(userVo.getId());
Set<String> resourceRequestPaths = resourceVoList.stream()
.filter(r -> "r".equals(r.getResourceType()))
.map(ResourceVo::getRequestPath)
.collect(Collectors.toSet());
userVo.setResourceRequestPaths(resourceRequestPaths);
// 3.1。获取用户角色列表
List<RoleVo> roleVoList = roleService.findRoleVoListByUserId(userVo.getId());
Set<String> roleLabelSet = roleVoList.stream()
.map(RoleVo::getLabel)
.collect(Collectors.toSet());
userVo.setRoleLabels(roleLabelSet);
userVo.setRoleList(roleVoList);
// 4.生成token,参数定义在 配置文件中
String uuidToken = UUID.randomUUID().toString();
userVo.setUserToken(uuidToken);
// 敏感数据处理
userVo.setPassword("");
Map<String,Object> clamis = new HashMap<>();
clamis.put("currentUser",JSONUtil.toJsonStr(userVo));
String jwtToken = JwtUtil.createJWT(
jwtTokenManagerProperties.getBase64EncodedSecretKey(),
jwtTokenManagerProperties.getTtl(),
clamis);
// 5.存redis,使用uuid,不用将jwt传到前端,需要校验时,用uuid获取jwt
Long ttl = Long.valueOf(jwtTokenManagerProperties.getTtl() / 1000);
String userTokenKey = UserCacheConstant.USER_TOKEN + userVo.getUsername();
redisTemplate.opsForValue().set(userTokenKey,uuidToken,ttl, TimeUnit.SECONDS);
String jwtTokenKey = UserCacheConstant.JWT_TOKEN + uuidToken;
redisTemplate.opsForValue().set(jwtTokenKey,jwtToken,ttl,TimeUnit.SECONDS);
return userVo;
}
}
三、用户数据存入线程中
3-1、ThreadLocal
工具类
package com.zzyl.utils;
import com.alibaba.fastjson.JSONObject;
import com.zzyl.base.BaseVo;
import lombok.extern.slf4j.Slf4j;
/**
* subjectContent.java * 用户主体对象
*/
@Slf4j
public class UserThreadLocal {
/***
* 创建线程局部userVO变量
*/
public static ThreadLocal<String> subjectThreadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return null;
}
};
// 提供线程局部变量set方法
public static void setSubject(String subject) {
subjectThreadLocal.set(subject);
}
// 提供线程局部变量get方法
public static String getSubject() {
return subjectThreadLocal.get();
}
//清空当前线程,防止内存溢出
public static void removeSubject() {
subjectThreadLocal.remove();
}
private static final ThreadLocal<Long> LOCAL = new ThreadLocal<>();
private UserThreadLocal() {
}
/**
* 将authUserInfo放到ThreadLocal中
*
* @param authUserInfo {@link Long}
*/ public static void set(Long authUserInfo) {
LOCAL.set(authUserInfo);
}
/**
* 从ThreadLocal中获取authUserInfo
*/ public static Long get() {
return LOCAL.get();
}
/**
* 从当前线程中删除authUserInfo
*/ public static void remove() {
LOCAL.remove();
}
/**
* 从当前线程中获取前端用户id
* @return 用户id
*/ public static Long getUserId() {
return LOCAL.get();
}
/**
* 从当前线程中获取前端后端id
* @return 用户id
*/ public static Long getMgtUserId() {
String subject = subjectThreadLocal.get();
if (ObjectUtil.isEmpty(subject)) {
return null;
}
BaseVo baseVo = JSONObject.parseObject(subject, BaseVo.class);
return baseVo.getId() ;
}
}
3-2、定义登录拦截器将数据存ThreadLocal
-
该拦截器实现了HandlerInterceptor接口,覆盖了preHandle和afterCompletion方法,分别用于处理请求前和请求后的逻辑。
-
在preHandle方法中,首先判断处理器(handler)是否为MethodHandle类型,如果不是,则直接返回true,表示继续处理下一个拦截器或执行Controller。这样可以避免拦截非Controller的请求。
-
然后从请求的header中获取名为SecurityConstant.USER_TOKEN的token(通常是用户的身份验证token),并从Redis中获取对应的token。如果获取到了token,则解析其中的数据并将用户信息存入ThreadLocal中,以便后续的Controller可以使用。
-
在afterCompletion方法中,移除当前线程中的用户信息,以确保线程安全和数据隔离。
import com.zzyl.constant.SecurityConstant;
import com.zzyl.constant.UserCacheConstant;
import com.zzyl.properties.JwtTokenManagerProperties;
import com.zzyl.utils.JwtUtil;
import com.zzyl.utils.ObjectUtil;
import com.zzyl.utils.UserThreadLocal;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 登录拦截器:将数据存入线程
*/
@Component
public class UserTokenIntercept implements HandlerInterceptor {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private JwtTokenManagerProperties jwtTokenManagerProperties;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 只拦截 controller 请求
if (!(handler instanceof HandlerMethod)){
return true;
}
// 从 header获取 uuid的token
String uuidToken = request.getHeader(SecurityConstant.USER_TOKEN);// authorization
if (!ObjectUtil.isEmpty(uuidToken)){
String key = UserCacheConstant.JWT_TOKEN + uuidToken;
String token = redisTemplate.opsForValue().get(key);
if (!ObjectUtil.isEmpty(token)){
// 读取数据
Claims claims = JwtUtil.parseJWT(jwtTokenManagerProperties.getBase64EncodedSecretKey(), token);
String currentUserStr = String.valueOf(claims.get("currentUser")); // 根据存的名修改
// 存入ThreadLocal中
UserThreadLocal.setSubject(currentUserStr);
}
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 移除当前线程中的参数
UserThreadLocal.remove();
}
}
3-3、定义WebMvcConfig
-
导入必要的包和类: 首先导入了一些需要的类和包,包括拦截器、Jackson序列化相关的类以及一些Spring MVC的配置类。
-
定义WebMvcConfig类: 定义了一个名为WebMvcConfig的类,并使用了@Configuration注解,表明这是一个配置类。
-
实现WebMvcConfigurer接口: WebMvcConfig类实现了WebMvcConfigurer接口,这意味着它可以对Spring MVC进行高级配置。
-
注入拦截器: 在类中注入了UserInterceptor和UserTokenIntercept拦截器。
-
定义拦截器规则: 在addInterceptors方法中,配置了拦截器的规则。分别为后台和小程序端定义了不同的拦截规则,包括需要排除的路径和需要拦截的路径。
-
定义资源路径映射: 在addResourceHandlers方法中,配置了资源路径的映射,以支持webjars、swagger等资源。
-
配置Jackson序列化和反序列化规则: 在jackson2ObjectMapperBuilderCustomizer方法中,配置了Jackson的序列化和反序列化规则,包括对Long、LocalDateTime、LocalDate等类型的数据进行特殊处理。
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.zzyl.intercept.UserInterceptor;
import com.zzyl.intercept.UserTokenIntercept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* webMvc高级配置
*/
@Configuration
@ComponentScan("springfox.documentation.swagger.web")
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
UserInterceptor userInterceptor;
@Autowired
private UserTokenIntercept userTokenIntercept;
// 后台 滤掉swagger相关路径和登录相关接口
private static final String[] ADMIN_EXCLUDE_PATH_PATTERNS = new String[]{
"/swagger-ui.html",
"/webjars/**",
"/swagger-resources",
"/v2/api-docs",
"/customer/**",
"/security/login",
"/common/**",
"/user/refresh/**"};
// 小程序拦截 滤掉swagger相关路径和登录相关接口
private static final String[] EXCLUDE_PATH_PATTERNS = new String[]{
"/swagger-ui.html",
"/webjars/**",
"/swagger-resources",
"/v2/api-docs",
// 登录接口
"/customer/user/login",
// 房型列表接口
"/customer/roomTypes",
"/customer/orders/project/**",
"/user/refresh/**"};
/**
* 拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 后台拦截器
registry.addInterceptor(userTokenIntercept).excludePathPatterns(ADMIN_EXCLUDE_PATH_PATTERNS).addPathPatterns("/**");
// 小程序端接口鉴权拦截器
registry.addInterceptor(userInterceptor).excludePathPatterns(EXCLUDE_PATH_PATTERNS).addPathPatterns("/customer/**");
}
/**
* 资源路径 映射
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//支持webjars
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
//支持swagger
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
//支持小刀
registry.addResourceHandler("doc.html")
.addResourceLocations("classpath:/META-INF/resources/");
}
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
// 序列化
builder.serializerByType(Long.class, ToStringSerializer.instance);
builder.serializerByType(BigInteger.class, ToStringSerializer.instance);
builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
builder.serializerByType(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
builder.serializerByType(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
// 反序列化
builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
builder.deserializerByType(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
builder.deserializerByType(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
};
}
}
四、自定义授权管理器
4-1、自定义授权管理器
- 获取
uuidToken
- 根据
uuidToken
获取jwtTokenkey
从而获取JwtToken
- 解析数据
- 判断前端的uuid和后端的uuid是否一致
- 判断token的过期时间,为jwt续期
- 封装当前请求路径
- 验证用户是否有权限
import cn.hutool.core.text.AntPathMatcher;
import cn.hutool.json.JSONUtil;
import com.zzyl.constant.SecurityConstant;
import com.zzyl.constant.UserCacheConstant;
import com.zzyl.properties.JwtTokenManagerProperties;
import com.zzyl.utils.JwtUtil;
import com.zzyl.utils.ObjectUtil;
import com.zzyl.vo.UserVo;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* 授权管理器
*/
@Slf4j
@Component
public class JwtAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private JwtTokenManagerProperties jwtTokenManagerProperties;
// 路径匹配
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext requestAuthorizationContext) {
// 1、获取uuidToken
HttpServletRequest request = requestAuthorizationContext.getRequest();
String uuidToken = request.getHeader(SecurityConstant.USER_TOKEN); // 获取"authorization";
if (ObjectUtil.isEmpty(uuidToken)){
return new AuthorizationDecision(false);
}
// 2、从redis获取jwtToken
String jwtTokenKey = UserCacheConstant.JWT_TOKEN + uuidToken;
String jwtToken = redisTemplate.opsForValue().get(jwtTokenKey);
if (ObjectUtil.isEmpty(jwtToken)){
return new AuthorizationDecision(false);
}
// 3、解析数据
Claims claims = JwtUtil.parseJWT(jwtTokenManagerProperties.getBase64EncodedSecretKey(), jwtToken);
UserVo userVo = JSONUtil.toBean(String.valueOf(claims.get("currentUser")), UserVo.class);
// 4、判断前端的uuid和后端的uuid是否一致(安全性)
String redisUuidToken = redisTemplate.opsForValue().get(UserCacheConstant.USER_TOKEN + userVo.getUserToken());
if (!uuidToken.equals(redisUuidToken)){
return new AuthorizationDecision(false);
}
// 5、判断token的过期时间,为jwt续期,避免二次登录浪费时间(实用性)
// 重新生成 JwtToken,uuidToken重新设置过期时间
Long expire = redisTemplate.opsForValue().getOperations().getExpire(jwtTokenKey);
if (expire <= 600){
Map<String, Object> clamis = new HashMap<>();
clamis.put("currentUser",String.valueOf(clamis.get("currentUser")));
// 生成新的jwt newJwtToken
String newJwtToken = JwtUtil.createJWT(
jwtTokenManagerProperties.getBase64EncodedSecretKey(),
jwtTokenManagerProperties.getTtl(),
clamis);
Long ttl = Long.valueOf(jwtTokenManagerProperties.getTtl() / 1000);
// 将新的jwtToken存redis
redisTemplate.opsForValue().set(jwtTokenKey,newJwtToken,ttl,TimeUnit.SECONDS);
// 为uuid更新过期时间
redisTemplate.expire(UserCacheConstant.USER_TOKEN+userVo.getUsername(),ttl,TimeUnit.SECONDS);
}
// 当前请求的路径: GET/nursing_project/** String method = request.getMethod(); // GET
String requestURI = request.getRequestURI(); // /nursing_project/**
String targetUrl = method + requestURI;
// 验证用户是否有权限
for (String resourceRequestPath : userVo.getResourceRequestPaths()){
boolean isMatch = antPathMatcher.match(resourceRequestPath, targetUrl);
if (isMatch){
return new AuthorizationDecision(true);
}
}
return new AuthorizationDecision(false);
}
}
4-2、配置放行的url
所以,我们需要在上述配置中添加放行的url列表,为了更方便的维护,我们可以放到application.yml文件中,如下:
zzyl:
framework:
security:
ignore-url:
# - /**
- /resource/menus/**
- /resource/myButten/**
- /customer/**
- /security/login
- /security/logout
- /doc.html
- /*-swagger/**
- /swagger-resources
- /v2/api-docs
- /webjars/**
- /common/**
- /ws/**
4-3、新增对应的properties配置类
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* 忽略配置及跨域
*/
@Slf4j
@Data
@ConfigurationProperties(prefix = "zzyl.framework.security")
@Configuration
public class SecurityConfigProperties {
String defaulePassword ;
List<String> ignoreUrl = new ArrayList<>();
List<String> origins = new ArrayList<>();
String loginPage;
//令牌有效时间
Integer accessTokenValiditySeconds = 3*24*3600;
//刷新令牌有效时间
Integer refreshTokenValiditySeconds= 7*24*3600;
}
4-4、SecurityConfig
添加 授权管理器
.antMatchers( ignoreUrl.toArray(new String[ignoreUrl.size()]) )
获取放行的url.anyRequest().access(jwtAuthorizationManager);
注入自定义授权管理器
import com.zzyl.properties.SecurityConfigProperties;
import com.zzyl.security.JwtAuthorizationManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import java.util.List;
/**
* 权限核心配置类
*/
@Configuration
@EnableConfigurationProperties(SecurityConfigProperties.class)
public class SecurityConfig {
@Autowired
JwtAuthorizationManager jwtAuthorizationManager;
@Autowired
private SecurityConfigProperties securityConfigProperties;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
List<String> ignoreUrl = securityConfigProperties.getIgnoreUrl();
http.authorizeHttpRequests()
.antMatchers( ignoreUrl.toArray(new String[ignoreUrl.size()]) )
.permitAll()
// 指定对于任何请求,使用jwtAuthorizationManager来进行访问控制的判断。
.anyRequest().access(jwtAuthorizationManager);
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS );//关闭session
http.headers().cacheControl().disable();//关闭缓存
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
/**
* BCrypt密码编码
* @return
*/
@Bean
public BCryptPasswordEncoder bcryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
五、数据权限
5-1、权限分类
-
全部(状态为4):我们查询数据不做任何限制
-
本人数据(状态为1):目前的表中,都有一个字段叫做create_by,获取当前登录人的id去匹配即可
and create_by = ${userId}
- 本级(状态3):在当前表中需要设置一个部门编号,根据当前登录人查询对应部门编号,再到表中去匹配即可
OR dept_no = ${deptNo}
- 本级及以下(状态2),找到当前表中的部门编号,再找到部门下的子部门编号进行匹配
OR dept_no IN ( SELECT dept_no FROM sys_dept WHERE dept_no = ${deptNo} or parent_dept_no like ${deptNo}
- 自定义(状态0):需要进行关联查询;根据当前登录人找到对应的角色,然后通过角色再查询关联的部门列表,中间表为:sys_role_dept
OR dept_no IN ( SELECT dept_no FROM sys_role_dept WHERE role_id = ${roleId} )
示例:
select * from retreat where 1=1 OR dept_no IN ( SELECT dept_no FROM sys_role_dept WHERE role_id = ${roleId} )
5-2、定义注解 @DataScope
import java.lang.annotation.*;
/**
* DataScope * @describe: 数据权限过滤注解
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
public String deptAlias() default "";
public String userAlias() default "";
}
5-3、定义注解切入点
-
dataScopeFilter方法根据用户拥有的角色和数据权限设置,构建相应的SQL字符串来过滤数据:
- 根据不同的数据权限级别,拼接不同的SQL条件,例如:只能查看自己的数据、只能查看本部门数据等。
- 将生成的SQL字符串放入BaseDto的params中,以便在查询操作中使用。
-
getAnnotationLog方法用于获取被@DataScope注解标记的方法上的注解信息。
import cn.hutool.json.JSONUtil;
import com.zzyl.base.BaseDto;
import com.zzyl.base.DataScope;
import com.zzyl.utils.NoProcessing;
import com.zzyl.utils.ObjectUtil;
import com.zzyl.utils.StringUtils;
import com.zzyl.utils.UserThreadLocal;
import com.zzyl.vo.RoleVo;
import com.zzyl.vo.UserVo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* DataScopeAspect * @author itheima
**/@Aspect
@Component
public class DataScopeAspect {
/**
* 自定数据权限
*/
public static final String DATA_SCOPE_CUSTOM = "0";
/**
* 仅本人数据权限
*/
public static final String DATA_SCOPE_SELF = "1";
/**
* 部门及以下数据权限
*/
public static final String DATA_SCOPE_DEPT_AND_CHILD = "2";
/**
* 部门数据权限
*/
public static final String DATA_SCOPE_DEPT = "3";
/**
* 全部数据权限
*/
public static final String DATA_SCOPE_ALL = "4";
/**
* 数据权限过滤关键字
*/
public static final String DATA_SCOPE = "dataScope";
// 配置织入点
@Pointcut("@annotation(com.zzyl.base.DataScope)")
public void dataScopePointCut() {
}
//@Before 注解标记的方法会在目标方法执行之前执行 handleDataScope(point);方法
@Before("dataScopePointCut()")
public void doBefore(JoinPoint point) throws Throwable {
handleDataScope(point);
}
/**
* 1、对加入 @DataScope 注解的方法 先进入这个方法
* @param joinPoint
*/
protected void handleDataScope(final JoinPoint joinPoint) {
// 获得注解
DataScope controllerDataScope = getAnnotationLog(joinPoint);
if (controllerDataScope == null) {
return;
}
// 获取当前的用户
String subject = UserThreadLocal.getSubject();
UserVo userVo = JSONUtil.toBean(subject, UserVo.class);
// 如果是超级管理员,则不过滤数据
if (StringUtils.isNotNull(userVo) && !userVo.getUsername().equals("admin")) {
dataScopeFilter(
joinPoint,
userVo,
controllerDataScope.deptAlias(),
controllerDataScope.userAlias());
}
}
/**
* 3、数据范围过滤
*
* @param joinPoint 切点
* @param user 用户
* @param userAlias 别名
*/
public static void dataScopeFilter(JoinPoint joinPoint, UserVo user, String deptAlias, String userAlias) {
System.out.println("过滤数据---------------");
StringBuilder sqlString = new StringBuilder();
for (RoleVo role : user.getRoleList()) {
String dataScope = role.getDataScope();//拥有的数据权限
// 如果是全部数据权限,则不过滤数据
if (DATA_SCOPE_ALL.equals(dataScope)) {
sqlString = new StringBuilder();
break;
// 如果是自定数据权限,则只查看自己的数据
} else if (DATA_SCOPE_CUSTOM.equals(dataScope)) {
sqlString.append(" OR dept_no IN ( SELECT dept_no FROM sys_role_dept WHERE role_id = " + role.getId() + " ) ");
// 如果是部门数据权限,则只查看本部门数据
} else if (DATA_SCOPE_DEPT.equals(dataScope)) {
sqlString.append(" and (dept_no = " + user.getDeptNo() + ") ");
// 如果是部门及以下数据权限,则查看本部门以及下级数据
} else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
String str = NoProcessing.processString(user.getDeptNo()) + "%";
sqlString.append(
" OR dept_no IN ( SELECT dept_no FROM sys_dept WHERE dept_no = " + user.getDeptNo() + " or parent_dept_no like '" + str + "')");
// 如果是仅本人数据权限,则只查看本人的数据
} else if (DATA_SCOPE_SELF.equals(dataScope)) {// or u.user_id = 登录用户id
sqlString.append(" and ( create_by = " + user.getId()+")");
}
}
if (StringUtils.isNotBlank(sqlString.toString())) {
Object params = joinPoint.getArgs()[0]; //获取第一个参数 要求一定得是一个BaseEntity 在Service执行前 则就已经加上了 Sql or u.user_id = 登录用户id
if (ObjectUtil.isNotNull(params) && params instanceof BaseDto) {
BaseDto baseDto = (BaseDto) params;
baseDto.getParams().put(DATA_SCOPE, sqlString);
// todo 给 BaseDto put 进入一个 key为:DATA_SCOPE,值为sqlString 的字段
}
}
}
/**
* 2、是否存在注解,如果存在就获取
*/
private DataScope getAnnotationLog(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(DataScope.class);
}
return null;
}
}
5-4、退住管理示例
controller
层
@ApiOperation(value = "退住管理", notes = "退住管理列表页")
@PostMapping("/selectByPage")
@DataScope
public ResponseResult selectByPage(@RequestBody RetreatReqDto retreatReqDto) {
// 1、RetreatReqDto extends BaseDto
// 2、在DataScopeAspect 里面 baseDto.getParams().put(DATA_SCOPE, sqlString);
// 3、 <if test="params.dataScope != null">
// ${params.dataScope}
// </if>
// todo RetreatReqDto 里面有一个 dataScope 字段,存储sql拼接
return retreatService.selectByPage(retreatReqDto);
}
实现类
/**
* 退住管理列表查询
* @return
*/
@Override
public ResponseResult selectByPage(RetreatReqDto retreatReqDto) {
PageHelper.startPage(retreatReqDto.getPageNum(), retreatReqDto.getPageSize());
Page<List<Retreat>> pages = retreatMapper.selectByPage(retreatReqDto);
PageResponse<Retreat> of = PageResponse.of(pages, Retreat.class);
return ResponseResult.success(of);
}
mapper
层
<if test="params.dataScope != null">
${params.dataScope}
</if>
<select id="selectByPage" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from retreat
where status = 2 <if test="retreatCode != null and retreatCode != ''">
AND retreat_code = #{retreatCode}
</if>
<if test="name != null">
and name like concat('%', #{name}, '%')
</if>
<if test="idCardNo != null and idCardNo != ''">
AND id_card_no = #{idCardNo}
</if>
<if test="startTime != null and endTime != null">
and check_out_time between #{startTime} and #{endTime}
</if>
<if test="params.dataScope != null">
${params.dataScope}
</if>
order by create_time desc
</select>
五、Activiti7集成
一、依赖配置
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.10.0</version>
</dependency>
<!--如果activiti依赖下载不了,可以配置如下地址进行下载-->
<repositories>
<repository>
<id>activiti-releases</id>
<url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-releases</url>
</repository>
</repositories>
二、生成流程图(入住)
使用
bpmnjs
工具生成bpmn文件,和截图保存在resource/bpmn里面
入住申请流程 bpmn 文件
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:activiti="http://activiti.org/bpmn" id="sample-diagram" targetNamespace="http://activiti.org/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:process id="checkIn-new" isExecutable="true">
<bpmn2:startEvent id="StartEvent_1">
<bpmn2:outgoing>Flow_0hsq6o3</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:userTask id="Activity_1kgujpb" name="发起人申请" activiti:formKey="0" activiti:assignee="${assignee0}">
<bpmn2:incoming>Flow_0hsq6o3</bpmn2:incoming>
<bpmn2:incoming>Flow_0a87sox</bpmn2:incoming>
<bpmn2:outgoing>Flow_0agpc1o</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0hsq6o3" sourceRef="StartEvent_1" targetRef="Activity_1kgujpb" />
<bpmn2:userTask id="Activity_1a3ot1n" name="入住评估-处理" activiti:formKey="1" activiti:assignee="${assignee1}">
<bpmn2:incoming>Flow_0agpc1o</bpmn2:incoming>
<bpmn2:outgoing>Flow_0kn6i1r</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0agpc1o" sourceRef="Activity_1kgujpb" targetRef="Activity_1a3ot1n" />
<bpmn2:userTask id="Activity_18g3qee" name="副院长审批-处理" activiti:formKey="2" activiti:assignee="${assignee2}">
<bpmn2:incoming>Flow_0kn6i1r</bpmn2:incoming>
<bpmn2:outgoing>Flow_0kqma66</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0kn6i1r" sourceRef="Activity_1a3ot1n" targetRef="Activity_18g3qee" />
<bpmn2:userTask id="Activity_12u7891" name="入住选配-处理" activiti:formKey="3" activiti:assignee="${assignee3}">
<bpmn2:incoming>Flow_0s8ila1</bpmn2:incoming>
<bpmn2:outgoing>Flow_0vvo1b1</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="Activity_0k9is7s" name="签约办理-处理" activiti:formKey="4" activiti:assignee="${assignee4}">
<bpmn2:incoming>Flow_0vvo1b1</bpmn2:incoming>
<bpmn2:outgoing>Flow_1dmh6fk</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0vvo1b1" sourceRef="Activity_12u7891" targetRef="Activity_0k9is7s" />
<bpmn2:endEvent id="Event_0qrw42r">
<bpmn2:incoming>Flow_1dmh6fk</bpmn2:incoming>
<bpmn2:incoming>Flow_0u7ajk5</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:sequenceFlow id="Flow_1dmh6fk" sourceRef="Activity_0k9is7s" targetRef="Event_0qrw42r" />
<bpmn2:exclusiveGateway id="Gateway_1bq869n" name="是否通过">
<bpmn2:incoming>Flow_0kqma66</bpmn2:incoming>
<bpmn2:outgoing>Flow_0s8ila1</bpmn2:outgoing>
<bpmn2:outgoing>Flow_1t9mtuw</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:sequenceFlow id="Flow_0kqma66" sourceRef="Activity_18g3qee" targetRef="Gateway_1bq869n" />
<bpmn2:sequenceFlow id="Flow_0s8ila1" sourceRef="Gateway_1bq869n" targetRef="Activity_12u7891">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${ops == 1}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:exclusiveGateway id="Gateway_1vz7ax5" name="是否结束流程">
<bpmn2:incoming>Flow_1t9mtuw</bpmn2:incoming>
<bpmn2:outgoing>Flow_0u7ajk5</bpmn2:outgoing>
<bpmn2:outgoing>Flow_0a87sox</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:sequenceFlow id="Flow_1t9mtuw" sourceRef="Gateway_1bq869n" targetRef="Gateway_1vz7ax5">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${ops > 1}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="Flow_0u7ajk5" sourceRef="Gateway_1vz7ax5" targetRef="Event_0qrw42r">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${ops == 2}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="Flow_0a87sox" sourceRef="Gateway_1vz7ax5" targetRef="Activity_1kgujpb">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${ops == 3}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="checkIn-new">
<bpmndi:BPMNEdge id="Flow_0hsq6o3_di" bpmnElement="Flow_0hsq6o3">
<di:waypoint x="268" y="250" />
<di:waypoint x="320" y="250" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0agpc1o_di" bpmnElement="Flow_0agpc1o">
<di:waypoint x="420" y="250" />
<di:waypoint x="480" y="250" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0kn6i1r_di" bpmnElement="Flow_0kn6i1r">
<di:waypoint x="580" y="250" />
<di:waypoint x="640" y="250" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0vvo1b1_di" bpmnElement="Flow_0vvo1b1">
<di:waypoint x="880" y="410" />
<di:waypoint x="960" y="410" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1dmh6fk_di" bpmnElement="Flow_1dmh6fk">
<di:waypoint x="1060" y="410" />
<di:waypoint x="1116" y="410" />
<di:waypoint x="1116" y="502" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0kqma66_di" bpmnElement="Flow_0kqma66">
<di:waypoint x="690" y="290" />
<di:waypoint x="690" y="385" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0s8ila1_di" bpmnElement="Flow_0s8ila1">
<di:waypoint x="715" y="410" />
<di:waypoint x="780" y="410" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1t9mtuw_di" bpmnElement="Flow_1t9mtuw">
<di:waypoint x="690" y="435" />
<di:waypoint x="690" y="495" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0u7ajk5_di" bpmnElement="Flow_0u7ajk5">
<di:waypoint x="715" y="520" />
<di:waypoint x="1098" y="520" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0a87sox_di" bpmnElement="Flow_0a87sox">
<di:waypoint x="665" y="520" />
<di:waypoint x="370" y="520" />
<di:waypoint x="370" y="290" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="232" y="232" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1a3ot1n_di" bpmnElement="Activity_1a3ot1n">
<dc:Bounds x="480" y="210" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_18g3qee_di" bpmnElement="Activity_18g3qee">
<dc:Bounds x="640" y="210" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1kgujpb_di" bpmnElement="Activity_1kgujpb">
<dc:Bounds x="320" y="210" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1bq869n_di" bpmnElement="Gateway_1bq869n" isMarkerVisible="true">
<dc:Bounds x="665" y="385" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="589" y="400" width="44" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_12u7891_di" bpmnElement="Activity_12u7891">
<dc:Bounds x="780" y="370" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0k9is7s_di" bpmnElement="Activity_0k9is7s">
<dc:Bounds x="960" y="370" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0qrw42r_di" bpmnElement="Event_0qrw42r">
<dc:Bounds x="1098" y="502" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1vz7ax5_di" bpmnElement="Gateway_1vz7ax5" isMarkerVisible="true">
<dc:Bounds x="665" y="495" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="659" y="552" width="67" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>
三、部署流程实列
因为流程属于纯后台开发使用,无需在页面展示,并且项目启动之后,只需要部署一次即可,我们暂时可以手动进行部署,可以编写单元测试部署流程,代码如下:
@Test
public void testDeploymentByCheckIn(){
String fileName = "bpmn/checkIn_new.bpmn"; // 文件名
//定义流程
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource(fileName)
.name("checkIn-new") // 文件里面的id值
.deploy();
//部署流程
System.out.println(deployment.getId());
System.out.println(deployment.getName());
}
四、申请入住
1:输入表单之后,根据老人姓名和身份证号查询老人信息
2:如果能查询到老人,并且在申请入住中,则不处理该老人
3:保存老人信息到老人表中
4:保存入住信息到入住表中
5:保存操作记录(需查询下一个审核人)
6:启动流程实例
6.1 为流程图中的5个assignee设置流程变量,需要按照部门找到对应的办理人
6.2 在流程实例启动之前,则需要设计bussinessKey–>checkIn:入住id
6.3 判断第一个任务是否是当前登录人,如果是,则直接完成任务
4-1、申请入住接口
@RestController
@RequestMapping("/checkIn")
@Api(tags = "入住")
public class CheckInController {
@Autowired
private CheckInService checkInService;
@PostMapping("/create")
@ApiOperation("申请入住")
public ResponseResult createCheckIn(@RequestBody CheckInDto checkInDto){
return checkInService.createCheckIn(checkInDto);
}
}
接收类 CheckInDto
package com.zzyl.dto;
import com.zzyl.base.BaseDto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* @author sjqn
*/
@Data
@ApiModel(description = "入住实体类")
public class CheckInDto extends BaseDto {
@ApiModelProperty(value = "保存1 提交2")
private Integer save;
@ApiModelProperty(value = "老人")
private ElderDto elderDto;
/**
* 其他申请信息
*/
@ApiModelProperty(value = "其他信息")
private String otherApplyInfo;
/**
* 健康评估信息
*/
@ApiModelProperty(value = "健康评估信息")
private String reviewInfo;
/**
* 能力评估
*/
@ApiModelProperty(value = "能力评估")
private String reviewInfo1;
/**
* 评估报告
*/
@ApiModelProperty(value = "评估信息")
private String reviewInfo2;
/**
* 入住时间
*/
@ApiModelProperty(value = "入住时间")
private LocalDateTime checkInTime;
/**
* 家属信息
*/
@ApiModelProperty(value = "家属信息")
private List<MemberElderDto> memberElderDtos;
/**
* 一寸照片
*/
@ApiModelProperty(value = "一寸照片")
private String url1;
/**
* 身份证人像面
*/
@ApiModelProperty(value = "身份证人像面")
private String url2;
/**
* 身份证国徽面
*/
@ApiModelProperty(value = "身份证国徽面")
private String url3;
/**
* 任务ID
*/
@ApiModelProperty(value = "任务ID")
private String taskId;
}
4-2、接口-实现
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zzyl.base.ResponseResult;
import com.zzyl.constant.AccraditationRecordConstant;
import com.zzyl.constant.RetreatConstant;
import com.zzyl.dto.CheckInDto;
import com.zzyl.dto.ElderDto;
import com.zzyl.entity.CheckIn;
import com.zzyl.entity.Dept;
import com.zzyl.entity.Elder;
import com.zzyl.entity.User;
import com.zzyl.mapper.CheckInMapper;
import com.zzyl.mapper.DeptMapper;
import com.zzyl.mapper.UserMapper;
import com.zzyl.service.CheckInService;
import com.zzyl.service.ElderService;
import com.zzyl.utils.CodeUtil;
import com.zzyl.utils.UserThreadLocal;
import com.zzyl.vo.RecordVo;
import com.zzyl.vo.retreat.ElderVo;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author : like
* @date : 2024/3/28 20:26
* @Version: 1.0
*/@Service
public class CheckInServiceImpl implements CheckInService {
// 老人信息表操作
@Autowired
private ElderService elderService;
// 申请入住表
@Autowired
private CheckInMapper checkInMapper;
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 部门管理
@Autowired
private DeptMapper deptMapper;
// 用户管理
@Autowired
private UserMapper userMapper;
@Autowired
private AccraditationRecordService accraditationRecordService;
private static final String CHECK_IN_CODE_PREFIX="RZ";
// 申请入住
@Override
public ResponseResult createCheckIn(CheckInDto checkInDto) {
// 1、校验老人是否入住中(操作老人表)
ElderVo elderVo = elderService.selectByIdCardAndName(checkInDto.getElderDto().getIdCardNo(), checkInDto.getElderDto().getName());
if (null != elderVo && elderVo.getStatus().equals(4)){
return ResponseResult.error(checkInDto.getElderDto().getName()+"已经发起了入住申请");
}
// 2、保存老人数据(操作老人表)
ElderDto elderDto = BeanUtil.toBean(checkInDto.getElderDto(), ElderDto.class);
elderDto.setImage(checkInDto.getUrl1()); // 头像
JSONObject jsonObject = BeanUtil.toBean(checkInDto.getOtherApplyInfo(), JSONObject.class);
elderDto.setSex(jsonObject.getStr("sex"));
elderDto.setAge(jsonObject.getStr("age"));
Elder elder = elderService.insert(elderDto);
// 3、保存入住数据(操作申请表)
CheckIn checkIn = new CheckIn();
// 编号(redis生成)
String code = CodeUtil.generateCode(CHECK_IN_CODE_PREFIX, redisTemplate, 5);
checkIn.setCheckInCode(code);
// 标题(老人表获取)
checkIn.setTitle(elder.getName()+"的入住申请");
checkIn.setElderId(elder.getId());
// 获取当前登录人的信息
String subject = UserThreadLocal.getSubject();
User user = JSONUtil.toBean(subject, User.class);
checkIn.setCounselor(user.getRealName()); // 真实姓名getRealName
checkIn.setApplicat(user.getRealName()); // 申请人applicat
checkIn.setApplicatId(user.getId());
checkIn.setDeptNo(user.getDeptNo()); // 部门编号
// 状态
checkIn.setFlowStatus(CheckIn.FlowStatus.APPLY.getCode()); // 流程状态
checkIn.setStatus(CheckIn.Status.APPLICATION.getCode()); // 状态
// 其他信息
checkIn.setOtherApplyInfo(JSONUtil.toJsonStr(checkInDto));
checkInMapper.insert(checkIn);
// 4、准备流程变量的参数 map
Map<String ,Object> variables = setVariables(checkIn);
// 5、启动流程实列
start(checkIn.getId(),user,"checkIn-new",variables,true);
// 6、保存操作记录
Long nextAssignee = getNextAssignee("checkIn", "checkIn:" + checkIn.getId());
RecordVo recordVo = getRecordVo(
checkIn,
user,
AccraditationRecordConstant.AUDIT_STATUS_PASS,
"同意",
"发起申请 - 申请入住",
"护理组组长 - 入住评估",
nextAssignee,
AccraditationRecordConstant.RECORD_HANDLE_TYPE_PROCESSED
);
accraditationRecordService.insert(recordVo);
return ResponseResult.success();
}
4、封装流程数据
// 4、准备流程变量的参数 map
Map<String ,Object> variables = setVariables(checkIn);
/**
* 4、封装变量数据(流程)
* @param checkIn
* @return
*/
public Map<String,Object> setVariables(CheckIn checkIn){
Map<String, Object> variables = new HashMap<>();
// 申请入住 - 养老顾问
variables.put("assignee0",checkIn.getApplicatId());
// 入住评估 - 护理主管
String nursingDeptCode = RetreatConstant.NURSING_DEPT_CODE; // 护理部门
Dept dept = deptMapper.selectByDeptNo(nursingDeptCode);
variables.put("assignee1",dept.getLeaderId());
// 审核 - 副院长
List<Long> idList = userMapper.selectByDeptNo(RetreatConstant.DEAN_OFFICE_DEPT_CODE);
variables.put("assignee2",idList.get(0));
// 入住选配 - 养老顾问
variables.put("assignee3",checkIn.getApplicatId());
// 签约 - 法务
List<Long> userIdList = userMapper.selectByDeptNo(RetreatConstant.LEGAL_DEPT_CODE); // 法务部门
variables.put("assignee4",userIdList.get(userIdList.size()-1));
// 业务类型
variables.put("processType",3);
// 业务状态
variables.put("processStatus",1);
// 流程Code
variables.put("processCode",checkIn.getCheckInCode());
variables.put("processTitle", checkIn.getTitle()); // 标题
variables.put("assignee0Name", checkIn.getApplicat()); // 申请人姓名
variables.put("applicationTime", checkIn.getCreateTime()); // 申请时间
return variables;
}
5、启动流程实列
// 5、启动流程实列
start(checkIn.getId(),user,“checkIn-new”,variables,true);
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
/**
* 5、启动流程实列
* @param id 业务id
* @param user 当前登录用户
* @param processDefinitionKey 流程定义的key
* @param variables 流程变量参数
* @param isAuto 是否自动完成
*/
private void start(Long id, User user, String processDefinitionKey, Map<String,Object> variables,boolean isAuto){
// 1、启动流程实列 (流程key,流程唯一键值,流程变量)
String businessKey = processDefinitionKey + ":" + id; // 标识业务实例的唯一键,checkIn.getId() 老人id
runtimeService.startProcessInstanceByKey(processDefinitionKey,businessKey,variables);
// 2、查询任务,执行任务
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey) // 流程key
.processInstanceBusinessKey(businessKey) // 流程唯一键值
.taskAssignee(user.getId().toString()) // 执行任务人
.list();
// 3、判断是否首节点
taskList = taskList.stream().filter(t -> "0".equals(t.getFormKey())).collect(Collectors.toList());
if (!CollectionUtil.isEmpty(taskList) && isAuto){
// 4、执行任务
for (Task task:taskList){
Map<String, Object> variables2 = new HashMap<>();
variables2.put("processStatus",1);
variables2.put("ops",1);
taskService.complete(task.getId(),variables2); // 完成任务id为task.getId()的,并且put流程值
}
}
}
6-1、获取下一个审核人
// 6、保存操作记录
Long nextAssignee = getNextAssignee(“checkIn”, “checkIn:” + checkIn.getId());
/**
* 6-1、获取下一个审核人
* @param processDifinitionKey
* @param bussinessKey
* @return
*/
private Long getNextAssignee(String processDifinitionKey, String bussinessKey) {
Task task = taskService.createTaskQuery()
.processDefinitionKey(processDifinitionKey)
.processInstanceBusinessKey(bussinessKey)
.singleResult();
if(task != null){
return Long.valueOf(task.getAssignee());
}
return null;
}
6-2、封装审核记录参数
// 6、保存操作记录
Long nextAssignee = getNextAssignee(“checkIn”, “checkIn:” + checkIn.getId());
RecordVo recordVo = getRecordVo(
checkIn,
user,
AccraditationRecordConstant.AUDIT_STATUS_PASS,
“同意”,
“发起申请 - 申请入住”,
“护理组组长 - 入住评估”,
nextAssignee,
AccraditationRecordConstant.RECORD_HANDLE_TYPE_PROCESSED
);
/**
* 6-2、封装审核记录参数
* @param checkIn 入住对象
* @param user 当前登录用户
* @param status 审核状态
* @param option 操作意见
* @param step 当前步骤
* @param nextSetp 下一步操作人
* @param nextAssignee 下一个审核人
* @param handleType 处理类型(0:已审批,1:已处理)
* @return
*/
private RecordVo getRecordVo(CheckIn checkIn, User user, Integer status, String option, String step, String nextSetp, Long nextAssignee, Integer handleType) {
RecordVo recordVo = new RecordVo();
recordVo.setId(checkIn.getId());
recordVo.setType(AccraditationRecordConstant.RECORD_TYPE_CHECK_IN);
recordVo.setFlowStatus(checkIn.getFlowStatus());
recordVo.setHandleType(handleType);
recordVo.setStatus(status);
recordVo.setOption(option);
recordVo.setNextStep(nextSetp);
recordVo.setNextAssignee(nextAssignee);
recordVo.setUserId(user.getId());
recordVo.setStep(step);
recordVo.setRealName(user.getRealName());
return recordVo;
}
6-3、操作申请表保存记录
import com.zzyl.entity.AccraditationRecord;
import com.zzyl.mapper.AccraditationRecordMapper;
import com.zzyl.mapper.UserRoleMapper;
import com.zzyl.utils.ObjectUtil;
import com.zzyl.vo.RecordVo;
import com.zzyl.vo.UserRoleVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class AccraditationRecordService implements com.zzyl.service.AccraditationRecordService {
@Autowired
private AccraditationRecordMapper accraditationRecordMapper;
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private UserRoleMapper userRoleMapper;
@Override
public void insert(RecordVo recordVo) {
// 封装数据
AccraditationRecord acc = new AccraditationRecord();
acc.setHandleType(recordVo.getHandleType());
acc.setAuditStatus(recordVo.getStatus());
acc.setBussniessId(recordVo.getId());
acc.setCurrentStep(recordVo.getStep());
acc.setCreateTime(LocalDateTime.now());
acc.setOpinion(recordVo.getOption());
acc.setApproverName(recordVo.getRealName());
acc.setApproverId(recordVo.getUserId());
acc.setType(recordVo.getType());
// 当前的审核步骤
Long increment = redisTemplate.boundValueOps("RD:" + recordVo.getId().toString()).increment();
acc.setStepNo(increment);
// 下一个审核人
if (ObjectUtil.isEmpty(recordVo.getNextAssignee())){
acc.setNextStep(recordVo.getNextStep());
// 根据id查询,
List<UserRoleVo> userRoleVos = userRoleMapper.selectByUserId(recordVo.getNextAssignee());
UserRoleVo userRoleVo = userRoleVos.get(0);
acc.setNextApproverId(userRoleVo.getId());
acc.setNextApproverRole(userRoleVo.getRoleName());
acc.setNextApprover(userRoleVo.getUserName());
}
accraditationRecordMapper.insert(acc);
}
}
4-3、完善现有代码
将启动流程实例,保存操作记录,封装接口
import com.zzyl.base.PageResponse;
import com.zzyl.dto.PendingTasksDto;
import com.zzyl.entity.CheckIn;
import com.zzyl.entity.PendingTasks;
import com.zzyl.entity.User;
import java.util.Map;
/**
* @author : like
* @date : 2024/3/30 20:41
* @Version: 1.0
*/public interface ActFlowCommService {
/**
* 查询下一个审核人
* @param processDefinitionKey 流程定义key
* @param businessKey
* @return
*/
public Long getNextAssignee(String processDefinitionKey, String businessKey);
/**
* 启动流程实例,并且自动执行首页点
* @param id 业务id
* @param user 当前登录用户
* @param processDefinitionKey 流程定义的key
* @param variables 流程变量参数
* @param isAuto 是否自动完成
*/
public void start(Long id, User user, String processDefinitionKey, Map<String, Object> variables, boolean isAuto);
}
实现类
import cn.hutool.core.collection.CollectionUtil;
import com.zzyl.entity.User;
import com.zzyl.service.ActFlowCommService;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class ActFlowCommServiceImpl implements ActFlowCommService {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private HistoryService historyService;
/**
* 查询下一个审核人
* @param processDefinitionKey 流程定义key
* @param businessKey
* @return
*/
@Override
public Long getNextAssignee(String processDefinitionKey, String businessKey) {
Task task = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.processInstanceBusinessKey(businessKey).singleResult();
if(task != null){
return Long.valueOf(task.getAssignee());
}
return null;
}
/**
* 启动流程实例,并且自动执行首页点
* @param id 业务id
* @param user 当前登录用户
* @param processDefinitionKey 流程定义的key
* @param variables 流程变量参数
* @param isAuto 是否自动完成
*/
@Override
public void start(Long id, User user, String processDefinitionKey, Map<String, Object> variables, boolean isAuto) {
//启动流程实例
String businessKey = processDefinitionKey +":" + id;
variables.put("bussinessKey",businessKey);
runtimeService.startProcessInstanceByKey(processDefinitionKey,businessKey,variables);
//查询任务,执行任务
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.processInstanceBusinessKey(businessKey)
.taskAssignee(user.getId().toString())
.list();
//判断是否是首节点
list = list.stream().filter(t->"0".equals(t.getFormKey())).collect(Collectors.toList());
if(!CollectionUtil.isEmpty(list) && isAuto){
//执行任务
for (Task task : list) {
Map<String, Object> variables2 = new HashMap<>();
variables2.put("processStatus",1);
variables2.put("ops",1);
taskService.complete(task.getId(),variables2);
}
}
}
}
完整申请入住
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zzyl.base.ResponseResult;
import com.zzyl.constant.AccraditationRecordConstant;
import com.zzyl.constant.RetreatConstant;
import com.zzyl.dto.CheckInDto;
import com.zzyl.dto.ElderDto;
import com.zzyl.entity.CheckIn;
import com.zzyl.entity.Dept;
import com.zzyl.entity.Elder;
import com.zzyl.entity.User;
import com.zzyl.mapper.CheckInMapper;
import com.zzyl.mapper.DeptMapper;
import com.zzyl.mapper.UserMapper;
import com.zzyl.service.ActFlowCommService;
import com.zzyl.service.CheckInService;
import com.zzyl.service.ElderService;
import com.zzyl.utils.CodeUtil;
import com.zzyl.utils.UserThreadLocal;
import com.zzyl.vo.RecordVo;
import com.zzyl.vo.retreat.ElderVo;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author : like
* @date : 2024/3/28 20:26
* @Version: 1.0
*/@Service
public class CheckInServiceImpl implements CheckInService {
// 老人信息表操作
@Autowired
private ElderService elderService;
// 申请入住表
@Autowired
private CheckInMapper checkInMapper;
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 部门管理
@Autowired
private DeptMapper deptMapper;
// 用户管理
@Autowired
private UserMapper userMapper;
@Autowired
private AccraditationRecordService accraditationRecordService;
private static final String CHECK_IN_CODE_PREFIX="RZ";
@Autowired
private ActFlowCommService actFlowCommService;
// 申请入住
@Override
public ResponseResult createCheckIn(CheckInDto checkInDto) {
// 1、校验老人是否入住中(操作老人表)
ElderVo elderVo = elderService.selectByIdCardAndName(checkInDto.getElderDto().getIdCardNo(), checkInDto.getElderDto().getName());
if (null != elderVo && elderVo.getStatus().equals(4)){
return ResponseResult.error(checkInDto.getElderDto().getName()+"已经发起了入住申请");
}
// 2、保存老人数据(操作老人表)
ElderDto elderDto = BeanUtil.toBean(checkInDto.getElderDto(), ElderDto.class);
elderDto.setImage(checkInDto.getUrl1()); // 头像
JSONObject jsonObject = BeanUtil.toBean(checkInDto.getOtherApplyInfo(), JSONObject.class);
elderDto.setSex(jsonObject.getStr("sex"));
elderDto.setAge(jsonObject.getStr("age"));
Elder elder = elderService.insert(elderDto);
// 3、保存入住数据(操作申请表)
CheckIn checkIn = new CheckIn();
// 编号(redis生成)
String code = CodeUtil.generateCode(CHECK_IN_CODE_PREFIX, redisTemplate, 5);
checkIn.setCheckInCode(code);
// 标题(老人表获取)
checkIn.setTitle(elder.getName()+"的入住申请");
checkIn.setElderId(elder.getId());
// 获取当前登录人的信息
String subject = UserThreadLocal.getSubject();
User user = JSONUtil.toBean(subject, User.class);
checkIn.setCounselor(user.getRealName()); // 真实姓名getRealName
checkIn.setApplicat(user.getRealName()); // 申请人applicat
checkIn.setApplicatId(user.getId());
checkIn.setDeptNo(user.getDeptNo()); // 部门编号
// 状态
checkIn.setFlowStatus(CheckIn.FlowStatus.APPLY.getCode()); // 流程状态
checkIn.setStatus(CheckIn.Status.APPLICATION.getCode()); // 状态
// 其他信息
checkIn.setOtherApplyInfo(JSONUtil.toJsonStr(checkInDto));
checkInMapper.insert(checkIn);
// 4、准备流程变量的参数 map Map<String ,Object> variables = setVariables(checkIn);
// 5、启动流程实列
actFlowCommService.start(checkIn.getId(),user,"checkIn-new",variables,true);
// 6、保存操作记录
Long nextAssignee = actFlowCommService.getNextAssignee("checkIn", "checkIn:" + checkIn.getId());
// 封装 操作记录
RecordVo recordVo = getRecordVo(
checkIn,
user,
AccraditationRecordConstant.AUDIT_STATUS_PASS,
"同意",
"发起申请 - 申请入住",
"护理组组长 - 入住评估",
nextAssignee,
AccraditationRecordConstant.RECORD_HANDLE_TYPE_PROCESSED
);
accraditationRecordService.insert(recordVo);
return ResponseResult.success();
}
/**
* 4、封装变量数据(流程)
* @param checkIn
* @return
*/
public Map<String,Object> setVariables(CheckIn checkIn){
Map<String, Object> variables = new HashMap<>();
// 申请入住 - 养老顾问
variables.put("assignee0",checkIn.getApplicatId());
// 入住评估 - 护理主管
String nursingDeptCode = RetreatConstant.NURSING_DEPT_CODE; // 护理部门
Dept dept = deptMapper.selectByDeptNo(nursingDeptCode);
variables.put("assignee1",dept.getLeaderId());
// 审核 - 副院长
List<Long> idList = userMapper.selectByDeptNo(RetreatConstant.DEAN_OFFICE_DEPT_CODE);
variables.put("assignee2",idList.get(0));
// 入住选配 - 养老顾问
variables.put("assignee3",checkIn.getApplicatId());
// 签约 - 法务
List<Long> userIdList = userMapper.selectByDeptNo(RetreatConstant.LEGAL_DEPT_CODE); // 法务部门
variables.put("assignee4",userIdList.get(userIdList.size()-1));
// 业务类型
variables.put("processType",3);
// 业务状态
variables.put("processStatus",1);
// 流程Code
variables.put("processCode",checkIn.getCheckInCode());
return variables;
}
/**
* 6-2、封装审核记录参数
* @param checkIn 入住对象
* @param user 当前登录用户
* @param status 审核状态
* @param option 操作意见
* @param step 当前步骤
* @param nextSetp 下一步操作人
* @param nextAssignee 下一个审核人
* @param handleType 处理类型(0:已审批,1:已处理)
* @return
*/
private RecordVo getRecordVo(CheckIn checkIn, User user, Integer status, String option, String step, String nextSetp, Long nextAssignee, Integer handleType) {
RecordVo recordVo = new RecordVo();
recordVo.setId(checkIn.getId());
recordVo.setType(AccraditationRecordConstant.RECORD_TYPE_CHECK_IN);
recordVo.setFlowStatus(checkIn.getFlowStatus());
recordVo.setHandleType(handleType);
recordVo.setStatus(status);
recordVo.setOption(option);
recordVo.setNextStep(nextSetp);
recordVo.setNextAssignee(nextAssignee);
recordVo.setUserId(user.getId());
recordVo.setStep(step);
recordVo.setRealName(user.getRealName());
return recordVo;
}
}
五、查询代办
5-1、查询代办接口
@RestController
@RequestMapping("/pending_tasks")
@Api(tags = "待办")
public class PendingTasksController extends BaseController {
@Autowired
private ActFlowCommService actFlowCommService;
@PostMapping("/selectByPage")
@ApiOperation(value = "查询待办", notes = "传入退住对象")
public ResponseResult<PendingTasks> selectByPage(@RequestBody PendingTasksDto pendingTasksDto){
//只查询有当前登录人的任务
Long userId = UserThreadLocal.getMgtUserId();
pendingTasksDto.setAssigneeId(userId);
PageResponse<PendingTasks> pendingTasksPageResponse = actFlowCommService.myTaskInfoList(pendingTasksDto);
return success(pendingTasksPageResponse);
}
}
5-2、实现方法
5-2-1、查询条件
-
查询是否已处理:
- 如果
pendingTasksDto.getIsHandle()
不为空,根据其值判断是否已处理:- 若为 1,则已处理,使用
taskQuery.finished()
方法; - 若不为 1,则未处理,使用
taskQuery.unfinished()
方法。
- 若为 1,则已处理,使用
- 如果
-
任务名称模糊查询:
- 使用
taskQuery.taskNameLike("%处理")
方法设置任务名称模糊查询条件,其中%处理
是一个通配符,表示任务名称以“处理”结尾的任务。
- 使用
-
任务受让人查询:
- 使用
taskQuery.taskAssignee(pendingTasksDto.getAssigneeId().toString())
方法设置任务受让人的查询条件。
- 使用
-
时间范围查询:
- 如果
pendingTasksDto.getStartTime()
和pendingTasksDto.getEndTime()
都不为空,则设置任务创建时间的范围条件:- 使用
taskQuery.taskCreatedAfter(pendingTasksDto.getStartTime())
方法设置任务创建时间在起始时间之后; - 使用
taskQuery.taskCompletedBefore(pendingTasksDto.getEndTime())
方法设置任务完成时间在结束时间之前。
- 使用
- 如果
-
单据类别查询:
- 如果
pendingTasksDto.getType()
不为空,则根据单据类别设置查询条件:- 使用
taskQuery.processVariableValueEquals("processType", pendingTasksDto.getType())
方法。
- 使用
- 如果
-
单据编码查询:
- 如果
pendingTasksDto.getCode()
不为空,则根据单据编码设置查询条件:- 使用
taskQuery.processVariableValueEquals("processCode", pendingTasksDto.getCode())
方法。
- 使用
- 如果
-
任务状态查询:
- 如果
pendingTasksDto.getStatus()
不为空,则根据任务状态设置查询条件:- 使用
taskQuery.processVariableValueEquals("processStatus", pendingTasksDto.getStatus())
方法。
- 使用
- 如果
5-2-2、分页查询条件
-
includeProcessVariables()
: 这个方法是用来包括查询结果中的流程变量。这意味着每个返回的历史任务实例将包含关联的流程变量,这可以让你获取更多关于每个任务实例的上下文信息。 -
orderByHistoricTaskInstanceStartTime().desc()
: 这是排序条件,表示根据历史任务实例的开始时间(startTime
)进行降序排序。这意味着最新开始的任务将首先出现在结果列表中。 -
listPage(startIndex, pageSize)
: 这个方法用于分页查询结果。它接受两个参数:startIndex
(起始索引): 这是结果列表中的起始位置,用于指定从哪里开始获取结果。这个值是通过计算得出的,pendingTasksDto.getPageNum() - 1
计算得到当前页的前一页,然后乘以pendingTasksDto.getPageSize()
得到跳过的结果数量。如果getPageNum()
返回的是当前页码,那么这个计算确保了从正确的位置开始分页。pageSize
(页面大小): 这指定了每页要返回的结果数量。这个值是直接从pendingTasksDto.getPageSize()
获取的,表示每页显示的任务实例数。
// 二、分页封装
long count = taskQuery.count(); // 条数
List<HistoricTaskInstance> list = taskQuery
.includeProcessVariables() // 获取流程实例数据
.orderByHistoricTaskInstanceStartTime().desc() // 根据时间降序
.listPage(
(pendingTasksDto.getPageNum() - 1) * pendingTasksDto.getPageSize(),
pendingTasksDto.getPageSize()
); // (起始索引,每页条数)
5-2-3、完整实现
@Autowired
private HistoryService historyService;
/**
* 查询我的代办和我的申请列表
* @param pendingTasksDto
* @return
*/
@Override
public PageResponse getMyTaskList(PendingTasksDto pendingTasksDto) {
// 一、创建查询条件
HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery();
// 查询是否已处理
if (ObjectUtil.isNotEmpty(pendingTasksDto.getIsHandle())){
if (pendingTasksDto.getIsHandle() == 1){
taskQuery.finished();
} else {
taskQuery.unfinished();
}
}
// 查询条件
taskQuery.taskNameLike("%处理");
taskQuery.taskAssignee(pendingTasksDto.getAssigneeId().toString());
// 时间范围查询
if (ObjectUtil.isNotEmpty(pendingTasksDto.getStartTime()) && ObjectUtil.isNotEmpty(pendingTasksDto.getEndTime())){
// 加入开始,结束时间不为空则
taskQuery
.taskCreatedAfter(pendingTasksDto.getStartTime())
.taskCompletedBefore(pendingTasksDto.getEndTime());
}
// 单据类型
if (ObjectUtil.isNotEmpty(pendingTasksDto.getType())){
taskQuery.processVariableValueEquals("processType",pendingTasksDto.getType());
}
// 单据code
if (ObjectUtil.isNotEmpty(pendingTasksDto.getCode())) {
taskQuery.processVariableValueEquals("processCode", pendingTasksDto.getCode());
}
// 任务状态
if (ObjectUtil.isNotEmpty(pendingTasksDto.getStatus())) {
taskQuery.processVariableValueEquals("processStatus", pendingTasksDto.getStatus());
}
// 二、分页封装
long count = taskQuery.count(); // 条数
List<HistoricTaskInstance> list = taskQuery
.includeProcessVariables() // 获取流程实例数据
.orderByHistoricTaskInstanceStartTime().desc() // 根据时间降序
.listPage(
(pendingTasksDto.getPageNum() - 1) * pendingTasksDto.getPageSize(),
pendingTasksDto.getPageSize()
); // (起始索引,每页条数)
// 三、封装数据
List<PendingTasks> pendingTasksList = new ArrayList<>();
for (HistoricTaskInstance task : list){
// 获取流程变量
Map<String, Object> processVariables = task.getProcessVariables();
//
PendingTasks pendingTasks = new PendingTasks();
pendingTasks.setId(task.getId());
pendingTasks.setCode(processVariables.get("processCode").toString());
pendingTasks.setType(Integer.parseInt(processVariables.get("processType").toString()));
pendingTasks.setTitle(processVariables.get("processTitle").toString());
pendingTasks.setApplicat(processVariables.get("assignee0Name").toString());
pendingTasks.setStatus(Integer.parseInt(processVariables.get("processStatus").toString()));
pendingTasks.setAssigneeId(Long.valueOf(task.getAssignee()));
LocalDateTime applicationTime = LocalDateTimeUtil.parse(processVariables.get("applicationTime").toString());
pendingTasks.setApplicationTime(applicationTime);
pendingTasksList.add(pendingTasks);
}
return PageResponse.of(
pendingTasksList,
pendingTasksDto.getPageNum(),
pendingTasksDto.getPageSize(),
(count + pendingTasksDto.getPageSize() - 1) / pendingTasksDto.getPageSize(),
count
);
}
5-2-4、获取当前登录人信息,完善控制层
@RestController
@RequestMapping("/pending_tasks")
@Api(tags = "待办")
public class PendingTasksController extends BaseController {
@Autowired
private ActFlowCommService actFlowCommService;
@PostMapping("/selectByPage")
@ApiOperation(value = "查询待办", notes = "传入退住对象")
public ResponseResult<PendingTasks> selectByPage(@RequestBody PendingTasksDto pendingTasksDto){
//只查询有当前登录人的任务
Long userId = UserThreadLocal.getMgtUserId();
pendingTasksDto.setAssigneeId(userId);
PageResponse<PendingTasks> pendingTasksPageResponse = actFlowCommService.getMyTaskList(pendingTasksDto);
return success(pendingTasksPageResponse);
}
}
六、查询申请
新增
ApplicationsController
类,直接调用actFlowCommService
中myTaskInfoList
方法即可
@RestController
@RequestMapping("/applications")
@Api(tags = "我的申请")
public class ApplicationsController {
@Autowired
private ActFlowCommService actFlowCommService;
@PostMapping("/selectByPage")
@ApiOperation(value = "查询我的申请")
public ResponseResult<ApplicationsVo> selectByPage(@RequestBody ApplicationsDto applicationsDto){
Long mgtUserId = UserThreadLocal.getMgtUserId();
applicationsDto.setApplicatId(mgtUserId);
PendingTasksDto pendingTasksDto = BeanUtil.toBean(applicationsDto, PendingTasksDto.class);
PageResponse<PendingTasks> pendingTasksPageResponse = actFlowCommService.getMyTaskList(pendingTasksDto);
return ResponseResult.success(pendingTasksPageResponse);
}
}
修改查询条件
actFlowCommService.getMyTaskList
// 判断是申请、还是处理条件
if (ObjectUtil.isNotEmpty(pendingTasksDto.getApplicatId())){
taskQuery.taskAssignee(pendingTasksDto.getApplicatId().toString());
taskQuery.taskNameLike("%申请");
} else {
taskQuery.taskNameLike("%处理");
taskQuery.taskAssignee(pendingTasksDto.getAssigneeId().toString());
}
七、入住表单查询,审批时显示数据
7-1、接口定义
点击处理时,获取数据
@GetMapping
@ApiOperation(value = "入住表单查询")
public ResponseResult<TasVo> getCheckIn(
@RequestParam @ApiParam(value = "入住编码") String code,
@RequestParam @ApiParam(value = "处理人ID") String assigneeId,
@RequestParam @ApiParam(value = "流程状态") Integer flowStatus,
@RequestParam(required = false) @ApiParam(value = "任务Id") String taskId) {
return checkInService.getCheckIn(code, assigneeId, flowStatus, taskId);
}
7-2、实现方法
@Autowired
private AccraditationRecordMapper accraditationRecordMapper;
/**
* 查询入住信息
* @param code
* @param assigneeId
* @param flowStatus
* @param taskId
* @return
*/
@Override
public ResponseResult<TasVo> getCheckIn(String code, String assigneeId, Integer flowStatus, String taskId) {
// 一、封装显示数据
CheckIn checkIn = checkInMapper.selectByCheckInCode(code); // 根据code查询入住单
CheckInVo checkInVo = BeanUtil.toBean(checkIn, CheckInVo.class);
// 封装详细信息
JSONObject jsonObject = JSON.parseObject(checkInVo.getOtherApplyInfo(), JSONObject.class);
if (ObjectUtil.isNotEmpty(jsonObject)) {
JSONObject otherApplyInfo = JSON.parseObject(jsonObject.getStr("otherApplyInfo"), JSONObject.class);
checkInVo.setOtherApplyInfo(otherApplyInfo.toString());
List<MemberElderDto> memberElderDtos = JSON.parseArray(jsonObject.getStr("memberElderDtos"), MemberElderDto.class);
checkInVo.setMemberElderDtos(memberElderDtos);
ElderDto elderDto1 = JSON.parseObject(jsonObject.getStr("elderDto"), ElderDto.class);
checkInVo.setElderDto(elderDto1);
//取出url信息
checkInVo.setUrl1(jsonObject.getStr("url1"));
checkInVo.setUrl2(jsonObject.getStr("url2"));
checkInVo.setUrl3(jsonObject.getStr("url3"));
}
//如果flowStatus小于0,重新赋值
if (flowStatus < 0) {
flowStatus = checkIn.getFlowStatus();
}
// 二、控制数据显示还是隐藏
Integer isShow = 1; // isShow字段:0 只展示操作记录,1:展示详细数据集操作记录
int step = 1;
if (ObjectUtil.isNotEmpty(taskId)) {
//查询当前流程在第几步
isShow = actFlowCommService.isCurrentUserAndStep(taskId, flowStatus, checkIn.getFlowStatus());
}
checkInVo.setIsShow(isShow);
// TODO 查询入住评估内容
JSONObject reviewInfoJsonObj = JSON.parseObject(checkInVo.getReviewInfo());
if(ObjectUtil.isNotEmpty(reviewInfoJsonObj)){
String reviewInfo = reviewInfoJsonObj.getString("reviewInfo");
String reviewInfo1 = reviewInfoJsonObj.getString("reviewInfo1");
String reviewInfo2 = reviewInfoJsonObj.getString("reviewInfo2");
checkInVo.setReviewInfo(reviewInfo);
checkInVo.setReviewInfo1(reviewInfo1);
checkInVo.setReviewInfo2(reviewInfo2);
}
// 三、数据封装显示阶段
TasVo tasVo = new TasVo();
tasVo.setCheckIn(checkInVo);
tasVo.setIsShow(isShow);
tasVo.setType(3); //流程类型:入住
// 四、获取审批记录数据
List<AccraditationRecord> accraditationRecordList
= accraditationRecordMapper.getAccraditationRecordByBuisId(checkIn.getId(), PendingTasksConstant.TASK_TYPE_CHECK_IN);
tasVo.setAccraditationRecords(accraditationRecordList);
return ResponseResult.success(tasVo);
}
7-2-1、判断当前操作是第几步
/**
* 判断当前操作的是第几步
* @param taskId
* @param flowStatus
* @param dbFlowStatus
* @return
*/
@Override
public Integer isCurrentUserAndStep(String taskId, Integer flowStatus, Integer dbFlowStatus) {
//当前任务人的节点
HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery()
.taskId(taskId)
.singleResult();
String formKey = historicTaskInstance.getFormKey();
//如果前端传递的流程状态跟数据库一致,当前操作人的任务节点与状态一致
if (dbFlowStatus.equals(flowStatus) && dbFlowStatus.equals(CheckIn.Status.APPLICATION.getCode())
&& formKey.equals(dbFlowStatus.toString())) {
return 1;
}
return 0;
}
7-2-2、获取审批记录数据
@Select("select * from accraditation_record where bussniess_id = #{bussniessId} and type = #{type} order by step_no")
public List<AccraditationRecord> getAccraditationRecordByBuisId(@Param("bussniessId")long bussniessId, @Param("type")Integer type);