增加用户管理功能。管理员可以添加删除用户、修改用户密码、重置pushkey
7个文件已修改
3个文件已添加
619 ■■■■■ 已修改文件
src/main/java/com/genersoft/iot/vmp/service/IUserService.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/UserMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/Login.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/UserManager.vue 236 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/addUser.vue 159 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/changePasswordForAdmin.vue 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/layout/UiHeader.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/router/index.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IUserService.java
@@ -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 @@
    int updateUsers(User user);
    boolean checkPushAuthority(String callId, String sign);
    PageInfo<User> getUsers(int page, int count);
    int resetPushKey(int id);
}
src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java
@@ -3,6 +3,8 @@
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 @@
@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private UserMapper userMapper;
@@ -64,4 +66,16 @@
            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);
    }
}
src/main/java/com/genersoft/iot/vmp/storager/dao/UserMapper.java
@@ -55,4 +55,11 @@
    @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);
}
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java
@@ -8,6 +8,7 @@
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 @@
        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";
    }
}
web_src/src/components/Login.vue
@@ -86,7 +86,7 @@
      }).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('/');
web_src/src/components/UserManager.vue
New 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>
web_src/src/components/dialog/addUser.vue
New 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>
web_src/src/components/dialog/changePasswordForAdmin.vue
New 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>
web_src/src/layout/UiHeader.vue
@@ -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 @@
      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"
    }
web_src/src/router/index.js
@@ -17,6 +17,7 @@
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 @@
          name: 'map',
          component: map,
        },
        {
          path: '/userManager',
          name: 'userManager',
          component: userManager,
        }
        ]
    },
    {