(企业Docker实战) 第六篇:建立持续集成环境03

标签: gblfy技术文档

六、 git核心概念

6.1. git学习地址

git是开发所需要必备的非常重要的基础技能 https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000

6.2. github和码云的介绍

watch,star和fork的作用,详情见视频

6.3. git常用命令和操作

6.3.1. 命令行常用命令

# 克隆仓库
git clone xxx

# 查看当前状态
git status

# 新建分支
git checkout -b dev

# 切换分支
git checkout dev

# 拉取代码
git pull
git pull origin master:master

# 提交代码
git push origin master

# 打tag
git tag -a v1.0 -m "正式发布1.0"

# stash
git stash

# unstash
git stash pop

6.3. IDEA操作方法(具体看视频)

6.3.1. 合并分支

6.3.2. compare with

6.3.3. rename

6.3.4. 看历史记录(所有的和单个文件的)

6.3.5. revert 6.stash

6.3.7. cherry pick

6.3.8. reset curent branch

6.4 git技巧

6.4.1. 分支用法实战

master分支:上线用,跟线上代码保持一致。 dev分支:开发分支,开发人员写完提到本分支。 test分支:相对稳定的分支,供上线前的测试。 feature分支:开发部分功能用的分支。 bug分支:修复线上bug用。 分支的作用:一方面分支的建立可以让并行开发互不影响,另一方面,分支的建立可以让代码保留多种状态。

6.4.2. 解决冲突

冲突产生于合并代码,拉取远程代码的时候,会经常遇到。 产生冲突的时候建议用IDEA解决,很智能,很方便,如果你不用IDEA开发,同样的我建议你用IDEA开发,感觉比Eclipse好些。
为了解决冲突时,不会丢失你自己写的本地代码,不要先pull代码,再去提交代码,每次要先commit你的代码再去pull代码,再去解决冲突。这和svn不一样,svn是以一个文件为一个提交单位,git是以一整个commit为一个提交单位。

合并时候,重点是比对,你和别人代码冲突的部分,如果你不确定该用谁的代码,一定要询问和你代码冲突部分是谁写的,你们商量一下,或者直接请示你的项目负责人。

在idea中,出现冲突会提示如下,
在这里插入图片描述

解决冲突过程如下,

在这里插入图片描述

6.4.3. rebase

merge和rebase都可以理解为合并的意思,merge合并之后会产生新的提交记录,rebase不会产生新的记录,而是把所有两个分支的提交归并为一条线上。

扫描二维码关注公众号,回复: 9748287 查看本文章

在拉取代码的时候,我们一般选rebase,如下
在这里插入图片描述
但是在合并代码的时候我们选merge,为了体现合并的过程,合并代码的时候我们一般用–no-ff模式,禁用fast-forward模式,并加上注释-m

git merge dev --no-ff -m "合并dev到test,增加修改人员接口,修复多个bug,完善查看订单功能,xxxxxxx"

6.4.4. bug修复流程

如果生产环境出现bug,若不是很紧急,可以在dev分支开发,等下次上线一起发布,如果bug需要紧急修复,请按以下步骤进行:

6.4.4.1. 暂存手头工作(如果有没完成的工作的话)

如果手头有没写完的代码,并且不能提交,提交后可能影响别人开发,那么可以把这些没写完的代码进行暂存(git stash),等修复完bug之后,再恢复这些代码(git stash pop),如果用的IDEA,可以选择如下选项
在这里插入图片描述

然后填入备注信息:
在这里插入图片描述

6.4.4.2. 新建bugfix分支

stash完之后,切换到master分支(master为线上的代码),在master分支新建一个bugfix分支,例如订单详情有个bug需要修复

image_1d2puvmr714d959e1lem1chkisp13.png-25.2kB

之后,在此分支修复bug,改完后可不提交到远程仓库,如果多人协作修改这个bug,需要提交到远程分支。

6.4.4.3. 测试bug是否修复完成

代码写完之后,本地进行测试,也可以在jenkins上发布到test环境测试一下,确保bug修复完毕,并且没有影响到其他业务运行。

6.4.4.4. 合并分支

合并刚才创建的bugfix_order_detail分支到master分支,并打标签,如下

git merge bugfix_order_detail --no-ff -m "合并bugfix_order_detail到master,修复了订单详情显示的问题"

上线成功后,可删除掉本地的bugfix_order_detail分支。

6.4.4.5. 继续进行之前手头上的工作

恢复暂存的代码,如下

在这里插入图片描述
或者直接用命令

git stash pop

6.5 maven知识点

6.5.1. 仓库的概念

本地仓库: 本地存放jar包的地方,默认存放路径在路径名为

.m2/respository/

,可以再settings.xml中配置。

  • 中央仓库:
    Maven 中央仓库是由 Maven 社区提供的仓库,其中包含了大量常用的库。中央仓库包含了绝大多数流行的开源Java构件,以及源码、作者信息、SCM、信息、许可证信息等。一般来说,简单的Java项目依赖的构件都可以在这里下载到。

  • nexus私服:
    开发人员自己定制仓库,包含了所需要的代码库或者其他工程中用到的 jar 文件。一般公司都会搭建一个自己的私服,加快jar包下载速度,也可以deploy公司内部的jar包。

6.5.2. pom详解

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/maven-v4_0_0.xsd">

    <!-- 父项目坐标 -->
    <parent>
        <!--被继承的父项目的构件标识符 -->
        <artifactId />
        <!--被继承的父项目的全球唯一标识符 -->
        <groupId />
        <!--被继承的父项目的版本 -->
        <version />
        <!-- 父项目的pom.xml文件的相对路径 -->
        <relativePath />
    </parent>

    <!--项目的全球唯一标识符,通常使用全限定的包名区分该项目和其他项目。 -->
    <groupId>asia.banseon</groupId>
    <!-- 构件的标识符,它和group ID一起唯一标识一个构件 -->
    <artifactId>banseon-maven2</artifactId>
    <!--项目产生的构件类型,jar、war、pom。 -->
    <packaging>jar</packaging>
    <!--项目当前版本,格式为:主版本.次版本.增量版本-限定版本号 -->
    <version>1.0-SNAPSHOT</version>

    <!--项目的名称, Maven产生的文档用 -->
    <name>banseon-maven</name>
    <!-- 项目的详细描述, Maven 产生的文档用. -->
    <description>A maven project to study maven.</description>

    <!-- 项目内的一些常量 -->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <jwt.version>0.9.0</jwt.version>
    </properties>

    <!-- 项目的依赖管理 -->
    <dependencyManagement>
        <dependencies>
        </dependencies>
    </dependencyManagement>

    <!-- 项目的依赖 -->
    <dependencies>
        <dependency>
            <groupId>cn.stylefeng.roses</groupId>
            <artifactId>kernel-core</artifactId>
            <version>1.1.0</version>
            <!-- 排除依赖 -->
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <!-- 项目构建配置 -->
    <build>
        <!-- 插件 -->
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>

        <!-- 资源文件夹 -->
        <resources>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>

    <!-- 不同环境构建参数 -->
     <profiles>
        <profile>
            <id>local</id>
            <properties>
                <spring.active>local</spring.active>
            </properties>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
        </profile>
        <profile>
            <id>dev</id>
            <properties>
                <spring.active>dev</spring.active>
            </properties>
        </profile>
    </profiles>

</project>

6.5.3. maven命令

# 1. 清除工作空间编译的文件
mvn clean

# 2. 编译并打包
mvn package

# 3. 编译打包构建到本地仓库
mvn install

# 4. 发布到远程仓库
mvn deploy

# 5. 跳过测试
-Dmaven.test.skip=true

6.5.4. 继承

子maven模块可以继承父maven模块的一些属性。子模块可以用如下一段配置来继承某个父模块。

<parent>  
    <groupId>com.xxx</groupId>
    <artifactId>parent-project</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <relativePath>../pom.xml</relativePath>  
</parent>

6.5.5. 聚合

父模块可以用如下一段配置来聚合多个子模块,以便在clean和package等命令时,自动帮我们执行父子模块的操作。一般父模块的packaging属性为pom。

<modules>
    <module>moduleA</module>  
    <module>moduleB</module>        
    <module>moduleC</module>
</modules>

6.5.6. 依赖冲突

Maven采用"最近获胜策略"的方式处理依赖冲突。如下图,resolev-web会依赖project-在这里插入图片描述

解决方法: 第一种,显式加入对project-common 2.0版本的依赖。

<dependency>       
   <groupId>project-common</groupId>      
   <artifactId>project-commmon</artifactId>  
   <version>2.0</version>   
</dependency>

第二种,resolve-web对project-A的dependency声明中,将project-common排除掉。

<dependency>  
    <groupId>project-A</groupId>  
    <artifactId>project-A</artifactId>  
    <version>1.0</version>  
    <exclusions>  
      <exclusion>  
          <groupId>project-common</groupId>  
          <artifactId>project-commmon</artifactId>  
      </exclusion>  
    </exclusions>  
</dependency>

6.5.7. SNAPSHOT

在框架持续开发期间,如果该框架被多个项目依赖,每次升级一个版本都要通知被调用的人,版本升级给调用者会带来极大的不便,采用SNAPSHOT可以避免此问题。

正式版本和快照版本的主要区别在于,本地获取这些依赖的机制有所不同。假设你依赖一个库的正式版本,构建的时候构建工具会先在本次仓库中查找是否已经有了这个依赖库,如果没有的话才会去远程仓库中去拉取。

快照版本会每隔一定时间去远程仓库拉去看有没有更新的变化。频率共有四种,分别是always、daily、interval(分钟为单位)、never(跟正式版本一样)。

<repositories>
    <repository>
        <id>xxx</id>
        <url>xxx</url>
        <snapshots>
            <enabled>true</enabled>
            <updatePolicy>always</updatePolicy>
        </snapshots>
    </repository>
</repositories>

6.6 maven搭建多模块项目

6.6.1. 建立父模块

<packing>改为pom,增加<modules>标签

6.6.2. 整合两个子模块

将子模块的parent设置为父模块的坐标信息,相关的<version>可根据继承,减少依赖。

6.6.3. 模块A依赖模块B

如果模块之间需要引用,直接加入对方模块坐标即可

6.7. spring boot核心原理

推荐一本系统讲解spring boot的书籍《Spring Boot实战》Craig Walls著

6.7.1. 自动配置

针对很多spring应用程序常见的应用功能,spring boot能自动提供相关配置。

如何覆盖自动配置?

6.7.2. 起步依赖

告诉spring boot需要什么功能,他就能引入需要的库。

6.8. 简洁代码

6.8.1. 规范统一的类和方法注释

/**
 * 修改用户状态,返回修改生效的行数
 *
 * @param userId 用户id
 * @param status 用户的状态
 * @return 被修改的行数
 * @author fengshuonan
 * @Date 2019/2/24 21:34
 */
 int setStatus(@Param("userId") Long userId, @Param("status") String status);

/**
 * 跳转到角色列表页面
 *
 * @author fengshuonan
 * @Date 2018/12/23 6:30 PM
 */
@RequestMapping("")
public String index() {
    return PREFIX + "/role.html";
}

6.8.2. 逻辑的注释

//如果开启了记住我功能
if ("on".equals(remember)) {
    token.setRememberMe(true);
} else {
    token.setRememberMe(false);
}

//执行shiro登录操作
currentUser.login(token);

//登录成功,记录登录日志
ShiroUser shiroUser = ShiroKit.getUserNotNull();
LogManager.me().executeLog(LogTaskFactory.loginLog(shiroUser.getId(), getIp()));

6.9. 组织包结构

在日常开发中,业务模块的包结构划分一般划分为三个config、core、modular

6.9.1. config包

config包存放整个模块的配置类,因为项目基于spring boot开发,大部分的spring配置都换成了java bean方式的配置,所以单独分一个包来存放配置,config包中除了存放配置类,还有一些以Properties结尾的类,这些类的作用是启动应用的时候把application.yml中的配置映射到类的属性上

6.9.2. core包

core包存放当前模块所运行的一些核心机制,例如全局的异常拦截器,日志AOP,权限的AOP,项目初始化后的监听器,工具类等,还可以存放一些对某些框架的扩展,例如对beetl模板的扩展配置和工具类,对flowable的扩展类,shiro的一些拓展类等等

6.9.3. modular包

modular存放按业务划分的业务代码,若本模块中包含多个模块业务,则在modular中建立多个业务包,在具体的业务包下再建立controller、entity、service、factory、mapper、model、service、wrapper这几个包,如果当前模块中只存在一类业务,那么没有必要在modular包下再建立多个业务模块,可直接在modular模块建立controller、entity、service等等

这样拆分的好处在于把业务,配置和运行机制清晰的拆分开,提高项目的可维护性,加快项目的开发效率!

6.10. 常量和枚举

目的:更优雅的写出代码,避免出现魔法值。

6.10.1. 常量

代码中的固定值,一般用public static final标识 常用在某种标识上,比如说缓存前缀标识,系统环境常量等

表现的形势一般分两种:

public class DefaultSystem {
    public static final String DEFAULT_PWD = "111111";
}
public interface DefaultSystem {
    String DEFAULT_PWD = "111111";
}

6.10.2. 枚举

一般用在状态和类型等,这样具有可列举的项时使用。

  • //第一种
public enum Color {  
    RED, GREEN, BLANK, YELLOW  
}
  • //第二种
public enum ManagerStatus {

    OK("ENABLE", "启用"), FREEZED("LOCKED", "冻结"), DELETED("DELETED", "被删除");

    String code;
    String message;

    ManagerStatus(String code, String message) {
        this.code = code;
        this.message = message;
    }

    public static String getDescription(String value) {
       ...
    }
}
  • //第三种
public enum BizExceptionEnum implements AbstractBaseExceptionEnum {

    TOKEN_EXPIRED(700, "token过期"),
    TOKEN_ERROR(700, "token验证失败");

    ...

    @Override
    public Integer getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }
}

6.11. 接口和抽象类

6.11.1. 接口多用在制定规范

1.Feign远程接口使用接口

@RequestMapping("/api/dict")
public interface DictApi {

    @RequestMapping(value = "/addDict", method = RequestMethod.POST)
    void addDict(@RequestBody Dict dict);

    @RequestMapping(value = "/updateDict", method = RequestMethod.POST)
    void updateDict(@RequestBody Dict dict);

    @RequestMapping(value = "/deleteDict", method = RequestMethod.POST)
    void deleteDict(@RequestParam("dictId") Long dictId);
}

2.作为方法参数,用一个接口,接收不同子类

public interface AbstractBaseExceptionEnum {

    /**
     * 获取异常的状态码
     */
    Integer getCode();

    /**
     * 获取异常的提示信息
     */
    String getMessage();
}

3.为了拓展,写不同的实现,切换时,减少代码修改量

public interface SmsManager {

    /**
     * 发送短信
     *
     * @param phoneNumber  电话号码
     * @param templateCode 模板号码
     * @param params       模板里参数的集合
     * @author fengshuonan
     * @Date 2018/7/6 下午2:32
     */
    void sendSms(String phoneNumber, String templateCode, Map<String, Object> params);

}

有需要拓展的地方,预判是否需要接口。

6.11.2. 抽象类多用在封装

1.封装模板代码,模板方法模式里常用

public abstract class AbstractTreeBuildFactory<T> {

    /**
     * 树节点构建整体过程
     *
     * @author fengshuonan
     * @Date 2018/7/26 上午9:45
     */
    public List<T> doTreeBuild(List<T> nodes) {

        //构建之前的节点处理工作
        List<T> readyToBuild = beforeBuild(nodes);

        //具体构建的过程
        List<T> builded = executeBuilding(readyToBuild);

        //构建之后的处理工作
        return afterBuild(builded);
    }

    /**
     * 构建之前的处理工作
     *
     * @author fengshuonan
     * @Date 2018/7/26 上午10:10
     */
    protected abstract List<T> beforeBuild(List<T> nodes);

    /**
     * 具体的构建过程
     *
     * @author fengshuonan
     * @Date 2018/7/26 上午10:11
     */
    protected abstract List<T> executeBuilding(List<T> nodes);

    /**
     * 构建之后的处理工作
     *
     * @author fengshuonan
     * @Date 2018/7/26 上午10:11
     */
    protected abstract List<T> afterBuild(List<T> nodes);
}

2.封装核心算法到抽象类,子类只需继承,并编写一小部分逻辑实现整个类功能

public abstract class BaseControllerWrapper {

    ...

    @SuppressWarnings("unchecked")
    public <T> T wrap() {

        /**
         * 包装结果
         */
        if (single != null) {
            wrapTheMap(single);
        }
        if (multi != null) {
            for (Map<String, Object> map : multi) {
                wrapTheMap(map);
            }
        }

        /**
         * 根据请求的参数响应
         */
        if (page != null) {
            return (T) page;
        }
        if (pageResult != null) {
            return (T) pageResult;
        }
        if (single != null) {
            return (T) single;
        }
        if (multi != null) {
            return (T) multi;
        }

        return null;
    }

    protected abstract void wrapTheMap(Map<String, Object> map);
}

5.5 业务异常

  1. 场景
    试着想象一下下面的场景

例如一个下单逻辑,首先经过Controller,之后Controller调用了AService,Aservice调用了BService,Bservice调用了CService

经过了这4层的业务逻辑后,在CService的代码里有个判断,如下

if(用户余额 < 0){
直接返回这个“用户余额”为0的消息给前端
}
常规的方法,CService返回给BService,B返回给A,A返回给控制器

那么,有没有更方便的方法?

有的,那就是用自定义异常

  1. 如何使用
    首先,创建一个属于系统的自定义异常
/**
 * 业务异常的封装
 *
 * @author fengshuonan
 * @date 2016年11月12日 下午5:05:10
 */
public class ServiceException extends RuntimeException {

    private Integer code;

    private String errorMessage;

    public ServiceException(Integer code, String errorMessage) {
        super(errorMessage);
        this.code = code;
        this.errorMessage = errorMessage;
    }

    public ServiceException(AbstractBaseExceptionEnum exception) {
        super(exception.getMessage());
        this.code = exception.getCode();
        this.errorMessage = exception.getMessage();
    }

    ...
}

那么,如何使用,在程序的控制器层,service层都可以抛出异常

//判断当前用户id不是超级管理员的用户id,一言不合就抛出异常
if (userId.equals(Const.ADMIN_ID)) {
throw new ServiceException(BizExceptionEnum.CANT_CHANGE_ADMIN);
}
如果程序制定到这个逻辑,判断成立的话,看一下前端接收到内容是啥

{
“code”:600,
“data”:"",
“exceptionClazz”:"",
“message”:“不能删除超级管理员”,
“success”:false
}
疑问?在哪里拦截的异常

  1. 统一拦截异常
    全局统一拦截的异常,cn.stylefeng.guns.core.aop.GlobalExceptionHandler

@ControllerAdvice控制器加强

@ExceptionHandler(ServiceException.class)拦截制定参数的异常,拦截到之后执行方法内的逻辑

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)返回前端的http状态码

整体写法跟spring mvc的控制器很相似

@ControllerAdvice
@Order(-1)
public class GlobalExceptionHandler {

/**
 * 拦截业务异常
 */
@ExceptionHandler(ServiceException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public ErrorResponseData bussiness(ServiceException e) {
    return new ErrorResponseData(e.getCode(), e.getMessage());
}

...

}
6.1 项目导入和运行
1.项目导入
登录https://gitee.com/stylefeng/roses,找到子项目地址 image.png-48.6kB

可fork之后clone,也可直接下载zip包 image.png-86kB

之后打开idea点open,选择下载的项目即可 image.png-16.5kB

2.项目的运行
导入完所有项目后,可按如下步骤启动各个组件

启动roses-config-server配置中心
启动roses-cloud-register注册中心
启动roses-spring-boot-admin监控中心
启动roses-system系统管理基础服务
启动roses-gateway网关

3.如何修改roses-kernel核心框架
如果公司业务的开发,需要涉及到框架的修改,则可以导入roses-kernel项目,然后修改,升级版本,mvn install到本地仓库或者公司的私服(推荐)即可。

6.2 快速开发微服务
1.服务提供者
在roses-system开发一个provider作为服务提供者。

1.编写api

/**

  • 示例服务

  • @author fengshuonan

  • @Date 2019/3/7 8:17 PM
    */
    @RequestMapping("/api/example")
    public interface ExampleSysApi {

    /**

    • 测试远程接口
    • @author fengshuonan
    • @Date 2019/3/7 8:17 PM
      */
      @RequestMapping(value = “/test1”, method = RequestMethod.POST)
      ResponseData test1(@RequestParam(“param”) String param);

}
2.编写provider

/**
 * 测试提供者
 *
 * @author fengshuonan
 * @Date 2019/3/7 8:18 PM
 */
@RestController
public class ExampleSysProvider implements ExampleSysApi {

    @Autowired
    private SysUserService sysUserService;

    @Override
    public ResponseData test1(String param) {
        List<SysUser> list = sysUserService.list();
        return ResponseData.success(list);
    }
}

2.服务消费者
1.引入openfeign包

org.springframework.cloud spring-cloud-starter-openfeign 2.启动类增加注解
@EnableFeignClients
@EnableDiscoveryClient
3.配置eureka注册地址

eureka:
  instance:
    prefer-ip-address: true
    lease-expiration-duration-in-seconds: 20   
    lease-renewal-interval-in-seconds: 5      
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
    registry-fetch-interval-seconds: 10  

4.编写消费者接口

/**
 * 测试服务消费者
 *
 * @author fengshuonan
 * @date 2018-08-07-下午3:12
 */
@FeignClient("roses-system")
public interface ExampleServiceConsumer extends ExampleSysApi {

}

5.注入消费者,调用接口,编写测试

/**
 * 网关服务
 *
 * @author fengshuonan
 * @Date 2017/11/10 上午11:24
 */
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableFeignClients(basePackages = "cn.stylefeng.roses.gateway.modular.consumer")
@EnableZuulProxy
public class RosesGatewayApplication {

    @Autowired
    private ExampleServiceConsumer exampleServiceConsumer;

    public static void main(String[] args) {
        SpringApplication.run(RosesGatewayApplication.class, args);
    }

    @Bean
    public CommandLineRunner commandLineRunner() {
        return new CommandLineRunner() {
            @Override
            public void run(String... args) throws Exception {
                System.out.println(exampleServiceConsumer.test1("123"));
            }
        };
    }

}

想学习更多微服务、分布式、中间件、数据库、项目快速构建等系列技术
请访问http://gblfy.com
让我们一起进步!!!

发布了898 篇原创文章 · 获赞 123 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/weixin_40816738/article/details/91489307