依赖注入(微服务下父模块和子模块)
多级菜单工具类生成树
自动引入自定义的类,通过调用里面的生成树方法,将需要进行生成树形数据的全体数据置于其中。
/**
* 使用递归方法建菜单
* @param sysMenuList
* @return
*/
public static List<SysMenu> buildTree(List<SysMenu> sysMenuList) {
List<SysMenu> trees = new ArrayList<>();
for (SysMenu sysMenu : sysMenuList) {
if (sysMenu.getParentId().longValue() == 0) {
trees.add(findChildren(sysMenu,sysMenuList));
}
}
return trees;
}
/**
* 递归查找子节点
* @param treeNodes
* @return
*/
public static SysMenu findChildren(SysMenu sysMenu, List<SysMenu> treeNodes) {
sysMenu.setChildren(new ArrayList<SysMenu>());
for (SysMenu it : treeNodes) {
if(sysMenu.getId().longValue() == it.getParentId().longValue()) {
//if (sysMenu.getChildren() == null) {
// sysMenu.setChildren(new ArrayList<>());
//}
sysMenu.getChildren().add(findChildren(it,treeNodes));
}
}
return sysMenu;
}
}
使用方法:直接调用方法,将需要转换为树形数据放到里面的方法即可
List<SysMenu> sysMenuList =sysMenuMapper.selectAll();
if (CollectionUtils.isEmpty(sysMenuList)) return null;
List<SysMenu> treeList = MenuHelper.buildTree(sysMenuList); //构建树形数据
跨域请求(默认方案二)
将其置于工具类,主类的pom在引入工具类的模块。就可以对主类进行控制
方案一:在IndexController上添加 @CrossOrigin注解
@RestController
@RequestMapping(value = "/admin/system/index")
@CrossOrigin(allowCredentials = "true" , originPatterns = "*" , allowedHeaders = "*")
public class IndexController {
}
弊端:每一个controller类上都来添加这样的一个接口影响开发效率、维护性较差
方案二:添加一个配置类配置跨域请求
// com.atguigu.spzx.manager.config
@Component
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 添加路径规则
.allowCredentials(true) // 是否允许在跨域的情况下传递Cookie
.allowedOriginPatterns("*") // 允许请求来源的域规则
.allowedMethods("*")
.allowedHeaders("*") ; // 允许所有的请求头
}
}
拦截器
有些网页需要阻拦,有些不需要阻拦,可以放行
创建一个拦截器
@Component
public class LoginAuthInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String , String> redisTemplate ;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取请求方式
String method = request.getMethod();
if("OPTIONS".equals(method)) { // 如果是跨域预检请求,直接放行
return true ;
}
// 获取token
String token = request.getHeader("token");
if(StrUtil.isEmpty(token)) {
responseNoLoginInfo(response) ;
return false ;
}
// 如果token不为空,那么此时验证token的合法性
String sysUserInfoJson = redisTemplate.opsForValue().get("user:login:" + token);
if(StrUtil.isEmpty(sysUserInfoJson)) {
responseNoLoginInfo(response) ;
return false ;
}
// 将用户数据存储到ThreadLocal中
SysUser sysUser = JSON.parseObject(sysUserInfoJson, SysUser.class);
AuthContextUtil.set(sysUser);
// 重置Redis中的用户数据的有效时间
redisTemplate.expire("user:login:" + token , 30 , TimeUnit.MINUTES) ;
// 放行
return true ;
}
//响应208状态码给前端
private void responseNoLoginInfo(HttpServletResponse response) {
Result<Object> result = Result.build(null, ResultCodeEnum.LOGIN_AUTH);
PrintWriter writer = null;
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=utf-8");
try {
writer = response.getWriter();
writer.print(JSON.toJSONString(result));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) writer.close();
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
AuthContextUtil.remove(); // 移除threadLocal中的数据
}
}
拦截器注册
需要在WebMvcConfigurer进行实现注册
excludePathPatterns是排除不需要拦截的路径
addPathPatterns是需要拦截的路径,/**是对所有的路径进行拦截
public class WebMvcConfiguration implements WebMvcConfigurer {
@Autowired
private LoginAuthInterceptor loginAuthInterceptor;
//拦截器注册
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginAuthInterceptor)
.excludePathPatterns("/admin/system/index/login" , "/admin/system/index/generateValidateCode","/admin/**", "/doc.html",
"/v3/api-docs/**",
"/webjars/**",
"/swagger-resources/**",
"/favicon.ico")//不需要拦截路径
.addPathPatterns("/**");//所有路径拦截
}
全局异常处理器
定义自定义异常(将其封装好)
package com.atguigu.spzx.exception;
import com.atguigu.spzx.model.vo.common.ResultCodeEnum;
import lombok.Data;
@Data
public class GuiguException extends RuntimeException {
private Integer code ; // 错误状态码
private String message ; // 错误消息
private ResultCodeEnum resultCodeEnum ; // 封装错误状态码和错误消息
public GuiguException(ResultCodeEnum resultCodeEnum) {
this.resultCodeEnum = resultCodeEnum ;
this.code = resultCodeEnum.getCode() ;
this.message = resultCodeEnum.getMessage();
}
public GuiguException(Integer code , String message) {
this.code = code ;
this.message = message ;
}
}
加入全局异常捕获器
捕获两种异常GuiguException与Exception,抛出哪种异常则调用哪种方法。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = GuiguException.class) // 处理自定义异常
@ResponseBody
public Result error(GuiguException exception) {
exception.printStackTrace();//打印java异常栈调用
return Result.build(null , exception.getResultCodeEnum()) ;
}
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e){
e.printStackTrace();
return Result.build(null , 201,"出现了异常") ;
}
}
UUID生成Key
String token = UUID.randomUUID().toString().replace("-", "");
随机数字验证码
String s = RandomUtil.randomNumbers(4);
生成图片验证码
CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(150, 48, 4, 20);
String codeValue = circleCaptcha.getCode();
String imageBase64 = circleCaptcha.getImageBase64();
// 生成uuid作为图片验证码的key
String codeKey = UUID.randomUUID().toString().replace("-", "");
Md5加密
String md5InputPassword = DigestUtils.md5DigestAsHex(inputPassword.getBytes());
Knife4jConfig
@Configuration
public class Knife4jConfig {
@Bean
public GroupedOpenApi adminApi() { // 创建了一个api接口的分组
return GroupedOpenApi.builder()
.group("admin-api") // 分组名称
.pathsToMatch("/admin/**") // 接口请求路径规则
.build();
}
/***
* @description 自定义接口信息
*/
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("尚品甑选API接口文档")
.version("1.0")
.description("尚品甑选API接口文档")
.contact(new Contact().name("atguigu"))); // 设定作者
}
}
上下文线程共享
如果登陆过需要获取角色信息,调用该方法即可
package com.atguigu.spzx.utils;
import com.atguigu.spzx.model.entity.system.SysUser;
public class AuthContextUtil {
//创建threadlocal对象
private static final ThreadLocal<SysUser> threadLocal = new ThreadLocal<>() ;
// 定义存储数据的静态方法
public static void set(SysUser sysUser) {
threadLocal.set(sysUser);
}
// 定义获取数据的方法
public static SysUser get() {
return threadLocal.get() ;
}
// 删除数据的方法
public static void remove() {
threadLocal.remove();
}
}
RedisConfig工具类
依赖需要加入
<!-- redis的起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
工具类代码
@Configuration // 声明这是一个 Spring 配置类
public class RedisConfig {
@Bean // 将方法返回的对象注册为 Spring 容器中的 Bean
public CacheManager cacheManager(LettuceConnectionFactory connectionFactory) {
// 1. 定义序列化器(用于 Redis Key 和 Value 的序列化)
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 2. 配置 Redis 缓存行为
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600)) // 设置缓存默认过期时间:600 秒(10 分钟)
// 指定 Key 的序列化方式(String 类型,避免 Redis 中出现二进制乱码)
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisSerializer))
// 指定 Value 的序列化方式(JSON 格式,支持存储复杂对象)
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(genericJackson2JsonRedisSerializer));
// 3. 构建 RedisCacheManager
RedisCacheManager cacheManager = RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config) // 应用上述配置
.build();
return cacheManager;
}
}
公共类定义手机号格式,方便判断错误
package com.hmdp.utils;
import cn.hutool.core.util.StrUtil;
import com.hmdp.constants.RegexConstants;
/**
* @author ghp
*/
public class RegexUtils {
/**
* 是否是无效手机格式
* @param phone 要校验的手机号
* @return true:符合,false:不符合
*/
public static boolean isPhoneInvalid(String phone){
return mismatch(phone, RegexConstants.PHONE_REGEX);
}
/**
* 是否是无效邮箱格式
* @param email 要校验的邮箱
* @return true:符合,false:不符合
*/
public static boolean isEmailInvalid(String email){
return mismatch(email, RegexConstants.EMAIL_REGEX);
}
/**
* 是否是无效验证码格式
* @param code 要校验的验证码
* @return true:符合,false:不符合
*/
public static boolean isCodeInvalid(String code){
return mismatch(code, RegexConstants.VERIFY_CODE_REGEX);
}
// 校验是否不符合正则格式
private static boolean mismatch(String str, String regex){
if (StrUtil.isBlank(str)) {
return true;
}
return !str.matches(regex);
}
}
public static boolean isPhoneInvalid(String phone){
return mismatch(phone, RegexConstants.PHONE_REGEX);
}
//mismatch()方法接收两个数组,并返回两个数组之间第一个不同项的索引。 例如, {1, 2, 3, 4, 5} 和 {1, 2, 3, 5, 8} 在索引 3 上不同。
正则表达式
public abstract class RegexConstants {
/**
* 手机号正则
*/
public static final String PHONE_REGEX = "^1([0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9])";
/**
* 邮箱正则
*/
public static final String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$";
/**
* 密码正则。4~32位的字母、数字、下划线
*/
public static final String PASSWORD_REGEX = "^\w{4,32}$";
/**
* 验证码正则, 6位数字或字母
*/
public static final String VERIFY_CODE_REGEX = "^[a-zA-Z\d]{6}$";
}
Result返回封装
package com.atiguigu.Result;
com.atiguigu.Result.Result
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private int code;
private String message;
private T data;
public Result(T data) {
this.code = 200;
this.message = "success";
this.data = data;
}
public Result(T data, boolean success, String message) {
if (success) {
this.code = 200;
this.message = "success";
} else {
this.code = 500;
this.message = message;
}
this.data = data;
}
public Result(int code, String message) {
this.code = code;
this.message = message;
this.data = null;
}
public static <T> Result<T> success(T data) {
return new Result<>(data);
}
public static <T> Result<T> fail(String message) {
return new Result<>(500, message);
}
public static <T> Result<T> fail(int code, String message) {
return new Result<>(code, message);
}
}