From 3b598b0e816d57e816a3fe20e9f8045718a9f7a8 Mon Sep 17 00:00:00 2001 From: soul2 <1052986332@qq.com> Date: Tue, 16 Apr 2024 17:12:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E8=AF=B7=E6=B1=82=E5=92=8C?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E7=9A=84=E5=8A=A0=E8=A7=A3=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jyjc/admin/bean/UserLoginStatusBean.java | 7 + .../admin/filter/ReplaceStreamFilter.java | 25 +++- .../admin/filter/Soul2ResponseWrapper.java | 120 +++++++++++++++++ .../admin/interceptor/FinallyInterceptor.java | 20 ++- .../repository/IUserLoginOutRepository.java | 9 ++ .../impl/UserLoginOutRepositoryImpl.java | 27 ++++ .../jyjc/admin/service/IUserService.java | 10 ++ .../admin/service/impl/UserServiceImpl.java | 8 ++ src/main/resources/logback-spring.xml | 127 ++++++++++++++++++ 9 files changed, 347 insertions(+), 6 deletions(-) create mode 100644 src/main/java/cn/soul2/jyjc/admin/filter/Soul2ResponseWrapper.java create mode 100644 src/main/resources/logback-spring.xml diff --git a/src/main/java/cn/soul2/jyjc/admin/bean/UserLoginStatusBean.java b/src/main/java/cn/soul2/jyjc/admin/bean/UserLoginStatusBean.java index 99e5446..f6ec446 100644 --- a/src/main/java/cn/soul2/jyjc/admin/bean/UserLoginStatusBean.java +++ b/src/main/java/cn/soul2/jyjc/admin/bean/UserLoginStatusBean.java @@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.time.LocalDateTime; +import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; @Component @@ -12,6 +13,12 @@ public class UserLoginStatusBean { private ConcurrentHashMap loginStatusMap = new ConcurrentHashMap<>(); + public void loadUserLoginStatus(Collection caches) { + for (UserLoginOutDO cache : caches) { + loginStatusMap.put(cache.getId(), cache); + } + } + /** * 缓存登录信息 * diff --git a/src/main/java/cn/soul2/jyjc/admin/filter/ReplaceStreamFilter.java b/src/main/java/cn/soul2/jyjc/admin/filter/ReplaceStreamFilter.java index 0581760..5437f66 100644 --- a/src/main/java/cn/soul2/jyjc/admin/filter/ReplaceStreamFilter.java +++ b/src/main/java/cn/soul2/jyjc/admin/filter/ReplaceStreamFilter.java @@ -1,11 +1,14 @@ package cn.soul2.jyjc.admin.filter; +import cn.soul2.jyjc.admin.utils.EncryptUtils; import lombok.extern.slf4j.Slf4j; import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.io.PrintWriter; /** * @author shaoduo @@ -24,12 +27,28 @@ public class ReplaceStreamFilter implements Filter { //如果是文件上传则会报错可以判断是否是文件上传不读取流即可 if (ServletFileUpload.isMultipartContent((HttpServletRequest) request)) { chain.doFilter(request, response); - return; + } else if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) request).getMethod())) { + chain.doFilter(request, response); } else { ServletRequest requestWrapper = new ShaoduoRequestWrapper((HttpServletRequest) request); // System.out.printf("ReplaceStreamFilter触发, Method: %s, URI: %s%n", ((HttpServletRequest) request).getMethod(), ((HttpServletRequest) request).getRequestURI()); - chain.doFilter(requestWrapper, response); - return; + HttpServletResponse httpResponse = (HttpServletResponse) response; + Soul2ResponseWrapper soul2ResponseWrapper = new Soul2ResponseWrapper(httpResponse); + chain.doFilter(requestWrapper, soul2ResponseWrapper); + try { + byte[] data = soul2ResponseWrapper.getResponseData(); + log.debug("原始返回 -> " + new String(data)); + String encryptBody = EncryptUtils.encrypt(new String(data)); + log.debug("加密返回 -> " + encryptBody); + soul2ResponseWrapper.setHeader("encrypt", "1"); + soul2ResponseWrapper.setHeader("Access-Control-Expose-Headers", "encrypt"); + PrintWriter out = response.getWriter(); + out.print(encryptBody); + out.flush(); + out.close(); + } catch (Exception e) { + e.printStackTrace(); + } } } diff --git a/src/main/java/cn/soul2/jyjc/admin/filter/Soul2ResponseWrapper.java b/src/main/java/cn/soul2/jyjc/admin/filter/Soul2ResponseWrapper.java new file mode 100644 index 0000000..28cf4b6 --- /dev/null +++ b/src/main/java/cn/soul2/jyjc/admin/filter/Soul2ResponseWrapper.java @@ -0,0 +1,120 @@ +package cn.soul2.jyjc.admin.filter; + +import lombok.SneakyThrows; + +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; +import java.io.*; + +/** + * @author Soul2 + * @description 包装ServletResponse, 照抄自 temp_44 + * @date 2024-04-15 + */ +public class Soul2ResponseWrapper extends HttpServletResponseWrapper { + private ByteArrayOutputStream buffer = null; + private ServletOutputStream out = null; + private PrintWriter writer = null; + + public Soul2ResponseWrapper(HttpServletResponse resp) throws IOException { + super(resp); + // 真正存储数据的流 + buffer = new ByteArrayOutputStream(); + out = new WapperedOutputStream(buffer); + writer = new PrintWriter(new OutputStreamWriter(buffer, + this.getCharacterEncoding())); + } + + /** + * 重载父类获取outputstream的方法 + */ + @Override + public ServletOutputStream getOutputStream() throws IOException { + return out; + } + + /** + * 重载父类获取writer的方法 + */ + @Override + public PrintWriter getWriter() throws UnsupportedEncodingException { + return writer; + } + + /** + * The default behavior of this method is to call + * setCharacterEncoding(String charset) on the wrapped response object. + * + * @param charset + * @since 2.4 + */ + @SneakyThrows + @Override + public void setCharacterEncoding(String charset) { + super.setCharacterEncoding(charset); + writer = new PrintWriter(new OutputStreamWriter(buffer, + this.getCharacterEncoding())); + } + + /** + * 重载父类获取flushBuffer的方法 + */ + @Override + public void flushBuffer() throws IOException { + if (out != null) { + out.flush(); + } + if (writer != null) { + writer.flush(); + } + } + + @Override + public void reset() { + buffer.reset(); + } + + /** + * 将out、writer中的数据强制输出到WapperedResponse的buffer里面,否则取不到数据 + */ + public byte[] getResponseData() throws IOException { + flushBuffer(); + return buffer.toByteArray(); + } + + /** + * 内部类,对ServletOutputStream进行包装 + */ + private class WapperedOutputStream extends ServletOutputStream { + private ByteArrayOutputStream bos = null; + + public WapperedOutputStream(ByteArrayOutputStream stream) + throws IOException { + bos = stream; + } + + @Override + public void write(int b) throws IOException { + bos.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + bos.write(b, 0, b.length); + } + + @Override + public boolean isReady() { + // TODO Auto-generated method stub + return false; + } + + @Override + public void setWriteListener(WriteListener writeListener) { + // TODO Auto-generated method stub + + } + } +} diff --git a/src/main/java/cn/soul2/jyjc/admin/interceptor/FinallyInterceptor.java b/src/main/java/cn/soul2/jyjc/admin/interceptor/FinallyInterceptor.java index 6109dab..4806d09 100644 --- a/src/main/java/cn/soul2/jyjc/admin/interceptor/FinallyInterceptor.java +++ b/src/main/java/cn/soul2/jyjc/admin/interceptor/FinallyInterceptor.java @@ -4,12 +4,14 @@ import cn.soul2.jyjc.admin.annotation.SkinEncrypt; import cn.soul2.jyjc.admin.annotation.SkinLogin; import cn.soul2.jyjc.admin.bean.UserLoginStatusBean; import cn.soul2.jyjc.admin.filter.ShaoduoRequestWrapper; +import cn.soul2.jyjc.admin.service.IUserService; import cn.soul2.jyjc.admin.utils.EncryptUtils; import cn.soul2.jyjc.admin.vo.base.Back; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; @@ -31,8 +33,21 @@ public class FinallyInterceptor implements HandlerInterceptor { @Resource private UserLoginStatusBean userLoginStatusBean; + @Autowired + private IUserService userService; + + private boolean load = true; + + private void loadUserLoginStatus() { + userLoginStatusBean.loadUserLoginStatus(userService.getLoginedUser()); + } + @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if (load) { + loadUserLoginStatus(); + load = false; + } boolean pass = false; // 允许OPTIONS请求通过 if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { @@ -81,7 +96,7 @@ public class FinallyInterceptor implements HandlerInterceptor { JSONObject obj = JSON.parseObject(EncryptUtils.decrypt(sourceParamBody)); String afterBody = JSONObject.toJSONString(obj); shaoduoRequestWrapper.setBody(afterBody); - log.info(String.format("解密: %s -> %s", sourceParamBody, afterBody)); + log.debug(String.format("解密: %s -> %s", sourceParamBody, afterBody)); } @@ -99,7 +114,7 @@ public class FinallyInterceptor implements HandlerInterceptor { } else if (userLoginStatusBean != null) { if (!userLoginStatusBean.containsToken(token)) { // Token 无效,拒绝请求 - response.setStatus(0); + response.setContentType("application/json"); Back back = new Back().setCode(40401).setMessage("Token invalid!"); // 转换为 JSON 字符串 @@ -122,5 +137,4 @@ public class FinallyInterceptor implements HandlerInterceptor { } return pass; } - } diff --git a/src/main/java/cn/soul2/jyjc/admin/repository/IUserLoginOutRepository.java b/src/main/java/cn/soul2/jyjc/admin/repository/IUserLoginOutRepository.java index b2a9bf0..8772e48 100644 --- a/src/main/java/cn/soul2/jyjc/admin/repository/IUserLoginOutRepository.java +++ b/src/main/java/cn/soul2/jyjc/admin/repository/IUserLoginOutRepository.java @@ -8,6 +8,8 @@ import cn.soul2.jyjc.admin.entity.UserLoginOutDO; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; +import java.util.List; + /** *

* 用户登入登出表 服务类 @@ -59,4 +61,11 @@ public interface IUserLoginOutRepository extends IService { */ Boolean removeByUserId(String userId); + /** + * 重建当前登录的用户 + * + * @return {@link List}<{@link UserLoginOutDO}> + */ + List getLoginedUser(); + } diff --git a/src/main/java/cn/soul2/jyjc/admin/repository/impl/UserLoginOutRepositoryImpl.java b/src/main/java/cn/soul2/jyjc/admin/repository/impl/UserLoginOutRepositoryImpl.java index 84eed82..7af7664 100644 --- a/src/main/java/cn/soul2/jyjc/admin/repository/impl/UserLoginOutRepositoryImpl.java +++ b/src/main/java/cn/soul2/jyjc/admin/repository/impl/UserLoginOutRepositoryImpl.java @@ -12,6 +12,7 @@ import cn.soul2.jyjc.admin.utils.base.PageUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -19,6 +20,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collectors; /** *

@@ -96,4 +103,24 @@ public class UserLoginOutRepositoryImpl extends ServiceImpl getLoginedUser() { + LocalDateTime now = LocalDateTime.now(); + LambdaQueryWrapper query = Wrappers.lambdaQuery(); + query + .isNull(UserLoginOutDO::getLogoutTime) + .gt(UserLoginOutDO::getLapseTime, now); + List list = super.list(query); + if (CollectionUtils.isEmpty(list)) { + return new ArrayList<>(); + } + + return new ArrayList<>(list + .stream() + .collect(Collectors.toMap(UserLoginOutDO::getUserId, + Function.identity(), + BinaryOperator.maxBy(Comparator.comparing(UserLoginOutDO::getLoginTime)))) + .values()); + } } diff --git a/src/main/java/cn/soul2/jyjc/admin/service/IUserService.java b/src/main/java/cn/soul2/jyjc/admin/service/IUserService.java index 53a3392..823e531 100644 --- a/src/main/java/cn/soul2/jyjc/admin/service/IUserService.java +++ b/src/main/java/cn/soul2/jyjc/admin/service/IUserService.java @@ -2,8 +2,11 @@ package cn.soul2.jyjc.admin.service; import cn.soul2.jyjc.admin.dto.UserLoginDTO; import cn.soul2.jyjc.admin.dto.UserLogoutDTO; +import cn.soul2.jyjc.admin.entity.UserLoginOutDO; import cn.soul2.jyjc.admin.vo.UserVO; +import java.util.List; + /** * @author Soul2 * @date 2024-03-29 20:47 @@ -43,4 +46,11 @@ public interface IUserService { */ Boolean remove(String userId); + /** + * 重建当前登录的用户 + * + * @return {@link List}<{@link UserLoginOutDO}> + */ + List getLoginedUser(); + } diff --git a/src/main/java/cn/soul2/jyjc/admin/service/impl/UserServiceImpl.java b/src/main/java/cn/soul2/jyjc/admin/service/impl/UserServiceImpl.java index f44fd05..792aba6 100644 --- a/src/main/java/cn/soul2/jyjc/admin/service/impl/UserServiceImpl.java +++ b/src/main/java/cn/soul2/jyjc/admin/service/impl/UserServiceImpl.java @@ -3,6 +3,7 @@ package cn.soul2.jyjc.admin.service.impl; import cn.soul2.jyjc.admin.dto.UserLoginDTO; import cn.soul2.jyjc.admin.dto.UserLogoutDTO; import cn.soul2.jyjc.admin.entity.UserDO; +import cn.soul2.jyjc.admin.entity.UserLoginOutDO; import cn.soul2.jyjc.admin.repository.IUserLoginOutRepository; import cn.soul2.jyjc.admin.repository.IUserRepository; import cn.soul2.jyjc.admin.service.IUserService; @@ -12,6 +13,8 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.List; + /** * @author Soul2 * @date 2024-03-29 20:47 @@ -84,4 +87,9 @@ public class UserServiceImpl implements IUserService { loginOutRepository.removeByUserId(userId); return userRepository.removeById(userId); } + + @Override + public List getLoginedUser() { + return loginOutRepository.getLoginedUser(); + } } diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..1b9a132 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,127 @@ + + + + + + logback + + + + + + + + + + + + + DEBUG + + + + ${log.pattern} + UTF-8 + + + + + + + + ${log.path}/debug.log + + + ${log.pattern} + UTF-8 + + + + + ${debug.path} + + 10MB + + + 7 + + + + DEBUG + + + + + + + ${log.path}/info.log + + + ${log.pattern} + UTF-8 + + + + + ${info.path} + + 100MB + + + 60 + + + + INFO + + + + + + + ${log.path}/error.log + + + ${log.pattern} + UTF-8 + + + + ${error.path} + + 10MB + + + 60 + + + + ERROR + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file