SpringBoot整合SpringSecurity实现权限控制(五):用户管理

x33g5p2x  于2021-12-30 转载在 Spring  
字(18.7k)|赞(0)|评价(0)|浏览(315)

系列文章目录
《SpringBoot整合SpringSecurity实现权限控制(一):实现原理》
《SpringBoot整合SpringSecurity实现权限控制(二):权限数据基本模型设计》
《SpringBoot整合SpringSecurity实现权限控制(三):前端动态装载路由与菜单》
《SpringBoot整合SpringSecurity实现权限控制(四):角色管理》

一、前言

系统的使用者称为用户,仅可在被赋予的权限范围之内操作系统。
管理员是一种特殊的用户,拥有系统操作的最高权限。

  • 本文将实现用户的管理功能,重点实现用户的角色分配。
    – 用户的新增(自助注册)可参考以下文章:《手把手教你通过SpringBoot实现邮箱注册码验证》

  • 实现效果如下:

二、后端实现

2.1 创建用户实体类

  • 该实体类对应用户表,记录用户的基础信息。
/** * 用户表 * * @author zhuhuix * @date 2020-04-03 */
@ApiModel(value = "用户信息")
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@TableName("sys_user")
public class SysUser implements Serializable {

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    private String userName;

    @JsonIgnore
    private String password;

    private String nickName;

    /** * 性别 0-未知 1-male,2-female */
    private Integer gender;

    /** * 头像地址 */
    private String avatarUrl;

    private String country;

    private String province;

    private String city;

    @Email
    private String email;

    private String phone;

    private String remarks;

    @TableLogic
    private Boolean enabled;

    private Timestamp lastPasswordResetTime;

    private Timestamp createTime;

    @Builder.Default
    private Timestamp updateTime = Timestamp.valueOf(LocalDateTime.now());

}

2.2 创建用户角色实体类

  • 该实体类对应用户角色表,记录用户对应的角色信息(一个角户可对应多个角色)。

/** * 用户角色表 * * @author zhuhuix * @date 2021-09-29 */
@ApiModel(value = "用户角色信息")
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@TableName("sys_user_role")
public class SysUserRole {

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    private Long userId;

    private Long roleId;

    private Timestamp createTime;
}

2.3 添加用户与用户角色Mapper接口

  • 通过该Mapper接口可操作用户实体与用户角色实体
/** * 用户DAO接口 * * @author zhuhuix * @date 2021-07-19 */
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {

    /** * 查询用户角色 * @param userId 用户id * @return 角色信息 */
    @Select("select r.id,r.role_code,r.role_name,r.description,r.enabled,r.create_time,r.update_time " +
            "from sys_role r " +
            "INNER JOIN sys_user_role ur ON r.id=ur.role_id where ur.user_id=#{userId} ")
    List<SysRole> selectUserRoles(Long userId);

    /** * 查询用户权限 * @param userId 用户id * @return 权限信息 */
    @Select("SELECT m.id, m.`path`, m.`name`, m.`component`, m.`icon`, m.`cache`, m.`hidden`, m.`redirect`, m.p_id " +
            "FROM " +
            "sys_menu m " +
            "INNER JOIN sys_permission p ON p.menu_id = m.id " +
            "INNER JOIN sys_user_role ur ON ur.role_id = p.role_id " +
            "INNER JOIN sys_user u ON u.id = ur.user_id " +
            "INNER JOIN sys_role r ON r.id = ur.role_id where ur.user_id=#{userId}"+
            " and m.enabled=1 " +
            " order by m.sort "
    )
    List<PermissionDto> selectUserPermission(Long userId);
}
/** * 用户角色DAO接口 * * @author zhuhuix * @date 2021-09-29 */
@Mapper
public interface SysUserRoleMapper extends BaseMapper<SysUserRole> {
}

2.4 实现用户的增删改查服务

/** * 用户管理服务接口 * * @author zhuhuix * @date 2020-04-03 */
public interface SysUserService {

    /** * 增加用户 * * @param user 待新增的用户 * @return 增加成功的用户 */
    SysUser create(SysUser user);

    /** * 删除用户 * * @param user 待删除的用户 * @return 删除成功的用户 */
    SysUser delete(SysUser user);

    /** * 修改用户 * * @param user 待修改的用户 * @return 修改成功的用户 */
    SysUser update(SysUser user);

    /** * 根据id查找用户 * * @param id 用户id * @return 用户信息 */
    SysUser findById(Long id);

    /** * 根据userName查找用户 * * @param userName 用户帐号 * @return 用户帐号对应的用户 */
    SysUser findByUserName(String userName);

    /** * 判断注册使用的邮箱是否存在 * * @param email 邮箱号 * @return 是否找到 */
    boolean registerEmailExist(String email);

    /** * 获取用户信息 * * @return 用户信息 */
    UserDetails getUserInfo();

    /** * 修改用户头像 * * @param file 文件 * @return json */
    Map<String, String> updateAvatar(MultipartFile file);

    /** * 获取用户角色信息 * * @param userId 用户id * @return 角色信息 */
    List<SysRole> getUserRoles(Long userId);

    /** * 保存用户角色 * * @param userId 用户id * @param roleIds 角色id列表 * @return 是否成功 */
    Boolean saveUserRoles(Long userId,Set<Long> roleIds);

    /** * 获取用户权限信息 * * @param userId 用户id * @return 权限信息 */
    List<PermissionDto> getUserPermission(Long userId);

    /** * 根据条件查询用户信息 * * @param sysUserQueryDto 查询条件 * @return 用户列表 */
    List<SysUser> list(SysUserQueryDto sysUserQueryDto);

    /** * 批量删除用户 * * @param ids 待删除的用户id列表 * @return 是否成功 */
    Boolean delete(Set<Long> ids);

}
/** * 用户管理服务实现类 * * @author zhuhuix * @date 2020-04-03 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class SysUserServiceImpl implements SysUserService {

    private final SysUserMapper sysUserMapper;
    private final SysUserRoleMapper sysUserRoleMapper;
    private final UploadFileTool uploadFileTool;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysUser create(SysUser user) {
        if (sysUserMapper.insert(user) > 0) {
            return user;
        }
        throw new RuntimeException("增加用户信息失败");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysUser delete(SysUser user) {
        QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(SysUser::getUserName, user.getUserName());
        if (sysUserMapper.delete(queryWrapper) > 0) {
            return user;
        }
        throw new RuntimeException("删除用户信息失败");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysUser update(SysUser user) {
        if (sysUserMapper.updateById(user) > 0) {
            return user;
        }
        throw new RuntimeException("更新用户信息失败");
    }

    @Override
    public SysUser findById(Long id) {
        return sysUserMapper.selectById(id);
    }

    @Override
    public SysUser findByUserName(String userName) {
        return sysUserMapper.selectOne(new QueryWrapper<SysUser>().lambda().eq(SysUser::getUserName, userName));
    }

    @Override
    public boolean registerEmailExist(String email) {
        QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(SysUser::getEmail, email);
        return sysUserMapper.selectOne(queryWrapper) != null;
    }

    @Override
    public UserDetails getUserInfo() {
        UserDetailsService userDetailsService = SpringContextHolder.getBean(UserDetailsService.class);
        return userDetailsService.loadUserByUsername(getCurrentLoginUserName());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, String> updateAvatar(MultipartFile file) {
        SysUser sysUser = findByUserName(getCurrentLoginUserName());

        UploadFile uploadFile = uploadFileTool.upload(sysUser.getUserName(), file.getOriginalFilename(), file);
        sysUser.setAvatarUrl(uploadFile.getType() + File.separator + uploadFile.getFileName());
        update(sysUser);
        return new HashMap<String, String>(1) {{
            put("avatar", uploadFile.getFileName());
        }};

    }

    @Override
    public List<PermissionDto> getUserPermission(Long userId) {
        return sysUserMapper.selectUserPermission(userId);
    }

    @Override
    public List<SysRole> getUserRoles(Long userId) {
        return sysUserMapper.selectUserRoles(userId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveUserRoles(Long userId, Set<Long> roleIds) {
        // 首先清除该用户原有的角色信息
        QueryWrapper<SysUserRole> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(SysUserRole::getUserId, userId);
        sysUserRoleMapper.delete(queryWrapper);
        // 再进行添加
        for (Long roleId : roleIds) {
            SysUserRole sysUserRole = new SysUserRole();
            sysUserRole.setUserId(userId);
            sysUserRole.setRoleId(roleId);
            sysUserRole.setCreateTime(Timestamp.valueOf(LocalDateTime.now()));
            sysUserRoleMapper.insert(sysUserRole);
        }

        return true;
    }

    private String getCurrentLoginUserName() {
        final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            throw new RuntimeException("登录状态已过期");
        }
        if (authentication.getPrincipal() instanceof UserDetails) {
            UserDetails userDetails = (UserDetails) authentication.getPrincipal();
            return (userDetails.getUsername());
        }
        throw new RuntimeException("找不到当前登录的信息");
    }

    @Override
    public List<SysUser> list(SysUserQueryDto sysUserQueryDto) {
        QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
        if (!StringUtils.isEmpty(sysUserQueryDto.getUserName())) {
            queryWrapper.lambda().like(SysUser::getUserName, sysUserQueryDto.getUserName())
                    .or().like(SysUser::getNickName, sysUserQueryDto.getUserName());
        }
        if (!StringUtils.isEmpty(sysUserQueryDto.getCreateTimeStart())
                && !StringUtils.isEmpty(sysUserQueryDto.getCreateTimeEnd())) {
            queryWrapper.and(wrapper -> wrapper.lambda().between(SysUser::getCreateTime,
                    new Timestamp(sysUserQueryDto.getCreateTimeStart()),
                    new Timestamp(sysUserQueryDto.getCreateTimeEnd())));
        }
        return sysUserMapper.selectList(queryWrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean delete(Set<Long> ids) {
        if (sysUserMapper.deleteBatchIds(ids) > 0) {
            return true;
        }
        throw new RuntimeException("删除用户信息失败");
    }
}

2.5 编写Controller层

  • 共实现以下9个后台接口

/** * api用户信息 * * @author zhuhuix * @date 2021-08-16 */
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/api/user")
@Api(tags = "用户信息接口")
public class SysUserController {

    private final SysUserService sysUserService;

    @ApiOperation("获取当前登录用户信息")
    @GetMapping()
    public ResponseEntity<Object> getUserInfo() {
        return ResponseEntity.ok(sysUserService.getUserInfo());
    }

    @ApiOperation("根据id获取用户信息")
    @GetMapping("/{id}")
    public ResponseEntity<Object> getUserInfo(@PathVariable Long id) {
        return ResponseEntity.ok(sysUserService.findById(id));
    }

    @ApiOperation("更新用户信息")
    @PostMapping()
    public ResponseEntity<Object> saveUser(@RequestBody SysUser user) {
        return ResponseEntity.ok(sysUserService.update(user));
    }

    @PreAuthorize("hasAuthority('user:updateAvatar')")
    @ApiOperation("修改用户头像")
    @PostMapping(value = "/updateAvatar")
    public ResponseEntity<Object> updateAvatar(@RequestParam MultipartFile avatar) {
        return ResponseEntity.ok(sysUserService.updateAvatar(avatar));
    }

    @ApiOperation("根据条件查询用户列表")
    @PostMapping("/list")
    public ResponseEntity<Object> getSysUserList(@RequestBody SysUserQueryDto sysUserQueryDto) {
        return ResponseEntity.ok(sysUserService.list(sysUserQueryDto));
    }

    @ApiOperation("批量删除用户")
    @DeleteMapping
    public ResponseEntity<Object> deleteUsers(@RequestBody Set<Long> ids) {
        return ResponseEntity.ok(sysUserService.delete(ids));
    }

    @ApiOperation("获取用户角色")
    @GetMapping("/roles/{userId}")
    public ResponseEntity<Object> getUserRoles(@PathVariable Long userId) {
        return ResponseEntity.ok(sysUserService.getUserRoles(userId));
    }

    @ApiOperation("保存用户角色")
    @PostMapping("/roles/{userId}")
    public ResponseEntity<Object> saveUserRoles(@PathVariable Long userId, @RequestBody Set<Long> ids) {
        return ResponseEntity.ok(sysUserService.saveUserRoles(userId, ids));
    }

    @ApiOperation("获取用户权限")
    @GetMapping("/permission/{userId}")
    public ResponseEntity<Object> getUserPermission(@PathVariable Long userId) {
        return ResponseEntity.ok(sysUserService.getUserPermission(userId));
    }

}

三、前端实现

3.1 添加用户api访问接口

import request from '@/utils/request'

// 登录
export function login(data) {
  return request({
    url: '/api/auth/login',
    method: 'post',
    data
  })
}

// 注销
export function logout() {
  return request({
    url: '/api/auth/logout',
    method: 'delete'
  })
}

// 获取当前登录用户信息
export function getInfo() {
  return request({
    url: '/api/user',
    method: 'get'
  })
}

// 根据用户id获取用户信息
export function getInfoById(id) {
  return request({
    url: '/api/user/' + id,
    method: 'get'
  })
}

// 保存并更新用户
export function saveUser(data) {
  return request({
    url: '/api/user',
    method: 'post',
    data
  })
}

// 根据用户id列表批量删除用户
export function deleteUser(ids) {
  return request({
    url: '/api/user',
    method: 'delete',
    data: ids
  })
}

// 根据条件查询获取用户列表
export function getUserList(params) {
  return request({
    url: '/api/user/list',
    method: 'post',
    data: JSON.stringify(params)
  })
}

// 根据用户id获取用户权限
export function getUserPermission(userId) {
  return request({
    url: '/api/user/permission/' + userId,
    method: 'get'
  })
}

// 获取用户角色
export function getUserRoles(userId) {
  return request({
    url: '/api/user/roles/' + userId,
    method: 'get'
  })
}

// 分配用户角色
export function saveUserRoles(userId, roleIds) {
  return request({
    url: '/api/user/roles/' + userId,
    method: 'post',
    data: roleIds
  })
}

3.2 编写前端页面

  • 我们需要编写一个完整的页面:
  1. 通过登录账号或用户名,注册起始与结束时间查询用户,并以列表的形式进行显示。

  1. 通过点击“分配角色”按钮,跳出表单,选取角色信息给用户进行角色分配。

  1. 在列表上选取用户(可多选),点击“删除”按钮,删除选取的用户,删除前要有提示。

  • 前端关键源码如下:
    – src/user/index.vue
<template>
  <div class="app-container">
    <!--工具栏-->
    <div class="head-container">
      <!-- 搜索 -->
      <el-input
        v-model="userName"
        size="small"
        clearable
        placeholder="输入账号或用户名称搜索"
        style="width: 200px"
        class="filter-item"
        @keyup.enter.native="doQuery"
      />
      <el-date-picker
        v-model="createTime"
        :default-time="['00:00:00', '23:59:59']"
        type="daterange"
        range-separator=":"
        size="small"
        class="date-item"
        value-format="yyyy-MM-dd HH:mm:ss"
        start-placeholder="开始日期"
        end-placeholder="结束日期"
      />
      <el-button
        class="filter-item"
        size="mini"
        type="success"
        icon="el-icon-search"
        @click="doQuery"
      >搜索</el-button>
      <el-button
        class="filter-item"
        size="mini"
        type="danger"
        icon="el-icon-circle-plus-outline"
        :disabled="selections.length===0"
        @click="doDelete"
      >删除</el-button>
    </div>

    <el-row>
      <!--角色分配表单-->
      <el-dialog append-to-body :close-on-click-modal="false" :visible.sync="showDialog" title="角色分配" width="600px">
        <el-form ref="form" :inline="true" :model="form" size="small" label-width="76px">
          <el-form-item label="登录账号" prop="userName">
            <el-input v-model="form.userName" :disabled="true" />
          </el-form-item>
          <el-form-item label="昵称" prop="nickName">
            <el-input v-model="form.nickName" :disabled="true" />
          </el-form-item>
          <el-form-item style="margin-bottom: 0;" label="角色" prop="userRoles">
            <el-select
              v-model="userRoles"
              style="width: 455px"
              multiple
              filterable
              placeholder="请选择"
              @remove-tag="deleteTag"
              @change="changeRole"
            >
              <el-option
                v-for="item in roles"
                :key="item.roleCode"
                :label="item.roleName"
                :value="item.id"
              />
            </el-select>
          </el-form-item>
        </el-form>
        <div slot="footer" class="dialog-footer">
          <el-button type="text" @click="doCancel">取消</el-button>
          <el-button :loading="formLoading" type="primary" @click="doSubmit">确认</el-button>
        </div>
      </el-dialog>
      <el-tabs v-model="activeName" type="border-card">
        <el-tab-pane label="用户列表" name="userList">
          <el-table ref="table" v-loading="loading" :data="users" style="width: 100%; font-size: 12px;" @selection-change="selectionChangeHandler">
            <el-table-column type="selection" width="55" />
            <el-table-column :show-overflow-tooltip="true" width="150" prop="userName" label="登录账号" />
            <el-table-column :show-overflow-tooltip="true" width="150" prop="nickName" label="用户昵称" />
            <el-table-column prop="gender" width="60" label="性别">
              <template slot-scope="scope">
                <el-tag v-if="scope.row.gender===1" type="success">男</el-tag>
                <el-tag v-if="scope.row.gender===2" type="warning">女</el-tag>
                <el-tag v-if="scope.row.gender===0" type="info">未知</el-tag>
              </template>
            </el-table-column>
            <el-table-column :show-overflow-tooltip="true" prop="phone" width="150" label="电话" />
            <el-table-column :show-overflow-tooltip="true" prop="city" label="所在地区">
              <template slot-scope="scope">
                <span>{{ scope.row.province }} {{ scope.row.city }} {{ scope.row.country }}</span>
              </template>
            </el-table-column>
            <el-table-column :show-overflow-tooltip="true" prop="avatarUrl" width="80" label="头像">
              <template slot-scope="scope">
                <img
                  :src="
                    scope.row.avatarUrl
                      ? baseApi + '/file/' + scope.row.avatarUrl
                      : Avatar
                  "
                  class="avatar"
                >
              </template>
            </el-table-column>
            <el-table-column :show-overflow-tooltip="true" prop="createTime" width="155" label="注册日期">
              <template slot-scope="scope">
                <span>{{ parseTime(scope.row.createTime) }}</span>
              </template>
            </el-table-column>
            <el-table-column
              label="操作"
              width="160"
              align="center"
              fixed="right"
            >
              <template slot-scope="scope">
                <el-button size="mini" type="text" round @click="doAssignRole(scope.row.id)">分配角色</el-button>

              </template>
            </el-table-column>
          </el-table>
        </el-tab-pane>
      </el-tabs>

    </el-row>

  </div>
</template>

<script>
import { mapGetters } from 'vuex'
import Avatar from '@/assets/images/avatar.png'
import { parseTime } from '@/utils/index'
import { getUserList, deleteUser, getInfoById, getUserRoles, saveUserRoles } from '@/api/user'
import { getRoleList } from '@/api/role'
export default {
  name: 'User',
  data() {
    return {
      Avatar: Avatar,
      activeName: 'userList',
      showDialog: false,
      loading: false,
      formLoading: true,
      form: {},
      users: [],
      selections: [],
      userName: '',
      createTime: null,
      roles: [],
      userRoles: []
    }
  },
  computed: {
    ...mapGetters([
      'baseApi'
    ])
  },
  created() {

  },
  methods: {
    parseTime,
    doQuery() {
      this.users = []
      var param = { userName: this.userName }
      if (this.createTime != null) {
        param.createTimeStart = Date.parse(this.createTime[0])
        param.createTimeEnd = Date.parse(this.createTime[1])
      }
      getUserList(param).then(res => {
        if (res) {
          this.users = res
        }
      })
    },
    doDelete() {
      const ids = []
      this.selections.forEach((res) => {
        ids.push(res.id)
      })
      this.$confirm(`确认删除这些用户吗?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() =>
        deleteUser(ids).then(res => {
          if (res) {
            this.$notify({
              title: '删除成功',
              type: 'success',
              duration: 2500
            })
            this.doQuery()
          }
        })
      ).catch(() => {
      })
    },
    // 选择改变
    selectionChangeHandler(val) {
      this.selections = val
    },
    doAssignRole(id) {
      this.form = {}
      this.userRoles = []
      this.roles = []
      this.showDialog = true
      this.formLoading = true
      getInfoById(id).then((res) => {
        this.form = { id: res.id, userName: res.userName, nickName: res.nickName, gender: res.gender, phone: res.phone }
        var param = { }
        getRoleList(param).then(res => {
          if (res) {
            this.roles = res
          }
          getUserRoles(id).then((res) => {
            if (res) {
              res.forEach(role => {
                this.userRoles.push(role.id)
              })
            }
            this.formLoading = false
          })
        })
      })
    },
    doCancel() {
      this.showDialog = false
      this.form = {}
    },
    doSubmit() {
      this.formLoading = true
      saveUserRoles(this.form.id, this.userRoles).then(() => {
        this.showDialog = false
        this.$notify({
          title: '保存成功',
          type: 'success',
          duration: 2500
        })
      })
    },
    deleteTag(value) {
      this.userRoles.forEach(function(data, index) {
        if (data.id === value) {
          this.userRoles.splice(index, value)
        }
      })
    },
    changeRole(value) {
      // console.log(this.userRoles)
    }
  }
}

</script>

<style rel="stylesheet/scss" lang="scss">
.avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
}
</style>

<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
  text-align: left;
}
</style>

四、效果演示

五、源码

相关文章