微服务实战项目_天机学堂01_初识项目

文章目录

  • 一.项目简述
  • 二.Jenkins
  • 三.模拟真实业务:紧急bug修复和代码阅读
  • 四.测试和部署
  • 五.代码阅读-获取登录用户


一.项目简述

Q:天机学堂是什么?

A:天机学堂是一个基于微服务架构的生产级在线教育项目

主要有两个端(项目已上线,可以点击查看):

  • 管理后台: https://tjxt-admin.itheima.net 其核心业务主体包括老师、管理员、其他员工,核心业务围绕着老师展开

  • 用户端:链接: https://tjxt-user.itheima.net/#/main/index 其核心业务主体就是学员,所有业务围绕着学员的展开


系统架构图

在这里插入图片描述

技术架构图

在这里插入图片描述


开发模式

一个企业微服务项目,往往包含数十个不同的微服务模块。参与开发的工作人员也从几人到数百人不等。 每个开发人员或者开发小组负责开发部分微服务模块,分工合作,完成整个微服务项目开发。

天机学堂项目组: 后端5人(包含1个开发组长), 前端2人,测试2人, UI 1人,架构师1人。目前没有移动端,只有PC端。

在这里插入图片描述

图片中打○的是部分完成,需要继续开发;打×的是没有开发,需要从基础开发

补充:企业开发环境一般分为本地环境、测试环境、生产环境:

  • 本地开发环境(loacl):自己的电脑环境

  • 测试/开发环境(test/dev):在内网中搭建的一套大家都可以访问使用的环境

  • 生产环境(prod):最终给用户使用的环境


模拟企业开发环境

为了模拟企业中的开发环境,所以我们需要通过VMware导入linux虚拟机,该虚拟机中已经安装了课程中所需要的各种环境,包括,git、maven私服、mysql、RabbitMQ等。


二.Jenkins

首先了解这三个概念:

持续集成(Continuous Integration,CI):
CI 是一种软件开发实践,它要求团队成员将其工作频繁地集成到共享存储库中。每次代码提交后,自动触发构建和测试过程,以确保新代码与现有代码集成,不会导致破坏或错误。CI的目标是尽早发现和解决集成问题,以减少集成阶段的错误。

持续交付(Continuous Delivery,CD):
CD 是在CI的基础上构建的概念。它强调不仅要频繁集成代码,还要自动化部署和测试过程,以便能够随时准备好发布软件。持续交付的目标是确保在任何时候都能够生成可靠的、可部署的软件版本,并在需要时快速地交付给用户。但它并不一定要求立即部署到生产环境。

持续部署(Continuous Deployment,CD):
持续部署是CD实践的更进一步,它要求所有通过CI和CD流程的代码变更都自动部署到生产环境,没有人工干预。这意味着经过测试的代码在通过CI和CD流水线后,自动地、立即地部署到生产环境。持续部署的目标是尽量减少发布的手动步骤,从而降低出错的可能性,并能够更迅速地将新功能、修复和改进推送到用户。

持续集成的优点

  • 自动构建、发布、测试

  • 降低风险


Jenkins,原名Hudson,2011年改为现在的名字 它是一个开源的实现持续集成的软件工具。

官方网站:链接: http://jenkins-ci.org/

先了解Jenkins工作流程:

在这里插入图片描述

1.当在idea上提交和推送代码后,会触发Gogs中Web钩子

举个例子:
gogs上配置, 也就是说当我们有代码提交到gogs, 就会触发 http://192.168.150.101:18080/gogs-webhook/?job=tjxt-dev-build

在这里插入图片描述

2. http://192.168.150.101:18080/gogs-webhook/?job=tjxt-dev-build对应的就是Jenkin的任务

在这里插入图片描述

该任务会根据推送的代码重新集成、测试和部署(先将项目打成jar包后构建docker镜像)整个项目,保持我们的虚拟机(在公司中是服务器)上的项目持续更新


项目结构:

在这里插入图片描述


代码规范

  • 项目结构:目前企业微服务开发项目结构有两种模式

    a. 每个项目对应每一个微服务,需要创建一个Project,尽可能降低耦合

    b. 每个项目创建一个Project ,项目下的多个微服务是Project下的Module,方便管理(天机学堂采取的方式)

  • 实体类规范
    a. DTO:数据传输对象,在客户端与服务端间传递数据,例如微服务之间的请求参数和返回值、前端提交的表单

    b. PO:持久层对象,与数据库表一一对应,作为查询数据库时的返回值

    c. VO:视图对象,返回给前端用于封装页面展示的数据

    d. QUERY:查询对象,一般是用于封装复杂查询条件

  • 接口文档规范
    项目开发注释必不可少,而controller接口紧紧加注释还不够,最好把接口形成规范化文档。一方面方便其它微服务来查看接口,另一方面也方便自己基于接口做测试。天机学堂中采用了swagger来实现接口文档。

  • 依赖注入: Spring提供了依赖注入的功能,方便我们管理和使用各种Bean,常见的方式有:

    a. 字段注入(@Autowired 或 @Resource)

    b. 构造函数注入

    c. set方法注入


配置文件

在这里插入图片描述


异常处理

在项目运行过程中,或者业务代码流程中,可能会出现各种类型异常,为了加以区分,我们定义了一些自定义异常对应不同场景:

在这里插入图片描述

在开发业务的过程中,如果出现对应类型的问题,应该优先使用这些自定义异常。

当微服务抛出这些异常时,需要一个统一的异常处理类,同样在tj-common模块中定义了:

package com.tianji.common.autoconfigure.mvc.advice;

import com.tianji.common.constants.Constant;
import com.tianji.common.domain.R;
import com.tianji.common.exceptions.CommonException;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.WebUtils;
import feign.FeignException;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.util.NestedServletException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;

@RestControllerAdvice
@Slf4j
public class CommonExceptionAdvice {

    @ExceptionHandler(DbException.class)
    public Object handleDbException(DbException e) {
        log.error("mysql数据库操作异常 -> ", e);
        return processResponse(e.getStatus(), e.getCode(), e.getMessage());
    }

    @ExceptionHandler(CommonException.class)
    public Object handleBadRequestException(CommonException e) {
        log.error("自定义异常 -> {} , 状态码:{}, 异常原因:{}  ",e.getClass().getName(), e.getStatus(), e.getMessage());
        log.debug("", e);
        return processResponse(e.getStatus(), e.getCode(), e.getMessage());
    }

    @ExceptionHandler(FeignException.class)
    public Object handleFeignException(FeignException e) {
        log.error("feign远程调用异常 -> ", e);
        return processResponse(e.status(), e.status(), e.contentUTF8());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getAllErrors()
                .stream().map(ObjectError::getDefaultMessage)
                .collect(Collectors.joining("|"));
        log.error("请求参数校验异常 -> {}", msg);
        log.debug("", e);
        return processResponse(400, 400, msg);
    }
    @ExceptionHandler(BindException.class)
    public Object handleBindException(BindException e) {
        log.error("请求参数绑定异常 ->BindException, {}", e.getMessage());
        log.debug("", e);
        return processResponse(400, 400, "请求参数格式错误");
    }

    @ExceptionHandler(NestedServletException.class)
    public Object handleNestedServletException(NestedServletException e) {
        log.error("参数异常 -> NestedServletException,{}", e.getMessage());
        log.debug("", e);
        return processResponse(400, 400, "请求参数异常");
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public Object handViolationException(ConstraintViolationException e) {
        log.error("请求参数异常 -> ConstraintViolationException, {}", e.getMessage());

        return processResponse( HttpStatus.OK.value(), HttpStatus.BAD_REQUEST.value(),
                e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).distinct().collect(Collectors.joining("|"))
                );
    }

    @ExceptionHandler(Exception.class)
    public Object handleRuntimeException(Exception e) {
        log.error("其他异常 uri : {} -> ", WebUtils.getRequest().getRequestURI(), e);
        return processResponse(500, 500, "服务器内部异常");
    }

    private Object processResponse(int status, int code, String msg){
        // 1.标记响应异常已处理(避免重复处理)
        WebUtils.setResponseHeader(Constant.BODY_PROCESSED_MARK_HEADER, "true");
        // 2.如果是网关请求,http状态码修改为200返回,前端基于业务状态码code来判断状态
        // 如果是微服务请求,http状态码基于异常原样返回,微服务自己做fallback处理
        return WebUtils.isGatewayRequest() ?
                R.error(code, msg).requestId(MDC.get(Constant.REQUEST_ID_HEADER))
                : ResponseEntity.status(status).body(msg);
    }
}

需要说明的是:

@RestControllerAdvice 是 Spring Boot 中用于全局处理异常的注解。它是在 Spring MVC 中的 @ControllerAdvice 注解的基础上,专门用于处理 RESTful 服务的异常情况。

在一个 Spring Boot 应用中,@RestControllerAdvice 注解的类可以包含多个被 @ExceptionHandler 注解修饰的方法,这些方法用于处理不同类型的异常。当在控制器(@RestController 注解的类或方法)中抛出异常时,@RestControllerAdvice 注解的类中匹配的异常处理方法会被调用,从而实现全局异常处理的功能。


三.模拟真实业务:紧急bug修复和代码阅读

1.BUG复现

使用Jack登录,能删除自己购买的课程(订单); 使用Rose登录,不能够删除自己的购买的课程;

2.分析

阅读代码

如果是我们自己写的代码,肯定很容易找到业务入口、整个业务线路。但现在我们是接手他人项目,所以只能通过其它途径来梳理业务:

  1. 如果开发业务的同事还在,直接与开发该业务的同事交流

  2. 如果开发者已离职,可以查看相关接口文档

  3. 如果没有文档,也可以查看前端请求,顺藤摸瓜

3.根据流程找到bug出处(很重要的思路)

在这里插入图片描述

  1. 找到到请求入口:在企业内开发,我们是接手他人代码,可以通过交流弄清楚入口。否则就只能通过前端入手。

  2. 理清请求链路:找到入口后,先总览项目结构,弄清楚前端请求如何抵达微服务,经过了哪些地方,这对解决BUG会有帮助。

  3. 紧跟主线:源码非常繁杂,不要试图弄清楚每一个细枝末节。紧跟业务主线,先梳理整体脉络,形成整体业务流程图。

  4. 步步深入:最后,基于业务场景,定位核心问题点,抽丝剥茧,展开看来阅读细节,寻找问题所在。

我们可以根据流程思路按照之前我们的环境部署方案,api.tianji.com这个域名会被解析到192.168.150.101这个地址,然后被Nginx反向代理到网关微服务。而网关则会根据请求路径和路由规则,把请求再路由到具体微服务。这里请求路径以/ts开头,对应的微服务是trade-service,也就是交易微服务。这样,整个请求链路就比较清楚了:

在这里插入图片描述

通过我们的分析,请求的入口是tj-trade服务下的deleteOrder()接口

4.分析找到出错接口后,就可以使用debug来调试,找出具体出错的代码了

部署本地服务

1.debug启动本地tj-trade服务(注意:配置走local环境)

2.让测试环境网关路由到本地,只需要在本地启动服务,然后将测试环境的tj-trade服务权重设置为0或者停止

权重设置为0:

在这里插入图片描述
停止服务:

docker stop tj-trade

使用我们本地的微服务后,就可以使用断点调试了:

1.在OrderServiceImpl的deleteOrder方法加断点

在这里插入图片描述
2.分别使用Rose和Jack登录删除自己的订单

3.我们发现当是Rose登录时候,进入到了if里面, Rose的userId是129

在这里插入图片描述

4.userId使用的是包装类型Long, 当值>128时候,不能用!=判断是否相等了

具体来说,Long类型中使用了享元的机制,当数字在[-128,127]之间,Long是同一个对象即范围内的整数值都是缓存中的同一个实例,故jack删除订单会显示删除成功,而rose在范围之外,使用==对比的是两个对象的地址,故返回不能删除他人的订单

在这里插入图片描述

5.解决BUG

在这里插入图片描述


四.测试和部署

测试流程

在这里插入图片描述

  1. 本地单元测试:以代码块、方法作为测试的最小单元,测试基本功能是否健康
  2. 本地接口测试:在本地测试整个接口是否按照预期方式运行。一般可以采用Swagger或者PostMan来测试
  3. 组件测试:如果业务跨多个微服务,则需要与多微服务组件联合来做组件测试
  4. 开发环境联调: 最后,将项目部署到开发环境,做前后端联调。然后做测试环境、预发布环境联调

部署

最后,测试没有问题,我们就可以将代码部署到测试环境去了。

我们在Jenkins中配置了web钩子,代码推送后自动触发构建。不过需要注意的是,默认情况下我们推送的代码不管是哪个分支都会触发构建,而且构建默认是基于lesson-init分支,需要重新配置。


五.代码阅读-获取登录用户

我们在解决订单bug的时候看到了一行代码: Long userId = UserContext.getUser() 那么,这是从哪里获取用户信息呢?

1.实现思路

天机学堂是基于JWT实现登录的,登录信息就保存在请求头的token中。因此要获取当前登录用户,只要获取请求头,解析其中的token即可。

但是,每个微服务都可能需要登录用户信息,在每个微服务都做token解析就属于重复编码了。因此我们的把token解析的行为放到了网关中,然后由网关把用户信息放入请求头,传递给下游微服务。

在这里插入图片描述

每个微服务要从请求头拿出用户信息,在业务中使用,也比较麻烦,所以我们定义了一个HandlerInterceptor,拦截进入微服务的请求,并获取用户信息,存入UserContext(底层基于ThreadLocal)这样后续的业务处理时就能直接从UserContext中获取用户了:

在这里插入图片描述

2.网关鉴权

接下来我们一起来看看具体实现的代码。

首先是网关登录校验、传递用户信息的逻辑,tj-gateway中的AccountAuthFilter:

在这里插入图片描述

可以看到,网关将登录的用户信息放入请求头中传递到了下游的微服务。因此,我们只要在微服务中获取请求头,即可拿到登录的用户信息。

3.用户信息上下文

然后是微服务中的获取请求头中的用户信息的拦截器。由于这个拦截器在每个微服务中都需要,与其重复编写,不如抽取到一个模块中。

所以在tj-auth模块中,有一个tj-auth-resource-sdk模块,已经把拦截器定义好了:

具体代码如下:

@Slf4j
    public class UserInfoInterceptor implements HandlerInterceptor {

        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            // 1.尝试获取头信息中的用户信息
            String authorization = request.getHeader(JwtConstants.USER_HEADER);
            // 2.判断是否为空
            if (authorization == null) {
                return true;
            }
            // 3.转为用户id并保存到UserContext中
            try {
                Long userId = Long.valueOf(authorization);
                UserContext.setUser(userId);
                return true;
            } catch (NumberFormatException e) {
                log.error("用户身份信息格式不正确,{}, 原因:{}", authorization, e.getMessage());
                return true;
            }
        }

        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            // 清理用户信息
            UserContext.removeUser();
        }
    }

在这个拦截器中,获取到用户信息后保存到了UserContext中,这是一个基于ThreadLocal的工具,可以确保不同的请求之间互不干扰,避免线程安全问题发生

4.小结

梳理一下,登录信息传递的过程是这样的:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/328719.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

项目配置集成unocss指南

项目配置集成 unocss 指南 什么是 UnoCSS? Unocss 是一个基于 Tailwind CSS的工具 ,它通过静态分析 HTML 和 CSS 代码,自动消除未使用的样式,以减小生成的 CSS 文件大小。这个工具可以帮助开发者在使用 Tailwind CSS 进行开发时…

【linux】visudo

碎碎念 visudo命令是用来修改一个叫做 /etc/sudoers 的文件的,用来设置哪些 用户 和 组 可以使用sudo命令。并且使用visudo而不是使用 vi /etc/sudoers 的原因在于:visudo自带了检查功能,可以判断是否存在语法问题,所以更加安全 …

大神们都在用的5款AI写作软件

在当今信息爆炸的时代,写作已经成为了人们生活和工作中不可或缺的一部分。然而,对于许多人来说,写作并不是一件轻松的事情。幸运的是,随着人工智能技术的不断发展,AI写作软件应运而生。这些软件利用先进的自然语言处理…

HTML5:dialog

JavaScript 练手小技巧&#xff1a;HTML5 的 dialog 标签制作对话框_dialog html-CSDN博客 <dialog id"dialog"> <h2 align"center">修改</h2> <input type"text" id"title1" placeholder"标题" value…

【论文阅读笔记】4篇Disentangled representation learning用于图像分割的论文

4篇应用解耦表示学习的文章&#xff0c;这里只关注如何解耦&#xff0c;更多细节不关注&#xff0c;简单记录一下。 1.Robust Multimodal Brain Tumor Segmentation via Feature Disentanglement and Gated Fusion Chen C, Dou Q, Jin Y, et al. Robust multimodal brain tum…

写点东西《最佳 Web 框架不存在 》

写点东西《&#x1f947;最佳 Web 框架不存在 &#x1f6ab;》 TLDR&#xff1b;您选择的 Web 应用程序框架并不重要。嗯&#xff0c;它很重要&#xff0c;但并不像其他人希望您相信的那样重要。 2024 年存在如此多的库和框架&#xff0c;而且最好的库和框架仍然备受争议&…

瑞_Java开发手册_(四)安全规约

&#x1f64a;前言&#xff1a;本文章为瑞_系列专栏之《Java开发手册》的安全规约篇。由于博主是从阿里的《Java开发手册》学习到Java的编程规约&#xff0c;所以本系列专栏主要以这本书进行讲解和拓展&#xff0c;有需要的小伙伴可以点击链接下载。本文仅供大家交流、学习及研…

Kafka 的架构

实验过程 1.三个虚拟机中解压kafka软件包 tar -zxvf kafka_2.11-1.1.1.tgz 2.修改 3 个节点配置文件 在 zookeeper 节点&#xff0c;进入 kafka_2.11-1.1.1/config 目录下&#xff0c;编辑 server.properties 文件 [rootdb1 ~]# cd kafka_2.11-1.1.1/config [rootdb1 con…

使用斐波那契(Fibonacci)数列来测试各大语言的性能

笔者使用最多的语言是C&#xff0c;目前项目中在使用Go&#xff0c;也使用过不少其它语言&#xff0c;像Erlang&#xff0c;Python&#xff0c;Lua&#xff0c;C#等等。最近看到C#夺冠&#xff0c;首次荣获 TIOBE 年度编程语言&#xff0c;同时也看到网上有不少Java与C#之争的文…

Ubuntu 22.04安装使用easyconnect

EasyConnect 百度百科&#xff0c;EasyConnect能够帮助您在办公室之外使用公司内网的所有系统及应用。在您的公司部署深信服远程应用发布解决方案后&#xff0c;您的公司所有业务系统及应用都可以轻松迁移至移动互联网上。您可以通过手机、PAD等智能移动终端随时随地开展您的业…

现代工程科技杂志现代工程科技杂志社现代工程科技编辑部2023年第21期目录

能源科技 配网故障停电原因及改进对策研究 上官安琪 110kV变电站电气自动化技术及应用策略 陈祥 变电运维误操作事故预控措施分析 高翔;韦婉 智能变电站变电运维安全与设备维护探究 温亮亮;覃万全 110kV变电站电气设计及其防雷保护案例研析 谢旭平 变电运维…

Kafka系列(四)

本文接kafka三&#xff0c;代码实践kafkaStream的应用&#xff0c;用来完成流式计算。 kafkastream 关于流式计算也就是实时处理&#xff0c;无时间概念边界的处理一些数据。想要更有性价比地和java程序进行结合&#xff0c;因此了解了kafka。但是本人阅读了kafka地官网&#…

探索 Python:发现有趣的库——第 1 章:数据可视化之旅

在一个充满活力的科技世界中&#xff0c;数据分析专家“算法仙”和编程爱好者“代码侠”相遇了&#xff0c;决定一起踏上数据可视化的探险之旅。他们将运用 Matplotlib 和 Seaborn 这两个强大的 Python 库&#xff0c;将枯燥的数据转化为生动的图形。 算法仙&#xff1a;你好&…

利用先进的条形码识别和 OCR 技术改善机场行李处理

机场每年处理数百万件行李&#xff0c;主要航空公司每家运输超过 1 亿件行李。每年有 2500 万件行李被错误处理&#xff0c;正确处理至关重要。使用最好的技术是关键&#xff0c;首先是从机场到飞机的正确转乘。 行李分拣 Dynamsoft 的客户是一家机场行李分拣解决方案提供商。…

【Linux 内核源码分析】RCU机制

RCU 基本概念 Linux内核的RCU&#xff08;Read-Copy-Update&#xff09;机制是一种用于实现高效读取和并发更新数据结构的同步机制。它在保证读操作不被阻塞的同时&#xff0c;也能够保证数据的一致性。 RCU的核心思想是通过延迟资源释放来实现无锁读取&#xff0c;并且避免了…

Go新项目-配置文件的选取及区别和写法(1)

先说结论&#xff1a;我们选型TOML yaml&#xff0c;toml&#xff0c;json&#xff0c;ini 实际业务都有用 实际栗子是&#xff1a;我们想要把Go的切片作为配置文件&#xff0c;YAML写起来比较吃力&#xff0c;TOML就很容易了。 配置文件是用于配置计算机程序的参数、初始化设…

FPGA设计时序约束十六、虚拟时钟Virtual Clock

目录 一、序言 二、Virtual Clock 2.1 设置界面 三、工程示例 3.1 工程设计 3.2 工程代码 3.3 时序报告 3.4 答疑 四、参考资料 一、序言 在时序约束中&#xff0c;存在一个特殊的时序约束&#xff0c;虚拟时钟Virtual Clock约束&#xff0c;根据名称可看出时钟不是实…

自动化测试——Python基础

文章目录 前言一、Python的基础语法1.标识符2.注释 二、Python中常见的数据类型1.Number&#xff08;数字&#xff09;1.1.int&#xff08;整数数据类型&#xff09;1.2.float&#xff08;浮点型&#xff09;1.3.bool&#xff08;布尔类型&#xff09; 2.String&#xff08;字符…

Redis 消息队列和发布订阅

文章目录 基本模式生产者消费者原理&模型redis实现java实现 发布者订阅者原理&模型redis实现java实现 stream模式原理&模型工作原理redis实现Java实现 选型外传 基本模式 采用redis 三种方案&#xff1a; ● 生产者消费者&#xff1a;一个消息只能有一个消费者 ●…

canvas绘制美队盾牌

查看专栏目录 canvas示例教程100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…