(B站云e办)SpringBoot开发项目实战记录(二)(验证图形码开发,根据id获取菜单接口,reids优化菜单接口)

一、验证码开发(google)

1.1 后端生成验证吗

1. pom

<!-- google kaptcha依赖 -->
        <dependency>
             <groupId>com.github.axet</groupId>
             <artifactId>kaptcha</artifactId>
             <version>0.0.9</version>
        </dependency>

2. 验证码的配置类

package com.jzq.server.config.captcha;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;

/**
 * 验证码的配置类
 */
@Configuration
public class CaptchaConfig {

    @Bean
    public DefaultKaptcha defaultKaptcha() {
        //验证码生成器
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        //配置
        Properties properties = new Properties();
        //是否有边框
        properties.setProperty("kaptcha.border", "yes");
        //设置边框颜色
        properties.setProperty("kaptcha.border.color", "105,179,90");
        //边框粗细度,默认为1
        // properties.setProperty("kaptcha.border.thickness","1");
        //验证码
        properties.setProperty("kaptcha.session.key", "code");
        //验证码文本字符颜色 默认为黑色
        properties.setProperty("kaptcha.textproducer.font.color", "blue");
        //设置字体样式
        properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅 黑");
        //字体大小,默认40
        properties.setProperty("kaptcha.textproducer.font.size", "30");
        //验证码文本字符内容范围 默认为abced2345678gfynmnpwx
        // properties.setProperty("kaptcha.textproducer.char.string", "");
        //字符长度,默认为5
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        //字符间距 默认为2
        properties.setProperty("kaptcha.textproducer.char.space", "4");
        //验证码图片宽度 默认为200
        properties.setProperty("kaptcha.image.width", "100");
        //验证码图片高度 默认为40
        properties.setProperty("kaptcha.image.height", "40");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

3. 生成验证码controller

通过注入验证码配置类中的DefaultKaptcha对象
通过DefaultKaptcha对象去获取生成的text,拿到随机数后通过DefaultKaptcha对象创建图片defaultKaptcha.createImage(text);通过resp推送到浏览器流。

package com.jzq.server.controller;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;

/**
 * 验证码
 */

@RestController

public class CaptchaController {

    @Autowired
    private DefaultKaptcha defaultKaptcha;

    @ApiOperation(value = "验证码")
    @GetMapping(value = "/captcha")
    public void capacha(HttpServletRequest request, HttpServletResponse response) {
        // 定义response输出类型为image/jpeg类型
        response.setDateHeader("Expires", 0);
        // Set standard HTTP/1.1 no-cache headers.
        response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
        // Set IE extended HTTP/1.1 no-cache headers (use addHeader).
                response.addHeader("Cache-Control", "post-check=0, pre-check=0");
        // Set standard HTTP/1.0 no-cache header.
        response.setHeader("Pragma", "no-cache");
        // return a jpeg
        response.setContentType("image/jpeg");
        //-------------------生成验证码 begin --------------------------

        // 获取验证码的文本内容
        String text = defaultKaptcha.createText();
        System.out.println("验证码:" + text);

        // 将验证码放到session
        request.getSession().setAttribute("captcha", text);
        // 根据文本验证码内容创建图形验证码
        BufferedImage image = defaultKaptcha.createImage(text);
        ServletOutputStream servletOutputStream = null;
        try {
            servletOutputStream = response.getOutputStream();
            // 输出流输出图片, 格式为jpg
            ImageIO.write(image, "jpg", servletOutputStream);
            // 发送到浏览器
            servletOutputStream.flush();

        }catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (servletOutputStream != null) {
                try {
                    servletOutputStream.close();
                }catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        //-------------------生成验证码 end ----------------------------
    }
}

注意:
⭐在security配置文件中放行一下验证码请求 ("/captcha")

演示:

在这里插入图片描述

1.2 校验验证码

在登录逻辑处加上验证码参数,先判断验证是否一致!
在这里插入图片描述

二、菜单接口开发

2.1 根据用户id查询菜单列表

1. 菜单实体类

⭐ 知识点:
@TableName(“t_menu”) 指该实体类与数据库哪个表关联
@ApiModel(value=“Menu对象”, description=""): 描述返回对象的意义
@TableField(exist = false) : 表示不是表中的属性

package com.jzq.server.pojo;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import java.util.List;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * <p>
 * 
 * </p>
 *
 * @author seven
 * @since 2022-01-02
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_menu")
@ApiModel(value="Menu对象", description="")
public class Menu implements Serializable {

    private static final long serialVersionUID = 1L;

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

    @ApiModelProperty(value = "url")
    private String url;

    @ApiModelProperty(value = "path")
    private String path;

    @ApiModelProperty(value = "组件")
    private String component;

    @ApiModelProperty(value = "菜单名")
    private String name;

    @ApiModelProperty(value = "图标")
    private String iconCls;

    @ApiModelProperty(value = "是否保持激活")
    private Boolean keepAlive;

    @ApiModelProperty(value = "是否要求权限")
    private Boolean requireAuth;

    @ApiModelProperty(value = "父id")
    private Integer parentId;

    @ApiModelProperty(value = "是否启用")
    private Boolean enabled;

    @ApiModelProperty(value = "子菜单")
    @TableField(exist = false)   // 表示不是表中的属性
    private List<Menu> children;  // 子菜单
}

2. 根据id查菜单列表的controller

@RestController
@RequestMapping("/system/cfg")
public class MenuController {

    @Autowired
    private IAdminService adminService;

    @ApiOperation(value = "通过用户id查询菜单列表")
    @GetMapping("/menu")
    public List<Menu> getMenusByAdminId() {
        return adminService.getMenusByAdminId();
    }
}

3. 根据id查菜单列表的service

⭐知识点:

  1. 如何拿到security全局的用户(UserDetails) : SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    /**
     * 根据用户id获取菜单列表
     * @return
     */
    @Override
    public List<Menu> getMenusByAdminId() {
        // 获取 Security全局内的用户信息
        Admin admin = (Admin) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        System.out.println(admin);
        System.out.println(SecurityContextHolder.getContext().getAuthentication().getPrincipal());

        // 通过id查询数据库
        return adminMapper.getMenusByAdminId(admin.getId());
    }

4. 根据id查菜单列表的maapper

4.1 sql解析
  1. 这个sql关联了三个表 t_menu, t_admin_role,t_menu_role 分别对应 菜单表(两份一份父菜单pm1,一份子菜单m2, 自身关联) 用户权限表 菜单权限表
  2. pm1(父菜单) 通过 id 与 m2(子菜单)的parentId 关联 : pm1.id = m2.parentId
    子菜单与菜单权限表关联 :m2.id = mr.mid
    菜单权限与用户权限关联(通过rid): mr.rid = ar.rid
    根据具体用户查询:ar.adminId = #{id}
    并显示出所有未禁用的子菜单:m2.enabled = TRUE
SELECT DISTINCT pm1.*,
    m2.id id2, m2.url url2, m2.path path2, m2.component component2, m2.`name` name2,
    m2.iconCls iconCls2, m2.keepAlive keepAlive2, m2.requireAuth requireAuth2,
    m2.parentId parentId2, m2.enabled enabled2
    FROM  t_menu pm1, t_menu m2, t_admin_role ar, t_menu_role mr
    WHERE pm1.id = m2.parentId AND m2.id = mr.mid and mr.rid = ar.rid and ar.adminId = #{id} and m2.enabled = TRUE
4.2 mapper.xml
  1. 通过select 标签查询, 因为我们查的sql是嵌套查询, 把子菜单设置到自定义的children中
  2. 所以我们自定义返回类型Menus, 而实体类中有list属性的children存储子菜单,我们除了 extends="BaseResultMap"继承其他属性,还应通过collection 标签设置子菜单属性,每个属性我们查询时声明 属性名2
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jzq.server.mapper.MenuMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.jzq.server.pojo.Menu">
        <id column="id" property="id" />
        <result column="url" property="url" />
        <result column="path" property="path" />
        <result column="component" property="component" />
        <result column="name" property="name" />
        <result column="iconCls" property="iconCls" />
        <result column="keepAlive" property="keepAlive" />
        <result column="requireAuth" property="requireAuth" />
        <result column="parentId" property="parentId" />
        <result column="enabled" property="enabled" />
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, url, path, component, name, iconCls, keepAlive, requireAuth, parentId, enabled
    </sql>


    <resultMap id="Menus" type="com.jzq.server.pojo.Menu" extends="BaseResultMap">
        <!--  除了BaseResultMap的属性,我们还在Menu设置了一个list用存放子菜单  -->
        <collection property="children" ofType="com.jzq.server.pojo.Menu">
            <id column="id2" property="id" />
            <result column="url2" property="url" />
            <result column="path2" property="path" />
            <result column="component2" property="component" />
            <result column="name2" property="name" />
            <result column="iconCls2" property="iconCls" />
            <result column="keepAlive2" property="keepAlive" />
            <result column="requireAuth2" property="requireAuth" />
            <result column="parentId2" property="parentId" />
            <result column="enabled2" property="enabled" />
        </collection>
    </resultMap>

    <!--根据id查询菜单列表-->
    <select id="getMenusByAdminId" resultMap="Menus">
        SELECT DISTINCT pm1.*,
         m2.id id2, m2.url url2, m2.path path2, m2.component component2, m2.`name` name2,
         m2.iconCls iconCls2, m2.keepAlive keepAlive2, m2.requireAuth requireAuth2,
         m2.parentId parentId2, m2.enabled enabled2
        FROM  t_menu pm1, t_menu m2, t_admin_role ar, t_menu_role mr
        WHERE pm1.id = m2.parentId AND m2.id = mr.mid and mr.rid = ar.rid and ar.adminId = #{id} and m2.enabled = TRUE
    </select>
</mapper>

4.3 mapper接口
public interface MenuMapper extends BaseMapper<Menu> {
    /**
     * 通过用户id查询菜单列表
     * @param id
     * @return
     */
    List<Menu> getMenusByAdminId(Integer id);
}

展示:
在这里插入图片描述

2.2 Redis优化菜单功能

1. pom

<!-- spring data redis 依赖 -->
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- commons-pool2 对象池依赖 -->
<dependency>
 <groupId>org.apache.commons</groupId>
 <artifactId>commons-pool2</artifactId>
</dependency>

2. redis在配置文件中的配置

spring:

  # Redis配置
  redis:
    # 超时时间
    timeout: 10000ms
    # 服务器地址
    host: xxxx.xxxx.xxxx.xxxx
    # 端口号
    port: 6379
    # 数据库(第几个)
    database: 0
    # 密码 (没有密码可以不设置)
    # password:
    lettuce:
      pool:
        # 最大连接数(默认8)
        max-active: 1024
        # 最大连接阻塞时间,默认-1
        max-wait: 10000ms
        # 最大空闲连接
        max-idle: 200
        # 最小空闲连接
        min-idle: 5

3. redis配置类

⭐知识点:
1.注意带参数RedisConnectionFactory redisConnectionFactory
2.设置String和Hash序列化器
3. 设置连接工厂redisConnectionFactory

/**
 * redis配置类
 */
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // String类型 key序列器
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // String类型 value序列其
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        // Hash类型 key序列器
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        // Hash类型 value序列器
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        // 设置连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
}

4. 修改获取菜单的具体逻辑service

⭐知识点:

  1. 注入redis(配置类中的Bean)
    private RedisTemplate redisTemplate;
  2. 获取redis :ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
  3. 获取redis中的值:valueOperations.get(“menu_”+admin.getId());
  4. 设置redis中的值:valueOperations.set(“menu_”+admin.getId(), menuList);
@Autowired
    private RedisTemplate redisTemplate;

    /**
     * 根据用户id获取菜单列表
     * @return
     */
    @Override
    public List<Menu> getMenusByAdminId() {
        // 获取 Security全局内的用户信息
        Admin admin = (Admin) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

        // 首先先在redis中获取
        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
        // 根据用户id查菜单数据
        List<Menu> menuList = (List<Menu>) valueOperations.get("menu_"+admin.getId());
        // 判断List是否为空,即是判断redis中是否存在数据
        if (CollectionUtils.isEmpty(menuList)) {
            // 如果空的话,在数据库中查找
            menuList = menuMapper.getMenusByAdminId(admin.getId());
            // 设置到redis中
            valueOperations.set("menu_"+admin.getId(), menuList);
        }
        // 通过id查询数据库
        return menuList;
    }

示例:
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值