Commit 5889fc8facde4774e54ddbf34df14b812cbd2436

Authored by 二世阿博
Committed by JEECG开源社区
1 parent 1183dd0a

!62 新增监控在线用户

* 去掉durid广告
* 新增监控在线用户功能
* 新增监控在线用户功能
ant-design-vue-jeecg/src/api/login.js
... ... @@ -71,4 +71,17 @@ export function thirdLogin(token,thirdType) {
71 71 'Content-Type': 'application/json;charset=UTF-8'
72 72 }
73 73 })
  74 +}
  75 +
  76 +/**
  77 + * 强退其他账号
  78 + * @param token
  79 + * @returns {*}
  80 + */
  81 +export function forceLogout(parameter) {
  82 + return axios({
  83 + url: '/sys/online/forceLogout',
  84 + method: 'post',
  85 + data: parameter
  86 + })
74 87 }
75 88 \ No newline at end of file
... ...
ant-design-vue-jeecg/src/views/system/SysOnlineList.vue 0 → 100644
  1 +<template>
  2 + <a-card :bordered="false">
  3 + <!-- 查询区域 -->
  4 + <div class="table-page-search-wrapper">
  5 + <a-form layout="inline" @keyup.enter.native="searchQuery">
  6 + <a-row :gutter="24">
  7 + <a-col :md="6" :sm="12">
  8 + <a-form-item label="账号">
  9 + <a-input placeholder="请输入账号查询" v-model="queryParam.username"></a-input>
  10 + </a-form-item>
  11 + </a-col>
  12 + <a-col :md="6" :sm="8">
  13 + <span style="float: left;overflow: hidden;" class="table-page-search-submitButtons">
  14 + <a-button type="primary" @click="searchQuery" icon="search">查询</a-button>
  15 + <a-button type="primary" @click="searchReset" icon="reload" style="margin-left: 8px">重置</a-button>
  16 + </span>
  17 + </a-col>
  18 + </a-row>
  19 + </a-form>
  20 + </div>
  21 + <!-- 查询区域-END -->
  22 +
  23 + <!-- table区域-begin -->
  24 + <div>
  25 + <div class="ant-alert ant-alert-info" style="margin-bottom: 16px;">
  26 + <i class="anticon anticon-info-circle ant-alert-icon"></i> 已选择 <a style="font-weight: 600">{{ selectedRowKeys.length }}</a>项
  27 + <a style="margin-left: 24px" @click="onClearSelected">清空</a>
  28 + </div>
  29 +
  30 + <a-table
  31 + ref="table"
  32 + size="middle"
  33 + :scroll="{x:true}"
  34 + bordered
  35 + rowKey="token"
  36 + :columns="columns"
  37 + :dataSource="dataSource"
  38 + :pagination="ipagination"
  39 + :loading="loading"
  40 + :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
  41 + class="j-table-force-nowrap"
  42 + @change="handleTableChange">
  43 +
  44 + <template slot="avatarslot" slot-scope="text, record, index">
  45 + <div class="anty-img-wrap">
  46 + <a-avatar shape="square" :src="getAvatarView(record.avatar)" icon="user"/>
  47 + </div>
  48 + </template>
  49 +
  50 + <span slot="action" slot-scope="text, record">
  51 + <a-popconfirm title="强制退出用户?" @confirm="() => handleForce(record)">
  52 + <a-button type="danger">强退</a-button>
  53 + </a-popconfirm>
  54 + </span>
  55 +
  56 + </a-table>
  57 + </div>
  58 +
  59 + </a-card>
  60 +</template>
  61 +
  62 +<script>
  63 +
  64 + import '@/assets/less/TableExpand.less'
  65 + import { mixinDevice } from '@/utils/mixin'
  66 + import { JeecgListMixin } from '@/mixins/JeecgListMixin'
  67 + import { forceLogout } from '@/api/login'
  68 + import {filterDictTextByCache} from '@/components/dict/JDictSelectUtil'
  69 +
  70 + import {getFileAccessHttpUrl} from '@/api/manage';
  71 +
  72 + export default {
  73 + name: "OnlineList",
  74 + mixins:[JeecgListMixin, mixinDevice],
  75 + components: {},
  76 + data () {
  77 + return {
  78 + description: '在线用户管理页面',
  79 + queryParam: {
  80 + username: ''
  81 + },
  82 + // 表头
  83 + columns: [
  84 + {
  85 + title:'用户账号',
  86 + align:"center",
  87 + dataIndex: 'username'
  88 + },{
  89 + title:'用户姓名',
  90 + align:"center",
  91 + dataIndex: 'realname'
  92 + },{
  93 + title: '头像',
  94 + align: "center",
  95 + width: 120,
  96 + dataIndex: 'avatar',
  97 + scopedSlots: {customRender: "avatarslot"}
  98 + },{
  99 + title:'生日',
  100 + align:"center",
  101 + dataIndex: 'birthday'
  102 + },{
  103 + title: '性别',
  104 + align: "center",
  105 + dataIndex: 'sex',
  106 + customRender: (text) => {
  107 + //字典值翻译通用方法
  108 + return filterDictTextByCache('sex', text);
  109 + }
  110 + },{
  111 + title:'手机号',
  112 + align:"center",
  113 + dataIndex: 'phone'
  114 + },{
  115 + title: '操作',
  116 + dataIndex: 'action',
  117 + scopedSlots: {customRender: 'action'},
  118 + align: "center",
  119 + width: 170
  120 + }
  121 + ],
  122 + url: {
  123 + list: "/sys/online/list"
  124 + },
  125 + dictOptions:{},
  126 + }
  127 + },
  128 + created() {
  129 + },
  130 + computed: {
  131 + importExcelUrl: function(){
  132 + return `${window._CONFIG['domianURL']}/${this.url.importExcelUrl}`;
  133 + },
  134 + },
  135 + methods: {
  136 + getAvatarView: function (avatar) {
  137 + return getFileAccessHttpUrl(avatar)
  138 + },
  139 + handleForce(record) {
  140 + let that = this;
  141 + let forceParam = {
  142 + token: record.token
  143 + }
  144 + return forceLogout(forceParam).then((res) => {
  145 + if (res.success) {
  146 + that.loadData();
  147 + this.$message.success('强制退出用户”'+record.realname+'“成功!');
  148 + } else {
  149 + that.$message.warning(res.message);
  150 + }
  151 + })
  152 + }
  153 + }
  154 + }
  155 +</script>
  156 +<style scoped>
  157 + @import '~@assets/less/common.less';
  158 +</style>
0 159 \ No newline at end of file
... ...
jeecg-boot/db/jeecgboot-mysql-5.7.sql
... ... @@ -5687,6 +5687,7 @@ INSERT INTO `sys_permission` VALUES (&#39;fb367426764077dcf94640c843733985&#39;, &#39;2a470f
5687 5687 INSERT INTO `sys_permission` VALUES ('fba41089766888023411a978d13c0aa4', 'e41b69c57a941a3bbcce45032fe57605', 'AUTO树表单列表', '/online/cgformTreeList/:code', 'modules/online/cgform/auto/OnlCgformTreeList', NULL, NULL, 1, NULL, '1', 9.00, 0, NULL, 1, 1, NULL, 1, NULL, 'admin', '2019-05-21 14:46:50', 'admin', '2019-06-11 13:52:52', 0, 0, '1', NULL);
5688 5688 INSERT INTO `sys_permission` VALUES ('fc810a2267dd183e4ef7c71cc60f4670', '700b7f95165c46cc7a78bf227aa8fed3', '请求追踪', '/monitor/HttpTrace', 'modules/monitor/HttpTrace', NULL, NULL, 1, NULL, NULL, 4.00, 0, NULL, 1, 1, NULL, 0, NULL, 'admin', '2019-04-02 09:46:19', 'admin', '2019-04-02 11:37:27', 0, 0, NULL, NULL);
5689 5689 INSERT INTO `sys_permission` VALUES ('fedfbf4420536cacc0218557d263dfea', '6e73eb3c26099c191bf03852ee1310a1', '新消息通知', '/account/settings/notification', 'account/settings/Notification', NULL, NULL, 1, 'NotificationSettings', NULL, NULL, NULL, '', 1, 1, NULL, NULL, NULL, NULL, '2018-12-26 19:02:05', NULL, NULL, 0, 0, NULL, NULL);
  5690 +INSERT INTO `sys_permission` VALUES ('1402436404646010882', '08e6b9dc3c04489c8e1ff2ce6f105aa4', '在线用户', '/isystem/online', 'system/SysOnlineList', NULL, NULL, 1, NULL, '1', 1.00, 0, NULL, 1, 1, 0, 0, NULL, 'admin', '2021-06-09 09:24:30', 'admin', '2021-06-09 09:37:20', 0, 0, '1', 0);
5690 5691  
5691 5692 -- ----------------------------
5692 5693 -- Table structure for sys_permission_data_rule
... ...
jeecg-boot/db/增量SQL/2.4.3升级到2.4.5增量MYSQL.sql
... ... @@ -13,6 +13,8 @@ ADD COLUMN `third_user_id` varchar(100) NULL COMMENT &#39;第三方app用户账号&#39;
13 13  
14 14 -- 新增第三方APP消息测试菜单
15 15 INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) VALUES ('1387612436586065922', '2a470fc0c3954d9dbb61de6d80846549', '第三方APP消息测试', '/jeecg/ThirdAppMessageTest', 'jeecg/ThirdAppMessageTest', '1', NULL, NULL, '1', NULL, '1', '3', '0', NULL, '1', '0', '0', NULL, 'admin', '2021-04-29 11:39:20', 'admin', '2021-04-29 11:39:27', '0', '0', '1', '0');
  16 +-- 新增监控在线用户
  17 +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) VALUES ('1402436404646010882', '08e6b9dc3c04489c8e1ff2ce6f105aa4', '在线用户', '/isystem/online', 'system/SysOnlineList', NULL, NULL, 1, NULL, '1', 1.00, 0, NULL, 1, 1, 0, 0, NULL, 'admin', '2021-06-09 09:24:30', 'admin', '2021-06-09 09:37:20', 0, 0, '1', 0);
16 18  
17 19 -- 定时任务:一个类允许配置多个调度
18 20 -- 删除定时任务表唯一索引
... ...
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/DruidConfig.java 0 → 100644
  1 +package org.jeecg.config;
  2 +
  3 +import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
  4 +import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
  5 +import com.alibaba.druid.util.Utils;
  6 +import org.springframework.boot.autoconfigure.AutoConfigureAfter;
  7 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  8 +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
  9 +import org.springframework.boot.web.servlet.FilterRegistrationBean;
  10 +import org.springframework.context.annotation.Bean;
  11 +import org.springframework.context.annotation.Configuration;
  12 +
  13 +import javax.servlet.*;
  14 +import java.io.IOException;
  15 +
  16 +@Configuration
  17 +@AutoConfigureAfter(DruidDataSourceAutoConfigure.class)
  18 +public class DruidConfig {
  19 +
  20 + /**
  21 + * 带有广告的common.js全路径,druid-1.1.14
  22 + */
  23 + private static final String FILE_PATH = "support/http/resources/js/common.js";
  24 + /**
  25 + * 原始脚本,触发构建广告的语句
  26 + */
  27 + private static final String ORIGIN_JS = "this.buildFooter();";
  28 + /**
  29 + * 替换后的脚本
  30 + */
  31 + private static final String NEW_JS = "//this.buildFooter();";
  32 +
  33 + /**
  34 + * 去除Druid监控页面的广告
  35 + *
  36 + * @param properties DruidStatProperties属性集合
  37 + * @return {@link org.springframework.boot.web.servlet.FilterRegistrationBean}
  38 + */
  39 + @Bean
  40 + @ConditionalOnWebApplication
  41 + @ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true")
  42 + public FilterRegistrationBean<RemoveAdFilter> removeDruidAdFilter(
  43 + DruidStatProperties properties) throws IOException {
  44 + // 获取web监控页面的参数
  45 + DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
  46 + // 提取common.js的配置路径
  47 + String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
  48 + String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
  49 + // 获取common.js
  50 + String text = Utils.readFromResource(FILE_PATH);
  51 + // 屏蔽 this.buildFooter(); 不构建广告
  52 + final String newJs = text.replace(ORIGIN_JS, NEW_JS);
  53 + FilterRegistrationBean<RemoveAdFilter> registration = new FilterRegistrationBean<>();
  54 + registration.setFilter(new RemoveAdFilter(newJs));
  55 + registration.addUrlPatterns(commonJsPattern);
  56 + return registration;
  57 + }
  58 +
  59 + /**
  60 + * 删除druid的广告过滤器
  61 + *
  62 + * @author BBF
  63 + */
  64 + private class RemoveAdFilter implements Filter {
  65 +
  66 + private final String newJs;
  67 +
  68 + public RemoveAdFilter(String newJS) {
  69 + this.newJs = newJS;
  70 + }
  71 +
  72 + @Override
  73 + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  74 + throws IOException, ServletException {
  75 + chain.doFilter(request, response);
  76 + // 重置缓冲区,响应头不会被重置
  77 + response.resetBuffer();
  78 + response.getWriter().write(newJs);
  79 + }
  80 + }
  81 +}
... ...
jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/modules/system/controller/SysOnlineController.java 0 → 100644
  1 +package org.jeecg.modules.system.controller;
  2 +
  3 +import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.apache.commons.lang.StringUtils;
  6 +import org.apache.shiro.SecurityUtils;
  7 +import org.jeecg.common.api.vo.Result;
  8 +import org.jeecg.common.constant.CacheConstant;
  9 +import org.jeecg.common.constant.CommonConstant;
  10 +import org.jeecg.common.system.api.ISysBaseAPI;
  11 +import org.jeecg.common.system.util.JwtUtil;
  12 +import org.jeecg.common.system.vo.LoginUser;
  13 +import org.jeecg.common.util.RedisUtil;
  14 +import org.jeecg.common.util.oConvertUtils;
  15 +import org.jeecg.modules.base.service.BaseCommonService;
  16 +import org.jeecg.modules.system.service.ISysUserService;
  17 +import org.jeecg.modules.system.vo.SysOnlineVO;
  18 +import org.springframework.beans.BeanUtils;
  19 +import org.springframework.beans.factory.annotation.Autowired;
  20 +import org.springframework.data.redis.core.RedisTemplate;
  21 +import org.springframework.web.bind.annotation.*;
  22 +
  23 +import javax.annotation.Resource;
  24 +import java.util.ArrayList;
  25 +import java.util.Collection;
  26 +import java.util.Collections;
  27 +import java.util.List;
  28 +
  29 +/**
  30 + * @Description: 在线用户
  31 + * @Author: chenli
  32 + * @Date: 2020-06-07
  33 + * @Version: V1.0
  34 + */
  35 +@RestController
  36 +@RequestMapping("/sys/online")
  37 +@Slf4j
  38 +public class SysOnlineController {
  39 +
  40 + @Autowired
  41 + private RedisUtil redisUtil;
  42 +
  43 + @Autowired
  44 + public RedisTemplate redisTemplate;
  45 +
  46 + @Autowired
  47 + public ISysUserService userService;
  48 +
  49 + @Autowired
  50 + private ISysBaseAPI sysBaseAPI;
  51 +
  52 + @Resource
  53 + private BaseCommonService baseCommonService;
  54 +
  55 + @RequestMapping(value = "/list", method = RequestMethod.GET)
  56 + public Result<Page<SysOnlineVO>> list(@RequestParam(name="username", required=false) String username, @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
  57 + @RequestParam(name="pageSize", defaultValue="10") Integer pageSize) {
  58 + Collection<String> keys = redisTemplate.keys(CommonConstant.PREFIX_USER_TOKEN + "*");
  59 + SysOnlineVO online;
  60 + List<SysOnlineVO> onlineList = new ArrayList<SysOnlineVO>();
  61 + for (String key : keys) {
  62 + online = new SysOnlineVO();
  63 + String token = (String) redisUtil.get(key);
  64 + if (!StringUtils.isEmpty(token)){
  65 + online.setToken(token);
  66 + LoginUser loginUser = sysBaseAPI.getUserByName(JwtUtil.getUsername(token));
  67 + BeanUtils.copyProperties(loginUser, online);
  68 + if (StringUtils.isNotEmpty(username)) {
  69 + if (StringUtils.equals(username, online.getUsername())) {
  70 + onlineList.add(online);
  71 + }
  72 + } else {
  73 + onlineList.add(online);
  74 + }
  75 + }
  76 + }
  77 +
  78 + Page<SysOnlineVO> page = new Page<SysOnlineVO>(pageNo, pageSize);
  79 + int count = onlineList.size();
  80 + List<SysOnlineVO> pages = new ArrayList<>();
  81 + //计算当前页第一条数据的下标
  82 + int currId = pageNo>1 ? (pageNo-1)*pageSize:0;
  83 + for (int i=0; i<pageSize && i<count - currId;i++){
  84 + pages.add(onlineList.get(currId+i));
  85 + }
  86 + page.setSize(pageSize);
  87 + page.setCurrent(pageNo);
  88 + page.setTotal(count);
  89 + //计算分页总页数
  90 + page.setPages(count %10 == 0 ? count/10 :count/10+1);
  91 + page.setRecords(pages);
  92 +
  93 + Collections.reverse(onlineList);
  94 + onlineList.removeAll(Collections.singleton(null));
  95 + Result<Page<SysOnlineVO>> result = new Result<Page<SysOnlineVO>>();
  96 + result.setSuccess(true);
  97 + result.setResult(page);
  98 + return result;
  99 + }
  100 +
  101 + /**
  102 + * 强退用户
  103 + */
  104 + @RequestMapping(value = "/forceLogout",method = RequestMethod.POST)
  105 + public Result<Object> forceLogout(@RequestBody SysOnlineVO online) {
  106 + //用户退出逻辑
  107 + if(oConvertUtils.isEmpty(online.getToken())) {
  108 + return Result.error("退出登录失败!");
  109 + }
  110 + String username = JwtUtil.getUsername(online.getToken());
  111 + LoginUser sysUser = sysBaseAPI.getUserByName(username);
  112 + if(sysUser!=null) {
  113 + baseCommonService.addLog("强制: "+sysUser.getRealname()+"退出成功!", CommonConstant.LOG_TYPE_1, null,sysUser);
  114 + log.info(" 强制 "+sysUser.getRealname()+"退出成功! ");
  115 + //清空用户登录Token缓存
  116 + redisUtil.del(CommonConstant.PREFIX_USER_TOKEN + online.getToken());
  117 + //清空用户登录Shiro权限缓存
  118 + redisUtil.del(CommonConstant.PREFIX_USER_SHIRO_CACHE + sysUser.getId());
  119 + //清空用户的缓存信息(包括部门信息),例如sys:cache:user::<username>
  120 + redisUtil.del(String.format("%s::%s", CacheConstant.SYS_USERS_CACHE, sysUser.getUsername()));
  121 + //调用shiro的logout
  122 + SecurityUtils.getSubject().logout();
  123 + return Result.ok("退出登录成功!");
  124 + }else {
  125 + return Result.error("Token无效!");
  126 + }
  127 + }
  128 +}
... ...
jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/modules/system/vo/SysOnlineVO.java 0 → 100644
  1 +package org.jeecg.modules.system.vo;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonFormat;
  4 +import lombok.Data;
  5 +import org.jeecg.common.aspect.annotation.Dict;
  6 +import org.springframework.format.annotation.DateTimeFormat;
  7 +
  8 +import java.util.Date;
  9 +
  10 +/**
  11 + *
  12 + * @Author: chenli
  13 + * @Date: 2020-06-07
  14 + * @Version: V1.0
  15 + */
  16 +@Data
  17 +public class SysOnlineVO {
  18 + /**
  19 + * 会话id
  20 + */
  21 + private String id;
  22 +
  23 + /**
  24 + * 会话编号
  25 + */
  26 + private String token;
  27 +
  28 + /**
  29 + * 用户名
  30 + */
  31 + private String username;
  32 +
  33 + /**
  34 + * 用户名
  35 + */
  36 + private String realname;
  37 +
  38 + /**
  39 + * 头像
  40 + */
  41 + private String avatar;
  42 +
  43 + /**
  44 + * 生日
  45 + */
  46 + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
  47 + @DateTimeFormat(pattern = "yyyy-MM-dd")
  48 + private Date birthday;
  49 +
  50 + /**
  51 + * 性别(1:男 2:女)
  52 + */
  53 + @Dict(dicCode = "sex")
  54 + private Integer sex;
  55 +
  56 + /**
  57 + * 手机号
  58 + */
  59 + private String phone;
  60 +}
... ...