Java – SpringBoot基础项目 – UnBlog 博客
常用项目工具类下载:
AppHttpCodeEnum
用于定义WEB服务中出现各种问题的枚举类,可以规范项目中的错误表达方式。
package cn.unsoft.utils.enums;
public enum AppHttpCodeEnum {
// 成功
SUCCESS(200,"操作成功"),
// 登录
NEED_LOGIN(401,"需要登录后操作"),
NO_OPERATOR_AUTH(403,"无权限操作"),
SYSTEM_ERROR(500,"出现错误"),
USERNAME_EXIST(501,"用户名已存在"),
PHONENUMBER_EXIST(502,"手机号已存在"),
EMAIL_EXIST(503, "邮箱已存在"),
REQUIRE_USERNAME(504, "必需填写用户名"),
REGISTER_INFO_ERROR(507, "用户信息不完整"),
REGISTER_EXIST_ERROR(507, "用户已被注册"),
UPLOAD_TYPE_ERROR(506, "上传的文件格式有误"),
DOWNLOAD_ERROR(506, "文件下载失败"),
LOGIN_ERROR(505,"用户名或密码错误");
int code;
String msg;
AppHttpCodeEnum(int code, String errorMessage){
this.code = code;
this.msg = errorMessage;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
BeanCopyUtils
用于把数据表中的实体类,转化为输出到前端显示的Vo实体类或前端传到服务器的DTO实体类,把该赋值的自动赋值。
支持单个Bean转换,也支持List集合中的Bean转换。
package cn.unsoft.utils;
import org.springframework.beans.BeanUtils;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.stream.Collectors;
/**
* 用于封装 Bean 复制的工具类
*/
public class BeanCopyUtils {
private BeanCopyUtils() {
}
/**
* 把一个数据库实体对象复制必要的数据到前端实体对象中
*
* @param bean
* @param clazz
* @param <Vo>
* @return
*/
public static <Vo> Vo copyBean(Object bean, Class<Vo> clazz) {
Vo vo = null;
try {
Constructor<Vo> constructor = clazz.getConstructor();
vo = constructor.newInstance();
BeanUtils.copyProperties(bean, vo);
} catch (Exception e) {
throw new RuntimeException(e);
}
return vo;
}
/**
* 把一个数据库实体对象集合复制必要的数据到前端实体对象中,并封装成集合
* @param objList
* @param clazz
* @return
* @param <T>
* @param <Vo>
*/
public static <T,Vo> List<Vo> copyBeanList(List<T> objList,Class<Vo> clazz){
return objList.stream().map(item -> copyBean(item, clazz)).collect(Collectors.toList());
}
}
使用方法:
BeanCopyUtils.copyBean(bean类,BeanVo.class)
JwtUtil
生成JWT密钥的工具,以token的形式发给前端保存,用以身份认证
package cn.unsoft.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
/**
* JWT工具类
*/
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 24*60 * 60 *1000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "unsoft";
public static String getUUID(){
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
/**
* 生成jtw
* @param subject token中要存放的数据(json格式)
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}
/**
* 生成jtw
* @param subject token中要存放的数据(json格式)
* @param ttlMillis token超时时间
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("sg") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
}
/**
* 创建token
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
return builder.compact();
}
public static void main(String[] args) throws Exception {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
Claims claims = parseJWT(token);
System.out.println(claims);
}
/**
* 生成加密后的秘钥 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
RedisCache
Redis 的操作封装,支持部分Redis操作,未来有新的操作需求可以加到里面
package cn.unsoft.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisCache {
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key) {
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection) {
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext()) {
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey) {
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 删除Hash中的数据
*
* @param key
* @param hkey
*/
public void delCacheMapValue(final String key, final String hkey) {
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 对Redis中的Map数据进行自增操作
*
* @param hash Redis 中定义的 Hash值
* @param key Map 中的 key 值
* @param v 自增迭代量
*/
public void incrementCacheMapValue(String hash, String key, int v) {
redisTemplate.boundHashOps(hash).increment(key, v);
}
}
ResponseResult
用于封装JSON返回数据的包装类,所有返回给前端都统一使用该类先进行包装
package cn.unsoft.utils;
import cn.unsoft.utils.enums.AppHttpCodeEnum;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.io.Serializable;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> implements Serializable {
private Integer code;
private String msg;
private T data;
public ResponseResult() {
this.code = AppHttpCodeEnum.SUCCESS.getCode();
this.msg = AppHttpCodeEnum.SUCCESS.getMsg();
}
public ResponseResult(Integer code, T data) {
this.code = code;
this.data = data;
}
public ResponseResult(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public ResponseResult(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static ResponseResult errorResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.error(code, msg);
}
public static ResponseResult okResult() {
ResponseResult result = new ResponseResult();
return result;
}
public static ResponseResult okResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.ok(code, null, msg);
}
public static ResponseResult okResult(Object data) {
ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getMsg());
if(data!=null) {
result.setData(data);
}
return result;
}
public static ResponseResult errorResult(AppHttpCodeEnum enums){
return setAppHttpCodeEnum(enums,enums.getMsg());
}
public static ResponseResult errorResult(AppHttpCodeEnum enums, String msg){
return setAppHttpCodeEnum(enums,msg);
}
public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
return okResult(enums.getCode(),enums.getMsg());
}
private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String msg){
return okResult(enums.getCode(),msg);
}
public ResponseResult<?> error(Integer code, String msg) {
this.code = code;
this.msg = msg;
return this;
}
public ResponseResult<?> ok(Integer code, T data) {
this.code = code;
this.data = data;
return this;
}
public ResponseResult<?> ok(Integer code, T data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
return this;
}
public ResponseResult<?> ok(T data) {
this.data = data;
return this;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
SecurityUtils
当系统引入Spring Security功能后,对于用户的登陆,后端会产生一个 UserDetails 的实现类对象,该工具类是用于在任何地方获取到当前请求的 UserDetails 用户类
package cn.unsoft.utils;
import cn.unsoft.domain.entity.LoginUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
/**
* 用户ID工具类
* 用于获取用户的 UserId 或 LoginUser 对象
*/
public class SecurityUtils {
/**
* 取得当前登陆用户
*
* @return
*/
public static Long getUserId() {
return getLoginUser().getUser().getId();
}
/**
* 获取当前账号的 LoginUser 对象
*
* @return
*/
public static LoginUser getLoginUser() {
return (LoginUser) getAuthentication().getPrincipal();
}
/**
* 获取当前账号的 Authentication 对象
*
* @return
*/
public static Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
/**
* 判断是否为管理员
*
* @return
*/
public static boolean isAdmin() {
Long userId = getUserId();
return userId != null && userId.equals(1L);
}
}
LoginUser
实现 UserDetails 接口的自定义实现类,用于Security中包装已登陆成功的user实体类对象。
package cn.unsoft.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
private User user;
// 用于保存该用户允许操作的权限,权限来自于 Menu 表中的 perms
// 通过请求方法中与该用户的 permissions 中对比是否存在
// 可以判断出 该请求方法是否允许该登陆用户访问
private List<String> permissions;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
UploadUtils
用于把文件上传到【七牛云】平台的工具类,上传完成后,会返回上传链接
url地址前缀需要在yml文件中指定
package cn.unsoft.utils;
import com.google.gson.Gson;
import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
/**
* 用于处理七牛云OSS对象存储功能的工具类
*/
@Component
@Data
@ConfigurationProperties("oss")
public class UploadUtils {
private String accessKey;
private String secretKey;
private String bucket;
private String domain;
/**
* 使用七牛云进行文件上传
* 使用 数据流 方式上传文件
*
* @return
*/
public String upload(InputStream inputStream, String fileName) {
//构造一个带指定 Region 对象的配置类
Configuration cfg = new Configuration(Region.autoRegion());
cfg.resumableUploadAPIVersion = Configuration.ResumableUploadAPIVersion.V2;// 指定分片上传版本
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//...生成上传凭证,然后准备上传
//默认不指定key的情况下,以文件内容的hash值作为文件名
String key = generateFilePath(fileName);
Auth auth = Auth.create(accessKey, secretKey);
String upToken = auth.uploadToken(bucket);
try {
Response response = uploadManager.put(inputStream, key, upToken, null, null);
//解析上传成功的结果
DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
return domain + putRet.key;
} catch (QiniuException ex) {
Response r = ex.response;
System.err.println(r.toString());
try {
System.err.println(r.bodyString());
} catch (QiniuException ex2) {
//ignore
}
}
return null;
}
/**
* 生成UUID随机名称
* 目录 yyyy/MM/dd/uuid.xxx
*
* @param fileName
* @return
*/
public String generateFilePath(String fileName) {
//根据日期生成路径 2022/1/15/
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");
String datePath = sdf.format(new Date());
//uuid作为文件名
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
//后缀和文件后缀一致
int index = fileName.lastIndexOf(".");
// test.jpg -> .jpg
String fileType = fileName.substring(index);
return new StringBuilder().append(datePath).append(uuid).append(fileType).toString();
}
}
yml中指定【七牛云】服务参数
# 配置自定义属性参数
oss:
accessKey: # 七牛云ak码
secretKey: # 七牛云sk码
bucket: un-blog # 七牛云存储对象名
domain: "http://rszvkzqrf.hn-bkt.clouddn.com/" # OSS图片服务器域名
WebUtils
用于某些场境下,响应请求不在@ResponseBody的环境中,不能自动把Bean转换成JSON对象时,可以通过该类创建JSON返回。
package cn.unsoft.utils;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
public class WebUtils
{
/**
* 将字符串渲染到客户端
*
* @param response 渲染对象
* @param string 待渲染的字符串
* @return null
*/
public static void renderString(HttpServletResponse response, String string) {
try
{
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
}
catch (IOException e)
{
e.printStackTrace();
}
}
/**
* 设置下载文件请求
* @param response
*/
public static void downloadFileHandler(HttpServletResponse response, String fileName) throws UnsupportedEncodingException {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码
String name = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + name);
}
}
配置文件类
FastJsonRedisSerializer
用于使用 FastJson 在 Redis中的序列化工具
package cn.unsoft.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.util.Assert;
import java.nio.charset.Charset;
/**
* Redis使用FastJson序列化
*
* @author sg
*/
public class FastJsonRedisSerializer<T> implements RedisSerializer<T>
{
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
static
{
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
public FastJsonRedisSerializer(Class<T> clazz)
{
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException
{
if (t == null)
{
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException
{
if (bytes == null || bytes.length <= 0)
{
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz);
}
protected JavaType getJavaType(Class<?> clazz)
{
return TypeFactory.defaultInstance().constructType(clazz);
}
}
MyBatisPlusConfig
MyBatisPlus的基本配置Bean
package cn.unsoft.config;
import cn.unsoft.utils.SecurityUtils;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionManager;
import javax.sql.DataSource;
import java.util.Date;
/**
* MyBatisPlus 配置类项
* MybatisPlusInterceptor 用于创建一个分页插件,提供分页功能
* <p>
* insertFill 是 MyBatisPlus 提供的字段填充功能,可以填充插入数据库时,某些字段自动填充
* updateFill 是 MyBatisPlus 提供的字段填充功能,可以填充更新数据库时,某些字段自动填充
* 自动填充,需要在实体类的成员中加上 @TableField(fill) 注解
*/
@Configuration
public class MyBatisPlusConfig implements MetaObjectHandler {
/**
* 3.4.0之后版本
*
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
/**
* 对某些字段设定自动数据填充功能
* @param metaObject 由 MyBatisPlus 提供的当前正在处理的数据行中的元数据
* 包含了当前正在处理的数据行的实体类对象等信息
* 包含了处理该数据行的 条件 信息
* 可以在这个metaObject对象中处理需要加工的字段
* 当metaObject被提交出去之后,MP 将按照 metaObject中
* 所提供的实体类数据进行数据库写入操作
*/
@Override
public void insertFill(MetaObject metaObject) {
Long userId = null;
try {
// 如果取不到UserId,说明这个是一个注册账号的请求
userId = SecurityUtils.getUserId();
} catch (Exception e) {
e.printStackTrace();
userId = -1L;//表示是自己创建
}
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("createBy", userId, metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName("updateBy", userId, metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName("updateBy", SecurityUtils.getUserId(), metaObject);
}
/**
* 配置数据库事务管理器
*/
@Autowired
private DataSource dataSource;
@Bean
public TransactionManager transactionManager(){
return new DataSourceTransactionManager(dataSource);
}
}
RedisConfig
Redis 的基本配置Bean
package cn.unsoft.config;
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.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
{
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
WebConfig
配置基本的Web设置,如Date时间转JSON的格式设置,和关闭跨域问题。
package cn.unsoft.config;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.serializer.ToStringSerializer;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 关闭CORS跨域问题
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径
registry.addMapping("/**")
// 设置允许跨域请求的域名
.allowedOriginPatterns("*")
// 是否允许cookie
.allowCredentials(true)
// 设置允许的请求方式
.allowedMethods("GET", "POST", "DELETE", "PUT")
// 设置允许的header属性
.allowedHeaders("*")
// 跨域允许时间
.maxAge(3600);
}
/**
* 注入fastJsonHttpMessageConvert
* 用于解决 Date 类型转成 JSON 时的格式不正确问题
* @return
*/
@Bean
public HttpMessageConverter fastJsonHttpMessageConverters() {
//1.需要定义一个Convert转换消息的对象
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
// 把所有 Long 类型的数据都转换成 String 数据
SerializeConfig.globalInstance.put(Long.class, ToStringSerializer.instance);
fastJsonConfig.setSerializeConfig(SerializeConfig.globalInstance);
fastConverter.setFastJsonConfig(fastJsonConfig);
// HttpMessageConverter<?> converter = fastConverter;
return fastConverter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(fastJsonHttpMessageConverters());
}
}
切面日志类
LogAspect
用于对某些请求进行日志监控的切面类
package cn.unsoft.aop;
import cn.unsoft.annotation.SystemLog;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Component
@Aspect
@Slf4j
public class LogAspect {
/**
* 配置一个切面定位
* 使用 @annotation 定义,则所有使用了 cn.unsoft.annotation.SystemLog 注解的方法
* 都会被包装成一个 aop 切面类
*/
@Pointcut("@annotation(cn.unsoft.annotation.SystemLog)")
public void pointcut() {
}
/**
* 配置触发切面的处理方法
* 难点+重点:
* 1.在切面方法中,需要获取到接入点(被切入)的注册对象,从而获取切面信息 (另开一个方法获取)
* 2.需要获取到该接入点收到的请求对象,从而获取请求信息 (另开一个方法获取)
*/
@Around("pointcut()")
public void log(ProceedingJoinPoint point) throws Throwable {
Object result = null;
SystemLog systemLog = getSystemLogAnno(point);
HttpServletRequest request = getRequestContext();
MethodSignature methodSignature = (MethodSignature) point.getSignature();
log.info("=======Start=======");
try {
result = point.proceed();
// 打印请求 URL
log.info("URL : {}", request.getRequestURL());
// 打印描述信息
log.info("BusinessName : {}", systemLog.businessName());
// 打印 Http method
log.info("HTTP Method : {}", request.getMethod());
// 打印调用 controller 的全路径以及执行方法
log.info("Class Method : {}.{}", point.getSignature().getDeclaringTypeName(), methodSignature.getName());
// 打印请求的 IP
log.info("IP : {}", request.getRemoteHost());
// 打印请求入参
log.info("Request Args : {}", JSON.toJSONString(point.getArgs()));
// 打印出参
log.info("Response : {}", JSON.toJSONString(result));
} finally {
// 结束后换行
log.info("=======End=======" + System.lineSeparator());
}
}
/**
* 获取到请求对象
* 重点:
* 本方法,需要通过 @Around 提供的切面点对象,来获取 HttpServletRequest 的对象
* <p>
* 解释:
* 1.可以通过 RequestContextHolder 来获取当前请求的对象,
* RequestContextHolder 是一个线程安全的全局请求池,在里面可以获取到当前请求的一些相关信息
* <p>
* 2.所获得的 RequestAttributes 对象,是一个接口类型,实现这个接口的实现类非常多,我们可以通过
* 强转成 ServletRequestAttributes 实现类,来获取 HttpServletRequest 对象,因为它包含了
* HttpServletRequest 对象
*
* @return
*/
private HttpServletRequest getRequestContext() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return requestAttributes.getRequest();
}
/**
* 获取到注解对象
* 重点:
* 本方法,需要通过 @Around 提供的切面点对象,来获取接入点的注解
* <p>
* 解释:
* 1.通过 ProceedingJoinPoint 的 getSignature 方法可以获取到接入点的签名信息
* Signature 对象,但是 Signature 是一个接口,而 Signature 接口有多种实现方法
* 因为我们注解使用到方法中,所以可以强转为 MethodSignature 实现类。
* <p>
* 2.通过 MethodSignature 实现类,我们可以获取到详细的与方法有关的信息,如方法名,方法上方的注解
* 我们就可以获取到方法上面的对应注解对象
*
* @return
*/
private SystemLog getSystemLogAnno(ProceedingJoinPoint point) {
MethodSignature method = (MethodSignature) point.getSignature();
SystemLog annotation = method.getMethod().getAnnotation(SystemLog.class);
return annotation;
}
}
字面量规范类
SystemConstant
可以把一些字面量定义为一个常量,可以在开发中规范字面量的含意或语义化,可按需求增加
package cn.unsoft.constants;
public class SystemConstant {
public static final String TOKEN = "token";
}
异常处理类
GlobalExceptionHandler
用于处理除已知异常以处的所有其它异常的处理定义
package cn.unsoft.exception.system;
import cn.unsoft.utils.ResponseResult;
import cn.unsoft.utils.enums.AppHttpCodeEnum;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 设置异常处理 AOP 类
* 本类是用于接收当系统中发生抛出异常时的处理类
* 当系统发生抛出异常时,系统全根据 ExceptionHandler 处理对应异常的方法
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理自定义抛出的异常类
*
* @param e
* @return
*/
@ExceptionHandler(SystemException.class)
public ResponseResult systemException(SystemException e) {
ResponseResult result = ResponseResult.errorResult(e.getCode(), e.getMsg());
return result;
}
/**
* 处理系统全局异常抛出
* 除了指定定义的异常处,其它异常的抛出都会触发这个方法
*
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
public ResponseResult exception(Exception e) {
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(), e.getMessage());
}
}
SystemException
用于自己调用的经过格式封装后的异常类,即如果在开发过程中知道某些地方可能出现异常的地方,可以用于调用它作为返回异常给前端的异常类
package cn.unsoft.exception.system;
import cn.unsoft.utils.enums.AppHttpCodeEnum;
/**
* 用于自定义异常的异常类
* 这个类可以用在系统需要抛出异常的地方,就可以使用
* throw new SystemException(AppHttpCodeEnum.xxx)
*/
public class SystemException extends RuntimeException{
private Integer code;
private String msg;
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
public SystemException(AppHttpCodeEnum httpCodeEnum) {
super(httpCodeEnum.getMsg());
this.code = httpCodeEnum.getCode();
this.msg = httpCodeEnum.getMsg();
}
}
请求过滤器
JwtAuthenticationTokenFilter
当用于携带着由jwt生成的token字串过来时,会经过这个过滤器,这个过滤器被按排在 usernamepassword 过滤器之前,用于拦截请求并判断用户是否已登陆,如果登陆了,则在Redis中取出该UserDetails对象,并识别授权。
package cn.unsoft.filter;
import cn.unsoft.constants.SystemConstant;
import cn.unsoft.domain.entity.LoginUser;
import cn.unsoft.utils.JwtUtil;
import cn.unsoft.utils.RedisCache;
import cn.unsoft.utils.ResponseResult;
import cn.unsoft.utils.WebUtils;
import cn.unsoft.utils.enums.AppHttpCodeEnum;
import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
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.Objects;
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
/**
* 获取token进行验证的过滤器
* 注意:这是一个过滤器,定义完过滤器后,应该要把过滤器加到请求控制中
*
* @param httpServletRequest
* @param httpServletResponse
* @param filterChain
* @throws ServletException
* @throws IOException
*/
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String token = httpServletRequest.getHeader(SystemConstant.TOKEN);
// 如果没有,则直接放行
if (!StringUtils.hasText(token)) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
return;
}
// 如果有,则先jwt解密
Claims claims = null;
try {
claims = JwtUtil.parseJWT(token);
} catch (Exception e) {
// 如果jwt解密有误
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
return;
}
// 如果jwt解密成功
String userid = claims.getSubject();
// 在Redis中取出 LoginUser
LoginUser loginUser = redisCache.getCacheObject("admin::login::" + userid);
if (Objects.isNull(loginUser)) {
// 用户在Redis中不存在,有可能过时了。
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
return;
}
// 如果Redis中存在LoginUser,把LoginUser存到 SecurityContextHolder
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
// 处理完token之后一定要放行
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
Security 配置类
SpringSecurityConfig
对于使用SpringSecurity模块的Bean配置类,用于配置授权异常,哪些api接口需要登陆才能访问等配置
package cn.unsoft.config;
import cn.unsoft.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
// 开启用户权限控制制度,开启后就可以使用 @PreAuthorize() 对请求方法进行权限鉴权操作
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
// 针对前端请求的验证过滤配置
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
String[] anonymousMatchers = {
"/user/login"
};
// 需要验证才能访问的请求 url
String[] authMatchers = {
""
};
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers(anonymousMatchers).anonymous()
// 对于注销接口,需要登陆权限后才能访问
// .antMatchers(authMatchers).authenticated()
// 除上面外的所有请求全部不需要认证即可访问
.anyRequest().authenticated();
// 把验证 token 的过滤器加到请求过滤中,而且是加到 usernamepass 用户登陆请求之前
// 当然,因为没有调用 super.configure() 所以没有加载 usernamepass 用户登陆的模块
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 把认证异常加到请求配置中
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
// 关闭默认注销功能
http.logout().disable();
//允许跨域
http.cors();
}
/**
* 定义密码加密方式
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Security 异常类
AccessDeniedHandlerImpl
对于用户已成功登陆,却访问他不该访问的api请求时,所抛出的异常处理。
package cn.unsoft.filter;
import cn.unsoft.constants.SystemConstant;
import cn.unsoft.domain.entity.LoginUser;
import cn.unsoft.utils.JwtUtil;
import cn.unsoft.utils.RedisCache;
import cn.unsoft.utils.ResponseResult;
import cn.unsoft.utils.WebUtils;
import cn.unsoft.utils.enums.AppHttpCodeEnum;
import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
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.Objects;
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
/**
* 获取token进行验证的过滤器
* 注意:这是一个过滤器,定义完过滤器后,应该要把过滤器加到请求控制中
*
* @param httpServletRequest
* @param httpServletResponse
* @param filterChain
* @throws ServletException
* @throws IOException
*/
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String token = httpServletRequest.getHeader(SystemConstant.TOKEN);
// 如果没有,则直接放行
if (!StringUtils.hasText(token)) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
return;
}
// 如果有,则先jwt解密
Claims claims = null;
try {
claims = JwtUtil.parseJWT(token);
} catch (Exception e) {
// 如果jwt解密有误
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
return;
}
// 如果jwt解密成功
String userid = claims.getSubject();
// 在Redis中取出 LoginUser
LoginUser loginUser = redisCache.getCacheObject("admin::login::" + userid);
if (Objects.isNull(loginUser)) {
// 用户在Redis中不存在,有可能过时了。
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
return;
}
// 如果Redis中存在LoginUser,把LoginUser存到 SecurityContextHolder
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
// 处理完token之后一定要放行
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
AuthenticationEntryPointImpl
对于用户登陆不成功时抛出的异常处理。
package cn.unsoft.exception.security;
import cn.unsoft.utils.ResponseResult;
import cn.unsoft.utils.WebUtils;
import cn.unsoft.utils.enums.AppHttpCodeEnum;
import com.alibaba.fastjson.JSON;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* SpringSecurity 认证失败时的异常处理方法
* 认证失败是指用户账号密码不正确时所产生的异常处理,Spring 会调用 AuthenticationEntryPoint 接口的实现方法
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
/**
* 当用户登陆失败,或访问需要登陆的请求时而没有必需的token时,会触发
* (就是jwt解密token验证类中抛出异常时的调用)
* @param httpServletRequest
* @param httpServletResponse
* @param e
* @throws IOException
* @throws ServletException
*/
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
ResponseResult result = null;
if(e instanceof BadCredentialsException){
// 用户账号密码登陆错误时的异常
result = ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_ERROR.getCode(),e.getMessage());
}else if(e instanceof InsufficientAuthenticationException){
// 用户访问需要登陆才能访问的接口时的异常
result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}else{
// 其它异常
result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(),"认证或授权失败");
}
WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
}
}
YML常用配置
server:
port: 7777
spring:
datasource:
druid: # 数据源
url: jdbc:mysql://localhost:3306/un_blog?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: tzminglove
driver-class-name: com.mysql.cj.jdbc.Driver
servlet: # 文件上传配置
multipart:
max-file-size: 2MB # 最大文件大小
max-request-size: 5MB # 指定每次请求的最大值,默认为10MB
enabled: true # 开启文件上传
redis:
host: localhost
username:
password:
port: 6379
mybatis-plus: # mp配置
configuration:
# 日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: delFlag # 逻辑删除的字段
logic-delete-value: 1 # 删除了的时候的值
logic-not-delete-value: 0 # 没有删除的时候的值
id-type: auto # 设置自增ID规则
mapper-locations: classpath*:/mapper/**/*.xml
# 配置自定义属性参数
oss:
accessKey: # 七牛云ak码
secretKey: # 七牛云sk码
bucket: un-blog # 七牛云存储对象名
domain: "http://rszvkzqrf.hn-bkt.clouddn.com/" # OSS图片服务器域名
整体参考项目下载:
共有 0 条评论