SpringBoot开发项目实战记录
一、验证码开发(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
⭐知识点:
- 如何拿到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解析
- 这个sql关联了三个表 t_menu, t_admin_role,t_menu_role 分别对应 菜单表(两份一份父菜单pm1,一份子菜单m2, 自身关联) 用户权限表 菜单权限表
- 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
- 通过select 标签查询, 因为我们查的sql是嵌套查询, 把子菜单设置到自定义的children中
- 所以我们自定义返回类型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
⭐知识点:
- 注入redis(配置类中的Bean)
private RedisTemplate redisTemplate;- 获取redis :ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
- 获取redis中的值:valueOperations.get(“menu_”+admin.getId());
- 设置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;
}
示例: