一、表结构的设计

上篇文章《尚品甄选》:后台系统——权限管理之角色管理,已经介绍了什么是权限管理以及角色表结构的设计。今天来设计用户表用户角色表

一个用户可以担任多个角色,反之亦然,因此用户表与角色表是多对多的关系;一个角色操作多个菜单,一个菜单可以被多个角色操作,因此角色表与菜单表也是多对多的关系。为了建立用户表与角色表的联系,需要建立角色用户关系表,用来存储uid与roleid;同样,也需要建立角色菜单关系表,通过roleid与mid来建立角色表与菜单表的联系

在这里插入图片描述

# 用户表
CREATE TABLE `sys_user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '会员id',
  `username` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名',
  `password` varchar(32) NOT NULL DEFAULT '' COMMENT '密码',
  `name` varchar(50) DEFAULT NULL COMMENT '姓名',
  `phone` varchar(11) DEFAULT NULL COMMENT '手机',
  `avatar` varchar(255) DEFAULT NULL COMMENT '头像',
  `description` varchar(255) DEFAULT NULL COMMENT '描述',
  `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(1:正常 0:停用)',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '删除标记(0:不可用 1:可用)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表'
# 用户角色表
CREATE TABLE `sys_user_role` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `role_id` bigint NOT NULL DEFAULT '0' COMMENT '角色id',
  `user_id` bigint NOT NULL DEFAULT '0' COMMENT '用户id',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '删除标记(0:可用 1:不可用)',
  PRIMARY KEY (`id`),
  KEY `idx_role_id` (`role_id`),
  KEY `idx_admin_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb3 COMMENT='用户角色'

二、MinIO的使用

2.1 MinIO介绍

  • 目前可用于文件存储的网络服务选择也有不少,比如阿里云OSS、七牛云、腾讯云等等,可是收费都有点小贵。为了节约成本,我们可以使用MinIO做为文件服务器。
  • MinIO是一个开源的分布式对象存储服务器,支持S3协议并且可以在多节点上实现数据的高可用和容错。它采用Go语言开发,拥有轻量级、高性能、易部署等特点,并且可以自由选择底层存储介质。它基于Apache License 开源协议,兼容Amazon S3云存储接口。适合存储非结构化数据,如图片,音频,视频,日志等。
  • MinIO的主要特点包括:
    1、高性能: MinIO基于GO语言编写,具有高速、轻量级、高并发等性能特点,还支持多线程和缓存等机制进行优化,可以快速地处理大规模数据。
    2、安装部署简单: MinIO只需要执行几行命令便可轻松搞定,大大降低了学习和维护成本。
    3、操作简单,自带管理界面: MinIO在服务端安装后就自带了UI界面,可谓开箱即用。
    4、性能优秀,读写速度快: MinIO号称是世界上速度最快的对象存储服务器,读对象的速度能达到183 GB/s,写对象的速度能达到171 GB/s。
    5、支持云原生容器化部署: MinIO提供了与K8s、ETCD、Docker等容器化技术深度集成方案,可以说MinIO就是为云环境而生的。
    6、提供多语言SDK的支持: MinIO提供了绝大部分主流开发语言的SDK以及文档,比如Java、Python、Golang、JS、.NET等等。
    7、兼容亚马逊S3 API: 亚马逊云的 S3 API(接口协议) 是在全球范围内达到共识的对象存储的协议,是全世界认可的对象存储标准。而MinIO是采用S3兼容协议的产品之一。
  • S3协议是Amazon Web Services (AWS) 提供的对象存储服务(Simple Storage Service)的API协议。它是一种RESTful风格的Web服务接口,使用HTTP/HTTPS协议进行通信,支持多种编程语言和操作系统,并实现了数据的可靠存储、高扩展性以及良好的可用性。

2.2 Docker安装MinIO

接下来我们用docker来安装MinIO:首先拉取MinIO镜像

docker pull minio/minio

接着创建目录:一个用来存放配置,一个用来存储上传文件的目录

mkdir -p /home/minio/config
mkdir -p /home/minio/data

最后创建Minio容器,就可以使用喽

docker run -p 9000:9000 -p 9090:9090 \
     --net=host \
     --name minio \
     -d --restart=always \
     -e "MINIO_ACCESS_KEY=minioadmin" \
     -e "MINIO_SECRET_KEY=minioadmin" \
     -v /home/minio/data:/data \
     -v /home/minio/config:/root/.minio \
     minio/minio server \
     /data --console-address ":9090" -address ":9000"

访问:http://192.168.88.66:9090/login(这里是自己的主机ip) 用户名:minioadmin 密码:minioadmin,就可以进入管理界面。注意:设置minio中的桶的访问权限为public,如下所示:

首先点击创建一个桶,名字随意
在这里插入图片描述
我这里桶名字设置为spzx-bucket
在这里插入图片描述
点击修改为public
在这里插入图片描述

三、使用Minio实现用户头像上传

在进行用户添加时,选择要上传的用户图像,此时会请求后端上传文件接口,将图片的二进制数据传递到后端,后端需要将数据图片存储起来,然后给前端返回图片的访问地址,接着前端需要将图片的访问地址设置给对应的数据模型,当用户点击提交按钮的时候,此时就会将表单进行提交,后端将数据保存起来即可。对应的流程图如下所示:
在这里插入图片描述
首先来看看核心的业务层代码,整体思路是:把minio配置信息放在配置文件中,代码中用MinioProperties类来接收配置信息。首先创建一个Minio的客户端对象;接着判断桶是否存在,不存在则创建一个新的桶;然后设置存储的图片名称,格式为:年月日/UUID+上传的图片名称;最后是上传图片,返回图片的地址给前端。

public class FileUploadServiceImpl implements FileUploadService {
    @Autowired
    private MinioProperties minioProperties;
    @Override
    public String fileUpload(MultipartFile multipartFile) {
        try {
            //创建一个Minio的客户端对象
            MinioClient minioClient = MinioClient.builder()
                    .endpoint(minioProperties.getEndpointUrl())
                    .credentials(minioProperties.getAccessKey(), minioProperties.getSecreKey())
                    .build();
            //判断桶是否存在,不存在则创建一个新的桶
            boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(minioProperties.getBucketName()).build());
            if (!exists) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(minioProperties.getBucketName()).build());
            }
            //设置存储对象名称,格式为:yyyyMMdd/UUID.jpg
            String dateDir = DateUtil.format(new Date(), "yyyyMMdd");
            String uuid = UUID.randomUUID().toString().replace("-", "");
            String fileName = dateDir + "/" + uuid + multipartFile.getOriginalFilename();
            log.info("fileName:{}", fileName);
            //文件上传
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(minioProperties.getBucketName())
                    .stream(multipartFile.getInputStream(), multipartFile.getSize(), -1)
                    .object(fileName)
                    .build();
            minioClient.putObject(putObjectArgs);
            return minioProperties.getEndpointUrl() + "/" + minioProperties.getBucketName() + "/" + fileName;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

配置文件的内容:服务端地址、账号、密码、桶名称。

  minio:
    endpointUrl: http://192.168.88.66:9000
    accessKey: minioadmin
    secreKey: minioadmin
    bucketName: spzx-bucket

配置类不要忘记在启动类上让其生效。

@Data
@ConfigurationProperties(prefix="spzx.minio")
public class MinioProperties {
    private String endpointUrl;
    private String accessKey;
    private String secreKey;
    private String bucketName;
}

当我们点击上传图片后,图片就会以我们定义的格式存放在服务器中。
在这里插入图片描述

四、查询用户

需求说明:
1、如果在搜索表单中输入查询关键字以及创建的开始时间和结束时间,那么此时就需要按照查询关键字以及创建的开始时间和结束时间进行条件查询
2、查询关键字搜索的字段是用户名,在查询的时候需要进行模糊查询;搜索结果进行分页

debug走一遍,查询条件如下图所示:
在这里插入图片描述

首先来到controller层,拿到前端传来的额数据:
在这里插入图片描述
进入到业务层,通过mapper接口查询到正确的数据,将结果返回给前端:
在这里插入图片描述
对应的SQL语句如下所示:

    <!--    List<SysUser> findByPage(SysUserDto sysUserDto);-->
    <select id="findByPage" resultType="com.atguigu.spzx.model.entity.system.SysUser">
        select
        <include refid="columns"></include>
        from sys_user
        <where>
            <if test="keyword != null and keyword != ''">
                and username like CONCAT('%',#{keyword},'%')
            </if>
            <if test="createTimeBegin != null and createTimeBegin != ''">
                and create_time &gt;= #{createTimeBegin}
            </if>
            <if test="createTimeEnd != null and createTimeEnd != ''">
                and create_time &lt;= #{createTimeEnd}
            </if>
            and is_deleted = 0
        </where>
        order by id desc
    </select>

五、添加用户

需求说明:
当用户点击添加按钮的时候,弹出对话框,用户在该表单中点击提交按钮的时候将表单进行提交,在后端需要将提交过来的表单数据保存到数据库中即可,页面效果如下所示:
在这里插入图片描述
来到controller层,拿到表格中提交的数据
在这里插入图片描述
来到业务层,判断用户名是否已经存在,存在则抛出自定义异常;反之则将密码进行加密后,设置用户不可用的状态后,保存到数据库中。
在这里插入图片描述
保存数据到数据库的SQL如下:

    <!--    void saveSysUser(SysUser sysUser);-->
    <insert id="saveSysUser">
        insert into sys_user (username, password, name, phone, avatar, description, status)
        values (#{userName}, #{password}, #{name}, #{phone}, #{avatar}, #{description}, #{status})
    </insert>

六、修改用户

需求说明:
当用户点击修改按钮的时候,在该对话框中需要将当前行所对应的用户数据在该表单页面进行展示。当用户在该表单中点击提交按钮的时候那么此时就需要将表单进行提交,在后端需要提交过来的表单数据修改数据库中的即可。页面效果如下所示:
在这里插入图片描述
修改用户的controller层与业务层没有很大的差别,主要是SQL语句的不同,通过controller层,可以看到修改的数据信息
在这里插入图片描述
业务层非常简单
在这里插入图片描述
SQL语句编写如下:

<!--void updateSysUser(SysUser sysUser);-->
    <update id="updateSysUser">
        update sys_user set
        <if test="userName != null and userName != ''">
            username = #{userName},
        </if>
        <if test="avatar != null and avatar != ''">
            avatar = #{avatar},
        </if>
        <if test="password != null and password != ''">
            password = #{password},
        </if>
        <if test="name != null and name != ''">
            name = #{name},
        </if>
        <if test="phone != null and phone != ''">
            phone = #{phone},
        </if>
        <if test="description != null and description != ''">
            description = #{description},
        </if>
        <if test="status != null and status != ''">
            status = #{status},
        </if>
        update_time = now()
        where
        id = #{id}
    </update>

七、删除用户

需求说明:
当点击删除按钮的时候此时需要弹出一个提示框,询问是否需要删除数据?如果用户点击是,那么此时向后端发送请求传递id参数,后端接收id参数进行逻辑删除。页面效果如下所示:
在这里插入图片描述
controller层拿到要删除的用户id
在这里插入图片描述
业务层删除数据库数据
在这里插入图片描述
删除语句比较简单,直接使用注解的方式

    @Update("update sys_user set update_time = now() ,is_deleted = 1  where id = #{userId}")
    void deleteById(Long userId);

八、分配角色

当用户点击"分配角色"按钮的时候,在对话框中会展示出来系统中所有的角色信息。用户此时就可以选择对应的角色,选择完毕以后,点击确定按钮,此时就需要请求后端接口,将选中的角色数据保存保存到sys_user_role表中。效果图如下所示:
在这里插入图片描述

8.1 角色数据回显

当点击分配角色按钮的时候,除了需要将系统中所有的角色数据查询处理以外。还需要将当前登录用户所对应的角色数据查询出来,在进行展示的时候需要用户所具有的角色数据需要是选中的状态。

首先controller层拿到用户的id
在这里插入图片描述
首先查询数据库表中所有的角色数据,用于前端页面的展示
在这里插入图片描述
然后根据用户角色表,查询当前用户的角色;最后统一将数据封装到map中返回给前端
在这里插入图片描述
这两个SQL语句都很简单,编写如下:

@Mapper
public interface SysRoleUserMapper {
    @Select("select role_id from sys_user_role where user_id = #{userId}")
    List<Long> findSysUserRoleByUserId(Long userId);
}
@Mapper
public interface SysRoleMapper {
    @Select("select * from sys_role where is_deleted = 0")
    List<SysRole> findAllRoles();
}

8.2 保存角色数据(批量插入优化)

controller层获取用户id以及分配的角色id
在这里插入图片描述
业务层是个事务,在分配角色之前,先把该用户之前所对应的角色数据删除;接着再分配新的角色数据给该用户。这里使用批量插入数据库,也算是个小优化。
在这里插入图片描述
SQL代码编写如下:

    <!--    void doAssign(Long userId, List<Long> roleIdList);-->
    <insert id="doAssign">
        insert into sys_user_role(user_id, role_id, create_time, update_time, is_deleted)
        values
        <foreach collection="roleIdList" item="item" separator=",">
            (#{userId},#{item},now(),now(),0)
        </foreach>
    </insert>
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。