springcloud微服务拆分实战之大二(下)java期末大作孽

Huterox      2022-06-06     795

关键词:

文章目录

前言

由于时间关系,这次期末作孽只能做一个非常简单的玩意,由于期末作业可以在阶段作品的基础上进行优化,于是我有个大胆的想法,为什么我不阔以直接把SpringBoot 构建的单体项目拆分为微服务架构,这个也是一个升级嘛,顺便复习一下微服务。

本次微服务拆分的案例是:嘿从零开始基于SpringBoot 打造在线聊天室(4.4W字最长博文)

(PS:主要是为了对付期末作业,如果没事的话,别这样乱拆)

这个是基于vue + springBoot 整合mybatis websocket 做的在线聊天室(这里直接使用Map来代替redis,所以直需要mysql做存储)

由于我们只是对后端进行技术升级,所以,我们前端基本上不用动,甚至连接口都不用换。这充分体现了前后端分离的第一个好处呀。那第二个好处咧?当然是基于vue更加方便去嫖组件呀!

所以页面还是这个样子:

这个样子

and this:

项目创建

废话不多说,我们重新创建我们的项目。这部分内容可以参考这两篇博客,我这里就不复述了。

SpringCloud微服务项目搭建流程

SpringCloud基本微服务构建(Eureka+GateWay)

我们这里先创建出四个module, 分别是登录,注册,Eureka,GateWay。

这里呢,我把登录和注册拆分为两个模块了,也就是这样。

之所以这样拆呢其实也很简单,如果后面有微信登录,注册,qq邮箱等等第三方验证登录的时候,业务肯定很复杂,所以这两个模块还是需要拆分的。

但是在这里的话,又有个问题,因为登录和注册在很多方面又是相互耦合的,例如User。你登录的时候需要User实体,同样注册也需要呀,如果拆分,那么可能就需要每一个模块都需要User,例如在这里:

那么这里的话如果不优化,那么就会出现代码重复。

那么如何优化呢,其实也很简单,我们单独把User服务再拆分出来,也就是这样:

不过,这里为了省事,我就不拆了,毕竟这玩意我就是闹着玩,没办法交个期末作业,而且我这样做压根就不算优化。

登录/注册拆分

这里忘了说了,我这里拆的话还是每一个服务在共用同一个数据库,懒得再拆表了。

注册模块

注册模块的话比较好拆分
因为用到的东西很少。

就是做一个简单的插入

Controller代码


@Controller
@ResponseBody
public class Register 

    @Autowired
    RegisterMessage registerMessage;
    @Autowired
    UserService userService;

    @PostMapping("/register")
    public RegisterMessage Register(@RequestBody Map<String, Object> userMap) throws Exception 
        String account = (String) userMap.get("account");
        String username = (String)userMap.get("username");
        String password = (String) userMap.get("password");
        if(account!=null && password!=null)
            userService.addUser(account,username,password);
            registerMessage.setFlag(1);
        else 
            registerMessage.setFlag(-1);
        
        return registerMessage;
    


Server代码

@Service
public class UserService 

    @Autowired
    UserMapper userMapper;
    public User selectUserById(Integer id)return userMapper.selectUserById(id);
    public User selectUserByAccount(String account)
        return userMapper.selectUserByAccount(account);
    
    public User selectUserByAccountAndPassword(String account,String password)
        return userMapper.selectUserByAccountAndPassword(account,password);
    
    @Transactional(rollbackFor = Exception.class)
    public int addUser(String account,String username,String password) throws Exception 
//        发生异常回滚
        int flag = 1;

        try 
            userMapper.AddUser(account, username, password);
        catch (Exception e)
            flag = -1;
            throw new Exception("用户添加异常");
        
        return flag;
    


就这两个核心的。

登录模块

这个登录模块就稍微复杂了一点,因为还有个token生成,并且登录后进入聊天室也是在这里做的,所以,功能复杂一点。

对比原来的项目,就少了个聊天模块

然后这里保留的话,也是保留了比较多的东西的

拦截器保留

持久层保留

这部分的话也是,其实就是保留User和Friend,看过原来的单体项目的应该知道,这个friend其实室没事用的,本来要做的,但是没去做,以后再说吧。

服务层

之后的话,服务层和注册的不太一样,不过也是原来单体架构里面的服务层一样的


@Service
public class UserService 

    @Autowired
    UserMapper userMapper;
    public User selectUserById(Integer id)return userMapper.selectUserById(id);
    public User selectUserByAccount(String account)
        return userMapper.selectUserByAccount(account);
    
    public User selectUserByAccountAndPassword(String account,String password)
        return userMapper.selectUserByAccountAndPassword(account,password);
    
    @Transactional(rollbackFor = Exception.class)
    public int addUser(String account,String username,String password) throws Exception 
//        发生异常回滚
        int flag = 1;

        try 
            userMapper.AddUser(account, username, password);
        catch (Exception e)
            flag = -1;
            throw new Exception("用户添加异常");
        
        return flag;
    


至于工具类,这个就保留了两个

网关GateWay配置

做完这些之后的话,还需要再配置一下网关,主要是为了做跨域。
两个方案。

一个是配置文件:


      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8090"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期

还有一个,自然就是配置类

@Configuration
public class CorsConfig 
    @Bean
    public CorsWebFilter corsFilter() 
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");//允许所有请求头
        config.addAllowedOrigin("*");//允许所有请求方法,例如get,post等
        config.addAllowedHeader("*");//允许所有的请求来源
        config.setMaxAge(360000L);
        config.setAllowCredentials(true);//允许携带cookie

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);//对所有经过网关的请求都生效

        return new CorsWebFilter(source);
    


前端设置

这里做了之后我们前端还要再设置一下,让请求走网关。

我的网关的完整配置如下:

server:
  port: 8000


spring:
  application:
    name: gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true

      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8090"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期

      routes:
        - id: loginclient
          uri: lb://loginclient
          predicates:
            - Path=/loginClient/**
          filters:
            - StripPrefix=1

        - id: registerclient
          uri: lb://registerclient
          predicates:
            - Path=/registerClient/**
          filters:
            - StripPrefix=1




eureka:
  client:
    service-url:  # eureka的地址信息
      defaultZone: http://127.0.0.1:10086/eureka

然后再我的前端的话也是原来做了跨域处理的,但是GateWay的话需要重新设置一下,主要是让GateWay通过一下Axios请求

然后在我的登录,注册模块就是这样的:

到这里的话我们的登录注册功能其实就改造完成了。接下来是聊天改造。

聊天拆分

这部分也是分两个部分,一个是聊天的信息加载,还有一个是,websocket 网络聊天。

聊天拆分的话,这里我们将引入RabbitMQ,用来做聊天信息的持久化,原来是,你发了我就存,这样做可以,但是qps多了就炸了,所以引入消息队列。当然这里的话我们还是使用mysql来做存储,实际上我们应该使用mangodb的,但是作业要求是mysql,而且mysql也可以勉强完成工作,大不了SpringBoot 那里再开启缓存嘛,降低一下查询压力(只要没有发送新消息) 而且这里还能再拆分,那就是在线聊天人员统计,管理这一块,不过不能再搞了,因为容易被锤~

当然最重要的原因是:

一个SpringBoot 起来就烧掉我起码100MB 的内存。技术除了牛逼之外,我觉得省钱也是很重要的。
(这也是为什么要搞个好点的电脑搞开发,不然你连idea可能都养不起)

那么这里的话,其实还是要简单一点,当然本来我是打算这样的:

但是成萧何,败也萧何,当初SpringBoot单体项目构建的时候图方便,没怎么考虑,就是单纯用Map实现,然后用Session做服务验证。没有考虑使用redis存储,而在我们分布式微服务当中,如果我们把服务拆分了,那么对应的是不同的域名(端口) 这样一来,session无法做到跨域。如下图:

而解决方法也是redis,SpringBoot 提供了一套方案,把不同的域名的session存起来,然后设置那些域名公用一套session,主要是里面的值嘛(当然我们也可以自己实现,提取Module组件,搞一个全局Map存session)

Login服务保留聊天(跨域问题)

所以没有办法,我们任然需要Login服务保留聊天服务,这个时候也许叫Login服务已经不合适了,因为这哪是Login服务呀,但是这里我们就先姑且叫这个为Login服务吧,不改了,费劲。

完整的项目如下:

我们直接保留聊天的在LoginClient里面,于是:

那么这个时候,就不用做拆分了嘛,当然不是。

别忘了,我们存储聊天记录的时候是即使的,这样做是很容易蹦掉的,所以,这里我们引入消息队列。

引入RabbitMQ

我们专门对LoginClient服务做如下改动
首先是配置

<!--        消息转化器-->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>





        <!-- amqp 消息队列依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

然后是配置类,这个主要是用来做消息对象序列化转为json的,因为默认是传输Java对象的时候会变成序列化字符。这里为了节约空间和资源,转为json


@Configuration
public class JsonMQ 

    @Bean
    public MessageConverter jsonMessageConverter()

        return new Jackson2JsonMessageConverter();
    


最后是我们不在我们这里进行存储了,而是专门让一个服务存储

RabbitMQ配置

我在RabbitMQ 里面专门创建了一个用户

  rabbitmq:
    host: 127.0.0.1
    port: 5672
    virtual-host: /huterox
    username: huterox
    password: 865989840

在消息发送者,和消费者里面都这样设置就完了。

消费者存储

我们这个时候看到这个玩意

其实没啥,就是把原来消息存储的那些Server搞过来了

此外,我们还要在这里创建一个队列,当然也可以直接用注解,只是我这里习惯配置类,

总结

到此,一个很简单的微服务拆分案例就无了,不过现在这个拆分方法并不好,最理想的方案是拆分,5个微服务,登录,注册,用户管理,聊天,聊天存储。然后用户管理服务既作为一个服务,也作为一个组件Moudle,当然这里也能单独把用户相关的pojo,之类的单独作为一个组件,然后登录,注册,用户管理(CURD)来使用它,然后三者用feign通信。 然后就是聊天和存储,这个就是消息队列干的事情,不过他们之间还需要一个聊天消息的服务可以拆出来,或者拆出来一个组件,至于跨域就在用redis。

但是我这里不这样干,原因很简单,本来没啥好拆的,我就是单纯为了期末作业,并且期末作业要演示,用的东西多了我本地不好演示,我的内网服务器没法在教室访问,因为网段不一样,网络服务器,没有小钱钱了啦丫!

微服务架构整理-(七springcloud实战之resttemplate)(代码片段)

SpringCloud实战之RestTemplateGET请求getForEntitygetForObjectPOST请求postForEntitypostForObjectpostForLocationPUT请求DELETE请求总结RestTemplate不仅在SpringCloud中会使用,一般在服务之间相互调用的时候会使用此类。上一篇介绍Ribbon的时候通过在此类... 查看详情

微服务之springcloud干货:springcloud简介

什么是微服务架构  微服务架构就是系统架构设计的一种风格,它主旨将一个独立的系统,拆分成各个微服务,各个微服务独立运行,他们之间通过Http的RestfulAPI进行通信,拆分出来的微服务是根据原系统高耦合部分进行构建... 查看详情

推荐7个牛哄哄springcloud实战项目(代码片段)

把一个大型的单个应用程序和服务拆分为数个甚至数十个的支持微服务,这就是微服务架构的架构概念,通过将功能分解到各个离散的服务中以实现对解决方案的解耦。关于微服务相关的学习资料不多,而GitHub上的开源项目可以... 查看详情

微服务架构整理-(十springcloud实战之hystrix[3])(代码片段)

SpringCloud实战之Hystrix[3]-看版监控前言搭建Dashboard服务创建springboot工程添加依赖配置启动类添加配置文件引用Dashboard服务添加Hystrix依赖添加springboot服务监控依赖配置springboot监控接口的访问权限结果总结前言当需要查看服务的状... 查看详情

微服务架构整理-(十springcloud实战之hystrix[3])(代码片段)

SpringCloud实战之Hystrix[3]-看版监控前言搭建Dashboard服务创建springboot工程添加依赖配置启动类添加配置文件引用Dashboard服务添加Hystrix依赖添加springboot服务监控依赖配置springboot监控接口的访问权限结果总结前言当需要查看服务的状... 查看详情

微服务架构整理-(六springcloud实战之ribbon)

SpringCloud实战之Ribbon负责均衡介绍Ribbon概念Ribbon中的负载均衡算法Ribbon使用添加一个服务提供者添加Ribbon区别服务提供者访问服务提供者运行结果总结负责均衡介绍负载均衡分为硬件负载均衡和软件负载均衡。常用见的如下:... 查看详情

微服务架构整理-(八springcloud实战之hystrix[1])(代码片段)

SpringCloud实战之Hystrix[1]-基本用法前言Hystrix概念Hystrix使用添加依赖配置启动类添加熔断机制服务降级超时异常总结前言我们知道,在微服务架构中,每个服务都是独立运行,服务与服务之间通过注册中心彼此发现和消... 查看详情

微服务架构整理-(八springcloud实战之hystrix[1])(代码片段)

SpringCloud实战之Hystrix[1]-基本用法前言Hystrix概念Hystrix使用添加依赖配置启动类添加熔断机制服务降级超时异常总结前言我们知道,在微服务架构中,每个服务都是独立运行,服务与服务之间通过注册中心彼此发现和消... 查看详情

微服务架构整理-(九springcloud实战之hystrix[2])

SpringCloud实战之Hystrix[2]-自定义异常熔断处理自定义熔断机制测试同步调用异步调用总结上文中,对异常处理是使用注解@HystrixCommand完成的,当然也可以不使用此注解来完成,我们可以自定义一个机制处理超时,... 查看详情

springcloud实战微服务之——微服务简介以及入门使用(代码片段)

微服务概述微服务是什么?微服务解决了什么问题?微服务有什么特点?单体架构是什么?一个归档包包含了应用所有功能的应用程序,我们通常称之为单体应用。架构单体应用的架构风格,我们称之为单... 查看详情

微服务架构整理-(十二springcloud实战之zuul网关)(代码片段)

SpringCloud实战之Zuul网关Zuul概念Zuul构建网关创建SpringBoot工程添加依赖添加注解添加配置文件基础配置注册Eureka添加路由规则Zuul请求过滤定义过虑逻辑请求结果Zuul路由规则总结Zuul概念Zuul是Netflix旗下的又一重要成员,是一个... 查看详情

微服务架构整理-(六springcloud实战之ribbon)(代码片段)

SpringCloud实战之Ribbon负责均衡介绍Ribbon概念Ribbon中的负载均衡算法Ribbon使用添加一个服务提供者添加Ribbon区别服务提供者访问服务提供者运行结果总结负责均衡介绍负载均衡分为硬件负载均衡和软件负载均衡。常用见的如下:... 查看详情

springcloud实战微服务之——ribbon详解(代码片段)

Ribbon简介需要解决的问题:① 如何在配置EurekaClient注册中心时不去硬编码Eureka Server的地址?② 在微服务不同模块间进行通信时,如何不去硬编码服务提供者的地址?③当部署多个相同微服务时,如何实现... 查看详情

微服务架构整理-(九springcloud实战之hystrix[2])(代码片段)

SpringCloud实战之Hystrix[2]-自定义异常熔断处理自定义熔断机制测试同步调用异步调用总结上文中,对异常处理是使用注解@HystrixCommand完成的,当然也可以不使用此注解来完成,我们可以自定义一个机制处理超时,... 查看详情

springcloud实战微服务之——服务发现与服务注册

服务发现与服务注册——基本概念(一)服务发现与服务注册如何解决硬编码问题?上次的实例中是采用的硬编码,实际上是不可取的。因为在实际开发中,服务组件的端口和IP都是动态变化的。一旦一个组件... 查看详情

springcloud微服务安全实战_6-1_微服务之间的通讯安全之概述

   到目前为止已经实现了一个基于微服务的,前后端分离(这里我用的jquery做的,并不是真的前后端分离,因为我不会vue和angular所以没用)的架构。在网关上做了限流、认证、审计、授权等安全机制,在前端应用上... 查看详情

springcloud微服务安全实战_6-2_jwt认证之认证服务改造

一、认证服务器上发Token的改造:uuid字符串改造为JWT之前生成的token都是一个无意义的字符串uuid,也存在着上一篇https://www.cnblogs.com/lihaoyang/p/12203586.html 所说的几个问题。本篇就要把token改造成JWT。在认证服务器配置类Authoriza... 查看详情

springcloud微服务安全实战_3-8_api安全之登录

 前面的文章 https://www.cnblogs.com/lihaoyang/p/11967121.html 说了用过滤器实现HttpBasic认证,在请求头里携带用户名和密码,存在的问题是,你不可能让用户每个请求都输入用户名密码吧,即使前端把用户名密码存起来,这也... 查看详情