feat(新增): 增加Redis事务,重构项目结构

This commit is contained in:
bunny 2024-09-27 10:39:40 +08:00
parent 391dad3840
commit 9444088b48
115 changed files with 1324 additions and 3234 deletions

View File

@ -40,11 +40,26 @@
<artifactId>redisson</artifactId>
<version>3.26.1</version>
</dependency>
<!-- minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
</dependency>
<!-- 查询ip地址 -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.6.5</version>
</dependency>
<!-- WebSocket -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
</dependency>
<!-- 邮箱 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -19,12 +19,12 @@ public class MyBatisPlusFieldConfig implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 设置属性值
this.strictInsertFill(metaObject, "isDeleted", Integer.class, 0);
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName("deleteStatus", 1, metaObject);
if (BaseContext.getUsername() != null) {
this.setFieldValByName("createBy", BaseContext.getUsername(), metaObject);
this.setFieldValByName("updateBy", BaseContext.getUsername(), metaObject);
this.setFieldValByName("createUser", BaseContext.getUsername(), metaObject);
this.setFieldValByName("updateUser", BaseContext.getUsername(), metaObject);
}
}
@ -33,7 +33,9 @@ public class MyBatisPlusFieldConfig implements MetaObjectHandler {
*/
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName("updateBy", BaseContext.getUsername(), metaObject);
if (BaseContext.getUserId() != null) {
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName("updateUser", BaseContext.getUsername(), metaObject);
}
}
}

View File

@ -45,11 +45,14 @@ public class RedisConfiguration {
redisTemplate.setConnectionFactory(connectionFactory);
// 设置key序列化为string
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 设置value序列化为JSON使用GenericJackson2JsonRedisSerializer替换默认序列化
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
// 开启Redis事务
redisTemplate.setEnableTransactionSupport(true);
return redisTemplate;
}

View File

@ -1,12 +1,10 @@
package cn.bunny.common.service.context;
import cn.bunny.vo.system.login.LoginVo;
import cn.bunny.vo.user.LoginVo;
public class BaseContext {
private static final ThreadLocal<Long> userId = new ThreadLocal<>();
private static final ThreadLocal<String> username = new ThreadLocal<String>();
private static final ThreadLocal<Long> adminId = new ThreadLocal<>();
private static final ThreadLocal<String> adminName = new ThreadLocal<>();
private static final ThreadLocal<LoginVo> loginVo = new ThreadLocal<>();
// 用户id相关
@ -39,26 +37,4 @@ public class BaseContext {
userId.remove();
loginVo.remove();
}
// adminId 相关
public static Long getAdminId() {
return adminId.get();
}
public static void setAdminId(Long _adminId) {
adminId.set(_adminId);
}
public static String getAdminName() {
return adminName.get();
}
public static void setAdminName(String _adminName) {
adminName.set(_adminName);
}
public static void removeAdmin() {
adminName.remove();
adminId.remove();
}
}

View File

@ -1,10 +1,12 @@
package cn.bunny.common.service.exception;
import cn.bunny.pojo.constant.ExceptionConstant;
import cn.bunny.pojo.result.Result;
import cn.bunny.pojo.result.ResultCodeEnum;
import cn.bunny.pojo.result.constant.ExceptionConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@ -12,6 +14,9 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.io.FileNotFoundException;
import java.nio.file.AccessDeniedException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@RestControllerAdvice
@Slf4j
@ -32,18 +37,42 @@ public class GlobalExceptionHandler {
public Result<Object> exceptionHandler(RuntimeException exception) throws FileNotFoundException {
log.error("GlobalExceptionHandler===>运行时异常信息:{}", exception.getMessage());
exception.printStackTrace();
return Result.error(null, 500, "出错了");
return Result.error(null, 500, "出错了");
}
// 捕获系统异常
@ExceptionHandler(Exception.class)
@ResponseBody
public Result<Object> error(Exception exception) {
log.error("GlobalExceptionHandler===>系统异常信息:{}", exception.getMessage());
log.error("捕获系统异常:{}", exception.getMessage());
log.error("GlobalExceptionHandler===>系统异常信息:{}", (Object) exception.getStackTrace());
// 错误消息
String message = exception.getMessage();
// 匹配到内容
String patternString = "Request method '(\\w+)' is not supported";
Matcher matcher = Pattern.compile(patternString).matcher(message);
if (matcher.find()) return Result.error(null, 500, "请求方法错误,不是 " + matcher.group(1));
// 请求API不存在
String noStaticResource = "No static resource (.*)\\.";
Matcher noStaticResourceMatcher = Pattern.compile(noStaticResource).matcher(message);
if (noStaticResourceMatcher.find())
return Result.error(null, 500, "请求API不存在 " + noStaticResourceMatcher.group(1));
// 返回错误内容
return Result.error(null, 500, "系统异常");
}
// 表单验证字段
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<String> handleValidationExceptions(MethodArgumentNotValidException ex) {
log.error("表单验证失败:{}", ex.getMessage());
String errorMessage = ex.getBindingResult().getFieldErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(", "));
return Result.error(null, 201, errorMessage);
}
// 特定异常处理
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
@ -73,10 +102,10 @@ public class GlobalExceptionHandler {
// 截取用户名
String username = message.split(" ")[2];
// 错误信息
String errorMessage = username + ExceptionConstant.ALREADY_USER_Exception;
String errorMessage = username + ExceptionConstant.ALREADY_USER_EXCEPTION;
return Result.error(errorMessage);
} else {
return Result.error(ExceptionConstant.UNKNOWN_Exception);
return Result.error(ExceptionConstant.UNKNOWN_EXCEPTION);
}
}
}

View File

@ -1,13 +1,6 @@
package cn.bunny.common.service.interceptor;
import cn.bunny.common.service.context.BaseContext;
import cn.bunny.common.service.utils.JwtHelper;
import cn.bunny.common.service.utils.ResponseUtil;
import cn.bunny.pojo.result.Result;
import cn.bunny.pojo.result.ResultCodeEnum;
import cn.bunny.pojo.result.constant.RedisUserConstant;
import cn.bunny.vo.system.login.LoginVo;
import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
@ -16,11 +9,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Map;
/**
* * 微服务请求其它模块找不到Token无法从线程中获取值
* 传递请求头在微服务中
@ -34,31 +24,31 @@ public class UserTokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
String token = request.getHeader("token");
Map<String, Object> mapByToken = JwtHelper.getMapByToken(token);
LoginVo loginVo = JSONObject.parseObject(JSONObject.toJSONString(mapByToken), LoginVo.class);
Object redisUserinfo = redisTemplate.opsForValue().get(RedisUserConstant.getUserLoginInfoPrefix(loginVo.getEmail()));
// Map<String, Object> mapByToken = JwtHelper.getMapByToken(token);
// LoginVo loginVo = JSONObject.parseObject(JSONObject.toJSONString(mapByToken), LoginVo.class);
// Object redisUserinfo = redisTemplate.opsForValue().get(RedisUserConstant.getUserLoginInfoPrefix(loginVo.getEmail()));
//
// // 不是动态方法直接返回
// if (!(handler instanceof HandlerMethod)) return true;
//
// // token过期-提示身份验证过期
// if (JwtHelper.isExpired(token)) {
// ResponseUtil.out(response, Result.error(ResultCodeEnum.AUTHENTICATION_EXPIRED));
// return false;
// }
// // 解析不到userId
// if (loginVo.getId() == null) {
// ResponseUtil.out(response, Result.error(ResultCodeEnum.LOGIN_AUTH));
// return false;
// }
// if (redisUserinfo == null) {
// ResponseUtil.out(response, Result.error(ResultCodeEnum.LOGIN_AUTH));
// return false;
// }
// 不是动态方法直接返回
if (!(handler instanceof HandlerMethod)) return true;
// token过期-提示身份验证过期
if (JwtHelper.isExpired(token)) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.AUTHENTICATION_EXPIRED));
return false;
}
// 解析不到userId
if (loginVo.getId() == null) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.LOGIN_AUTH));
return false;
}
if (redisUserinfo == null) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.LOGIN_AUTH));
return false;
}
BaseContext.setUserId(loginVo.getId());
BaseContext.setUsername(loginVo.getEmail());
BaseContext.setLoginVo(loginVo);
// BaseContext.setUserId(loginVo.getId());
// BaseContext.setUsername(loginVo.getEmail());
// BaseContext.setLoginVo(loginVo);
return true;
}

View File

@ -0,0 +1,206 @@
package cn.bunny.common.service.utils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpUtil {
public static HttpResponse doGet(String host, String path, String method, Map<String, String> headers, Map<String, String> querys) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpGet request = new HttpGet(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
return httpClient.execute(request);
}
public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, Map<String, String> bodys) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (bodys != null) {
List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();
for (String key : bodys.keySet()) {
nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
}
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
request.setEntity(formEntity);
}
return httpClient.execute(request);
}
public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, String body) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (StringUtils.isNotBlank(body)) {
request.setEntity(new StringEntity(body, "utf-8"));
}
return httpClient.execute(request);
}
public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, byte[] body) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (body != null) {
request.setEntity(new ByteArrayEntity(body));
}
return httpClient.execute(request);
}
public static HttpResponse doPut(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, String body) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPut request = new HttpPut(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (StringUtils.isNotBlank(body)) {
request.setEntity(new StringEntity(body, "utf-8"));
}
return httpClient.execute(request);
}
public static HttpResponse doPut(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, byte[] body) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPut request = new HttpPut(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (body != null) {
request.setEntity(new ByteArrayEntity(body));
}
return httpClient.execute(request);
}
public static HttpResponse doDelete(String host, String path, String method, Map<String, String> headers, Map<String, String> querys) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpDelete request = new HttpDelete(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
return httpClient.execute(request);
}
private static String buildUrl(String host, String path, Map<String, String> querys) throws UnsupportedEncodingException {
StringBuilder sbUrl = new StringBuilder();
sbUrl.append(host);
if (!StringUtils.isBlank(path)) {
sbUrl.append(path);
}
if (null != querys) {
StringBuilder sbQuery = new StringBuilder();
for (Map.Entry<String, String> query : querys.entrySet()) {
if (!sbQuery.isEmpty()) {
sbQuery.append("&");
}
if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
sbQuery.append(query.getValue());
}
if (!StringUtils.isBlank(query.getKey())) {
sbQuery.append(query.getKey());
if (!StringUtils.isBlank(query.getValue())) {
sbQuery.append("=");
sbQuery.append(URLEncoder.encode(query.getValue(), StandardCharsets.UTF_8));
}
}
}
if (!sbQuery.isEmpty()) {
sbUrl.append("?").append(sbQuery);
}
}
return sbUrl.toString();
}
private static HttpClient wrapClient(String host) {
HttpClient httpClient = new DefaultHttpClient();
if (host.startsWith("https://")) {
sslClient(httpClient);
}
return httpClient;
}
private static void sslClient(HttpClient httpClient) {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] xcs, String str) {
}
public void checkServerTrusted(X509Certificate[] xcs, String str) {
}
};
ctx.init(null, new TrustManager[]{tm}, null);
SSLSocketFactory ssf = new SSLSocketFactory(ctx);
ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ClientConnectionManager ccm = httpClient.getConnectionManager();
SchemeRegistry registry = ccm.getSchemeRegistry();
registry.register(new Scheme("https", 443, ssf));
} catch (Exception ex) {
throw new RuntimeException();
}
}
}

View File

@ -0,0 +1,21 @@
package cn.bunny.common.service.utils.ip;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ApiModel(value = "IpEntity对象", description = "用户IP相关信息")
public class IpEntity {
@ApiModelProperty("原始地址")
private String remoteAddr;
@ApiModelProperty("IP归属地")
private String ipRegion;
}

View File

@ -0,0 +1,99 @@
package cn.bunny.common.service.utils.ip;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
public class IpUtil {
private static Searcher searcher;
/**
* 判断是否为合法 IP
*/
public static boolean checkIp(String ipAddress) {
String ip = "([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}";
Pattern pattern = Pattern.compile(ip);
Matcher matcher = pattern.matcher(ipAddress);
return matcher.matches();
}
/**
* 在服务启动时 ip2region 加载到内存中
*/
@PostConstruct
private static void initIp2Region() {
try {
InputStream inputStream = new ClassPathResource("/ipdb/ip2region.xdb").getInputStream();
byte[] bytes = FileCopyUtils.copyToByteArray(inputStream);
searcher = Searcher.newWithBuffer(bytes);
} catch (Exception exception) {
log.error("ip转换错误消息{}", exception.getMessage());
log.error("ip转换错误栈{}", (Object) exception.getStackTrace());
}
}
/**
* 获取 ip 所属地址
*
* @param ip ip
*/
public static String getIpRegion(String ip) {
if (ip.equals("0:0:0:0:0:0:0:1")) ip = "127.0.0.1";
boolean isIp = checkIp(ip);
if (isIp) {
initIp2Region();
try {
// searchIpInfo 的数据格式 国家|区域|省份|城市|ISP
String searchIpInfo = searcher.search(ip);
String[] splitIpInfo = searchIpInfo.split("\\|");
if (splitIpInfo.length > 0) {
if ("中国".equals(splitIpInfo[0])) {
// 国内属地返回省份
return splitIpInfo[2];
} else if ("0".equals(splitIpInfo[0])) {
if ("内网IP".equals(splitIpInfo[4])) {
// 内网 IP
return splitIpInfo[4];
} else {
return "";
}
} else {
// 国外属地返回国家
return splitIpInfo[0];
}
}
} catch (Exception exception) {
log.error("获取 ip 所属地址消息:{}", exception.getMessage());
log.error("获取 ip 所属地址:{}", (Object) exception.getStackTrace());
}
return "";
} else {
throw new IllegalArgumentException("非法的IP地址");
}
}
/**
* * 获取当前用户登录IP地址
*
* @return IP地址
*/
public static IpEntity getCurrentUserIpAddress() {
// 获取用户IP地址
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String remoteAddr = requestAttributes != null ? requestAttributes.getRequest().getRemoteAddr() : "0:0:0:0:0:0:0:1";
String ipRegion = IpUtil.getIpRegion(remoteAddr);
return IpEntity.builder()
.remoteAddr(remoteAddr)
.ipRegion(ipRegion)
.build();
}
}

View File

@ -1,8 +1,8 @@
package cn.bunny.module.mail.utils;
package cn.bunny.common.service.utils.mail;
import cn.bunny.common.service.utils.EmptyUtil;
import cn.bunny.pojo.email.EmailSend;
import cn.bunny.pojo.result.constant.MailMessageConstant;
import cn.bunny.pojo.common.EmailSend;
import cn.bunny.pojo.constant.MailMessageConstant;
public class MailSendCheckUtil {
/**

View File

@ -1,7 +1,7 @@
package cn.bunny.module.mail.utils;
package cn.bunny.common.service.utils.mail;
import cn.bunny.pojo.email.EmailSend;
import cn.bunny.pojo.email.EmailSendInit;
import cn.bunny.pojo.common.EmailSend;
import cn.bunny.pojo.common.EmailSendInit;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import org.springframework.mail.SimpleMailMessage;
@ -43,6 +43,7 @@ public class MailSenderUtil {
MimeMessage message = javaMailSender.createMimeMessage();
// 创建 MimeMessageHelper
MimeMessageHelper helper = new MimeMessageHelper(message, true);
String ccParam = emailSend.getCcParam();
// 设置发送人
helper.setFrom(username);
@ -53,11 +54,15 @@ public class MailSenderUtil {
// 设置发送消息 为富文本
helper.setText(emailSend.getMessage(), emailSend.getIsRichText());
// 设置抄送人
helper.setCc(emailSend.getCcParam().split(","));
if (ccParam != null) {
helper.setCc(ccParam.split(","));
}
// 邮件添加附件
MultipartFile[] files = emailSend.getFile();
for (MultipartFile file : files) {
helper.addAttachment(Objects.requireNonNull(file.getOriginalFilename()), file);
if (files != null) {
for (MultipartFile file : files) {
helper.addAttachment(Objects.requireNonNull(file.getOriginalFilename()), file);
}
}
// 发送邮件

View File

@ -1,3 +1,5 @@
Configuration example
mail:
host: smtp.qq.com # 邮箱地址
port: 465 # 邮箱端口号

View File

@ -1,4 +1,4 @@
package cn.bunny.module.minio.properties;
package cn.bunny.common.service.utils.minio;
import io.minio.MinioClient;
import lombok.Data;
@ -21,7 +21,6 @@ public class MinioProperties {
@Bean
public MinioClient minioClient() {
log.info("注册MinioClient...");
return MinioClient.builder().endpoint(endpointUrl).credentials(accessKey, secretKey).build();
}
}

View File

@ -0,0 +1,142 @@
package cn.bunny.common.service.utils.minio;
import cn.bunny.common.service.exception.BunnyException;
import cn.bunny.pojo.common.MinioFIlePath;
import cn.bunny.pojo.constant.MinioConstant;
import cn.bunny.pojo.result.ResultCodeEnum;
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
/**
* Minio操作工具类 简化操作步骤
* ByBunny0212
*/
@Component
@Slf4j
public class MinioUtil {
@Autowired
private MinioProperties properties;
@Autowired
private MinioClient minioClient;
/**
* 获取Minio文件路径
*/
public static MinioFIlePath getMinioFilePath(String buckName, String minioPreType, MultipartFile file) {
String uuid = UUID.randomUUID().toString();
// 定义日期时间格式
LocalDateTime currentDateTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM-dd");
String extension = "";
// 原始文件名
String filename = file.getOriginalFilename();
if (StringUtils.hasText(filename) && filename.contains(".")) {
extension = "." + filename.substring(filename.lastIndexOf(".") + 1);
}
// UUID防止重名
String uuidFilename = uuid + extension;
// 拼接时间+UUID文件名
String timeUuidFilename = currentDateTime.format(formatter) + "/" + uuidFilename;// 加上时间路径
// 上传根文件夹+拼接时间+UUID文件名
String filepath = MinioConstant.getType(minioPreType) + timeUuidFilename;
// 桶名称+上传根文件夹+拼接时间+UUID文件名
String buckNameFilepath = "/" + buckName + MinioConstant.getType(minioPreType) + timeUuidFilename;
// 设置及Minio基础信息
MinioFIlePath minioFIlePath = new MinioFIlePath();
minioFIlePath.setFilename(filename);
minioFIlePath.setUuidFilename(uuidFilename);
minioFIlePath.setTimeUuidFilename(timeUuidFilename);
minioFIlePath.setFilepath(filepath);
minioFIlePath.setBucketNameFilepath(buckNameFilepath);
return minioFIlePath;
}
/**
* * 上传文件并返回处理信息
*/
public MinioFIlePath getUploadMinioObjectFilePath(MultipartFile file, String minioPreType) throws IOException {
// 如果buckName为空设置为默认的桶
String bucketName = properties.getBucketName();
if (file != null) {
MinioFIlePath minioFile = getMinioFilePath(bucketName, minioPreType, file);
String filepath = minioFile.getFilepath();
// 上传对象
putObject(bucketName, filepath, file.getInputStream(), file.getSize());
// 设置图片地址
return minioFile;
}
return null;
}
/**
* 获取默认bucket文件并返回字节数组
*
* @param objectName 对象名称
* @return 文件流对象
*/
public byte[] getBucketObjectByte(String objectName) {
// 如果buckName为空设置为默认的桶
String bucketName = properties.getBucketName();
try {
objectName = objectName.replace("/" + bucketName, "");
GetObjectResponse getObjectResponse = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
return getObjectResponse.readAllBytes();
} catch (Exception exception) {
exception.getStackTrace();
}
throw new BunnyException(ResultCodeEnum.GET_BUCKET_EXCEPTION);
}
/**
* 获取Minio全路径名Object带有桶名称
*
* @param objectName 对象名称
* @return 全路径
*/
public String getObjectNameFullPath(String objectName) {
String url = properties.getEndpointUrl();
return url + objectName;
}
/**
* 上传文件
*
* @param bucketName 桶名称
* @param filename 文件名
* @param inputStream 输入流
* @param size 大小
*/
public void putObject(String bucketName, String filename, InputStream inputStream, Long size) {
try {
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(filename).stream(inputStream, size, -1).build());
} catch (Exception exception) {
log.error("上传文件失败:{}", (Object) exception.getStackTrace());
throw new BunnyException(ResultCodeEnum.UPDATE_ERROR);
}
}
}

View File

@ -1,5 +1,7 @@
package cn.bunny.dto.email;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -9,13 +11,21 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "EmailTemplateDto", title = "邮箱模板请求内容", description = "邮箱模板请求内容")
public class EmailTemplateDto {
// 模板名称
@Schema(name = "templateName", title = "模板名称")
@NotBlank(message = "模板名称不能为空")
private String templateName;
// 主题
@Schema(name = "subject", title = "主题")
@NotBlank(message = "主题不能为空")
private String subject;
// 邮件内容
@Schema(name = "body", title = "邮件内容")
@NotBlank(message = "邮件内容不能为空")
private String body;
// 邮件类型
@Schema(name = "type", title = "邮件类型")
private String type;
}

View File

@ -1,5 +1,8 @@
package cn.bunny.dto.email;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -12,19 +15,31 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "EmailUsersDto", title = "邮箱用户发送基础内容", description = "邮箱用户发送基础内容")
public class EmailUsersDto {
// 修改时需要传
@Schema(name = "id", title = "主键")
@NotBlank(message = "id不能为空")
private Long id;
// 邮箱
@Schema(name = "email", title = "邮箱")
@NotBlank(message = "邮箱不能为空")
private String email;
// 密码
@Schema(name = "password", title = "密码")
@NotBlank(message = "密码不能为空")
private String password;
// SMTP服务器
@Schema(name = "host", title = "SMTP服务器")
private String host;
// 端口号
@Schema(name = "port", title = "端口号")
@NotNull(message = "端口号不能为空")
private Integer port;
// 邮箱协议
@Schema(name = "smtpAgreement", title = "邮箱协议")
private Integer smtpAgreement;
// 是否为默认邮件
@Schema(name = "isDefault", title = "是否为默认邮件")
private Boolean isDefault;
}

View File

@ -1,5 +1,7 @@
package cn.bunny.dto.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -9,11 +11,22 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "LoginDto", title = "登录表单内容", description = "登录表单内容")
public class LoginDto {
// 用户名
@Schema(name = "username", title = "用户名")
@NotBlank(message = "用户名不能为空")
private String username;
// 密码
@Schema(name = "password", title = "密码")
@NotBlank(message = "密码不能为空")
private String password;
// 邮箱验证码
@Schema(name = "emailCode", title = "邮箱验证码")
@NotBlank(message = "邮箱验证码不能为空")
private String emailCode;
@Schema(name = "readMeDay", title = "记住我的天数")
private Long readMeDay = 1L;
}

View File

@ -1,4 +1,4 @@
package cn.bunny.entity.base;
package cn.bunny.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;

View File

@ -0,0 +1,56 @@
package cn.bunny.entity.system;
import cn.bunny.entity.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* <p>
* 管理员用户信息
* </p>
*
* @author Bunny
* @since 2024-06-26
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName("sys_user")
@Schema(name = "AdminUser对象", title = "用户信息", description = "用户信息")
public class AdminUser extends BaseEntity {
@Schema(name = "username", title = "用户名")
private String username;
@Schema(name = "nickName", title = "昵称")
private String nickName;
@Schema(name = "email", title = "邮箱")
private String email;
@Schema(name = "phone", title = "手机号")
private String phone;
@Schema(name = "password", title = "密码")
private String password;
@Schema(name = "avatar", title = "头像")
private String avatar;
@Schema(name = "sex", title = "性别", description = "0:女 1:男")
private Byte sex;
@Schema(name = "summary", title = "个人描述")
private String summary;
@Schema(name = "lastLoginIp", title = "最后登录IP")
private String lastLoginIp;
@Schema(name = "lastLoginIpAddress", title = "最后登录ip归属地")
private String lastLoginIpAddress;
@Schema(name = "status", title = "状态", description = "1:禁用 0:正常")
private Byte status;
}

View File

@ -0,0 +1,38 @@
package cn.bunny.entity.system;
import cn.bunny.entity.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* <p>
*
* </p>
*
* @author Bunny
* @since 2024-05-19
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName("sys_email_template")
@Schema(name = "EmailTemplate对象", title = "邮件模板表", description = "邮件模板表")
public class EmailTemplate extends BaseEntity {
@Schema(name = "templateName", title = "模板名称")
private String templateName;
@Schema(name = "subject", title = "主题")
private String subject;
@Schema(name = "body", title = "邮件内容")
private String body;
@Schema(name = "type", title = "邮件类型")
private String type;
@Schema(name = "isDefault", title = "是否默认")
private Boolean isDefault;
}

View File

@ -0,0 +1,45 @@
package cn.bunny.entity.system;
import cn.bunny.entity.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* <p>
* 邮箱发送表
* </p>
*
* @author Bunny
* @since 2024-05-17
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName("sys_email_users")
@Schema(name = "EmailUsers对象", title = "邮箱发送表", description = "邮箱发送表")
public class EmailUsers extends BaseEntity {
@Schema(name = "email", title = "邮箱")
private String email;
@Schema(name = "emailTemplate", title = "使用邮件模板")
private Long emailTemplate;
@Schema(name = "password", title = "密码")
private String password;
@Schema(name = "host", title = "Host地址")
private String host;
@Schema(name = "port", title = "端口号")
private Integer port;
@Schema(name = "smtpAgreement", title = "邮箱协议")
private String smtpAgreement;
@Schema(name = "isDefault", title = "是否为默认邮件")
private Byte isDefault;
}

View File

@ -1,4 +1,4 @@
package cn.bunny.entity.system.log;
package cn.bunny.entity.system;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;

View File

@ -1,58 +0,0 @@
package cn.bunny.entity.system.admin;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
*
* </p>
*
* @author Bunny
* @since 2024-05-18
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("admin_power")
@ApiModel(value = "AdminPower对象", description = "")
public class AdminPower implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ApiModelProperty("权限ID")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty("权限名称")
private String powerName;
@ApiModelProperty("权限编码")
private String powerCode;
@ApiModelProperty("描述")
private String description;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
@ApiModelProperty("更新用户")
private String updateUser;
@ApiModelProperty("是否删除0-未删除1-已删除")
private Byte isDelete;
}

View File

@ -1,55 +0,0 @@
package cn.bunny.entity.system.admin;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
*
* </p>
*
* @author Bunny
* @since 2024-05-18
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("admin_role")
@ApiModel(value = "AdminRole对象", description = "")
public class AdminRole implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private String id;
@ApiModelProperty("角色名称")
private String roleName;
@ApiModelProperty("描述")
private String description;
@ApiModelProperty("角色代码")
private String roleCode;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
@ApiModelProperty("操作用户")
private String updateUser;
@ApiModelProperty("是否删除")
private Byte isDeleted;
}

View File

@ -1,56 +0,0 @@
package cn.bunny.entity.system.admin;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
*
* </p>
*
* @author Bunny
* @since 2024-05-18
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("admin_role_power")
@ApiModel(value = "AdminRolePower对象", description = "")
public class AdminRolePower implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("ID")
@TableId(value = "id", type = IdType.AUTO)
private String id;
@ApiModelProperty("角色id")
private String roleId;
@ApiModelProperty("权限id")
private String powerId;
@ApiModelProperty("描述")
private String description;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
@ApiModelProperty("更新用户")
private String updateUser;
@ApiModelProperty("是否删除0-未删除1-已删除")
private Byte isDelete;
}

View File

@ -1,56 +0,0 @@
package cn.bunny.entity.system.admin;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
*
* </p>
*
* @author Bunny
* @since 2024-05-18
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("admin_user_role")
@ApiModel(value = "AdminUserRole对象", description = "")
public class AdminUserRole implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("ID")
@TableId(value = "id", type = IdType.AUTO)
private String id;
@ApiModelProperty("用户id")
private String userId;
@ApiModelProperty("角色id")
private String roleId;
@ApiModelProperty("描述")
private String description;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
@ApiModelProperty("更新用户")
private String updateUser;
@ApiModelProperty("是否删除0-未删除1-已删除")
private Byte isDelete;
}

View File

@ -1,21 +0,0 @@
package cn.bunny.entity.system.admin.auth;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AuthUserRole {
// 用户id
private Long userId;
// 角色id
private Long roleId;
// 角色代码
private String roleCode;
// 描述
private String roleDescription;
}

View File

@ -1,42 +0,0 @@
package cn.bunny.entity.system.email;
import cn.bunny.entity.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serial;
import java.io.Serializable;
/**
* <p>
*
* </p>
*
* @author Bunny
* @since 2024-05-19
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("email_template")
@ApiModel(value = "EmailTemplate对象", description = "邮件模板")
public class EmailTemplate extends BaseEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ApiModelProperty("模板名称")
private String templateName;
@ApiModelProperty("主题")
private String subject;
@ApiModelProperty("邮件内容")
private String body;
@ApiModelProperty("邮件类型")
private String type;
}

View File

@ -1,49 +0,0 @@
package cn.bunny.entity.system.email;
import cn.bunny.entity.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serial;
import java.io.Serializable;
/**
* <p>
* 邮箱发送表
* </p>
*
* @author Bunny
* @since 2024-05-17
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("email_users")
@ApiModel(value = "EmailUsers对象", description = "邮箱发送表")
public class EmailUsers extends BaseEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ApiModelProperty("邮箱")
private String email;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("Host地址")
private String host;
@ApiModelProperty("端口号")
private Integer port;
@ApiModelProperty("邮箱协议")
private String smtpAgreement;
@ApiModelProperty("是否为默认邮件")
private Integer isDefault;
}

View File

@ -1,61 +0,0 @@
package cn.bunny.entity.system.user;
import cn.bunny.entity.base.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 用户信息
* </p>
*
* @author Bunny
* @since 2024-05-17
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@ApiModel(value = "User对象", description = "用户信息")
public class User extends BaseEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ApiModelProperty("昵称")
private String nickName;
@ApiModelProperty("邮箱")
private String email;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("头像")
private String avatar;
@ApiModelProperty("0:女 1:男")
private Byte sex;
@ApiModelProperty("个人描述")
private String personDescription;
@ApiModelProperty("加入时间")
private LocalDateTime joinTime;
@ApiModelProperty("最后登录时间")
private LocalDateTime lastLoginTime;
@ApiModelProperty("最后登录IP")
private String lastLoginIp;
@ApiModelProperty("最后登录ip地址")
private String lastLoginIpAddress;
@ApiModelProperty("积分")
private Integer totalIntegral;
@ApiModelProperty("当前积分")
private Integer currentIntegral;
@ApiModelProperty("0:禁用 1:正常")
private Byte status;
}

View File

@ -1,4 +1,4 @@
package cn.bunny.pojo.email;
package cn.bunny.pojo.common;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

@ -1,4 +1,4 @@
package cn.bunny.pojo.email;
package cn.bunny.pojo.common;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

@ -1,4 +1,4 @@
package cn.bunny.pojo.file;
package cn.bunny.pojo.common;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

@ -0,0 +1,22 @@
package cn.bunny.pojo.constant;
import lombok.Data;
@Data
public class ExceptionConstant {
public static final String UNKNOWN_EXCEPTION = "未知错误";
// 用户相关
public static final String USER_NOT_LOGIN_EXCEPTION = "用户未登录";
public static final String USERNAME_IS_EMPTY_EXCEPTION = "用户名不能为空";
public static final String ALREADY_USER_EXCEPTION = "用户已存在";
public static final String USER_NOT_FOUND_EXCEPTION = "用户不存在";
// 密码相关
public static final String PASSWORD_EXCEPTION = "密码错误";
public static final String PASSWORD_NOT_EMPTY_EXCEPTION = "密码不能为空";
public static final String OLD_PASSWORD_EXCEPTION = "旧密码不匹配";
public static final String PASSWORD_EDIT_EXCEPTION = "密码修改失败";
public static final String OLD_PASSWORD_SAME_NEW_PASSWORD_EXCEPTION = "旧密码与新密码相同";
}

View File

@ -0,0 +1,10 @@
package cn.bunny.pojo.constant;
import lombok.Data;
@Data
public class LocalDateTimeConstant {
public static final String YYYY_MM_DD = "yyyy-MM-dd";
public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
public static final String YYYY_MM_DD_HH_MM_SS_SLASH = "yyyy/MM/dd HH:mm:ss";
}

View File

@ -1,4 +1,4 @@
package cn.bunny.pojo.result.constant;
package cn.bunny.pojo.constant;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package cn.bunny.pojo.result.constant;
package cn.bunny.pojo.constant;
import lombok.Data;
@ -34,6 +34,6 @@ public class MinioConstant {
public static String getType(String type) {
String value = typeMap.get(type);
if (value != null) return value;
throw new RuntimeException(FileMessageConstant.COMPOSE_OBJECT_EXCEPTION);
throw new RuntimeException("上传类型错误或缺失");
}
}

View File

@ -0,0 +1,34 @@
package cn.bunny.pojo.constant;
import lombok.Data;
/**
* Redis用户前缀设置
*/
@Data
public class RedisUserConstant {
// 过期时间
public static final Long REDIS_EXPIRATION_TIME = 15L;// 15 /分钟 Redis过期
public static final Integer Cookie_EXPIRATION_TIME = 5 * 60 * 60;// cookies 过期时间 5 分钟
private static final String ADMIN_LOGIN_INFO_PREFIX = "admin::login_info::";
private static final String ADMIN_EMAIL_CODE_PREFIX = "admin::email_code::";
private static final String USER_LOGIN_INFO_PREFIX = "user::login_info::";
private static final String USER_EMAIL_CODE_PREFIX = "user::email_code::";
public static String getAdminLoginInfoPrefix(String adminUser) {
return ADMIN_LOGIN_INFO_PREFIX + adminUser;
}
public static String getAdminUserEmailCodePrefix(String adminUser) {
return ADMIN_EMAIL_CODE_PREFIX + adminUser;
}
public static String getUserLoginInfoPrefix(String user) {
return USER_LOGIN_INFO_PREFIX + user;
}
public static String getUserEmailCodePrefix(String user) {
return USER_EMAIL_CODE_PREFIX + user;
}
}

View File

@ -1,8 +1,9 @@
package cn.bunny.pojo.result.constant;
package cn.bunny.pojo.constant;
import lombok.Data;
@Data
public class UserConstant {
public static final String USER_AVATAR = "https://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132";
public static final String PERSON_DESCRIPTION = "这个人很懒没有介绍...";
}

View File

@ -11,36 +11,44 @@ public enum ResultCodeEnum {
SUCCESS(200, "操作成功"),
SUCCESS_LOGOUT(200, "退出成功"),
EMAIL_CODE_REFRESH(200, "邮箱验证码已刷新"),
// 验证错误 201
USERNAME_NOT_EMPTY(201, "用户名不能为空"),
PASSWORD_NOT_EMPTY(201, "密码不能为空"),
USERNAME_OR_PASSWORD_NOT_EMPTY(201, "用户名或密码不能为空"),
EMAIL_CODE_NOT_EMPTY(201, "邮箱验证码不能为空"),
SEND_EMAIL_CODE_NOT_EMPTY(201, "请先发送邮箱验证码"),
EMAIL_CODE_NOT_MATCHING(201, "邮箱验证码不匹配"),
LOGIN_ERROR(201, "账号或密码错误"),
LOGIN_ERROR_USERNAME_PASSWORD_NOT_EMPTY(201, "登录信息不能为空"),
GET_BUCKET_EXCEPTION(201, "获取文件信息失败"),
// 数据相关 206
ILLEGAL_REQUEST(206, "非法请求"),
REPEAT_SUBMIT(206, "重复提交"),
DATA_ERROR(206, "数据异常"),
// 身份过期 208
LOGIN_AUTH(208, "请先登陆"),
AUTHENTICATION_EXPIRED(208, "身份验证过期"),
SESSION_EXPIRATION(208, "会话过期"),
// 封禁 209
FAIL_NO_ACCESS_DENIED_USER_LOCKED(209, "该账户被封禁"),
THE_SAME_USER_HAS_LOGGED_IN(209, "相同用户已登录"),
// 提示错误
URL_ENCODE_ERROR(216, "URL编码失败"),
ILLEGAL_CALLBACK_REQUEST_ERROR(217, "非法回调请求"),
FETCH_USERINFO_ERROR(219, "获取用户信息失败"),
// 无权访问 403
FAIL_REQUEST_NOT_AUTH(403, "用户未认证"),
FAIL_NO_ACCESS_DENIED(403, "无权访问"),
FAIL_NO_ACCESS_DENIED_USER_OFFLINE(403, "用户强制下线"),
LOGGED_IN_FROM_ANOTHER_DEVICE(403, "没有权限访问"),
// 系统错误 500
SERVICE_ERROR(500, "服务异常"),
UPDATE_ERROR(500, "上传文件失败"),
FAIL(500, "失败"),
;

View File

@ -1,52 +0,0 @@
package cn.bunny.pojo.result.constant;
import lombok.Data;
@Data
public class ExceptionConstant {
public static final String UNKNOWN_Exception = "未知错误";
public static final String TOKEN_IS_EMPTY = "token为空";
public static final String DATA_IS_EMPTY = "数据为空";
public static final String REQUEST_DATA_NOT_EMPTY_Exception = "请求参数为空";
public static final String UPDATE_DTO_IS_NULL_Exception = "修改参数为空";
public static final String ADD_DATA_IS_EMPTY_Exception = "添加数据为空";
public static final String DELETE_ID_IS_NOT_EMPTY_Exception = "删除id不能为空";
// 文章操作相关
public static final String DO_LIKE_COMMENT_NOT_EXIST = "点赞内容不存在";
public static final String REPLY_USER_EMPTY_EXCEPTION = "回复的用户不存在";
public static final String REPLY_USER_ID_EMPTY_EXCEPTION = "回复的用户不能为空";
public static final String MENU_IS_NOT_EXIST_Exception = "菜单不存在";
public static final String POST_COMMENT_EMPTY_Exception = "评论内容不能为空";
public static final String ARTICLE_ID_NOT_EMPTY_Exception = "文章id不能为空";
public static final String UPDATE_ID_IS_NOT_EMPTY_Exception = "修改id不能为空";
public static final String CANNOT_TOP_OTHER_USER = "不能操作此内容";
public static final String ARTICLE_NOT_FOUND_EXCEPTION = "文章未找到";
// 登录相关
public static final String USER_TOKEN_OUT_OF_DATE_Exception = "用户登录过期";
public static final String LOGIN_DTO_IS_EMPTY_Exception = "登录参数不能为空";
public static final String LOGIN_FAILED_Exception = "登录失败";
// 账号相关
public static final String ACCOUNT_NOT_FOUND_Exception = "账号不存在";
public static final String ACCOUNT_LOCKED_Exception = "账号被锁定";
// 用户相关
public static final String USER_NOT_LOGIN_Exception = "用户未登录";
public static final String USERNAME_IS_EMPTY_Exception = "用户名不能为空";
public static final String ALREADY_USER_Exception = "用户已存在";
public static final String USER_NOT_FOUND_Exception = "用户不存在";
// 密码相关
public static final String PASSWORD_Exception = "密码错误";
public static final String PASSWORD_NOT_EMPTY_Exception = "密码不能为空";
public static final String OLD_PASSWORD_Exception = "旧密码不匹配";
public static final String PASSWORD_EDIT_Exception = "密码修改失败";
public static final String OLD_PASSWORD_SAME_NEW_PASSWORD_Exception = "旧密码与新密码相同";
// 验证码错误
public static final String PLEASE_SEND_EMAIL_CODE_Exception = "请先发送验证码";
public static final String MESSAGE_CODE_NOT_PASS_Exception = "短信验证码未过期";
public static final String MESSAGE_CODE_UNAUTHORIZED_Exception = "短信验证码未授权,请联系管理员";
public static final String VERIFICATION_CODE_ERROR_Exception = "验证码错误";
public static final String CAPTCHA_IS_EMPTY_Exception = "验证码不能为空";
public static final String KEY_IS_EMPTY_Exception = "验证码key不能为空";
public static final String VERIFICATION_CODE_DOES_NOT_MATCH_Exception = "验证码不匹配";
public static final String VERIFICATION_CODE_IS_EMPTY_Exception = "验证码失效或不存在";
}

View File

@ -1,21 +0,0 @@
package cn.bunny.pojo.result.constant;
import lombok.Data;
@Data
public class FileMessageConstant {
public static final String DOWNLOAD_BUCKET_EXCEPTION = "下载文件失败";
public static final String FILE_UPLOAD_EXCEPTION = "文件上传失败";
public static final String BUCKET_EXISTS_EXCEPTION = "查询文件对象失败";
public static final String DELETE_BUCKET_EXCEPTION = "删除文件对象失败";
public static final String FILE_IS_EMPTY = "文件信息为空";
public static final String FILE_IS_NOT_EXITS = "文件信息为空";
public static final String GET_BUCKET_EXCEPTION = "获取文件信息失败";
public static final String QUERY_BUCKET_EXCEPTION = "查询文件信息失败";
public static final String CREATE_BUCKET_EXCEPTION = "创建文件对象失败";
public static final String UPDATE_BUCKET_EXCEPTION = "更新文件对象失败";
public static final String COMPOSE_OBJECT_EXCEPTION = "对象错误";
public static final String COPY_BUCKET_EXCEPTION = "复制文件内容失败";
public static final String DISABLE_BUCKET_EXCEPTION = "禁用文件失败";
public static final String ENABLE_BUCKET_EXCEPTION = "启用文件失败";
}

View File

@ -1,11 +0,0 @@
package cn.bunny.pojo.result.constant;
import lombok.Data;
@Data
public class LocalDateTimeConstant {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
public static final String DEFAULT_DATE_TIME_SECOND_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
}

View File

@ -1,67 +0,0 @@
package cn.bunny.pojo.result.constant;
import lombok.Data;
/**
* Redis用户前缀设置
*/
@Data
public class RedisUserConstant {
// 管理员用户
public static final String ADMIN_LOGIN_INFO_PREFIX = "ADMIN::LOGIN_INFO::";
public static final String ADMIN_EMAIL_CODE_PREFIX = "ADMIN::EMAIL_CODE::";
// 普通用户
public static final String USER_LOGIN_INFO_PREFIX = "USER::LOGIN_INFO::";
public static final String USER_EMAIL_CODE_PREFIX = "USER::EMAIL_CODE::";
public static final String USER_DO_LIKE_PREFIX = "USER::doLike::";
/**
* * 管理员用户登录信息
*
* @param adminUser 管理员用户
* @return 登录信息key
*/
public static String getAdminLoginInfoPrefix(String adminUser) {
return ADMIN_LOGIN_INFO_PREFIX + adminUser;
}
/**
* * 管理员用户邮箱验证码
*
* @param adminUser 管理员用户
* @return 管理员用户邮箱验证码key
*/
public static String getAdminUserEmailCodePrefix(String adminUser) {
return ADMIN_EMAIL_CODE_PREFIX + adminUser;
}
/**
* * 用户登录信息
*
* @param user 用户名
* @return 登录信息key
*/
public static String getUserLoginInfoPrefix(String user) {
return USER_LOGIN_INFO_PREFIX + user;
}
/**
* * 用户邮箱验证码
*
* @param user 用户名
* @return 用户邮箱验证码key
*/
public static String getUserEmailCodePrefix(String user) {
return USER_EMAIL_CODE_PREFIX + user;
}
/**
* * 用户点赞操作
*
* @param user 用户名
* @return 用户点赞key
*/
public static String getUserDoLikePrefix(String user) {
return USER_DO_LIKE_PREFIX + user;
}
}

View File

@ -1,14 +0,0 @@
package cn.bunny.pojo.result.constant;
import lombok.Data;
/**
* 数据库中自动填充字段
*/
@Data
public class SQLAutoFillConstant {
public static final String SET_CREATE_TIME = "setCreateTime";
public static final String SET_UPDATE_TIME = "setUpdateTime";
public static final String SET_CREATE_USER = "setCreateUser";
public static final String SET_UPDATE_USER = "setUpdateUser";
}

View File

@ -1,13 +0,0 @@
package cn.bunny.pojo.result.constant;
import lombok.Data;
import java.util.Arrays;
import java.util.List;
@Data
public class SecurityConstant {
public static String[] annotations = {"/", "/test/**", "/diagram-viewer/**", "/editor-app/**", "/*.html",
"/*/*/noAuth/**", "/*/noAuth/**", "/favicon.ico", "/swagger-resources/**", "/webjars/**", "/v3/**", "/swagger-ui.html/**", "/doc.html"};
public static List<String> annotationsList = Arrays.asList(annotations);
}

View File

@ -1,14 +0,0 @@
package cn.bunny.pojo.result.constant;
import lombok.Data;
/**
* 状态常量启用或者禁用
*/
@Data
public class StatusConstant {
// 启用为1
public static final Integer ENABLE = 1;
// 禁用为0
public static final Integer DISABLE = 0;
}

View File

@ -1,11 +0,0 @@
package cn.bunny.pojo.tree;
import java.util.List;
public interface AbstractTreeNode {
Long getId();
Long getParentId();
void setChildren(List<? extends AbstractTreeNode> children);
}

View File

@ -1,29 +0,0 @@
package cn.bunny.pojo.tree;
import java.util.ArrayList;
import java.util.List;
public class TreeBuilder<T extends AbstractTreeNode> {
public List<T> buildTree(List<T> nodeList) {
List<T> tree = new ArrayList<>();
for (T node : nodeList) {
if (node.getParentId() == 0) {
node.setChildren(getChildren(node.getId(), nodeList));
tree.add(node);
}
}
return tree;
}
private List<T> getChildren(Long nodeId, List<T> nodeList) {
List<T> children = new ArrayList<>();
for (T node : nodeList) {
if (node.getParentId().equals(nodeId)) {
node.setChildren(getChildren(node.getId(), nodeList));
children.add(node);
}
}
return children;
}
}

View File

@ -0,0 +1,47 @@
package cn.bunny.vo;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@Schema(name = "BaseVo", title = "基础返回对象内容", description = "基础返回对象内容")
public class BaseVo implements Serializable {
@Schema(name = "id", title = "主键")
@JsonProperty("id")
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Long id;
@Schema(name = "updateTime", title = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime updateTime;
@Schema(name = "createTime", title = "发布时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime createTime;
@Schema(name = "createUser", title = "创建用户")
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Long createUser;
@Schema(name = "updateUser", title = "操作用户")
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Long updateUser;
}

View File

@ -1,5 +1,6 @@
package cn.bunny.vo.email;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -9,11 +10,14 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "EmailTemplateVo对象", title = "邮箱模板返回内容", description = "邮箱模板返回内容")
public class EmailTemplateVo {
// 模板名称
@Schema(name = "templateName", title = "模板名称")
private String templateName;
// 主题
@Schema(name = "subject", title = "主题")
private String subject;
// 邮件内容
@Schema(name = "body", title = "邮件内容")
private String body;
}

View File

@ -1,48 +0,0 @@
package cn.bunny.vo.system.login;
import cn.bunny.pojo.result.constant.LocalDateTimeConstant;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
/**
* 用户登录返回内容
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LoginVo {
private Long id;
private String nickName;
private String email;
private String password;
private String avatar;
private Byte sex;
private String personDescription;
@JsonFormat(pattern = LocalDateTimeConstant.DEFAULT_DATE_TIME_SECOND_FORMAT)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime joinTime;
@JsonFormat(pattern = LocalDateTimeConstant.DEFAULT_DATE_TIME_SECOND_FORMAT)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime lastLoginTime;
private String lastLoginIp;
private String lastLoginIpAddress;
private Integer totalIntegral;
private Integer currentIntegral;
private Byte status;
private String token;
private List<String> roleList;
private List<String> powerList;
}

View File

@ -1,43 +0,0 @@
package cn.bunny.vo.system.user;
import cn.bunny.pojo.result.constant.LocalDateTimeConstant;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 获取用户信息返回参数
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserInfoVo {
private Long userId;
private String nickName;
private String email;
private String avatar;
private Byte sex;
private String personDescription;
@JsonFormat(pattern = LocalDateTimeConstant.DEFAULT_DATE_TIME_SECOND_FORMAT)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime joinTime;
@JsonFormat(pattern = LocalDateTimeConstant.DEFAULT_DATE_TIME_SECOND_FORMAT)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime lastLoginTime;
private String lastLoginIp;
private String lastLoginIpAddress;
private Integer totalIntegral;
private Integer currentIntegral;
private Byte status;
}

View File

@ -0,0 +1,72 @@
package cn.bunny.vo.user;
import cn.bunny.vo.BaseVo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.List;
/**
* 用户登录返回内容
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "LoginVo对象", title = "登录成功返回内容", description = "登录成功返回内容")
public class LoginVo extends BaseVo {
@Schema(name = "nickName", title = "昵称")
private String nickName;
@Schema(name = "username", title = "用户名")
private String username;
@Schema(name = "email", title = "邮箱")
private String email;
@Schema(name = "phone", title = "手机号")
private String phone;
@Schema(name = "password", title = "密码")
private String password;
@Schema(name = "avatar", title = "头像")
private String avatar;
@Schema(name = "sex", title = "0:女 1:男")
private Byte sex;
@Schema(name = "personDescription", title = "个人描述")
private String personDescription;
@Schema(name = "articleMode", title = "文章显示模式")
private String articleMode;
@Schema(name = "layout", title = "页面布局方式")
private String layout;
@Schema(name = "lastLoginIp", title = "最后登录IP")
private String lastLoginIp;
@Schema(name = "lastLoginIpAddress", title = "最后登录ip地址")
private String lastLoginIpAddress;
@Schema(name = "totalIntegral", title = "积分")
private Integer totalIntegral;
@Schema(name = "currentIntegral", title = "当前积分")
private Integer currentIntegral;
@Schema(name = "status", title = "0:禁用 1:正常")
private Boolean status;
@Schema(name = "token", title = "令牌")
private String token;
@Schema(name = "roleList", title = "角色列表")
private List<String> roleList;
@Schema(name = "powerList", title = "权限列表")
private List<String> powerList;
}

View File

@ -1,4 +1,4 @@
package cn.bunny.vo.system.login;
package cn.bunny.vo.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;

View File

@ -1,26 +0,0 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bunny</groupId>
<artifactId>module</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>module-mail</artifactId>
<packaging>jar</packaging>
<name>module-mail</name>
<url>https://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,27 +0,0 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bunny</groupId>
<artifactId>module</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>module-minio</artifactId>
<packaging>jar</packaging>
<name>module-minio</name>
<url>https://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,35 +0,0 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bunny</groupId>
<artifactId>module</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>module-rabbitMQ</artifactId>
<packaging>jar</packaging>
<name>module-rabbitMQ</name>
<url>https://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.16.0-rc1</version>
</dependency>
</dependencies>
</project>

View File

@ -1,42 +0,0 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bunny</groupId>
<artifactId>bunny-template</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>module</artifactId>
<packaging>pom</packaging>
<name>module</name>
<url>https://maven.apache.org</url>
<modules>
<module>module-minio</module>
<module>module-mail</module>
<module>module-rabbitMQ</module>
<module>spring-security</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>cn.bunny</groupId>
<artifactId>service-utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>4.27.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,32 +0,0 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bunny</groupId>
<artifactId>module</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-security</artifactId>
<packaging>jar</packaging>
<name>spring-security</name>
<url>https://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- spring-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- spring-security-test -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,91 +0,0 @@
package cn.bunny.security.filter;
import cn.bunny.common.service.context.BaseContext;
import cn.bunny.common.service.exception.BunnyException;
import cn.bunny.common.service.utils.JwtHelper;
import cn.bunny.common.service.utils.ResponseUtil;
import cn.bunny.pojo.result.Result;
import cn.bunny.pojo.result.ResultCodeEnum;
import cn.bunny.pojo.result.constant.RedisUserConstant;
import cn.bunny.vo.system.login.LoginVo;
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private final RedisTemplate<String, Object> redisTemplate;
public TokenAuthenticationFilter(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException, BunnyException {
String token = request.getHeader("token");
// login请求就没token直接放行因为后边有其他的过滤器
if (token == null) {
doFilter(request, response, chain);
return;
}
// 如果想让这个用户下线清空Redis这个用户值返回未登录判断Redis是否有这个用户
// 如果想让这个用户锁定清空Redis值并在数据库中设置status值为1
String userName = JwtHelper.getUsername(token);
Object usernameObject = redisTemplate.opsForValue().get(RedisUserConstant.getUserLoginInfoPrefix(userName));
if (usernameObject == null) {
Result<Object> error = Result.error(ResultCodeEnum.LOGIN_AUTH);
ResponseUtil.out(response, error);
return;
}
// 获取Redis中登录信息
LoginVo loginVo = JSON.parseObject(JSON.toJSONString(usernameObject), LoginVo.class);
// 如果是登录接口直接放行
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
if (authentication != null) {
// 设置用户详细信息
authentication.setDetails(loginVo.getPersonDescription());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
// 请求头是否有token
String token = request.getHeader("token");
String username = RedisUserConstant.getAdminLoginInfoPrefix(JwtHelper.getUsername(token));
List<SimpleGrantedAuthority> authList = new ArrayList<>();
if (!StringUtils.hasText(username)) return null;
// 当前用户信息放到ThreadLocal里面
BaseContext.setAdminId(JwtHelper.getUserId(token));
BaseContext.setAdminName(username);
// 通过username从redis获取权限数据
Object UserObject = redisTemplate.opsForValue().get(username);
// 把redis获取字符串权限数据转换要求集合类型 List<SimpleGrantedAuthority>
if (UserObject != null) {
LoginVo loginVo = JSON.parseObject(JSON.toJSONString(UserObject), LoginVo.class);
List<String> roleList = loginVo.getRoleList();
roleList.forEach(role -> authList.add(new SimpleGrantedAuthority(role)));
return new UsernamePasswordAuthenticationToken(username, null, authList);
} else {
return new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
}
}
}

View File

@ -1,139 +0,0 @@
package cn.bunny.security.filter;
import cn.bunny.common.service.utils.ResponseUtil;
import cn.bunny.dto.user.LoginDto;
import cn.bunny.pojo.result.Result;
import cn.bunny.pojo.result.ResultCodeEnum;
import cn.bunny.pojo.result.constant.RedisUserConstant;
import cn.bunny.security.handelr.SecurityAuthenticationFailureHandler;
import cn.bunny.security.handelr.SecurityAuthenticationSuccessHandler;
import cn.bunny.security.service.CustomUserDetailsService;
import cn.bunny.vo.system.login.LoginVo;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* * UsernamePasswordAuthenticationFilter
* * 也可以在这里添加验证码短信等的验证
* 由于SpringSecurity的登录只能是表单形式 并且用户名密码需要时usernamepassword,可以通过继承 UsernamePasswordAuthenticationFilter 获取登录请求的参数
* 再去设置到 UsernamePasswordAuthenticationToken 来改变请求传参方式参数名等 或者也可以在登录的时候加入其他参数等等
*/
public class TokenLoginFilterService extends UsernamePasswordAuthenticationFilter {
private final RedisTemplate<String, Object> redisTemplate;
private final CustomUserDetailsService customUserDetailsService;
private LoginDto loginDto;
// 依赖注入
public TokenLoginFilterService(AuthenticationConfiguration authenticationConfiguration, RedisTemplate<String, Object> redisTemplate, CustomUserDetailsService customUserDetailsService) throws Exception {
this.setAuthenticationSuccessHandler(new SecurityAuthenticationSuccessHandler());
this.setAuthenticationFailureHandler(new SecurityAuthenticationFailureHandler());
this.setPostOnly(false);
// ? 指定登录接口及提交方式可以指定任意路径
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/*/login", HttpMethod.POST.name()));
this.setAuthenticationManager(authenticationConfiguration.getAuthenticationManager());
// 依赖注入
this.redisTemplate = redisTemplate;
this.customUserDetailsService = customUserDetailsService;
}
/**
* * 登录认证获取输入的用户名和密码调用方法认证
* 接受前端login登录参数
* 在这里可以设置短信验证登录
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
// 获取用户信息
loginDto = new ObjectMapper().readValue(request.getInputStream(), LoginDto.class);
// 登录验证码判断
String username = loginDto.getUsername();
String emailCode = loginDto.getEmailCode().toLowerCase();
String redisEmailCode = (String) redisTemplate.opsForValue().get(RedisUserConstant.getAdminUserEmailCodePrefix(username));
// 如果不存在验证码
if (!StringUtils.hasText(emailCode)) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.EMAIL_CODE_NOT_EMPTY));
return null;
}
if (!StringUtils.hasText(redisEmailCode)) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.SEND_EMAIL_CODE_NOT_EMPTY));
return null;
}
// 验证码不匹配
if (!Objects.equals(redisEmailCode.toLowerCase(), emailCode)) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.EMAIL_CODE_NOT_MATCHING));
return null;
}
// 封装对象将用户名密码传入
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
return this.getAuthenticationManager().authenticate(authenticationToken);
} catch (IOException e) {
throw new RuntimeException(e.getLocalizedMessage());
}
}
/**
* * 认证成功调用方法
* 返回登录成功后的信息
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) {
// 封装返回对象
LoginVo loginVo = customUserDetailsService.login(loginDto);
// 判断用户是否被锁定
if (loginVo.getStatus() == 1) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.FAIL_NO_ACCESS_DENIED_USER_LOCKED));
return;
}
// 将值存入Redis中
redisTemplate.opsForValue().set(RedisUserConstant.getAdminLoginInfoPrefix(loginVo.getEmail()), loginVo, 15, TimeUnit.DAYS);
// 将Redis中验证码删除
redisTemplate.delete(RedisUserConstant.getAdminUserEmailCodePrefix(loginVo.getEmail()));
// 返回登录信息
ResponseUtil.out(response, Result.success(loginVo));
}
/**
* * 认证失败调用方法失败判断
* 1. 是否包含用户名
* 2. 是否包含密码
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
// 账号和密码不能为空
if (loginDto == null) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.LOGIN_ERROR_USERNAME_PASSWORD_NOT_EMPTY));
}
// 用户名为空
if (!StringUtils.hasText(loginDto.getUsername())) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.USERNAME_NOT_EMPTY));
}
// 密码为空
if (!StringUtils.hasText(loginDto.getPassword())) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.PASSWORD_NOT_EMPTY));
}
// 抛出异常账号或密码错误
ResponseUtil.out(response, Result.error(null, ResultCodeEnum.LOGIN_ERROR));
}
}

View File

@ -19,13 +19,12 @@
<module>common</module>
<module>dao</module>
<module>service</module>
<module>module</module>
</modules>
<properties>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<java.version>21</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<java.version>17</java.version>
<junit.version>3.8.1</junit.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<mysql.version>8.0.30</mysql.version>

View File

@ -18,6 +18,6 @@ COPY target/*.jar /home/bunny/app.jar
ENTRYPOINT ["java","-jar","/home/bunny/app.jar"]
#暴露 8800 端口
EXPOSE 8080
EXPOSE 7070
# maven 打包mvn clean package -Pprod -DskipTests
# mvn clean package -Pprod -DskipTests

View File

@ -25,38 +25,35 @@
<artifactId>service-utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- rabbitMQ -->
<dependency>
<groupId>cn.bunny</groupId>
<artifactId>module-rabbitMQ</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- security -->
<dependency>
<groupId>cn.bunny</groupId>
<artifactId>spring-security</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 消除 spring-security 黄色 -->
<!-- spring-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- spring-security-test -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
</dependency>
<!-- amqp -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.16.0-rc1</version>
</dependency>
<!-- 消除service utils黄色 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mail模块 -->
<dependency>
<groupId>cn.bunny</groupId>
<artifactId>module-mail</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>

View File

@ -9,13 +9,13 @@ import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@ComponentScan(basePackages = {"cn.bunny"})
@MapperScan("cn.bunny.service.mapper")
@EnableScheduling// 定时任务
@EnableCaching// 开启缓存注解
@EnableTransactionManagement// 开启事务注解
@SpringBootApplication
@ComponentScan("cn.bunny")
@EnableScheduling
@EnableCaching
@EnableTransactionManagement
@Slf4j
@SpringBootApplication
public class ServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);

View File

@ -1,15 +0,0 @@
package cn.bunny.service.aop.annotation;
import cn.bunny.pojo.enums.OperationType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
// 数据库操作类型
OperationType value();
}

View File

@ -1,28 +0,0 @@
package cn.bunny.service.aop.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
@Pointcut("execution(* cn.bunny.service.web.service.impl..*(..))")
public void autoFillPointcut() {
}
/**
* 之前操作
*
* @param joinPoint 参数
*/
@Before("autoFillPointcut()")
public void autoFill(JoinPoint joinPoint) {
log.info("开始进行自动填充");
}
}

View File

@ -1,100 +1,24 @@
package cn.bunny.service.aop.aspect;
import cn.bunny.common.service.utils.JwtHelper;
import cn.bunny.entity.system.log.SystemLog;
import cn.bunny.service.aop.annotation.SkipLog;
import cn.bunny.service.mapper.SystemLogMapper;
import cn.bunny.vo.system.login.LoginVo;
import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Arrays;
import java.util.Map;
@Aspect
@Component
@Slf4j
public class AutoLogAspect {
@Autowired
private SystemLogMapper systemLogMapper;
@Pointcut("execution(* cn.bunny.service.web.controller..*(..))")
@Pointcut("execution(* cn.bunny.service.controller..*(..))")
public void point() {
}
@Around(value = "point()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
Object result;
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 是否有跳过注解如果有跳过注解就不执行当前操作
SkipLog annotation = signature.getMethod().getAnnotation(SkipLog.class);
// 目标方法所在类名路径
String classPath = joinPoint.getSignature().getDeclaringTypeName();
// 当前执行的方法名
String methodName = signature.getName();
// 入参内容
String args = Arrays.toString(joinPoint.getArgs());
// 获取用户token
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String token = request.getHeader("token");
// 初始化系统日志对象
SystemLog systemLog = new SystemLog();
// token转为实体对象
Map<String, Object> mapByToken = JwtHelper.getMapByToken(token);
LoginVo loginVo = JSONObject.parseObject(JSONObject.toJSONString(mapByToken), LoginVo.class);
// 插入Ip地址
systemLog.setIpAddress(request.getRemoteHost());
try {
// 当为null时跳过执行
if (annotation != null) return joinPoint.proceed();
if (args.equals("[null]")) {
systemLog.setArgs(null);
} else {
systemLog.setArgs(args);
}
// 登录返回Vo不为空即插入
if (loginVo != null) {
systemLog.setNickname(loginVo.getNickName());
systemLog.setEmail(loginVo.getEmail());
systemLog.setUpdateUser(loginVo.getId());
}
systemLog.setClassPath(classPath);
systemLog.setMethodName(methodName);
systemLog.setToken(token);
// 目标对象连接点方法的执行
result = joinPoint.proceed();
systemLog.setResult(JSONObject.toJSONString(result));
} catch (Exception exception) {
String message = exception.getMessage();
StackTraceElement[] stackTrace = exception.getStackTrace();
// 如果报错设置报错的堆栈和消息放到数据库中
systemLog.setErrorStack(Arrays.toString(stackTrace));
systemLog.setErrorMessage(message);
// 插入日志数据到数据库
systemLogMapper.insert(systemLog);
throw exception;
}
// 插入日志数据到数据库
systemLogMapper.insert(systemLog);
return result;
System.out.println("AOP方法执行");
return joinPoint.proceed();
}
}

View File

@ -1,7 +1,13 @@
package cn.bunny.service.controller;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -15,4 +21,16 @@ public class IndexController {
public String index() {
return "欢迎访问 Bunny Java Template欢迎去Giteehttps://gitee.com/BunnyBoss/java_single.git";
}
@Operation(summary = "生成验证码", description = "生成验证码")
@GetMapping("checkCode")
public ResponseEntity<byte[]> checkCode() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_JPEG);
// 生成验证码
CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(150, 48, 4, 2);
byte[] image = captcha.getImageBytes();
return new ResponseEntity<>(image, headers, HttpStatus.OK);
}
}

View File

@ -1,36 +0,0 @@
package cn.bunny.service.controller;
import cn.bunny.dto.user.LoginDto;
import cn.bunny.pojo.result.Result;
import cn.bunny.service.service.UserService;
import cn.bunny.vo.system.login.LoginVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "登录相关接口")
@RestController
@RequestMapping("/admin")
public class LoginController {
@Autowired
private UserService userService;
@Operation(summary = "登录接口", description = "后台用户登录接口")
@PostMapping("login")
public Result<LoginVo> login(@RequestBody LoginDto loginDto) {
LoginVo vo = userService.login(loginDto);
return Result.success(vo);
}
@Operation(summary = "发送邮箱验证码", description = "发送邮箱验证码")
@PostMapping("noAuth/sendEmail")
public Result<String> sendEmail(String email) {
userService.sendEmail(email);
return Result.success();
}
}

View File

@ -1,31 +0,0 @@
package cn.bunny.service.controller;
import cn.bunny.service.service.LoginService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "web相关接口")
@RestController
@RequestMapping("/api")
public class WebController {
@Autowired
private LoginService loginService;
@Operation(summary = "生成验证码", description = "生成验证码")
@GetMapping("checkCode")
public ResponseEntity<byte[]> checkCode() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_JPEG);
byte[] image = loginService.checkCode();
return new ResponseEntity<byte[]>(image, headers, HttpStatus.OK);
}
}

View File

@ -1,34 +0,0 @@
package cn.bunny.service.mapper;
import cn.bunny.entity.system.admin.AdminPower;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* <p>
* Mapper 接口
* </p>
*
* @author Bunny
* @since 2024-05-18
*/
@Mapper
public interface AdminPowerMapper extends BaseMapper<AdminPower> {
/**
* 查询用户权限信息
*
* @param roleIdList 角色id 列表
* @return 用户对应的权限
*/
AdminPower[] selectByPowerWithRoleIdList(List<Long> roleIdList);
/**
* 查询用户权限
*
* @param userId 用户id
* @return 用户权限列表
*/
List<AdminPower> queryByUserIdWithPower(Long userId);
}

View File

@ -1,26 +0,0 @@
package cn.bunny.service.mapper;
import cn.bunny.entity.system.admin.AdminRole;
import cn.bunny.entity.system.admin.auth.AuthUserRole;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author Bunny
* @since 2024-05-18
*/
@Mapper
public interface AdminRoleMapper extends BaseMapper<AdminRole> {
/**
* 查询用户所有的角色信息
*
* @param userId 用户id
* @return 用户对应的角色
*/
AuthUserRole[] selectByRoleWithUserId(Long userId);
}

View File

@ -1,24 +0,0 @@
package cn.bunny.service.mapper;
import cn.bunny.entity.system.email.EmailUsers;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author Bunny
* @since 2024-05-14
*/
@Mapper
public interface EmailUsersMapper extends BaseMapper<EmailUsers> {
/**
* 彻底删除邮箱用户
*
* @param id 用户ID
*/
void thoroughDeleteById(Long id);
}

View File

@ -1,18 +0,0 @@
package cn.bunny.service.mapper;
import cn.bunny.entity.system.log.SystemLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* 系统日志表 Mapper 接口
* </p>
*
* @author Bunny
* @since 2024-05-31
*/
@Mapper
public interface SystemLogMapper extends BaseMapper<SystemLog> {
}

View File

@ -1,25 +0,0 @@
package cn.bunny.service.mapper;
import cn.bunny.entity.system.user.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* 用户信息 Mapper 接口
* </p>
*
* @author Bunny
* @since 2024-05-18
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
/**
* 前台用户登录接口
*
* @param username 邮箱/昵称
* @param password Image
* @return 登录参数
*/
User login(String username, String password);
}

View File

@ -1,4 +1,4 @@
package cn.bunny.module.rabbitMQ.config;
package cn.bunny.service.mq;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;

View File

@ -1,4 +1,4 @@
package cn.bunny.module.rabbitMQ.listener;
package cn.bunny.service.mq.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

View File

@ -1,4 +1,4 @@
package cn.bunny.module.rabbitMQ.listener;
package cn.bunny.service.mq.listener;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;

View File

@ -1,4 +1,4 @@
package cn.bunny.module.rabbitMQ.listener;
package cn.bunny.service.mq.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

View File

@ -1,4 +1,4 @@
package cn.bunny.module.rabbitMQ.listener;
package cn.bunny.service.mq.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

View File

@ -1,4 +1,4 @@
package cn.bunny.module.rabbitMQ.listener;
package cn.bunny.service.mq.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

View File

@ -1,4 +1,4 @@
package cn.bunny.module.rabbitMQ.listener;
package cn.bunny.service.mq.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

View File

@ -1,4 +1,4 @@
package cn.bunny.service.mq;
package cn.bunny.service.mq.publish;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;

View File

@ -1,54 +0,0 @@
package cn.bunny.service.security;
import cn.bunny.dto.user.LoginDto;
import cn.bunny.entity.system.admin.AdminRole;
import cn.bunny.entity.system.user.User;
import cn.bunny.security.custom.CustomUser;
import cn.bunny.service.mapper.AdminRoleMapper;
import cn.bunny.service.mapper.UserMapper;
import cn.bunny.service.service.UserService;
import cn.bunny.vo.system.login.LoginVo;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class CustomUserDetailsService implements cn.bunny.security.service.CustomUserDetailsService {
@Autowired
private UserMapper userMapper;
@Autowired
private UserService userService;
@Autowired
private AdminRoleMapper adminRoleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据邮箱查询用户名
User user = userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getEmail, username));
List<AdminRole> sysRoleList = adminRoleMapper.selectList(null);
// 都为空抛出异常用户不存在
if (user == null) {
throw new UsernameNotFoundException("");
}
// 查询所有的角色
List<String> roleAuthoritieList = sysRoleList.stream().map(AdminRole::getRoleCode).toList();
return new CustomUser(user, AuthorityUtils.createAuthorityList(roleAuthoritieList));
}
/**
* 前台用户登录接口
*
* @param loginDto 登录参数
* @return 登录后结果返回
*/
@Override
public LoginVo login(LoginDto loginDto) {
return userService.login(loginDto);
}
}

View File

@ -1,12 +1,13 @@
package cn.bunny.security.config;
package cn.bunny.service.security.config;
import cn.bunny.security.custom.CustomPasswordEncoder;
import cn.bunny.security.filter.TokenAuthenticationFilter;
import cn.bunny.security.filter.TokenLoginFilterService;
import cn.bunny.security.handelr.SecurityAccessDeniedHandler;
import cn.bunny.security.handelr.SecurityAuthenticationEntryPoint;
import cn.bunny.security.service.CustomAuthorizationManagerService;
import cn.bunny.security.service.CustomUserDetailsService;
import cn.bunny.service.security.custom.CustomPasswordEncoder;
import cn.bunny.service.security.filter.NoTokenAuthenticationFilter;
import cn.bunny.service.security.filter.TokenAuthenticationFilter;
import cn.bunny.service.security.filter.TokenLoginFilterService;
import cn.bunny.service.security.handelr.SecurityAccessDeniedHandler;
import cn.bunny.service.security.handelr.SecurityAuthenticationEntryPoint;
import cn.bunny.service.security.service.CustomAuthorizationManagerService;
import cn.bunny.service.security.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -60,7 +61,8 @@ public class WebSecurityConfig {
.rememberMe(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorize -> {
// 有样式文件不需要访问权限
authorize.requestMatchers(RegexRequestMatcher.regexMatcher("^\\S*[css|js]$")).permitAll();
// authorize.requestMatchers(RegexRequestMatcher.regexMatcher("^\\S*[css|js]$")).permitAll();
authorize.requestMatchers(RegexRequestMatcher.regexMatcher("^.*\\.(css|js)$")).permitAll();
// 上面都不是需要鉴权访问
authorize.anyRequest().access(customAuthorizationManager);
})
@ -73,6 +75,7 @@ public class WebSecurityConfig {
// 登录验证过滤器
.addFilterBefore(new TokenLoginFilterService(authenticationConfiguration, redisTemplate, customUserDetailsService), UsernamePasswordAuthenticationFilter.class)
// 其它权限鉴权过滤器
.addFilterAt(new NoTokenAuthenticationFilter(redisTemplate), UsernamePasswordAuthenticationFilter.class)
.addFilterAt(new TokenAuthenticationFilter(redisTemplate), UsernamePasswordAuthenticationFilter.class)
// 自定义密码加密器和用户登录
.passwordManagement(customPasswordEncoder).userDetailsService(customUserDetailsService);
@ -88,8 +91,11 @@ public class WebSecurityConfig {
// 排出鉴定路径
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
String[] annotations = {"/", "/test/**", "/admin/login", "/*.html", "/*/*/noAuth/**", "/*/noAuth/**", "/favicon.ico",
"/swagger-resources/**", "/swagger-ui.html/**", "/v3/**", "/api/**"
String[] annotations = {
"/", "/ws/**",
"/*/*/noAuth/**", "/*/noAuth/**", "/noAuth/**",
"/media.ico", "/favicon.ico", "*.html",
"/swagger-resources/**", "/v3/**", "/swagger-ui/**"
};
return web -> web.ignoring().requestMatchers(annotations);
}

View File

@ -1,4 +1,4 @@
package cn.bunny.security.custom;
package cn.bunny.service.security.custom;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;

View File

@ -1,5 +1,6 @@
package cn.bunny.security.custom;
package cn.bunny.service.security.custom;
import cn.bunny.entity.system.AdminUser;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
@ -13,9 +14,9 @@ import java.util.Collection;
@Getter
@Setter
public class CustomUser extends User {
private cn.bunny.entity.system.user.User user;
private AdminUser user;
public CustomUser(cn.bunny.entity.system.user.User user, Collection<? extends GrantedAuthority> authorities) {
public CustomUser(AdminUser user, Collection<? extends GrantedAuthority> authorities) {
super(user.getEmail(), user.getPassword(), authorities);
this.user = user;
}

View File

@ -0,0 +1,55 @@
package cn.bunny.service.security.filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class NoTokenAuthenticationFilter extends OncePerRequestFilter {
private final RedisTemplate<String, Object> redisTemplate;
public NoTokenAuthenticationFilter(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// // 判断是否有 token
// String token = request.getHeader("token");
// if (token == null) {
// ResponseUtil.out(response, Result.error(ResultCodeEnum.LOGIN_AUTH));
// return;
// }
//
// // 判断 token 是否过期
// boolean expired = JwtHelper.isExpired(token);
// if (expired) {
// ResponseUtil.out(response, Result.error(ResultCodeEnum.AUTHENTICATION_EXPIRED));
// return;
// }
//
// // token 存在查找 Redis
// String username = JwtHelper.getUsername(token);
// Long userId = JwtHelper.getUserId(token);
// LoginVo loginVo = (LoginVo) redisTemplate.opsForValue().get(RedisUserConstant.getAdminLoginInfoPrefix(username));
//
// // 判断用户是否禁用
// if (loginVo != null && loginVo.getStatus()) {
// ResponseUtil.out(response, Result.error(ResultCodeEnum.FAIL_NO_ACCESS_DENIED_USER_LOCKED));
// return;
// }
//
// // 设置用户信息
// BaseContext.setUsername(username);
// BaseContext.setUserId(userId);
// BaseContext.setLoginVo(loginVo);
// 执行下一个过滤器
doFilter(request, response, filterChain);
}
}

View File

@ -0,0 +1,61 @@
package cn.bunny.service.security.filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private final RedisTemplate<String, Object> redisTemplate;
public TokenAuthenticationFilter(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String token = request.getHeader("token");
// 判断是否有token如果后面还有过滤器就这样写
// if (token == null) {
// doFilter(request, response, chain);
// return;
// }
// 自定义实现内容
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
/**
* * 用户请求判断
*
* @param request 请求
* @return 验证码方法
*/
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
// 请求头是否有token
String token = request.getHeader("token");
String username = "admin";
List<SimpleGrantedAuthority> authList = new ArrayList<>();
// 设置角色内容
if (token != null) {
List<String> roleList = new ArrayList<>();
return new UsernamePasswordAuthenticationToken(username, null, authList);
} else {
return new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
}
}
}

View File

@ -0,0 +1,86 @@
package cn.bunny.service.security.filter;
import cn.bunny.dto.user.LoginDto;
import cn.bunny.pojo.constant.RedisUserConstant;
import cn.bunny.pojo.result.Result;
import cn.bunny.pojo.result.ResultCodeEnum;
import cn.bunny.service.security.handelr.SecurityAuthenticationFailureHandler;
import cn.bunny.service.security.handelr.SecurityAuthenticationSuccessHandler;
import cn.bunny.service.security.service.CustomUserDetailsService;
import cn.bunny.vo.user.LoginVo;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.bind.annotation.RequestMethod;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import static cn.bunny.common.service.utils.ResponseUtil.out;
/**
* * UsernamePasswordAuthenticationFilter
* * 也可以在这里添加验证码短信等的验证
* 由于SpringSecurity的登录只能是表单形式 并且用户名密码需要时usernamepassword,可以通过继承 UsernamePasswordAuthenticationFilter 获取登录请求的参数
* 再去设置到 UsernamePasswordAuthenticationToken 来改变请求传参方式参数名等 或者也可以在登录的时候加入其他参数等等
*/
public class TokenLoginFilterService extends UsernamePasswordAuthenticationFilter {
private final RedisTemplate<String, Object> redisTemplate;
private final CustomUserDetailsService customUserDetailsService;
private LoginDto loginDto;
public TokenLoginFilterService(AuthenticationConfiguration authenticationConfiguration, RedisTemplate<String, Object> redisTemplate, CustomUserDetailsService customUserDetailsService) throws Exception {
this.setAuthenticationSuccessHandler(new SecurityAuthenticationSuccessHandler());
this.setAuthenticationFailureHandler(new SecurityAuthenticationFailureHandler());
this.setPostOnly(false);
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/*/login", RequestMethod.POST.name()));
this.setAuthenticationManager(authenticationConfiguration.getAuthenticationManager());
this.redisTemplate = redisTemplate;
this.customUserDetailsService = customUserDetailsService;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
loginDto = new LoginDto();
loginDto.setUsername("admin");
loginDto.setPassword("password");
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
return getAuthenticationManager().authenticate(authenticationToken);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) throws IOException, ServletException {
LoginVo loginAdminVo = customUserDetailsService.login(loginDto);
if (loginAdminVo.getStatus()) {
out(response, Result.error(ResultCodeEnum.FAIL_NO_ACCESS_DENIED_USER_LOCKED));
return;
}
redisTemplate.opsForValue().set(RedisUserConstant.getAdminLoginInfoPrefix(loginAdminVo.getEmail()), loginAdminVo, 15, TimeUnit.DAYS);
redisTemplate.delete(RedisUserConstant.getAdminUserEmailCodePrefix(loginAdminVo.getEmail()));
out(response, Result.success(loginAdminVo));
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
String password = loginDto.getPassword();
String username = loginDto.getUsername();
if (password == null || password.isBlank() || username == null || username.isBlank()) {
out(response, Result.error(ResultCodeEnum.USERNAME_OR_PASSWORD_NOT_EMPTY));
} else {
out(response, Result.error(null, ResultCodeEnum.LOGIN_ERROR));
}
}
}

View File

@ -1,4 +1,4 @@
package cn.bunny.security.handelr;
package cn.bunny.service.security.handelr;
import cn.bunny.pojo.result.Result;
import cn.bunny.pojo.result.ResultCodeEnum;

View File

@ -1,4 +1,4 @@
package cn.bunny.security.handelr;
package cn.bunny.service.security.handelr;
import cn.bunny.common.service.utils.ResponseUtil;
import cn.bunny.pojo.result.Result;

View File

@ -1,4 +1,4 @@
package cn.bunny.security.handelr;
package cn.bunny.service.security.handelr;
import cn.bunny.pojo.result.Result;
import com.alibaba.fastjson2.JSON;

View File

@ -1,4 +1,4 @@
package cn.bunny.security.handelr;
package cn.bunny.service.security.handelr;
import cn.bunny.pojo.result.Result;
import com.alibaba.fastjson2.JSON;

View File

@ -1,4 +1,4 @@
package cn.bunny.security.service;
package cn.bunny.service.security.service;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;

View File

@ -1,7 +1,7 @@
package cn.bunny.security.service;
package cn.bunny.service.security.service;
import cn.bunny.dto.user.LoginDto;
import cn.bunny.vo.system.login.LoginVo;
import cn.bunny.vo.user.LoginVo;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

View File

@ -1,13 +1,9 @@
package cn.bunny.service.security;
package cn.bunny.service.security.service.iml;
import cn.bunny.common.service.utils.JwtHelper;
import cn.bunny.entity.system.admin.AdminPower;
import cn.bunny.security.service.CustomAuthorizationManagerService;
import cn.bunny.service.mapper.AdminPowerMapper;
import cn.bunny.service.security.service.CustomAuthorizationManagerService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
@ -25,9 +21,6 @@ import java.util.function.Supplier;
@Component
@Slf4j
public class CustomAuthorizationManagerServiceImpl implements CustomAuthorizationManagerService {
@Autowired
private AdminPowerMapper adminPowerMapper;
@Override
public void verify(Supplier<Authentication> authentication, RequestAuthorizationContext requestAuthorizationContext) {
CustomAuthorizationManagerService.super.verify(authentication, requestAuthorizationContext);
@ -42,9 +35,6 @@ public class CustomAuthorizationManagerServiceImpl implements CustomAuthorizatio
String requestURI = request.getRequestURI();// 请求地址
String method = request.getMethod();// 请求方式
List<String> roleCodeList = authentication.get().getAuthorities().stream().map(GrantedAuthority::getAuthority).toList();// 角色代码列表
if (token == null) {
throw new AccessDeniedException("");
}
return new AuthorizationDecision(hasRoleList(requestURI, method, userId));
}
@ -57,16 +47,6 @@ public class CustomAuthorizationManagerServiceImpl implements CustomAuthorizatio
* @param userId 用户id
*/
private Boolean hasRoleList(String requestURI, String method, Long userId) {
// 查询用户权限
List<AdminPower> powerList = adminPowerMapper.queryByUserIdWithPower(userId);
// 如果查询到当前地址符合这个地址
for (AdminPower adminPower : powerList) {
String description = adminPower.getDescription();
if (description.equals(requestURI) || requestURI.matches(description)) {
return true;
}
}
return false;
return true;
}
}

Some files were not shown because too many files have changed in this diff Show More