完成主要在线 session 的功能

pull/2/head
YunaiV 2021-01-30 01:21:25 +08:00
parent ab94fe2d4b
commit 753c7678ee
34 changed files with 1349 additions and 1260 deletions

View File

@ -13,12 +13,10 @@ import com.ruoyi.common.core.controller.BaseController;
*/
@Controller
@RequestMapping("/tool/swagger")
public class SwaggerController extends BaseController
{
public class SwaggerController extends BaseController {
@PreAuthorize("@ss.hasPermi('tool:swagger:view')")
@GetMapping()
public String index()
{
public String index() {
return redirect("/swagger-ui.html");
}
}

View File

@ -3,7 +3,7 @@ import request from '@/utils/request'
// 查询在线用户列表
export function list(query) {
return request({
url: '/monitor/online/list',
url: '/system/user-session/page',
method: 'get',
params: query
})
@ -12,7 +12,7 @@ export function list(query) {
// 强退用户
export function forceLogout(tokenId) {
return request({
url: '/monitor/online/' + tokenId,
url: '/system/user-session/delete?id=' + tokenId,
method: 'delete'
})
}

View File

@ -1,18 +1,18 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
<el-form-item label="登录地址" prop="ipaddr">
<el-form-item label="登录地址" prop="userIp">
<el-input
v-model="queryParams.ipaddr"
v-model="queryParams.userIp"
placeholder="请输入登录地址"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="用户名称" prop="userName">
<el-form-item label="用户名称" prop="username">
<el-input
v-model="queryParams.userName"
v-model="queryParams.username"
placeholder="请输入用户名称"
clearable
size="small"
@ -27,24 +27,17 @@
</el-form>
<el-table
v-loading="loading"
:data="list.slice((pageNum-1)*pageSize,pageNum*pageSize)"
:data="list"
style="width: 100%;"
>
<el-table-column label="序号" type="index" align="center">
<el-table-column label="会话编号" align="center" prop="id" width="300" />
<el-table-column label="登录名称" align="center" prop="username" width="100" />
<el-table-column label="部门名称" align="center" prop="deptName" width="100" />
<el-table-column label="登陆地址" align="center" prop="userIp" width="100" />
<el-table-column label="userAgent" align="center" prop="userAgent" :show-overflow-tooltip="true" />
<el-table-column label="登录时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{(pageNum - 1) * pageSize + scope.$index + 1}}</span>
</template>
</el-table-column>
<el-table-column label="会话编号" align="center" prop="tokenId" :show-overflow-tooltip="true" />
<el-table-column label="登录名称" align="center" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="部门名称" align="center" prop="deptName" />
<el-table-column label="主机" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
<el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
<el-table-column label="浏览器" align="center" prop="browser" />
<el-table-column label="操作系统" align="center" prop="os" />
<el-table-column label="登录时间" align="center" prop="loginTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.loginTime) }}</span>
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
@ -54,18 +47,18 @@
type="text"
icon="el-icon-delete"
@click="handleForceLogout(scope.row)"
v-hasPermi="['monitor:online:forceLogout']"
v-hasPermi="['system:user-session:delete']"
>强退</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="pageNum" :limit.sync="pageSize" />
<pagination v-show="total>0" :total="total" :page.sync="pageNo" :limit.sync="pageSize" />
</div>
</template>
<script>
import { list, forceLogout } from "@/api/monitor/online";
import { list, forceLogout } from "@/api/system/session";
export default {
name: "Online",
@ -77,12 +70,12 @@ export default {
total: 0,
//
list: [],
pageNum: 1,
pageSize: 10,
//
queryParams: {
ipaddr: undefined,
userName: undefined
pageNo: 1,
pageSize: 10,
userIp: undefined,
username: undefined
}
};
},
@ -94,14 +87,14 @@ export default {
getList() {
this.loading = true;
list(this.queryParams).then(response => {
this.list = response.rows;
this.total = response.total;
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.pageNum = 1;
this.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
@ -111,12 +104,12 @@ export default {
},
/** 强退按钮操作 */
handleForceLogout(row) {
this.$confirm('是否确认强退名称为"' + row.userName + '"的数据项?', "警告", {
this.$confirm('是否确认强退名称为"' + row.username + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return forceLogout(row.tokenId);
return forceLogout(row.id);
}).then(() => {
this.getList();
this.msgSuccess("强退成功");

View File

@ -4,8 +4,14 @@ import cn.iocoder.dashboard.common.pojo.CommonResult;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageItemRespVO;
import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageReqVO;
import cn.iocoder.dashboard.modules.system.convert.auth.SysUserSessionConvert;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.auth.SysUserSessionDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dept.SysDeptDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService;
import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService;
import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
import cn.iocoder.dashboard.util.collection.MapUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
@ -14,26 +20,50 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
import static cn.iocoder.dashboard.util.collection.CollectionUtils.convertList;
@Api("用户 Session API")
@RestController
@RequestMapping("/user-session")
@RequestMapping("/system/user-session")
public class SysUserSessionController {
@Resource
private SysUserSessionService userSessionService;
@Resource
private SysUserService userService;
@Resource
private SysDeptService deptService;
@ApiOperation("获得 Session 分页列表")
@PreAuthorize("@ss.hasPermission('system:user-session:page')")
@GetMapping("/page")
public CommonResult<PageResult<SysUserSessionPageItemRespVO>> getUserSessionPage(@Validated SysUserSessionPageReqVO reqVO) {
// 获得 Session 分页
PageResult<SysUserSessionDO> sessionPage = userSessionService.getUserSessionPage(reqVO);
PageResult<SysUserSessionDO> pageResult = userSessionService.getUserSessionPage(reqVO);
//
return null;
// 获得拼接需要的数据
Map<Long, SysUserDO> userMap = userService.getUserMap(
convertList(pageResult.getList(), SysUserSessionDO::getUserId));
Map<Long, SysDeptDO> deptMap = deptService.getDeptMap(
convertList(userMap.values(), SysUserDO::getDeptId));
// 拼接结果返回
List<SysUserSessionPageItemRespVO> sessionList = new ArrayList<>(pageResult.getList().size());
pageResult.getList().forEach(session -> {
SysUserSessionPageItemRespVO respVO = SysUserSessionConvert.INSTANCE.convert(session);
sessionList.add(respVO);
// 设置用户账号
MapUtils.findAndThen(userMap, session.getUserId(), user -> {
respVO.setUsername(user.getUsername());
// 设置用户部门
MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> respVO.setDeptName(dept.getName()));
});
});
return success(new PageResult<>(sessionList, pageResult.getTotal()));
}
@ApiOperation("删除 Session")

View File

@ -8,6 +8,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.Date;
@ApiModel(value = "用户在线 Session Response VO", description = "相比用户基本信息来说,会多部门、用户账号等信息")
@Data
@NoArgsConstructor
@ -25,7 +27,7 @@ public class SysUserSessionPageItemRespVO extends PageParam {
private String userAgent;
@ApiModelProperty(value = "登陆时间", required = true)
private String createTime;
private Date createTime;
@ApiModelProperty(value = "用户账号", required = true, example = "yudao")
private String username;

View File

@ -14,7 +14,6 @@ import javax.validation.constraints.NotEmpty;
public class SysUserSessionPageReqVO extends PageParam {
@ApiModelProperty(value = "用户 IP", example = "127.0.0.1", notes = "模糊匹配")
@NotEmpty(message = "用户 IP 不能为空")
private String userIp;
@ApiModelProperty(value = "用户账号", example = "yudao", notes = "模糊匹配")

View File

@ -0,0 +1,16 @@
package cn.iocoder.dashboard.modules.system.convert.auth;
import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageItemRespVO;
import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserPageItemRespVO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.auth.SysUserSessionDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface SysUserSessionConvert {
SysUserSessionConvert INSTANCE = Mappers.getMapper(SysUserSessionConvert.class);
SysUserSessionPageItemRespVO convert(SysUserSessionDO session);
}

View File

@ -1,9 +1,21 @@
package cn.iocoder.dashboard.modules.system.dal.mysql.dao.auth;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageReqVO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.auth.SysUserSessionDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
@Mapper
public interface SysUserSessionMapper extends BaseMapper<SysUserSessionDO> {
public interface SysUserSessionMapper extends BaseMapperX<SysUserSessionDO> {
default PageResult<SysUserSessionDO> selectPage(SysUserSessionPageReqVO reqVO, Collection<Long> userIds) {
return selectPage(reqVO, new QueryWrapperX<SysUserSessionDO>()
.inIfPresent("user_id", userIds)
.likeIfPresent("user_ip", reqVO.getUserIp()));
}
}

View File

@ -48,5 +48,9 @@ public interface SysUserMapper extends BaseMapperX<SysUserDO> {
return selectList(new QueryWrapperX<SysUserDO>().like("nickname", nickname));
}
default List<SysUserDO> selectListByUsername(String username) {
return selectList(new QueryWrapperX<SysUserDO>().like("username", username));
}
}

View File

@ -3,6 +3,7 @@ package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.auth;
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.dashboard.framework.security.core.LoginUser;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
@ -27,7 +28,7 @@ public class SysUserSessionDO extends BaseDO {
/**
* , sessionId
*/
@TableId
@TableId(type = IdType.INPUT)
private String id;
/**
*

View File

@ -30,8 +30,8 @@ public class SysLoginUserRedisDAO {
stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(loginUser), LOGIN_USER.getTimeout());
}
public void delete(String accessToken) {
String redisKey = formatKey(accessToken);
public void delete(String sessionId) {
String redisKey = formatKey(sessionId);
stringRedisTemplate.delete(redisKey);
}

View File

@ -0,0 +1 @@
package cn.iocoder.dashboard.modules.system.job;

View File

@ -1,19 +1,26 @@
package cn.iocoder.dashboard.modules.system.service.auth.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
import cn.iocoder.dashboard.framework.security.core.LoginUser;
import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageReqVO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dao.auth.SysUserSessionMapper;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.auth.SysUserSessionDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
import cn.iocoder.dashboard.modules.system.dal.redis.dao.auth.SysLoginUserRedisDAO;
import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService;
import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Date;
import static cn.iocoder.dashboard.util.collection.CollectionUtils.convertSet;
/**
* 线 Session Service
*
@ -27,10 +34,12 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
@Resource
private SysLoginUserRedisDAO loginUserRedisDAO;
@Resource
private SysUserSessionMapper userSessionMapper;
@Resource
private SysUserService userService;
@Override
public String createUserSession(LoginUser loginUser, String userIp, String userAgent) {
// 生成 Session 编号
@ -39,8 +48,8 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
loginUser.setUpdateTime(new Date());
loginUserRedisDAO.set(sessionId, loginUser);
// 写入 DB 中
SysUserSessionDO userSession = SysUserSessionDO.builder().userId(loginUser.getId())
.userIp(userIp).userAgent(userAgent).build();
SysUserSessionDO userSession = SysUserSessionDO.builder().id(sessionId)
.userId(loginUser.getId()).userIp(userIp).userAgent(userAgent).build();
userSessionMapper.insert(userSession);
// 返回 Session 编号
return sessionId;
@ -59,7 +68,10 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
@Override
public void deleteUserSession(String sessionId) {
// 删除 Redis 缓存
loginUserRedisDAO.delete(sessionId);
// 删除 DB 记录
userSessionMapper.deleteById(sessionId);
}
@Override
@ -74,7 +86,15 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
@Override
public PageResult<SysUserSessionDO> getUserSessionPage(SysUserSessionPageReqVO reqVO) {
return null;
// 处理基于用户昵称的查询
Collection<Long> userIds = null;
if (StrUtil.isNotEmpty(reqVO.getUsername())) {
userIds = convertSet(userService.listUsersByUsername(reqVO.getUsername()), SysUserDO::getId);
if (CollUtil.isEmpty(userIds)) {
return PageResult.empty();
}
}
return userSessionMapper.selectPage(reqVO, userIds);
}
/**

View File

@ -79,6 +79,14 @@ public interface SysUserService {
*/
List<SysUserDO> listUsersByNickname(String nickname);
/**
*
*
* @param username
* @return
*/
List<SysUserDO> listUsersByUsername(String username);
/**
*
*

View File

@ -92,6 +92,11 @@ public class SysUserServiceImpl implements SysUserService {
return userMapper.selectListByNickname(nickname);
}
@Override
public List<SysUserDO> listUsersByUsername(String username) {
return userMapper.selectListByUsername(username);
}
/**
*
*

View File

@ -26,35 +26,35 @@ public class CollectionUtils {
return from.stream().filter(predicate).collect(Collectors.toList());
}
public static <T, U> List<U> convertList(List<T> from, Function<T, U> func) {
public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return from.stream().map(func).collect(Collectors.toList());
}
public static <T, U> Set<U> convertSet(List<T> from, Function<T, U> func) {
public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
if (CollUtil.isEmpty(from)) {
return new HashSet<>();
}
return from.stream().map(func).collect(Collectors.toSet());
}
public static <T, K> Map<K, T> convertMap(List<T> from, Function<T, K> keyFunc) {
public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return from.stream().collect(Collectors.toMap(keyFunc, item -> item));
}
public static <T, K, V> Map<K, V> convertMap(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return from.stream().collect(Collectors.toMap(keyFunc, valueFunc));
}
public static <T, K> Map<K, List<T>> convertMultiMap(List<T> from, Function<T, K> keyFunc) {
public static <T, K> Map<K, List<T>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
@ -62,7 +62,7 @@ public class CollectionUtils {
Collectors.mapping(t -> t, Collectors.toList())));
}
public static <T, K, V> Map<K, List<V>> convertMultiMap(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
public static <T, K, V> Map<K, List<V>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
@ -71,7 +71,7 @@ public class CollectionUtils {
}
// 暂时没想好名字,先以 2 结尾噶
public static <T, K, V> Map<K, Set<V>> convertMultiMap2(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
public static <T, K, V> Map<K, Set<V>> convertMultiMap2(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}