Springboot(3.0)->Springcloud(2022.0.1)踩坑指南

本文介绍了微服务项目的模块划分,包括公共模块、网关模块和服务模块。详细讲解了如何使用Nacos进行服务注册和发现,以及遇到的问题和解决方案。同时,文章还阐述了网关的配置,如路由转发和权限处理,特别是自定义拦截器的实现,用于权限验证和接口版本管理。最后提到了请求参数的接收方式。
摘要由CSDN通过智能技术生成

0、项目结构

在这里插入图片描述

0.1公共模块

里面包含一些基础的根据类,全局的参数定义,公共的数据结构等,可把service里的entity放到此处,所有模块共享,也在这里写入一些公共依赖,其他模块通过pom文件引入

		<dependency>
            <groupId>***.***</groupId>
            <artifactId>rosemanor-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>=

0.2网关模块

做所有请求的转发,外部请求都打到网管模块,在此处做权限验证等工作

0.3服务模块

在此处实现具体业务

1、nacos服务注册和服务发现

nacos版本要和springboot和springcloud对应,官网说明
服务注册后,在控制台看不到问题
在这里插入图片描述
在这里插入图片描述
得点击才能看到,在这个地方卡了几个小时,误以为没有注册上
nacos配置管理
在这里插入图片描述
把nacos相关配置放到bootstrap下,其优先级比application高

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        group:  ROSEMANOR
      #加载配置,一定要配置config信息
      config:
        server-addr: 127.0.0.1:8848 # Nacos 作为配置中心地址
        file-extension: yaml  #指定yaml格式的配置 yml会报错,nacos识别yaml
        # 加载配置才需要以下配置
        group: ROSEMANOR
  application:
    name: rosemanor-store
server:
  port: 4033
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <version>3.0.4</version> 
            <!--和springboot版本对应-->
        </dependency>

2、网关配置

spring:
  cloud:
    gateway:
      # 自定义参数,用于后续去掉公共前缀
      api-prefix: /rosemanor-api/rosemanor  
      discovery:
        locator:
          enabled: true
      routes:
        - id: rosemanor-third-service
          uri: lb://rosemanor-third-service
          predicates:
          	# 匹配的格式
            - Path=/rosemanor-api/rosemanor/wechat/**
          filters:
          	# 转发的时候去掉前缀
            - RewritePath=/rosemanor-api/rosemanor(?<segment>/?.*), $\{segment}

3、网关权限处理

3.1 拦截流程

定义拦截器,对请求进行拦截,将请求分为3类,一类内部请求,不做转发,一类公开请求,不做拦截,直接转发,一类带权限请求,进行权限判断
服务启动时,将服务下的所有接口信息写入redis,同时带上接口信息,对接口版本自增
网关判断前先比对url版本信息,版本信息没变,就用缓存的接口信息,避免每次都得从redis读取大量接口缓存,否则更新url信息
对url进行匹配,匹配后根据接口类型,对权限进行判断,做对应出来

3.2 拦截器代码

@Component
public class AuthFilter implements GlobalFilter, Ordered {

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    private int version = 0;

    private static final int innerPermission = -2;
    private static final int skipPermission = -1;

    @Value("${spring.cloud.gateway.api-prefix:/rosemanor-api/rosemanor}")
    private String prefix;
    
    // redis操作相关,根据自己的工具类来即可
    @Autowired
    private RedisUtil redisUtil;

    private Map<String, Integer> pathMap = new HashMap<>();

    @PostConstruct
    public void init() {
        updateUrl(-1);
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        try {
            int newVersion = (int) redisUtil.get(GlobalConstant.URL_VERSION);
            if (newVersion != version) {
                updateUrl(newVersion);
            }
        } catch (Exception ignored) {
        }
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        for (Map.Entry<String, Integer> e : pathMap.entrySet()) {
            if (antPathMatcher.match(e.getKey(), path)) {
                if (e.getValue() == innerPermission) {
                    System.out.println("reject");
                    return reject(exchange.getResponse());
                } else if (e.getValue() == skipPermission) {
                    System.out.println("skip");
                    return chain.filter(exchange);
                } else {
                	// 加上自己的逻辑
                    System.out.println("permission");
                    return chain.filter(exchange);
                }
            }
        }
        return reject(exchange.getResponse());
    }

    private Mono<Void> reject(ServerHttpResponse response) {
        //拦截
        response.setStatusCode(HttpStatus.FORBIDDEN);
        JsonObject message = new JsonObject();
        message.addProperty("success", false);
        message.addProperty("code", 28004);
        message.addProperty("data", "鉴权失败");
        byte[] bits = message.toString().getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //指定编码,否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        response.writeWith(Mono.just(buffer));
        return response.setComplete();
    }


    //影响的是全局过滤器的执行顺序,值越小优先级越高
    @Override
    public int getOrder() {
        return 0;
    }

    public synchronized void updateUrl(int newVersion) {
    	// 只有第一个进来的进行更新,后续的直接返回
        if (newVersion == version)
            return;
        try {
            version = Integer.parseInt(String.valueOf(redisUtil.get(GlobalConstant.URL_VERSION)));
        } catch (Exception e) {
            version = 0;
        }
        // GlobalConstant.URL_SET_NAME 自定义字符串标识,全局统一即可
        Map<Object, Object> map = redisUtil.hGetAll(GlobalConstant.URL_SET_NAME);
        Map<String, Integer> newMap = new HashMap<>();
        for (Map.Entry<Object, Object> e : map.entrySet()) {
            if (e.getValue().toString().equals(GlobalConstant.URL_INNER)) {
                newMap.put(prefix + e.getKey().toString(), innerPermission);
            } else if (e.getValue().toString().equals(GlobalConstant.URL_SKIP_AUTH)) {
                newMap.put(prefix + e.getKey().toString(), skipPermission);
            } else {
                try {
                    newMap.put(prefix + e.getKey().toString(), Integer.parseInt(String.valueOf(e.getValue().toString())));
                } catch (Exception exception) {
                    newMap.put(prefix + e.getKey().toString(), 0);
                }
            }
        }
        // 直接赋值新的map信息,之前的map弃用,防止多线程之间出现冲突
        pathMap = newMap;
    }

}

3.3 服务注册接口信息代码

springboot新版本使用了PathPattern代替了AntPathPattern,需要在配置文件内选择路径匹配的方式

spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
@Component
public class InitRunner implements ApplicationRunner {

    @Autowired
    private WebApplicationContext webApplicationContext;
    @Resource
    private RedisUtil redisUtil;

    @Override
    public void run(ApplicationArguments args) {
        // springboot3会有多个bean冲突,这里指定bean名称
        RequestMappingHandlerMapping mapping = webApplicationContext.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
        Annotation[] annotationArr;
        boolean isInnerInterface, isPermission, isSkipAuth;
        int permissionCode = 1;
        Set<String> path;
        for (Map.Entry<RequestMappingInfo, HandlerMethod> e : map.entrySet()) {
            isInnerInterface = false;
            isSkipAuth = false;
            isPermission = false;
            annotationArr = e.getValue().getMethod().getDeclaredAnnotations();
            for (Annotation annotation : annotationArr) {

                if (annotation.annotationType().getName().equals(InnerInterface.class.getName())) {
                    // 内部请求
                    isInnerInterface = true;
                    break;
                } else if (annotation.annotationType().getName().equals(SkipAuthentication.class.getName())) {
                    // 不做权限验证的请求
                    isSkipAuth = true;
                    break;
                } else if (annotation.annotationType().getName().equals(Permission.class.getName())) {
                    // 需要验证权限的请求
                    isPermission = true;
                    permissionCode = ((Permission) annotation).permissionCode();
                    break;
                }
            }
            if (isInnerInterface) {
                // 需选择ant_path_matcher,否则新版本的springboot获取到的为空
                if (e.getKey().getPatternsCondition() != null) {
                    path = e.getKey().getPatternsCondition().getPatterns();
                    for (String tempPath : path)
                        redisUtil.hSet(GlobalConstant.URL_SET_NAME, tempPath, GlobalConstant.URL_INNER);
                }
            } else if (isSkipAuth) {  // 不登录即可访问
                if (e.getKey().getPatternsCondition() != null) {
                    path = e.getKey().getPatternsCondition().getPatterns();
                    for (String tempPath : path)
                        redisUtil.hSet(GlobalConstant.URL_SET_NAME, tempPath, GlobalConstant.URL_SKIP_AUTH);
                }
            } else if (isPermission) {
                if (e.getKey().getPatternsCondition() != null) {
                    path = e.getKey().getPatternsCondition().getPatterns();
                    for (String tempPath : path)
                        redisUtil.hSet(GlobalConstant.URL_SET_NAME, tempPath, String.valueOf(permissionCode));
                }
            } else {   // 普通用户可以访问的请求,和跳过权限认证的区别是得进行身份认证(必须先登录)
                if (e.getKey().getPatternsCondition() != null) {
                    path = e.getKey().getPatternsCondition().getPatterns();
                    for (String tempPath : path)
                        redisUtil.hSet(GlobalConstant.URL_SET_NAME, tempPath, "0");
                }
            }
        }
        redisUtil.incr(GlobalConstant.URL_VERSION, 1);
    }
}

接口示例

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SkipAuthentication {
    int type() default 1;
}

在这里插入图片描述

4、请求参数接收

	"@RequestMapping" -> "@RequestParam"
	"@PostMapping" -> "@RequestBody"
	url参数 "/get-user/{uid}" -> "@PathVariable('uid') int uid"
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值