使用Spring cloud框架完成中小信息化项目

一. 中小项目特点和系统设计要点

中小软件项目有以下特点:

1. 中小项目系统通常部署在客户机房或者客户私有云池,少量部署在公有云

2. 系统访问客户为用户的系统管理员(管理配置系统),用户管理层(查看数据统计分析信息),用户员工(通常通过微信公众号和小程序访问特定模块),并发访问量不大,但业务逻辑可能比较复杂

3. 系统集成较多,需要集成大量外部系统和用户采购的硬件。软件后期修改项目较多

从软件架构四个维度分析:

1. 可用性:对数据保存性要求较高,需要做统计分析。对系统可用性要求普通,可承受短期服务不可用。故数据节点考虑备份,应用节点可以采用单点。

2. 可修改性:要求高,需要不停集成新第三方系统和新功能

3. 安全性:普通安全性要求,通过客户云池网闸防火墙控制外部访问,内部服务器安全由运维保证

4. 性能:中低性能要求,访问量不大。

采用第二代微服务技术开发,选择spring cloud作为微服务技术栈,前后端分离设计,后端只提供接口

二. Spring cloud框架相关技术

1. 注册中心eureka:每个微服务只需要知道注册中心的地址,向注册中心发送服务上线信息,并定期拉去服务列表就可以获取其他微服务的ip地址,编码时不用指定其他微服务的IP,可以使用rest template或者feign的方式实现RPC调用

2. API网关spring gateway:和spring cloud切合较好,配合eureka可以自动拉去微服务的rest接口,不需要代码配置。前端和客户端使用统一的网关地址可以访问所有微服务API

3. 配置中心nacos:阿里开源配置中心,相比本地用配置文件进行配置,集中配置中心可以实现可视化配置,集中配置,配置实时修改,配置共享等功能

4. API鉴权JWT token: 使用JWT token不使用cookie,一套接口实现前端,APP,第三方外部系统共用

三. 微服务和系统说明

1. eureka

在pom中增加eureka依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

main方法中增加eureka server注解:

@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }

}

其他微服务要注册到eureka需要在pom中配置依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

同时在配置文件中配置eureka地址:

eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${eureka.service.port}/eureka/
eureka.instance.prefer-ip-address=true
eureka.instance.hostname =${eureka.instance.hostname}

2. nacos配置中心

官方文档地址:https://nacos.io/zh-cn/docs/what-is-nacos.html

按文档部署好配置中心后,增加3个公共配置:

systemAdmin.properties中配置jwt token的加密密钥和jwt token的过期时间,redis.properties配置redis的连接鉴权信息,dataSource.properites配置mysql数据库的连接鉴权信息

在需要加载配置项的微服务中增加nacos依赖配置:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>0.2.1.RELEASE</version>
</dependency>

在bootstrap.properties中指定配置中心的ip地址以及加载的配置项:

spring.cloud.nacos.config.server-addr=${spring.cloud.nacos.config.server-addr}
spring.cloud.nacos.config.ext-config[0].data-id=systemAdmin.properties
spring.cloud.nacos.config.ext-config[0].refresh=true
spring.cloud.nacos.config.ext-config[1].data-id=dataSource.properties
spring.cloud.nacos.config.ext-config[2].data-id=redis.properties

在代码中使用配置项和普通配置文件配置没有区别使用@Value即可

3. gateway微服务

pom文件中配置spring gateway依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

配置文件中开启自动发现微服务接口和接口名称小写

spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lower-case-service-id=true

gateway中实现一个拦截器AuthFilter,对前端请求的jwt token进行解析,并调用system-admin服务判断该请求用户是否是合法用户并且具备该API的权限:

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String url = exchange.getRequest().getURI().getPath();
        //跳过不需要验证的路径
        if(Arrays.asList(skipAuthUrls).contains(url)){
            return chain.filter(exchange);
        }
        //从请求头中取出token
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        //未携带token
        if (token == null || token.isEmpty()) {
            ServerHttpResponse originalResponse = exchange.getResponse();
            originalResponse.setStatusCode(HttpStatus.OK);
            originalResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            byte[] response = "{\"code\": \"401\",\"msg\": \"no jwt token in request header\"}"
                    .getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = originalResponse.bufferFactory().wrap(response);
            return originalResponse.writeWith(Flux.just(buffer));
        }
        //取出token包含的身份
      String userId = restTemplate.getForEntity(
            "http://system-admin/auth/checkPermission?token=" + token + "&&apiUrl= " + url, String.class).getBody();
       if(userId == null || userId.isEmpty()){
            ServerHttpResponse originalResponse = exchange.getResponse();
            originalResponse.setStatusCode(HttpStatus.OK);
            originalResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            byte[] response = "{\"code\": \"10002\",\"msg\": \"invalid token.\"}"
                    .getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = originalResponse.bufferFactory().wrap(response);
            return originalResponse.writeWith(Flux.just(buffer));
        }
        if(userId.equals("NoPermission")) {
           ServerHttpResponse originalResponse = exchange.getResponse();
            originalResponse.setStatusCode(HttpStatus.OK);
            originalResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            byte[] response = "{\"code\": \"401\",\"msg\": \"this user do not have the access permission\"}"
                    .getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = originalResponse.bufferFactory().wrap(response);
            return originalResponse.writeWith(Flux.just(buffer));
        }
        //将现在的request,添加当前身份
        ServerHttpRequest mutableReq = exchange.getRequest().mutate().header("Authorization-UserId", userId).build();
        ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
        return chain.filter(mutableExchange);
    }

 

API白名单过滤中,设置登录和获取验证码接口可以不经过鉴权直接访问

4. system-admin鉴权微服务

system-admin主要提供鉴权相关接口,登录,验证码获取,校验权限等功能

(1)获取图片验证码

@GetMapping("/getCode")
	@ApiOperation(value = "获取图片验证码(有效期为60秒),返回数据为jpg图片", consumes = "application/x-www-form-urlencoded")
	public void getCode(HttpServletResponse response, HttpServletRequest request) throws IOException {
		String verifyCode = VerifyCodeUtil.generateVerifyCode(4);
		String uuid = UUID.randomUUID().toString().replaceAll("-", "");
		stringRedisTemplate.opsForHash().put(Constants.REDIS_PREFIX_IMG_CODE_UUID + uuid, "code", verifyCode);
		stringRedisTemplate.expire(Constants.REDIS_PREFIX_IMG_CODE_UUID + uuid, 60, TimeUnit.SECONDS);
		response.addHeader("imgcode-uuid", uuid);
		VerifyCodeUtil.outputImage(214, 80, response.getOutputStream(), verifyCode);
	}

由于不使用cookie,需要在图片验证码返回头中增加一个id,前端登录需要把这个id和图片验证码一起发回

(2)登录接口,校验用户名密码,生产JWT token返回给前端

(3)jwt token刷新接口,刷新token并把老的token放入黑名单

(4)检查权限接口,校验当前用户是否是合法用户并且具备相应的API访问权限

5. service1业务微服务demo

一个业务微服务的demo,打印一个hello world。构建完毕后通过API网关直接访问:localhost:30002/server1/hello,返回无权限。先获取验证码,然后登录获取token后,在访问请求的header中增加

Authorization: [你获取的token]

再次访问hello接口,返回打印的hello world

四. 完整demo源代码

git地址: https://github.com/liuxiang19870216/lxdemo_springcloud

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值