增加用户管理功能。管理员可以添加删除用户、修改用户密码、重置pushkey

pull/549/head
jiang 2022-07-18 17:09:35 +08:00
parent fc89b7b517
commit c6fbd03276
10 changed files with 617 additions and 2 deletions

View File

@ -1,6 +1,7 @@
package com.genersoft.iot.vmp.service;
import com.genersoft.iot.vmp.storager.dao.dto.User;
import com.github.pagehelper.PageInfo;
import java.util.List;
@ -21,4 +22,8 @@ public interface IUserService {
int updateUsers(User user);
boolean checkPushAuthority(String callId, String sign);
PageInfo<User> getUsers(int page, int count);
int resetPushKey(int id);
}

View File

@ -3,6 +3,8 @@ package com.genersoft.iot.vmp.service.impl;
import com.genersoft.iot.vmp.service.IUserService;
import com.genersoft.iot.vmp.storager.dao.UserMapper;
import com.genersoft.iot.vmp.storager.dao.dto.User;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@ -11,7 +13,7 @@ import java.util.List;
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper userMapper;
@ -64,4 +66,16 @@ public class UserServiceImpl implements IUserService {
return userMapper.checkPushAuthorityByCallIdAndSign(callId, sign).size() > 0;
}
}
@Override
public PageInfo<User> getUsers(int page, int count) {
PageHelper.startPage(page, count);
List<User> users = userMapper.getUsers();
return new PageInfo<>(users);
}
@Override
public int resetPushKey(int id) {
return userMapper.resetPushKey(id);
}
}

View File

@ -55,4 +55,11 @@ public interface UserMapper {
@Select("select * from user where md5(pushKey) = '${sign}'")
List<User> checkPushAuthorityByCallId(String sign);
@Select("select u.idu.username,u.pushKey,u.roleId, r.id as roleID, r.name as roleName, r.authority as roleAuthority , r.createTime as roleCreateTime , r.updateTime as roleUpdateTime FROM user u join user_role r on u.roleId=r.id")
@ResultMap(value="roleMap")
List<User> getUsers();
@Delete("update user set pushKey=MD5(NOW()+#{id}) where id=#{id}")
int resetPushKey(int id);
}

View File

@ -8,6 +8,7 @@ import com.genersoft.iot.vmp.storager.dao.dto.Role;
import com.genersoft.iot.vmp.storager.dao.dto.User;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.github.pagehelper.PageInfo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
@ -177,4 +178,67 @@ public class UserController {
result.setData(allUsers);
return new ResponseEntity<>(result, HttpStatus.OK);
}
/**
*
*
* @param page
* @param count
* @return
*/
@ApiOperation("分页查询用户")
@ApiImplicitParams({
@ApiImplicitParam(name = "page", value = "当前页", required = true, dataTypeClass = Integer.class),
@ApiImplicitParam(name = "count", value = "每页查询数量", required = true, dataTypeClass = Integer.class),
})
@GetMapping("/users")
public PageInfo<User> users(int page, int count) {
return userService.getUsers(page, count);
}
@ApiOperation("重置pushkey")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", required = true, value = "用户Id", dataTypeClass = Integer.class),
})
@RequestMapping("/resetPushKey")
public ResponseEntity<WVPResult<String>> resetPushKey(@RequestParam Integer id) {
// 获取当前登录用户id
int currenRoleId = SecurityUtils.getUserInfo().getRole().getId();
WVPResult<String> result = new WVPResult<>();
if (currenRoleId != 1) {
// 只用角色id为0才可以删除和添加用户
result.setCode(-1);
result.setMsg("用户无权限");
return new ResponseEntity<>(result, HttpStatus.FORBIDDEN);
}
int resetPushKeyResult = userService.resetPushKey(id);
result.setCode(resetPushKeyResult > 0 ? 0 : -1);
result.setMsg(resetPushKeyResult > 0 ? "success" : "fail");
return new ResponseEntity<>(result, HttpStatus.OK);
}
@ApiOperation("管理员修改普通用户密码")
@ApiImplicitParams({
@ApiImplicitParam(name = "adminId", required = true, value = "管理员id", dataTypeClass = String.class),
@ApiImplicitParam(name = "userId", required = true, value = "用户id", dataTypeClass = String.class),
@ApiImplicitParam(name = "password", required = true, value = "新密码未md5加密的密码", dataTypeClass = String.class),
})
@PostMapping("/changePasswordForAdmin")
public String changePasswordForAdmin(@RequestParam int userId, @RequestParam String password) {
// 获取当前登录用户id
LoginUser userInfo = SecurityUtils.getUserInfo();
if (userInfo == null) {
return "fail";
}
Role role = userInfo.getRole();
if (role != null && role.getId() == 1) {
boolean result = userService.changePassword(userId, DigestUtils.md5DigestAsHex(password.getBytes()));
if (result) {
return "success";
}
}
return "fail";
}
}

View File

@ -86,7 +86,7 @@ export default {
}).then(function (res) {
console.log(JSON.stringify(res));
if (res.data.code == 0 && res.data.msg == "success") {
that.$cookies.set("session", {"username": that.username}) ;
that.$cookies.set("session", {"username": that.username,"roleId":res.data.data.role.id}) ;
//
that.cancelEnterkeyDefaultAction();
that.$router.push('/');

View File

@ -0,0 +1,236 @@
<template>
<div id="app" style="width: 100%">
<div class="page-header">
<div class="page-title">用户列表</div>
<div class="page-header-btn">
<el-button icon="el-icon-plus" size="mini" style="margin-right: 1rem;" type="primary" @click="addUser">
添加用户
</el-button>
</div>
</div>
<!--用户列表-->
<el-table :data="userList" style="width: 100%;font-size: 12px;" :height="winHeight"
header-row-class-name="table-header">
<el-table-column prop="username" label="用户名" min-width="160"/>
<el-table-column prop="pushKey" label="pushkey" min-width="160"/>
<el-table-column prop="role.name" label="类型" min-width="160"/>
<el-table-column label="操作" min-width="450" fixed="right">
<template slot-scope="scope">
<el-button size="medium" icon="el-icon-edit" type="text" @click="edit(scope.row)"></el-button>
<el-divider direction="vertical"></el-divider>
<el-button size="medium" icon="el-icon-refresh" type="text" @click="resetPushKey(scope.row)">pushkey</el-button>
<el-divider direction="vertical"></el-divider>
<el-button size="medium" icon="el-icon-delete" type="text" @click="deleteUser(scope.row)"
style="color: #f56c6c">删除
</el-button>
</template>
</el-table-column>
</el-table>
<changePasswordForAdmin ref="changePasswordForAdmin"></changePasswordForAdmin>
<addUser ref="addUser"></addUser>
<el-pagination
style="float: right"
@size-change="handleSizeChange"
@current-change="currentChange"
:current-page="currentPage"
:page-size="count"
:page-sizes="[15, 25, 35, 50]"
layout="total, sizes, prev, pager, next"
:total="total">
</el-pagination>
</div>
</template>
<script>
import uiHeader from '../layout/UiHeader.vue'
import changePasswordForAdmin from './dialog/changePasswordForAdmin.vue'
import addUser from '../components/dialog/addUser.vue'
export default {
name: 'userManager',
components: {
uiHeader,
changePasswordForAdmin,
addUser
},
data() {
return {
userList: [], //
currentUser: {}, //
videoComponentList: [],
updateLooper: 0, //
currentUserLenth: 0,
winHeight: window.innerHeight - 200,
currentPage: 1,
count: 15,
total: 0,
getUserListLoading: false
};
},
mounted() {
this.initData();
this.updateLooper = setInterval(this.initData, 10000);
},
destroyed() {
this.$destroy('videojs');
clearTimeout(this.updateLooper);
},
methods: {
initData: function () {
this.getUserList();
},
currentChange: function (val) {
this.currentPage = val;
this.getUserList();
},
handleSizeChange: function (val) {
this.count = val;
this.getUserList();
},
getUserList: function () {
let that = this;
this.getUserListLoading = true;
this.$axios({
method: 'get',
url: `/api/user/users`,
params: {
page: that.currentPage,
count: that.count
}
}).then(function (res) {
that.total = res.data.total;
that.userList = res.data.list;
that.getUserListLoading = false;
}).catch(function (error) {
that.getUserListLoading = false;
});
},
edit: function (row) {
this.$refs.changePasswordForAdmin.openDialog(row, () => {
this.$refs.changePasswordForAdmin.close();
this.$message({
showClose: true,
message: "密码修改成功",
type: "success",
});
setTimeout(this.getDeviceList, 200)
})
},
deleteUser: function (row) {
let msg = "确定删除此用户?"
if (row.online !== 0) {
msg = "<strong>确定删除此用户?</strong>"
}
this.$confirm(msg, '提示', {
dangerouslyUseHTMLString: true,
confirmButtonText: '确定',
cancelButtonText: '取消',
center: true,
type: 'warning'
}).then(() => {
this.$axios({
method: 'delete',
url: `/api/user/delete?id=${row.id}`
}).then((res) => {
this.getUserList();
}).catch((error) => {
console.error(error);
});
}).catch(() => {
});
},
resetPushKey: function (row) {
let msg = "确定重置pushkey"
if (row.online !== 0) {
msg = "<strong>确定重置pushkey</strong>"
}
this.$confirm(msg, '提示', {
dangerouslyUseHTMLString: true,
confirmButtonText: '确定',
cancelButtonText: '取消',
center: true,
type: 'warning'
}).then(() => {
this.$axios({
method: 'get',
url: `/api/user/resetPushKey?id=${row.id}`
}).then((res) => {
this.getUserList();
}).catch((error) => {
console.error(error);
});
}).catch(() => {
});
},
addUser: function () {
this.$refs.addUser.openDialog()
}
}
}
</script>
<style>
.videoList {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
}
.video-item {
position: relative;
width: 15rem;
height: 10rem;
margin-right: 1rem;
background-color: #000000;
}
.video-item-img {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
width: 100%;
height: 100%;
}
.video-item-img:after {
content: "";
display: inline-block;
position: absolute;
z-index: 2;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
width: 3rem;
height: 3rem;
background-image: url("../assets/loading.png");
background-size: cover;
background-color: #000000;
}
.video-item-title {
position: absolute;
bottom: 0;
color: #000000;
background-color: #ffffff;
line-height: 1.5rem;
padding: 0.3rem;
width: 14.4rem;
}
</style>

View File

@ -0,0 +1,159 @@
<template>
<div id="addUser" v-loading="isLoging">
<el-dialog
title="添加用户"
width="40%"
top="2rem"
:close-on-click-modal="false"
:visible.sync="showDialog"
:destroy-on-close="true"
@close="close()"
>
<div id="shared" style="margin-right: 20px;">
<el-form ref="passwordForm" :rules="rules" status-icon label-width="80px">
<el-form-item label="用户名" prop="username">
<el-input v-model="username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="用户类型" prop="roleId">
<el-select v-model="roleId" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="password" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="confirmPassword" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<div style="float: right;">
<el-button type="primary" @click="onSubmit"></el-button>
<el-button @click="close"></el-button>
</div>
</el-form-item>
</el-form>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
name: "addUser",
props: {},
computed: {},
created() {
this.getAllRole();
},
data() {
let validatePass1 = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入新密码'));
} else {
if (this.confirmPassword !== '') {
this.$refs.passwordForm.validateField('confirmPassword');
}
callback();
}
};
let validatePass2 = (rule, value, callback) => {
if (this.confirmPassword === '') {
callback(new Error('请再次输入密码'));
} else if (this.confirmPassword !== this.password) {
callback(new Error('两次输入密码不一致!'));
} else {
callback();
}
};
return {
value:"",
options: [],
loading: false,
username: null,
password: null,
roleId: null,
confirmPassword: null,
listChangeCallback: null,
showDialog: false,
isLoging: false,
rules: {
newPassword: [{required: true, validator: validatePass1, trigger: "blur"}, {
pattern: /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[~!@#$%^&*()_+`\-={}:";'<>?,.\/]).{8,20}$/,
message: "密码长度在8-20位之间,由字母+数字+特殊字符组成",
},],
confirmPassword: [{required: true, validator: validatePass2, trigger: "blur"}],
},
};
},
methods: {
openDialog: function (callback) {
this.listChangeCallback = callback;
this.showDialog = true;
},
onSubmit: function () {
this.$axios({
method: 'post',
url: "/api/user/add",
params: {
username: this.username,
password: this.password,
roleId: this.roleId
}
}).then((res) => {
if (res.data.code === 0) {
this.$message({
showClose: true,
message: '添加成功',
type: 'success',
});
this.showDialog = false;
this.listChangeCallback()
} else {
this.$message({
showClose: true,
message: res.data.msg,
type: 'error'
});
}
}).catch((error) => {
console.error(error)
});
},
close: function () {
this.showDialog = false;
this.password = null;
this.confirmPassword = null;
this.username = null;
this.roleId = null;
},
getAllRole:function () {
this.$axios({
method: 'get',
url: "/api/role/all"
}).then((res) => {
this.loading = true;
console.info(res)
res.data
console.info(res.data.code)
if (res.data.code === 0) {
console.info(res.data.data)
this.options=res.data.data
}
}).catch((error) => {
console.error(error)
});
}
},
};
</script>

View File

@ -0,0 +1,121 @@
<template>
<div id="changePassword" v-loading="isLoging">
<el-dialog
title="修改密码"
width="40%"
top="2rem"
:close-on-click-modal="false"
:visible.sync="showDialog"
:destroy-on-close="true"
@close="close()"
>
<div id="shared" style="margin-right: 20px;">
<el-form ref="passwordForm" :rules="rules" status-icon label-width="80px">
<el-form-item label="新密码" prop="newPassword" >
<el-input v-model="newPassword" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="confirmPassword" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<div style="float: right;">
<el-button type="primary" @click="onSubmit"></el-button>
<el-button @click="close"></el-button>
</div>
</el-form-item>
</el-form>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
name: "changePasswordForAdmin",
props: {},
computed: {},
created() {},
data() {
let validatePass1 = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入新密码'));
} else {
if (this.confirmPassword !== '') {
this.$refs.passwordForm.validateField('confirmPassword');
}
callback();
}
};
let validatePass2 = (rule, value, callback) => {
if (this.confirmPassword === '') {
callback(new Error('请再次输入密码'));
} else if (this.confirmPassword !== this.newPassword) {
callback(new Error('两次输入密码不一致!'));
} else {
callback();
}
};
return {
newPassword: null,
confirmPassword: null,
userId: null,
showDialog: false,
isLoging: false,
listChangeCallback: null,
form: {},
rules: {
newPassword: [{ required: true, validator: validatePass1, trigger: "blur" }, {
pattern: /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[~!@#$%^&*()_+`\-={}:";'<>?,.\/]).{8,20}$/,
message: "密码长度在8-20位之间,由字母+数字+特殊字符组成",
},],
confirmPassword: [{ required: true, validator: validatePass2, trigger: "blur" }],
},
};
},
methods: {
openDialog: function (row, callback) {
console.log(row)
this.showDialog = true;
this.listChangeCallback = callback;
if (row != null) {
this.form = row;
}
},
onSubmit: function () {
this.$axios({
method: 'post',
url:"/api/user/changePasswordForAdmin",
params: {
password: this.newPassword,
userId: this.form.id,
}
}).then((res)=> {
if (res.data === "success"){
this.$message({
showClose: true,
message: '修改成功',
type: 'success'
});
this.showDialog = false;
}else {
this.$message({
showClose: true,
message: '修改密码失败,是否已登录(接口鉴权关闭无法修改密码)',
type: 'error'
});
}
}).catch((error)=> {
console.error(error)
});
},
close: function () {
this.showDialog = false;
this.newPassword = null;
this.confirmPassword = null;
this.userId=null;
this.adminId=null;
},
},
};
</script>

View File

@ -13,6 +13,7 @@
<el-menu-item index="/cloudRecord">云端录像</el-menu-item>
<el-menu-item index="/mediaServerManger">节点管理</el-menu-item>
<el-menu-item index="/parentPlatformList/15/1">国标级联</el-menu-item>
<el-menu-item v-if="editUser" index="/userManager"></el-menu-item>
<!-- <el-submenu index="/setting">-->
<!-- <template slot="title">系统设置</template>-->
@ -47,9 +48,11 @@ export default {
alarmNotify: false,
sseSource: null,
activeIndex: this.$route.path,
editUser: this.$cookies.get("session").roleId==1
};
},
created() {
console.log(this.$cookies.get("session"))
if (this.$route.path.startsWith("/channelList")) {
this.activeIndex = "/deviceList"
}

View File

@ -17,6 +17,7 @@ import sip from '../components/setting/Sip.vue'
import media from '../components/setting/Media.vue'
import live from '../components/live.vue'
import deviceTree from '../components/common/DeviceTree.vue'
import userManager from '../components/UserManager.vue'
import wasmPlayer from '../components/common/jessibuca.vue'
import rtcPlayer from '../components/dialog/rtcPlayer.vue'
@ -103,6 +104,11 @@ export default new VueRouter({
name: 'map',
component: map,
},
{
path: '/userManager',
name: 'userManager',
component: userManager,
}
]
},
{