# README

* SpringBoot
  * [spring boot 运维](/springboot/springboot-yun-wei)
  * [Spring Boot 部署war包](/springboot/springboot-bu-shu-war-bao)
  * [springboot搭建](/springboot/springboot-da-jian)
  * [spring boot 读取配置文件](/springboot/springboot-du-qu-pei-zhi-wen-jian)
  * [简单总结](/springboot/jian-dan-zong-jie)
  * [spring配置文件](/springboot/spring-pei-zhi)
  * [spring boot Configuration](/springboot/springboot-zhi-configuration)
  * [spring boot 配置文件](/springboot/springboot-pei-zhi-wen-jian)
  * [spring boot 配置mybatis](/springboot/springboot-pei-zhi-mybatis)
* MacAwesome
  * [markdown使用](https://github.com/othorizon/DarkTime/blob/master/MacAwesome/markdown.md)
  * [MAC APP Awesome](https://github.com/othorizon/DarkTime/blob/master/MacAwesome/app.md)
  * \[[markdown使用](https://github.com/othorizon/DarkTime/blob/master/markdown.md)]\(/MacAwesome/SUMMARY.md)
  * [chrome](https://github.com/othorizon/DarkTime/blob/master/MacAwesome/chrome.md)
  * [intellij idea](https://github.com/othorizon/DarkTime/blob/master/MacAwesome/intellij_idea.md)
  * [MacAwesome](https://github.com/othorizon/DarkTime/blob/master/MacAwesome/README.md)
  * [VS Code 的使用](https://github.com/othorizon/DarkTime/blob/master/MacAwesome/VSCode.md)
  * [MAC Shell命令](https://github.com/othorizon/DarkTime/blob/master/MacAwesome/Shell命令.md)
* database
  * [druid使用](/database/druid)
* 框架
  * [项目拆析](/kuang-jia/xiang-mu-chai-xi)
  * [各种框架和工具](/kuang-jia/kuang-jia)
* docker
  * [kubernetes](/docker/kubernetes)
  * [docker](/docker/docker)
  * [docker 常用镜像](/docker/docker-images)
* 效率工具
  * [解决dns污染导致域名解析失败](/xiaolgong-ju/dns-host)
  * [sonarqube 相关配置](/xiaolgong-ju/sonarqube-dai-ma-zhi-liang)
  * [Iterm2 使用](/xiaolgong-ju/iterm)
  * [gitbook](/xiaolgong-ju/gitbook)
  * [github awesome github资源推荐](/xiaolgong-ju/githubawesome)
  * [playground 在线试用平台汇总](/xiaolgong-ju/playground)
  * [linux中的office](/xiaolgong-ju/linux-office)
  * [linux screen 工具](/xiaolgong-ju/screen)
  * [简单Mock服务（moco）](/xiaolgong-ju/mock-server)
  * [npm](/xiaolgong-ju/npm)
  * [Visual Studio Code 的使用](/xiaolgong-ju/vscode)
  * [配置开发环境](/xiaolgong-ju/dev-env)
  * [homebrew的使用](/xiaolgong-ju/homebrew)
  * [汇总](/xiaolgong-ju/tools)
* tomcat
  * [tomcat目录规范](/tomcat/tomcat-gui-fan)
* code\_snippets
* 专题
  * [RESTful API](/zhuan-ti/restfulapi)
  * [serveless服务](/zhuan-ti/serveless-fu-wu)
  * [搭建私有云主机 折腾记](/zhuan-ti/da-jian-si-you-yun-zhu-ji)
  * [开发中的各种疑难杂症问题](/zhuan-ti/yi-nan-za-zheng)
  * [spring 最佳实践](/zhuan-ti/spring-best-practice)
  * [LLM 大语言模型](/zhuan-ti/llm)
  * notelive
    * [文章框架](/zhuan-ti/notelive/notelive-kuang-jia)
    * [notelive vue版本开发](/zhuan-ti/notelive/notelive-vue)
    * [notelive 开发 札记](/zhuan-ti/notelive/notelive-kai-fa)
  * [webrtc技术分析](/zhuan-ti/webrtc)
  * [反向代理](/zhuan-ti/proxy)
  * spring-cloud
    * [spring boot admin 监控服务](/zhuan-ti/spring-cloud/spring-boot-admin)
    * [Spring Cloud 整理汇总](/zhuan-ti/spring-cloud/spring-cloud)
* python
  * [python 学习](/python/python_school)
  * [Python 修饰器的一些小细节](/python/decorator)
* 云主机
  * [aliyun 主机的种种](/yun-zhu-ji/aliyun)
* maven
  * [maven使用](/maven/maven-shi-yong)
  * [maven项目增加编译版本号 buildnumber-maven-plugin](/maven/buildnumber-maven-plugin)
  * [仓库](/maven/maven-cang-ku)
* java
  * [java 开发常用工具类](/java/utils)
  * [java](/java/java)
  * [apache commons pool 对象连接池](/java/apache-common-pool)
* 大数据
  * [kafka](/da-shu-ju/kafka)
  * [gobblin](/da-shu-ju/gobblin)
  * [sqoop 简介及使用](/da-shu-ju/sqoop_overview)
  * [hbase](/da-shu-ju/hbase)
  * [gobblin](/da-shu-ju/gobblin_overview)
  * [sqoop源码解析](/da-shu-ju/sqoop)
  * [hadoop map reduce](/da-shu-ju/mapreduce)
  * [大数据 学习札记](/da-shu-ju/da-shu-ju-zha-ji)
* 脚本
  * python
    * [批量请求url 解析json数据](/jiao-ben/python/pi-liang-qing-qiu-url)
  * js
    * [sheetjs-js读取excel](/jiao-ben/js/js-du-qu-excel)
  * shell
    * [自动生成bitbook的summary文件](/jiao-ben/shell/sheng-cheng-gitbooksummary)
    * [linux/mac 实用脚本](/jiao-ben/shell/shi-yong-jiao-ben)
    * [自动创建tomcat项目脚本](/jiao-ben/shell/chuang-jian-tomcat-xiang-mu)
    * [批量处理文件内容脚本](/jiao-ben/shell/pi-liang-chu-li-wen-jian-nei-rong)
* nginx
  * [nginx](/nginx/nginx)
  * [ngix 文件浏览器 文件服务器](/nginx/nginx-wen-jian-liu-lan-qi)
* linux
  * [群晖nas札记](/linux/nas)
  * [ftp](/linux/ftp)
  * [linux 运维](/linux/yun-wei)
  * [常用命令](/linux/linux-regular-cmd)
  * [linux](/linux/linux)
  * [mysqldump脚本](/linux/mysqldump-jiao-ben)
  * [代理](/linux/proxy)
  * [简易灰度部署脚本 不使用jenkins的纯shell方式](/linux/project_deploy_sh)
  * [shell脚本](/linux/shell)
  * [附加文档](/linux/linux-addition)
* mysql
  * [sql](/mysql/sql)
* 游戏开发
  * [Unity 2020 学习笔记](/you-xi-kai-fa/unity-xue-xi-bi-ji)
* 学习笔记
  * [centos常用环境安装](/xue-xi-bi-ji/centos-zhuang-ji)
  * [gradle 学习](/xue-xi-bi-ji/gradle)
  * [建站经历](/xue-xi-bi-ji/jian-zhan)
  * python
    * [爬虫教程](/xue-xi-bi-ji/python/pa-chong)
  * [如何解决百度爬虫无法爬取搭建在Github上的个人博客的问题？ - 知乎](https://www.zhihu.com/question/30898326)]\(/学习笔记/待整理.md)
  * baas
    * [在本地部署Parse Server](/xue-xi-bi-ji/baas/parse-server)
  * [mysql学习标记](/xue-xi-bi-ji/mysql)
  * [java code snippets](/xue-xi-bi-ji/java_code_snippets)
  * [非Spring Boot Web项目 注册节点到Eureka Server并提供服务](/xue-xi-bi-ji/spring-cloud-not-boot)
  * kotlin
    * [Kotlin 学习札记](/xue-xi-bi-ji/kotlin/kotlin)
  * [spring cloud](/xue-xi-bi-ji/spring-cloud)
  * [vim配置](/xue-xi-bi-ji/vim-pei-zhi)
  * 前端
    * [开发PWA应用](/xue-xi-bi-ji/qian-duan/pwa)
* jenkins
  * [jenkins配置备份](/jenkins/jenkins-pei-zhi)
  * [gitlab触发Jenkins 自动构建](/jenkins/gitlab-chu-fa-jenkins-zi-dong-gou-jian)
  * [安装与使用](/jenkins/an-zhuang-yu-shi-yong)
* npm
  * [npm 使用](/npm/npm)
* git
  * [ignore](/git/ignore)
  * [git使用总结](/git/git)
  * [git配置多个远程仓库](/git/git-pei-zhi-duo-ge-yuan-cheng-cang-ku)
* 前端
  * [swig](/qian-duan/swig)
  * [解决跨域请求问题](/qian-duan/kua-yu-qing-qiu-wen-ti)
  * [angularjs 学习](/qian-duan/angularjs)
  * [scriptbot的前端开发经验总结](/qian-duan/scriptbot-kai-fa-li-cheng)
  * [各种资源](/qian-duan/zi-yuan)
  * [一些有用的js代码](/qian-duan/js-dai-ma-kuai)


# SpringBoot


# spring boot 运维

## 如何优雅关闭 Spring Boot 应用

[如何优雅关闭 Spring Boot 应用 - 闻人的技术博客 - 博客园](https://www.cnblogs.com/one12138/p/11241274.html)


# Spring Boot 部署war包


# springboot搭建

[测试类写法](https://stackoverflow.com/questions/42998911/spring-boot-jpa-test-unable-to-find-a-springbootconfiguration-when-doing-a)

## 修改日志工具为slf4j

排除原日志依赖,并引入log4j的包

```markup
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
</dependency>
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j</artifactId>
            <version>1.3.8.RELEASE</version>
</dependency>
```

## 数据源配置

```java
@Configuration
@MapperScan(basePackages ={"com.rule.converter.dao.boss"},sqlSessionFactoryRef = "bossSqlSessionFactory")
public class BossDataSourceConfig {
    @Primary
    @Bean(name = "bossDataSource")
    @ConfigurationProperties(prefix = "datasource.boss")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name = "bossTransactionManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("bossDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Primary
    @Bean(name = "bossSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("bossDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:com/rule/converter/dao/boss/**/*.xml"));
//        factoryBean.setTypeAliasesPackage("com.dc.es.report.pojo.main");
        return factoryBean.getObject();
    }
}
```

## jenkins配置

启动脚本

```bash
#!/bin/sh -ex
BUILD_ID=tomcat8-ruleconvert

APP_HOME=/usr/local/boot-ruleconvert-9530

echo branch:$branch

mvn clean package -Dmaven.test.skip=true -P${profile} -U


ssh 10.0.0.0 "pkill -9 -f ${APP_HOME}" || echo noProcess

ssh 10.0.0.0 "rm -f ${APP_HOME}/convert.jar"

scp target/convert.jar 10.0.0.0:$APP_HOME/convert.jar

ssh 10.0.0.0 "sh -l ${APP_HOME}/restart.sh"
#下面这个方案行不通无论是否使用nohup都会在ssh断开后结束程序
#ssh 10.0.0.0 "java -jar ${APP_HOME}/convert.jar --server.port=9530 > /dev/null 2>&1 &"
```

服务器上的restart.sh

```bash
#!/bin/sh

APP_HOME=/usr/local/boot-ruleconvert-9530
pkill -9 -f $APP_HOME/convert.jar || echo noProcess
#这里必须要cd到app目录，因为jenkins的ssh没有指定目录的情况下，执行命令会在/home/{jenkins-user} 目录下，所以如果日志配置了相对路径，则会打印到home/user目录下
cd $APP_HOME
#nohup确保不会中断
nohup java -jar $APP_HOME/convert.jar --server.port=9530  > /dev/null 2>&1 &
```

```bash
#!/bin/sh

APP_NAME=$1
APP_HOME=$2
PORT=$3

echo $APP_NAME
echo $APP_HOME

pkill -9 -f $APP_HOME/$APP_NAME.jar || echo noProcess
cd $APP_HOME
#nohup确保不会中断
nohup java -jar $APP_NAME.jar --server.port=$PORT --logging.file=logs/$APP_NAME.log  >/dev/null 2>&1 &
```


# spring boot 读取配置文件

[四种读取properties文件的方式](https://www.imooc.com/article/18252)

## 通过@ConfigurationProperties方式

\`\`\`java 读取配置文件 import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component;

@Component @ConfigurationProperties(prefix = "bill") public class BillActivitiConfig {

```
@Getter
@Setter
private static RoleId roleId;

@Getter
@Setter
private static Email email;

@Getter
@Setter
public static class RoleId {
    private String cal;
    private String convert;
    private String push;
}

@Getter
@Setter
public static class Email {
    private String receiver;
    private String cc;
}
```

}

````
```properties application-dev.properties
# bill
# 这里注意java文件中属性名是驼峰大小写，写到配置文件应该使用 ‘-’ 分割的全小写
bill.role-id.cal=21
bill.role-id.convert=22
bill.role-id.push=23
#notify email
bill.email.receiver=zhanghao6@kingsoft.com
bill.email.cc=GUOMEINA@kingsoft.com
````

ConfigurationProperties 注解需要添加依赖

\`\`\`xml pom.xml

&#x20;org.springframework.boot spring-boot-configuration-processor true \`\`\`


# 简单总结

\[TOC]

## 笔记

spring boot 采用“习惯优于配置，简化了大量配置。 Spring Boot更多的是采用Java Config的方式，对Spring进行配置。 采用了spring-boot-start-actuator之后，直接以REST的方式，获取进程的运行期性能参数。![spring-boot-start-actuator](https://3585898866-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LcehtLiMKFYRXXJ4Dzh%2F-Lcei1h6xvrd1a6Mrkgg%2F-Lcei66uqwAvFkOWlxi2%2F2018-03-02-14-36-36.png?generation=1555494893304124\&alt=media)

## 参考资料

[Spring Boot导入XML配置【从零开始学Spring Boot】](http://blog.csdn.net/linxingliang/article/details/52263727) [Spring Boot浅谈(是什么/能干什么/优点和不足) - CSDN博客](http://blog.csdn.net/fly_zhyu/article/details/76407830) [Spring MVC将异常映射到HTTP状态码 - CSDN博客](http://blog.csdn.net/Q_AN1314/article/details/51736357)

## 启动

To run that application, use the `java -jar` command,exit the application hit `ctrl-c` [spring-boot-offical-reference](http://docs.spring.io/spring-boot/docs/2.0.0.M3/reference/htmlsingle/#getting-started-first-application-run)

### 后台运行

使用`ctrl+z` 或者加 `&`在命令后面可以切换到后台运行，使用`jobs`和`fg`命令管理后台任务 ​

后台运行命令： `java -jar spring-boot01-1.0-SNAPSHOT.jar > log.file 2>&1 &` 详解（[参考](https://app.yinxiang.com/shard/s9/nl/679699/eb8e1e11-f4d0-413c-8098-110924dde5e4/)）：

* `> log.file` 打印日志，也可以从项目配置文件里配置日志文件，默认只打印控制台
* `2>&1` 把错误输出（2） 重定向到标准输出（1）

### 命令行参数

[参考](http://blog.csdn.net/isea533/article/details/50281151) 通过Java -jar app.jar --name="Spring" --server.port=9090方式来传递参数。

参数用--xxx=xxx的形式传递。

配置到`application.properties`文件中的参数都可以从外部传入后覆盖

更多常见的应用属性请浏览[这里](http://docs.spring.io/spring-boot/docs/1.2.3.RELEASE/reference/html/common-application-properties.html)

可以通过SpringApplication.setAddCommandLineProperties(false)禁用命令行配置。

### 配置文件从外部配置

[参考](http://www.jb51.net/article/108670.htm)

1. 通过命令行来重写和配置环境变量，优先级最高，例如可以通过下面的命令来重写spring boot 内嵌tomcat的服务端口，注意“=”俩边不要有空格 `java -jar demo.jar --server.port=9000` 如果想要设置多个变量怎么办，可以已json的格式字符串来设置 `java -jar demo.jar --spring.application.json='{"foo":"bar"}'`
2. 只需将配置文件放置在jar同一目录下即可，或者在jar所在目录创建一个config目录，将配置文件放置在config目录下面也可。[参考](http://blog.csdn.net/wo541075754/article/details/52540455)

### 非web的项目

[Spring Boot非web应用程序实例](http://www.yiibai.com/spring-boot/non-web-application-example.html)

```java
import org.springframework.boot.CommandLineRunner;

@SpringBootApplication
public class SpringBootConsoleApplication implements CommandLineRunner {

    public static void main(String[] args) throws Exception {

        SpringApplication.run(SpringBootConsoleApplication.class, args);

    }

    //access command line arguments
    @Override
    public void run(String... args) throws Exception {
        //do something
    }
}
```

## 零散知识

* [当spring 容器初始化完成后执行某个方法](https://www.cnblogs.com/rollenholt/p/3612440.html)
* [@Bean在@Configuration和在@Component中的区别](http://blog.csdn.net/ttjxtjx/article/details/49866011)
* [Spring Boot使用HandlerInterceptorAdapter和WebMvcConfigurerAdapter](https://www.cnblogs.com/EasonJim/p/7727012.html)
* [SpringBoot初始教程之Servlet、Filter、Listener配置(七)](http://blog.csdn.net/king_is_everyone/article/details/53116744)
* 本地jar 服务器部署war
  * [Spring boot 项目发布war 本地调试jar 方法](http://blog.csdn.net/mimica247706624/article/details/60765730)
  * [springBoot 由jar包转换为war包](http://blog.csdn.net/hao134838/article/details/71436834)
  * [把spring-boot项目部署到tomcat容器中](http://blog.csdn.net/javahighness/article/details/52515226)


# spring配置文件

## 参考

[spring context:component-scan 使用说明(转)](https://www.cnblogs.com/youngjoy/p/3817471.html) [**为啥Spring和Spring MVC包扫描要分开？**](http://labreeze.iteye.com/blog/2359957) [Springmvc controller 层 @Transactional 不起作用](http://blog.csdn.net/qq_36776347/article/details/77224468)

## 知识点

spring mvc是对spring的扩展，在spring的基础上增加了对`<servlet>`的更多的支持

#### 关于Spring事务\的理解

参考： [关于Spring事务\的理解](http://blog.csdn.net/catoop/article/details/50067785)

注解前缀： 在使用SpringMvc的时候，配置文件中我们经常看到 annotation-driven 这样的注解，其含义就是支持注解，一般根据前缀 tx、mvc 等也能很直白的理解出来分别的作用。 就是支持事务注解的（@Transactional） 、 就是支持mvc注解的，说白了就是使Controller中可以使用MVC的各种注解。

事物配置位置说明：

> \ only looks for @Transactional on beans in the same application context it is defined in. This means that, if you put \ in a WebApplicationContext for a DispatcherServlet, it only checks for @Transactional beans in your controllers, and not your services.

意思就是：**\只会查找和它在相同的应用上下文（spirng的上下文 和 spring mvc的上下文就是两个不同的上下文）**&#x4E2D;定义的bean上面的@Transactional注解，如果你把它放在Dispatcher的应用上下文中，它只检查控制器（Controller）上的@Transactional注解，而不是你services上的@Transactional注解。 所以，可以确定的是我们是可以在Controller上使用事务注解的，但是我们不推荐这样做（本人也从来没有这样做过），这里只是为了说明spring对\的使用。

补充说明： 如果我们在spring的配置文件中

#### 加载顺序

```markup
<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-context.xml,classpath*:datasource-*.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
```

**在这个配置文件中，contextConfigLocation先加载，DispatcherServlet后加载**

#### [SpringMVC Controller配置和事务问题](https://segmentfault.com/q/1010000003791904)

*原理：Spring 是父容器， Spring MVC是子容器， 子容器可以访问父容器的bean,父容器不能访问子容器的bean* 参考：[**为啥Spring和Spring MVC包扫描要分开？**](http://labreeze.iteye.com/blog/2359957)

**配置如下：**

dispatcherServlet配置 spring-mvc.xml(spring-mvc 配置） mvc是子容器 mvc配置加载controller，并排出其他的，注意要使用`use-default-filters="false"`不去使用默认扫描

```markup
<!-- 使用Annotation自动注册Bean,只扫描@Controller -->
    <context:component-scan base-package="com.mxmht.xxx" use-default-filters="false"><!-- base-package 如果多个，用“,”分隔 -->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
```

contextConfigLocation 配置spring-context.xml(spring 配置） spring是主/父容器 `exclude-filter`会在默认扫描(use-default-filters不指定会使用默认的扫描)的注解中过滤掉指定的注解

```markup
    <!-- 使用Annotation自动注册Bean，解决事物失效问题：在主容器中不扫描@Controller注解，在SpringMvc中只扫描@Controller注解。  -->
    <context:component-scan base-package="com.mxmht.xxx"><!-- base-package 如果多个，用“,”分隔 -->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
```

这个就相当于base-package="com.mxmht.xxx" 楼上的com.xxx

#### [Springmvc controller 层 @Transactional 不起作用](http://blog.csdn.net/qq_36776347/article/details/77224468)

> 将Spring的配置controller的扫描关掉了，让Spring-MVC自己去扫描自己的controller. @transanction解释器的部分，将@transanction解释器在Spring-mvc中开启，这样就ok了。\
> 自己在想了一下实际上就是Spring与Spring mvc配置文件解析的是分开的，你在applicationContext.xml里面开启了@transanction解释器，就会在applicationContext.xml配置的扫描包的时候把扫描到的@transanction这样的注解开启事务，然后Spring-mvc.xml也有扫描而且没有开启解释器就把，有事务功能的controller替换为没有事务功能的controller，@Transactional 就不起作用

## application.properties文件配置

### SpringBoot配置属性之DataSource

[SpringBoot配置属性之DataSource - xixicat - SegmentFault](https://segmentfault.com/a/1190000004316491)

```
datasource.boss.url=jdbc:mysql://10.0.0.0:9800/boss
datasource.boss.username=root
datasource.boss.password=123456
datasource.boss.driver-class-name=com.mysql.jdbc.Driver
datasource.boss.max-idle=10
datasource.boss.max-wait=10000
datasource.boss.min-idle=5
datasource.boss.initial-size=5

#指定获取连接时连接校验的sql查询语句.
datasource.boss.validation-query=SELECT 1

#当从连接池借用连接时，是否测试该连接.
datasource.boss.test-on-borrow=true

#当连接空闲时，是否执行连接测试.
datasource.boss.test-while-idle=true

#指定空闲连接检查、废弃连接清理、空闲连接池大小调整之间的操作时间间隔
datasource.boss.time-between-eviction-runs-millis=60000
```


# spring boot Configuration

## 配置Spring Boot通过@ConditionalOnProperty来控制Configuration是否生效

[配置Spring Boot通过@ConditionalOnProperty来控制Configuration是否生效](https://blog.csdn.net/dalangzhonghangxing/article/details/78420057)

```java
@Configuration
@AutoConfigureBefore({NotifierListenerConfiguration.class,CompositeNotifierConfiguration.class})
public class EmailNotifierConfiguration {

    @Bean
    @ConditionalOnMissingBean
    // @ConditionalOnProperty("admin.notify.email.to")
    @ConditionalOnProperty(value="admin.notify.email.to", havingValue = "true")
    public EmailNotifier mailNotifier() {
        return new EmailNotifier();
    }

    @ConfigurationProperties("admin.notify.email")
    public class EmailNotifier extends AbstractStatusChangeNotifier {
        private String to[];
        private String cc[];
        @Override
        protected void doNotify(ClientApplicationEvent event) throws Exception {
            System.out.println("XXXXX");
        }

        public String[] getTo() {
            return to;
        }

        public void setTo(String[] to) {
            this.to = to;
        }

        public String[] getCc() {
            return cc;
        }

        public void setCc(String[] cc) {
            this.cc = cc;
        }
    }
}
```

`properties application.properties admin.notify.email.to=aa admin.notify.email.cc=bb`


# spring boot 配置文件

## @ConfigurationProperties

参考配置

```java
@Configuration
public class MailNotifyConfiguration {
    @Configuration
    @ConditionalOnProperty("mail.server-url")
    @AutoConfigureBefore({AdminServerNotifierAutoConfiguration.NotifierTriggerConfiguration.class, AdminServerNotifierAutoConfiguration.CompositeNotifierConfiguration.class})
    public static class MailNotifierConfiguration {
        private final ApplicationContext applicationContext;

        public MailNotifierConfiguration(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }

        @Bean
        @ConditionalOnMissingBean
        @ConfigurationProperties("notify.mail")
        public MailServerNotifier mailNotifier(InstanceRepository repository) {
            return new MailServerNotifier(emailSender(), repository, mailNotifierTemplateEngine());
        }

        @Bean
        @ConfigurationProperties("mail")
        public EmailSender emailSender() {
            return new EmailSender();
        }
        @Bean
        public TemplateEngine mailNotifierTemplateEngine() {
            SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
            resolver.setApplicationContext(this.applicationContext);
            resolver.setTemplateMode(TemplateMode.HTML);
            resolver.setCharacterEncoding(StandardCharsets.UTF_8.name());

            SpringTemplateEngine templateEngine = new SpringTemplateEngine();
            templateEngine.addTemplateResolver(resolver);
            return templateEngine;
        }
    }
}
public class EmailSender{
    private String serverUrl;
     public String getServerUrl() {
        return serverUrl;
    }

    public void setServerUrl(String serverUrl) {
        this.serverUrl = serverUrl;
    }
    //省略代码...
}
```

```
# 对应的配置文件
mail.server-url=http://server.com/mail/sendMail
notify.mail.enabled=true
notify.mail.to=exapmle@example.com
notify.mail.cc=exapmle@example.com
notify.mail.ignore-changes=
notify.mail.additional-properties.kbi_env=default
```

## 配置提示 配置元数据

### 描述

Spring Boot jar包含元数据文件，提供所有支持的配置属性的详细信息。这些文件旨在允许IDE开发人员在用户使用application.properties 或application.yml文件时提供上下文帮助和“代码完成” 元数据文件位于 jar包中的 META-INF/spring-configuration-metadata.json

### 自动生成

spring 提供了工具来帮助自动生成元数据，仅需引入依赖既可：

> You can easily generate your own configuration metadata file from items annotated with @ConfigurationProperties by using the spring-boot-configuration-processor jar. The jar includes a Java annotation processor which is invoked as your project is compiled. To use the processor, include a dependency on spring-boot-configuration-processor. <https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/html/configuration-metadata.html#configuration-metadata-annotation-processor>

生成的元数据文件存放在 META-INF/spring-configuration-metadata.json ![](https://3585898866-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LcehtLiMKFYRXXJ4Dzh%2F-Lcei1h6xvrd1a6Mrkgg%2F-Lcei5cVDqXNUl612k9F%2F2018-08-29-11-47-43.png?generation=1555494891196388\&alt=media)

注意： spring会识别`@ConfigurationProperties`注解的类，类中的属性必须有get方法才能给识别出来生成元数据

### 配置元数据

除了自动生成的元数据之外,也可以自己编写元数据， 在`resource`目录下创建`META-INF/additional-spring-configuration-metadata.json` ![](https://3585898866-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LcehtLiMKFYRXXJ4Dzh%2F-Lcei1h6xvrd1a6Mrkgg%2F-Lcei5cX1PGhn86x4CiD%2F2018-08-29-11-49-03.png?generation=1555494891539659\&alt=media)

配置详解参考： [配置元数据 - CSDN博客](https://blog.csdn.net/L_Sail/article/details/70342023) [Spring Boot的附录：附录B. 配置元数据](https://www.breakyizhan.com/springboot/3490.html)


# spring boot 配置mybatis

## 单数据源的简单配置

可以直接配置数据库

## 多数据源的配置

多个数据源需要写配置类，通过包路径指定不同的数据源


# MacAwesome


# markdown使用


# MAC APP Awesome


# \[markdown使用]\(/MacAwesome/SUMMARY.md)


# chrome


# intellij idea


# MacAwesome


# VS Code 的使用


# MAC Shell命令


# database


# druid使用

## 连接申请超时设定

配置参数为：

```markup
   <!-- 配置获取连接等待超时的时间 单位是毫秒 -->
   <property name="maxWait" value="10000"/>
   <property name="validationQuery" value="SELECT 'x'"/>
   <property name="validationQueryTimeout" value="2"/>
```

在1.0.23版本中，mysql的连接超时判断的方法还未被调用，超时时间的参数配置无效。 推荐升级到目前主流版本 1.0.29，在该版本，mysql的超时检测方法已被重载，要注意时间参数的单位为秒。 而且默认并不会调用validationQuery参数配置的sql语句，默认是使用ping的方式检测，ping检测方法也可以关闭 `-Ddruid.mysql.usePingMethod=false` ![](https://3585898866-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LcehtLiMKFYRXXJ4Dzh%2F-Lcei1h6xvrd1a6Mrkgg%2F-Lcei4TqBohSHFn1FLCf%2F15027068421199.jpg?generation=1555494890574615\&alt=media)

`validationQueryTimeout`是用来配置查询SQL的超时时间的，但是超时后会继续重试，而不是报错。`maxWait`参数才是真正确定了申请连接的超时时间，当一个申请连接操作达到`maxWait`指定的时间后便会抛出错误

## 配置监控

[Druid--基本配置及内置监控使用 - CSDN博客](https://blog.csdn.net/szzt_lingpeng/article/details/50612258)


# 框架


# 项目拆析

收集各类项目使用的技术栈

## sharegpt

<https://twitter.com/steventey/status/1599816553490366464>

Introducing ShareGPT → <http://sharegpt.com> Share your wildest ChatGPT conversations with one click.

Built with @nextjs, @vercel and @upstash

## sov2ex

[GitHub - bynil/sov2ex: A site search for V2EX](https://github.com/Bynil/sov2ex)

SOV2EX 的诞生离不开它们：

* [Elasticsearch](https://www.elastic.co/)
* [IK Analysis for Elasticsearch](https://github.com/medcl/elasticsearch-analysis-ik)
* [Flask](http://flask.pocoo.org/)
* [MongoDB](https://www.mongodb.com/)
* [Node.js](https://nodejs.org/) · [NPM](https://www.npmjs.com)
* [Webpack](https://webpack.github.io/)
* [React](https://facebook.github.io/react)
* [ES6](http://es6-features.org/) · [Babel](https://babeljs.io)
* [PostCSS](http://postcss.org/) · [cssnext](http://cssnext.io/)
* [jQuery](https://jquery.com/) · [pangu.js](https://github.com/vinta/pangu.js) · [Velocity.js](http://velocityjs.org/)
* [Visual Studio Code](https://code.visualstudio.com/)
* [Sketch](https://www.sketchapp.com/) · [Pixelmator](http://www.pixelmator.com/)
* Icon from <http://iconfont.cn>
* Material Design usage <https://material.io/guidelines/>
* 咖啡 · 网易音乐 · Google Chrome · rMBP


# 各种框架和工具

## 后端

* 规则引擎
  * [Aviator](http://fnil.net/aviator/) Aviator是一个轻量级、高性能的Java表达式执行引擎。[学习资料](http://loveshisong.cn/%E7%BC%96%E7%A8%8B%E6%8A%80%E6%9C%AF/2016-02-24-%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%BC%95%E6%93%8Eaviator.html)
  * [drools](http://www.drools.org/) 重量级规则引擎
* [人人开源/renren-security - 码云 Gitee.com](https://gitee.com/babaio/renren-security) 采用Spring、MyBatis、Shiro框架，开发的一套权限系统
* [Lemur开源/easypoi - 码云 Gitee.com](https://gitee.com/lemur/easypoi) POI 工具类,Excel的快速导入导出
* [ctripcorp/apollo](https://github.com/ctripcorp/apollo) 携程框架部门研发的分布式配置中心
* [akaxincom/openzaly](https://github.com/akaxincom/openzaly) openzaly 是 Akaxin 的服务器源代码，用以搭建私有聊天服务器
* [zeroturnaround/zt-exec](https://github.com/zeroturnaround/zt-exec) 调用shell脚本的工具，支持实时日志打印。

### nodejs

* [Express - 基于 Node.js 平台的 web 应用开发框架 | Express 中文文档](http://www.expressjs.com.cn/)

### python

* [Flask (A Python Microframework)](http://flask.pocoo.org/) Flask 是一个 Python 实现的 Web 开发微框架
* [Django | The Web framework for perfectionists with deadlines](https://www.djangoproject.com/) Django是一个开源的python Web应用框架，mvc架构

## 前端

* [amazeUI](http://amazeui.org/)\
  开源 HTML5 跨屏前端框架
* [layui - 经典模块化前端 UI 框架](https://www.layui.com/)
* [plasmo](https://www.plasmo.com/) 浏览器插件开发框架

## 浏览器模拟

[Browserless - #1 Web Automation & Headless Browser Automation Tool](https://www.browserless.io/)

[Puppeteer | Puppeteer](https://pptr.dev/) Browserless可以集成Puppeteer库

以下两个都是基于Puppeteer的sass服务： [Build and Run Synthetics That Scale](https://www.checklyhq.com/) [HTML to PDF or screenshots API service for developpers - Doppio - Doppio](https://doppio.sh/)

## 网站搭建

[More than just a WordPress Slider](https://www.sliderrevolution.com/) [Semplice 6 — Build your portfolio with pride](https://www.semplice.com/) [builder.io](https://builder.io/)

### 插件

* [ace code editor](https://ace.c9.io/)\
  web代码编辑器，支持大量的代码高亮

## 云部署平台：网站托管、云函数计算

### Cloudflare

缺点：价格贵 Cloudflare（CF）是一个提供CDN，域名管理，静态页面托管，边缘计算函数部署等的网络服务供应商。常见的用途：购买和/或托管你的域名（解析、动态域名等），给你的服务器套上CDN（可以隐藏ip免被墙），部署网站（CF Pages）。CF免费提供大多数服务。

### Vercel

优点：国内速度快，基本可以免费用 Vercel 是一个全球化的云平台，旨在帮助开发人员更快地构建和部署现代 Web 应用程序。本项目以及许多Web应用可以一键免费部署在Vercel上。无需懂代码，无需懂linux，无需服务器，无需付费，无需设置OpenAI API代理。缺点是需要绑定域名才可以在国内无墙访问。

### netlify

> Netlify 是一个现代化的 web 开发平台，它为静态网站和 web 应用程序提供部署、版本控制、自动化构建、托管、安全认证和其他服务。 与其他 web 开发平台不同的是，Netlify 支持静态网站的部署和托管。它允许开发人员使用流行的前端框架（如 React、Vue、Angular 等）和静态网站生成器（如 Jekyll、Hugo、Gatsby 等），并使用 Git 或其他版本控制工具进行持续集成和部署。开发人员可以通过 Git 提交代码并使用 Netlify 在几分钟内自动构建、测试和部署他们的应用程序。 Netlify 还提供了其他一些有用的功能，比如自动 HTTPS、分布式 CDN、A/B 测试、预渲染和无头浏览器。这些功能使得开发人员可以快速、高效地构建现代 web 应用程序，并为用户提供更好的体验。 总的来说，Netlify 是一个功能丰富、易于使用的 web 开发平台，它可以帮助开发人员更轻松地构建、发布和管理静态网站和 web 应用程序。 by cahtgpt

[Develop and deploy websites and apps in record time | Netlify](https://www.netlify.com/)

### 腾讯云函数

### 阿里云函数

### Sealos - 以 kubernetes 为内核的云操作系统发行版

[sealos](https://sealos.io/zh-Hans/)

## AI

* [Dust - Design and Deploy Large Language Models Apps](https://dust.tt/) AI应用开发


# docker


# kubernetes

## 在线学习环境 交互式学习环境

[Kubernetes狐狸教程](https://www.freeaihub.com/kubernetes/) [kubernetes官方教程](https://kubernetes.io/zh/docs/home/) [Katacoda - Interactive Learning Platform for Software Engineers](https://www.katacoda.com/) [精选项目课程\_IT热门课程\_蓝桥云课课程 - 蓝桥云课](https://www.lanqiao.cn/courses/) [腾讯云实验平台](https://cloud.tencent.com/labs) [阿里云全球培训中心](https://edu.aliyun.com/) [Educative: Interactive Courses for Software Developers](https://www.educative.io/)

**Kubernetes狐狸教程 vnc 浏览器打开失败解决方法** 安装其他Debian浏览器

```
apt-get update
# 火狐浏览器
apt-get install iceweasel
# goole浏览器
apt-get install google-chrome-stable
```

## 学习札记

### 参考资料

[Kubernetes概述：Pods、Nodes、Containers和Clusters - DockOne.io](http://dockone.io/article/3050)\
[十分钟带你理解Kubernetes核心概念 - DockOne.io](http://www.dockone.io/article/932)\
[第一次部署Kubernetes - DockOne.io](http://www.dockone.io/article/8255)\
[kubernetes Setup](https://kubernetes.io/docs/setup/)\
[Install and Set Up kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/#optional-kubectl-configurations)\
[kubernetes-sigs/kind](https://github.com/kubernetes-sigs/kind)\
[bsycorp/kind](https://github.com/bsycorp/kind)\
[Kubernetes（k8s）中文文档 目录\_Kubernetes中文社区](https://www.kubernetes.org.cn/docs)\
[Kubernetes(K8S)中文文档\_Kubernetes中文社区](http://docs.kubernetes.org.cn/)

[Kubernetes Handbook - Kubernetes中文指南/云原生应用架构实践手册 by Jimmy Song(宋净超)](https://jimmysong.io/kubernetes-handbook/)

### 部署kubernetes

MAC 上启用k8s [docker desktop上启用kubernetes](https://blog.csdn.net/chinaloong1949/article/details/103440150)

> docker 启用k8s 国内网路问题解决：<https://github.com/AliyunContainerService/k8s-for-docker-desktop>

### 笔记

[Overview of kubectl](https://kubernetes.io/docs/reference/kubectl/overview/)

**pod** 在Pod中的任何容器都共享了容器命名空间以及本地网络。因此在Pod的容器直接可以非常方便的进行通讯，就好像它们是运行在同一个机器上一样，同时彼此之间又保持隔离。\
Pod中可以包含多个容器，但是你还是应该尽可能的限制一下。因为Pod是作为一个最小单元，整体进行伸缩。这可能导致资源的浪费以及更多的费用开销。为了避免这种问题。Pod应该尽可能的保持"小"，通常指应该包含一个主进程，以及与其紧密合作的辅助容器（这些辅助容器通常被称为Sidecar）。 <http://dockone.io/article/3050>

**services** 如果Pods是短暂的，那么重启时IP地址可能会改变，怎么才能从前端容器正确可靠地指向后台容器呢？\
Service是定义一系列Pod以及访问这些Pod的策略的一层抽象。Service通过Label找到Pod组。因为Service是抽象的，所以在图表里通常看不到它们的存在，这也就让这一概念更难以理解。

### 学习

[Using kubectl to Create a Deployment](https://kubernetes.io/docs/tutorials/kubernetes-basics/deploy-app/deploy-intro/) [学习 Kubernetes 基础知识](https://kubernetes.io/zh/docs/tutorials/kubernetes-basics/)\
[Play with Kubernetes](https://labs.play-with-k8s.com/) [Kubernetes Playground | Katacoda](https://www.katacoda.com/courses/kubernetes/playground)

学习环境部署

```bash
# 运行kind https://github.com/bsycorp/kind
docker run --name kind -itd --privileged -p 8443:8443 -p 10080:10080 bsycorp/kind:latest-1.13
#进入
docker exec -it kind /bin/bash
# 这个kind是alpine 的Linux，因此不太一样
# 容器内安装bash complete
# https://www.oschina.net/translate/alpine-linux-install-bash-using-apk-command
apk add bash-completion
echo 'source /etc/profile.d/bash_completion.sh' >> ~/.bashrc
#安装提示 https://kubernetes.io/docs/tasks/tools/install-kubectl/#optional-kubectl-configurations
echo 'source <(kubectl completion bash)' >> ~/.bashrc
```

```bash
kubectl run kubernetes-bootcamp --image=jocatalin/kubernetes-bootcamp --port=8080
```

pod 的yml配置

```yml
apiVersion: v1
kind: Pod
metadata:
  name: gitea-pod
spec:
  containers:
  - name: gitea-container
    image: gitea/gitea:1.4
```

service 的json

```json
{
    "apiVersion": "v1",
    "kind": "Service",
    "metadata": {
        "name": "my-service"
    },
    "spec": {
        "selector": {
            "app": "MyApp"
        },
        "ports": [
            {
                "protocol": "TCP",
                "port": 80,
                "targetPort": 9376
            }
        ]
    }
}
```

## playground 搭建

### 单节点的环境安装

<https://github.com/bsycorp/kind\\>
使用这个kind可以做到一键安装kubenetes环境 非常简单。\
`docker run -it --privileged -p 8443:8443 -p 10080:10080 bsycorp/kind:latest-1.12`

### 多节点的环境安装

S1:\
安装一个 基于Centos的docker环境，即 docker in docker

```bash
#借助这个基于centos的dind镜像来开发
docker run --privileged --name kube-playground -itd cubedhost/centos7-dind-node
docker exec -it kube-playground bash
```

**以下均为容器内的操作**

准备： 如果系统默认没有命令补全提示，则需要安装

```bash
#yum install epel-release  
yum install bash-completion
# 安装docker completion
curl -XGET https://raw.githubusercontent.com/docker/cli/master/contrib/completion/bash/docker > /etc/bash_completion.d/docker
```

S2: 安装go

```bash
#这是国内安装源 https://golang.google.cn/dl/
curl -O https://dl.google.com/go/go1.11.9.linux-amd64.tar.gz  
tar -C /usr/local -xzf go1.11.1.linux-amd64.tar.gz  
#gopath是go的工作目录，下载的程序都会在这里
mkdir -p ~/go; echo "export GOPATH=$HOME/go" >> ~/.bashrc  
# gopath目录中的bin目录是安装的程序的可执行文件
echo "export PATH=$PATH:$HOME/go/bin:/usr/local/go/bin" >> ~/.bashrc  
source ~/.bashrc  
```

S3: 安装kind\
<https://github.com/kubernetes-sigs/kind\\>
kind是一个多节点的docker in docker的kubernetes服务集群部署工具

kind使用go安装，但是默认的安装方式`go get -u sigs.k8s.io/kind` 所使用的域名 sigs.k8s.io在国内是无法访问的，\
但是其实这个域名是会转到github的，因此可以通过手动从github下载后将目录地址换成sigs.k8s.io。

ps. go下载的文件会存放在$GOPATH/src中，本地已经存在时，go get会直接使用本地的文件。 <https://www.cnblogs.com/52php/p/6434771.html>

```bash
cd $GOPATH/src
git clone https://github.com/kubernetes-sigs/kind.git
mkdir sigs.k8s.io
mv kind sigs.k8s.io/kind
# 这里go get命令不要加-u参数了，这个参数会联网去检查。
go get sigs.k8s.io/kind
```

S4: 安装kubectl\
<https://kubernetes.io/docs/tasks/tools/install-kubectl/\\>
官方文档中的安装方式国内无法访问，因此参考这个:\
[阿里云 kubernetes yum 镜像仓库 CentOS](https://www.jianshu.com/p/9bac174bd2c5)\
\~\~[kubectl安装(1.9版本)(解决国内安装kubectl失败)](https://blog.csdn.net/faryang/article/details/79427573) \~\~

```bash
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

yum info kubectl
yum install kubectl

#安装补全提示 https://kubernetes.io/docs/tasks/tools/install-kubectl/#optional-kubectl-configurations
echo 'source <(kubectl completion bash)' >>~/.bashrc
source ~/.bashrc

# 因为尚未配置集群所以执行version会看到链接失败提示
kubectl version
```

S4: 使用kind

```bash
# 安装补全提示
echo 'source <(kind completion bash)' >> ~/.bashrc
source ~/.bashrc
#创建集群
kind create cluster
#导出集群配置
export KUBECONFIG="$(kind get kubeconfig-path)"
kubectl cluster-info
# check集群
kubectl version
```

以上就安装好了

### 开始游戏

**bootcamp** [Interactive Tutorial - Deploying an App](https://kubernetes.io/docs/tutorials/kubernetes-basics/deploy-app/deploy-interactive/)

```bash
kubectl run kubernetes-bootcamp --image=jocatalin/kubernetes-bootcamp:v2 --port=8080
kubectl describe pods kubernetes-bootcamp
kubectl proxy &

export POD_NAME=$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')
echo Name of the Pod: $POD_NAME
curl http://localhost:8001/api/v1/namespaces/default/pods/$POD_NAME/proxy/
kubectl logs $POD_NAME
```

### 多节点配置

[kind multi-node clusters](https://kind.sigs.k8s.io/docs/user/quick-start/#configuring-your-kind-cluster)

示例配置

```yml
# three node (two workers) cluster config
kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
nodes:
- role: control-plane
- role: worker
- role: worker
```

```bash
cat <<EOF > multi-kind.yml
kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
nodes:
- role: control-plane
- role: worker
- role: worker
EOF
kind create cluster --config=multi-kind.yml --name=multi-kind

export KUBECONFIG="$(kind get kubeconfig-path --name=multi-kind)"
kubectl cluster-info
``
```


# docker

## 常用命令

`docker run -d -p 8100:8100 --name custome_name -it IMAGE_NAME` `-d` 后台 `-i` 让容器的标准输入保持打开 `-t` 让docker分配一个伪终端并绑定到容器的标准输入上 `-e` username="ritchie" 设置环境变量；

> `-i`打开了输入，`-t`模拟一个终端
>
> 如果没有后台运行，在不使用-t参数的情况下，会因为没有终端而无法退出输出界面。因此如果不需要交互输入而且又没有-d参数置于后台，那么应该加上-t参数分配一个终端用于执行ctrl+c命令退出

`docker run --rm image_name [cmd]` --rm 参数会在容器退出时自动删除容器以及产生volume,一般在测试的时候会用

`docker start CONTAINER_ID` 启动容器,使用`-i`参数可以打开标准输入

`docker exec -it CONTAINER_ID /bin/bash` 进入容器 `docker logs --details -f CONTAINER_ID` 查看日志

`docker run -d -p 8100:8100 -v /root/workspace/projects/scriptbot-8100/docker_scriptbot.log:/scriptbot.log -v /root/workspace/projects/scriptbot-8100/scriptbot.jar:/scriptbot.jar --name scriptbot java:8 java -jar /scriptbot.jar --spring.profiles.active=demo --server.port=8100 --logging.file=/scriptbot.log` 通过挂载文件的方式启动

Docker Volume `docker run --name container-test -v /data debian /bin/bash` ：将`/data`挂在到容器中，文件可以在主机直接操作，`docker inspect -f {{.Volumes}} container-test`可以看到该volume在主机上的存储位置。dockerfile文件中的`VOLUME /data`是一样的效果。 `docker run -v /home/adrian/data:/data debian ls /data`：这种方式使用`-v`可以明确指定将主机的目录`/home/adrian/data`挂载到容器内的`/data`上

[docker磁盘占用空间查看及清理](https://blog.csdn.net/weixin_32820767/article/details/81196250)

`docker run -it --entrypoint="/bin/bash" image:tag` 使用--entrypoint覆盖入口

### 离线安装镜像

```bash
# 1 拉取镜像
docker pull images_names

# 2 保存本地
docker save [IMAGE ID/REPOSITORY]> /root/image.tar

# 3 上传服务器后 加载镜像
docker load < /root/image.tar
```

## docker的技巧

### 用docker来做linux应用程序，docker run --rm 的应用

在linux系统中可以借助docker镜像来做程序，而不需要真的去安装程序。

以安装redis客户端为例

```bash
#!/bin/bash
docker run -it --rm registry.cn-hangzhou.aliyuncs.com/boshen-ns/redis:3 redis-cli $*
```

编写一个如上的shell脚本`redis-cli`。\
`--rm`参数类似于playground模式，让容器在停止之后自动删除，保持干净。 `$*`的意思是接收执行shell脚本时的所有参数。

然后把该脚本连接到`/usr/local/bin`目录`ln -s /home/dev/redis-cli /usr/local/bin/redis-cli`，当然也可以直接在这个目录编写该脚本也一样。

授权`chmod +x /usr/local/bin/redis-cli`

这样就完成了，只要在终端输入redis-cli 就可以了

## docker理解

[(转)Docker镜像中的base镜像理解 - 笑侃码农 - 博客园](https://www.cnblogs.com/kb342/p/7649598.html)

## 深入学习

### docker 容器权限

[\[docker\]privileged参数 - CSDN博客](https://blog.csdn.net/hxpjava1/article/details/78392725)

```bash
$ docker help run
...
--privileged=false         Give extended privileges to this container
...
```

在没有开启`privileged`配置的默认情况下，docker容器内的root用户只是外部的一个普通用户权限，很多操作是不允许的，比如启动服务，比如在容器中再次启动一个docker服务。 因此`docker run --privileged=true`启动特权设置将允许你在容器内进行root操作。

### CMD 与 ENTRYPOINT

在Dockerfile中，只能有一个ENTRYPOINT指令，如果有多个ENTRYPOINT指令则以最后一个为准。\
在Dockerfile中，只能有一个CMD指令，如果有多个CMD指令则以最后一个为准。\
在Dockerfile中，ENTRYPOINT指令或CMD指令，至少必有其一。\
任何docker run设置的命令参数或CMD指令的命令，都将作为ENTRYPOINT指令的命令参数，追加到ENTRYPOINT指令的命令之后。\
如果没有ENTRYPOINT指令而是使用CMD作为启动命令，那么docker run设置的命令参数会覆盖CMD指令。

### 容器间的通讯

使用 --link 或者使用network [docker network基础 - wadeson - 博客园](https://www.cnblogs.com/jsonhc/p/7823286.html)

## Docker volume

[理解Docker（8）：Docker 存储之卷（Volume） - SammyLiu - 博客园](https://www.cnblogs.com/sammyliu/p/5932996.html) [Docker容器学习梳理--Volume数据卷使用 - 散尽浮华 - 博客园](https://www.cnblogs.com/kevingrace/p/6238195.html)

**关于文件覆盖** [Docker数据持久之volume和bind mount - CSDN博客](https://blog.csdn.net/docerce/article/details/79265858)

[官方文档-docker volume create](https://docs.docker.com/engine/reference/commandline/volume_create/#extended-description)

### 是什么

在不使用数据卷轴的情况下，docker容器内的数据只存在于其生命周期内，且容器外部以及其他容器都无法访问，容器一旦删除数据也丢失了（除非commit一个新的镜像），因此会有散需求需要满足： 一是 容器之间共享数据，二是容器内数据的持久化，三是容器共享宿主机数据。为了解决这三个问题所以有了数据卷轴的概念，在下文的使用中的[使用卷轴](https://book.rizon.top/docker/pages/-Lcei2wy5PyBibZPwbH-#使用卷轴)中会说明如何解决这两个问题。

### 使用

有两种使用方式，一个是docker run命令的`-v`参数，一个是dockerfile文件中的`VOLUME`E命令

\>

```bash
$ docker run --help
-v, --volume list                    Bind mount a volume  
    --volume-driver string           Optional volume driver for the container  
    --volumes-from list              Mount volumes from the specified container(s)
```

#### 挂载卷轴

**-v 方式挂载**

`-v [host-dir]:container-dir:[rw|wo]`

如果指定`host-dir`,那么就会挂载指定的目录到容器中的目录上，并且会覆盖容器中指定目录的内容。 如果不指定`host-dir`,那么会在系统的`/var/lib/docker/volumes`目录下生成一个目录挂载到容器内。 可以通过`docker inspect container-id`来查看挂载情况

**VOLUME方式挂载**

在编写dockerfile文件时可以通过`VOLUME dir`的方式去挂载一个卷轴，这种方式与使用-v但是不指定`host-dir`是相同的

#### 删除卷轴

Volume只有在下列情况下才能被自动删除：

* 该容器是用`docker rm －v`命令来删除的（-v是必不可少的）。
* docker run中使用了`--rm`参数

即使用以上两种命令，也只能删除没有容器连接的Volume。**连接到用户指定主机目录的Volume永远不会被docker删除**。即通过`-v host-dir:container-dir`明确指定主机目录的情况下，是不会删除主机上的文件的 如果你没有使用上面两种方式去删除卷轴，那么通过`docker volume COMMAND`将可以删除僵尸卷轴

`docker volume rm VOLUME [VOLUME...]`删除指定的卷轴 `docker volume prune` 清理所有不使用的卷轴

```bash
$ docker volume --help
Usage: docker volume COMMAND

Manage volumes

Commands:
  create      Create a volume
  inspect     Display detailed information on one or more volumes
  ls          List volumes
  prune       Remove all unused local volumes
  rm          Remove one or more volumes
```

**正如前面所说使用**`-v host-dir:container-dir`**指定的卷轴不会被删除，而且也不会出现在volume卷轴管理中，即通过**`docker volume ls`**命令是看不到这种方式创造的卷轴的**

docker volume命令是在后来的版本中引入的新功能，它除了可以管理所有的卷轴外（除了明确指定主机目录的卷轴），还可以独立的去创建一个卷轴，这样可以方便的在多个容器之间共享卷轴

#### 使用卷轴

卷轴的目的是为了解决前文提出的三个问题，那么我们这里一一说明

**数据持久化以及共享宿主机数据场景**

我们通过`-v host-dir:container-dir`的方式将主机上的一个目录映射到容器内，这样对container-dir目录的所有操作就是对主机host-dir目录的操作，容器删除后该目录的数据仍然存在。 这样便解决了数据持久化问题，以及宿主机和容器共享数据问题。

**容器之间共享数据场景**

docker run命令中可以通过`--volumes-from`参数来共享其他容器或卷轴的数据

一种方式是 `docker run --volumes-from container-id`指定一个容器可以共享该容器中创建的卷轴，这里只是共享指定容器中创建的数据卷轴而不是共享容器的数据（目标容器运行与否无关）.

还有一种更优雅的方式则是通过`docker volume`命令

```bash
#https://docs.docker.com/engine/reference/commandline/volume_create/#extended-description
$ docker volume create hello

hello

$ docker run -d -v hello:/world busybox ls /world
```

**⚠️通过**`docker volime`**命令创建的卷轴在删除容器时即使加了**`-v`**参数(**`docker rm -v container-id`**)也不会删除卷轴**

## docker Compose

常用命令

```bash
docker-compose up -d [service]
#start/stop/restart/kill 服务:
docker-compose start/stop/restart/kill [service]
# Stops containers and removes containers, networks, volumes(-v参数), and images(--rmi参数)
docker-compose down -v
```

[Compose 命令说明 - Docker —— 从入门到实践 - 极客学院Wiki](http://wiki.jikexueyuan.com/project/docker-technology-and-combat/commands.html) [Docker Compose 配置文件详解](https://www.jianshu.com/p/2217cfed29d7) [Docker-Compose入门 - CSDN博客](https://blog.csdn.net/chinrui/article/details/79155688) [docker-compose ports和expose的区别 - 歪麦博客](https://www.awaimai.com/2138.html)

在一个docker-compose中的容器，会被自动放在一个网络环境里，不用再使用 --link 去连接容器了，可以直接通过容器的名称(`container_name`)或者service的名称来访问其他容器。使用`docker network ls`命令可以看到会自动创建一个桥接网络，在第一次运行compose时也可以看到提示创建了网络。

```
#web容器中可以通过yapi-mongodb 这个容器名称或者 mongodb 这个service名称 访问mongodb容器:ping yapi-mongodb；ping mongodb
version: "3"
services:
  mongodb:
    image: mongo:latest
    container_name: yapi-mongodb
    volumes:
      - ./mongo/db:/data/db
  web:
    image: crper/yapi
    container_name: yapi-web
    depends_on:
      - mongodb
    ports:
      #ports中如果不指定映射到的端口比如只写`- 3000`则会随机映射到主机的端口
      - "3000:3000"
```

参考配置：

* <https://store.docker.com/community/images/fiochen227/yapi>
* <https://store.docker.com/community/images/branchzero/yapi>

## dockerfile 的多阶段构建 （多个from）

示例

```
## https://github.com/zerotier/ZeroTierOne/blob/2d21c18ebdacc1c011831c19c162e795c0fe6dde/ext/installfiles/linux/zerotier-containerized/Dockerfile
FROM debian:stretch as builder

## Supports x86_64, x86, arm, and arm64

RUN apt-get update && apt-get install -y curl gnupg
RUN apt-key adv --keyserver ha.pool.sks-keyservers.net --recv-keys 0x1657198823e52a61  && \
    echo "deb http://download.zerotier.com/debian/stretch stretch main" > /etc/apt/sources.list.d/zerotier.list
RUN apt-get update && apt-get install -y zerotier-one=1.2.12

FROM alpine:latest
MAINTAINER Adam Ierymenko <adam.ierymenko@zerotier.com>

LABEL version="1.2.12"
LABEL description="Containerized ZeroTier One for use on CoreOS or other Docker-only Linux hosts."

# Uncomment to build in container
#RUN apk add --update alpine-sdk linux-headers

RUN apk add --update libgcc libstdc++

RUN mkdir -p /var/lib/zerotier-one

COPY --from=builder /var/lib/zerotier-one/zerotier-cli /usr/sbin/zerotier-cli
COPY --from=builder /var/lib/zerotier-one/zerotier-idtool /usr/sbin/zerotier-idtool
COPY --from=builder /usr/sbin/zerotier-one /usr/sbin/zerotier-one

ADD main.sh /
RUN chmod 0755 /main.sh
ENTRYPOINT ["/main.sh"]
CMD ["zerotier-one"]
```

## 参考

[**Docker折腾记: (1)构建yapi容器,从构建发布到可用 - 掘金**](https://juejin.im/post/5b4c518b6fb9a04fd4508af1)

[在Docker容器中运行Spring Boot应用](https://blog.csdn.net/lsy0903/article/details/53048198/)

[CentOS7 Tomcat 启动过程很慢,JVM上的随机数与熵池策略](https://blog.csdn.net/lanmo555/article/details/51909021)

[docker运行jar文件](https://blog.csdn.net/bianchengninhao/article/details/80143950)

[Docker network第五讲-替代“--link”（Docker系列）](https://blog.csdn.net/zsl129/article/details/53939646)

[离线服务器下docker的部署与应用](https://blog.csdn.net/u011372108/article/details/80549731)

[**Docker镜像构建-构建yapi容器,从构建发布到可用**](https://juejin.im/post/5b4c518b6fb9a04fd4508af1)

[Docker数据持久化与容器迁移 - shiningrise - 博客园](https://www.cnblogs.com/shiningrise/p/5821604.html)

[如何选择Docker基础镜像 - CSDN博客](https://blog.csdn.net/nklinsirui/article/details/80967677)

[docker容器配置ssh登入实录 - CSDN博客](https://blog.csdn.net/u011552182/article/details/78650907)


# docker 常用镜像

## 命令

`docker build --rm -t name:tag -f Dockerfile .`

## mysql

具体帮助查看：<https://hub.docker.com/r/library/mysql/>

```bash
docker volume create mysqldb
docker run --hostname mysql --name mysql -v mysqldb:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.6.41
```

## python

### 网易云音乐下载

```
## https://github.com/othorizon/Dockerfiles
FROM alpine/git:1.0.7 as stage1

ENV NETEASE_DL_GIT https://github.com/ziwenxie/netease-dl
RUN git clone $NETEASE_DL_GIT /netease_dl_git

FROM python:3.6 as stage2

COPY --from=stage1 /netease_dl_git /netease_dl_git
WORKDIR /netease_dl_git
RUN python setup.py install

FROM python:3.6-alpine

LABEL description="dockerized netease-dl(https://github.com/ziwenxie/netease-dl)"
LABEL dockerfile_author="https://github.com/othorizon"

COPY --from=stage2 /usr/local/lib/python3.6/site-packages /usr/local/lib/python3.6/site-packages
COPY --from=stage2 /usr/local/bin/netease-dl /usr/local/bin/netease-dl

VOLUME [ "/output" ]

ENTRYPOINT [ "netease-dl","-o","/output" ]

CMD ["--help"]
```

## 优秀dockerfile示例

```
## https://github.com/zerotier/ZeroTierOne/blob/2d21c18ebdacc1c011831c19c162e795c0fe6dde/ext/installfiles/linux/zerotier-containerized/Dockerfile
FROM debian:stretch as builder

## Supports x86_64, x86, arm, and arm64

RUN apt-get update && apt-get install -y curl gnupg
RUN apt-key adv --keyserver ha.pool.sks-keyservers.net --recv-keys 0x1657198823e52a61  && \
    echo "deb http://download.zerotier.com/debian/stretch stretch main" > /etc/apt/sources.list.d/zerotier.list
RUN apt-get update && apt-get install -y zerotier-one=1.2.12

FROM alpine:latest
MAINTAINER Adam Ierymenko <adam.ierymenko@zerotier.com>

LABEL version="1.2.12"
LABEL description="Containerized ZeroTier One for use on CoreOS or other Docker-only Linux hosts."

# Uncomment to build in container
#RUN apk add --update alpine-sdk linux-headers

RUN apk add --update libgcc libstdc++

RUN mkdir -p /var/lib/zerotier-one

COPY --from=builder /var/lib/zerotier-one/zerotier-cli /usr/sbin/zerotier-cli
COPY --from=builder /var/lib/zerotier-one/zerotier-idtool /usr/sbin/zerotier-idtool
COPY --from=builder /usr/sbin/zerotier-one /usr/sbin/zerotier-one

ADD main.sh /
RUN chmod 0755 /main.sh
ENTRYPOINT ["/main.sh"]
CMD ["zerotier-one"]
```


# 效率工具


# 解决dns污染导致域名解析失败

打开<http://ping.chinaz.com/gist.github.com> 输入希望解析的域名，比如`gist.github.com`,然后`ping检测` 检测结束后按照响应时间排序找一个有效的ip配置host ![](https://ws4.sinaimg.cn/large/006tKfTcly1fpxs49br3zj31kw099799.jpg) ![](https://ws3.sinaimg.cn/large/006tKfTcly1fpxs51vz0lj31iy0h2k3x.jpg)

```
192.30.253.118 http://gist.github.com
192.30.253.119 http://gist.github.com
```


# sonarqube 相关配置

* 使用maven向sonar推送代码时指定分支： `-Dsonar.branch=branchname`
* 配置单元测试代码覆盖率,使用JaCoCO 1. To launch JaCoCo as part of your Maven build, use this command: mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install -Dmaven.test.failure.ignore=true 2. 想要让sonar去展示得到单元测试的报告，需要配置单元测试报告目录：sonar.jacoco.reportPaths=target/jacoco.exec

  ```
    方法1:在maven项目中配置
    方法2:在mvn去执行sonar的代码扫描时增加这个参数配置
  ```
* 示例jenkins构建命令

```bash
mvn clean org.jacoco:jacoco-maven-plugin:0.7.9:prepare-agent \
package org.sonarsource.scanner.maven:sonar-maven-plugin:3.3.0.603:sonar \
-Dmaven.test.failure.ignore=true -Dsonar.jacoco.reportPaths=target/jacoco.exec -Dsonar.branch=${specify} -Pdev -U
```


# Iterm2 使用

\[TOC]

## 参考

* [iTerm2 与 Tmux 的整合](http://www.jianshu.com/p/f2e585453196)
* [iTerm2 for mac配置克隆会话功能 ](http://blog.itpub.net/29773961/viewspace-2143751/)
* [Mac端iTerms2实现Xshell的“复制SSH渠道”功能](http://www.jianshu.com/p/086d4b3cc00a)
* [Mac下iterm2 克隆会话功能](http://blog.csdn.net/zly9923218/article/details/52458161)

## 配置

### 实现session复制（会话复制）

1. 在`.ssh`目录下，修改（没有则新建） `config`文件

   ```bash
    vi .ssh/config

    #添加如下内容
    #防止github被ssh记忆
    Host github.com other-exception.example.com
    ControlPath none

    Host *
    ControlMaster auto
    ControlPersist yes
    ControlPath ~/.ssh/master-%r@%h:%p
   ```
2. ITerm2 配置Profiles

   ![](https://3585898866-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LcehtLiMKFYRXXJ4Dzh%2F-Lcei1h6xvrd1a6Mrkgg%2F-Lcei45GJi0XyK0QJ4Hm%2F15103143996026.png?generation=1555494890298115\&alt=media)

解释：

* 配置不对github域名起作用，不然会影响使用sshkey来请求github。[ssh ControlMaster exceptions](https://unix.stackexchange.com/questions/122835/ssh-controlmaster-exceptions)
* ControlPersist为长连接，打开之后即使关闭了所有relay的ssh连接，一段时间内也能无需密码重新连接。可按时间配置,`ControlPersist 8h`这就是保持8小时
* ControlMaster配合ControlPath一起使用，当打开了一个relay的ssh连接之后，再次打开无需重复输入密码，但是关闭所有连接后，再次连接relay仍需输入密码。
* Compression为压缩选项，打开之后加快数据传输速度。`Compression yes`

参考： [复制SSH会话,避免多次密码输入](https://www.topjishu.com/11051.html) [复制SSH会话,避免多次密码输入-evernote](https://app.yinxiang.com/shard/s9/nl/679699/e39ff3a8-0172-4e3c-8491-dfac5b5939b1/) [ssh省去重复输入密码](http://blog.csdn.net/xuanwu_yan/article/details/45666797) [ssh省去重复输入密码-evernote](https://app.yinxiang.com/shard/s9/nl/679699/b7e80339-71b2-461f-b45d-194a6ef9b767/)

## local echo

解决ssh网速满，导致打字卡顿问题。 原因是 ssh时每个输入字符都会向远端发送数据，所以可以开启local ehco 和local line Editing。 详见：[ssh 打字卡顿 - SegmentFault 思否](https://segmentfault.com/q/1010000000150459) 可以使用mosh这个工具，[Mosh: the mobile shell](https://mosh.org/#usage)


# gitbook

## 使用

自动生成summary文件[summarybuilder](https://www.npmjs.com/package/summarybuilder)

```bash
gitbook init //初始化目录文件
gitbook help //列出gitbook所有的命令
gitbook --help //输出gitbook-cli的帮助信息
gitbook build //生成静态网页
gitbook serve //生成静态网页并运行服务器
gitbook build --gitbook=2.0.1 //生成时指定gitbook的版本, 本地没有会先下载
gitbook ls //列出本地所有的gitbook版本
gitbook ls-remote //列出远程可用的gitbook版本
gitbook fetch 标签/版本号 //安装对应的gitbook版本
gitbook update //更新到gitbook的最新版本
gitbook uninstall 2.0.1 //卸载对应的gitbook版本
gitbook build --log=debug //指定log的级别
gitbook builid --debug //输出错误信息
```

### 插件

[Plugins for GitBook](https://plugins.gitbook.com/)

启用方法：To add a plugin, create or edit your book.json to include the plugin identifier:

```javascript
{
    "plugins": ["thePlugin"]
}
```

**导航插件 my-navigator** add a table of content navigator on the right top of each page, and a return to top button without smart-phone or tablet

```javascript
{
    "plugins": ["my-navigator"]
}
```

## 脚本

自动生成summary文件的脚本，用shell写的，逐级递归遍历 记：脚本写于2018-03-10 02:14 ，`summarybuilder`这个是其他人用npm写的工具，但是不太好用，于是自己写了一个，不会写shell深夜研究了好久才弄出来

```bash
#!/bin/bash
#set -x

getspcae(){
    tab=""
    for ((i=2;i<$1;i++))
    do
     tab=${tab}"  "
    done
    echo "${tab}"
}

writeline(){
    fpath=$1
    depth=$2
    title=`head -1 $fpath`
    title=${title:1}
    #将开头的#号去掉
    title=${title/#\#/}
    #去掉开头空格
    title=${title/# /}

    tab=`getspcae $depth`

    result="${result}${tab}* [${title}](${fpath})\n"
}
getnode(){
    title=$1
    depth=$2
    title=${title:2}
    title=${title#*/}
    tab=`getspcae $depth`

    echo "${tab}* ${title}"
}
dive(){
    local dirs=$1
    if [ -z "$dirs" ]; then return;fi


    local depth=$2
    local node=$3
    local nextdepth=`expr $2 + 1`
    #写节点
    if [ -n "$node" ];then
      result="${result}${node}\n"
    fi

    for dir in ${dirs[@]}
    do

        if [ -d "${dir}" ];then
          nextnode=`getnode "${dir}" ${nextdepth}`
          dive "`find ${dir} \( -iname "*.md" -or -type d \) -d 1`" $nextdepth "${nextnode}"
        else
          writeline "${dir}" ${nextdepth}
        fi
    done
}

result=""
#\( -iname "*.md" -or -type d \)
dive "`find . \( -iname "*.md" -or -type d \) ! -iname "README.MD" ! -iname "SUMMARY.MD" ! -iname "_SUMMARY.MD" ! -path "./.git" -d 1`" 1

#写文件
echo make SUMMARY.md
echo "${result}" > SUMMARY.md
echo finish
```


# github awesome github资源推荐

## 工具类

* [moco](https://github.com/dreamhead/moco) 一个简单快捷的mock接口服务，[详细使用](/xiaolgong-ju/mock-server)
* [cacher](https://www.cacher.io/) **推荐** gist代码片段管理UI工具，有客户端和web端
* [~~Lepton~~](https://github.com/hackjutsu/Lepton) ~~gist代码片段管理UI工具~~
* [noVNC](https://github.com/novnc/noVNC) webssh,web端ssh,HTML VNC Client Library and Application
* [txthinking/brook](https://github.com/txthinking/brook) 跨平台vpn工具，proxy代理工具

## gist 代码片段管理

作者：章乐 链接：<https://www.zhihu.com/question/21343711/answer/32023379> 1. <http://roughdraft.io> 可以把Gist的代码片段作为一个独立的网页发布。 2. <http://bl.ocks.org> 可以把gist里的HTML、CSS、JavaScript代码渲染好的结果在浏览器中展示出来


# playground 在线试用平台汇总

## 其他

**Wordpress** [WordPress Sandbox - Free Test/Staging/Sandbox WordPress Site with Admin Access](https://wpsandbox.net/) [Quick WordPress Testing & Staging Sites – TasteWP](https://tastewp.com/#!)

## 大数据类

**mongodb** [MongoDB Online Playground | HumongouS.io](https://www.humongous.io/app/playground/mongodb/new) [Mongo playground](https://mongoplayground.net/) [Explore Your Data with Playgrounds — MongoDB for VS Code](https://www.mongodb.com/docs/mongodb-vscode/playgrounds/)

**ClickHouse** <https://github.com/lodthe/clickhouse-playground> <https://fiddle.clickhouse.com/>

**mysql、postgreSql、SQLite** [DB Fiddle - SQL Database Playground](https://www.db-fiddle.com/)


# linux中的office

\[TOC]

## libreoffice

[参考](http://blog.csdn.net/ljihe/article/details/77250206)

* 安装

  `yum install libreoffice` &#x20;
* 转换文档格式\
  无论是word还是excel都可以转换为pdf、html等 word转pdf `/usr/bin/libreoffice --invisible --convert-to pdf *.docx` word转html `/usr/bin/libreoffice --invisible --convert-to html *.docx` 批量转换

  ```bash
  mkdir doc_pdf
  mkdir doc_html
  mkdir xls_html
  find . -name '*.doc*' -print0 |xargs -0 /usr/bin/libreoffice  --invisible --convert-to pdf --outdir 'doc_pdf'
  find . -name '*.doc*' -print0 |xargs -0 /usr/bin/libreoffice  --invisible --convert-to html --outdir 'doc_html'
  find . -name '*.xls*' -print0 |xargs -0 /usr/bin/libreoffice  --invisible --convert-to html --outdir 'xls_html'
  ```

  > 如果转换出来的中文乱码，则需要安装中文字体，常见的如宋体'simsun.ttf' sudo cp simsun.ttc /usr/share/fonts cd /usr/share/fonts 修改权限 sudo chmod 644 simsun.ttc 更新字体缓存： sudo fc-cache -fv


# linux screen 工具

[Screen User’s Manual](https://www.gnu.org/software/screen/manual/screen.html)

## 常用命令

### 修改session name

创建session 时，使用参数指定名字： `screen -S my_screen_name`

修改已有session name：`ctrl+a :sessionname my_screen_name`

## FAQ

### screen中出现命令无法使用问题 环境变量缺失文图

这是因为进入screenn之后，用户环境变量没有加载（比如 \~/.bashrc),\
在screen的配置文件: `～/.screenrc`文件或者 `/etc/screenrc` 或者其他在`man screen`中提及的文件中添加如下内容：

```bash
# make the shell in every window a login shell
shell -$SHELL
```

参考：[How do I ask screen to behave like a standard bash shell?](https://serverfault.com/questions/126009/how-do-i-ask-screen-to-behave-like-a-standard-bash-shell) [Linux screen如何加载用户配置 - yasaken - CSDN博客](https://blog.csdn.net/yasaken/article/details/7418583)

### screen 中使用256色 ls等命令没有颜色显示

screen默认使用8-color，\
在screen的配置文件: `～/.screenrc`文件或者 `/etc/screenrc` 或者其他在`man screen`中提及的文件中添加如下内容：

```bash
term screen-256color
```

参考： [GNU Screen #use\_256\_colors - ArchWiki](https://wiki.archlinux.org/index.php/GNU_Screen#Use_256_colors)


# 简单Mock服务（moco）

下载地址：<https://github.com/dreamhead/moco>

## 简单使用 （Standalone Moco Runner）

[Qucik Start 官方说明](https://github.com/dreamhead/moco#quick-start) [Standalone Moco Runner Download（v 0.11.1)](http://central.maven.org/maven2/com/github/dreamhead/moco-runner/0.11.1/moco-runner-0.11.1-standalone.jar)

### 启动

`java -jar moco-runner-<version>-standalone.jar http -p 12306 -s 22306 -c settings.json` `-p`启动端口 `-s`shutdown端口，可以不指定，会默认分配。shutdown命令`java -jar moco-runner-<version>-standalone.jar shutdown -s 9527` `-c`配置文件

### 配置

\`\`\`json settings.json \[ { "request": { "uri": "/json", "method" : "post" #如果不指定method则get、post均可以 }, "response": { "json": #json格式返回值 { "foo" : "bar" } } }, { "request": { "uri": "/text", "method" : "get" }, "response": { "text" : "bar" #text格式返回值 } } ]

````
### 匹配json请求参数

[JSON Request](https://github.com/dreamhead/moco/blob/master/moco-doc/apis.md#json-request)

#### 完全匹配json请求

```json
{
    "request": {
        "uri": "/json",
        "json": {
            "foo": "bar"
        }
    },
    "response": {
        "text": "foo"
    }
}
````

#### 使用JSONPath来匹配是否包含指定的参数

示例： 当请求参数包含一个`"$.biz.type"="lookback_bill"`时，则返回数据

```bash
curl -X POST \
  http://10.111.17.157:8100/activiti/thrid/calApi \
  -H 'Content-Type: application/json' \
  -d '{
    "biz":{
        "type":"lookback_bill",
        "as":"assa",
        "aaa":"xxx"
    }
}'
```

\`\`\`json settings.json { "request": { "uri": "/activiti/thrid/calApi", "json\_paths":{ "$.biz.type":"lookback\_bill" } }, "response": { "json": { "code": 200, "message": "模拟计费成功" } } }

````
[官方文档](https://github.com/dreamhead/moco/blob/master/moco-doc/apis.md#jsonpath)
官方示例：

```json
{
  "request":
    {
      "uri": "/jsonpath",
      "json_paths":
        {
          "$.book[*].price": "1"
        }
    },
  "response":
    {
      "text": "response_for_json_path_request"
    }
}
````


# npm

[nodejs-npm源的配置 - CSDN博客](http://blog.csdn.net/wangmeng951011/article/details/52953517) [淘宝 NPM 镜像](https://npm.taobao.org/)

npm全局应用安装目录：`usr/local/lib`


# Visual Studio Code 的使用

\[TOC]

## 插件

> 插件的快捷键配置、使用说明都在的首页中

* JSON Tools (eriklynd.json-tools) &#x20;

  Tools for manipulate JSON. You can pretty/minify JSON with this extension.
* Git History (git log) (donjayamanne.githistory) &#x20;

  View git log along with the graph and details.
* REST Client (humao.rest-client) &#x20;

  REST Client allows you to send HTTP request and view the response in Visual Studio Code directly.
* SQL Beautify (sensourceinc.vscode-sql-beautify) &#x20;

  格式化sql，人气较低，不太好用
* [Settings Sync](https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync) 将vscode的设置同步到gist来存储

### markdown

* markdownlint  :语法提示
* Markdown All in One   :All you need for Markdown (keyboard shortcuts, table of contents, auto preview and more).
* Markdown Preview Enhanced :强推，md预览
* Markdown PasteURL ：快捷的粘贴url地址
* Markdown Paste    ：快捷的粘贴图片

## 个性化设置

> Settings File Locations Depending on your platform, the user settings file is located here: Windows `%APPDATA%\Code\User\settings.json` Mac `$HOME/Library/Application Support/Code/User/settings.json` Linux `$HOME/.config/Code/User/settings.json`

### 首选项-设置

> VSCode的设置是json形式的配置，做了图形化显示，设置分为用户设置和工作区设置，工作区设置就是保存在当前工作区的`settings.json`，用户设置则是全局的。自定义的设置是一个新的json配置文件，里面的配置会覆盖系统默认配置文件，所以不用担心修改错了东西而改不回去

```javascript
{
    "rest-client.environmentVariables": {
        "local": {
            "host": "localhost",
            "token": "test token"
        },
        "production": {
            "host": "example.com",
            "token": "product token"
        }
    },
    "terminal.integrated.fontFamily": "'Meslo LG L DZ for Powerline'",
    "workbench.activityBar.visible": true,
    "java.errors.incompleteClasspath.severity": "ignore",
    "workbench.iconTheme": "vs-seti",
    "files.autoGuessEncoding": false,
    "workbench.editor.closeOnFileDelete": false,
    "gitHistory.showEditorTitleMenuBarIcons": false,
    "workbench.statusBar.feedback.visible": false,
    "pasteImage.path": "./media"
}
```

* terminal.integrated.fontFamily

  调整终端的字体，如果之前使用了oh-my-zsh的终端主题，该主题使用了Powerline的字体（有一些特殊符号），所以在vsCode集成的终端控制台也应该修改字体 ![fontShow](https://3585898866-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LcehtLiMKFYRXXJ4Dzh%2F-Lcei1h6xvrd1a6Mrkgg%2F-Lcei5AJEWeh0vLdexHw%2F15023566387149.png?generation=1555494891250460\&alt=media)
* "files.hotExit": "onExit"

  是否允许热退出，即文件未保存的情况下也可以退出编辑器，下次打开会恢复，默认是允许的。

#### 其他首选项设置

* problems.decorations.enabled

  文件图标上显示错误信息的数字提示。The number of errors/warnings is shown in the decoration. The setting problems.decorations.enabled allows you to enable/disable the error/warning decorations.

### 首选项-键盘快捷方式

> 键盘快捷键设置也是json格式，与系统设置原理一样，默认打开是图形化界面，但是可以进入`高级自定义`修改`keybindings.json`文件 这里除了定义系统按键也可以定义插件（json设置中以extesion前缀）的按键设置，当然 插件的快捷键也可以在插件的首页里进行配置更为清晰

```javascript
// 将键绑定放入此文件中以覆盖默认值
[
    {
        "key": "cmd+/",
        "command": "editor.action.triggerSuggest",
        "when": "editorHasCompletionItemProvider && editorTextFocus && !editorReadonly"
    },
    {
        "key": "ctrl+/",
        "command": "editor.action.commentLine",
        "when": "editorTextFocus && !editorReadonly"
    },
    {
        "key": "ctrl+d",
        "command": "editor.action.deleteLines",
        "when": "editorTextFocus && !editorReadonly"
    },
    {
        "key": "cmd+alt+l",
        "command": "editor.action.formatDocument",
        "when": "editorHasDocumentFormattingProvider && editorTextFocus && !editorReadonly"
    },
    {
        "key": "cmd+d",
        "command": "editor.action.copyLinesDownAction",
        "when": "editorTextFocus && !editorReadonly"
    },
    {
        "key": "ctrl+cmd+j",
        "command": "extension.prettyJSON"
    },
    {
        "key": "ctrl+cmd+m",
        "command": "extension.minifyJSON"
    },
    {
        "key": "cmd+.",
        "command": "-editor.action.quickFix",
        "when": "editorHasCodeActionsProvider && editorTextFocus && !editorReadonly"
    },
    {
        "key": "cmd+[Period]",
        "command": "editor.action.quickFix"
    },
    {
        "key": "ctrl+[Backquote]",
        "command": "workbench.action.terminal.toggleTerminal"
    }
]
```

### 任务配置

#### typescript的一键编译任务

```javascript
//自动运行tsc编译当前窗口的文件，并使用node命令来执行编译后的js文件
{
    "label": "Run tests",
    "type": "shell",
    "command": "(echo ${file}|awk -F '.' '{print $1}'|xargs tsc) && (echo ${file}|awk -F '.' '{print $1}'|xargs node)",
    "group": {
        "kind": "build",
        "isDefault": true
    },
    "presentation": {
        "reveal": "always",
        "panel": "shared"
    }
}
```


# 配置开发环境

## vagrant

vagrant可以快速启动一个虚拟机，在虚拟机环境中创建开发环境，与主机隔离。并且开发环境可以快速打包分享给他人。 而且和intellj brains系列的开发套件兼容很好。

### 安装

[Install | Vagrant | HashiCorp Developer](https://developer.hashicorp.com/vagrant/downloads)

`vagrant autocomplete install --bash --zsh`

> [Command-Line Interface | Vagrant | HashiCorp Developer](https://developer.hashicorp.com/vagrant/docs/cli)

### 基本使用

amr64 架构的ubuntu镜像：[Vagrant Cloud by HashiCorp](https://app.vagrantup.com/bento/boxes/ubuntu-20.04-arm64)

```shell
vagrant init bento/ubuntu-20.04-arm64
vagrant up
```

这个镜像的作者bento是受官方信任的，并且这个镜像支持parallels作为provider。

### 使用parallels desktop作为虚拟机

[GitHub - Parallels/vagrant-parallels: Vagrant Parallels Provider](https://github.com/Parallels/vagrant-parallels)

**原理** parallese desktop的virtual machine包含一个uuid，vagrant同该uuid将一个vagrant的配置和指定的vm虚拟机建立联系。\
打开一个pvm的虚拟机文件（右键显示包内容），其中包含一个config.vps文件，文本打开文件，内容：

```xml
<?xml version="1.0" encoding="UTF-8"?>
<ParallelsVirtualMachine schemaVersion="1.0" dyn_lists="VirtualAppliance 0">
   <AppVersion>18.1.1-53328</AppVersion>
   <ValidRc>2147485060</ValidRc>
   <Identification dyn_lists="">
      <VmUuid>{9c27ae79-fbe5-4c84-9cdc-e1459f642b91}</VmUuid>
      <VmType>0</VmType>
```

其中 `<VmUuid>` 就是这个虚拟机的uuid。 vagrant的配置文件

```bash
$cd .vagrant/machines/default/parallels
$ls -al
action_provision action_set_name  box_meta creator_uid id index_uuid private_key synced_folders vagrant_cwd
$cat id
9c27ae79-fbe5-4c84-9cdc-e1459f642b91
$cat action_set_name
1.5:9c27ae79-fbe5-4c84-9cdc-e1459f642b91
```

如果想使用一个现存的vm虚拟机作为vagrant的虚拟机，可以通过修改`action_provision`和`id`这两个文件的值来实现关联到存在的vm。

## podman/docker 等虚拟环境

### ubunt

**安装c编译环境**

[ubuntu-ports | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror](https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu-ports/)

```shell
apt-get update
apt install make
apt install gcc
apt install build-essential gdb 

```

**arm64配置国内源**

参考：<https://blog.csdn.net/weixin\\_45902201/article/details/128821714>

```bash
# 默认注释了源码仓库，如有需要可自行取消注释
deb https://mirrors.ustc.edu.cn/ubuntu-ports/ focal main restricted universe multiverse
# deb-src https://mirrors.ustc.edu.cn/ubuntu-ports/ focal main main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu-ports/ focal-updates main restricted universe multiverse
# deb-src https://mirrors.ustc.edu.cn/ubuntu-ports/ focal-updates main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu-ports/ focal-backports main restricted universe multiverse
# deb-src https://mirrors.ustc.edu.cn/ubuntu-ports/ focal-backports main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu-ports/ focal-security main restricted universe multiverse
# deb-src https://mirrors.ustc.edu.cn/ubuntu-ports/ focal-security main restricted universe multiverse

# 预发布软件源，不建议启用
# deb https://mirrors.ustc.edu.cn/ubuntu-ports/ focal-proposed main restricted universe multiverse
# deb-src https://mirrors.ustc.edu.cn/ubuntu-ports/ focal-proposed main restricted universe multiverse

```


# homebrew的使用

## 使用

1. 先安装HomeBrew(参考官网) 搜索：`brew search` MySQL 查询：`brew info mysql` 主要看具体的信息，比如目前的版本，依赖，安装后注意事项等 更新：`brew update` 这会更新 Homebrew 自己，并且使得接下来的两个操作有意义—— 检查过时（是否有新版本）：`brew outdated` 这回列出所有安装的软件里可以升级的那些 升级：`brew upgrade` 升级所有可以升级的软件们 清理：`brew cleanup` 清理不需要的版本极其安装包缓存
2. `brew update` 更新brew
3. `brew install MongoDB` 安装mongodb数据库
4. `brew services start mongodb` 启动mongodb数据库
5. `brew services stop mongodb` 停止mongodb数据库

## 技巧

homebrew有个`services`命令可以管理自启动服务

```
brew services [-v|--verbose] [list | run | start | stop | restart | cleanup] [...]
    Easily start and stop formulae via launchctl.
```


# 汇总

[在线工具 汇总 - Cacher Snippet](https://snippets.cacher.io/snippet/b24e669bd80d9e31542b)

## 生产力工具

* 接口管理平台
  * [小幺鸡，简单好用的接口文档管理工具](http://www.xiaoyaoji.cn/)
  * [The Best APIs are Built with Swagger Tools | Swagger](https://swagger.io/)
  * [APIJSON.org](http://apijson.org/) 后端接口和文档自动化，前端(客户端) 定制返回JSON的数据和结构！
  * [NEI - 接口管理平台](https://nei.netease.com/)
  * [RAP2 Delores](http://rap2.taobao.org/)
  * [eoLinker接口管理平台 | 全球领先API接口管理平台，Google谷歌开发者联盟合作项目企业](https://www.eolinker.com/)

### 前端

* [xiaopiu-产品原型设计工具与团队实时协作平台](https://www.xiaopiu.com/)

  免费的，在线原型设计，很强大
* [墨刀-原型设计](https://modao.cc/) &#x20;

  收费的强大易用的原型设计与协同工具
* [sketch-UI设计](http://www.sketchcn.com/) &#x20;

  轻量易用的矢量设计工具
* [蓝湖](https://lanhuapp.com/) &#x20;

  前端设计工具，可以生成css、ios、android、微信小程序代码

### 后端

* [electron](https://electronjs.org/) &#x20;

  Electron是由Github开发，用HTML，CSS和JavaScript来构建跨平台桌面应用程序的一个开源库。&#x20;

### 其他

* [focusky-可以制作‘一张图’演示动画](http://www.focusky.com.cn/) 国产的类似prezi的
* [prezi](https://prezi.com/) 可以制作一张大图的演示动画
* [draw.io](https://www.draw.io/) 绘制流程图 国外
* [processon](https://www.processon.com/) 绘制流程图 国产

## 在线工具

* 在线环境模拟学习平台
  * [开发者实验室 - 腾讯云](https://cloud.tencent.com/developer/labs)
  * [Katacoda - Interactive Learning Platform for Software Engineers](https://www.katacoda.com/)
  * [Learn to Code - for Free | Codecademy](https://www.codecademy.com)
  * [在线做实验，高效学编程 - 实验楼](https://www.shiyanlou.com/)
* [softto-各种线上工具](https://softo.co/) &#x20;

  拥有大量的各种线上工具，We build world class online software
* 在线记事本
  * [notelive-在线记事本](https://note.rizon.top) &#x20;

    自己做的咯，因为是静态服务，功能很单一，特色是html代码可以直接执行
  * [pastebin](<https://pastebin.com >)

    丰富的语法支持和功能支持，提供API
  * [notes.cat](https://notes.cat/) [upstairs.cc](https://upstairs.cc)

    简易的在线记事本
  * 在线markdown记事本
    * [mak.ink](https://mak.ink/) &#x20;

      <https://inns.studio/mak>  <https://github.com/inns/mak> 可以同步到gist的风格很简单舒服的在线markdown
    * [StackEdit](https://stackedit.io/)  较重的在线markdown，支持同步到github、google drive 等。功能较丰富
* [myjson](http://myjson.com/) A simple JSON store for your web or mobile app
* 在线文件共享与传输
  * [sharedrop](https://www.sharedrop.io/) &#x20;

    在线通过浏览器实现，局域网跨设备的p2p文件共享,点对点传输不会保存在服务器上
  * [send-anywhere](https://send-anywhere.com/) &#x20;

    任意设备之间文件传输，有客户端也可以在线共享，支持p2p传输和上传服务器后分享出去
  * [fileroom](https://fileroom.io/) &#x20;

    在线通过浏览器实现文件共享，文件会上传到服务器临时保存
* [石墨文档](https://shimo.im/) &#x20;

  可多人实时协作的云端文档与表格
* 图床
  * [sm.ms图床](https://sm.ms/) &#x20;

    国外的,很多图床工具使用的默认配置
  * [simimg.com简单图床](https://simimg.com/) &#x20;

    单张最大10mb，可匿名可注册
  * [极简图床](https://jiantuku.com/) &#x20;

    只是一个图床工具,weibo,七牛,阿里云三种方式,有chrome浏览器插件
* [图片压缩tinypng](https://tinypng.com/) &#x20;

  支持png和jpg的图片压缩,国外的
* [picdietjs前端图片压缩](https://www.picdiet.com/zh-cn) &#x20;

  借助浏览器来压缩图片，在线批量压缩图片神器，并且提供了js图片上传组件
* [花密web](https://flowerpassword.com/app/web) &#x20;

  用一个简短明文和你的密码合成新的密码，使密码更安全又不会难以记忆,除了web版还有客户端和chrome插件
* [10分钟邮箱bccto](http://mail.bccto.me/) &#x20;

  生成一个10分钟的临时邮箱,支持域名绑定
* [格式转换cloud convert](https://cloudconvert.com/) &#x20;

  convert anything to anything。国外很专业的格式转换工具,很牛,还提供了API接口
* [文件转换器](https://convertio.co/zh/) &#x20;

  在线且免费,转换文件到任何格式的先进工具。需要FQ。Facebook Video Downloader,Cut Song,压缩GIF,Merge Video
* [临时网盘bitsend](https://bitsend.jp/?setLang=zh-tw) &#x20;

  匿名上传，不限大小，可保存14天，支持密码等各种下载配置
* [17ce域名监测速度测试](https://www.17ce.com/) &#x20;

  在线测速,免费网站性能监测平台，ping、dns、dig、trace
* [asciinema终端录屏](https://asciinema.org/) &#x20;

  可以录制终端操作后上传到服务器共享
* [showterm终端录屏](http://showterm.io/) &#x20;

  终端录屏，支持慢放，有进度条
* [在线快速抠图](https://www.gaoding.com/koutu)
* [神奇又好玩的网站（不定期更新中）-知乎](https://zhuanlan.zhihu.com/p/21745861) &#x20;
* 网页归档
  * [Archive.today](https://archive.today/) 永久保存网页快照
  * [Perma](https://perma.cc/) 为网页建立永久档案，不怕网页被删除、被修改、被下线[介绍](http://www.appinn.com/perma-cc/)
  * [web.archive.org](https://web.archive.org/) 保存网页快照的互联网档案馆
* [来自v2ex🔗](https://www.v2ex.com/t/443398) &#x20;

  &#x20;v2ex的用户alwayshere开帖集思广益，本人会在去了解了这些工具之后逐步整理过来，目前先放个链接

## code工具

* github
  * [rawgit](https://rawgit.com/) &#x20;

    RawGit serves raw files directly from GitHub with proper Content-Type headers
  * [roughdraft](http://roughdraft.io) &#x20;

    可以把Gist的代码片段作为一个独立的网页发布
* 前端开发
  * CDN 加速服务
    * [bootcdn](http://www.bootcdn.cn/) &#x20;

      国内,稳定、快速、免费的前端开源项目 CDN 加速服务
    * [jsdelivr](https://www.jsdelivr.com/) &#x20;

      支持npm、github仓库的js加速
    * [baomitu](https://cdn.baomitu.com/) &#x20;

      国内，cdn服务，google字体
    * [loli-css](https://css.loli.net/) &#x20;

      国内,常用前端公共库 CDN 服务，google字体
    * [cdnjs](https://cdnjs.com/)
    * [unpkg](https://unpkg.com/) &#x20;

      unpkg is a fast, global content delivery network for everything on npm
  * [favicon生成](https://realfavicongenerator.net/) &#x20;

    一键生成各个浏览器各个平台的favicon
* [freecodecamp](https://www.freecodecamp.cn/) &#x20;

  很不错的帮助学习编程的开源社区
* 代码辅助
  * [coder](https://coder.com/) web版vscode，可内网部署，可使用官方在线服务。
* 在线运行js
  * [codepen](https://codepen.io/)
  * [codesandbox](https://codesandbox.io/) &#x20;

    国外的在线代码沙盒，速度很慢，支持vue、angular、react等模版开发
  * [RunJS](http://runjs.cn/) &#x20;

    国内，用户量很大的广场，在线编辑、展示、分享、交流你的 JavaScript 代码
  * [jsfiddle](https://jsfiddle.net/) &#x20;

    国外，需要fq
* [enhancer一站式信息系统开发云平台](https://enhancer.io/) &#x20;

  绝大多数情况下，您只需编写 SQL，无需编写代码，即可快速完成各类系统的开发
* [Compiler Explorer](https://godbolt.org) 可以把c++等语言转换为汇编语言


# tomcat


# tomcat目录规范

## tomcat目录规范

规则如下：tomcat-\[业务名字]-\[端口]-\[节点号]

> 单机单实例时，节点号为空。多实例，需要节点号。

如：

```
tomcat-jenkins-9400
tomcat-demo-9530-1
tomcat-demo-9540-2
tomcat-demo-9550-3
```

## tomcat端口规范

要使用较为连续的端口号，符合一定的规律。如下：

```
正例：
tomcat-demo1-9530 所需要的端口 9530、9531、9532
tomcat-demo2-9540 所需要的端口 9540、9541、9542
tomcat-demo3-9550 所需要的端口 9550、9551、9552
反例：
tomcat-demo1-9530 所需要的端口 9530、9500、9510【避免不规律命名】
tomcat-demo2-9540 所需要的端口 8989、8006、8010【避免不规律命名】
tomcat-demo3-9550 所需要的端口 9550、9750、9850【避免不规律命名】
```

## tomcat端口修改

在自定义端口或单机多实例部署时，需要修改server.xml文件中的Connector Port、shutdown，AJP端口。具体如下：

大约69行处修改 **start端口** 如下所示的 **9520端口**：

![](https://3585898866-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LcehtLiMKFYRXXJ4Dzh%2F-Lcei1h6xvrd1a6Mrkgg%2F-Lcei3re3aycSFpT5yDj%2F14994183093091.jpg?generation=1555494889932614\&alt=media)

大约22行处修改 **shutdown端口** 如下所示的 **9521端口**：

![](https://3585898866-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LcehtLiMKFYRXXJ4Dzh%2F-Lcei1h6xvrd1a6Mrkgg%2F-Lcei3rgx-BbhfZqDerX%2F14994183260421.jpg?generation=1555494888845724\&alt=media)

大约91行处修改 **AJP端口** 如下所示的 **9522端口**：

![](https://3585898866-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LcehtLiMKFYRXXJ4Dzh%2F-Lcei1h6xvrd1a6Mrkgg%2F-Lcei3riuGPuA_9HdVIT%2F14994183334234.jpg?generation=1555494893400537\&alt=media)


# code\_snippets


# 专题


# RESTful API

## 参考

[服务端指南 | 良好的 API 设计指南 - 掘金](https://juejin.im/post/59826a4e518825359c5e72d1) [RESTful 架构详解 | 菜鸟教程](https://www.runoob.com/w3cnote/restful-architecture.html) [RESTful API 最佳实践 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2018/10/restful-api-best-practices.html) [RESTful API浅谈 - 老\_张 - 博客园](https://www.cnblogs.com/imyalost/p/7923230.html)

## 概述

### 资源与URI

动词 http method+ 名词uri

```
【GET】          /users                # 查询用户信息列表
【GET】          /users/1001           # 查看某个用户信息
【POST】         /users                # 新建用户信息
【PUT】          /users/1001           # 更新用户信息(全部字段)
【PATCH】        /users/1001           # 更新用户信息(部分字段)
【DELETE】       /users/1001           # 删除用户信息
```

避免多级url，而是采用过滤条件

```
GET /authors/12/categories/2
应该换成
GET /authors/12?categories=2
```

> 使用\_或-来让URI可读性更好 使用/来表示资源的层级关系 使用?用来过滤资源 ,或;可以用来表示同级资源的关系

**动词的覆盖** 有些客户端无法支持PUT、DELETE等方法，这种情况必须使用POT来模拟， 客户端可以在发出的HTTP请求中，加上`X-HTTP-Method-Override`属性，告诉服务器使用哪一个动词

```http
POST /api/Person/4 HTTP/1.1  
X-HTTP-Method-Override: PUT
```

#### 版本

api接口升级时，为了去兼容一些暂时无法升级的客户端（比如移动端的app是运行在客户设备上，不像服务器上的客户端可以直接升级），需要保留旧版本接口。 为了解决版本兼容问题，在RESTful API中使用了版本号，常见的情况下在url使用版本号，也可以在header中使用版本号，不过一般都在url中，这样更直观。

```http
【GET】  /v1/users/{user_id}  // 版本 v1 的查询用户列表的 API 接口
【GET】  /v2/users/{user_id}  // 版本 v2 的查询用户列表的 API 接口
```

#### 提供链接

为了方便api使用者，让资源之间具有连通性（超媒体），在响应体中，返回其他资源的url

例如，GitHub 的 API 都在 <https://api.github.com/> 这个域名。访问它，就可以得到其他 URL。

```javascript
{
  ...
  "feeds_url": "https://api.github.com/feeds",
  "followers_url": "https://api.github.com/user/followers",
  "following_url": "https://api.github.com/user/following{/target}",
  "gists_url": "https://api.github.com/gists{/gist_id}",
  "hub_url": "https://api.github.com/hub",
  ...
}
```

在返回头中返回link

```bash
url -I https://api.github.com/orgs/github/repos
HTTP/1.1 200 OK

ETag: "c4f394acb6f2603b159ee8d694d308cb"
X-GitHub-Media-Type: github.v3; format=json
Link: <https://api.github.com/organizations/9919/repos?page=2>; rel="next", <https://api.github.com/organizations/9919/repos?page=10>; rel="last"
```

### 状态码

应该尽量在http状态码中表述错误状态，而不是统一使用200，统一200的方式实际上几近取消了状态码

```http
HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": "Invalid payoad.",
  "detail": {
     "surname": "This field is required."
  }
}
```

## 附加

### 常用状态码

**GET** 安全且幂等 获取表示 变更时获取表示（缓存） 200（OK） - 表示已在响应中发出 204（无内容） - 资源有空表示 301（Moved Permanently） - 资源的URI已被更新 303（See Other） - 其他（如，负载均衡） 304（not modified）- 资源未更改（缓存） 400 （bad request）- 指代坏请求（如，参数错误） 404 （not found）- 资源不存在 406 （not acceptable）- 服务端不支持所需表示 500 （internal server error）- 通用错误响应 503 （Service Unavailable）- 服务端当前无法处理请求

**POST** 不安全且不幂等 使用服务端管理的（自动产生）的实例号创建资源 创建子资源 部分更新资源 如果没有被修改，则不过更新资源（乐观锁） 200（OK）- 如果现有资源已被更改 201（created）- 如果新资源被创建 202（accepted）- 已接受处理请求但尚未完成（异步处理） 301（Moved Permanently）- 资源的URI被更新 303（See Other）- 其他（如，负载均衡） 400（bad request）- 指代坏请求 404 （not found）- 资源不存在 406 （not acceptable）- 服务端不支持所需表示 409 （conflict）- 通用冲突 412 （Precondition Failed）- 前置条件失败（如执行条件更新时的冲突） 415 （unsupported media type）- 接受到的表示不受支持 500 （internal server error）- 通用错误响应 503 （Service Unavailable）- 服务当前无法处理请求

**PUT** 不安全但幂等 用客户端管理的实例号创建一个资源 通过替换的方式更新资源 如果未被修改，则更新资源（乐观锁） 200 （OK）- 如果已存在资源被更改 201 （created）- 如果新资源被创建 301（Moved Permanently）- 资源的URI已更改 303 （See Other）- 其他（如，负载均衡） 400 （bad request）- 指代坏请求 404 （not found）- 资源不存在 406 （not acceptable）- 服务端不支持所需表示 409 （conflict）- 通用冲突 412 （Precondition Failed）- 前置条件失败（如执行条件更新时的冲突） 415 （unsupported media type）- 接受到的表示不受支持 500 （internal server error）- 通用错误响应 503 （Service Unavailable）- 服务当前无法处理请求

**DELETE** 不安全但幂等 删除资源 200 （OK）- 资源已被删除 301 （Moved Permanently）- 资源的URI已更改 303 （See Other）- 其他，如负载均衡 400 （bad request）- 指代坏请求 404 （not found）- 资源不存在 409 （conflict）- 通用冲突 500 （internal server error）- 通用错误响应 503 （Service Unavailable）- 服务端当前无法处理请求


# serveless服务

## 参考

先看这篇了解概念：[聊一聊Serverless\_冷月心的技术博客\_51CTO博客](https://blog.51cto.com/lengyuexin/3008718) 腾讯的文档里写的也挺好的：[Serverless 应用中心-腾讯云](https://cloud.tencent.com/product/sls) 如果想自己搭建fass服务看这个：[动手搭建ServerLess服务](https://zhuanlan.zhihu.com/p/413246974)

## 开源faas

OpenFaaS（faas 仓库 20.3k）、Kubeless（6.7k）、Fission（6.4k）、OpenWhisk（5.4k）、Knative（serving 仓库 3.9k）、fnproject、OpenFunction 等

## 腾讯云函数使用指南

腾讯云函数使用了腾讯云的几个相关产品，这个费用方面的事情在云函数文档中没有额外做说明，如果不注意会产生不必要的费用。

**云函数** 在页面控制台创建和管理函数

**serverless应用** 这个容易混淆，serverless是腾讯的另一款cli工具，使用该工具可以在本地快速的创建部署和调试云函数，使用该工具创建云函数时会先创建一个应用，然后创建云函数， serverless创建的云函数和在网页控制台直接创建的云函数，本质没有区别，但是两者目前(2022-04-21)在文件结构上有一些出入，猜测两个团队的东西没有完全融合。

**COS对象存储** 这是一个坑点，很容易疏忽，使用serverless的cli工具进行函数部署时，会自动使用对象存储，部署的文件会存储到bucket桶中，而且不会自动删除，所以如果不主动删除就会有存储费用产生。。 这点官方文档一笔带过，产生了费用后，查来查去文档才发现是serverless工具的锅。

**日志服务（Cloud Log Service，CLS）** CFS函数产生的日志默认会投递到日志服务中，在日志主题中会有专门的一个CFS日志，如果日志量达到一定程度会产生费用，所以需要注意这一点，在 函数管理-函数配置 页面可以关闭日志投递，但是这样就会导致无法看函数日志了，因此应当控制日志量，以及在日志主题中调整日志的保留时间

## 总结

总结几个关键点：

serverless服务是无状态的，函数调用结束后一般情况下实例就会销毁了，因此不不能使用全局变量、内存缓存这类东西，在开发中应该时刻确保每个http请求是完全独立的。 这也正是无状态服务的优势，方便的进行伸缩扩展、负载均衡，一个http请求可以转发到任意一个server上。


# 搭建私有云主机 折腾记

记录搭建主机的过程，包括其中的探索和踩坑。

## 选购迷你服务器

## 内网穿透

### 花生壳 内网穿透 简单

### frp内网穿透

#### 将frp配置为系统服务 systemctl

## 提高稳定性

## 效率工具

* mosh
* screen
* htop

### mosh

### screen

[效率工具/screen.md](/xiaolgong-ju/screen)

不登陆会话的情况下退出会话 `screen -X -S screenName quit` 通过-X 参数来在screenName会话中执行quit命令

### htop

[htop使用备忘 - aurain - C++博客](http://www.cppblog.com/aurain/archive/2014/11/20/208928.html) [linux htop：比top更好用的top](https://www.jianshu.com/p/6e9b0cc7f348)

## FAQ

### NetworkManager 和 network

> 网络管理器(NetworManager)是检测网络、自动连接网络的程序。无论是无线还是有线连接，它都可以令您轻松管理。对于无线网络,网络管理器优先连接已知的网络并可以自动切换到最可靠的无线网络。利用网络管理器的程序可以自由切换在线和离线模式。网络管理器会相对无线网络优先选择有线网络，支持 VPN。网络管理器最初由 Redhat 公司开发，现在由 GNOME 管理。 <https://wiki.archlinux.org/index.php/NetworkManager_(简体中文>)
>
> NetworkManager 是一个自动切换网络连接的后台进程。很多笔记本用户都需要启用该功能，它让你能够在无线网络和有线网络之间切换。大多数台式机用户应该关闭该服务。一些 DHCP 用户可能需要开启它。
>
> 1、/etc/network/interfaces（/etc/init.d/networking） 2、Network-Manager 两套方案是冲突的，不能同时共存。 第一个方案适用于没有X的环境，如：服务器；或者那些完全不需要改动连接的场合。 第二套方案使用于有桌面的环境，特别是笔记本，搬来搬去，网络连接情况随时会变的。 <https://my.oschina.net/qihh/blog/60581>


# 开发中的各种疑难杂症问题

## 跨系统的事物一致性问题

## Spring的AOP代理

[Spring AOP——Spring 中面向切面编程 - SharpCJ - 博客园](https://www.cnblogs.com/joy99/p/10941543.html)\
[Spring AOP：内部调用陷阱](https://zhewuzhou.github.io/2018/09/01/Spring_AOP_Trap/)\
[透过现象看原理：详解Spring中Bean的this调用导致AOP失效的原因 - 光闪的个人空间 - OSCHINA](https://my.oschina.net/guangshan/blog/1807721)

## 业务层数据过滤带来的分页问题

[分页场景下、查询数据后排除部分内容导致不足一页的 问题怎么解决？](https://www.v2ex.com/t/603295)

核心：

1. 取出一部分数据过滤后，不足一页再次取数据
2. 记录上一次查询的cursor位置，下一次查询时客户端范围该位置从该位置查询下一页，也就是每次只能从第一页开始查询，且不可以跳页


# spring 最佳实践

[othorizon/spring-best-practices](https://github.com/othorizon/spring-best-practices)


# LLM 大语言模型

## 对比

langchain：基于python，可以进行复杂的开发，并且提供了Prompt、Loader、Chain、Agent、Memory一些框架工具 dust：构建了低代码的开发平台 LlamaIndex (GPT Index)：基于langchian构建

## langchain

<https://langchain.com/>

参考：[大语言模型集成工具 LangChain](https://zhuanlan.zhihu.com/p/599688026)

## dust

> Dust是一个大语言模型应用程序的设计和部署工具，用户可以通过Dust来快速建立并部署自然语言理解的应用程序，例如智能客服、智能问答系统或智能聊天机器人。Dust可以帮助用户快速搭建和部署应用程序，从而节省时间和精力。 by chatgpt

[Dust - Design and Deploy Large Language Models Apps](https://dust.tt/)

## LlamaIndex (GPT Index)

[GitHub - jerryjliu/llama\_index: LlamaIndex (GPT Index) is a project that provides a central interface to connect your LLM's with external data.](https://github.com/jerryjliu/llama_index) [Welcome to LlamaIndex 🦙 (GPT Index)! — LlamaIndex documentation](https://gpt-index.readthedocs.io/en/latest/index.html)

## llamahub.ai

旨在降低对接LlamaIndex和LangChain的成本。

> 这是一个由社区创建的简单数据加载器/读取器库。它的目标是让连接大型语言模型和各种知识来源变得非常简单。这些工具是通用的，可以用于LlamaIndex（例如构建索引时）和LangChain（例如构建代理使用的不同工具时）。例如，这个库包含了解析Google Docs、SQL数据库、PDF文件、PowerPoints、Notion、Slack、Obsidian等多种文件格式的加载器。请注意，不同类型的加载器产生相同类型的文档，因此您可以在同一个索引中轻松使用它们。

<http://llamahub.ai> [GitHub - emptycrown/llama-hub: A library of data loaders for LLMs made by the community -- to be used with GPT Index and/or LangChain](https://github.com/emptycrown/llama-hub)

## qdrant

> Qdrant是一个矢量搜索引擎，能够快速检索大规模矢量数据。它的使用场景非常广泛，例如信息检索、推荐系统、生物信息学、视频分析等领域。对于那些需要频繁搜索大量矢量数据的应用程序，Qdrant可以提供有效的解决方案。 by chatgpt

矢量搜索引擎 [qdrant](https://qdrant.tech/)

## Labelbox: 开发和管理文本和图像注释项目的平台，可用于从大规模数据集中进行标注

[Labelbox](https://labelbox.com/)

## 参考

[使用 LangChain + OpenAI GPT 来构建问答知识库的聊天机器人](https://blog.chaos.run/dreams/build-question-answering-chatbot-using-langchain-openai-gpt/)


# notelive


# 文章框架

* [文章框架](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xJpZlr2aCG-umI#文章框架)
  * [代码](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xJpZlr2aCG-umI#代码)
    * [前端](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xJpZlr2aCG-umI#前端)
      * [html](/zhuan-ti/notelive/notelive-kuang-jia#html)
      * [webpack](/zhuan-ti/notelive/notelive-kuang-jia#webpack)
    * [后端](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xJpZlr2aCG-umI#后端)
      * [nodejs+express](/zhuan-ti/notelive/notelive-kuang-jia#nodejsexpress)
      * [mongodb](/zhuan-ti/notelive/notelive-kuang-jia#mongodb)
  * [部署](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xJpZlr2aCG-umI#部署)
    * [后台服务](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xJpZlr2aCG-umI#后台服务)
      * [docker部署mongodb](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xJpZlr2aCG-umI#docker部署mongodb)
      * [pm2管理nodejs项目](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xJpZlr2aCG-umI#pm2管理nodejs项目)
    * [静态网站](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xJpZlr2aCG-umI#静态网站)
      * [云虚拟主机](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xJpZlr2aCG-umI#云虚拟主机)
      * [代码托管平台](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xJpZlr2aCG-umI#代码托管平台)
    * [域名解析](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xJpZlr2aCG-umI#域名解析)
      * [nginx的配置](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xJpZlr2aCG-umI#nginx的配置)
  * [辅助](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xJpZlr2aCG-umI#辅助)
    * [webstorm](/zhuan-ti/notelive/notelive-kuang-jia#webstorm)
    * [cloud studio](/zhuan-ti/notelive/notelive-kuang-jia#cloud-studio)

## 代码

### 前端

#### html

* 样式

#### webpack

* 配置
* 代码调试

### 后端

#### nodejs+express

* url匹配（eg.'/render/:title/:queryPassword?'）
* 路由
* 跨域请求

#### mongodb

创建索引： `db.note.createIndex({title:1},{unique:true})` [MongoDB 索引 | 菜鸟教程](http://www.runoob.com/mongodb/mongodb-indexing.html)

## 部署

### 后台服务

#### docker部署mongodb

* docker安装
* docker的简单使用

#### pm2管理nodejs项目

* pm2的常用命令
* pm2配置文件的配置

### 静态网站

#### 云虚拟主机

* 虚拟主机的开通与使用
* 虚拟主机绑定域名
* ftp上传的shell脚本

#### 代码托管平台

* coding.net 的cloud studio
* github pages服务
* coding.net的静态网页服务

### 域名解析

* 域名解析的常用配置
* ssl证书的申请
* nginx的配置

#### nginx的配置

## 辅助

### webstorm

### cloud studio

* coding.net的cloud studio工具的使用


# notelive vue版本开发

## 技术点

实时写入到本地存储，然后在阶段性的同步到服务器 [vue中的watch方法 实时同步存储数据 - front-gl - 博客园](https://www.cnblogs.com/mmzuo-798/p/10259774.html)\
[客户端存储 — Vue.js](https://cn.vuejs.org/v2/cookbook/client-side-storage.html)

localstorage的更加特性化功能实现，增加了keys等操作； [yarkovaleksei/vue2-storage](https://github.com/yarkovaleksei/vue2-storage)

[vuejs-templates](https://github.com/vuejs-templates) [Simple Todo App with Vue - CodeSandbox](https://codesandbox.io/s/o29j95wx9)\
[vue-axios](https://www.npmjs.com/package/vue-axios)

## 开发中的技巧

[让webstorm 识别vue cli3项目中的@路径别名正确解析的配置方法](https://blog.csdn.net/weixin_43343144/article/details/88668787)

获取localStorage中的key：`Object.keys(localStorage)`version

## vue知识沉淀

“:” 是指令 “v-bind”的缩写，“@”是指令“v-on”的缩写；“.”是修饰符。详细如下 <https://blog.csdn.net/yangfengjueqi/article/details/86536347>

emit 触发一个事件，可以通过v-on监听 <https://www.cnblogs.com/sweeneys/p/10201458.html>

## 高亮编辑器

选择使用 vs的Monaco Editor

[vue 代码编辑器monaco-editor使用](http://luchenqun.com/vue-monaco-editor/)\
[Package - monaco-editor-vue](http://npm.taobao.org/package/monaco-editor-vue)

[Vue cli2.0 项目中使用Monaco Editor编辑器 - 时间脱臼 - 博客园](https://www.cnblogs.com/helloluckworld/p/9663308.html)

```javascript
ed=monaco.editor.create(document.getElementById("container"), {
    value: "function hello() {\n\talert('Hello world!');\n}",
    language: "javascript"
});

ed.getModel().onDidChangeContent((e)=>{
    console.log(ed.getValue())
});
```

```javascript
// Register a new language
monaco.languages.register({ id: 'mySpecialLanguage' });

// Register a tokens provider for the language
monaco.languages.setMonarchTokensProvider('mySpecialLanguage',{
    //自定义配置
});


monaco.editor.create(document.getElementById("container"), {
    value: '## 123123qwq',
    language: 'mySpecialLanguage'
});
```


# notelive 开发 札记

* [notelive 开发 札记](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xKUHiKrO8M7i6A#notelive-开发-札记)
  * [背景](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xKUHiKrO8M7i6A#背景)
  * [技术栈](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xKUHiKrO8M7i6A#技术栈)
  * [部署方案](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xKUHiKrO8M7i6A#部署方案)
    * [前端部署](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xKUHiKrO8M7i6A#前端部署)
    * [后端部署](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xKUHiKrO8M7i6A#后端部署)
      * [pm2管理项目](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xKUHiKrO8M7i6A#pm2管理项目)
  * [开发PWA应用](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xKUHiKrO8M7i6A#开发pwa应用)
  * [参考资料](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xKUHiKrO8M7i6A#参考资料)
  * [一些零碎的知识点](https://book.rizon.top/zhuan-ti/notelive/pages/-Lcei2xKUHiKrO8M7i6A#一些零碎的知识点)

## 背景

之前采用leancloud+html页面写了一个简单的在线记事本工具，[note.rizon.top](https://github.com/othorizon/DarkTime/tree/c8ec15f468bb4129348d85bcc8e364e73940d6b1/专题/notelive/note.rizon.top)。现在得空准备重写该项目。一方面，之前因为受限于没有后台服务的纯前端页面因此无法实现更复杂的功能，所以这次准备在功能上进行一次迭代升级。另一方面，也是为了学习新的框架。这次采用的框架是 mongodb+nodejs+express+html，之所以这么选一方面这是市面上常见的方案，另一方面这些都是我没接触过的东西，后者原因最重要咯。

## 技术栈

docker：mongodb采用docker部署

数据库：mongodb 原因是存储json对象方便，毕竟只是个轻量级的小项目

前端：html，会使用lodash的throttle函数来进行函数节流，参考：[debounce与throttle区别](https://blog.csdn.net/ligang2585116/article/details/75003436)

后端：nodejs 使用express框架。express框架封装了http模块，使得开发后台服务变得更简单。

## 部署方案

前端部署到到支持静态网站的代码托管平台，比如国内的coding，国外的github page。\
后台代码部署到自己的云服务器上，mongodb数据库使用docker部署。后端与mongodb为内网连接。

### 前端部署

1.配置托管 本次前端代码部署到github pages。 push前端代码到github之后，打开repository->settings，往下翻找到GitHub Pages，开启服务，默认给你提供了一个username.github.io的三级域名，这里我们绑定自定义域名（Custom domain）。 ![](https://3585898866-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LcehtLiMKFYRXXJ4Dzh%2F-Lcei1h6xvrd1a6Mrkgg%2F-Lcei5Uz5ea7hnp2y5f-%2Fgithubpages.png?generation=1555494890571104\&alt=media) 2.配置域名解析 保存后，打开你购买的域名的管理后台，配置域名解析，有两种配置方案一个是CNAME一个是A记录，A记录的对应IP可以查看githubpages的帮助文档，这里采用CNAME配置，比较简单。 ![](https://3585898866-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LcehtLiMKFYRXXJ4Dzh%2F-Lcei1h6xvrd1a6Mrkgg%2F-Lcei5V0nI_hH2iIBRrp%2Fdns.png?generation=1555494890453981\&alt=media)

### 后端部署

1.docker部署mongodb数据库 安装好docker环境，安装帮助可以参考：[centos常用环境安装#安装docker](https://rizon.top/tech/centos%E7%8E%AF%E5%A2%83%E5%AE%89%E8%A3%85/#%E5%AE%89%E8%A3%85docker-docker-ce) 1）拉取mongo镜像`docker pull mongo` 2）启动镜像`docker run -d --name notelive-mongo --hostname notelive-mongo -p 127.0.0.1:8807:27017 mongo` `-d`参数后台运行,`-p 127.0.0.1:8807:27017`将容器内的27017端口绑定到宿主机的127内网ip的8807端口上，这样在机器上就可以用8807端口访问服务，而且即使当你的8807端口可以外网访问，也无法在外网请求到该服务。 3）部署后台代码，npm安装依赖 简单的启动服务方案是直接node命令启动:`PORT=8805 AllowOrigin=http://live.rizon.top nohup node ./bin/www >>log.log 2>&1 &`,写入环境变量并将日志重定向到文件后在后台运行服务。

好一些的方案是借助pm2工具管理服务,

#### pm2管理项目

参考： [pm2使用心得](https://blog.csdn.net/frankenjoy123/article/details/73460055) [使用PM2来部署nodejs项目](https://www.jianshu.com/p/d2a640b8661c) [pm2配置文件介绍 - CSDN博客](https://blog.csdn.net/ningzheyuan/article/details/80590773) [PM2实用入门指南 - 程序猿小卡 - 博客园](https://www.cnblogs.com/chyingp/p/pm2-documentation.html)

> 全局安装pm2`npm install -g pm2` 启动服务`pm2 start ./bin/server.js` 查看所有服务`pm2 list` 查看服务详情`pm2 show <id|name>` 查看日志 `pm2 logs <id|name> [--lines 1000]`

`pm2 startOrRestart ./app.json` 通过配置文件启动

```javascript
{
  "apps": [
    {
      "name": "notelive",
      "max_memory_restart": "100M",
      "script": "./bin/server",
      "log_date_format": "YYYY-MM-DD HH:mm:ss",
      "out_file": "./logs/out.log",
      "error_file": "./logs/error.log",
      "instances": 2,
      "exec_mode": "cluster",
      "env": {
        "NODE_ENV": "production",
        "PORT": 8805,
        "AllowOrigin": "http://live.rizon.top"
      },
      "env_dev": {
        "NODE_ENV": "develop",
        "PORT": 8808,
        "AllowOrigin": "*",
        "mongo":{
          "url": "mongodb://localhost:8907/",
          "db": "demo",
          "col": "note"
        }
      }
    }
  ]
}
```

`env`为默认的环境配置（生产环境），`env_dev`、`env_test`来指定不同环境的环境变量配置，通过参数`--env`指定环境：`pm2 start app.js --env dev`

## 开发PWA应用

参考：

[PWA介绍及快速上手搭建一个PWA应用 - 游魂的技术日志 - SegmentFault 思否](https://segmentfault.com/a/1190000014639473)\
[使用offline-plugin搭配webpack轻松实现PWA - Jrain-前端玩具盆 - SegmentFault 思否](https://segmentfault.com/a/1190000010669126)

使用localStorage存储当前工作内容，localStorage比cookie更适合存储当前会话的数据。

## 参考资料

mongodb： [Node.js 连接 MongoDB | 菜鸟教程](http://www.runoob.com/nodejs/nodejs-mongodb.html) [Node.js MongoDB](https://www.w3schools.com/nodejs/nodejs_mongodb.asp)

nodejs： [Nodejs进阶：用debug模块打印调试日志 - 程序猿小卡的前端专栏 - SegmentFault 思否](https://segmentfault.com/a/1190000009183793)

## 一些零碎的知识点

1. JavaScript的函数声明与函数表达式的区别

```javascript
    // 1,函数声明(Function Declaration)
    function funDeclaration(type){
        return type==="Declaration";
    }
    // 2,函数表达式(Function Expression)。
    var funExpression = function(type){
        return type==="Expression";
    }
```

Javascript 中函数声明和函数表达式是存在区别的，函数声明在JS解析时进行函数提升，因此在同一个作用域内，不管函数声明在哪里定义，该函数都可以进行调用。而函数表达式的值是在JS运行时确定，并且在表达式赋值完成后，该函数才能调用。来源：[JavaScript的函数声明与函数表达式的区别](https://www.cnblogs.com/xbj-2016/p/5903611.html) 2. js异步函数同步调用 Promise

[Node.js的那些坑（三）——如何在异步方法都执行后再执行 - CSDN博客](https://blog.csdn.net/duzixi/article/details/53842182) [JS - Promise使用详解--摘抄笔记 - sweeeper - 博客园](https://www.cnblogs.com/sweeeper/p/8442613.html)


# webrtc技术分析

## 参考

[WebRTC：连接建立过程的网络穿透](https://zhuanlan.zhihu.com/p/75387873) [WebRTC 初体验](https://blog.whezh.com/webrtc-first-experience/) [WebRTC 是如何进行通信的，WebRCT 的三种网络结构 | 野狗 WebRTC 专栏 | Wilddog Blog](https://blog.wilddog.com/?p=2196)

相关链接： [群晖 nas 的外网访问是直连 nas 服务器的吗；以及 NAT 技术的一些问题请教，谢谢 - V2EX](https://www.v2ex.com/t/625035)

## 知识

> 因为NAT带来的网络穿透问题，不管是直接通信，还是间接通信，都不是简单的事情。WebRTC通过ICE框架来解决网络穿透的问题，并对应用开发者屏蔽了复杂的技术细节。 ICE (Interactive Connectivity Establishment) ，交互式连接建立，是一种NAT穿透的框架，它集成了多种NAT穿越技术，比如STUN、TURN。 直接通信STUN：Client A <——————————————> Client B 间接通信TURN：Client A <——————代理 ——————> Client B

## 项目

[基于 webrtc 的浏览器 p2p 技术 - V2EX](https://www.v2ex.com/t/241322#reply12)

webtorrent：\
基于p2p的流播放种子文件\
[WebTorrent - Streaming browser torrent client](https://webtorrent.io/)\
[webtorrent/webtorrent - github](https://github.com/webtorrent/webtorrent)

filepizza：\
基于p2p的文件传输，也依赖了webtorrent的技术 [kern/filepizza - github](https://github.com/kern/filepizza)


# 反向代理

## 参考

[利用CloudFlare的Workers和Pages反代Github并缓存实现Github文件加速访问 - 腾讯云开发者社区-腾讯云](https://cloud.tencent.com/developer/article/2211557?aareaSource=106000.5\&traceId=1_ukk36Kjy5cdfjc1piUj)

[EtherDream/jsproxy](https://github.com/EtherDream/jsproxy)


# spring-cloud


# spring boot admin 监控服务

## 重写spring boot的邮件通知服务

### 原理

查阅源码可以看到 邮件发送是调用了`MailSender`的实现类，在`org.springframework.mail.javamail`包中`JavaMailSenderImpl`实现了该接口，

```java
@Configuration
@ConditionalOnBean(MailSender.class)
@AutoConfigureAfter({ MailSenderAutoConfiguration.class })
@AutoConfigureBefore({ NotifierListenerConfiguration.class,
    CompositeNotifierConfiguration.class })
public static class MailNotifierConfiguration {
    @Autowired
    private MailSender mailSender;

    @Bean
    @ConditionalOnMissingBean
    @ConfigurationProperties("spring.boot.admin.notify.mail")
    public MailNotifier mailNotifier() {
        return new MailNotifier(mailSender);
    }
}
```

继续查阅`JavaMailSenderImpl`的配置文件代码可以看到`JavaMailSenderImpl`的注册配置中启用了`@ConditionalOnMissingBean(MailSender.class)`这个注解，所以只要重新写代码实现`MailSender`接口就可以替代原有的邮件发送代码

```java
@Configuration
@ConditionalOnClass({ MimeMessage.class, MimeType.class })
@ConditionalOnMissingBean(MailSender.class)
@Conditional(MailSenderCondition.class)
@EnableConfigurationProperties(MailProperties.class)
@Import(JndiSessionConfiguration.class)
public class MailSenderAutoConfiguration {

    private final MailProperties properties;

    private final Session session;

    public MailSenderAutoConfiguration(MailProperties properties,
            ObjectProvider<Session> session) {
        this.properties = properties;
        this.session = session.getIfAvailable();
    }

    @Bean
    public JavaMailSenderImpl mailSender() {
        JavaMailSenderImpl sender = new JavaMailSenderImpl();
        if (this.session != null) {
            sender.setSession(this.session);
        }
        else {
            applyProperties(sender);
        }
        return sender;
    }
    //....省略代码
}
```

### 实现

以下是具体的实现代码

```java
import com.google.gson.Gson;
import jodd.http.HttpRequest;
import jodd.http.HttpResponse;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.stereotype.Component;

/**
 * Created by Rizon on 2018/4/18.
 */
@Component
public class EmailSender implements MailSender {
    private final static Logger LOGGER = LoggerFactory.getLogger(EmailSender.class);

    @Value("${mail.server.url}")
    private String EMAIL_SERVER_URL;

    @Override
    public void send(SimpleMailMessage simpleMailMessage){
        this.send(new SimpleMailMessage[]{simpleMailMessage});
    }

    @Override
    public void send(SimpleMailMessage... simpleMailMessages){
        for (SimpleMailMessage simpleMailMessage : simpleMailMessages) {
            String to = StringUtils.join(simpleMailMessage.getTo(), ",");
            String cc = StringUtils.join(simpleMailMessage.getCc(), ",");
            String subject = simpleMailMessage.getSubject();
            String body = simpleMailMessage.getText();
            doSend(to, cc, subject, body);
        }
    }

    public void doSend(String receiver, String ccAddress, String subject, String body) {
        Email email = new Email(receiver, ccAddress, subject, body);
        LOGGER.info(String.format("Send-Email-Request: [url=%s, to=%s, subject=%s, body=%s]", EMAIL_SERVER_URL,
                email.receiver, email.subject, email.body));
        HttpRequest request = HttpRequest.post(EMAIL_SERVER_URL).multipart(true);

        HttpResponse response = request
                .form("sendEmailInfo", new Gson().toJson(email)).timeout(5000).send();
        LOGGER.info(String.format("Send-Email-Response: [response=%s]", response.bodyText()));
    }
    @Getter
    @Setter
    @AllArgsConstructor
    public static class Email {
        private String receiver;   //多收件人逗号分隔
        private String ccAddress;
        private String subject;
        private String body;
    }
}
```


# Spring Cloud 整理汇总

按照目前已学习的资料做了简单整理

\[TOC]

## 概述

cloud的微服务是采用Rest协议，dubbo采用的则是RPC协议

## 框架

[**Eureka**](/zhuan-ti/spring-cloud/spring-cloud#eureka)： 服务中心

[**Config**](/zhuan-ti/spring-cloud/spring-cloud#config)： 配置中心

[**Zuul**](/zhuan-ti/spring-cloud/spring-cloud#zuul)： 网关服务，外部应用访问微服务内部的API网关

[**Bus**](/zhuan-ti/spring-cloud/spring-cloud#bus)： 消息总线，AMQP的方式(目前支持Kafka和RabbitMQ)实现夸应用的spring 事件发布与监听 配置中心配置变更后通知所有客户端更新配置就是借助消息总线实现的。

[**Admin**](/zhuan-ti/spring-cloud/spring-cloud#admin): 监控服务，提供了监控界面来统一监控所有服务的状态，有报警功能。 `Spring Boot Actuator`提供了对单个Spring Boot的监控,而Admin则可以统一监控所有服务

## 细说

### 创建Cloud项目

父POM配置

```markup
```

### Eureka

[springcloud(二)：注册中心Eureka](http://www.ityouknow.com/springcloud/2017/05/10/springcloud-eureka.html) 依赖

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

### Config

依赖

```
```

### Zuul

### Bus

### Admin


# python


# python 学习

## 环境

### 安装python

安装python

```bash
apt install python2.7
apt install python3.4
```

[CentOS 7 安装 Python 3.6 | CentOS教程 - 与知](https://www.yuzhi100.com/tutorial/centos/centos-anzhuang-python36)

安装pip

> pip is already installed if you are using Python 2 >=2.7.9 or Python 3 >=3.4 downloaded from python.org or if you are working in a Virtual Environment created by virtualenv or pyvenv. Just make sure to upgrade pip.\
> [Installation — pip 18.1 documentation](https://pip.pypa.io/en/stable/installing/)

[同时安装 Python 2 与Python 3 的方法及pip模块的下载安装 - 天木星辰 - 博客园](https://www.cnblogs.com/zcool/p/7147245.html)\
使用这个脚本[get-pip.py](https://bootstrap.pypa.io/get-pip.py),使用不同版本的python运行就会安装对应的pip。

安装pip的其他方式： `apt-get install python3-pip` [I have python3.4 but no pip or ensurepip.. is something wrong with my python3.4 version?](https://stackoverflow.com/questions/29871372/i-have-python3-4-but-no-pip-or-ensurepip-is-something-wrong-with-my-python3-4) `python -m ensurepip` [python3.6 安装后没有pip? - 知乎](https://www.zhihu.com/question/54906859)

### 安装jupyterlab

帮助文档 [The Jupyter Notebook — Jupyter Notebook 5.7.2 documentation](https://jupyter-notebook.readthedocs.io/en/stable/index.html)\
docker镜像 <https://hub.docker.com/r/jupyter/datascience-notebook/>\
官方的datascience镜像整合了很多东西，也可以选择使用base镜像。\
默认启用的是经典的jupyter，如果想使用正在测试的jupyterlab可以执行docker时增加cmd：`start.sh jupyter lab`。\
start.sh和start-notebook.sh提供了很多可选参数，参考[Common Features docker-stacks documentation](https://jupyter-docker-stacks.readthedocs.io/en/latest/using/common.html#start-sh)

完整启动命令：`docker run -d -it --name datascience_jupyter_lab -v jupyter_notebook:/home/jovyan/work -p 8909:8888 jupyter/datascience-notebook start.sh jupyter lab`

jupyter安装其他内核

[How do I add python3 kernel to jupyter (IPython)](https://stackoverflow.com/questions/28831854/how-do-i-add-python3-kernel-to-jupyter-ipython) [Installing the IPython kernel — IPython 7.1.1 documentation](https://ipython.readthedocs.io/en/stable/install/kernel_install.html#kernel-install)

```bash
python -m ipykernel install --help

# --user 表示安装到当前用户
python2 -m pip install ipykernel
python2 -m ipykernel install --user

python3 -m pip install ipykernel
python3 -m ipykernel install --user
```

### 安装python版本管理，包管理，虚拟环境 工具

virtualenv 、pyenv、Anaconda 均可以进行python的版本管理和包管理\
Anaconda是一个用于科学计算的Python发行版，自带了Numpy、Sklearn等机器学习相关的库\
Virtualenv是一个Python虚拟环境库，用来创建一个新的Python环境\
pyenv也是一个python虚拟环境工具，但是已经很久没有更新支持了

**安装virtualenv** [Installation - virtualenv 16.1.0 documentation](https://virtualenv.pypa.io/en/latest/installation/) [virtualenv-廖雪峰的官方网站](https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001432712108300322c61f256c74803b43bfd65c6f8d0d0000)

Python3.3以上的版本通过venv模块原生支持虚拟环境，可以代替Python之前的virtualenv。

```bash
python3 -m venv ./venv
source ./venv/bin/activate
`
```

### 环境常见问题

#### mac python2.7 报错 ImportError: No module named zlib

[\[已解决\] mac python2.7 报错 ImportError: No module named zlib - V2EX](https://www.v2ex.com/t/511697)

解决了，用 brew 安装 python\@2 时，其中有个警告时需要 apple commandline tools，如果没有他会从源码编译一个 python 出来，所以仍然可以安装上，但是这个是不 适配的，所以安装上命令行工具后(`xcode-select --install`)，再重新装一遍 python 就好了(`brew reinstall python@2`)

以下为原帖：

mac 系统，装了 python2 和 python3，但是执行 python2.7.10 的 pip 命令会报错。

```bash
Traceback (most recent call last):
  File "/usr/local/bin/pip", line 11, in <module>
    load_entry_point('pip==18.0', 'console_scripts', 'pip')()
  File "/usr/local/lib/python2.7/site-packages/pkg_resources/__init__.py", line 484, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "/usr/local/lib/python2.7/site-packages/pkg_resources/__init__.py", line 2714, in load_entry_point
    return ep.load()
  File "/usr/local/lib/python2.7/site-packages/pkg_resources/__init__.py", line 2332, in load
    return self.resolve()
  File "/usr/local/lib/python2.7/site-packages/pkg_resources/__init__.py", line 2338, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
  File "/usr/local/lib/python2.7/site-packages/pip/_internal/__init__.py", line 20, in <module>
    from pip._vendor.urllib3.exceptions import DependencyWarning
  File "/usr/local/lib/python2.7/site-packages/pip/_vendor/urllib3/__init__.py", line 8, in <module>
    from .connectionpool import (
  File "/usr/local/lib/python2.7/site-packages/pip/_vendor/urllib3/connectionpool.py", line 36, in <module>
    from .response import HTTPResponse
  File "/usr/local/lib/python2.7/site-packages/pip/_vendor/urllib3/response.py", line 3, in <module>
    import zlib
ImportError: No module named zlib
```

python 是用 homebrew 安装的。

网上说要安装 zlib 库，可是 mac 系统是自带一个版本的 zlib 的，我没法再装一个其他版本的吧？会冲突吧？ 用 brew 安装 zlib 时的提示：

```bash
zlib: stable 1.2.11 (bottled) [keg-only]
General-purpose lossless data-compression library
https://zlib.net/
/usr/local/Cellar/zlib/1.2.11 (12 files, 373KB)
  Poured from bottle on 2018-11-26 at 18:31:13
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/zlib.rb
==> Caveats
zlib is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.
```

## 知识

**init.py的作用** [Python杂谈: **init**.py的作用](https://www.cnblogs.com/tp1226/p/8453854.html)

> python2中一个目录只有带有`__init__.py`才能识别会一个模块，使用import导入，但是在python3中，就算没有这个文件也可以作为模块导入。

**空值与空对象判断** [Python中的空值判断](https://www.jianshu.com/p/a0d273550f70) [Python中的NULL和None](https://blog.csdn.net/songyunli1111/article/details/75145533) [python中的null值](https://www.cnblogs.com/landhu/p/6497975.html)

> python是把0，空字符串‘’，空列表\[]和None都看作False，把其他数值和非空字符串都看作True

**命令行调用python的几种方式**

已如下项目结构为例:

```bash
# 目录结构如下
package/
    __init__.py
    mod1.py
package2/
    __init__.py
    run.py
```

```python
# run.py 内容如下
import sys
from package import mod1
print(sys.path)
```

启动方式：

```bash
# 直接启动（失败）
$ python package2/run.py
Traceback (most recent call last):
  File "package2/run.py", line 2, in <module>
    from package import mod1
ImportError: No module named package

# 以模块方式启动（成功） -m 方式启动类似于import模块的操作，会把当前执行目录('')加入sys.path中
$ python -m package2.run
['',
'/usr/local/Cellar/python/2.7.11/Frameworks/Python.framework/Versions/2.7/lib/python27.zip',...]

# 设置PYTHONPATH变量 (成功)
$ PYTHONPATH=. python package2/run.py
```

## 项目开发

python项目开发中的知识

### 项目交付

**依赖管理** python中项目使用了那些第三方库，可以使用如下命令导出和导入。参考：[Python项目交付中环境迁移问题](https://www.jianshu.com/p/28b64c050f42)

```bash
# 导出所有库及库的版本到文件
pip freeze > requirements.txt
# 从文件安装依赖
pip install -r requirements.txt

# conda中的使用
conda env export > environment.yml
conda env create -f environment.yml
```

**项目迁移** 如果想要迁移项目，众多依赖包重新下载很费劲，但如果你的项目使用了虚拟环境，比如`virtualenv`，那么可以带着虚拟环境一起打包迁移。


# Python 修饰器的一些小细节

[Python 修饰器的一些小细节 - V2EX](https://www.v2ex.com/t/513981)

```python
"""
带参数修饰器会在初始化时就执行修饰器的代码并将方法体重新赋值给方法名。
"""


def deco1(args):
    """
    方法执行时不会再执行修饰器代码，**因为该代码返回了方法本身**
    等价于：
        def deco1(args):
            print("deco1:",args)
            return lambda fn:fn
        test1=deco1(args='deco1args')(mymethod)
    :param args:
    :return:
    """

    def decorator(fn):
        print('deco1:', args)
        return fn

    return decorator


@deco1("deco1args")
def test1():
    print("test1")


"""
不带参数修饰器在初始化的时候会将方法名赋值给修饰器方法，修饰器方法内部来手动调用被修饰的方法。
"""


def deco2(fn):
    """
    方法每次执行时都会执行修饰器因为 test2 重新赋值为修饰器的函数
    等价于：
        def deco2(fn):
            def inFn():
                print('deco2')
                fn()
            return inFn
        myMethod2 = deco2(lambda: print("call myMethod2"))
    :param fn:
    :return:
    """

    def decorator():
        print('deco2')
        fn()

    return decorator


@deco2
def test2():
    print("test2")


def deco3(fn):
    """
    不会执行 fn 因为 fn 没有被调用
    等价于:
        def deco3(fn):
            def inFn():
                print('deco3')
                return fn
            return inFn
        test3=deco3(test3)

    :return:
    """

    def decorator():
        print('deco3')
        return fn

    return decorator


@deco3
def test3():
    print("test3")


if __name__ == '__main__':
    print("-----开始执行 main 方法-----")

    print("test1 每次调用都不会执行修饰器：")
    test1()
    print("")
    test1()

    print("======")
    print("test2 每次调用都会执行修饰器：")
    test2()
    print("")
    test2()
    print("======")
    print("test3 不会被执行：")
    test3()
    print("test3 的返回值才是 test3 方法，因此要这样执行：")
    test3()()

"""
---------
"""

print("-------等价函数------")


def eqDeco1(args):
    print("deco1:", args)
    return lambda fn: fn


print("deco1 的等价：")

myMethod = eqDeco1(args='deco1args')(lambda: print("call myMethod1"))
myMethod()


def eqDeco2(fn):
    def inFn():
        print('deco2')
        fn()

    return inFn


myMethod2 = eqDeco2(lambda: print("call myMethod2"))
myMethod2()


def eqDeco3(fn):
    def inFn():
        print('deco3')
        return fn

    return inFn


myMethod3 = eqDeco3(lambda: print("call myMethod3"))
myMethod3()
myMethod3()()
```

执行结果

```bash
deco1: deco1args
-----开始执行 main 方法-----
test1 每次调用都不会执行修饰器：
test1

test1
======
test2 每次调用都会执行修饰器：
deco2
test2

deco2
test2
======
test3 不会被执行：
deco3
test3 的返回值才是 test3 方法，因此要这样执行：
deco3
test3
-------等价函数------
deco1 的等价：
deco1: deco1args
call myMethod1
deco2
call myMethod2
deco3
deco3
call myMethod3
```

## 在Django中实现flask的修饰器注册url映射

目录结构

```bash
django_school
├── __init__.py
├── controller
│   ├── __init__.py
│   ├── test.py
│   └── test2.py
├── settings.py
├── urls.py
└── wsgi.py
```

在`url.py`文件中创建修饰器，并import用到修饰器的方法以触发修饰器代码执行

```python
# url.py
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls)
]

# 创建修饰器
def route(url_path):
    def add(fn):
        urlpatterns.append(path(url_path, fn))
        return fn

    return add

# import文件 执行修饰器
from django_school.controller import test,test2
```

具体使用的代码

```python
# controller.test1.py
from django.http import HttpResponse
from django_school.urls import route

@route("demo1/")
def index1(request):
    return HttpResponse("this is demo1")
```


# 云主机


# aliyun 主机的种种

## 参考

[云服务器 ECS Linux CentOS 7 下使用iptables服务](https://help.aliyun.com/knowledge_detail/41319.html) [一键卸载阿里云安骑士监控以及云盾扫描IP方法](https://www.yd631.com/quartz-uninstall/) [卸载阿里云盾监控 & 屏蔽云盾 IP](https://liyuans.com/archives/uninstall-ali-cloud-shield-monitoring.html)


# maven


# maven使用

## maven多模块

maven的多模块，由于项目比较大，每次修改后构建整个工程耗时太久，需要单独构建某个模块，mvn有支持的选项：

```bash
-pl, --projects
        Build specified reactor projects instead of all projects
-am, --also-make
        If project list is specified, also build projects required by the list
-amd, --also-make-dependents
        If project list is specified, also build projects that depend on projects on the list
```

首先切换到工程的根目录， 单独构建模块 pingjuan-web，同时会构建 pingjuan-web 模块依赖的其他模块 `$ mvn install -pl pingjuan-web -am` 单独构建模块 pingjuan-common，同时构建依赖模块 pingjuan-common 的其他模块 `$ mvn install -pl pingjuan-common -am -amd`

### 指定构建子模块

如果想要构建的模块是一个子模块，则需要通过path路径的方式来指定子模块\
示例中 moduleA\_1是moduleA的子模块

`mvn clean package -pl moduleA/moduleA_1 -am`

## maven版本管理

参考： [Versions maven plugin 修改版本 - 详细使用](http://blog.csdn.net/ggbomb2/article/details/78316068) [versions-maven-plugin插件官网](http://www.mojohaus.org/versions-maven-plugin/index.html)

当使用此插件在父Maven项目下，运行如下命令将更新全部项目的版本号，包括子项目之间的依赖也都同步更新： `mvn versions:set -DnewVersion=2.0-SNAPSHOT`

当进入到子Maven项目下，运行如下命令将更新全部项目对该子项目引用的版本号： `mvn versions:set -DnewVersion=2.1-SNAPSHOT`

当更改版本号时有问题，可以通过以下命令进行版本号回滚： `mvn versions:revert`

如果一切都没有问题，那就直接提交版本号： `mvn versions:commit` 修改版本所产生的backup文件会在commit后自动删除。

## maven打tar.gz包来部署

[maven assembly打tar.gz包。 - CSDN博客](http://blog.csdn.net/sdlyjzh/article/details/53396370)


# maven项目增加编译版本号 buildnumber-maven-plugin

buildnumber-maven-plugin 这个maven插件可以在编译项目时将git的版本号、时间戳等信息存储到pom中，\
然后可以在代码接口中去读取这些值来方便的查阅代码的部署版本与时间。

[buildnumber-maven-plugin](http://www.mojohaus.org/buildnumber-maven-plugin/usage.html)

```
<!-- pom配置 -->
  <build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>buildnumber-maven-plugin</artifactId>
            <version>1.4</version>
            <executions>
                <execution>
                    <phase>validate</phase>
                    <goals>
                        <goal>create</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <items>
                    <item>timestamp</item>
                    <item>buildNumber</item>
                </items>
            </configuration>
        </plugin>
    </plugins>
  </build>
```

```
# application.yml配置 yml格式读取maven属性使用@@来表示，在properties格式中用${project.versio}
projectBuildVersionInfo:
  version: @project.version@
  buildTimestamp: @timestamp@
  scmVersion: @buildNumber@
```

```java
@RestController
@RequestMapping("/health")
public class ServerHealthController {

    @Autowired
    private VersionInfo versionInfo;

    @GetMapping(value = "/ping")
    public String ping() {
        return "pong";
    }

    @GetMapping("version")
    public VersionInfo buildVersion() {
        return versionInfo;
    }
    @Data
    @Component
    @ConfigurationProperties(prefix = "project-build-version-info")
    public static class VersionInfo {
        private String version;
        private String buildTimestamp;
        private String scmVersion;
    }
}
```


# 仓库

## 安装到私有仓库

```
mvn deploy:deploy-file\
 -DgroupId=com.example.example -DartifactId=common-utils -Dversion=1.0-SNAPSHOT\
 -Dpackaging=jar -Dfile=common-utils.jar\
 -DrepositoryId=snapshots\
 -Durl=http://127.0.0.1/nexus/content/repositories/snapshots/
```

### 标准的mvn deploy 到仓库

[pom配置之：\<distributionManagement>snapshot快照库和release发布库 - CSDN博客](http://blog.csdn.net/aitangyong/article/details/53332091)

```markup
<distributionManagement>
    <snapshotRepository>
        <id>snapshots</id>
        <name>snapshots</name>
        <url>http://127.0.0.1:8888/nexus/content/repositories/snapshots/</url>
    </snapshotRepository>
</distributionManagement>
```


# java


# java 开发常用工具类

## http请求

[java实现HTTP请求的三种方式](https://www.cnblogs.com/hhhshct/p/8523697.html)

[利用HttpClient4，实现get，post 参数，post json，post file - CSDN博客](https://blog.csdn.net/happy814506779/article/details/80432458)

org.apache.http.client.HttpClient httpclient 4.x版

```markup
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.6</version>
</dependency>
```

```java
package datacenter.plugins.el.utils;


import datacenter.plugins.el.common.PluginException;
import org.apache.commons.collections4.MapUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;

/**
 * http请求工具
 *
 * @author Rizon
 * @date 2018/9/12
 */

public class HttpUtils {
    private final static Logger log = Logger.getLogger(HttpUtils.class);

    private final static int CONNECT_TIMEOUT;
    private final static int SOCKET_TIMEOUT;
    private final static HttpClient HTTP_CLIENT;

    static {
        CONNECT_TIMEOUT = Integer.valueOf(ConfigUtil.getOrDefaultValue("http.connect-timeout", "3000"));
        SOCKET_TIMEOUT = Integer.valueOf(ConfigUtil.getOrDefaultValue("http.socket-timeout", "6000"));

        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        httpClientBuilder.setRetryHandler(
                new DefaultHttpRequestRetryHandler(3, true));
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(SOCKET_TIMEOUT)
                .setSocketTimeout(CONNECT_TIMEOUT)
                .build();
        httpClientBuilder.setDefaultRequestConfig(requestConfig);
        HTTP_CLIENT = httpClientBuilder.build();
    }

    public static String get(String url, List<NameValuePair> para) {
        URI uri;
        try {
            uri = new URIBuilder(url)
                    .addParameters(para)
                    .build();
        } catch (URISyntaxException e) {
            log.error("URISyntaxException", e);
            throw new PluginException("URISyntaxException", e);
        }
        return get(uri);
    }

    public static String get(String url, Map<String, String> para) {
        URI uri;
        try {
            URIBuilder uriBuilder = new URIBuilder(url);
            MapUtils.emptyIfNull(para).forEach(uriBuilder::addParameter);
            uri = uriBuilder.build();
        } catch (URISyntaxException e) {
            log.error("URISyntaxException", e);
            throw new PluginException("URISyntaxException", e);
        }
        return get(uri);
    }

    public static String get(URI uri) {
        log.info("http get: " + uri);
        try {
            HttpResponse response = HTTP_CLIENT.execute(new HttpGet(uri));
            if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
                System.out.println(response.getStatusLine().getStatusCode());
                return EntityUtils.toString(response.getEntity());
            } else {
                log.error("http status code!=200");
                throw new PluginException("http status code!=200");
            }
        } catch (IOException e) {
            log.error("http request IOException", e);
            throw new PluginException("http request IOException", e);
        }

    }
}
```

org.springframework.web.client.RestTemplate\
spring的好用的htto请求工具，但是包含在spring-web中，不适合单独使用

```markup
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.0.4.RELEASE</version>
</dependency>
```

org.apache.commons.httpclient.HttpClient apache的http工具3.x版，封装的不是很好，使用过于繁琐

```markup
<dependency>
            <groupId>commons-httpclient</groupId>
            <artifactId>commons-httpclient</artifactId>
            <version>3.1</version>
        </dependency>
```


# java

## 使用Java 8中的Stream

[使用Java 8中的Stream - 随风逝去,只是凋零 - 博客园](https://www.cnblogs.com/zxf330301/p/6586750.html)

### reduce

`reduce(accumulator)` ：参数是一个执行双目运算的 Functional Interface ，假如这个参数表示的操作为op，stream中的元素为x, y, z, …，则 reduce() 执行的就是 x op y op z ...，所以要求op这个操作具有结合性(associative)，即满足： (x op y) op z = x op (y op z)，满足这个要求的操作主要有：求和、求积、求最大值、求最小值、字符串连接、集合并集和交集等。另外，该函数的返回值是Optional的：

```java
Optional <integer>sum1 = numStream.reduce((x, y) -> x + y);
```

`reduce(identity, accumulator)` ：可以认为第一个参数为默认值，但需要满足 identity op x = x ，所以对于求和操作， identity 的值为0，对于求积操作， identity 的值为1。返回值类型是stream元素的类型：

```java
Integer sum2 = numStream.reduce(0, Integer::sum);
```

reduce 如果不加参数`identity`则返回的是optional类型的，reduce在进行双目运算时，其中一个场景是与`identity`做比较操作，因此我们应该满足`identity op x = x`

示例代码：分组后每组返回最大的一条数据

```java
List<StandardTaskController.TaskResponse> taskResponses = queryResult.stream().map(task -> buildTaskResponse(task, queryParam.getBusinessType()))
                .collect(Collectors.toList());
StandardTaskController.TaskResponse zeroTime = StandardTaskController.TaskResponse
            .builder().createTime(new Date(0)).build();
taskResponses=taskResponses.stream()
.collect(
        Collectors.collectingAndThen(
                Collectors.groupingBy(StandardTaskController.TaskResponse::getProInsId,
                        Collectors.reducing(zeroTime,
                                BinaryOperator.maxBy(Comparator.comparing(StandardTaskController.TaskResponse::getCreateTime)))),
                                                        r -> new ArrayList<>(r.values())));
```

### toMap

```java
public Map<KeyMapper.Type, Map<String, String>> getKeyMapper() {
        List<KeyMapper> mapper = bossRuleService.getKeyMapper();
        Map<KeyMapper.Type, List<KeyMapper>> collect = mapper.stream()
                .collect(Collectors.groupingBy(KeyMapper::getType));
        Map<KeyMapper.Type, Map<String, String>> keyMapper = new HashMap<>();
        for (Map.Entry<KeyMapper.Type, List<KeyMapper>> entry : collect.entrySet()) {
            //得到一个忽略大小写的map
            Map<String, String> map = entry.getValue().stream()
                    .collect(Collectors.toMap(m -> m.bossKey, m -> m.billKey,
                            (u, v) -> {
                                throw new IllegalStateException(String.format("同一个bossKey却存在两个billKey,key1:%s,key2:%s", u,v));
                            },
                            CaseInsensitiveMap::new));
            keyMapper.put(entry.getKey(), map);
        }
        if (MapUtils.isEmpty(keyMapper)) {
            throw new RuntimeException("keyMapper 映射数据集合为空");
        }
        return keyMapper;
    }
```

### 基础理论

#### 垃圾回收

[Java: 对象不再使用时赋值为null的作用和原理 - Penguin](https://www.polarxiong.com/archives/Java-对象不再使用时赋值为null的作用和原理.html)

[Java垃圾回收（GC）机制详解 - 平凡希 - 博客园](https://www.cnblogs.com/xiaoxi/p/6486852.html)

[面试总结：java程序执行过程 + JVM内存管理 + GC垃圾回收机制 - 两鬓已不能斑白的专栏 - CSDN博客](https://blog.csdn.net/u010429424/article/details/77333311)

> 在主流的商用程序语言中(Java和C#)，都是使用可达性分析算法判断对象是否存活的。这个算法的基本思路就是通过一系列名为GC Roots的对象作为起始点，从这些节点开始向下搜索，搜索所走过的路径称为引用链(Reference Chain)，当一个对象到GC Roots没有任何引用链相连时，则证明此对象是不可用的，下图对象object5, object6, object7虽然有互相判断，但它们到GC Roots是不可达的，所以它们将会判定为是可回收对象。 <https://blog.csdn.net/QuinnNorris/article/details/75040538>
>
> <https://www.zhihu.com/question/50381439/answer/226231622> GC管理的主要区域是Java堆，一般情况下只针对堆进行垃圾回收。方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象作为GC roots,被GC roots引用的对象不被GC回收。详细：GC Root常说的GC(Garbage Collector) roots，特指的是垃圾收集器（Garbage Collector）的对象，GC会收集那些不是GC roots且没有被GC roots引用的对象。一个对象可以属于多个root，GC root有几下种： Class - 由系统类加载器(system class loader)加载的对象，这些类是不能够被回收的，他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是，通过用户自定义的类加载器加载的类，除非相应的java.lang.Class实例以其它的某种（或多种）方式成为roots，否则它们并不是roots。 Thread - 活着的线程 Stack Local - Java方法的local变量或参数 JNI Local - JNI方法的local变量或参数 JNI Global - 全局JNI引用 Monitor Used - 用于同步的监控对象 Held by JVM - 用于JVM特殊目的由GC保留的对象，但实际上这个与JVM的实现是有关的。可能已知的一些类型是：系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。然而，JVM并没有为这些对象提供其它的信息，因此需要去确定哪些是属于"JVM持有"的了。

![](https://3585898866-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LcehtLiMKFYRXXJ4Dzh%2F-Lcei1h6xvrd1a6Mrkgg%2F-Lcei5SNs6B6MTP4EgUZ%2F2019-02-21-14-12-38.png?generation=1555494890277821\&alt=media)

堆里存着的是栈的引用


# apache commons pool 对象连接池

tags: apche-common-pool2

[基于Apache-Commons-Pool2实现Grpc客户端连接池-走在IT路上-51CTO博客](https://blog.51cto.com/andrewli/2148179)\
[使用Apache Commons Pool实现数据库连接池 - 怀揣梦想，努力前行 - CSDN博客](https://blog.csdn.net/a19881029/article/details/81436193)\
[apache-common-pool2对象池的使用 - u013332124的专栏 - CSDN博客](https://blog.csdn.net/u013332124/article/details/81042375)\
[apche-common-pool2](http://commons.apache.org/proper/commons-pool/)

## 相关

LoadingCache 缓存： [HBase Connection Management](http://richardstartin.uk/hbase-connection-management/)


# 大数据


# kafka

## docker compose 部署kafka

参考： [docker-compose安装kafka集群和kafka-manager管理界面](https://blog.csdn.net/sinat_31908303/article/details/80447383)\
[Kafka Docker-官方](http://wurstmeister.github.io/kafka-docker/)\
[使用docker安装kafka - CSDN博客](https://blog.csdn.net/lblblblblzdx/article/details/80548294)

```
#示例
version: '2'
services:
  zookeeper:
    image: wurstmeister/zookeeper   ## 镜像
    # ports:
      # - "2181:2181"                 ## 对外暴露的端口号
  kafka:
    image: wurstmeister/kafka       ## 镜像
    # ports:
      # - "9092:9092"
    environment:
      KAFKA_ADVERTISED_HOST_NAME: kafka   ## 修改:宿主机IP
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181       ## 卡夫卡运行是基于zookeeper的
    depends_on:
      - zookeeper
  kafka-manager:  
    image: sheepkiller/kafka-manager                ## 镜像：开源的web管理kafka集群的界面
    environment:
        ZK_HOSTS: zookeeper:2181                  ## 修改:宿主机IP
    ports:  
      - "9000:9000"                                 ## 暴露端口
    depends_on:
     - kafka
```

测试命令\
参考： [kafka实战 - 嘿123 - 博客园](https://www.cnblogs.com/hei12138/p/7805475.html)\
[Gobblin部署--standalone模式 - CSDN博客](https://blog.csdn.net/lmalds/article/details/53993826) [Gobblin部署--mapreduce模式 - CSDN博客](https://blog.csdn.net/lmalds/article/details/53994091)

打开管理界面(locahost:9000)，创建一个cluster，并创建一个topic（demo）。\
然后测试发送和接受消息\
<http://kafka.apache.org/documentation.html#quickstart>

```bash
# consumer
## 进入容器
docker exec -it kafka_kafka_1 /bin/sh
## 进入容器后执行,等待消息
kafka-console-consumer.sh --bootstrap-server kafka:9092 --topic demo --from-beginning

# producer
## 进入容器
docker exec -it kafka_kafka_1 /bin/sh
## 进入容器后执行
kafka-console-producer.sh --broker-list kafka:9092 --topic demo
## 然后试着输入一些文本来发送消息，查看消息接收
```

## 杂项

拼接jar包依赖路径

```bash
 --jars `ls /home/hdfs/test/gobblin-dist/lib/* | tr "\n" ","`
```


# gobblin

## docker安装

[Docker Integration - Gobblin Documentation](https://gobblin.readthedocs.io/en/latest/user-guide/Docker-Integration/)

```bash
docker run -v /home/gobblin/conf:/etc/opt/job-conf \
           -v /home/gobblin/work-dir:/home/gobblin/work-dir \
           -v /home/gobblin/logs:/var/log/gobblin \
           gobblin/gobblin-standalone:ubuntu-gobblin-latest
```

[Gobblin编译支持CDH5.4.0 - CSDN博客](https://blog.csdn.net/cssdongl/article/details/77750444)

## 采集kafka数据

[Gobblin采集kafka数据 - Syn良子 - 博客园](https://www.cnblogs.com/cssdongl/p/6121382.html)

## 概念

[Gobblin--一个用于Hadoop的统一"数据抽取框架" - CSDN博客](https://blog.csdn.net/lmalds/article/details/53940549)

### 组件

```
1、source
2、extractor
3、convertor
4、quality checker
5、writer
6、publisher
```

* Source主要负责将源数据整合到一系列workunits中，并指出对应的extractor是什么。这有点类似于Hadoop的InputFormat。
* Extractor则通过workunit指定数据源的信息，例如kafka，指出topic中每个partition的起始offset，用于本次抽取使用。Gobblin使用了watermark的概念，记录每次抽取的数据的起始位置信息。
* Converter顾名思义是转换器的意思，即对抽取的数据进行一些过滤、转换操作，例如将byte arrays 或者JSON格式的数据转换为需要输出的格式。转换操作也可以将一条数据映射成0条或多条数据（类似于flatmap操作）。
* Quality Checker即质量检测器，有2中类型的checker：record-level和task-level的策略。通过手动策略或可选的策略，将被check的数据输出到外部文件或者给出warning。
* Writer就是把导出的数据写出，但是这里并不是直接写出到output file，而是写到一个缓冲路径（ staging directory）中。当所有的数据被写完后，才写到输出路径以便被publisher发布。Sink的路径可以包括HDFS或者kafka或者S3中，而格式可以是Avro,Parquet,或者CSV格式。同时Writer也可是根据时间戳，将输出的文件输出到按照“小时”或者“天”命名的目录中。
* Publisher就是根据writer写出的路径，将数据输出到最终的路径。同时其提供2种提交机制：完全提交和部分提交；如果是完全提交，则需要等到task成功后才pub，如果是部分提交模式，则当task失败时，有部分在staging directory的数据已经被pub到输出路径了。

## 配置

[Configuration Glossary - Gobblin Documentation](https://gobblin.readthedocs.io/en/latest/user-guide/Configuration-Properties-Glossary/)

```bash
# TaskContext.getDataWriterBuilder
## DEFAULT_WRITER_BUILDER_CLASS = "org.apache.gobblin.writer.AvroDataWriterBuilder";
writer.builder.class=

# 要和writerBuilder保持统一
# writer输出文件的后缀 以及outputformat配置（但是writer里面好像并没用上？）
writer.output.format=ORC
```

```bash
#这两个配合决定了 可以让publisher分成job和task两个来配置
publish.data.at.job.level=false
job.commit.policy=successful

data.publisher.job.type=datacenter.plugins.gobblin.publisher.MyHiveRegistrationPublisher
data.publisher.task.type=org.apache.gobblin.publisher.BaseDataPublisher
```

### HiveWritableHdfsDataWriterBuilder和HiveSerDeConverter配置

```bash
# 这个converter里面要配置 一个 HiveSerDeWrapper 用于反序列化输入数据（serde.deserializer.type） 和序列化输出（serde.serializer.type）数据，有两种配置方式
converter.classes=org.apache.gobblin.converter.serde.HiveSerDeConverter
# 这个配置是HiveSerDeConverter解析数据的schema
avro.schema.literal={"namespace":"demo.hive","type":"record","name":"hiveorc","fields":[{"name":"jobRoles","type":["int"]},{"name":"peopleWeightAvg","type":["float"]},{"name":"peopleOrg","type":["string","null"]}]}

#配置序列化和反序列化
## this.serializer = HiveSerDeWrapper.getSerializer(state).getSerDe();
## this.deserializer = HiveSerDeWrapper.getDeserializer(state).getSerDe();
# style1
## 配置一个已经定义好的wrapper(一个wrapper包括inputformat和outputformat)
## org.apache.gobblin.hive.HiveSerDeWrapper.BuiltInHiveSerDe
serde.deserializer.type=TEXTFILE
serde.serializer.type=ORC

# style2
serde.deserializer.type=TEXTFILE
## 自定义一个wrapper
### 这个type必须是存在的org.apache.hadoop.hive.serde2.SerDe的实现类
### 如果这个class是在org.apache.gobblin.hive.HiveSerDeWrapper.BuiltInHiveSerDe中已经定义的，那么不会在使用'serde.serializer.input.format.type'和'serde.serializer.output.format.type',见[HiveSerDeWrapper的get](####HiveSerDeWrapper的get)
serde.serializer.type=org.apache.hadoop.hive.serde2.SerDe
### 定义inputformat
serde.serializer.input.format.type=org.apache.hadoop.hive.ql.io.orc.OrcInputFormat
### 定义outputformat
serde.serializer.output.format.type=org.apache.hadoop.hive.ql.io.orc.OrcOutputFormat

# HiveSerDeConverter通常可以配合HiveWritableHdfsDataWriterBuilder使用，
# HiveWritableHdfsDataWriterBuilder有两种配置方式
## 1. 定义 WRITER_WRITABLE_CLASS，和WRITER_OUTPUT_FORMAT_CLASS
writer.writable.class=org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe
writer.output.format.class=org.apache.hadoop.mapred.TextOutputFormat

## 2.定义 SERDE_SERIALIZER_TYPE，用于获取WRITER_WRITABLE_CLASS和WRITER_OUTPUT_FORMAT_CLASS
## 这个serde.serializer.type和convert中定义是一个东西，所以也是有两种风格定义，见HiveSerDeConverter配置
## 这个配置和HiveSerDeConverter是共用一个配置，HiveSerDeConverter序列化输出的数据，将由HiveWritableHdfsDataWriter使用相同的序列化配置去写出到文件 见[HiveWritableHdfsDataWriter的getWriter](####HiveWritableHdfsDataWriter的getWriter)
serde.serializer.type=ORC
```

### 压缩配置

`writer..codec.type` 配置压缩格式

以在 `org.apache.gobblin.writer.SimpleDataWriterBuilder`中为例：

```java
  @Override
  protected List<StreamCodec> buildEncoders() {
    Preconditions.checkNotNull(this.destination, "Destination must be set before building encoders");

    List<StreamCodec> encoders = new ArrayList<>();

    //从 writer..codec.type 中读取压缩类型
    Map<String, Object> compressionConfig =
        CompressionConfigParser.getConfigForBranch(this.destination.getProperties(), this.branches, this.branch);
    if (compressionConfig != null) {
      // 生成StreamCodec
      encoders.add(CompressionFactory.buildStreamCompressor(compressionConfig));
    }

    Map<String, Object> encryptionConfig = EncryptionConfigParser
        .getConfigForBranch(EncryptionConfigParser.EntityType.WRITER, this.destination.getProperties(), this.branches,
            this.branch);
    if (encryptionConfig != null) {
      encoders.add(EncryptionFactory.buildStreamCryptoProvider(encryptionConfig));
    }

    return encoders;
  }
```

## 源码分析

### HiveWritableHdfsDataWriterBuilder和HiveSerDeConverter

配置方法见 [HiveWritableHdfsDataWriterBuilder和HiveSerDeConverter配置](https://book.rizon.top/da-shu-ju/pages/-Lcei2xaWnWfpdzOMUK9###HiveWritableHdfsDataWriterBuilder和HiveSerDeConverter配置)

```java
package org.apache.gobblin.writer;
public class HiveWritableHdfsDataWriterBuilder<S> extends FsDataWriterBuilder<S, Writable> {
    public DataWriter<Writable> build() throws IOException {
        Preconditions.checkNotNull(this.destination);
        Preconditions.checkArgument(!Strings.isNullOrEmpty(this.writerId));

        State properties = this.destination.getProperties();
        // 如果定义了WRITER_WRITABLE_CLASS和WRITER_OUTPUT_FORMAT_CLASS则直接用这两个创建writer
        // 如果没定义而是配置了serde.serializer.type则用其去生成writer
        if (!properties.contains(WRITER_WRITABLE_CLASS) || !properties.contains(WRITER_OUTPUT_FORMAT_CLASS)) {
        HiveSerDeWrapper serializer = HiveSerDeWrapper.getSerializer(properties);
        // 从org.apache.hadoop.hive.serde2.SerDe的实现类中获取serializedClass
        properties.setProp(WRITER_WRITABLE_CLASS, serializer.getSerDe().getSerializedClass().getName());
        properties.setProp(WRITER_OUTPUT_FORMAT_CLASS, serializer.getOutputFormatClassName());
        }

        return new HiveWritableHdfsDataWriter(this, properties);
    }
}
```

#### HiveWritableHdfsDataWriter的getWriter

```java
package org.apache.gobblin.writer;

public class HiveWritableHdfsDataWriter extends FsDataWriter<Writable> {
  private RecordWriter getWriter() throws IOException {
      try {
        HiveOutputFormat<?, ?> outputFormat = HiveOutputFormat.class
            .cast(Class.forName(this.properties.getProp(HiveWritableHdfsDataWriterBuilder.WRITER_OUTPUT_FORMAT_CLASS))
                .newInstance());

        @SuppressWarnings("unchecked")
        Class<? extends Writable> writableClass = (Class<? extends Writable>) Class
            .forName(this.properties.getProp(HiveWritableHdfsDataWriterBuilder.WRITER_WRITABLE_CLASS));

        return outputFormat.getHiveRecordWriter(new JobConf(), this.stagingFile, writableClass, true,
            this.properties.getProperties(), null);
      } catch (Throwable t) {
        throw new IOException(String.format("Failed to create writer"), t);
      }
    }
}
```

#### HiveSerDeWrapper的get

```java
package org.apache.gobblin.hive;

public class HiveSerDeWrapper {
  /**
   * Get an instance of {@link HiveSerDeWrapper}.
   *
   * @param serDeType The SerDe type. If serDeType is one of the available {@link HiveSerDeWrapper.BuiltInHiveSerDe},
   * the other three parameters are not used. Otherwise, serDeType should be the class name of a {@link SerDe},
   * and the other three parameters must be present.
   */
  public static HiveSerDeWrapper get(String serDeType, Optional<String> inputFormatClassName,
      Optional<String> outputFormatClassName) {
    Optional<BuiltInHiveSerDe> hiveSerDe = Enums.getIfPresent(BuiltInHiveSerDe.class, serDeType.toUpperCase());
    if (hiveSerDe.isPresent()) {
      return new HiveSerDeWrapper(hiveSerDe.get());
    }
    Preconditions.checkArgument(inputFormatClassName.isPresent(),
        "Missing input format class name for SerDe " + serDeType);
    Preconditions.checkArgument(outputFormatClassName.isPresent(),
        "Missing output format class name for SerDe " + serDeType);
    return new HiveSerDeWrapper(serDeType, inputFormatClassName.get(), outputFormatClassName.get());
  }
}
```

### 数据写出

> Writer就是把导出的数据写出，但是这里并不是直接写出到output file，而是写到一个缓冲路径（ staging directory）中。当所有的数据被写完后，才写到输出路径以便被publisher发布。Sink的路径可以包括HDFS或者kafka或者S3中，而格式可以是Avro,Parquet,或者CSV格式。同时Writer也可是根据时间戳，将输出的文件输出到按照“小时”或者“天”命名的目录中 原文：<https://blog.csdn.net/lmalds/article/details/53940549>

STEP1.1 写到stagingFileOutputStream

\`\`\`java SimpleDataWriter.java package org.apache.gobblin.writer;

public class SimpleDataWriter extends FsDataWriter {

```
/**
* Write a source record to the staging file
*
* @param record data record to write
* @throws java.io.IOException if there is anything wrong writing the record
*/
@Override
public void write(byte[] record) throws IOException {
    Preconditions.checkNotNull(record);

    byte[] toWrite = record;
    if (this.recordDelimiter.isPresent()) {
    toWrite = Arrays.copyOf(record, record.length + 1);
    toWrite[toWrite.length - 1] = this.recordDelimiter.get();
    }
    if (this.prependSize) {
    long recordSize = toWrite.length;
    ByteBuffer buf = ByteBuffer.allocate(Longs.BYTES);
    buf.putLong(recordSize);
    toWrite = ArrayUtils.addAll(buf.array(), toWrite);
    }
    this.stagingFileOutputStream.write(toWrite);
    this.bytesWritten += toWrite.length;
    this.recordsWritten++;
}
```

}

````
STEP1.2 从stagingFile 移动到outputFile，即writer组件写出文件

>`outputFile`文件就是`writer.output.dir`配置的路径.
`public static final String WRITER_OUTPUT_DIR = WRITER_PREFIX + ".output.dir";`

```java
package org.apache.gobblin.writer;

public abstract class FsDataWriter<D> implements DataWriter<D>, FinalState, MetadataAwareWriter, SpeculativeAttemptAwareConstruct {
  /**
   * {@inheritDoc}.
   *
   * <p>
   *   This default implementation simply renames the staging file to the output file. If the output file
   *   already exists, it will delete it first before doing the renaming.
   * </p>
   *
   * @throws IOException if any file operation fails
   */
  @Override
  public void commit() throws IOException {
        this.closer.close();

        setStagingFileGroup();

        if (!this.fs.exists(this.stagingFile)) {
            throw new IOException(String.format("File %s does not exist", this.stagingFile));
        }

        FileStatus stagingFileStatus = this.fs.getFileStatus(this.stagingFile);

        // Double check permission of staging file
        if (!stagingFileStatus.getPermission().equals(this.filePermission)) {
            this.fs.setPermission(this.stagingFile, this.filePermission);
        }

        this.bytesWritten = Optional.of(Long.valueOf(stagingFileStatus.getLen()));

        LOG.info(String.format("Moving data from %s to %s", this.stagingFile, this.outputFile));
        // For the same reason as deleting the staging file if it already exists, deleting
        // the output file if it already exists prevents task retry from being blocked.
        if (this.fs.exists(this.outputFile)) {
            LOG.warn(String.format("Task output file %s already exists", this.outputFile));
            HadoopUtils.deletePath(this.fs, this.outputFile, false);
        }
        // ⚠️移动stagingFile到outputFile也就是writer的写出文件
        HadoopUtils.renamePath(this.fs, this.stagingFile, this.outputFile);
 }
}
````

STEP2 写出到最终目录，获取writer写出的文件发布到最终目录

> `public static final String DATA_PUBLISHER_FINAL_DIR = DATA_PUBLISHER_PREFIX + ".final.dir";`

```java
package org.apache.gobblin.publisher;

public class BaseDataPublisher extends SingleTaskDataPublisher {
  protected void publishData(WorkUnitState state, int branchId, boolean publishSingleTaskData,
      Set<Path> writerOutputPathsMoved)
      throws IOException {
    // Get a ParallelRunner instance for moving files in parallel
    ParallelRunner parallelRunner = this.getParallelRunner(this.writerFileSystemByBranches.get(branchId));

    // The directory where the workUnitState wrote its output data.
    // ⚠️这里就是获取上一步写出的writer文件
    Path writerOutputDir = WriterUtils.getWriterOutputDir(state, this.numBranches, branchId);

    if (!this.writerFileSystemByBranches.get(branchId).exists(writerOutputDir)) {
      LOG.warn(String.format("Branch %d of WorkUnit %s produced no data", branchId, state.getId()));
      return;
    }

    // The directory where the final output directory for this job will be placed.
    // It is a combination of DATA_PUBLISHER_FINAL_DIR and WRITER_FILE_PATH.
    Path publisherOutputDir = getPublisherOutputDir(state, branchId);

    if (publishSingleTaskData) {
      // Create final output directory
      WriterUtils.mkdirsWithRecursivePermissionWithRetry(this.publisherFileSystemByBranches.get(branchId), publisherOutputDir,
          this.permissions.get(branchId), retrierConfig);
      addSingleTaskWriterOutputToExistingDir(writerOutputDir, publisherOutputDir, state, branchId, parallelRunner);
    } else {
      if (writerOutputPathsMoved.contains(writerOutputDir)) {
        // This writer output path has already been moved for another task of the same extract
        // If publishSingleTaskData=true, writerOutputPathMoved is ignored.
        return;
      }

      if (this.publisherFileSystemByBranches.get(branchId).exists(publisherOutputDir)) {
        // The final output directory already exists, check if the job is configured to replace it.
        // If publishSingleTaskData=true, final output directory is never replaced.
        boolean replaceFinalOutputDir = this.getState().getPropAsBoolean(ForkOperatorUtils
            .getPropertyNameForBranch(ConfigurationKeys.DATA_PUBLISHER_REPLACE_FINAL_DIR, this.numBranches, branchId));

        // If the final output directory is not configured to be replaced, put new data to the existing directory.
        if (!replaceFinalOutputDir) {
          addWriterOutputToExistingDir(writerOutputDir, publisherOutputDir, state, branchId, parallelRunner);
          writerOutputPathsMoved.add(writerOutputDir);
          return;
        }

        // Delete the final output directory if it is configured to be replaced
        LOG.info("Deleting publisher output dir " + publisherOutputDir);
        this.publisherFileSystemByBranches.get(branchId).delete(publisherOutputDir, true);
      } else {
        // Create the parent directory of the final output directory if it does not exist
        WriterUtils.mkdirsWithRecursivePermissionWithRetry(this.publisherFileSystemByBranches.get(branchId),
            publisherOutputDir.getParent(), this.permissions.get(branchId), retrierConfig);
      }
      // ⚠️移动writerOutputDir到最终输出的publisherOutputDir
      movePath(parallelRunner, state, writerOutputDir, publisherOutputDir, branchId);
      writerOutputPathsMoved.add(writerOutputDir);
    }
  }

  /**
   * close操作将发布的文件路径publisherOutputDirs 附加到ConfigurationKeys.PUBLISHER_DIRS（`data.publisher.output.dirs`）属性的值中，供后续使用，比如 HiveRegistrationPublisher
   */
  @Override
  public void close()
      throws IOException {
    try {
      for (Path path : this.publisherOutputDirs) {
        this.state.appendToSetProp(ConfigurationKeys.PUBLISHER_DIRS, path.toString());
      }
    } finally {
      // 调用google的`com.google.common.io.Closer`这个closer可以注册多个closable(`this.stack.push(closeable)`)在执行close方法时就会调用（`(Closeable)this.stack.pop().close()`）因此可以实现链式的关闭
      this.closer.close();
    }
  }
}
```

STEP4 publish之后的后续publish操作

如果在配置文件中配置了这个publish：

```bash
##配置方案1
data.publisher.type=org.apache.gobblin.publisher.BaseDataPublisherWithHiveRegistration

## 配置方案2
### org.apache.gobblin.runtime.Task.shouldPublishDataInTask()
publish.data.at.job.level=false
job.commit.policy=successful

data.publisher.job.type=org.apache.gobblin.publisher.HiveRegistrationPublisher
data.publisher.task.type=org.apache.gobblin.publisher.BaseDataPublisher
```

```java
package org.apache.gobblin.publisher;

public class BaseDataPublisherWithHiveRegistration extends BaseDataPublisher {

  protected final HiveRegistrationPublisher hivePublisher;

  public BaseDataPublisherWithHiveRegistration(State state) throws IOException {
    super(state);
    //向BaseDataPublisher的closer中注册该HiveRegistrationPublisher
    this.hivePublisher = this.closer.register(new HiveRegistrationPublisher(state));
  }

  @Override
  public void publish(Collection<? extends WorkUnitState> states) throws IOException {
    super.publish(states);
    this.hivePublisher.publish(states);
  }

}
```

#### Source Extractor Converter 关系

**Source获取Extractor** Source配置`source.class=org.apache.gobblin.source.extractor.extract.kafka.KafkaDeserializerSource`

```java
public class KafkaSimpleSource extends KafkaSource<String, byte[]> {
  @Override
  public Extractor<String, byte[]> getExtractor(WorkUnitState state) throws IOException {
    return new KafkaSimpleExtractor(state);
  }
}
```

Extractor中配置了`readRecord`方法用于读取数据 Extractor中配置了`getSchema`方法用于获取schema

```java
public class KafkaSimpleExtractor extends KafkaExtractor<String, byte[]> {
  public KafkaSimpleExtractor(WorkUnitState state) {
    super(state);
    this.kafkaSchemaRegistry = new SimpleKafkaSchemaRegistry(state.getProperties());
  }
  @Override
  public String getSchema() throws IOException {
    try {
      return this.kafkaSchemaRegistry.getLatestSchemaByTopic(this.topicName);
    } catch (SchemaRegistryException e) {
      throw new RuntimeException(e);
    }
  }
}
```

**Converter 转换Schema，Schema从Extractor中获取** Converter配置`converter.classes=org.apache.gobblin.converter.json.JsonStringToJsonIntermediateConverter,org.apache.gobblin.converter.avro.JsonIntermediateToAvroConverter` 这个配置是链式的。按顺序依次转换，[Source Schema and Converters](https://gist.github.com/tilakpatidar/2591c8f4503bcbd0bc0ab212b31ec9b5)

```java
package org.apache.gobblin.runtime;

public class Task implements TaskIFace {
  private final Converter converter;
  private final InstrumentedExtractorBase extractor;

  public Task(TaskContext context, ...) {
      //this.taskContext.getExtractor() 的内容: getSource().getExtractor(this.taskState)
    this.extractor =
            closer.register(new InstrumentedExtractorDecorator<>(this.taskState, this.taskContext.getExtractor()));
  }
  private void runSynchronousModel() throws Exception {
    // 1。 转换schema
    Object schema = converter.convertSchema(extractor.getSchema(), this.taskState);

    // 2.转换数据
    //   extractor.readRecordEnvelope():从extractor获取数据
    //   converter.convertRecord(）:将从extractor获取的数据做转换
    RecordEnvelope recordEnvelope;
      // Extract, convert, and fork one source record at a time.
      while (!shutdownRequested() && (recordEnvelope = extractor.readRecordEnvelope()) != null) {
        onRecordExtract();
        AcknowledgableWatermark ackableWatermark = new AcknowledgableWatermark(recordEnvelope.getWatermark());
        if (watermarkTracker.isPresent()) {
          watermarkTracker.get().track(ackableWatermark);
        }
        for (Object convertedRecord : converter.convertRecord(schema, recordEnvelope, this.taskState)) {
          processRecord(convertedRecord, forkOperator, rowChecker, rowResults, branches,
              ackableWatermark.incrementAck());
        }
        ackableWatermark.ack();
      }
  }

}
```

### workunit的流转

在JobLauncher中调用source组件生成workunit然后生成hadoopjob去执行wu。

```java
package org.apache.gobblin.runtime;

public abstract class AbstractJobLauncher implements JobLauncher {
  @Override
  public void launchJob(JobListener jobListener) hrows JobException {
    //省略...
    Source<?, ?> source = this.jobContext.getSource();
    //1. 调用source组件生成workunit
    if (source instanceof WorkUnitStreamSource) {
      workUnitStream = ((WorkUnitStreamSource) source).getWorkunitStream(jobState);
    } else {
      workUnitStream = new BasicWorkUnitStream.Builder(source.getWorkunits(jobState)).build();
    }
    //省略...
    //2. Start the job and wait for it to finish
    // 见下方 MRJobLauncher$runWorkUnits 代码
    runWorkUnitStream(workUnitStream);
    //省略...
  }
}
```

```java
package org.apache.gobblin.runtime.mapreduce;

public class MRJobLauncher extends AbstractJobLauncher {
    protected void runWorkUnits(List<WorkUnit> workUnits) throws Exception {
      //省略...
      prepareHadoopJob(workUnits);
      //省略...
      this.job.waitForCompletion(true);
      //省略...
    }
}
  /**
   * Prepare the Hadoop MR job, including configuring the job and setting up the input/output paths.
   */
  private void prepareHadoopJob(List<WorkUnit> workUnits) throws IOException {
    TimingEvent mrJobSetupTimer = this.eventSubmitter.getTimingEvent(TimingEvent.RunJobTimings.MR_JOB_SETUP);

    // Add dependent jars/files
    addDependencies(this.job.getConfiguration());

    this.job.setJarByClass(MRJobLauncher.class);
    this.job.setMapperClass(TaskRunner.class);

    // The job is mapper-only
    this.job.setNumReduceTasks(0);

    this.job.setInputFormatClass(GobblinWorkUnitsInputFormat.class);
    this.job.setOutputFormatClass(GobblinOutputFormat.class);
    this.job.setMapOutputKeyClass(NullWritable.class);
    this.job.setMapOutputValueClass(NullWritable.class);

    // Set speculative execution

    this.job.setSpeculativeExecution(isSpeculativeExecutionEnabled(this.jobProps));

    this.job.getConfiguration().set("mapreduce.job.user.classpath.first", "true");

    // Job input path is where input work unit files are stored

    // Prepare job input
    // wu文件写入hdfs
    prepareJobInput(workUnits);
    // 设置inputPath
    FileInputFormat.addInputPath(this.job, this.jobInputPath);

    // Job output path is where serialized task states are stored
    FileOutputFormat.setOutputPath(this.job, this.jobOutputPath);

    // Serialize source state to a file which will be picked up by the mappers
    serializeJobState(this.fs, this.mrJobDir, this.conf, this.jobContext.getJobState(), this.job);

    if (this.jobProps.containsKey(ConfigurationKeys.MR_JOB_MAX_MAPPERS_KEY)) {
      GobblinWorkUnitsInputFormat.setMaxMappers(this.job,
          Integer.parseInt(this.jobProps.getProperty(ConfigurationKeys.MR_JOB_MAX_MAPPERS_KEY)));
    }

    mrJobSetupTimer.stop();
  }
```

### mapreduce相关

**map分块** hadoopjob的`GobblinWorkUnitsInputFormat`中的`getSplits`方法决定了map分块的方式和数量， 在`KafkaSource`中，`getWorkunits`方法是按照partition和`mr.job.max.mappers`参数配置来决定生成的wu的数量， 比如map数量为默认值100，topic的分区数量为2，那么就会生成100个multiworkunit，其中有两个是有job的，对应topic的两个分区，其余的job均为空，如果分区数量大于map数量，那么其中便会有一些multiworkunit去获取多个分区的数据。

```java
package org.apache.gobblin.source.extractor.extract.kafka;

public abstract class KafkaSource<S, D> extends EventBasedSource<S, D> {
  public List<WorkUnit> getWorkunits(SourceState state) {
    //省略...
    Map<String, List<WorkUnit>> workUnits = Maps.newConcurrentMap();
    //根据topic创建WorkUnitCreator
    for (KafkaTopic topic : topics) {
        threadPool.submit(
            new WorkUnitCreator(topic, state, Optional.fromNullable(topicSpecificStateMap.get(topic.getName())),
                workUnits));
      }
    //省略...
    //获取map数量
    int numOfMultiWorkunits =
          state.getPropAsInt(ConfigurationKeys.MR_JOB_MAX_MAPPERS_KEY, ConfigurationKeys.DEFAULT_MR_JOB_MAX_MAPPERS);
    //KafkaWorkUnitPacker是用来合并空的任务以及将workunit分配到numOfMultiWorkunits指定的数量的map上，见KafkaSingleLevelWorkUnitPacker
    List<WorkUnit> workUnitList = KafkaWorkUnitPacker.getInstance(this, state).pack(workUnits, numOfMultiWorkunits);
    //省略...
  }
  private class WorkUnitCreator implements Runnable {
    @Override
    public void run() {
      //省略...
      //生成workunit
      this.allTopicWorkUnits.put(this.topic.getName(),
          KafkaSource.this.getWorkUnitsForTopic(this.topic, this.state, this.topicSpecificState));
      //省略...
    }
  }

  private List<WorkUnit> getWorkUnitsForTopic(KafkaTopic topic, SourceState state, Optional<State> topicSpecificState) {
    Timer.Context context = this.metricContext.timer("isTopicQualifiedTimer").time();
    boolean topicQualified = isTopicQualified(topic);
    context.close();

    List<WorkUnit> workUnits = Lists.newArrayList();
    for (KafkaPartition partition : topic.getPartitions()) {
      WorkUnit workUnit = getWorkUnitForTopicPartition(partition, state, topicSpecificState);
      this.partitionsToBeProcessed.add(partition);
      if (workUnit != null) {

        // For disqualified topics, for each of its workunits set the high watermark to be the same
        // as the low watermark, so that it will be skipped.
        if (!topicQualified) {
          skipWorkUnit(workUnit);
        }
        workUnits.add(workUnit);
      }
    }
    return workUnits;
  }
}
```


# sqoop 简介及使用

## 1. 前言

Sqoop是用于在Hadoop和关系型数据库之间流转数据的工具。可以使用Sqoop从关系型数据库（RDBMS）比如Mysql或者Oracle导入数据到Hadoop分布式文件系统上，在Hadoop MapReduce上对数据做转换，然后导回到RDBMS上。 借助数据库的Schema描述信息，Sqoop自动实现了上述的很多过程，Sqoop使用MapReduce去导入和导出数据，这样可以提供并行操作以及提高容错能力。

## 2. Sqoop的Shell（命令行客户端）操作

```
usage: sqoop COMMAND [ARGS]

Available commands:
  codegen            生成Java代码
  create-hive-table  根据表结构生成hive表
  eval               执行SQL语句并显示结果
  export             导出HDFS目录到数据库表
  help               帮助
  import             从数据库导入数据到HDFS
  import-all-tables  导入数据库所有表到HDFS
  list-databases     列举所有的database
  list-tables        列举数据库中的所有表
  version            查看版本信息
```

### 2.1 sqoop import

#### 2.1.1 命令行客户端支持的命令参数

```
usage: sqoop import [GENERIC-ARGS] [TOOL-ARGS]

Common arguments:
   --connect <jdbc-uri>                                       Specify JDBC
                                                              connect
                                                              string
   --connection-manager <class-name>                          Specify
                                                              connection
                                                              manager
                                                              class name
   --connection-param-file <properties-file>                  Specify
                                                              connection
                                                              parameters
                                                              file
   --driver <class-name>                                      Manually
                                                              specify JDBC
                                                              driver class
                                                              to use
   --hadoop-home <hdir>                                       Override
                                                              $HADOOP_MAPR
                                                              ED_HOME_ARG
   --hadoop-mapred-home <dir>                                 Override
                                                              $HADOOP_MAPR
                                                              ED_HOME_ARG
   --help                                                     Print usage
                                                              instructions
   --metadata-transaction-isolation-level <isolationlevel>    Defines the
                                                              transaction
                                                              isolation
                                                              level for
                                                              metadata
                                                              queries. For
                                                              more details
                                                              check
                                                              java.sql.Con
                                                              nection
                                                              javadoc or
                                                              the JDBC
                                                              specificaito
                                                              n
   --oracle-escaping-disabled <boolean>                       Disable the
                                                              escaping
                                                              mechanism of
                                                              the
                                                              Oracle/OraOo
                                                              p connection
                                                              managers
-P                                                            Read
                                                              password
                                                              from console
   --password <password>                                      Set
                                                              authenticati
                                                              on password
   --password-alias <password-alias>                          Credential
                                                              provider
                                                              password
                                                              alias
   --password-file <password-file>                            Set
                                                              authenticati
                                                              on password
                                                              file path
   --relaxed-isolation                                        Use
                                                              read-uncommi
                                                              tted
                                                              isolation
                                                              for imports
   --skip-dist-cache                                          Skip copying
                                                              jars to
                                                              distributed
                                                              cache
   --temporary-rootdir <rootdir>                              Defines the
                                                              temporary
                                                              root
                                                              directory
                                                              for the
                                                              import
   --throw-on-error                                           Rethrow a
                                                              RuntimeExcep
                                                              tion on
                                                              error
                                                              occurred
                                                              during the
                                                              job
   --username <username>                                      Set
                                                              authenticati
                                                              on username
   --verbose                                                  Print more
                                                              information
                                                              while
                                                              working

Import control arguments:
   --append                                                   Imports data
                                                              in append
                                                              mode
   --as-avrodatafile                                          Imports data
                                                              to Avro data
                                                              files
   --as-parquetfile                                           Imports data
                                                              to Parquet
                                                              files
   --as-sequencefile                                          Imports data
                                                              to
                                                              SequenceFile
                                                              s
   --as-textfile                                              Imports data
                                                              as plain
                                                              text
                                                              (default)
   --autoreset-to-one-mapper                                  Reset the
                                                              number of
                                                              mappers to
                                                              one mapper
                                                              if no split
                                                              key
                                                              available
   --boundary-query <statement>                               Set boundary
                                                              query for
                                                              retrieving
                                                              max and min
                                                              value of the
                                                              primary key
   --columns <col,col,col...>                                 Columns to
                                                              import from
                                                              table
   --compression-codec <codec>                                Compression
                                                              codec to use
                                                              for import
   --delete-target-dir                                        Imports data
                                                              in delete
                                                              mode
   --direct                                                   Use direct
                                                              import fast
                                                              path
   --direct-split-size <n>                                    Split the
                                                              input stream
                                                              every 'n'
                                                              bytes when
                                                              importing in
                                                              direct mode
-e,--query <statement>                                        Import
                                                              results of
                                                              SQL
                                                              'statement'
   --fetch-size <n>                                           Set number
                                                              'n' of rows
                                                              to fetch
                                                              from the
                                                              database
                                                              when more
                                                              rows are
                                                              needed
   --inline-lob-limit <n>                                     Set the
                                                              maximum size
                                                              for an
                                                              inline LOB
-m,--num-mappers <n>                                          Use 'n' map
                                                              tasks to
                                                              import in
                                                              parallel
   --mapreduce-job-name <name>                                Set name for
                                                              generated
                                                              mapreduce
                                                              job
   --merge-key <column>                                       Key column
                                                              to use to
                                                              join results
   --split-by <column-name>                                   Column of
                                                              the table
                                                              used to
                                                              split work
                                                              units
   --split-limit <size>                                       Upper Limit
                                                              of rows per
                                                              split for
                                                              split
                                                              columns of
                                                              Date/Time/Ti
                                                              mestamp and
                                                              integer
                                                              types. For
                                                              date or
                                                              timestamp
                                                              fields it is
                                                              calculated
                                                              in seconds.
                                                              split-limit
                                                              should be
                                                              greater than
                                                              0
   --table <table-name>                                       Table to
                                                              read
   --target-dir <dir>                                         HDFS plain
                                                              table
                                                              destination
   --validate                                                 Validate the
                                                              copy using
                                                              the
                                                              configured
                                                              validator
   --validation-failurehandler <validation-failurehandler>    Fully
                                                              qualified
                                                              class name
                                                              for
                                                              ValidationFa
                                                              ilureHandler
   --validation-threshold <validation-threshold>              Fully
                                                              qualified
                                                              class name
                                                              for
                                                              ValidationTh
                                                              reshold
   --validator <validator>                                    Fully
                                                              qualified
                                                              class name
                                                              for the
                                                              Validator
   --warehouse-dir <dir>                                      HDFS parent
                                                              for table
                                                              destination
   --where <where clause>                                     WHERE clause
                                                              to use
                                                              during
                                                              import
-z,--compress                                                 Enable
                                                              compression

Incremental import arguments:
   --check-column <column>        Source column to check for incremental
                                  change
   --incremental <import-type>    Define an incremental import of type
                                  'append' or 'lastmodified'
   --last-value <value>           Last imported value in the incremental
                                  check column

Output line formatting arguments:
   --enclosed-by <char>               Sets a required field enclosing
                                      character
   --escaped-by <char>                Sets the escape character
   --fields-terminated-by <char>      Sets the field separator character
   --lines-terminated-by <char>       Sets the end-of-line character
   --mysql-delimiters                 Uses MySQL's default delimiter set:
                                      fields: ,  lines: \n  escaped-by: \
                                      optionally-enclosed-by: '
   --optionally-enclosed-by <char>    Sets a field enclosing character

Input parsing arguments:
   --input-enclosed-by <char>               Sets a required field encloser
   --input-escaped-by <char>                Sets the input escape
                                            character
   --input-fields-terminated-by <char>      Sets the input field separator
   --input-lines-terminated-by <char>       Sets the input end-of-line
                                            char
   --input-optionally-enclosed-by <char>    Sets a field enclosing
                                            character

Hive arguments:
   --create-hive-table                         Fail if the target hive
                                               table exists
   --external-table-dir <hdfs path>            Sets where the external
                                               table is in HDFS
   --hive-database <database-name>             Sets the database name to
                                               use when importing to hive
   --hive-delims-replacement <arg>             Replace Hive record \0x01
                                               and row delimiters (\n\r)
                                               from imported string fields
                                               with user-defined string
   --hive-drop-import-delims                   Drop Hive record \0x01 and
                                               row delimiters (\n\r) from
                                               imported string fields
   --hive-home <dir>                           Override $HIVE_HOME
   --hive-import                               Import tables into Hive
                                               (Uses Hive's default
                                               delimiters if none are
                                               set.)
   --hive-overwrite                            Overwrite existing data in
                                               the Hive table
   --hive-partition-key <partition-key>        Sets the partition key to
                                               use when importing to hive
   --hive-partition-value <partition-value>    Sets the partition value to
                                               use when importing to hive
   --hive-table <table-name>                   Sets the table name to use
                                               when importing to hive
   --map-column-hive <arg>                     Override mapping for
                                               specific column to hive
                                               types.

HBase arguments:
   --column-family <family>    Sets the target column family for the
                               import
   --hbase-bulkload            Enables HBase bulk loading
   --hbase-create-table        If specified, create missing HBase tables
   --hbase-row-key <col>       Specifies which input column to use as the
                               row key
   --hbase-table <table>       Import to <table> in HBase

HCatalog arguments:
   --hcatalog-database <arg>                        HCatalog database name
   --hcatalog-home <hdir>                           Override $HCAT_HOME
   --hcatalog-partition-keys <partition-key>        Sets the partition
                                                    keys to use when
                                                    importing to hive
   --hcatalog-partition-values <partition-value>    Sets the partition
                                                    values to use when
                                                    importing to hive
   --hcatalog-table <arg>                           HCatalog table name
   --hive-home <dir>                                Override $HIVE_HOME
   --hive-partition-key <partition-key>             Sets the partition key
                                                    to use when importing
                                                    to hive
   --hive-partition-value <partition-value>         Sets the partition
                                                    value to use when
                                                    importing to hive
   --map-column-hive <arg>                          Override mapping for
                                                    specific column to
                                                    hive types.

HCatalog import specific options:
   --create-hcatalog-table             Create HCatalog before import
   --drop-and-create-hcatalog-table    Drop and Create HCatalog before
                                       import
   --hcatalog-storage-stanza <arg>     HCatalog storage stanza for table
                                       creation

Accumulo arguments:
   --accumulo-batch-size <size>          Batch size in bytes
   --accumulo-column-family <family>     Sets the target column family for
                                         the import
   --accumulo-create-table               If specified, create missing
                                         Accumulo tables
   --accumulo-instance <instance>        Accumulo instance name.
   --accumulo-max-latency <latency>      Max write latency in milliseconds
   --accumulo-password <password>        Accumulo password.
   --accumulo-row-key <col>              Specifies which input column to
                                         use as the row key
   --accumulo-table <table>              Import to <table> in Accumulo
   --accumulo-user <user>                Accumulo user name.
   --accumulo-visibility <vis>           Visibility token to be applied to
                                         all rows imported
   --accumulo-zookeepers <zookeepers>    Comma-separated list of
                                         zookeepers (host:port)

Code generation arguments:
   --bindir <dir>                             Output directory for
                                              compiled objects
   --class-name <name>                        Sets the generated class
                                              name. This overrides
                                              --package-name. When
                                              combined with --jar-file,
                                              sets the input class.
   --escape-mapping-column-names <boolean>    Disable special characters
                                              escaping in column names
   --input-null-non-string <null-str>         Input null non-string
                                              representation
   --input-null-string <null-str>             Input null string
                                              representation
   --jar-file <file>                          Disable code generation; use
                                              specified jar
   --map-column-java <arg>                    Override mapping for
                                              specific columns to java
                                              types
   --null-non-string <null-str>               Null non-string
                                              representation
   --null-string <null-str>                   Null string representation
   --outdir <dir>                             Output directory for
                                              generated code
   --package-name <name>                      Put auto-generated classes
                                              in this package

Generic Hadoop command-line arguments:
(must preceed any tool-specific arguments)
Generic options supported are
-conf <configuration file>     specify an application configuration file
-D <property=value>            use value for given property
-fs <local|namenode:port>      specify a namenode
-jt <local|resourcemanager:port>    specify a ResourceManager
-files <comma separated list of files>    specify comma separated files to be copied to the map reduce cluster
-libjars <comma separated list of jars>    specify comma separated jar files to include in the classpath.
-archives <comma separated list of archives>    specify comma separated archives to be unarchived on the compute machines.

The general command line syntax is
bin/hadoop command [genericOptions] [commandOptions]


At minimum, you must specify --connect and --table
Arguments to mysqldump and other subprograms may be supplied
after a '--' on the command line.
```

#### 2.1.2 公共参数

`--connect <jdbc-url>` 制定JDBC连接串

`--connection-manager <class-name>` 指定连接管理类名

`--driver <class-name>` 指定JDBC驱动类

`--hadoop-mapred-home <dir>` 覆盖$HADOOP\_MAPRED\_HOME

`--help` 帮助

`--password-file` 指定密码文件

`-P` 从命令行读取密码

`--password <password>` 指定密码

`--username <username>` 指定用户名

`--verbose` 显示更丰富的日志

`--connection-param-file <filename>` 连接参数的可选配置文件

#### 2.1.3 连接一个数据库

使用`--conenct`来连接数据库\
`$ sqoop import --connect jdbc:mysql://database.example.com/employees`

这个连接串会连接database.example.com服务器上的employees数据库。必须要注意如果你准备在分布式Hadoop集群使用Sqoop导入数据，你不能使用`localhost`作为Url来连接数据源，因为这个连接串会被应用于你的MapReduce集群的任务节点上。\
指定数据库连接后，需要提供数据库访问的用户名和密码，密码有多种设定方式，显式的指定密码是不安全的

```bash
$ sqoop import --connect jdbc:mysql://database.example.com/employees \
    --username venkatesh --password-file ${user.home}/.password
```

Sqoop内置了MySql的驱动，对于其他的，需要自己安装驱动，将驱动文件放到`$SQOOP_HOME/lib`目录下，然后使用`--driver`参数指定驱动的全类名

```bash
$ sqoop import --driver com.microsoft.jdbc.sqlserver.SQLServerDriver \
    --connect <connect-string> ...
```

当使用JDBC连接一个数据库时，也可以使用`--connection-param-file`参数来通过一个配置文件指定额外的参数，这个文件会被作为标准的Java配置来解析并传递给驱动来创建连接。

#### 2.1.4 Import 配置参数

`--append` 追加数据到HDFS中已有的数据文件

`--as-avrodatafile` 导入数据为Avro格式

`--as-sequencefile` 导入数据为SqeuqnceFile

`--as-textfile` 导入数据格式为textfile(默认)

`--as-parquetfile` 导入数据为Parquet格式

`--boundary-query <statement>` 覆盖创建分割时的边界查询SQL

`--columns <col,col,col…>` 指定导入列

`--delete-target-dir` 如果存在，删除导入的target目录

`--direct` 如果数据源支持，使用direct模式导入

`-m,--num-mappers <n>` 指定并行导入数据的map数量(正整数)，默认为4个

`-e,--query <statement>` 导入查询语句得到的数据

`--split-by <column-name>` 指定分割split的字段

`--table < table-name >` 制定数据库中的表名

`--target-dir < dir >` 目标HDFS路径

`--warehouse-dir <dir>` 表目标位置父路径

`--where <where clause>` 导入的过滤条件，即sql语句的where条件

`--null-string < null-string >` 源表中为null的记录导入为string类型时显示为null-string，默认显示为”null”

`--null-non-string <null-string>` 源表中为null的记录导入为非string类型时显示为null-string，默认显示为”null”

### 2.2 sqoop export

#### 2.2.1 命令行客户端支持的命令参数

```
usage: sqoop export [GENERIC-ARGS] [TOOL-ARGS]

Common arguments:
   --connect <jdbc-uri>                                       Specify JDBC
                                                              connect
                                                              string
   --connection-manager <class-name>                          Specify
                                                              connection
                                                              manager
                                                              class name
   --connection-param-file <properties-file>                  Specify
                                                              connection
                                                              parameters
                                                              file
   --driver <class-name>                                      Manually
                                                              specify JDBC
                                                              driver class
                                                              to use
   --hadoop-home <hdir>                                       Override
                                                              $HADOOP_MAPR
                                                              ED_HOME_ARG
   --hadoop-mapred-home <dir>                                 Override
                                                              $HADOOP_MAPR
                                                              ED_HOME_ARG
   --help                                                     Print usage
                                                              instructions
   --metadata-transaction-isolation-level <isolationlevel>    Defines the
                                                              transaction
                                                              isolation
                                                              level for
                                                              metadata
                                                              queries. For
                                                              more details
                                                              check
                                                              java.sql.Con
                                                              nection
                                                              javadoc or
                                                              the JDBC
                                                              specificaito
                                                              n
   --oracle-escaping-disabled <boolean>                       Disable the
                                                              escaping
                                                              mechanism of
                                                              the
                                                              Oracle/OraOo
                                                              p connection
                                                              managers
-P                                                            Read
                                                              password
                                                              from console
   --password <password>                                      Set
                                                              authenticati
                                                              on password
   --password-alias <password-alias>                          Credential
                                                              provider
                                                              password
                                                              alias
   --password-file <password-file>                            Set
                                                              authenticati
                                                              on password
                                                              file path
   --relaxed-isolation                                        Use
                                                              read-uncommi
                                                              tted
                                                              isolation
                                                              for imports
   --skip-dist-cache                                          Skip copying
                                                              jars to
                                                              distributed
                                                              cache
   --temporary-rootdir <rootdir>                              Defines the
                                                              temporary
                                                              root
                                                              directory
                                                              for the
                                                              import
   --throw-on-error                                           Rethrow a
                                                              RuntimeExcep
                                                              tion on
                                                              error
                                                              occurred
                                                              during the
                                                              job
   --username <username>                                      Set
                                                              authenticati
                                                              on username
   --verbose                                                  Print more
                                                              information
                                                              while
                                                              working

Export control arguments:
   --batch                                                    Indicates
                                                              underlying
                                                              statements
                                                              to be
                                                              executed in
                                                              batch mode
   --call <arg>                                               Populate the
                                                              table using
                                                              this stored
                                                              procedure
                                                              (one call
                                                              per row)
   --clear-staging-table                                      Indicates
                                                              that any
                                                              data in
                                                              staging
                                                              table can be
                                                              deleted
   --columns <col,col,col...>                                 Columns to
                                                              export to
                                                              table
   --direct                                                   Use direct
                                                              export fast
                                                              path
   --export-dir <dir>                                         HDFS source
                                                              path for the
                                                              export
-m,--num-mappers <n>                                          Use 'n' map
                                                              tasks to
                                                              export in
                                                              parallel
   --mapreduce-job-name <name>                                Set name for
                                                              generated
                                                              mapreduce
                                                              job
   --staging-table <table-name>                               Intermediate
                                                              staging
                                                              table
   --table <table-name>                                       Table to
                                                              populate
   --update-key <key>                                         Update
                                                              records by
                                                              specified
                                                              key column
   --update-mode <mode>                                       Specifies
                                                              how updates
                                                              are
                                                              performed
                                                              when new
                                                              rows are
                                                              found with
                                                              non-matching
                                                              keys in
                                                              database
   --validate                                                 Validate the
                                                              copy using
                                                              the
                                                              configured
                                                              validator
   --validation-failurehandler <validation-failurehandler>    Fully
                                                              qualified
                                                              class name
                                                              for
                                                              ValidationFa
                                                              ilureHandler
   --validation-threshold <validation-threshold>              Fully
                                                              qualified
                                                              class name
                                                              for
                                                              ValidationTh
                                                              reshold
   --validator <validator>                                    Fully
                                                              qualified
                                                              class name
                                                              for the
                                                              Validator

Input parsing arguments:
   --input-enclosed-by <char>               Sets a required field encloser
   --input-escaped-by <char>                Sets the input escape
                                            character
   --input-fields-terminated-by <char>      Sets the input field separator
   --input-lines-terminated-by <char>       Sets the input end-of-line
                                            char
   --input-optionally-enclosed-by <char>    Sets a field enclosing
                                            character

Output line formatting arguments:
   --enclosed-by <char>               Sets a required field enclosing
                                      character
   --escaped-by <char>                Sets the escape character
   --fields-terminated-by <char>      Sets the field separator character
   --lines-terminated-by <char>       Sets the end-of-line character
   --mysql-delimiters                 Uses MySQL's default delimiter set:
                                      fields: ,  lines: \n  escaped-by: \
                                      optionally-enclosed-by: '
   --optionally-enclosed-by <char>    Sets a field enclosing character

Code generation arguments:
   --bindir <dir>                             Output directory for
                                              compiled objects
   --class-name <name>                        Sets the generated class
                                              name. This overrides
                                              --package-name. When
                                              combined with --jar-file,
                                              sets the input class.
   --escape-mapping-column-names <boolean>    Disable special characters
                                              escaping in column names
   --input-null-non-string <null-str>         Input null non-string
                                              representation
   --input-null-string <null-str>             Input null string
                                              representation
   --jar-file <file>                          Disable code generation; use
                                              specified jar
   --map-column-java <arg>                    Override mapping for
                                              specific columns to java
                                              types
   --null-non-string <null-str>               Null non-string
                                              representation
   --null-string <null-str>                   Null string representation
   --outdir <dir>                             Output directory for
                                              generated code
   --package-name <name>                      Put auto-generated classes
                                              in this package

HCatalog arguments:
   --hcatalog-database <arg>                        HCatalog database name
   --hcatalog-home <hdir>                           Override $HCAT_HOME
   --hcatalog-partition-keys <partition-key>        Sets the partition
                                                    keys to use when
                                                    importing to hive
   --hcatalog-partition-values <partition-value>    Sets the partition
                                                    values to use when
                                                    importing to hive
   --hcatalog-table <arg>                           HCatalog table name
   --hive-home <dir>                                Override $HIVE_HOME
   --hive-partition-key <partition-key>             Sets the partition key
                                                    to use when importing
                                                    to hive
   --hive-partition-value <partition-value>         Sets the partition
                                                    value to use when
                                                    importing to hive
   --map-column-hive <arg>                          Override mapping for
                                                    specific column to
                                                    hive types.

Generic Hadoop command-line arguments:
(must preceed any tool-specific arguments)
Generic options supported are
-conf <configuration file>     specify an application configuration file
-D <property=value>            use value for given property
-fs <local|namenode:port>      specify a namenode
-jt <local|resourcemanager:port>    specify a ResourceManager
-files <comma separated list of files>    specify comma separated files to be copied to the map reduce cluster
-libjars <comma separated list of jars>    specify comma separated jar files to include in the classpath.
-archives <comma separated list of archives>    specify comma separated archives to be unarchived on the compute machines.

The general command line syntax is
bin/hadoop command [genericOptions] [commandOptions]


At minimum, you must specify --connect, --export-dir, and --table
```

#### 2.2.2 公共参数

`--connect <jdbc-url>` 制定JDBC连接串

`--connection-manager <class-name>` 指定连接管理类名

`--driver <class-name>` 指定JDBC驱动类

`--hadoop-mapred-home <dir>` 覆盖$HADOOP\_MAPRED\_HOME

`--help` 帮助

`--password-file` 指定密码文件

`-P` 从命令行读取密码

`--password <password>` 指定密码

`--username <username>` 指定用户名

`--verbose` 显示更丰富的日志

`--connection-param-file <filename>` 连接参数的可选配置文件

#### 2.2.3 Export 配置参数

`--columns < col,col,col… >` 指定导出字段

`--direct` 使用direct模式

`--export-dir <dir>` 导出HDFS文件路径

`-m,--num-mappers <n>` 指定并行导出数据的map个数，默认为4个

`--table <table-name>` 指定数据库中的表名

`--update-key <col-name>` update模式下更新的字段，多个字段使用逗号分割

`--update-mode <mode>` 当一条数据找不到对应的记录时的操作，默认为updateonly即忽略插入操作，可以设置为allowinsert即遇到新记录会插入

`--input-null-string <null-string>` string类型字段表示null值的字符

`--input-null-non-string <null-string>` 非string类型字段表示null值的字符 `--staging-table < staging-table-name >` 在最终导出到目标表之前临时存储这些记录的表 `--clear-staging-table` 启用清除staging-table表中的记录

`--export-dir`参数，以及`--table`或者`--call`是必须指定的参数。因为这三个参数分别用来指定导出数据集，以及数据导出后的去处，既可以是导出到某张表，也可以是对每一条导出记录调用存储过程。有些数据库例如Mysql可以支持direct模式导出（在mysql中就是调用mysqldump导出数据）。

由于Sqoop将导出过程切分成了多个，那么就会有可能某个导出任务失败而导致只有部分数据提交到了导出数据库中。当失败job重试时，就有可能会出现数据重复，或者导出数据冲突等情况发生。这时可以指定一个--staging-table参数来避免这种情况的发生。导出的数据首先会缓存在该表中，最后等job执行成功后会将该表中的数据移动到最终目标表中。

### 2.3 其他配置

#### 2.3.1 使用HCatalog

`--hcatalog-database` 指定hcatalog表的数据库名

`--hcatalog-table` 指定表名

`--hcatalog-home` HCatalog安装目录

#### 2.3.2 Hive配置

`--hive-home` 指定hive home目录

`--hive-partition-key` 指定写入hive时的分区key

`--hive-partition-value` 指定写入hive时的分区value


# hbase

[我终于看懂了HBase，太不容易了...](https://segmentfault.com/a/1190000022820965)


# gobblin

## 概念

```
1、source
2、extractor
3、convertor
4、quality checker
5、writer
6、publisher
```

* Source主要负责将源数据整合到一系列workunits中，并指出对应的extractor是什么。这有点类似于Hadoop的InputFormat。
* Extractor则通过workunit指定数据源的信息，例如kafka，指出topic中每个partition的起始offset，用于本次抽取使用。Gobblin使用了watermark的概念，记录每次抽取的数据的起始位置信息。
* Converter顾名思义是转换器的意思，即对抽取的数据进行一些过滤、转换操作，例如将byte arrays 或者JSON格式的数据转换为需要输出的格式。转换操作也可以将一条数据映射成0条或多条数据（类似于flatmap操作）。
* Quality Checker即质量检测器，有2中类型的checker：record-level和task-level的策略。通过手动策略或可选的策略，将被check的数据输出到外部文件或者给出warning。
* Writer就是把导出的数据写出，但是这里并不是直接写出到output file，而是写到一个缓冲路径（ staging directory）中。当所有的数据被写完后，才写到输出路径以便被publisher发布。Sink的路径可以包括HDFS或者kafka或者S3中，而格式可以是Avro,Parquet,或者CSV格式。同时Writer也可是根据时间戳，将输出的文件输出到按照“小时”或者“天”命名的目录中。
* Publisher就是根据writer写出的路径，将数据输出到最终的路径。同时其提供2种提交机制：完全提交和部分提交；如果是完全提交，则需要等到task成功后才pub，如果是部分提交模式，则当task失败时，有部分在staging directory的数据已经被pub到输出路径了。

> Source：每个partition中起始offset都通过Source生成到workunit中；同时，从state中获取上一次抽取结尾的offset信息，以便判断本次Job执行的起始offset。 Extractor：Extractor会逐个抽取partition的数据，抽取完成一个后，会将末尾offset信息存到状态存储中。 Converter：LinkedIn内部的Kafka集群主要存储Avro格式的数据，并对此进行一些过滤和转换。 Quality Checker：LinkedIn中数据都会包含一个时间戳，以便决定放到哪个“小时”目录和“天”目录。对于没有时间戳的数据，则会根据record-level的策略将这些数据写到外部文件中。 Writer and Publisher：内部使用基于时间的writer和基于时间的publisher去写并pub数据。 [数据采集框架Gobblin简介](https://cloud.tencent.com/developer/article/1351988)

## 架构

![架构](https://ask.qcloudimg.com/http-save/yehe-2725853/v74nkx004r.jpeg?imageView2/2/w/1620) ![流程](https://upload-images.jianshu.io/upload_images/6504531-275a78b3b2639564.jpg) ![流程2](https://ask.qcloudimg.com/http-save/yehe-2725853/mlw4m84zp1.jpeg?imageView2/2/w/1620) ![自制](https://3585898866-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LcehtLiMKFYRXXJ4Dzh%2F-Lcei1h6xvrd1a6Mrkgg%2F-Lcei4JZsaLpSeKFwmXA%2Fgobblin-kafka.jpg?generation=1555494890494515\&alt=media)

## Task

A physical unit of execution for a Gobblin org.apache.gobblin.source.workunit.WorkUnit. Each task is executed by a single thread in a thread pool managed by the TaskExecutor and each Fork of the task is executed in a separate thread pool also managed by the TaskExecutor. Each Task consists of the following steps: Extracting, converting, and forking the source schema. Extracting, converting, doing row-level quality checking, and forking each data record. Putting each forked record into the record queue managed by each Fork. Committing output data of each Fork once all Forks finish. Cleaning up and exiting. Each Fork consists of the following steps: Getting the next record off the record queue. Converting the record and doing row-level quality checking if applicable. Writing the record out if it passes the quality checking. Cleaning up and exiting once all the records have been processed.


# sqoop源码解析

## 自定义hive配置

### 问题

sqoop操作hive时，会读取hive的配置文件，但是如果想要通过参数动态的覆盖默认的hive配置该如何？

### 探讨

首先看这个类`org.apache.sqoop.hive.HiveConfig`

```java
//class: org.apache.sqoop.hive.HiveConfig
//line: 42
public static Configuration getHiveConf(Configuration conf) throws IOException {
    //...
      Class HiveConfClass = Class.forName(HIVE_CONF_CLASS);
      return ((Configuration)(HiveConfClass.getConstructor(Configuration.class, Class.class)
          .newInstance(conf, Configuration.class)));
    //...
  }
```

这段代码获取的是`org.apache.hadoop.hive.conf.HiveConf`类的实例。在HiveConf中有这段代码

```java
// class: org.apache.hadoop.hive.conf.HiveConf
// line: 2935
public static Map<String, String> getConfSystemProperties() {
    Map<String, String> systemProperties = new HashMap<String, String>();
    for (ConfVars oneVar : ConfVars.values()) {
      if (System.getProperty(oneVar.varname) != null) {
        if (System.getProperty(oneVar.varname).length() > 0) {
          systemProperties.put(oneVar.varname, System.getProperty(oneVar.varname));
        }
      }
    }
    return systemProperties;
  }
```

在sqoop`getHiveConf`方法中去实例化HiveConf类时，会读取hive的配置，其中会调用到上述的`getConfSystemProperties`方法，该方法会读取系统变量中的参数来覆盖默认的配置。这些可用的参数可以在`ConfVars`枚举类中看到。

### 解决

因此如果想要覆盖默认的hive配置只需要添加系统变量即可，例如

```bash
java \
-D'hive.metastore.uris=thrift://service:90831' \
-cp libs \
org.apache.sqoop.Sqoop args
```

也可以通过修改sqoop配置文件的方式

## 获取sqoop导入导出记录数量

### 问题

如何获取sqoop导入或者导出数据条数，以进行例如上报日志等操作？

### 探讨

mapreduce有一个计数器的东西，其源码已经实现了对map输入输出的计数。

```java
//class: org.apache.sqoop.config.ConfigurationHelper
//line: 73
/**
* @return the number of mapper output records from a job using its counters.
*/
public static long getNumMapOutputRecords(Job job)
    throws IOException, InterruptedException {
return job.getCounters().findCounter(
    ConfigurationConstants.COUNTER_GROUP_MAPRED_TASK_COUNTERS,
    ConfigurationConstants.COUNTER_MAP_OUTPUT_RECORDS).getValue();
}

/**
* @return the number of mapper input records from a job using its counters.
*/
public static long getNumMapInputRecords(Job job)
    throws IOException, InterruptedException {
return job.getCounters().findCounter(
        ConfigurationConstants.COUNTER_GROUP_MAPRED_TASK_COUNTERS,
        ConfigurationConstants.COUNTER_MAP_INPUT_RECORDS).getValue();
}
```

对于导入操作来说，数据传入map并从map按照一条条的数据写出到hdfs中，因此`getNumMapOutputRecords`便是数据导入的条数，\
对于导出操作来说，传入map的数据是hdfs中的数据，一般情况下便是一条条的数据，因此`getNumMapInputRecords`便是数据导出的条数。

而对于一些特殊格式的数据，sqoop重写了计数器的计数逻辑。

```java
//class: org.apache.sqoop.mapreduce.mainframe.MainframeDatasetImportMapper
private long numberOfRecords;

//line: 50
public void map(LongWritable key,  SqoopRecord val, Context context)
    throws IOException, InterruptedException {
String dataset = inputSplit.getCurrentDataset();
outkey.set(val.toString());
numberOfRecords++;
mos.write(outkey, NullWritable.get(), dataset);
}
//line: 68
@Override
protected void cleanup(Context context)
    throws IOException, InterruptedException {
super.cleanup(context);
mos.close();
context.getCounter(
    ConfigurationConstants.COUNTER_GROUP_MAPRED_TASK_COUNTERS,
    ConfigurationConstants.COUNTER_MAP_OUTPUT_RECORDS)
    .increment(numberOfRecords);
}
```

在map的cleanup方法中进行了计数

### 解决

因此只需要从计数器获取数据即可

```java
//导出(export)操作条数
long inputRecords = ConfigurationHelper.getNumMapInputRecords(job);

//导入(import)操作条数
long outputRecords = ConfigurationHelper.getNumMapOutputRecords(job);
```


# hadoop map reduce

## hadoop 运行jar

[hadoop如何分发本地的jar文件](https://blog.csdn.net/xiaolang85/article/details/8618705)\
执行`hadoop jar abc.jar arg0 arg1 ...`实际上时执行了`java org.apache.hadoop.util.RunJar abc.jar arg0 arg1 ...`\
在RunJar中，会读取abc.jar文件，然后尝试从manifest中提取"Main-Class"作为mainClass，如果manifest中没有指定，则把abc.jar之后的下一个参数当成mainClass。比如`hadoop jar packagename.ClassName arg0 arg1`

当然你也可以像使用`java -cp dependency1.jar:dependency2.jar:main.jar packagename.ClassName`这样的方式来运行

```bash
export HADOOP_CALSSPATH=dependency1.jar:dependency2.jar:main.jar
hadoop packagename.ClassName arg0 arg1
```

## 依赖管理

1. 使用hadoop 命令运行jar包时，会自动携带hadoop的依赖，因此不需要将这些依赖打入jar包。
2. 对于hadoop之外的其他依赖包有多种方式来配置。（[【转】Mapreduce部署与第三方依赖包管理 - Mr.Ming2 - 博客园](https://www.cnblogs.com/Dhouse/p/6595344.html)）
   1. 将你的job打包成一个独立的jar包，依赖全部打包到jar包之中，这是最简单的方式，但是如果依赖太多则会导致jar包太大。
   2. 配置`export HADOOP_CLASSPATH=*.jar`来指定依赖的jar，但是这样的话需要在每个节点机器上均要同步部署这些依赖包以及配置环境变量。
   3. 使用`libjars`参数。可以在使用“hadoo jar”命令时，向启动的job传递“libjars”选项参数，同时配合ToolRunner工具来解析参数并运行Job。 libjars中需要指定job依赖的所有的jar全路径，并且这些jars必须在当前本地文件系统中(并非集群中都需要有此jars)

### libjars的配置

[MapReduce程序调用第三方Jar包的方式 - CSDN博客](https://blog.csdn.net/lizhang310/article/details/24399403)\
[解决Hadoop jar *.jar 主类名 -libjars* .jar \*.jar arg1 arg2 ... 中-libjars的方法 - CSDN博客](https://blog.csdn.net/fffpppccc/article/details/46721991)

首先你的程序要使用ToolRunner的方式来运行。

> adoop自带了一些辅助类。GenericOptionsParser是一个类，用来解释常用的Hadoop命令行选项，并根据需要，为Configuration对象设置相应的取值。通常不直接使用GenericOptionsParser，更方便的方式是：实现Tool接口，通过ToolRunner来运行应用程序，ToolRunner内部调用GenericOptionsParser。

```java
public class WordCount extends Configured implements Tool {
    @Override
    public int run(String[] arg0) throws Exception {
        Job job = new Job(getConf(), "word count");
        // 略...
        System.exit(job.waitForCompletion(true) ? 0 : 1);
        return 0;
    }
    public static void main(String[] args) throws Exception {
        int res = ToolRunner.run(new Configuration(), new WordCount(), args);
        System.exit(res);
    }
}
```

使用了这种方式后就可以在你的启动命令中通过指定`-libjars`来指定第三方依赖了，hadoop会将依赖上传到hdfs上共享使用。 启动命令:`hadoop jar main.jar packagename.ClassName -libjars a.jar,b.jar arg0 arg1`\
**注意的是**`-libjars a.jar,b.jar`**是放在指定的类名后面和要传入的参数前面。**

## 完整的示例代码

```java
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import org.zeroturnaround.exec.ProcessExecutor;

import java.io.IOException;
import java.util.Arrays;
import java.util.StringTokenizer;
import java.util.concurrent.TimeoutException;

/**
 * @author Rizon
 * @date 2018/9/13
 */
public class WordCount extends Configured implements Tool {

    public static class TokenizerMapper
            extends Mapper<Object, Text, Text, IntWritable> {

        private final static IntWritable one = new IntWritable(1);
        private Text word = new Text();

        @Override
        public void map(Object key, Text value, Context context
        ) throws IOException, InterruptedException {
            try {
                System.out.println(new ProcessExecutor()
                        .redirectOutput(System.out)
                        .readOutput(true)
                        .command("ls", "./")
                        .execute().outputUTF8());
            } catch (TimeoutException e) {
                e.printStackTrace();
            }

            StringTokenizer itr = new StringTokenizer(value.toString());
            while (itr.hasMoreTokens()) {
                word.set(itr.nextToken());
                context.write(word, one);
            }
        }
    }

    public static class IntSumReducer
            extends Reducer<Text, IntWritable, Text, IntWritable> {
        private IntWritable result = new IntWritable();

        @Override
        public void reduce(Text key, Iterable<IntWritable> values,
                           Context context
        ) throws IOException, InterruptedException {
            int sum = 0;
            for (IntWritable val : values) {
                sum += val.get();
            }
            result.set(sum);
            context.write(key, result);
        }
    }

    public int run(String[] args) throws Exception {
        System.out.println("====="+getConf().get("tmpjars"));
        Job job = Job.getInstance(getConf(), "word count");
        job.setJarByClass(WordCount.class);
        job.setMapperClass(TokenizerMapper.class);
        job.setCombinerClass(IntSumReducer.class);
        job.setReducerClass(IntSumReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);
        FileInputFormat.addInputPath(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));
        System.exit(job.waitForCompletion(true) ? 0 : 1);
        return 0;
    }

    public static void main(String[] args) throws Exception {
        try {
            System.out.println(new ProcessExecutor()
                    .redirectOutput(System.out)
                    .readOutput(true)
                    .command("ls", "./")
                    .execute().outputUTF8());
        } catch (TimeoutException e) {
            e.printStackTrace();
        }

        System.out.println(Arrays.asList(args));
        int res = ToolRunner.run(new Configuration(), new WordCount(), args);
        System.exit(res);
    }
}
```


# 大数据 学习札记

* [大数据 学习札记](#大数据-学习札记)
  * [参考资料](#参考资料)
  * [hadoop](#hadoop)
    * [参考](#参考)
    * [概念](#概念)
    * [使用](#使用)
  * [hue](#hue)
  * [hive](#hive)
  * [kylin](#kylin)
  * [presto](#presto)
    * [安装](#安装)
    * [presto 连接hive](#presto-连接hive)
    * [presto webui - yanagishima](#presto-webui---yanagishima)
  * [sqoop](#sqoop)
  * [Ambari](#ambari)

## 参考资料

[Hadoop教程™](https://www.yiibai.com/hadoop/)

[MapReduce(一) mapreduce基础入门 - 凌-风 - 博客园](https://www.cnblogs.com/liuwei6/p/6708116.html)

[presto、druid、sparkSQL、kylin的对比分析，如性能、架构等，有什么异同？ - 知乎](https://www.zhihu.com/question/41541395?sort=created)

[环境搭建 Hadoop+Hive(orcfile格式)+Presto实现大数据存储查询一 - 翟中龙 - 博客园](https://www.cnblogs.com/nyzhai/p/6102423.html)

## hadoop

### 参考

[Hadoop\_常用存储与压缩格式](https://www.cnblogs.com/eRrsr/p/6098454.html) [hadoop 四种压缩格式](https://blog.csdn.net/lifuxiangcaohui/article/details/52623660)

### 概念

> hadoop 的四大组件： HDFS：分布式存储系统 MapReduce：分布式计算系统 YARN： hadoop 的资源调度系统 Common： 以上三大组件的底层支撑组件，主要提供基础工具包和 RPC 框架等

> Namenode：是整个文件系统的管理节点。它维护着1.整个文件系统的文件目录树，2.文件/目录的元信息和每个文件对应的数据块列表。3.接收用户的操作请求。 Datanode：提供真实文件数据的存储服务。

> 以Apache Hadoop为主导的大数据技术的出现，使得中小型公司对于大数据的存储与处理也拥有了武器。目前Hadoop有不少发行版：华为发行版 收费 、Intel发行版 收费 、Cloudera发行版（Cloudera’s Distribution Including Apache Hadoop，简称 CDH ） 免费 、Hortonworks发行版（Hortonworks Data Platform，简称 HDP ） 免费 等，所有这些发行版均是基于Apache Hadoop社区版衍生出来的。

### 使用

操作hdfs系统可以使用hadoop fs 也可以使用 hdfs dfs ,两者效果一样。(hadoop dfs命令已不再建议使用)

[hadoop中的hadoop和hdfs和yarn命令](https://www.cnblogs.com/lzfhope/p/6952869.html) [Hadoop Hdfs常用命令](https://blog.csdn.net/sunshingheavy/article/details/53227581)

```bash
# [HDFS基本命令的使用](https://blog.csdn.net/afafawfaf/article/details/80254989)
hadoop fs -ls  显示当前目录结构，-ls -R 递归显示目录结构
hadoop fs -mkdir  创建目录
hadoop fs -rm   删除文件，-rm -R 递归删除目录和文件
hadoop fs -put  [localsrc] [dst]  从本地加载文件到HDFS
hadoop fs -get  [dst] [localsrc]  从HDFS导出文件到本地
hadoop fs -copyFromLocal [localsrc] [dst]  从本地加载文件到HDFS，与put一致
hadoop fs -copyToLocal [dst] [localsrc]  从HDFS导出文件到本地，与get一致
hadoop fs -test -e  检测目录和文件是否存在，存在返回值$?为0，不存在返回1
hadoop fs -text  查看文件内容
hadoop fs -du  统计目录下各文件大小，单位字节。-du -s 汇总目录下文件大小，-du -h 显示单位
hadoop fs -tail  显示文件末尾
hadoop fs -cp [src] [dst] 从源目录复制文件到目标目录
hadoop fs -mv [src] [dst] 从源目录移动文件到目标目录
```

## hue

***

## hive

[Hive快速入门 - CSDN博客](https://blog.csdn.net/trigl/article/details/61418571)

***

## kylin

[Kylin的cube模型 - Treant - 博客园](https://www.cnblogs.com/en-heng/p/5239311.html) [Kylin介绍 （很有用） - 一片黑 - 博客园](https://www.cnblogs.com/honey01/p/8351145.html) [Kylin的Hierarchies，Derived维度方面配置优化 - CSDN博客](https://blog.csdn.net/jiangshouzhuang/article/details/51286150)

***

## presto

> 实际业务中用Presto的原因
>
> 1. 第一点也是最重要的一点跨库和跨数据源，什么概念？跨库：mysql中有A库a表，B库b表，用Presto SQL可以实现a,b两表的join操作。跨数据源:mysql中有A库a表，oracle中有B库b表，用Presto SQL可以实现a,b两表的join操作。
> 2. Presto SQL和传统SQL大同小异，基本对于熟悉SQL的人员0成本进入
> 3. 数据量支持GB到PB字节
> 4. 集群部署，可水平扩展增强大的Presto SQL查询速度
> 5. 客户端可以是PHP，JAVA, Python, 命令行等等，这样可以作为一个平台级的中间件给团队提供支撑
> 6. Presto是JAVA书写而成，我是JAVA出身，对报错或者配置更熟悉
>
> EN官网:<https://prestodb.io/> CN官网:<http://prestodb-china.com/> GitHub:<https://github.com/prestodb/presto>

### 安装

Ambari安装presto [Ambari安装Presto(离线)](https://blog.csdn.net/wangpei1949/article/details/79952539) [ambari-presto-service 官方文档](https://prestodb.io/ambari-presto-service/) [github/ambari-presto-service](https://github.com/prestodb/ambari-presto-service/releases)

[Ambari界面Presto重启报错ERROR:presto\_client:Error connecting to presto server at: localhost:8285](https://blog.csdn.net/ZhouyuanLinli/article/details/77970542)

### presto 连接hive

[4.2. Hive连接器 — Presto 0.100 Documentation](http://prestodb-china.com/docs/current/connector/hive.html)

### presto webui - yanagishima

yanagishima可以连接hive和presto，搭建简单，功能也可以满足基本需要，github也一直在维护，推荐。 <https://github.com/yanagishima/yanagishima>

***

## sqoop

[\[总结\]----Sqoop 几种导入导出模式](https://blog.csdn.net/james__tao/article/details/78106396) [使用Sqoop从MySQL导入数据到Hive和HBase](https://www.cnblogs.com/wgp13x/p/5028220.html) [学习Hadoop第二十二课（Sqoop---数据迁移工具）](https://blog.csdn.net/anaitudou/article/details/80931994) [Sqoop 加载数据到ORC表 - CSDN博客](https://blog.csdn.net/Post_Yuan/article/details/64444724) [Sqoop将数据导入到hive时，数据错位的问题解决](https://www.2cto.com/database/201803/733896.html) [Sqoop import as OrC file](https://stackoverflow.com/questions/29978522/sqoop-import-as-orc-file) [sqoop指定多个字段导入类型 - CSDN博客](https://blog.csdn.net/wisdom_c_1010/article/details/78841196)

```bash
sqoop import --connect jdbc:mysql://localhost:3306/sqoop --username root --password root --table Student --direct
# 导入mysql表添加 --direct 参数速度更快，执行的是mysq自带的导出功能


#压缩
sqoop import \
-m 1 \
--connect jdbc:mysql://localhost:3306/test?useSSL=false --password root --username root \
--table dw_slice_rule --hive-table dw_slice_rule_compress --delete-target-dir --direct \
--hive-import --hive-database default --hive-overwrite --create-hive-table --delete-target-dir \
--compress --compression-codec org.apache.hadoop.io.compress.SnappyCodec

# 非压缩
sqoop import -m 1 --connect jdbc:mysql://localhost:3306/test?useSSL=false --password root --username root --table dw_slice_rule --hive-table dw_slice_rule --hive-import --hive-database default --hive-overwrite --create-hive-table  --delete-target-dir --direct
```

**orc导入** (使用hcatalog方式导入 不存在数据错位问题)

```bash
sqoop import \
-m 1 \
--connect jdbc:mysql://localhost:3306/test?useSSL=false --password root --username root \
--create-hcatalog-table --hcatalog-database my_hcat_db \
--table dw_slice_rule --hcatalog-table dw_slice_rule_orc \
--hcatalog-storage-stanza 'stored as orc tblproperties ("orc.compress"="SNAPPY")' --delete-target-dir

sqoop import \
 --connect jdbc:postgresql://foobar:5432/my_db \
 --driver org.postgresql.Driver \
 --connection-manager org.apache.sqoop.manager.GenericJdbcManager \
 --username foo \
 --password-file hdfs:///user/foobar/foo.txt \
 --table fact \
 --hcatalog-home /usr/hdp/current/hive-webhcat \
 --hcatalog-database my_hcat_db \
 --hcatalog-table fact \
 --create-hcatalog-table \
 --hcatalog-storage-stanza 'stored as orc tblproperties ("orc.compress"="SNAPPY")'
```

where条件导入

```bash
sqoop import \
--connect jdbc:mysql://localhost:9800/boss?useSSL=false --password root --username root \
--table crm_account --hive-table crm_account_dimension \
--hive-drop-import-delims \
--delete-target-dir --num-mappers 1 \
--hive-import --hive-database dc_measure --hive-overwrite --create-hive-table \
--compress --compression-codec org.apache.hadoop.io.compress.SnappyCodec

# --hive-drop-import-delims 用于解决数据导入时数据错位以及产生null值问题 [sqoop 从oracle到hive数据错位，并产生很多null值](https://blog.csdn.net/duyuanhai/article/details/76021549)
```

增量导入 [Sqoop增量导入实战 - CSDN博客](https://blog.csdn.net/qq_26937525/article/details/53670213)

```bash
#!/bin/bash
month=(201704 201705 201706 201707 201708 201709 201710 201711 201712)

for i in ${month[@]}
do

echo ">>>>import: $i"

sqoop import \
--connect jdbc:mysql://localhost:9901/mydb?useSSL=false --password 123456 --username root \
--incremental append \
--check-column stat_date \
--last-value "2017-04-31" \
--table dw_result_product_bill_detail_$i --hive-table dw_result_product_bill_detail_merge \
--hive-drop-import-delims \
--hive-import --hive-database bill \
--compress --compression-codec org.apache.hadoop.io.compress.SnappyCodec

done
```

***

## Ambari

hadoop集群可视化部署工具，具备Hadoop组件的安装、管理、运维等基本功能，提供Web UI进行可视化的集群管理，简化了大数据平台的安装、使用难度。 [Ambari 功能简介](https://blog.csdn.net/u012331758/article/details/78106714)


# 脚本


# python


# 批量请求url 解析json数据

```python
# -*- coding: UTF-8 -*-
#!/usr/bin/python

import urllib2
import urllib
import json

def byteify(input):
    if isinstance(input, dict):
        return {byteify(key): byteify(value) for key, value in input.iteritems()}
    elif isinstance(input, list):
        return [byteify(element) for element in input]
    elif isinstance(input, unicode):
        return input.encode('utf-8')
    else:
        return input


result="uid price\n"

def request(uid,cid):
    uid=str(uid)
    cid=str(cid)
    print "uid:",uid,"cid:",cid
    dataRes = urllib.urlopen('http://host/request&contractId='+cid+'&iamsid='+uid)
    #通过urllib模块中的urlopen的方法打开url
    data = dataRes.read()
    #通过read方法获取返回数据
    # print "url返回的json数据：",data
    #打印返回信息
    dataJSON = json.loads(data)
    dataJSON=dataJSON["data"]

    for price in dataJSON:
        if price["code"] == "kvm" :
                print uid,price["priceDate"]
                global result
                result=result+"\n"+uid+" "+price["priceDate"]

# param=[(70304362,157536),(70304362,157536)]
# for p in param:
#     request(p[0],p[1])


with open('uids.txt') as f:
    for line in f:
        uidcid=line.split()
        request(uidcid[0],uidcid[1])

#将返回的json格式的数据转化为python对象，json数据转化成了python中的字典，按照字典方法读取数据
# print "python的字典数据：",dataJSON
# print "字典中的data数据",dataJSON["data"]
# print "lists列表的数据",dataJSON["data"]["lists"][0]
#lists里面的数据是一个列表（按照序列编号来查看数据）
# print weatherJSON["data"]["lists"][0]["SongName"]
#lists的0号数据是一个字典，按照字典方法查看数据


#dumps()默认中文伟ascii编码格式，ensure_ascii默认为Ture
#禁用ascii编码格式，返回Unicode字符串
# dataJson=json.dumps(dataJSON,ensure_ascii=False)

with open("export.txt","w") as f:
    f.write(result.encode("utf-8"))
```

uids.txt 参数文件示例

\`\`\`txt uids.txt 参数文件 13368 156622 79682 156011 263190 155071

````
## 讲解

`dataJSON = json.loads(data)`读取的文件是unicode编码，如果要输出utf8格式的需要如下方式：

```python
#调用dumps方法输出字符串
dataStr=json.dumps(dataJSON,ensure_ascii=False)
#以utf8格式编码
print dataStr.encode("utf-8")
````

## 参考资料

[python爬虫 如何解析json文件 json文件的解析提取和jsonpath的应用 - CSDN博客](https://blog.csdn.net/t8116189520/article/details/78727971) [python 数据提取之JSON与JsonPATH - AlamZ - 博客园](https://www.cnblogs.com/alamZ/p/7413968.html)


# js


# sheetjs-js读取excel

参考文档：

先看这个：[如何使用JavaScript实现纯前端读取和导出excel文件-好记的博客](http://blog.haoji.me/js-excel.html) [GitHub - rockboom/SheetJS-docs-zh-CN: SheetJS中文文档，版本v0.14.0，持续更新中](https://github.com/rockboom/SheetJS-docs-zh-CN) [GitHub - SheetJS/sheetjs: SheetJS Community Edition -- Spreadsheet Data Toolkit](https://github.com/SheetJS/sheetjs) [使用 js-xlsx 处理 Excel 文件 - 星陨的菲 - 博客园](https://www.cnblogs.com/unreal-feather/p/12794129.html?ivk_sa=1024320u) 文件拖拽参考：[JS实现的文件拖拽上传功能示例\_javascript技巧\_脚本之家](https://www.jb51.net/article/140484.htm)

示例代码

```html
<!DOCTYPE html>
<html lang="zh-cn">

<head>
    <meta charset="UTF-8">
    <title>权限点制品生成</title>
    <style type="text/css">
        #box {
            width: 50%;
            height: 50%;
            border: 1px dashed #000;
            position: fixed;
            top: 25%;
            left: 25%;
            text-align: center;
            font: 20px/300px '微软雅黑';
            display: none;
            z-index: 999;
            background-color: rgb(128 128 128 / 50%);
        }

        table {
            border-collapse: collapse;
        }

        label {
            padding-left: 20px;
        }

        th,
        td {
            border: solid 1px #6D6D6D;
            padding: 5px 10px;
        }

        .mt-sm {
            margin-top: 8px;
        }

        body {
            background: #f4f4f4;
            padding: 0;
            margin: 0;
        }

        .container {
            width: 1024px;
            margin: 0 auto;
            background: #fff;
            padding: 20px;
            min-height: 100vh;
        }

        #verInfo {
            position: absolute;
            bottom: 2px;
        }
    </style>
</head>

<body>
    <div id="box">请将文件拖拽到此区域</div>
    <div class="container">
        <h1>步骤一：下载权限点原始文件</h1>
        <a href="https://kdocs.cn/fl/siUVrI5eP" target="_blank">权限点文档下载</a>
        <h1>步骤二：加载权限点Excel</h1>
        <div class="mt-sm">
            <input type="file" id="file" style="display:none;"
                accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" />
            <a href="javascript:selectFile()">加载本地excel文件</a> 或 <span style="color:red"><b>拖拽文件至该窗口</b></span>
            <div id="filePath"></div>
        </div>

        <h1>步骤三：选择套餐和项目</h1>
        <p>套餐</p>
        <div id="taocan"></div>
        <p>项目</p>
        <div id="project"></div>
        <p>附加选项</p>
        <div>
            <input type="checkbox" id="restOp" checked disabled>采用全量导入模式(禁止修改)
            <p style="color:red">全量导入模式下，导入权限点制品时，“是否清空原有权限点？”必须选择是</p>
        </div>

        <h1>步骤四：导出权限点制品</h1>
        <div class="mt-sm" style="padding-bottom:40px;">
            <input id="exportFile" type="button" onclick="exportExcel()" value="导出权限点制品" />
        </div>
        <div class="mt-sm" style="padding-bottom:40px;" id="error">

        </div>
        <div id="verInfo">
            <p style="font-size: small;color: grey;">该工具可以将产品维护的权限点文档处理成导入使用的制品文件，处理内容包括：公式转文本、无用列清理、非套餐权限点过滤、非项目权限点过滤。
            </p>
            <p>@Rizon ver:2022-03-23</p>

        </div>
    </div>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/xlsx/0.18.4/xlsx.core.min.js"></script>

    <script type="text/javascript">

        function selectFile() {
            document.getElementById('file').click();
        }

        // 读取本地excel文件
        function readWorkbookFromLocalFile(file, callback) {
            var reader = new FileReader();
            reader.onload = function (e) {
                var data = e.target.result;
                var workbook = XLSX.read(data, { type: 'binary' });
                if (callback) callback(workbook);
            };
            reader.readAsBinaryString(file);
        }


        // 读取 excel文件
        function outputWorkbook(workbook) {
            var sheetNames = workbook.SheetNames; // 工作表名称集合
            sheetNames.forEach(name => {
                var worksheet = workbook.Sheets[name]; // 只能通过工作表名称来获取指定工作表
                for (var key in worksheet) {
                    // v是读取单元格的原始值
                    console.log(key, key[0] === '!' ? worksheet[key] : worksheet[key].v);
                }
            });
        }
        let _worksheets;
        let taocan = [];
        let projects = [];
        let selectedTaocan = null;
        let selectedProject = null;
        function readWorkbook(workbook) {

            // var sheetNames = workbook.SheetNames; // 工作表名称集合
            var worksheet = workbook.Sheets["平台权限"]; // 这里我们只读取第一张sheet
            console.log(worksheet)
            _worksheets = workbook.Sheets;
            //删除筛选
            delete worksheet["!autofilter"];
            //获取套餐
            readTaocan(worksheet);
            renderTaocan();
            //获取项目
            readProjects(worksheet);
            renderProjects();
            //清理公式
            formual2txt(worksheet);


            // var csv = XLSX.utils.sheet_to_csv(worksheet);
            // document.getElementById('result').innerHTML = csv2table(csv);
        }
        function renderTaocan() {
            selectedTaocan = null;
            let content = "";
            for (let t in taocan) {
                content += '<label>' + taocan[t] + '</label><input name="taocanRadio" type="radio" id="' + taocan[t] + '">'
            }
            $('#taocan').html(content);
            $('input:radio[name="taocanRadio"]').change(function (e) {
                console.log(e.target.id);
                selectedTaocan = e.target.id;
            })
        }
        function renderProjects() {
            selectedProject = null;
            let content = "";
            for (let t in projects) {
                content += '<label>' + projects[t] + '</label><input name="projectRadio" type="radio" id="' + projects[t] + '">'
            }
            $('#project').html(content);
            $('input:radio[name="projectRadio"]').change(function (e) {
                console.log(e.target.id);
                selectedProject = e.target.id;
            })
            $('input:radio[id="标品"]').attr("checked", true);
            selectedProject = "标品";
        }
        function readTaocan(ws) {
            taocan = [];
            const range = XLSX.utils.decode_range(ws['!ref']);
            for (let i = 3; i <= range.e.r + 1; i++) {//第三行开始
                if (ws["F" + i] && ws["F" + i]["w"]) {
                    if (taocan.indexOf(ws["F" + i]["w"]) < 0) {
                        taocan.push(ws["F" + i]["w"]);
                    }
                }
            }
        }
        function readProjects(ws) {
            projects = [];
            const range = XLSX.utils.decode_range(ws['!ref']);
            for (let i = 3; i <= range.e.r + 1; i++) {//第三行开始
                if (ws["G" + i] && ws["G" + i]["w"]) {
                    ws["G" + i]["w"].split(";").forEach(e => {
                        if (projects.indexOf(e) < 0) {
                            projects.push(e);
                        }
                    });
                }
            }
        }

        function formual2txt(ws) {
            const range = XLSX.utils.decode_range(ws['!ref']);
            for (let col = range.s.c; col <= range.e.c; col++) {
                for (let row = range.s.r; row <= range.e.r; row++) {
                    let address = encodeCell(row, col);
                    //删除公式
                    if (ws[address] && ws[address]['f']) {
                        delete ws[address]['f'];
                    }
                }
            }
        }
        function delCols() {
            var worksheet = _worksheets["平台权限"];
            //删除0-7列
            for (i = 0; i < 7; i++) {
                deleteCol(worksheet, 0);
            }
            //迭代-环境 6个
            for (i = 0; i < 6; i++) {
                deleteCol(worksheet, 5);
            }
            //角色 12个
            for (i = 0; i < 12; i++) {
                deleteCol(worksheet, 18);
            }
        }
        function encodeCell(r, c) {
            return XLSX.utils.encode_cell({ r, c });
        }
        function deleteRow(ws, index) {
            const range = XLSX.utils.decode_range(ws['!ref']);

            for (let row = index; row < range.e.r; row++) {
                for (let col = range.s.c; col <= range.e.c; col++) {
                    ws[encodeCell(row, col)] = ws[encodeCell(row + 1, col)];
                }
            }

            range.e.r--;

            ws['!ref'] = XLSX.utils.encode_range(range.s, range.e);
        }
        function deleteCol(ws, index) {
            const range = XLSX.utils.decode_range(ws['!ref']);

            for (let col = index; col < range.e.c; col++) {
                for (let row = range.s.r; row <= range.e.r; row++) {
                    ws[encodeCell(row, col)] = ws[encodeCell(row, col + 1)];
                }
            }

            range.e.c--;

            ws['!ref'] = XLSX.utils.encode_range(range.s, range.e);
        }
        // 将csv转换成表格
        function csv2table(csv) {
            var html = '<table>';
            var rows = csv.split('\n');
            rows.pop(); // 最后一行没用的
            rows.forEach(function (row, idx) {
                var columns = row.split(',');
                columns.unshift(idx + 1); // 添加行索引
                if (idx == 0) { // 添加列索引
                    html += '<tr>';
                    for (var i = 0; i < columns.length; i++) {
                        html += '<th>' + (i == 0 ? '' : String.fromCharCode(65 + i - 1)) + '</th>';
                    }
                    html += '</tr>';
                }
                html += '<tr>';
                columns.forEach(function (column) {
                    html += '<td>' + column + '</td>';
                });
                html += '</tr>';
            });
            html += '</table>';
            return html;
        }

        function table2csv(table) {
            var csv = [];
            $(table).find('tr').each(function () {
                var temp = [];
                $(this).find('td').each(function () {
                    temp.push($(this).html());
                })
                temp.shift(); // 移除第一个
                csv.push(temp.join(','));
            });
            csv.shift();
            return csv.join('\n');
        }

        // csv转sheet对象
        function csv2sheet(csv) {
            var sheet = {}; // 将要生成的sheet
            csv = csv.split('\n');
            csv.forEach(function (row, i) {
                row = row.split(',');
                if (i == 0) sheet['!ref'] = 'A1:' + String.fromCharCode(65 + row.length - 1) + (csv.length - 1);
                row.forEach(function (col, j) {
                    sheet[String.fromCharCode(65 + j) + (i + 1)] = { v: col };
                });
            });
            return sheet;
        }

        // 导出配置
        function sheet2blob() {

            var workbook = {
                SheetNames: ['平台权限', '平台角色'],
                Sheets: { '平台权限': _worksheets['平台权限'], '平台角色': _worksheets['平台角色'] }
            };

            // 生成excel的配置项
            var wopts = {
                bookType: 'xlsx', // 要生成的文件类型
                bookSST: false, // 是否生成Shared String Table，官方解释是，如果开启生成速度会下降，但在低版本IOS设备上有更好的兼容性
                type: 'binary'
            };
            var wbout = XLSX.write(workbook, wopts);
            var blob = new Blob([s2ab(wbout)], { type: "application/octet-stream" });
            // 字符串转ArrayBuffer
            function s2ab(s) {
                var buf = new ArrayBuffer(s.length);
                var view = new Uint8Array(buf);
                for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
                return buf;
            }
            return blob;
        }

        /**
         * 通用的打开下载对话框方法，没有测试过具体兼容性
         * @param url 下载地址，也可以是一个blob对象，必选
         * @param saveName 保存文件名，可选
         */
        function openDownloadDialog(url, saveName) {
            if (typeof url == 'object' && url instanceof Blob) {
                url = URL.createObjectURL(url); // 创建blob地址
            }
            var aLink = document.createElement('a');
            aLink.href = url;
            aLink.download = saveName || ''; // HTML5新增的属性，指定保存文件名，可以不要后缀，注意，file:///模式下不会生效
            var event;
            if (window.MouseEvent) event = new MouseEvent('click');
            else {
                event = document.createEvent('MouseEvents');
                event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
            }
            aLink.dispatchEvent(event);
        }
        let restOp;
        function delNotIncludeRows() {
            restOp = $('#restOp').prop('checked');
            console.log('restOp', restOp);
            var ws = _worksheets["平台权限"];
            if (!selectedTaocan && !confirm("没有选择套餐，将会导出所有权限点，是否继续？")) {
                return "cancel";
            }
            //第三行开始
            for (; ;) {
                let r = delRowNext(ws, 3);
                if (r > 0) {
                    delRowNext(ws, r);
                } else {
                    break;
                }
            }
        }
        function delRowNext(ws, startRow) {
            const range = XLSX.utils.decode_range(ws['!ref']);
            for (let i = startRow; i <= range.e.r + 1; i++) {//第二行开始
                //套餐清理
                let address = "F" + i;
                if (selectedTaocan === "低") {
                    if (ws[address] && (ws[address]["w"] === "中" || ws[address]["w"] === "高")) {
                        deleteRow(ws, i - 1);
                        return i;
                    }
                } else if (selectedTaocan === "中") {
                    if (ws[address] && (ws[address]["w"] === "高")) {
                        deleteRow(ws, i - 1);
                        return i;
                    }
                }

                //项目清理
                address = "G" + i;
                if (ws[address] && ws[address]["w"]) {
                    let val = ws[address]["w"];
                    if (val.indexOf("标品") < 0 && val.indexOf(selectedProject) < 0) {
                        console.log("del", val, i - 1);
                        deleteRow(ws, i - 1);
                        return i;
                    }
                }

                //标记为删除的权限点 会清理掉
                if (restOp) {
                    address = "AE" + i;
                    if (ws[address] && ws[address]["v"]) {
                        let val = ws[address]["v"];
                        if (val === "") {
                            throw new Error("操作列单元格有空值，取消导出");
                        }
                        if (val.indexOf("修改") > -1) {
                            //改为新增
                            ws[address]["v"] = '新增';
                            delete ws[address]["w"];
                        } else if (val.indexOf("删除") > -1) {
                            //删除
                            console.log("del", val, i - 1);
                            deleteRow(ws, i - 1);
                            return i;
                        }
                    } else {
                        throw new Error("操作列单元格有空值，取消导出");
                    }
                }

            }
            return -1;
        }
        function readFile(file) {
            $('#filePath').html(file.name);
            readWorkbookFromLocalFile(file, function (workbook) {
                readWorkbook(workbook);
                document.getElementById("exportFile").value = "导出权限点制品";
                document.getElementById("exportFile").disabled = false;
            });
        }

        let _file;
        $(function () {
            document.getElementById("exportFile").disabled = true;
            document.getElementById('file').addEventListener('change', function (e) {
                var files = e.target.files;
                if (files.length == 0) return;
                var f = files[0];
                if (!/\.xlsx$/g.test(f.name)) {
                    alert('仅支持读取xlsx格式!');
                    return;
                }
                _file = f;
                readFile(f);
            });
        });

        function dateFormat(fmt, date) {
            let ret;
            const opt = {
                "Y+": date.getFullYear().toString(),        // 年
                "m+": (date.getMonth() + 1).toString(),     // 月
                "d+": date.getDate().toString(),            // 日
                "H+": date.getHours().toString(),           // 时
                "M+": date.getMinutes().toString(),         // 分
                "S+": date.getSeconds().toString()          // 秒
                // 有其他格式化字符需求可以继续添加，必须转化成字符串
            };
            for (let k in opt) {
                ret = new RegExp("(" + k + ")").exec(fmt);
                if (ret) {
                    fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
                };
            };
            return fmt;
        }

        function exportExcel() {
            document.getElementById("exportFile").disabled = true;
            document.getElementById("exportFile").value = "导出中...";
            // $('#exportFile').val("导出中...");
            //清理非套餐和项目数据
            let result;
            try {
                result = delNotIncludeRows();
            } catch (err) {
                alert(err);
                result = "cancel";
            }
            if (result === "cancel") {
                document.getElementById("exportFile").value = "导出权限点制品";
                document.getElementById("exportFile").disabled = false;
                return;
            }

            //删除无用的列
            delCols();
            var blob = sheet2blob();
            var version = _file.name.match(/[\d\.]+/);
            if (version && version.length > 0) {
                version = version[0]
            } else {
                version = "";
            }
            openDownloadDialog(blob, dateFormat("YYYYmmddHHMMSS", new Date()) + '-v' + version + '-企业云-' + selectedProject + '-套餐' + selectedTaocan + '-权限点制品.xlsx');

            //初始化
            readFile(_file);

        }


        //拖拽文件
        window.onload = function () {
            var oBox = document.getElementById('box');
            var timer = null;
            document.ondragover = function () {
                clearTimeout(timer);
                timer = setTimeout(function () {
                    oBox.innerHTML = '请将文件拖拽到此区域';
                    oBox.style.display = 'none';
                }, 200);
                oBox.style.display = 'block';
            };
            //进入子集的时候 会触发ondragover 频繁触发 不给ondrop机会
            oBox.ondragenter = function () {
                oBox.innerHTML = '请释放鼠标';
            };
            oBox.ondragover = function () {
                return false;
            };
            oBox.ondragleave = function () {
                oBox.innerHTML = '请将文件拖拽到此区域';
            };
            oBox.ondrop = function (ev) {
                _file = ev.dataTransfer.files[0];
                oBox.innerHTML = '读取中...';
                readFile(_file);
                return false;
            };
        };

    </script>
</body>

</html>
```


# shell


# 自动生成bitbook的summary文件

```bash
#!/bin/bash
# set -ex
root=$1
if [ -z "$root" ];then
root="./"
fi

cd $root

ignore="! -iname "README.MD" ! -iname "SUMMARY.MD" ! -iname "_SUMMARY.MD" ! -path "./.git" ! -path "./node_modules""

getspcae(){
    tab=""
    local count=`expr $1 - 2`
    if [ "${count}" -le 0 ]; then return;fi

    for a in `seq $count`
    do
     tab=${tab}"  "
    done
    echo "${tab}"
}

writeline(){
    fpath=$1
    depth=$2
    title=`head -1 $fpath`
    title=${title:1}
    #将开头的#号去掉
    title=${title/#\#/}
    #去掉开头空格
    title=${title/# /}
    #去掉开头的点
    fpath=${fpath:1}

    tab=`getspcae $depth`

    result="${result}${tab}* [${title}](${fpath})\n"
}
getnode(){
    title=$1
    depth=$2
    title=${title:2}
    title=${title#*/}
    tab=`getspcae $depth`

    echo "${tab}* ${title}"
}
dive(){
    local dirs=$1
    if [ -z "$dirs" ]; then return;fi


    local depth=$2
    local node=$3
    local nextdepth=`expr $2 + 1`
    #写节点
    if [ -n "$node" ];then
      result="${result}${node}\n"
    fi

    for dir in ${dirs[@]}
    do
        if [ -d "${dir}" ];then
          nextnode=`getnode "${dir}" ${nextdepth}`
        dive "`find ${dir} -maxdepth 1  -mindepth 1 \( -iname "*.md" -or -type d \)`" $nextdepth "${nextnode}"
        else
          writeline "${dir}" ${nextdepth}
        fi
    done
}

result=""

dive "`find . -maxdepth 1  -mindepth 1 \( -iname "*.md" -or -type d \) ${ignore}`" 1

#写文件
echo generate SUMMARY.md
echo "${result}" > ${root}/SUMMARY.md
echo finish
```


# linux/mac 实用脚本

* [linux/mac 实用脚本](#linuxmac-实用脚本)
  * [知识](#知识)
  * [mysqldump](#mysqldump)
  * [部署ss服务器教程](#部署ss服务器教程)
  * [服务状态检测](#服务状态检测)
  * [ssl证书过期检测](#ssl证书过期检测)
  * [实用命令](#实用命令)

## 知识

`set -ex`: `-e`:命令失败后立即结束 `-x`:打印每条执行的命令

## mysqldump

[MySQL mysqldump数据导出详解](https://www.cnblogs.com/chenmh/p/5300370.html) 复制表，调用方式:`sh copy.sh "-h127.0.0.0 -uroot -P8306 -p123456 boss_develop"`

```bash
#!/bin/bash
#set -x
REMOTE_HOST=$1
echo REMOTE_HOST:$REMOTE_HOST


oldifs=$IFS
dump(){
# -C启用压缩 --skip-triggers 不导出触发器
mysqldump -h10.69.34.205 -uadmin -P9800 -pDc@20120225 --opt -C boss $1 | mysql $REMOTE_HOST
}
dtd(){
        IFS=$oldifs
        arr=($1)
        for table in ${arr[@]}
        do
            echo start dump $table
            dump $table
        done
        IFS=$'\n'
}

IFS=$'\n'

#crm_measurement_units  crm_department crm_role_extend crm_price_item_ref_measure_time crm_time_units
#crm_special_rules_item crm_special_rules_product_item_rel crm_special_rules_product_type_rel
#dw_manual_rule dw_slice_rule
#crm_account_director crm_employee crm_region_base

tables="
crm_product_type crm_product_line_group
crm_account crm_contract contract_account_release crm_product_type_relation contract_product_price
crm_price_definition crm_price_detail
crm_special_rules_discount crm_contract_sprules crm_contract_sprules_regions
crm_rule_use_log crm_rule_use_lock crm_manual_rule crm_manual_rule_lock
month_bill_locked month_bill_summary_new
"

for table in ${tables[@]}
do
        read -r -p $table" ? [Y/n] " input
        case $input in
                [yY][eE][sS]|[yY]) dtd "${table}" ;;
                *) echo "No";;
        esac
done

IFS=$oldifs

echo finish copy
read
```

## 部署ss服务器教程

[阿里云搭建SS代理超详细教程！！！ - 天涯小嘿嘿 - 博客园](https://www.cnblogs.com/tianhei/p/7428622.html) 脚本来源：[teddysun/shadowsocks\_install](https://github.com/teddysun/shadowsocks_install)

```bash
#安装
wget --no-check-certificate -O shadowsocks-all.sh https://raw.githubusercontent.com/teddysun/shadowsocks_install/master/shadowsocks-all.sh
chmod +x shadowsocks-all.sh
./shadowsocks-all.sh 2>&1 | tee shadowsocks-all.log

#卸载
./shadowsocks-all.sh uninstall
```

## 服务状态检测

```bash
#!/bin/sh

service="boss-api-8086 boss-server-8080 boss-activiti-8095 boss-ruleconvert-8090 boss-bill-8071 boss-sync-8100"

for service in ${service[@]}
do
    if test $(pgrep -f $service | wc -l)  -eq 0
    then
      echo "ERROR "$service" NOT RUNNING"
    else
      echo $service" is runing....."
    fi
done

echo "check process finish,any key to exit"
read
```

## ssl证书过期检测

```bash
#!/bin/bash
# 检测https证书有效
echo '开始检查 https证书有效期 '

# 先写域名内容到文件中，再读取文件检查证书是否过期了
# 先清空文件
echo '' > /tmp/https_list.txt 

# 这里替换为自己的检查的域名即可
echo 'www.baidu.com' >> /tmp/https_list.txt
echo 'www.bing.com' >> /tmp/https_list.txt
echo 'www.google.com' >> /tmp/https_list.txt

source /etc/profile

# 定义错误的域名
errorDominStr=""

while read line; do
    echo "====================================================================================="
    
    echo "当前检测的域名：" $line
    end_time=$(echo | timeout 1 openssl s_client -servername $line -connect $line:443 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | awk -F '=' '{print $2}' )
    ([ $? -ne 0 ] || [[ $end_time == '' ]]) && echo '该域名链接不上,跳到下一个域名' && continue
    
    end_times=`date -d "$end_time" +%s `
    current_times=`date -d "$(date -u '+%b %d %T %Y GMT') " +%s `
    
    let left_time=$end_times-$current_times
    days=`expr $left_time / 86400`
    echo "剩余天数: " $days
    
    [ $days -lt 60 ] && echo "https 证书有效期少于60天，存在风险"  && errorDominStr="$errorDominStr \n $line"
    
done < /tmp/https_list.txt

echo -e "准备过期的域名为： \n  $errorDominStr"

if [ "$errorDominStr" = "" ]  
then  
  echo "不包含准备过期的域名"  
else    
  echo "包含准备过期的域名" && exit 10  
fi   
echo "Good bye!"
exit 0
```

## 实用命令

获取父线程名称 [get the name of the caller script in bash script](https://stackoverflow.com/questions/20572934/get-the-name-of-the-caller-script-in-bash-script)

```bash
echo $(ps $PPID | tail -n 1 | awk "{print \$5}")
```

简单for循环curl测试下载速度/for循环写成一行 参考：[linux脚本](https://book.rizon.top/jiao-ben/shell/pages/-Lcei2xw9WsoJypglBXW#curl测试下载速度)

```bash
# 测速
for i in {1..10}; do echo $i">>>>"; curl -o /dev/null -s -w '%{time_connect}:%{time_starttransfer}:%{time_total}\n' 'https://lib.baomitu.com/jquery/3.3.1/jquery.min.js'; done

#其他示例
for i in {1..10}; do echo 'test'; done
for ((i=1;i<10;i++)); do echo 'test'; done
for i in $(seq 1 10); do ehco 'test'; done
```


# 自动创建tomcat项目脚本

```bash
#!/bin/bash
checkNull(){
if [ -z "$1" ];then
echo 不能为空
exit 1
fi
}
create(){

unzip tomcat8.zip -d $pname
sed -i  "s/{SHUTDOWN_PORT}/${sport}/" $pname/conf/server.xml
sed -i  "s/{CONNECT_PORT}/${cport}/" $pname/conf/server.xml
sed -i  "s/{AJP_PORT}/${aport}/" $pname/conf/server.xml
echo $finish
exit 1
}
echo start create project

read -p "项目名称?  " pname
checkNull $pname

read -p "服务端口?  " cport
checkNull $cport

sport=`expr $cport + 1`
aport=`expr $cport + 2`

read -p "shutdown port？default:$sport  " sport2
if [ -n "$sport2" ];then
sport=$sport2
fi

read -p "ajp port?default:$aport  " aport2
if [ -n "$aport2" ];then
aport=$aport2
fi

echo "poroject name is:$pname"
echo "server port is:$cport"
echo "shutdown port is:$sport"
echo "ajp port is:$aport"

read -r -p "is OK?[y/n]" confirm
case $confirm in
        [yY][eE][sS]|[yY])
          create
          ;;
        *)
          echo "No,finish"
          exit 1
          ;;
esac
```


# 批量处理文件内容脚本

```bash
#!/bin/bash
set -e

prefix=$1

#fname='(-iname "type1_*.sql" -or -iname "type2_*.sql" -or -iname "type3_*.sql" -or -iname "type9_*.sql" -or -iname "type13_*.sql" -or -iname "type14_*.sql")'

root=$2
if [ -z "$prefix" ];then
echo prefix is null;
exit
fi
if [ -z "$root" ];then
root="./"
fi

cd $root

count=0;
groups="";

writeline(){
    count=`expr $count + 1`
    local fpath=$1
    echo $fpath
    # 组名
    local group=$2
    groups="${groups} ; ${group}"
    # 获取文件名
    local name=${fpath#*type*_}
    name=${name%%\.sql*}
    #替换单引号 '  为  \'
    sed "s/'/\\\'/g" ${fpath} > $root/temp

    sed -i "" "s/2018-04-01/{{PRE_MONTH_FIRST_DAY}}/g" $root/temp 
    sed -i "" "s/2018-04-19/{{PRE_PERIOD_FIRST_DAY}}/g" $root/temp 
    sed -i "" "s/2018-04-25/{{PRE_WEEK}}/g" $root/temp 
    sed -i "" "s/2018-04-26/{{CUR_PERIOD_FIRST_DAY}}/g" $root/temp 
    sed -i "" "s/2018-04-30/{{PRE_MONTH_LAST_DAY}}/g" $root/temp 
    sed -i "" "s/2018-05-02/{{RP_TODAY}}/g" $root/temp 


    local sql=`cat $root/temp`
    # sql=${sql//\'/\\\'}
    # result="${result}\n |0| ${name} |1| ${group} |2| '${sql//\'/\\\'}' |3|"


    if [ -z "$result" ];then
    result="('${name}','${group}','${sql}','${fpath}')"
    else
    result="${result},\n('${name}','${group}','${sql}','${fpath}')"
    fi
}

dive(){
    local dirs=$1
    local group=$2
    if [ -z "$dirs" ]; then return;fi

    for dir in ${dirs[@]}
    do
        if [ -d "${dir}" ];then
        #获取最后一级目录
        group=${dir##*/}
        dive "`find ${dir} -maxdepth 1  -mindepth 1 \( -iname "${prefix}_*.sql" -or -type d \)`" ${group}
        else
          writeline "${dir}" ${group}
        fi
    done
}

dive "`find . -maxdepth 1  -mindepth 1 \( -iname "${prefix}_*.sql" -or -type d \)`"
head="INSERT INTO spt_script ( name, group_name, script,descr) VALUES"
result="#groups:${groups}\n#======\n#count:${count}\n#========\n${head}\n${result};"

echo -e "${result}">${prefix}-export.sql
echo ${count}
```


# nginx


# nginx

## nginx

### nginx 做域名绑定

[nginx server\_name](http://tengine.taobao.org/nginx_docs/cn/docs/http/server_names.html) [nginx学习（十）——nginx的配置系统6之server\_name - CSDN博客](http://blog.csdn.net/xxcupid/article/details/52515237) [Nginx中的默认主机及location设置（摘自淘宝） - CSDN博客](http://blog.csdn.net/u010566813/article/details/51274954)

### nginx 配置ssl https请求

[Nginx启动SSL功能，并进行功能优化，你看这个就足够了](https://www.cnblogs.com/piscesLoveCc/p/6120875.html) [FreeSSL - 一个申请免费HTTPS证书的网站](https://freessl.org/) [Nginx+Https配置 - 倚楼听风雨 - SegmentFault 思否](https://segmentfault.com/a/1190000004976222) [官方-配置HTTPS服务器](http://tengine.taobao.org/nginx_docs/cn/docs/http/configuring_https_servers.html)

```
server {
    listen       443 ssl http2;
    listen       [::]:443 ssl http2;
    server_name *.rizon.top;
    ssl on;
    root html;
    index index.html index.htm;
    ssl_certificate   /etc/nginx/cert/certificate.pem;
    ssl_certificate_key  /etc/nginx/cert/certificate.key;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    location / {
        root /home/website;
        index index.html index.htm;
    }
}
```

### nginx 配置301跳转

[Nginx环境强制http 301跳转https设置记录](http://www.laozuo.org/9953.html)

\`\`\`conf /etc/nginx/nginx.conf server\_name example.com; return 301 <https://$host$request_uri>;

````
## nginx 反向代理

[nginx配置url重定向-反向代理-大風-51CTO博客](http://blog.51cto.com/lansgg/1575274)  

/etc/nginx/nginx.conf

```conf /etc/nginx/nginx.conf
server {
listen    80 default_server;
server_name    www.lansgg.com lansgg.com;
access_log    logs/lansgg.access.log main;
error_log    logs/lansgg.error.log;
root        /opt/nginx/nginx/html/lansgg;
location / {
    index index.html;
    }
location /other {
proxy_pass          http://192.168.10.129/other;
proxy_set_header    X-Real-IP $remote_addr;
    }
}
````

### 使用nginx stream

tag 代理mysql stream代理ssh tcp反向代理 nginx代理ssh

[Module ngx\_stream\_core\_module](http://nginx.org/en/docs/stream/ngx_stream_core_module.html)\
[caojx-git/learn nginx使用stream模块做ssh转发](https://github.com/caojx-git/learn/blob/master/notes/nginx/nginx使用stream模块做ssh转发.md)

/etc/nginx/nginx.conf

\`\`\`conf /etc/nginx/nginx.conf

## 要注意不要放到http块中，因为这个不是http转发而是tcp层的转发

## stream模块使用yum安装的高版本nginx是有的，编译安装的默认可能没有

stream {

```
#配置upstream是用与负载均衡的，可以不这样配置
upstream cloudsocket {
   hash $remote_addr consistent;
  # $binary_remote_addr;
   server 192.168.182.155:3306 weight=5 max_fails=3 fail_timeout=30s;
}
#代理mysql
server {
   listen 3306;#数据库服务器监听端口
   proxy_connect_timeout 10s;
   proxy_timeout 5m; #这个属性的含义不太清楚，和mysql的连接时间应该似乎有关系，时间太短似乎会导致mysql的连接被中断
   #负载均衡配置方法
   #proxy_pass cloudsocket;
   # 简单配置方法
   proxy_pass 10.69.65.96:3306;
}

#代理ssh
server {
        listen 80;
        proxy_pass ssh;
        proxy_connect_timeout 1h;
        proxy_timeout 1h;
}
```

}

````
## nginx 重定向 rewrite

[http重定向301/302/303/307](https://blog.csdn.net/reliveit/article/details/50776984)
[Nginx URL重写（rewrite）配置及信息详解](https://www.cnblogs.com/czlun/articles/7010604.html)

```conf /etc/nginx/nginx.conf
server {
        listen 80;
        server_name abc.com;
        #显式url跳转 永久重定向
        #rewrite ^/(.*)$ http://www.abc.com/$1 permanent;
        # 显式url跳转，临时重定向
        rewrite ^/(.*)$ https://rizon.top/tool/ last;
}
````

语法说明 rewrite regex replacement \[flag]; 关键字 正则 替代内容 flag标记

正则：perl兼容正则表达式语句进行规则匹配 替代内容：将正则匹配的内容替换成replacement flag标记：rewrite支持的flag标记

flag标记说明： last #本条规则匹配完成后，继续向下匹配新的location URI规则 break #本条规则匹配完成即终止，不再匹配后面的任何规则 redirect #返回302临时重定向，浏览器地址会显示跳转后的URL地址 permanent #返回301永久重定向，浏览器地址栏会显示跳转后的URL地址


# ngix 文件浏览器 文件服务器

## nginx配置

```
...
server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   /opt/boss-html;
            index  index.html index.htm;
        }
        location /files {
            alias   /usr/local/axure/workspace/git_home/;
            charset utf-8;
            autoindex on; # 索引
            autoindex_exact_size off; # 显示文件大小
            autoindex_localtime on; # 显示文件时间
        }
...
```

说明：

autoindex on; 打开索引另外两个参数最好也加上去:

autoindex\_exact\_size off; 默认为on，显示出文件的确切大小，单位是bytes。 改为off后，显示出文件的大概大小，单位是kB或者MB或者GB

autoindex\_localtime on; 默认为off，显示的文件时间为GMT时间。 改为on后，显示的文件时间为文件的服务器时间

charset utf-8; 中文编码

**alias 和 root的区别** 使用root时 location后面配置的地址会跟在root地址后面来请求访问，alias则不会

先看root的用法 location /request\_path/image/ { root /local\_path/image/; } 这样配置的结果就是当客户端请求 /request\_path/image/cat.png 的时候， Nginx把请求映射为/local\_path/image/**request\_path/image/**&#x63;at.png

再看alias的用法 location /request\_path/image/ { alias /local\_path/image/; } 这时候，当客户端请求 /request\_path/image/cat.png 的时候， Nginx把请求映射为/local\_path/image/cat.png

## Jenkins配置 实现自动部署

jenkins execute shell 执行脚本如下：

```
#!/bin/sh -ex

FILE_HOME=/usr/local/axure/workspace/git_home/

REMOTE_HOST=10.69.57.76

touch 数据更新日期_`date "+%Y-%m-%d_%H-%M-%S"`

# convert 2 pdf
mkdir doc_pdf
mkdir doc_html
mkdir xls_html
find . -name '*.doc*' -print0 |xargs -0 /usr/bin/libreoffice  --invisible --convert-to pdf --outdir 'doc_pdf'
find . -name '*.doc*' -print0 |xargs -0 /usr/bin/libreoffice  --invisible --convert-to html --outdir 'doc_html'
find . -name '*.xls*' -print0 |xargs -0 /usr/bin/libreoffice  --invisible --convert-to html --outdir 'xls_html'

ssh $REMOTE_HOST "rm -rf ${FILE_HOME}*"

scp -r * $REMOTE_HOST:$FILE_HOME

ssh $REMOTE_HOST "chmod -R 755 ${FILE_HOME}"
```

说明： libreoffice 是把office文档转换成可以页面直接看的文档，具体说明看文档 `效率工具/linux-office.md` jenkins 可以部署为通过钩子来自动触发，具体看文档 `jenkins/gitlab触发jenkins自动构建.md`


# linux


# 群晖nas札记

## 增强使用

### docker相关

安装网易云音乐下载器

```bash
docker pull python:3-slim
#下载netease-dl源码
git clone https://github.com/ziwenxie/netease-dl
docker run --rm -it -v /netease-dl:/apps python:3-slim bash
#容器内安装netease-dl
(docker) python /apps/setup.py install
```




---

[Next Page](/llms-full.txt/1)

