新农宝网站建设方案,模板网站好还是自助建站好,网站怎么建在国外,怎样去掉底部的wordpress文章目录 一.项目简述二.Jenkins三.模拟真实业务:紧急bug修复和代码阅读 一.项目简述
Q:天机学堂是什么?
A:天机学堂是一个基于微服务架构的生产级在线教育项目
主要有两个端(项目已上线,可以点击查看): 管理后台: https://tjxt-admin.itheima.net 其核心业务主体包括老师、… 文章目录 一.项目简述二.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 IntegrationCI CI 是一种软件开发实践它要求团队成员将其工作频繁地集成到共享存储库中。每次代码提交后自动触发构建和测试过程以确保新代码与现有代码集成不会导致破坏或错误。CI的目标是尽早发现和解决集成问题以减少集成阶段的错误。
持续交付Continuous DeliveryCD CD 是在CI的基础上构建的概念。它强调不仅要频繁集成代码还要自动化部署和测试过程以便能够随时准备好发布软件。持续交付的目标是确保在任何时候都能够生成可靠的、可部署的软件版本并在需要时快速地交付给用户。但它并不一定要求立即部署到生产环境。
持续部署Continuous DeploymentCD 持续部署是CD实践的更进一步它要求所有通过CI和CD流程的代码变更都自动部署到生产环境没有人工干预。这意味着经过测试的代码在通过CI和CD流水线后自动地、立即地部署到生产环境。持续部署的目标是尽量减少发布的手动步骤从而降低出错的可能性并能够更迅速地将新功能、修复和改进推送到用户。
持续集成的优点 自动构建、发布、测试 降低风险 Jenkins原名Hudson2011年改为现在的名字 它是一个开源的实现持续集成的软件工具。
官方网站链接: http://jenkins-ci.org/
先了解Jenkins工作流程: 1.当在idea上提交和推送代码后,会触发Gogs中Web钩子
举个例子: gogs上配置, 也就是说当我们有代码提交到gogs, 就会触发 http://192.168.150.101:18080/gogs-webhook/?jobtjxt-dev-build 2. http://192.168.150.101:18080/gogs-webhook/?jobtjxt-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.分析
阅读代码
如果是我们自己写的代码肯定很容易找到业务入口、整个业务线路。但现在我们是接手他人项目所以只能通过其它途径来梳理业务 如果开发业务的同事还在直接与开发该业务的同事交流 如果开发者已离职可以查看相关接口文档 如果没有文档也可以查看前端请求顺藤摸瓜
3.根据流程找到bug出处(很重要的思路) 找到到请求入口:在企业内开发我们是接手他人代码可以通过交流弄清楚入口。否则就只能通过前端入手。 理清请求链路:找到入口后先总览项目结构弄清楚前端请求如何抵达微服务经过了哪些地方这对解决BUG会有帮助。 紧跟主线:源码非常繁杂不要试图弄清楚每一个细枝末节。紧跟业务主线先梳理整体脉络形成整体业务流程图。 步步深入:最后基于业务场景定位核心问题点抽丝剥茧展开看来阅读细节寻找问题所在。
我们可以根据流程思路按照之前我们的环境部署方案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.解决