使用须知
本速搭为作者经验所成,若有问题请联系修改
该速搭不适用于新手,建议收藏,未来掌握springboot后可以使用
pom.xml配置
pom.xml启动器
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-devtools
true
org.springframework.boot
spring-boot-starter-thymeleaf
mysql
mysql-connector-java
runtime
org.projectlombok
lombok
true
junit
junit
org.springframework.boot
spring-boot-starter-validation
pom.xml构建排除项
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
yml设置
# *** 解除注释为选则使用 ***
spring:
# 设置数据库
datasource:
# *** 根据mysql版本调整 ***
driver-class-name: com.mysql.cj.jdbc.Driver
# *** 【必填】修改数据库(xgk2023) ***
url: jdbc:mysql://localhost:3306/Name?serverTimezone=GMT%2B8
# *** 【必填】修改用户名账号密码 ***
username: root
password: 123456
thymeleaf:
cache: false
# ***【选填】使templates里的html能访问到static里的静态资源 ***
# templates里的html访问不到static里的静态资源时添加
# mvc:
# static-path-pattern: /static
boolean serviceNameStartWithI = false;//user -> UserService 设置成true: user -> IUserService
generateByTables(serviceNameStartWithI, packageName, "user");
}
private void generateByTables(boolean serviceNameStartWithI, String packageName, String... tableNames) {
GlobalConfig config = new GlobalConfig();
String dbUrl = "jdbc:mysql://localhost:3306/xgk2023";
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setDbType(DbType.MYSQL)
.setUrl(dbUrl)
.setUsername("root")
.setPassword("123456")
.setDriverName("com.mysql.jdbc.Driver");
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig
.setCapitalMode(true)
.setEntityLombokModel(true) // 实体是否为lombok模型(默认 false)
.setNaming(NamingStrategy.underline_to_camel) // 命名法转换
.setInclude(tableNames);
config.setActiveRecord(false)
.setAuthor("hlt")
.setOutputDir("D://Projects//IDEA//springboot-01-helloworld//src//main//java")
.setFileOverride(true);
if (!serviceNameStartWithI) {
config.setServiceName("%sService");
}
new AutoGenerator().setGlobalConfig(config)
.setDataSource(dataSourceConfig)
.setStrategy(strategyConfig)
.setPackageInfo(
new PackageConfig()
.setParent(packageName)
.setController("controller")
.setEntity("entity")
).execute();
}
private void generateByTables(String packageName, String... tableNames) {
generateByTables(true, packageName, tableNames);
}
}
分页组件
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@MapperScan("com.sdjzu.xgk.mapper")
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}
分页组件使用方法
Page page = new Page<>(current, 10);
// 第一个参数 页码 第二个参数 每页数量
xgkMapper.selectPage(page, null);
// 第一个参数 分页器 第二个参数 查询条件
List xgks = page.getRecords();
实体与数据库关系绑定设置的注解
// 设置实体类对应的表名
@TableName("表名")
// 将属性对应的字段指定为主键
// value 用于指定主键的字段
// type 设置主键生成策略 默认雪花算法 案例中使用的自增
@TableId(value="字段名",type="IdType.AUTO")
// 指定属性所对应的字段名
@TableField("字段名")
// 指定属性对应的字段为逻辑删除字段
@TableLogic
SpringSecurity安全
注意:
Security配置中未定义的组件可先注释掉,不影响正常使用。 Security配置中需要的组件在本文章中都能找到
引入依赖
org.springframework.boot
spring-boot-starter-security
数据库时区设置
url: jdbc:mysql://localhost:3306/xgk2023?serverTimezone=GMT%2B8
# ?serverTimezone=GMT%2B8 注意添加这一部分
Security配置组件
import com.sdjzu.xgk.security.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
LoginFailureHandler loginFailureHandler;
@Autowired
LoginSuccessHandler loginSuccessHandler;
@Autowired
CaptchaFilter captchaFilter;
@Autowired
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
JwtAccessDeniedHandler jwtAccessDeniedHandler;
@Autowired
UserDetailServiceImpl userDetailService;
private static final String[] URL_WHITELIST = {
// "
.failureHandler(loginFailureHandler)
.loginPage("/xxx")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/xxx")
.permitAll()
// 注销配置
.and() // .and() 相当于 http
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/xxx")
.permitAll();
// 禁用session
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 配置拦截规则
.and()
.authorizeRequests()
.antMatchers(URL_WHITELIST).permitAll()
.anyRequest().authenticated() // 允许任何请求类型
// 配置异常处理器
.and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
// 配置过滤器
.and()
.addFilter(jwtAuthenticationFilter()) // 添加jwt身份验证过滤器
// 在用户名密码认证过滤器前添加验证码过滤器
.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class)
}
// 配置自定义的登录验证器
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService);
}
}
登录处理组件
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.sdjzu.xgk.entity.Users;
import com.sdjzu.xgk.mapper.UsersMapper;
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.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
UsersMapper usersMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
Users user = usersMapper.selectOne(wrapper);
if (user == null) {
throw new UsernameNotFoundException("用户名或密码不正确");
}
List auths = AuthorityUtils
.commaSeparatedStringToAuthorityList(
user.getRoot() + "," + user.getRole()
);
return new User(
user.getUsername(), // 账号
new BCryptPasswordEncoder().encode(user.getPassword()), // 密码
auths // 传入的角色 固定
);
}
public List getUserAuthority(String username){
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
Users user = usersMapper.selectOne(wrapper);
if (user == null) {
throw new UsernameNotFoundException("用户名或密码不正确");
}
return AuthorityUtils
.commaSeparatedStringToAuthorityList(
user.getRoot() + "," + user.getRole()
);
}
}
登录失败处理组件
import cn.hutool.json.JSONUtil;
import com.sdjzu.xgk.common.lang.Result;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream outputStream = response.getOutputStream();
Result result = Result.fail("用户名或密码错误");
result.setMsg(exception.getMessage());
result.setCode(201);
outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
outputStream.flush();
outputStream.close();
}
}
登录成功处理组件
import cn.hutool.json.JSONUtil;
import com.sdjzu.xgk.common.lang.Result;
import com.sdjzu.xgk.utils.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
JwtUtils jwtUtils;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
// 获取当前响应信息
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream outputStream = response.getOutputStream();
// 生成jwt,并添加到响应头中
String jwt = jwtUtils.generateToken(authentication.getName());
response.setHeader(jwtUtils.getHeader(), jwt);
// 生成Result,并添加到响应中
Result result = Result.succ("");
outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
outputStream.flush();
outputStream.close();
}
}
通过注解设置权限
在接口或接口类前添加如下注解
用户具有某个角色,可以访问方法
@Secured({"ROLE_sale","ROLE_manager"})
使用于进入方法前的权限验证
可以将登录用户的roles/premissions参数传入到方法中
@PreAuthorize("hasRole('ROLE_管理员')")
@PreAuthorize("hasAnyAuthority('menu:system')")
使用于进入方法后的权限验证 常用于返回值
@PostAuthorize("hasAnyAuthority('menu:system')")
以下两个注解用在上面几个注解之后
权限验证之后对数据进行过滤,留下用户名是admin1的数据
表达式中filterObject引用的方法是返回值List中的某一个元素
@PostFilter("filterObject.username == 'admin1'")
进入控制器之前对数据进行过滤
@PreFilter(value = "filterObject.id%2==0")
redis工具
引入依赖
org.springframework.boot
spring-boot-starter-data-redis
redis配置
import com.fasterxml.jackson.databind.ObjectMapper;
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.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
jackson2JsonRedisSerializer.setObjectMapper(new ObjectMapper());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
}
redis工具类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
//============================String=============================
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
//================================Map=================================
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
public Map hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
public boolean hmset(String key, Map map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public boolean hmset(String key, Map map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
//============================set=============================
public Set sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//===============================list=================================
public List lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public boolean lSet(String key, List value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public boolean lSet(String key, List value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//================有序集合 sort set===================
public boolean zSet(String key, Object value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
public long batchZSet(String key, Set typles) {
return redisTemplate.opsForZSet().add(key, typles);
}
public void zIncrementScore(String key, Object value, long delta) {
redisTemplate.opsForZSet().incrementScore(key, value, delta);
}
public void zUnionAndStore(String key, Collection otherKeys, String destKey) {
redisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey);
}
public long getZsetScore(String key, Object value) {
Double score = redisTemplate.opsForZSet().score(key, value);
if (score == null) {
return 0;
} else {
return score.longValue();
}
}
public Set getZSetRank(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
}
}
验证码验证
前置:需要redis工具
引入依赖
com.github.axet
kaptcha
0.0.9
验证码异常处理组件
import org.springframework.security.core.AuthenticationException;
public class CaptchaException extends AuthenticationException {
public CaptchaException(String msg) {
super(msg);
}
}
验证码过滤组件
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.sdjzu.xgk.common.exception.CaptchaException;
import com.sdjzu.xgk.common.lang.Const;
import com.sdjzu.xgk.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
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;
@Component
public class CaptchaFilter extends OncePerRequestFilter {
@Autowired
RedisUtil redisUtil;
@Autowired
LoginFailureHandler loginFailureHandler;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String url = httpServletRequest.getRequestURI();
// 捕获地址为 /login 且方式为 post 的请求
if ("/login".equals(url) && httpServletRequest.getMethod().equals("POST")) {
try{
// 校验验证码
validate(httpServletRequest);
} catch (CaptchaException e) {
// 检验验证码失败-->交给认证失败处理器
loginFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);
}
}
// 校验验证码成功-->交给下一个过滤器
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
// 校验验证码逻辑
private void validate(HttpServletRequest httpServletRequest) {
String code = httpServletRequest.getParameter("code");
String key = httpServletRequest.getParameter("token");
if (StringUtils.isBlank(code) || StringUtils.isBlank(key)) {
throw new CaptchaException("验证码错误"); // 判断code/key 是否为空或含有空白字符
}
if (!code.equals(redisUtil.hget(Const.CAPTCHA_KEY, key))) {
throw new CaptchaException("验证码错误"); // 判断redis里是否含有该验证码信息
}
// 一次性使用
redisUtil.hdel(Const.CAPTCHA_KEY, key); // 删除本次识别验证码在redis对应的信息
}
}
验证码样式配置
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 KaptchaConfig {
@Bean
public DefaultKaptcha producer() {
Properties properties = new Properties();
properties.put("kaptcha.border", "no"); // 是否有边框
properties.put("kaptcha.textproducer.font.color", "black"); // 颜色
properties.put("kaptcha.textproducer.char.space", "4"); // 每个字符的空行
properties.put("kaptcha.image.height", "40"); // 高度
properties.put("kaptcha.image.width", "120"); // 宽度
properties.put("kaptcha.textproducer.font.size", "30"); // 文字大小
Config config = new Config(properties);
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
验证码注入配置
import com.sdjzu.xgk.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
public class BaseController {
@Autowired
HttpServletRequest req;
@Autowired
RedisUtil redisUtil;
}
验证码路由配置
import cn.hutool.core.lang.UUID;
import cn.hutool.core.map.MapUtil;
import com.google.code.kaptcha.Producer;
import com.sdjzu.xgk.common.lang.Const;
import com.sdjzu.xgk.common.lang.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import sun.misc.BASE64Encoder;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@CrossOrigin
@RestController
public class AuthController extends BaseController {
@Autowired
Producer producer;
@GetMapping("/captcha")
public Result captcha() throws IOException {
String key = UUID.randomUUID().toString();
String code = producer.createText();
BufferedImage image = producer.createImage(code);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(image, "jpg", outputStream);
BASE64Encoder encoder = new BASE64Encoder();
String str = "data:image/jpeg;base64,";
String base64Img = str + encoder.encode(outputStream.toByteArray());
redisUtil.hset(Const.CAPTCHA_KEY, key, code, 120);
return Result.succ(
MapUtil.builder()
.put("token", key)
.put("captchaImg", base64Img)
.build()
);
}
}
jwt配置
引入依赖
io.jsonwebtoken
jjwt
0.9.1
cn.hutool
hutool-all
5.3.3
org.apache.commons
commons-lang3
3.11
org.freemarker
freemarker
2.3.30
jwt身份验证过滤器
import cn.hutool.core.util.StrUtil;
import com.sdjzu.xgk.entity.Users;
import com.sdjzu.xgk.mapper.UsersMapper;
import com.sdjzu.xgk.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
@Autowired
JwtUtils jwtUtils;
@Autowired
UsersMapper usersMapper;
@Autowired
UserDetailServiceImpl userDetailService;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String jwt = request.getHeader(jwtUtils.getHeader());
if (StrUtil.isBlankOrUndefined(jwt)) {
chain.doFilter(request, response); // token为空-->进入下一层过滤器
return;
}
Claims claim = jwtUtils.getClaimByToken(jwt); // 解析token
if (claim == null) {
throw new JwtException("token 异常"); // 解析token失败
}
if (jwtUtils.isTokenExpired(claim)) {
throw new JwtException("token已过期"); // 验证token时效
}
String username = claim.getSubject(); // 获取token所属用户名
UsernamePasswordAuthenticationToken token // 包装凭证
= new UsernamePasswordAuthenticationToken(
username, // 用户名
null,
userDetailService.getUserAuthority(username) // 权限
);
SecurityContextHolder.getContext().setAuthentication(token); // 存入Security
chain.doFilter(request, response); // 进入下一层过滤器
}
}
jwt认证入口点
import cn.hutool.json.JSONUtil;
import com.sdjzu.xgk.common.lang.Result;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8"); // 设置响应内容类型
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 设置响应状态码
ServletOutputStream outputStream = response.getOutputStream(); // 获取响应的输出流
//生成result,并添加到响应中
Result result = Result.fail("请先登录");
outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
outputStream.flush(); // 将缓冲区所有的数据输出
outputStream.close(); // 关闭缓冲区
}
}
jwt拒绝访问处理组件
package com.sdjzu.xgk.security;
import cn.hutool.json.JSONUtil;
import com.sdjzu.xgk.common.lang.Result;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8"); // 设置响应内容类型
response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 设置响应状态码
ServletOutputStream outputStream = response.getOutputStream(); // 获取响应的输出流
//生成result,并添加到响应中
Result result = Result.fail(accessDeniedException.getMessage());
outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
outputStream.flush(); // 将缓冲区所有的数据输出
outputStream.close(); // 关闭缓冲区
}
}
yml添加变量
markerhub:
jwt:
header: Authorization
expire: 604800 #7天,秒单位
secret: ji8n3439n439n43ld9ne93kdusyxgshx
jwt工具类
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
@Data
@Component
@ConfigurationProperties(prefix = "markerhub.jwt")
public class JwtUtils {
private long expire;
private String secret;
private String header;
// 生成jwt
public String generateToken(String username) {
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + 1000 * expire);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(username)
.setIssuedAt(nowDate)
.setExpiration(expireDate)// 7天過期
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
// 解析jwt
public Claims getClaimByToken(String jwt) {
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(jwt)
.getBody();
} catch (Exception e) {
return null;
}
}
// jwt是否过期
public boolean isTokenExpired(Claims claims) {
return claims.getExpiration().before(new Date());
}
// 一个完整的token
public String generateToken() {
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + 1000 * 60 * 60 * 24);
Date NotBefore = new Date(nowDate.getTime())
return Jwts.builder()
//设置头部
.setHeaderParam("typ", "JWT") //设置token的类型
.setHeaderParam("alg", "HS256") //声明token使用的签名算法
//添加载荷
.claim("xxx", "xxx") // 添加自定义信息
//设置主体 - 属于载荷
.setIssuer("jwt") // jwt签发者
.setSubject("user") // jwt所面向的用户
.setAudience("username") // 接收jwt的一方
.setExpiration(expireDate) // jwt的过期时间
.setNotBefore() // 定义在什么时间之前,该token都是不可用的
.setIssuedAt(nowDate) // jwt的签发时间
.setId(UUID.randomUUID().toString())// jwt的唯一身份标识,避免重复
//设置签证
.signWith(SignatureAlgorithm.HS256, signature) // 使用算法编码
.compact(); // 拼接签名
}
}
EasyExcel使用
引入依赖
com.alibaba
easyexcel
2.1.6
监听器配置
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.baomidou.mybatisplus.extension.service.IService;
import com.sdjzu.xgk.entity.Category;
import com.sdjzu.xgk.entity.Users;
import com.sdjzu.xgk.service.UsersService;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
// 有个很重要的点 ExcelListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class ExcelListener> extends AnalysisEventListener {
private List list = new ArrayList<>();
private static final int BATCH_COUNT = 5;
private Service usersService;
public ExcelListener(Service usersService) {
this.usersService = usersService;
}
@Override
public void invoke(Entity goods, AnalysisContext analysisContext) {
System.out.println("解析到一条数据:========================" + goods.toString());
// 数据存储到datas,供批量处理,或后续自己业务逻辑处理。
list.add(goods);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理datas
list.clear();
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
saveData();//确保所有数据都能入库
}
private void saveData() {
System.out.println("==============================" + list.size() + "条数据,开始存储到数据库");
usersService.saveBatch((Collection) list);
}
}
工具类
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
public class ExcelUtil {
public static void download(HttpServletResponse response, Class t, List list,String filename) throws IOException {
response.setContentType("application/vnd.ms-excel");// 设置文本内省
response.setCharacterEncoding("utf-8");// 设置字符编码
response.setHeader("Content-disposition", "attachment;filename="+filename+".xlsx"); // 设置响应头
EasyExcel.write(response.getOutputStream(), t).sheet("模板").doWrite(list); //用io流来写入数据
}
public static void upload( Class t, IService service, MultipartFile file) throws IOException {
EasyExcel.read(file.getInputStream(), t , new ExcelListener(service)).sheet().doRead();
}
}
对操作的实体添加注解
@ExcelIgnore // 该属性不会被导出
@ExcelProperty("权限") // 该属性的表头
使用方法
//导出为Excel
@RequestMapping("/downloadexcel.do")
public void download(HttpServletResponse response) throws IOException {
List list = usersMapper.selectList(null);
ExcelUtil.download(response,Users.class,list,"test"); // 最后一个参数为导出文件名
}
//导入Excel
@RequestMapping("/importexcel.do")
public void upload(@RequestParam(value = "excelFile") MultipartFile file) throws IOException{
ExcelUtil.upload(Users.class,usersService,file);
}