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());