Pre后台系统学习笔记

Docker 打包部署步骤
1. mvn clean package
2. 把target下的pre-alpine文件夹下的jar包以及Dockerfile 传输到服务器的某一路径
3. cd到路径
4. 构建镜像 docker build -t pre:1.0 .
5. 运行命令 docker run --name pre -p 8081:8081 -d pre:1.0


真正的 产品级别 dockerfile文件

# 基础镜像
FROM java:openjdk-8-jre-alpine
# 维护者信息
MAINTAINER lihaodongmail@163.com
#Default to UTF-8 file.encoding
ENV LANG C.UTF-8
#设置alpine时区
ENV TIMEZONE Asia/Shanghai
#alpine自带的包含dl-cdn的域名非常慢,需要修改后才能下载数据。
RUN apk add -U tzdata && ln -snf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime && echo "${TIMEZONE}" > /etc/timezone
RUN sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add -U tzdata && ln -snf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime && echo "${TIMEZONE}" > /etc/timezone
#解决验证码问题
RUN apk add fontconfig && apk add --update ttf-dejavu && fc-cache --force
#添加应用
ADD pre-1.0.0-SNAPSHOT.jar pre-1.0.0-SNAPSHOT.jar
#参数
#ENV PARAMS=""
#执行操作 默认启动线上环境
ENTRYPOINT [ "sh", "-c", "java -Xmx50m -Djava.security.egd=file:/dev/./urandom -jar pre-1.0.0-SNAPSHOT.jar --spring.profiles.active=prod" ]


3 定义全局异常 除了有 controllerAdvice还有

@Slf4j
@RestControllerAdvice
public class BExceptionHandler {


/**
* 处理自定义异常
*/
@ExceptionHandler(BaseException.class)
public R handleRRException(BaseException e) {
return R.error(e.getCode(), e.getMsg());
}

lombok的另外一个注解
重写 hashcode和equal

@EqualsAndHashCode(callSuper = true)

4
设计到了 mybatisplus的学习

5 使用fastjson和 string作为redistemplate的序列化类

import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
* @Classname RedisConfig
* @Description redis配置
* @Author Created by Lihaodong (alias:小东啊) lihaodongmail@163.com
* @Date 2019-06-19 10:40
* @Version 1.0
*/
@Configuration
@ConditionalOnClass(RedisOperations.class)
public class RedisConfig {

@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();

//使用fastjson序列化
FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<Object>(Object.class);
// value值的序列化采用fastJsonRedisSerializer
template.setValueSerializer(fastJsonRedisSerializer);
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}

@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

}

 

6 swagger2的使用:

import com.google.common.collect.Lists;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;

/**
* @Classname Swagger2Config
* @Description Swagger2 配置
* @Author Created by Lihaodong (alias:小东啊) lihaodongmail@163.com
* @Date 2019-06-18 14:37
* @Version 1.0
*/
@Configuration
@EnableSwagger2
public class Swagger2Config {

@Value("${jwt.header}")
private String tokenHeader;

@Bean
public Docket createRestApi() {
ParameterBuilder ticketPar = new ParameterBuilder();
ArrayList<Parameter> pars = Lists.newArrayList();
ticketPar.name(tokenHeader).description("token")
.modelRef(new ModelRef("string"))
.parameterType("header")
.defaultValue("Bearer ")
.required(true)
.build();
pars.add(ticketPar.build());
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//选择controller包
.apis(RequestHandlerSelectors.basePackage("com.xd.pre.controller"))
.paths(PathSelectors.any())
.build();
}

private ApiInfo apiInfo() {
return new ApiInfoBuilder()
//自定义信息可按需求填写
.title("Pre使用Swagger2构建RESTFul APIs")
.description("测试")
.termsOfServiceUrl("http://www.52lhd.com")
.contact(new Contact("小东啊","http://www.52lhd.com","lihaodongmail@163.com"))
.version("1.0")
.build();
}

}

7 在枚举上仍然可以使用 lombok,例如如下代码
分别表示 自动get方法和全 参数的构造函数

@Getter
@AllArgsConstructor

8 首页生成验证码的方式:

先生成图片,然后获取图片对应的 文字,保持到redis中2分钟,
图片则保持到本地, ImageIO.write 这种方式 保持图片 到 jpeg,格式
和imge和 目的地(目的地是流) ServletOutputStream.,
同时 请求时自带了request和response


/**
* 生成验证码
*
* @param response
* @param request
* @throws ServletException
* @throws IOException
*/
@GetMapping("/captcha.jpg")
public void captcha(HttpServletResponse response, HttpServletRequest request) throws IOException {
response.setHeader("Cache-Control", "no-store, no-cache");
response.setContentType("image/jpeg");
// 生成图片验证码
BufferedImage image = CaptchaUtil.createImage();
// 生成文字验证码
String randomText = CaptchaUtil.drawRandomText(image);
// 保存到验证码到 redis 有效期两分钟
redisTemplate.opsForValue().set(PreConstant.PRE_IMAGE_SESSION_KEY, randomText.toLowerCase(),2, TimeUnit.MINUTES);
ServletOutputStream out = response.getOutputStream();
ImageIO.write(image, "jpeg", out);
}

9 验证码 生成工具类方法都是固定的:

import lombok.experimental.UtilityClass;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;

/**
* @Classname CaptchaUtil
* @Description 生成验证码工具类
* @Author Created by Lihaodong (alias:小东啊) lihaodongmail@163.com
* @Date 2019-06-22 08:04
* @Version 1.0
*/
@UtilityClass
public class CaptchaUtil {


private int width = 200;
private int height = 50;


public BufferedImage createImage(){
//生成对应宽高的初始图片
return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
}

public static String drawRandomText(BufferedImage verifyImg) {
Graphics2D graphics = (Graphics2D) verifyImg.getGraphics();
//设置画笔颜色-验证码背景色
graphics.setColor(Color.WHITE);
//填充背景
graphics.fillRect(0, 0, width, height);
graphics.setFont(new Font("微软雅黑", Font.PLAIN, 30));
//数字和字母的组合
String baseNumLetter = "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
StringBuilder sBuffer = new StringBuilder();
//旋转原点的 x 坐标
int x = 10;
String ch = "";
Random random = new Random();
for (int i = 0; i < 4; i++) {
graphics.setColor(getRandomColor());
//设置字体旋转角度
//角度小于30度
int degree = random.nextInt() % 30;
int dot = random.nextInt(baseNumLetter.length());
ch = baseNumLetter.charAt(dot) + "";
sBuffer.append(ch);
//正向旋转
graphics.rotate(degree * Math.PI / 180, x, 45);
graphics.drawString(ch, x, 45);
//反向旋转
graphics.rotate(-degree * Math.PI / 180, x, 45);
x += 48;
}

//画干扰线
for (int i = 0; i < 6; i++) {
// 设置随机颜色
graphics.setColor(getRandomColor());
// 随机画线
graphics.drawLine(random.nextInt(width), random.nextInt(height), random.nextInt(width), random.nextInt(height));
}
//添加噪点
for (int i = 0; i < 30; i++) {
int x1 = random.nextInt(width);
int y1 = random.nextInt(height);
graphics.setColor(getRandomColor());
graphics.fillRect(x1, y1, 2, 1);
}
return sBuffer.toString();
}

/**
* 随机取色
*/
private static Color getRandomColor() {
Random ran = new Random();
return new Color(ran.nextInt(256), ran.nextInt(256), ran.nextInt(256));

}
}


10 配置 security的权限的 注解spring security
通过 @PreAuthorize 包含了 角色 和权限

/**
* 保存部门信息
*
* @param sysDept
* @return
*/
@SysLog(descrption = "保存部门信息")
@PostMapping
@PreAuthorize("hasAuthority('sys:dept:add')")
public R save(@RequestBody SysDept sysDept) {
return R.ok(deptService.save(sysDept));
}

/**
* 获取部门信息
*
* @return
*/
@GetMapping
@PreAuthorize("hasAuthority('sys:dept:view')")
public R getDeptList() {
return R.ok(deptService.selectDeptList());
}

11 通过注解log的 事件监听器的方式,自动保持被注解的
方法 生成的log保持 到数据库中,用到了自定义注解和
数据库保持和 spring自带的 eventListener


import com.xd.pre.domain.SysLog;
import com.xd.pre.service.ISysLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
* @Classname SysLogListener
* @Description 注解形式的监听 异步监听日志事件
* @Author 李号东 lihaodongmail@163.com
* @Date 2019-04-28 11:34
* @Version 1.0
*/
@Slf4j
@Component
public class SysLogListener {

@Autowired
private ISysLogService sysLogService;

@Async
@Order
@EventListener(SysLogEvent.class)
public void saveSysLog(SysLogEvent event) {
SysLog sysLog = (SysLog) event.getSource();
// 保存日志
sysLogService.save(sysLog);
}
}

import com.xd.pre.domain.SysLog;
import org.springframework.context.ApplicationEvent;

/**
* @Classname SysLogEvent
* @Description 系统日志事件
* @Author 李号东 lihaodongmail@163.com
* @Date 2019-04-28 11:34
* @Version 1.0
*/
public class SysLogEvent extends ApplicationEvent {

public SysLogEvent(SysLog sysLog) {
super(sysLog);
}
}

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.xd.pre.security.PreUser;
import com.xd.pre.security.util.SecurityUtil;
import com.xd.pre.utils.LogUtil;
import com.xd.pre.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Objects;

/**
* @Classname SysLogAspect
* @Description 系统日志切面
* @Author 李号东 lihaodongmail@163.comgit reset --merge
* @Date 2019-04-22 23:52
* @Version 1.0
* ①切面注解得到请求数据 -> ②发布监听事件 -> ③异步监听日志入库
*/
@Slf4j
@Aspect
@Component
public class SysLogAspect {


/**
*log实体类
**/
private com.xd.pre.domain.SysLog sysLog = new com.xd.pre.domain.SysLog();

/**
* 事件发布是由ApplicationContext对象管控的,我们发布事件前需要注入ApplicationContext对象调用publishEvent方法完成事件发布
**/
@Autowired
private ApplicationContext applicationContext;

/***
* 定义controller切入点拦截规则,拦截SysLog注解的方法
*/
@Pointcut("@annotation(com.xd.pre.log.SysLog)")
public void sysLogAspect() {

}

/***
* 拦截控制层的操作日志
* @param joinPoint
* @return
* @throws Throwable
*/
@Before(value = "sysLogAspect()")
public void recordLog(JoinPoint joinPoint) throws Throwable {

// 开始时间
long beginTime = Instant.now().toEpochMilli();
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
PreUser securityUser = SecurityUtil.getUser();
sysLog.setUserName(securityUser.getUsername());
sysLog.setActionUrl(URLUtil.getPath(request.getRequestURI()));
sysLog.setStartTime(LocalDateTime.now());
sysLog.setRequestIp(ServletUtil.getClientIP(request));
sysLog.setRequestMethod(request.getMethod());
sysLog.setUa(request.getHeader("user-agent"));
//访问目标方法的参数 可动态改变参数值
Object[] args = joinPoint.getArgs();
//获取执行的方法名
sysLog.setActionMethod(joinPoint.getSignature().getName());
// 类名
sysLog.setClassPath(joinPoint.getTarget().getClass().getName());
sysLog.setActionMethod(joinPoint.getSignature().getName());
sysLog.setFinishTime(LocalDateTime.now());
// 参数
sysLog.setParams(Arrays.toString(args));
sysLog.setDescription(LogUtil.getControllerMethodDescription(joinPoint));
long endTime = Instant.now().toEpochMilli();
sysLog.setConsumingTime(endTime - beginTime);
}

/**
* 返回通知
* @param ret
* @throws Throwable
*/
@AfterReturning(returning = "ret", pointcut = "sysLogAspect()")
public void doAfterReturning(Object ret) {
// 处理完请求,返回内容
R r = Convert.convert(R.class, ret);
if (r.getCode() == 200){
// 正常返回
sysLog.setType(1);
} else {
sysLog.setType(2);
sysLog.setExDetail(r.getMsg());
}
// 发布事件
applicationContext.publishEvent(new SysLogEvent(sysLog));
}

/**
* 异常通知
* @param e
*/
@AfterThrowing(pointcut = "sysLogAspect()",throwing = "e")
public void doAfterThrowable(Throwable e){
// 异常
sysLog.setType(2);
// 异常对象
sysLog.setExDetail(LogUtil.getStackTrace(e));
// 异常信息
sysLog.setExDesc(e.getMessage());
// 发布事件
applicationContext.publishEvent(new SysLogEvent(sysLog));
}

}

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)//元注解,定义注解被保留策略,一般有三种策略
//1、RetentionPolicy.SOURCE 注解只保留在源文件中,在编译成class文件的时候被遗弃
//2、RetentionPolicy.CLASS 注解被保留在class中,但是在jvm加载的时候北欧抛弃,这个是默认的声明周期
//3、RetentionPolicy.RUNTIME 注解在jvm加载的时候仍被保留
@Target({ElementType.METHOD}) //定义了注解声明在哪些元素之前
@Documented
public @interface SysLog {
//定义成员
String descrption() default "" ;//描述
}


用法就是

@SysLog(descrption = "保存部门信息")
@PostMapping
@PreAuthorize("hasAuthority('sys:dept:add')")
public R save(@RequestBody SysDept sysDept) {
return R.ok(deptService.save(sysDept));
}

11 dto的用法

DTO(Data Transfer Object):数据传输对象,这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,我泛指用于展示层与服务层之间的数据传输对象。


12 用 mybatisplus的好处


import com.xd.pre.domain.SysLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
* <p>
* 系统日志 Mapper 接口
* </p>
*
* @author lihaodong
* @since 2019-04-27
*/
public interface SysLogMapper extends BaseMapper<SysLog> {

}


如果时复杂的查询语句


public interface SysMenuMapper extends BaseMapper<SysMenu> {


@Select("select m.perms from sys_menu m, sys_user u, sys_user_role ur, sys_role_menu rm\n" +
" where u.user_id = #{user_id} and u.user_id = ur.user_id\n" +
" and ur.role_id = rm.role_id and rm.menu_id = m.menu_id")
List<String> findPermsByUserId(Integer userId);

}

好多语法用的是注解版的 mybatis,不提倡和 data-jpa很类似

public interface SysMenuMapper extends BaseMapper<SysMenu> {


@Select("select m.perms from sys_menu m, sys_user u, sys_user_role ur, sys_role_menu rm\n" +
" where u.user_id = #{user_id} and u.user_id = ur.user_id\n" +
" and ur.role_id = rm.role_id and rm.menu_id = m.menu_id")
List<String> findPermsByUserId(Integer userId);

}

 

12 Spring security 所有配置 共7个类,


package com.xd.pre.security;

import cn.hutool.core.util.ObjectUtil;
import com.xd.pre.domain.SysUser;
import com.xd.pre.service.ISysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.Set;

/**
 * @Classname UserDetailsServiceImpl
 * @Description 身份验证
 * @Author 李号东 lihaodongmail@163.com
 * @Date 2019-05-07 20:30
 * @Version 1.0
 */
@Slf4j
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private ISysUserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        SysUser user = userService.findByUserName(username);
        if (ObjectUtil.isNull(user)) {
            log.info("登录用户:" + username + " 不存在.");
            throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
        }
        // 获取用户拥有的角色
        // 用户权限列表,根据用户拥有的权限标识与如 @PreAuthorize("hasAuthority('sys:menu:view')") 标注的接口对比,决定是否可以调用接口
        // 权限集合
        Set<String> permissions = userService.findPermsByUserId(user.getUserId());
        // 角色集合
        Set<String> roleIds = userService.findRoleIdByUserId(user.getUserId());
        permissions.addAll(roleIds);
        Collection<? extends GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(permissions.toArray(new String[0]));
        return new PreUser(user.getUserId(), username, user.getPassword(), authorities);
    }
}

package com.xd.pre.security;

import com.xd.pre.domain.SysRole;
import com.xd.pre.domain.SysUser;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

/**
 * @Author 李号东
 * @Description 用户身份权限认证类 登陆身份认证
 * @Date 2019-05-07 09:11
 * @Param
 * @return
 **/
@Setter
@Getter
@Accessors(chain = true)
public class PreUser implements UserDetails {

    private static final long serialVersionUID = 1L;


    private Integer userId;
    private String username;
    private String password;
    private Collection<? extends GrantedAuthority> authorities;


    public PreUser(Integer userId, String username, String password, Collection<? extends GrantedAuthority> authorities) {
        this.userId = userId;
        this.username = username;
        this.password = password;
        this.authorities = authorities;

    }

    public PreUser(String username, String password, List<SysRole> role) {
        this.username = username;
        this.password = password;
    }

    public PreUser(String username, String password) {
        this.username = username;
        this.password = password;
    }

    /**
     * 返回分配给用户的角色列表
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    /**
     * 账户是否未过期,过期无法验证
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 指定用户是否解锁,锁定的用户无法进行身份验证
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 是否可用 ,禁用的用户不能身份验证
     * @return
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}


package com.xd.pre.security.util;

import com.alibaba.fastjson.JSON;
import com.xd.pre.exception.BaseException;
import com.xd.pre.security.PreUser;
import com.xd.pre.utils.R;
import lombok.experimental.UtilityClass;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @Classname SecurityUtil
 * @Description 安全服务工具类
 * @Author 李号东 lihaodongmail@163.com
 * @Date 2019-05-08 10:12
 * @Version 1.0
 */
@UtilityClass
public class SecurityUtil {

    /**
     * 获取用户
     *
     * @param authentication
     * @return PreUser
     * <p>
     */
    private PreUser getUser(Authentication authentication) {
        Object principal = authentication.getPrincipal();
        if (principal instanceof PreUser) {
            return (PreUser) principal;
        }
        return null;
    }

    public void writeJavaScript(R r, HttpServletResponse response) throws IOException {
        response.setStatus(200);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter printWriter = response.getWriter();
        printWriter.write(JSON.toJSONString(r));
        printWriter.flush();
    }

    /**
     * 获取Authentication
     */
    private Authentication getAuthentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    /**
     * @Author 李号东
     * @Description 获取用户
     * @Date 11:29 2019-05-10
     **/
    public PreUser getUser(){
        try {
            return (PreUser) getAuthentication().getPrincipal();
        } catch (Exception e) {
            throw new BaseException("登录状态过期", HttpStatus.UNAUTHORIZED.value());
        }
    }
}


package com.xd.pre.security.util;

import com.xd.pre.security.PreUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.log4j.Log4j2;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.stereotype.Component;

/**
 * @Classname JwtUtil
 * @Description JWT工具类
 * @Author 李号东 lihaodongmail@163.com
 * @Date 2019-05-07 09:23
 * @Version 1.0
 */
@Log4j2
@Component
public class JwtUtil {

    /**
     * 用户名称
     */
    private static final String USERNAME = Claims.SUBJECT;

    private static final String USERID = "userid";
    /**
     * 创建时间
     */
    private static final String CREATED = "created";
    /**
     * 权限列表
     */
    private static final String AUTHORITIES = "authorities";
    /**
     * 密钥
     */
    private static final String SECRET = "abcdefgh";
    /**
     * 有效期1小时
     */
    private static final long EXPIRE_TIME = 60 * 60 * 1000;

    @Value("${jwt.header}")
    private String tokenHeader;

    @Value("${jwt.tokenHead}")
    private String authTokenStart;

    /**
     * 生成令牌
     *
     * @return 令牌
     */
    public static String generateToken(PreUser userDetail) {
        Map<String, Object> claims = new HashMap<>(3);
        claims.put(USERID,userDetail.getUserId());
        claims.put(USERNAME, userDetail.getUsername());
        claims.put(CREATED, new Date());
        claims.put(AUTHORITIES, userDetail.getAuthorities());
        return generateToken(claims);
    }

    /**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private static String generateToken(Map<String, Object> claims) {
        Date expirationDate = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, SECRET).compact();
    }

    /**
     * 从令牌中获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public static String getUsernameFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.getSubject();
    }

    /**
     * 根据请求令牌获取登录认证信息
     *
     * @return 用户名
     */
    public PreUser getUserFromToken(HttpServletRequest request) {
        // 获取请求携带的令牌
        String token = getToken(request);
        if (StringUtils.isNotEmpty(token)) {
            Claims claims = getClaimsFromToken(token);
            if (claims == null) {
                return null;
            }
            String username = claims.getSubject();
            if (username == null) {
                return null;
            }
            if (isTokenExpired(token)) {
                return null;
            }
            // 解析对应的权限以及用户id
            Object authors = claims.get(AUTHORITIES);
            Integer userId = (Integer)claims.get(USERID);
            Set<String> perms = new HashSet<>();
            if (authors instanceof List) {
                for (Object object : (List) authors) {
                    perms.add(((Map) object).get("authority").toString());
                }
            }
            Collection<? extends GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(perms.toArray(new String[0]));
            if (validateToken(token, username)){
                // 未把密码放到jwt
                return new PreUser(userId,username,"",authorities);
            }
        }
        return null;
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    private static Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     * 验证令牌
     *
     * @param token
     * @param username
     * @return
     */
    private static Boolean validateToken(String token, String username) {
        String userName = getUsernameFromToken(token);
        return (userName.equals(username) && !isTokenExpired(token));
    }

    /**
     * 刷新令牌
     *
     * @param token
     * @return
     */
    public static String refreshToken(String token) {
        String refreshedToken;
        try {
            Claims claims = getClaimsFromToken(token);
            claims.put(CREATED, new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 判断令牌是否过期
     *
     * @param token 令牌
     * @return 是否过期
     */
    private static Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 获取请求token
     *
     * @param request
     * @return
     */
    private String getToken(HttpServletRequest request) {
        String token = request.getHeader(tokenHeader);
        if (StringUtils.isNotEmpty(token)) {
            token = token.substring(authTokenStart.length());
        }
        return token;
    }


}


package com.xd.pre.security.handle;

import cn.hutool.http.Status;
import com.xd.pre.security.util.SecurityUtil;
import com.xd.pre.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;

/**
 * 认证失败处理类 返回401
 * @author lihaodong
 */
@Slf4j
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {

    private static final long serialVersionUID = -8970718410437077606L;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
        log.error("请求访问: " + request.getRequestURI() + " 接口, 经jwt认证失败,无法访问系统资源.");
        SecurityUtil.writeJavaScript(R.error(Status.HTTP_UNAUTHORIZED,"请求访问:" + request.getRequestURI() + "接口,经jwt 认证失败,无法访问系统资源"),response);
    }
}

package com.xd.pre.security.filter;

import cn.hutool.core.util.ObjectUtil;
import com.xd.pre.security.PreUser;
import com.xd.pre.security.util.JwtUtil;
import com.xd.pre.service.ISysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.Set;

/**
 * @Author 李号东
 * @Description token过滤器来验证token有效性 引用的stackoverflow一个答案里的处理方式
 * @Date 00:32 2019-03-17
 * @Param
 * @return
 **/
@Slf4j
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private ISysUserService userService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

        PreUser securityUser = jwtUtil.getUserFromToken(request);
        if (ObjectUtil.isNotNull(securityUser)){
            Set<String> permissions = userService.findPermsByUserId(securityUser.getUserId());
            Collection<? extends GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(permissions.toArray(new String[0]));
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(securityUser, null, authorities);
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        chain.doFilter(request, response);
    }
}

package com.xd.pre.security.config;

import com.xd.pre.security.UserDetailsServiceImpl;
import com.xd.pre.security.filter.JwtAuthenticationTokenFilter;
import com.xd.pre.security.handle.AuthenticationEntryPointImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @Classname WebSecurityConfig
 * @Description Security配置类
 * @Author 李号东 lihaodongmail@163.com
 * @Date 2019-05-07 09:10
 * @Version 1.0
 */
@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationEntryPointImpl unauthorizedHandler;

    @Autowired
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;


    /**
     * 解决 无法直接注入 AuthenticationManager
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 配置策略
     *
     * @param httpSecurity
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {

        httpSecurity
                // 由于使用的是JWT,我们这里不需要csrf
                .csrf().disable()
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求
                .authorizeRequests()
                // 对于登录login 图标 要允许匿名访问
                .antMatchers("/login/**", "/favicon.ico").anonymous()
                .antMatchers(
                        HttpMethod.GET,
                        "/*.html",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js"
                ).permitAll()
                // swagger start
                .antMatchers("/swagger-ui.html").anonymous()
                .antMatchers("/swagger-resources/**").anonymous()
                .antMatchers("/webjars/**").anonymous()
                .antMatchers("/*/api-docs").anonymous()
                // swagger end
                .antMatchers("/captcha.jpg")
                .permitAll()
                // 访问/user 需要拥有admin权限
                //  .antMatchers("/user").hasAuthority("ROLE_ADMIN")
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable();
        // 添加JWT filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
                // 设置UserDetailsService
                .userDetailsService(userDetailsService)
                // 使用BCrypt进行密码的hash
                .passwordEncoder(passwordEncoder());
    }

    /**
     * 装载BCrypt密码编码器 密码加密
     *
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

 

 


13 果然事务全部加在了 service层了


@Transactional(rollbackFor = Exception.class)
@Override
public boolean removeById(Serializable id) {
// 部门层级删除
List<Integer> idList = this.list(Wrappers.<SysDept>query().lambda().eq(SysDept::getParentId, id)).stream().map(SysDept::getDeptId).collect(Collectors.toList());
// 删除自己
idList.add((Integer) id);
return super.removeByIds(idList);
}

14 list 迭代器的用法

 

13 果然事务全部加在了 service层了


    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean removeById(Serializable id) {
        // 部门层级删除
        List<Integer> idList = this.list(Wrappers.<SysDept>query().lambda().eq(SysDept::getParentId, id)).stream().map(SysDept::getDeptId).collect(Collectors.toList());
        // 删除自己
        idList.add((Integer) id);
        return super.removeByIds(idList);
    }

14 list 迭代器的用法

     */
    private SysDept getDepartment(Integer deptId) {
        List<SysDept> departments = baseMapper.selectList(Wrappers.<SysDept>query().select("dept_id", "name", "parent_id", "sort", "create_time"));
        Map<Integer, SysDept> map = departments.stream().collect(
                Collectors.toMap(SysDept::getDeptId, department -> department));

        for (SysDept dept : map.values()) {
            SysDept parent = map.get(dept.getParentId());
            if (parent != null) {
                List<SysDept> children = parent.getChildren() == null ? new ArrayList<>() : parent.getChildren();
                children.add(dept);
                parent.setChildren(children);
            }
        }
        return map.get(deptId);
    }

 

 15 双冒号的用法

 

List<String> a1 = Arrays.asList("a", "b", "c");
public static void printValur(String str) {
System.out.println("print value : " + str);
}
使用前
a:
for (String a : a1) {
printValur(a);
};
b:
a1.forEach(x -> MyTest.printValur(x));
使用后
a1.forEach(MyTest::printValur);

return sysDepts.stream().map(SysDept::getDeptId).collect(Collectors.toList());

posted @ 2019-08-02 06:37  冰封剑客  阅读(846)  评论(0编辑  收藏  举报