Przeglądaj źródła

Merge remote-tracking branch 'origin/master'

dengsixing 3 lat temu
rodzic
commit
7972cf49f6

+ 5 - 1
src/main/java/com/fdkankan/gateway/config/SaTokenConfigure.java

@@ -9,6 +9,8 @@ import cn.dev33.satoken.util.SaResult;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.fdkankan.common.response.ResultData;
+import com.fdkankan.gateway.util.CacheUtil;
+import com.fdkankan.redis.constant.RedisKey;
 import com.fdkankan.redis.util.RedisUtil;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -34,7 +36,7 @@ public class SaTokenConfigure {
                     // 登录校验 -- 拦截所有路由
                     SaRouter.match("/**",  r -> StpUtil.checkLogin());
                     //从redis中获取路由对应 权限
-                    String menu = redisUtil.get("manage_perm_menu");
+                    String menu = redisUtil.get(RedisKey.MANAGE_MENU);
                     if(StringUtils.isBlank(menu)){
                         SaRouter.match("/**", r -> StpUtil.checkRole("super-admin"));
                     }
@@ -44,6 +46,8 @@ public class SaTokenConfigure {
                         String url = jsonObject.getString("url");
                         String perm = jsonObject.getString("perms");
                         SaRouter.match(url, r -> StpUtil.checkPermission(perm));
+                        CacheUtil.manageMenuId.put(jsonObject.getString("id"),jsonObject);
+                        CacheUtil.manageMenuUrl.put(jsonObject.getString("url"),jsonObject);
                     }
 
                     // 权限认证 -- 不同模块, 校验不同权限

+ 5 - 2
src/main/java/com/fdkankan/gateway/config/StpInterfaceImpl.java

@@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.fdkankan.common.constant.ErrorCode;
 import com.fdkankan.common.exception.BusinessException;
+import com.fdkankan.redis.constant.RedisKey;
 import com.fdkankan.redis.util.RedisUtil;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -22,7 +23,8 @@ public class StpInterfaceImpl implements StpInterface {
 
     @Override
     public List<String> getPermissionList(Object loginId, String loginType) {
-        String permString = redisUtil.get("manage_perm_user:" + loginId);
+        String redisKey = String.format(RedisKey.MANAGE_PERM_USER, loginId);
+        String permString = redisUtil.get(redisKey);
         if(StringUtils.isBlank(permString)){
             throw new BusinessException(ErrorCode.USER_NOT_LOGIN);
         }
@@ -35,7 +37,8 @@ public class StpInterfaceImpl implements StpInterface {
     @Override
     public List<String> getRoleList(Object loginId, String loginType) {
         //从redis 中获取登录用户角色
-        String roleString = redisUtil.get("manage_role_user:" + loginId);
+        String redisKey = String.format(RedisKey.MANAGE_ROLE_USER, loginId);
+        String roleString = redisUtil.get(redisKey);
         if(StringUtils.isBlank(roleString)){
             throw new BusinessException(ErrorCode.USER_NOT_LOGIN);
         }

+ 25 - 0
src/main/java/com/fdkankan/gateway/factory/ManageFilterGatewayFilterFactory.java

@@ -0,0 +1,25 @@
+package com.fdkankan.gateway.factory;
+
+import com.fdkankan.gateway.filter.ManageFilter;
+import com.fdkankan.gateway.filter.ResponseFilter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.gateway.filter.GatewayFilter;
+import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
+import org.springframework.stereotype.Component;
+
+/**
+ * 异常统一返回错误信息过滤器
+ */
+@Component
+public class ManageFilterGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
+
+    @Autowired
+    private ManageFilter manageFilter;
+
+    @Override
+    public GatewayFilter apply(Object filter) {
+        return manageFilter;
+    }
+}
+
+

+ 3 - 1
src/main/java/com/fdkankan/gateway/filter/AccessLogFilter.java

@@ -66,7 +66,9 @@ public class AccessLogFilter  implements GlobalFilter, Ordered {
 
     @Override
     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
-
+        if (exchange.getAttribute("@manageFilter") != null) {
+            return chain.filter(exchange);
+        }
         ServerHttpRequest request = exchange.getRequest();
         // 请求路径
         String requestPath = request.getPath().pathWithinApplication().value();

+ 312 - 0
src/main/java/com/fdkankan/gateway/filter/CommonLogService.java

@@ -0,0 +1,312 @@
+package com.fdkankan.gateway.filter;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.net.URLDecoder;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.fdkankan.common.response.ResultData;
+import com.fdkankan.gateway.log.OperLog;
+import com.fdkankan.gateway.util.CacheUtil;
+import com.fdkankan.gateway.util.WebUtil;
+import com.fdkankan.redis.util.RedisUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.reactivestreams.Publisher;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
+import org.springframework.cloud.gateway.route.Route;
+import org.springframework.cloud.gateway.support.BodyInserterContext;
+import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferFactory;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.core.io.buffer.DefaultDataBufferFactory;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.codec.HttpMessageReader;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
+import org.springframework.stereotype.Component;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.reactive.function.BodyInserter;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.server.HandlerStrategies;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+@Component
+@Slf4j
+public class CommonLogService {
+
+    private final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();
+
+    @Autowired
+    RedisUtil redisUtil;
+
+    @Autowired
+    private MongoTemplate mongoTemplate;
+
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+
+        ServerHttpRequest request = exchange.getRequest();
+        // 请求路径
+        String uri = request.getPath().pathWithinApplication().value();
+        String requestPath;
+
+        Long userId = null;
+        String userName = null;
+        String nickName = null;
+        if(uri.equals("/service/auth/manage/login")){
+            requestPath = "登录";
+        }else {
+            requestPath = getRequestPath(uri);
+            userId =Long.valueOf( StpUtil.getExtra("userId").toString());
+            userName = StpUtil.getExtra("userName").toString();
+            nickName = StpUtil.getExtra("nickName").toString();
+        }
+        String operationType;
+        if(uri.contains("manage")){
+            operationType = "manage";
+        }else {
+            operationType ="other";
+        }
+        String ipAddress = WebUtil.getIpAddress(request);
+        String browser = WebUtil.getBrowser(request);
+
+        OperLog operLog = new OperLog();
+        operLog.setMethod(request.getMethodValue());
+        operLog.setUri(uri);
+        operLog.setCreateTime(new Date());
+        operLog.setIp(ipAddress);
+        operLog.setBrowser(browser);
+        operLog.setRequestPath(requestPath);
+        operLog.setOperationType(operationType);
+
+        operLog.setUserId(userId);
+        operLog.setUserName(userName);
+        operLog.setNickName(nickName);
+
+
+        MediaType mediaType = request.getHeaders().getContentType();
+
+        if(MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)
+                || MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)){
+            return writeBodyLog(exchange, chain, operLog);
+        }else{
+            return writeBasicLog(exchange, chain, operLog);
+        }
+    }
+
+    private String getRequestPath(String uri) {
+        JSONObject jsonObject = CacheUtil.manageMenuUrl.get(uri);
+
+        if(StringUtils.isEmpty(jsonObject)){
+            return null;
+        }
+        List<String> list = new ArrayList<>();
+        getMenuName(list,jsonObject.getString("id"));
+        Collections.reverse(list);
+        StringBuilder requestPath = new StringBuilder();
+        for (String path : list) {
+            requestPath.append("[").append(path).append("]").append("->");
+        }
+        int i = requestPath.lastIndexOf("->");
+        if(i < 0){
+            return requestPath.toString();
+        }
+        return requestPath.substring(0,i);
+    }
+
+    private int getMenuName( List<String> list,String menuId){
+        JSONObject jsonObject = CacheUtil.manageMenuId.get(menuId);
+        if(StringUtils.isEmpty(jsonObject)){
+            return -1;
+        }
+        list.add( jsonObject.getString("name"));
+        String parentId = jsonObject.getString("parentId");
+        if(!StringUtils.isEmpty(parentId)){
+            return getMenuName(list,parentId);
+        }
+        return 1;
+    }
+
+    private Mono<Void> writeBasicLog(ServerWebExchange exchange, GatewayFilterChain chain, OperLog operLog) {
+        StringBuilder builder = new StringBuilder();
+        MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
+        for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
+            builder.append(entry.getKey()).append("=").append(StrUtil.join(",", entry.getValue()));
+        }
+        operLog.setParams(builder.toString());
+
+        //获取响应体
+        ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, operLog);
+
+        return chain.filter(exchange.mutate().response(decoratedResponse).build())
+                .then(Mono.fromRunnable(() -> {
+                    // 打印日志
+                    writeAccessLog(operLog);
+                }));
+    }
+
+
+    /**
+     * 参考: org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory
+     * @param exchange
+     * @param chain
+     * @param operLog
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    private Mono writeBodyLog(ServerWebExchange exchange, GatewayFilterChain chain, OperLog operLog) {
+        ServerRequest serverRequest = ServerRequest.create(exchange,messageReaders);
+
+        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
+                .flatMap(body ->{
+                    operLog.setParams(URLDecoder.decode(body, StandardCharsets.UTF_8));
+                    return Mono.just(body);
+                });
+
+        // 通过 BodyInserter 插入 body(支持修改body), 避免 request body 只能获取一次
+        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
+        HttpHeaders headers = new HttpHeaders();
+        headers.putAll(exchange.getRequest().getHeaders());
+        // the new content type will be computed by bodyInserter
+        // and then set in the request decorator
+        headers.remove(HttpHeaders.CONTENT_LENGTH);
+
+        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
+
+        return bodyInserter.insert(outputMessage,new BodyInserterContext())
+                .then(Mono.defer(() -> {
+
+                    // 重新封装请求
+                    ServerHttpRequest decoratedRequest = requestDecorate(exchange, headers, outputMessage);
+
+                    // 记录响应日志
+                    ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, operLog);
+
+                    // 记录普通的
+                    return chain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build())
+                            .then(Mono.fromRunnable(() -> {
+                                // 打印日志
+                                writeAccessLog(operLog);
+                            }));
+                }));
+    }
+
+    /**
+     * 打印日志
+     * @param operLog 网关日志
+     */
+    private void writeAccessLog(OperLog operLog) {
+        log.info(JSON.toJSONString(operLog));
+        //日志写入mongodb
+        mongoTemplate.insert(operLog, "OperLog");
+    }
+
+
+
+    private Route getGatewayRoute(ServerWebExchange exchange) {
+        return exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
+    }
+
+
+    /**
+     * 请求装饰器,重新计算 headers
+     * @param exchange
+     * @param headers
+     * @param outputMessage
+     * @return
+     */
+    private ServerHttpRequestDecorator requestDecorate(ServerWebExchange exchange, HttpHeaders headers,
+                                                       CachedBodyOutputMessage outputMessage) {
+        return new ServerHttpRequestDecorator(exchange.getRequest()) {
+            @Override
+            public HttpHeaders getHeaders() {
+                long contentLength = headers.getContentLength();
+                HttpHeaders httpHeaders = new HttpHeaders();
+                httpHeaders.putAll(super.getHeaders());
+                if (contentLength > 0) {
+                    httpHeaders.setContentLength(contentLength);
+                } else {
+                    httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
+                }
+                return httpHeaders;
+            }
+
+            @Override
+            public Flux<DataBuffer> getBody() {
+                return outputMessage.getBody();
+            }
+        };
+    }
+
+
+    /**
+     * 记录响应日志
+     * 通过 DataBufferFactory 解决响应体分段传输问题。
+     */
+    private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, OperLog operLog) {
+        ServerHttpResponse response = exchange.getResponse();
+        DataBufferFactory bufferFactory = response.bufferFactory();
+
+        return new ServerHttpResponseDecorator(response) {
+
+            @Override
+            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
+                if (body instanceof Flux) {
+                    // 获取响应类型,如果是 json 就打印
+                    String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
+
+                    if (this.getStatusCode().equals(HttpStatus.OK)
+                            && StrUtil.isNotBlank(originalResponseContentType)
+//                            && originalResponseContentType.contains("application/json")
+                    ) {
+
+                        Flux<? extends DataBuffer> fluxBody = Flux.from(body);
+                        return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
+
+                            // 合并多个流集合,解决返回体分段传输
+                            DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
+                            DataBuffer join = dataBufferFactory.join(dataBuffers);
+                            byte[] content = new byte[join.readableByteCount()];
+                            join.read(content);
+
+                            // 释放掉内存
+                            DataBufferUtils.release(join);
+                            String responseResult = new String(content, StandardCharsets.UTF_8);
+
+                            ResultData resultData = JSON.parseObject(responseResult, ResultData.class);
+                            if(resultData.getCode() == 0){
+                                operLog.setResult("操作成功");
+                                if(operLog.getUserId() == null){
+                                    JSONObject jsonObject = (JSONObject) resultData.getData();
+                                    operLog.setUserId( Long.valueOf(jsonObject.getString("id")));
+                                    operLog.setUserName(jsonObject.getString("userName"));
+                                    operLog.setNickName(jsonObject.getString("nickName"));
+                                }
+                            }else {
+                                operLog.setResult("操作失败");
+                            }
+
+                            return bufferFactory.wrap(content);
+                        }));
+                    }
+                }
+                // if body is not a flux. never got there.
+                return super.writeWith(body);
+            }
+        };
+    }
+}

+ 34 - 0
src/main/java/com/fdkankan/gateway/filter/ManageFilter.java

@@ -0,0 +1,34 @@
+package com.fdkankan.gateway.filter;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.cloud.gateway.filter.GatewayFilter;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.core.Ordered;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+@Slf4j
+@Component
+@RefreshScope
+public class ManageFilter implements GatewayFilter, Ordered {
+
+    @Autowired
+    CommonLogService commonLogService;
+
+
+    @Override
+    public int getOrder() {
+        return -201;
+    }
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+        exchange.getAttributes().put("@manageFilter", true);
+        return  commonLogService.filter(exchange,chain);
+    }
+
+
+}

+ 63 - 0
src/main/java/com/fdkankan/gateway/log/OperLog.java

@@ -0,0 +1,63 @@
+package com.fdkankan.gateway.log;
+
+import com.fdkankan.mongodb.base.BaseMongoEntity;
+import lombok.Data;
+import org.springframework.data.mongodb.core.mapping.Document;
+import org.springframework.data.mongodb.core.mapping.Field;
+
+/**
+ * 操作日志
+ */
+@Data
+@Document("OperLog")
+public class OperLog extends BaseMongoEntity{
+
+
+    /**
+     * 用户id
+     */
+    @Field("userId")
+    private Long userId;
+
+    /**
+     * 账号
+     */
+    @Field("userName")
+    private String userName;
+
+    /**
+     * 姓名
+     */
+    @Field("nickName")
+    private String nickName;
+
+    /**请求路径*/
+    @Field("requestPath")
+    private String requestPath;
+
+    /**请求url*/
+    @Field("uri")
+    private String uri;
+
+    /**请求方式*/
+    @Field("method")
+    private String method;
+
+    /**请求方式*/
+    @Field("params")
+    private String params;
+
+    /**请求ip*/
+    @Field("ip")
+    private String ip;
+
+    @Field("browser")
+    private String browser;
+
+    @Field("result")
+    private String result;
+
+    @Field("operationType")
+    private String operationType;
+
+}

+ 11 - 0
src/main/java/com/fdkankan/gateway/util/CacheUtil.java

@@ -0,0 +1,11 @@
+package com.fdkankan.gateway.util;
+
+import com.alibaba.fastjson.JSONObject;
+
+import java.util.HashMap;
+
+public class CacheUtil {
+
+   public static HashMap<String, JSONObject> manageMenuUrl = new HashMap<>();
+   public static HashMap<String, JSONObject> manageMenuId = new HashMap<>();
+}

+ 12 - 0
src/main/java/com/fdkankan/gateway/util/WebUtil.java

@@ -1,6 +1,8 @@
 package com.fdkankan.gateway.util;
 
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.useragent.UserAgent;
+import cn.hutool.http.useragent.UserAgentUtil;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.server.reactive.ServerHttpRequest;
 
@@ -35,4 +37,14 @@ public class WebUtil {
         ip = request.getRemoteAddress().getAddress().getHostAddress();
         return ip;
     }
+
+    public static  String getBrowser(ServerHttpRequest request){
+        HttpHeaders headers = request.getHeaders();
+        String userAgentStr = headers.getFirst("User-Agent");
+        UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
+        String browserType = userAgent.getBrowser().toString();
+        String browserVersion = userAgent.getVersion();
+        String browserFormat = "%s(版本%s)";
+        return  String.format(browserFormat, browserType, browserVersion);
+    }
 }