16个文件已修改
15个文件已添加
2个文件已删除
| | |
| | | <artifactId>easyexcel</artifactId> |
| | | <version>3.1.1</version> |
| | | </dependency> |
| | | |
| | | <dependency> |
| | | <groupId>com.alibaba</groupId> |
| | | <artifactId>fastjson</artifactId> |
| | | <version>1.2.73</version> |
| | | </dependency> |
| | | </dependencies> |
| | | |
| | | </project> |
| | |
| | | import com.baomidou.mybatisplus.annotation.FieldFill; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | |
| | | import java.util.Date; |
| | | |
| | |
| | | * @version 1.0 |
| | | * @date 2022/9/7 |
| | | */ |
| | | @Data |
| | | public class BaseEntity { |
| | | @ApiModelProperty("创建时间") |
| | | @TableField(fill = FieldFill.INSERT) |
New file |
| | |
| | | package com.ycl.api; |
| | | |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * @author Lyq |
| | | * @version 1.0 |
| | | * @date 2022/9/9 |
| | | */ |
| | | @ApiModel |
| | | @Data |
| | | public class BasePageDTO<T> { |
| | | @ApiModelProperty("总数") |
| | | private long total; |
| | | @ApiModelProperty("列表") |
| | | private List<T> records = new ArrayList<>(); |
| | | } |
| | |
| | | @Min(value = 1, message = "最小条数1") |
| | | @Max(value = 100, message = "最大条数100") |
| | | private int pageSize = 20; |
| | | |
| | | |
| | | } |
| | |
| | | public boolean isEnabled() { |
| | | return umsAdmin.getStatus().equals(1); |
| | | } |
| | | |
| | | public Long getUserId(){ |
| | | return umsAdmin.getId(); |
| | | } |
| | | } |
New file |
| | |
| | | package com.ycl.config; |
| | | |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | import org.springframework.web.cors.CorsConfiguration; |
| | | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; |
| | | import org.springframework.web.filter.CorsFilter; |
| | | |
| | | /** |
| | | * 全局跨域配置 |
| | | */ |
| | | @Configuration |
| | | public class GlobalCorsConfig { |
| | | |
| | | /** |
| | | * 允许跨域调用的过滤器 |
| | | */ |
| | | @Bean |
| | | public CorsFilter corsFilter() { |
| | | CorsConfiguration config = new CorsConfiguration(); |
| | | //允许所有域名进行跨域调用 |
| | | config.addAllowedOriginPattern("*"); |
| | | //该用法在SpringBoot 2.7.0中已不再支持 |
| | | //config.addAllowedOrigin("*"); |
| | | //允许跨越发送cookie |
| | | config.setAllowCredentials(true); |
| | | //放行全部原始头信息 |
| | | config.addAllowedHeader("*"); |
| | | //允许所有请求方法跨域调用 |
| | | config.addAllowedMethod("*"); |
| | | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); |
| | | source.registerCorsConfiguration("/**", config); |
| | | return new CorsFilter(source); |
| | | } |
| | | } |
| | |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | |
| | | import javax.validation.constraints.Email; |
| | | import javax.validation.constraints.NotBlank; |
| | | import javax.validation.constraints.NotEmpty; |
| | | import javax.validation.constraints.NotNull; |
| | | import javax.validation.constraints.*; |
| | | |
| | | /** |
| | | * 用户登录参数 |
| | |
| | | private String username; |
| | | |
| | | @ApiModelProperty(value = "手机号码") |
| | | @Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号格式有误") |
| | | private String mobile; |
| | | |
| | | @NotEmpty |
| | |
| | | private String note; |
| | | |
| | | @ApiModelProperty(value = "是否党员,0:否,1:是",example = "0") |
| | | @NotNull(message = "党员未选择") |
| | | private byte isDy; |
| | | |
| | | @ApiModelProperty(value = "职务") |
| | | private String jobTitle; |
| | | |
| | | @ApiModelProperty(value = "部门id") |
| | | @NotNull(message = "部门不能为空") |
| | | private Long departmentId; |
| | | |
| | | @ApiModelProperty(value = "用户类型") |
| | |
| | | |
| | | @ApiModelProperty(value = "座机/分机") |
| | | private String zj; |
| | | |
| | | // @ApiModelProperty(value = "是否是负责人,0->false,1->true,默认0",example = "0") |
| | | // @NotNull(message = "负责人未选择") |
| | | // private byte isManager; |
| | | } |
New file |
| | |
| | | package com.ycl.dto.user; |
| | | |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * @author Lyq |
| | | * @version 1.0 |
| | | * @date 2022/9/9 |
| | | */ |
| | | public class AdminDepartDTO { |
| | | @Data |
| | | @ApiModel |
| | | public static class UserInfoDTO { |
| | | @ApiModelProperty(value = "用户Id") |
| | | private Long userId; |
| | | @ApiModelProperty(value = "用户名") |
| | | private String username; |
| | | } |
| | | |
| | | |
| | | } |
New file |
| | |
| | | package com.ycl.dto.user; |
| | | |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * @author Lyq |
| | | * @version 1.0 |
| | | * @date 2022/9/9 |
| | | */ |
| | | @Data |
| | | public class AdminDepartInfoDTO { |
| | | @ApiModelProperty("部门Id") |
| | | private Long departId; |
| | | |
| | | @ApiModelProperty("部门名称") |
| | | private String departName; |
| | | |
| | | @ApiModelProperty("部门描述") |
| | | private String departDes; |
| | | |
| | | @ApiModelProperty("部门类型") |
| | | private byte departType; |
| | | |
| | | @ApiModelProperty("用户Id") |
| | | private Long userId; |
| | | |
| | | @ApiModelProperty("用户名") |
| | | private String userName; |
| | | } |
New file |
| | |
| | | package com.ycl.entity.auth; |
| | | |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * @author Lyq |
| | | * @version 1.0 |
| | | * @date 2022/9/9 |
| | | */ |
| | | @Data |
| | | public class AuthInfo { |
| | | private Long userId; |
| | | private String username; |
| | | } |
New file |
| | |
| | | package com.ycl.entity.auth; |
| | | |
| | | import com.alibaba.fastjson.annotation.JSONField; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Data; |
| | | import lombok.NoArgsConstructor; |
| | | |
| | | import java.io.Serializable; |
| | | |
| | | /** |
| | | * @author Lyq |
| | | * @version 1.0 |
| | | * @date 2022/9/9 |
| | | * 操作者 |
| | | */ |
| | | @Data |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | public class Operator implements Serializable { |
| | | private static final long serialVersionUID = -6412584952129785095L; |
| | | |
| | | |
| | | /** |
| | | * 操作者Id |
| | | */ |
| | | @JSONField(serialize = false) |
| | | private long operatorId; |
| | | |
| | | /** |
| | | * 操作者Name |
| | | */ |
| | | @JSONField(serialize = false) |
| | | private String operatorName; |
| | | |
| | | |
| | | } |
| | |
| | | |
| | | import com.baomidou.mybatisplus.annotation.*; |
| | | import com.ycl.api.BaseEntity; |
| | | import com.ycl.dto.user.AdminDepartDTO; |
| | | import com.ycl.entity.user.UmsAdminDepart; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Getter; |
| | |
| | | @TableField(exist = false) |
| | | private List<SccgDepart> children; |
| | | |
| | | @TableField(exist = false) |
| | | private List<AdminDepartDTO.UserInfoDTO> userInfoDTOS; |
| | | |
| | | } |
New file |
| | |
| | | package com.ycl.entity.user; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.ycl.api.BaseEntity; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | import org.apache.catalina.LifecycleState; |
| | | |
| | | import java.io.Serializable; |
| | | import java.util.Date; |
| | | |
| | | /** |
| | | * <p> |
| | | * |
| | | * </p> |
| | | * |
| | | * @author lyq |
| | | * @since 2022-09-09 |
| | | */ |
| | | @Getter |
| | | @Setter |
| | | @TableName("ums_admin_depart") |
| | | @ApiModel(value = "UmsAdminDepart对象", description = "") |
| | | public class UmsAdminDepart extends BaseEntity implements Serializable { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @ApiModelProperty("主键") |
| | | private Long id; |
| | | |
| | | @ApiModelProperty("用户id") |
| | | private Long userId; |
| | | |
| | | @ApiModelProperty("部门id") |
| | | private Long departId; |
| | | |
| | | @ApiModelProperty("是否是管理员,0->false,1->true,默认0") |
| | | private Boolean isManager; |
| | | } |
| | |
| | | UNAUTHORIZED(401, "暂未登录或token已经过期"), |
| | | FORBIDDEN(403, "没有相关权限"), |
| | | |
| | | NULL_PARAMETER(-3, "空参"), |
| | | |
| | | RECORD_ALREADY_EXISTS(2001, "记录已存在"), |
| | | RECORD_SAVE_FAIL(2002, "记录保存失败"), |
| | | RECORD_UPDATE_FAIL(2003, "记录更新失败"), |
| | | RECORD_DELETE_FAIL(2004, "记录删除失败"), |
| | | RECORD_NOT_EXISTS(2005, "记录不存在"), |
| | | FILE_NOT_FOUND(2006, "文件不存在"), |
| | | FILE_TYPE_FAIL(2007, "文件格式错误"); |
| | | FILE_TYPE_FAIL(2007, "文件格式错误"), |
| | | |
| | | NOT_LOGGED(2015, "未登录,请登录后操作"), |
| | | |
| | | LOGIN_TIMEOUT(2016, "登录超时,请重新登录"), |
| | | |
| | | OPERATOR_TYPE_FETCH_FAIL(3001, "获取操作员失败"); |
| | | |
| | | |
| | | private long code; |
| | | private String message; |
New file |
| | |
| | | package com.ycl.mapper.user; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.ycl.entity.user.UmsAdminDepart; |
| | | import org.apache.ibatis.annotations.Param; |
| | | import org.springframework.security.core.parameters.P; |
| | | |
| | | import java.util.List; |
| | | |
| | | |
| | | /** |
| | | * <p> |
| | | * Mapper 接口 |
| | | * </p> |
| | | * |
| | | * @author lyq |
| | | * @since 2022-09-09 |
| | | */ |
| | | public interface UmsAdminDepartMapper extends BaseMapper<UmsAdminDepart> { |
| | | |
| | | void deletedByDepartId(@Param("departId") long departId); |
| | | |
| | | List<UmsAdminDepart> selectPageByUserId(@Param("userId") long userId, @Param("current") int current, @Param("pageSize") int pageSize); |
| | | } |
| | |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.ycl.api.BasePageDTO; |
| | | import com.ycl.entity.depart.SccgDepart; |
| | | import com.ycl.vo.depart.DepartVO; |
| | | import org.apache.catalina.LifecycleState; |
| | |
| | | * @param params |
| | | */ |
| | | void updateStatus(DepartVO.StatusDepartVO params); |
| | | |
| | | } |
| | |
| | | package com.ycl.service.depart.impl; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ycl.api.BasePageDTO; |
| | | import com.ycl.api.CommonResult; |
| | | import com.ycl.dto.user.AdminDepartDTO; |
| | | import com.ycl.entity.depart.SccgDepart; |
| | | import com.ycl.entity.user.UmsAdminDepart; |
| | | import com.ycl.enums.common.ResultCode; |
| | | import com.ycl.exception.ApiException; |
| | | import com.ycl.mapper.depart.SccgDepartMapper; |
| | | import com.ycl.mapper.user.UmsAdminDepartMapper; |
| | | import com.ycl.service.depart.SccgDepartService; |
| | | import com.ycl.service.user.UmsAdminDepartService; |
| | | import com.ycl.service.user.UmsAdminService; |
| | | import com.ycl.utils.common.PojoUtils; |
| | | import com.ycl.vo.depart.DepartVO; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.BeanUtils; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | |
| | | public class SccgDepartServiceImpl extends ServiceImpl<SccgDepartMapper, SccgDepart> implements SccgDepartService { |
| | | @Resource |
| | | private SccgDepartMapper sccgDepartMapper; |
| | | @Resource |
| | | private UmsAdminDepartService umsAdminDepartService; |
| | | @Resource |
| | | private UmsAdminService umsAdminService; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | throw new ApiException(ResultCode.RECORD_ALREADY_EXISTS); |
| | | } |
| | | BeanUtils.copyProperties(updateDepartVO, sccgDepart); |
| | | List<Long> userIds = updateDepartVO.getUserIds(); |
| | | if (CollUtil.isNotEmpty(userIds)) { |
| | | Long departId = updateDepartVO.getId(); |
| | | userIds.forEach(e -> { |
| | | UmsAdminDepart adminDepart = new UmsAdminDepart(); |
| | | adminDepart.setDepartId(departId); |
| | | adminDepart.setUserId(e); |
| | | umsAdminDepartService.save(adminDepart); |
| | | }); |
| | | } |
| | | if (sccgDepartMapper.updateById(sccgDepart) <= 0) { |
| | | throw new ApiException(ResultCode.RECORD_UPDATE_FAIL); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void delete(long id) { |
| | | if (sccgDepartMapper.deleteById(id) <= 0) { |
| | | throw new ApiException(ResultCode.RECORD_DELETE_FAIL); |
| | | } |
| | | umsAdminDepartService.deletedByDepartId(id); |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | @Override |
| | | public IPage<SccgDepart> pageDepart(DepartVO.PageDepartVO params) { |
| | | return null; |
| | | Page<SccgDepart> page = new Page<>(params.getCurrent(), params.getPageSize()); |
| | | LambdaQueryWrapper<SccgDepart> query = new LambdaQueryWrapper<>(); |
| | | if (StringUtils.isNotBlank(params.getDepartName())) { |
| | | query.like(SccgDepart::getDepartName, params.getDepartName()); |
| | | } |
| | | if (PojoUtils.Vo.isUsefulSearchParam(params.getDepartType())) { |
| | | query.like(SccgDepart::getDepartType, params.getDepartType()); |
| | | } |
| | | query.orderByDesc(SccgDepart::getCreateTime); |
| | | Page<SccgDepart> departPage = sccgDepartMapper.selectPage(page, query); |
| | | List<SccgDepart> records = departPage.getRecords(); |
| | | //负责人 |
| | | if (CollUtil.isNotEmpty(records)) { |
| | | for (SccgDepart record : records) { |
| | | List<UmsAdminDepart> umsAdminDeparts = umsAdminDepartService.queryByDepartId(record.getId()); |
| | | if (CollUtil.isNotEmpty(umsAdminDeparts)) { |
| | | List<AdminDepartDTO.UserInfoDTO> userInfoDTOS = umsAdminDeparts.stream().map(a -> { |
| | | AdminDepartDTO.UserInfoDTO userInfoDTO = new AdminDepartDTO.UserInfoDTO(); |
| | | userInfoDTO.setUserId(a.getUserId()); |
| | | userInfoDTO.setUsername(umsAdminService.getById(a.getUserId()).getUsername()); |
| | | return userInfoDTO; |
| | | }).collect(Collectors.toList()); |
| | | record.setUserInfoDTOS(userInfoDTOS); |
| | | } |
| | | } |
| | | } |
| | | return departPage; |
| | | } |
| | | |
| | | @Override |
| | |
| | | throw new ApiException(ResultCode.RECORD_UPDATE_FAIL); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 利用递归将最后一级空集合变为null,前端联级选择器最后才不会出现 暂无数据的bug |
| | |
| | | SccgDepart sccgDepart = this.sccgDepartMapper.selectOne(queryWrapper); |
| | | return sccgDepart; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.ycl.service.user; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.ycl.api.BasePageDTO; |
| | | import com.ycl.api.CommonPage; |
| | | import com.ycl.dto.user.AdminDepartInfoDTO; |
| | | import com.ycl.entity.user.UmsAdminDepart; |
| | | import com.ycl.vo.depart.DepartVO; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * 服务类 |
| | | * </p> |
| | | * |
| | | * @author lyq |
| | | * @since 2022-09-09 |
| | | */ |
| | | public interface UmsAdminDepartService extends IService<UmsAdminDepart> { |
| | | |
| | | List<UmsAdminDepart> queryByDepartId(Long departId); |
| | | |
| | | List<UmsAdminDepart> queryByUserId(long userId); |
| | | |
| | | void deletedByDepartId(long departId); |
| | | |
| | | BasePageDTO belongDepart(long userId, int current, int pageSize); |
| | | } |
New file |
| | |
| | | package com.ycl.service.user.impl; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ycl.api.BasePageDTO; |
| | | import com.ycl.api.CommonPage; |
| | | import com.ycl.dto.user.AdminDepartInfoDTO; |
| | | import com.ycl.entity.depart.SccgDepart; |
| | | import com.ycl.entity.user.UmsAdmin; |
| | | import com.ycl.entity.user.UmsAdminDepart; |
| | | import com.ycl.mapper.user.UmsAdminDepartMapper; |
| | | import com.ycl.service.depart.SccgDepartService; |
| | | import com.ycl.service.user.UmsAdminDepartService; |
| | | import com.ycl.service.user.UmsAdminService; |
| | | import com.ycl.vo.depart.DepartVO; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * 服务实现类 |
| | | * </p> |
| | | * |
| | | * @author lyq |
| | | * @since 2022-09-09 |
| | | */ |
| | | @Service |
| | | public class UmsAdminDepartServiceImpl extends ServiceImpl<UmsAdminDepartMapper, UmsAdminDepart> implements UmsAdminDepartService { |
| | | |
| | | @Resource |
| | | private UmsAdminDepartMapper umsAdminDepartMapper; |
| | | @Resource |
| | | private SccgDepartService sccgDepartService; |
| | | @Resource |
| | | private UmsAdminService umsAdminService; |
| | | |
| | | @Override |
| | | public List<UmsAdminDepart> queryByDepartId(Long departId) { |
| | | List<UmsAdminDepart> umsAdminDeparts = umsAdminDepartMapper.selectList(new LambdaQueryWrapper<UmsAdminDepart>() |
| | | .eq(UmsAdminDepart::getDepartId, departId) |
| | | .eq(UmsAdminDepart::getIsManager, 1)); |
| | | if (CollUtil.isNotEmpty(umsAdminDeparts)) { |
| | | return umsAdminDeparts; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | @Override |
| | | public List<UmsAdminDepart> queryByUserId(long userId) { |
| | | List<UmsAdminDepart> umsAdminDeparts = umsAdminDepartMapper.selectList(new LambdaQueryWrapper<UmsAdminDepart>().eq(UmsAdminDepart::getUserId, userId)); |
| | | if (CollUtil.isNotEmpty(umsAdminDeparts)) { |
| | | return umsAdminDeparts; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | @Override |
| | | public void deletedByDepartId(long departId) { |
| | | umsAdminDepartMapper.deletedByDepartId(departId); |
| | | } |
| | | |
| | | @Override |
| | | public BasePageDTO belongDepart(long userId, int current, int pageSize) { |
| | | BasePageDTO basePageDTO = new BasePageDTO(); |
| | | Long count = umsAdminDepartMapper.selectCount(new LambdaQueryWrapper<UmsAdminDepart>().eq(UmsAdminDepart::getUserId, userId)); |
| | | basePageDTO.setTotal(count); |
| | | if (count > 0) { |
| | | current = (current - 1) * pageSize; |
| | | List<UmsAdminDepart> departList = umsAdminDepartMapper.selectPageByUserId(userId, current, pageSize); |
| | | List<DepartVO.AdminDepartInfoVO> adminDepartInfoVOS = new ArrayList<>(); |
| | | DepartVO.AdminDepartInfoVO adminDepartInfoVO = null; |
| | | for (UmsAdminDepart umsAdminDepart : departList) { |
| | | adminDepartInfoVO = new DepartVO.AdminDepartInfoVO(); |
| | | SccgDepart sccgDepart = sccgDepartService.loadDepartById(umsAdminDepart.getDepartId()); |
| | | UmsAdmin umsAdmin = umsAdminService.getById(userId); |
| | | adminDepartInfoVO.setDepartId(umsAdminDepart.getDepartId()); |
| | | adminDepartInfoVO.setDepartName(sccgDepart.getDepartName()); |
| | | adminDepartInfoVO.setDepartDes(sccgDepart.getDepartDes()); |
| | | adminDepartInfoVO.setDepartType(sccgDepart.getDepartType()); |
| | | adminDepartInfoVO.setUserId(userId); |
| | | adminDepartInfoVO.setUserName(umsAdmin.getUsername()); |
| | | adminDepartInfoVOS.add(adminDepartInfoVO); |
| | | } |
| | | basePageDTO.setRecords(adminDepartInfoVOS); |
| | | } |
| | | return basePageDTO; |
| | | } |
| | | |
| | | } |
| | |
| | | import com.ycl.mapper.user.UmsResourceMapper; |
| | | import com.ycl.mapper.user.UmsRoleMapper; |
| | | import com.ycl.service.depart.SccgDepartService; |
| | | import com.ycl.service.redis.RedisService; |
| | | import com.ycl.service.user.UmsAdminCacheService; |
| | | import com.ycl.service.user.UmsAdminDepartService; |
| | | import com.ycl.service.user.UmsAdminRoleRelationService; |
| | | import com.ycl.service.user.UmsAdminService; |
| | | import com.ycl.utils.JwtTokenUtil; |
| | | import com.ycl.utils.SpringUtil; |
| | | import com.ycl.utils.common.LiveTimeMillisecond; |
| | | import com.ycl.utils.common.MacUtils; |
| | | import com.ycl.utils.common.PojoUtils; |
| | | import com.ycl.utils.common.RandomUtils; |
| | | import com.ycl.utils.redis.RedisKey; |
| | | import com.ycl.vo.user.UserVO; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.slf4j.Logger; |
| | |
| | | private UmsResourceMapper umsResourceMapper; |
| | | @Resource |
| | | private SccgDepartService sccgDepartService; |
| | | @Resource |
| | | private UmsAdminDepartService umsAdminDepartService; |
| | | @Resource |
| | | private RedisService redisService; |
| | | |
| | | @Override |
| | | public UmsAdmin getAdminByUsername(String username) { |
| | |
| | | String encodePassword = passwordEncoder.encode(umsAdmin.getPassword()); |
| | | umsAdmin.setPassword(encodePassword); |
| | | baseMapper.insert(umsAdmin); |
| | | //对用户名系统默认添加 |
| | | umsAdmin.setNickName(RandomUtils.getUserId(umsAdmin.getId())); |
| | | baseMapper.updateById(umsAdmin); |
| | | |
| | | return umsAdmin; |
| | | } |
| | | |
| | |
| | | String token = null; |
| | | //密码需要客户端加密后传递 |
| | | try { |
| | | UserDetails userDetails = loadUserByUsername(username); |
| | | AdminUserDetails userDetails = (AdminUserDetails) loadUserByUsername(username); |
| | | if (!passwordEncoder.matches(password, userDetails.getPassword())) { |
| | | Asserts.fail("密码不正确"); |
| | | } |
| | |
| | | } |
| | | UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); |
| | | SecurityContextHolder.getContext().setAuthentication(authentication); |
| | | token = jwtTokenUtil.generateToken(userDetails); |
| | | //根据用户id,用户姓名 |
| | | token = jwtTokenUtil.generateToken(userDetails.getUserId(), userDetails.getUsername()); |
| | | redisService.set(RedisKey.PLATFORM_TOKEN_KEY.concat(username), token, LiveTimeMillisecond.s7200.time); |
| | | // updateLoginTimeByUsername(username); |
| | | insertLoginLog(username); |
| | | } catch (AuthenticationException e) { |
| | |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public boolean update(Long id, UmsAdmin admin) { |
| | | admin.setId(id); |
| | | UmsAdmin rawAdmin = getById(id); |
| | |
| | | if (PojoUtils.Vo.isUsefulSearchParam(pageUserVO.getUserType())) { |
| | | queryWrapper.eq(UmsAdmin::getUserType, pageUserVO.getUserType()); |
| | | } |
| | | if (StringUtils.isNotBlank(pageUserVO.getMobile())) { |
| | | queryWrapper.like(UmsAdmin::getMobile, pageUserVO.getMobile()); |
| | | } |
| | | if (PojoUtils.Vo.isUsefulSearchParam(pageUserVO.getDepartmentId())) { |
| | | queryWrapper.eq(UmsAdmin::getDepartmentId, pageUserVO.getDepartmentId()); |
| | | } |
| | |
| | | |
| | | import cn.hutool.core.date.DateUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.ycl.entity.auth.AuthInfo; |
| | | import com.ycl.enums.common.ResultCode; |
| | | import com.ycl.exception.ApiException; |
| | | import io.jsonwebtoken.Claims; |
| | | import io.jsonwebtoken.Jwts; |
| | | import io.jsonwebtoken.SignatureAlgorithm; |
| | |
| | | */ |
| | | public class JwtTokenUtil { |
| | | private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class); |
| | | private static final String CLAIM_KEY_USERNAME = "sub"; |
| | | private static final String CLAIM_KEY_INFO = "sub"; |
| | | private static final String CLAIM_KEY_CREATED = "created"; |
| | | @Value("${jwt.secret}") |
| | | private String secret; |
| | |
| | | */ |
| | | public String generateToken(UserDetails userDetails) { |
| | | Map<String, Object> claims = new HashMap<>(); |
| | | claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername()); |
| | | claims.put(CLAIM_KEY_INFO, userDetails.getUsername()); |
| | | claims.put(CLAIM_KEY_CREATED, new Date()); |
| | | return generateToken(claims); |
| | | } |
| | |
| | | |
| | | /** |
| | | * 判断token在指定时间内是否刚刚刷新过 |
| | | * |
| | | * @param token 原token |
| | | * @param time 指定时间(秒) |
| | | */ |
| | |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | public AuthInfo parseToken(String token) { |
| | | AuthInfo authInfo = null; |
| | | try { |
| | | if (isTokenExpired(token)) { |
| | | throw new ApiException(ResultCode.NOT_LOGGED); |
| | | } |
| | | Claims claims = getClaimsFromToken(token); |
| | | String subject = claims.getSubject(); |
| | | authInfo = JSON.parseObject(subject, AuthInfo.class); |
| | | } catch (Exception e) { |
| | | throw new ApiException(e.getMessage()); |
| | | } |
| | | return authInfo; |
| | | } |
| | | |
| | | /** |
| | | * 根据用户id,用户名生成token |
| | | */ |
| | | public String generateToken(long userId, String username) { |
| | | Map<String, Object> claims = new HashMap<>(); |
| | | AuthInfo authInfo = new AuthInfo(); |
| | | authInfo.setUserId(userId); |
| | | authInfo.setUsername(username); |
| | | claims.put(CLAIM_KEY_INFO, JSONObject.toJSONString(authInfo)); |
| | | claims.put(CLAIM_KEY_CREATED, new Date()); |
| | | return generateToken(claims); |
| | | } |
| | | } |
New file |
| | |
| | | package com.ycl.utils.auth; |
| | | |
| | | import com.ycl.entity.auth.Operator; |
| | | import com.ycl.enums.common.ResultCode; |
| | | import com.ycl.exception.ApiException; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.stereotype.Controller; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * @author Lyq |
| | | * @version 1.0 |
| | | * @date 2022/9/9 |
| | | */ |
| | | @Component |
| | | public class OperatorAuthUtil { |
| | | |
| | | @Resource |
| | | private UserAuthUtil userAuthUtil; |
| | | |
| | | private static Map<Long, Operator> platformMap = new HashMap<>(); |
| | | |
| | | /** |
| | | * 获取操作者 |
| | | * |
| | | * @param request |
| | | * @return |
| | | * @throws ApiException |
| | | */ |
| | | public Operator fetchUser(HttpServletRequest request) throws ApiException { |
| | | try { |
| | | long memberId = userAuthUtil.fetchUserId(request); |
| | | Operator operator = platformMap.get(memberId); |
| | | if (operator != null) { |
| | | return operator; |
| | | } |
| | | String memberName = userAuthUtil.fetchUserName(request); |
| | | operator = new Operator(memberId, memberName); |
| | | platformMap.put(memberId, operator); |
| | | return operator; |
| | | } catch (Exception e) { |
| | | throw new ApiException(e.getMessage()); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | package com.ycl.utils.auth; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import com.ycl.entity.auth.AuthInfo; |
| | | import com.ycl.enums.common.ResultCode; |
| | | import com.ycl.exception.ApiException; |
| | | import com.ycl.service.redis.RedisService; |
| | | import com.ycl.utils.JwtTokenUtil; |
| | | import com.ycl.utils.common.LiveTimeMillisecond; |
| | | import com.ycl.utils.common.NetworkUtil; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | |
| | | /** |
| | | * @author Lyq |
| | | * @version 1.0 |
| | | * @date 2022/9/9 |
| | | */ |
| | | @Component("userAuthUtil") |
| | | public class UserAuthUtil { |
| | | @Resource |
| | | private JwtTokenUtil jwtTokenUtil; |
| | | @Resource |
| | | private RedisService redisService; |
| | | |
| | | |
| | | public void saveUser(Long userId, String token, String redisKey) { |
| | | redisService.set(redisKey.concat(userId.toString()), token, LiveTimeMillisecond.s2592000.time); |
| | | } |
| | | |
| | | /** |
| | | * 获取操作员id |
| | | * |
| | | * @param request |
| | | * @return |
| | | */ |
| | | public long fetchUserId(HttpServletRequest request) { |
| | | String accessToken = NetworkUtil.getAccessToken(request); |
| | | if (StrUtil.isBlank(accessToken)) { |
| | | throw new ApiException(ResultCode.NOT_LOGGED); |
| | | } |
| | | AuthInfo authInfo = jwtTokenUtil.parseToken(accessToken); |
| | | return authInfo.getUserId(); |
| | | } |
| | | |
| | | /** |
| | | * 获取操作员姓名 |
| | | * |
| | | * @param request |
| | | * @return |
| | | */ |
| | | public String fetchUserName(HttpServletRequest request) { |
| | | String accessToken = NetworkUtil.getAccessToken(request); |
| | | if (StrUtil.isBlank(accessToken)) { |
| | | throw new ApiException(ResultCode.NOT_LOGGED); |
| | | } |
| | | AuthInfo authInfo = jwtTokenUtil.parseToken(accessToken); |
| | | return authInfo.getUsername(); |
| | | } |
| | | } |
New file |
| | |
| | | package com.ycl.utils.common; |
| | | |
| | | /** |
| | | * redis缓存时间 |
| | | * 单位毫秒 |
| | | * @version 1.0 |
| | | * @date 2022/9/9 |
| | | */ |
| | | public enum LiveTimeMillisecond { |
| | | |
| | | s1(1000), |
| | | /** 5秒 */ |
| | | s5(5000), |
| | | |
| | | /** 10秒 */ |
| | | s10(10000), |
| | | |
| | | /** 1分钟 */ |
| | | s60(60000), |
| | | /** 2分钟 */ |
| | | |
| | | s120(120000), |
| | | /** 3分钟*/ |
| | | s180(180000), |
| | | |
| | | /** 5分钟*/ |
| | | s300(300000), |
| | | |
| | | /** 10分钟 */ |
| | | s600(600000), |
| | | |
| | | /** 30分钟 */ |
| | | s1800(1800000), |
| | | |
| | | /** 1小时 */ |
| | | s3600(3600000), |
| | | |
| | | /** 2小时 */ |
| | | s7200(7200000), |
| | | |
| | | /** 3小时 */ |
| | | s10800(10800000), |
| | | |
| | | /** 4小时 */ |
| | | s14400(14400000), |
| | | |
| | | /** 12小时 */ |
| | | s43200(43200000), |
| | | |
| | | /** 一天 */ |
| | | s86400(86400000), |
| | | /** 三天*/ |
| | | s259200000(259200000), |
| | | |
| | | /** 七天 */ |
| | | s604800(604800000), |
| | | |
| | | /** 30天*/ |
| | | s2592000(2592000000L), |
| | | |
| | | /** 60天*/ |
| | | s5184000(5184000000L), |
| | | /**180天*/ |
| | | s15552000(15552000000L); |
| | | |
| | | /** |
| | | * 超时时间 单位秒 |
| | | */ |
| | | public long time; |
| | | |
| | | private LiveTimeMillisecond(long time) { |
| | | this.time = time; |
| | | } |
| | | |
| | | } |
| | |
| | | * @since 2022-09-06 |
| | | */ |
| | | public final class RedisKey { |
| | | |
| | | /** |
| | | * 管理后台 |
| | | */ |
| | | public static final String PLATFORM_TOKEN_KEY = "platform-token:"; |
| | | } |
| | |
| | | package com.ycl.vo.depart; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.ycl.api.BasePageVO; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * @author Lyq |
| | |
| | | private String departDes; |
| | | |
| | | @ApiModelProperty("部门类型") |
| | | private Integer departType; |
| | | private byte departType; |
| | | |
| | | @ApiModelProperty("父级id,默认0") |
| | | private Long parentId; |
| | | |
| | | @ApiModelProperty("停用状态,0->false,1->true,默认停用") |
| | | private byte status; |
| | | // @ApiModelProperty("停用状态,0->false,1->true,默认停用") |
| | | // private byte status; |
| | | } |
| | | |
| | | @ApiModel |
| | |
| | | private String departDes; |
| | | |
| | | @ApiModelProperty("部门类型") |
| | | private Integer departType; |
| | | private byte departType; |
| | | |
| | | @ApiModelProperty("父级id,默认0") |
| | | private Long parentId; |
| | | |
| | | @ApiModelProperty("停用状态,0->false,1->true,默认停用") |
| | | private byte status; |
| | | @ApiModelProperty("用户Id集合") |
| | | private List<Long> userIds; |
| | | |
| | | // @ApiModelProperty("停用状态,0->false,1->true,默认停用") |
| | | // private byte status; |
| | | } |
| | | |
| | | @ApiModel |
| | | @Data |
| | | public static class PageDepartVO extends BasePageVO { |
| | | @ApiModelProperty(value = "部门类型,0默认查询全部", example = "0") |
| | | private Integer departType; |
| | | |
| | | @ApiModelProperty("部门名称") |
| | | private String departName; |
| | | } |
| | | |
| | | @Data |
| | | public static class AdminDepartInfoVO { |
| | | @ApiModelProperty("部门Id") |
| | | private Long departId; |
| | | |
| | | @ApiModelProperty("部门名称") |
| | | private String departName; |
| | | |
| | | @ApiModelProperty("部门描述") |
| | | private String departDes; |
| | | |
| | | @ApiModelProperty("部门类型") |
| | | private byte departType; |
| | | |
| | | @ApiModelProperty("用户Id") |
| | | private Long userId; |
| | | |
| | | @ApiModelProperty("用户名") |
| | | private String userName; |
| | | } |
| | | |
| | | } |
| | |
| | | @ApiModelProperty(value = "部门id,0即为查询全部,默认0",example = "0") |
| | | private Long departmentId; |
| | | |
| | | |
| | | @ApiModelProperty(value = "用户类型,0即为查询全部,默认0",example = "0") |
| | | private byte userType; |
| | | |
| | | @ApiModelProperty(value = "职务") |
| | | private String jobTitle; |
| | | |
| | | @ApiModelProperty(value = "电话") |
| | | private String mobile; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | <?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.ycl.mapper.user.UmsAdminDepartMapper"> |
| | | |
| | | <!-- 通用查询映射结果 --> |
| | | <resultMap id="BaseResultMap" type="com.ycl.entity.user.UmsAdminDepart"> |
| | | <id column="id" property="id"/> |
| | | <result column="user_id" property="userId"/> |
| | | <result column="depart_id" property="departId"/> |
| | | <result column="is_manager" property="isManager"/> |
| | | <result column="create_time" property="createTime"/> |
| | | <result column="update_time" property="updateTime"/> |
| | | </resultMap> |
| | | <!-- 通用查询结果列 --> |
| | | <sql id="Base_Column_List"> |
| | | id |
| | | , user_id,depart_id,is_manager,create_time,update_time |
| | | </sql> |
| | | |
| | | <delete id="deletedByDepartId"> |
| | | delete |
| | | from ums_admin_depart |
| | | where depart_id = #{departId} |
| | | </delete> |
| | | |
| | | <select id="selectPageByUserId" resultMap="BaseResultMap"> |
| | | select |
| | | <include refid="Base_Column_List"/> |
| | | from ums_admin_depart |
| | | where user_id=#{userId} |
| | | limit #{current},#{pageSize} |
| | | </select> |
| | | </mapper> |
New file |
| | |
| | | package com.ycl.controller; |
| | | |
| | | import com.ycl.entity.auth.Operator; |
| | | import com.ycl.enums.common.ResultCode; |
| | | import com.ycl.exception.ApiException; |
| | | import com.ycl.utils.auth.OperatorAuthUtil; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | |
| | | /** |
| | | * @author Lyq |
| | | * @version 1.0 |
| | | * @date 2022/9/9 |
| | | */ |
| | | @RestController |
| | | public class BaseController { |
| | | @Resource |
| | | private OperatorAuthUtil operatorAuthUtil; |
| | | @Resource |
| | | protected HttpServletRequest request; |
| | | |
| | | protected Operator fetchOperator(HttpServletRequest request) throws ApiException { |
| | | if (null == request) { |
| | | throw new ApiException(ResultCode.NULL_PARAMETER); |
| | | } |
| | | |
| | | Operator operator = operatorAuthUtil.fetchUser(request); |
| | | if (null == operator) { |
| | | throw new ApiException(ResultCode.OPERATOR_TYPE_FETCH_FAIL); |
| | | } |
| | | return operator; |
| | | } |
| | | } |
| | |
| | | package com.ycl.controller.depart; |
| | | |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ycl.api.BasePageDTO; |
| | | import com.ycl.api.BasePageVO; |
| | | import com.ycl.api.CommonPage; |
| | | import com.ycl.api.CommonResult; |
| | | import com.ycl.controller.BaseController; |
| | | import com.ycl.dto.user.AdminDepartInfoDTO; |
| | | import com.ycl.entity.auth.Operator; |
| | | import com.ycl.entity.depart.SccgDepart; |
| | | import com.ycl.entity.user.UmsAdminDepart; |
| | | import com.ycl.entity.user.UmsMenu; |
| | | import com.ycl.service.depart.SccgDepartService; |
| | | import com.ycl.service.user.UmsAdminDepartService; |
| | | import com.ycl.service.user.UmsMenuService; |
| | | import com.ycl.vo.depart.DepartVO; |
| | | import io.swagger.annotations.Api; |
| | |
| | | @RestController |
| | | @Api(tags = "部门管理模块") |
| | | @RequestMapping("/depart") |
| | | public class DepartController { |
| | | public class DepartController extends BaseController { |
| | | |
| | | @Autowired |
| | | private SccgDepartService departService; |
| | | @Resource |
| | | private UmsAdminDepartService umsAdminDepartService; |
| | | |
| | | @ApiOperation("添加部门") |
| | | @PostMapping(value = "/create") |
| | | public CommonResult<Void> create(@Validated @RequestBody DepartVO.AddDepartVO addDepartVO) { |
| | | departService.create(addDepartVO); |
| | | return CommonResult.success(null); |
| | | } |
| | | |
| | | @ApiOperation("编辑部门") |
| | | @PostMapping(value = "/update") |
| | | public CommonResult<Void> create(@Validated @RequestBody DepartVO.UpdateDepartVO params) { |
| | | departService.update(params); |
| | | return CommonResult.success(null); |
| | | } |
| | | |
| | |
| | | return CommonResult.success(null); |
| | | } |
| | | |
| | | @ApiOperation("查询全部部门") |
| | | @GetMapping(value = "/page") |
| | | public CommonResult<IPage<SccgDepart>> page(DepartVO.PageDepartVO params) { |
| | | IPage<SccgDepart> page = departService.pageDepart(params); |
| | | return CommonResult.success(page); |
| | | } |
| | | |
| | | @ApiOperation("查询我的部门") |
| | | @GetMapping(value = "/belongDepart") |
| | | public CommonResult<BasePageDTO> belongDepart(BasePageVO params) { |
| | | BasePageDTO basePageDTO= umsAdminDepartService.belongDepart(fetchOperator(request).getOperatorId(), params.getCurrent(), params.getPageSize()); |
| | | return CommonResult.success(basePageDTO); |
| | | } |
| | | } |
| | | |
| | |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ycl.api.CommonPage; |
| | | import com.ycl.api.CommonResult; |
| | | import com.ycl.controller.BaseController; |
| | | import com.ycl.dto.UmsAdminLoginParam; |
| | | import com.ycl.dto.UmsAdminParam; |
| | | import com.ycl.dto.UpdateAdminPasswordParam; |
| | | import com.ycl.entity.user.UmsAdmin; |
| | | import com.ycl.entity.user.UmsRole; |
| | | import com.ycl.service.redis.RedisService; |
| | | import com.ycl.service.user.UmsAdminService; |
| | | import com.ycl.service.user.UmsRoleService; |
| | | import com.ycl.utils.redis.RedisKey; |
| | | import com.ycl.vo.user.UserVO; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import java.security.Principal; |
| | | import java.util.HashMap; |
| | |
| | | @Controller |
| | | @Api(tags = "后台用户管理") |
| | | @RequestMapping("/admin") |
| | | public class UmsAdminController { |
| | | public class UmsAdminController extends BaseController { |
| | | @Value("${jwt.tokenHeader}") |
| | | private String tokenHeader; |
| | | @Value("${jwt.tokenHead}") |
| | |
| | | private UmsAdminService adminService; |
| | | @Autowired |
| | | private UmsRoleService roleService; |
| | | @Resource |
| | | private RedisService redisService; |
| | | |
| | | @ApiOperation(value = "用户注册") |
| | | @RequestMapping(value = "/register", method = RequestMethod.POST) |
| | |
| | | @RequestMapping(value = "/logout", method = RequestMethod.POST) |
| | | @ResponseBody |
| | | public CommonResult logout() { |
| | | String operatorName = fetchOperator(request).getOperatorName(); |
| | | redisService.del(RedisKey.PLATFORM_TOKEN_KEY.concat(operatorName)); |
| | | return CommonResult.success(null); |
| | | } |
| | | |
| | | @ApiOperation("分页") |
| | | @RequestMapping(value = "/page", method = RequestMethod.GET) |
| | | @RequestMapping(value = "/list", method = RequestMethod.GET) |
| | | @ResponseBody |
| | | public CommonResult<IPage<UmsAdmin>> list(@Validated UserVO.PageUserVO pageUserVO) { |
| | | IPage<UmsAdmin> page = adminService.pageUser(pageUserVO); |