<file_sep>---
layout: archives
home-title: 一年四季
description: 不以物喜，不以己悲，感时花溅泪，恨别鸟惊心
permalink: /archives.html
cover: 'https://images.unsplash.com/photo-1489861518096-4d12b732e831'
cover_author: '<NAME>'
cover_author_link: 'https://unsplash.com/@jdiegoph'
---<file_sep>---
layout: post
title: eclipse运行在gome3下面，右键菜单被截断
categories: Develop
description: eclipse运行在gome3下面，右键菜单被截断
keywords: Fedora, Linux, Gome3, Eclipse
---

刚更新了Fedora26, 又碰到Eclipse快捷菜单太长被截断的问题，这次记下来。

主要原因是Eclipse仍不支持GTK2, 所以在Eclipse的配置文件中加入下面这两行就可以了。

#### 修改eclipse.ini文件，添加以下两行

```text
--launcher.GTK_version
2
```

最后的文件看起来像这样，请注意上面那两行所在的位置

```text
-startup
plugins/org.eclipse.equinox.launcher_1.4.0.v20161219-1356.jar
--launcher.GTK_version
2
--launcher.library
plugins/org.eclipse.equinox.launcher.gtk.linux.x86_64_1.1.550.v20170928-1359
-product
org.eclipse.epp.package.jee.product
-showsplash
org.eclipse.epp.package.common
--launcher.defaultAction
openFile
--launcher.defaultAction
openFile
--launcher.appendVmargs
-vmargs
-Dosgi.requiredJavaVersion=1.8
-Dosgi.instance.area.default=@user.home/eclipse-workspace
-XX:+UseG1GC
-XX:+UseStringDeduplication
--add-modules=ALL-SYSTEM
-Dosgi.requiredJavaVersion=1.8
-Xms256m
-Xmx1024m
--add-modules=ALL-SYSTEM
```<file_sep>---
layout: post
title: fedora29 vmware ssh 失败
categories: Operation
description: fedora29 vmware ssh 失败
keywords: linux，fedora, ssh
---

fedora总能搞出一些事来，这次fedora29就有ssh无法使用的问题。总是报Broken pipe的错误。

解决办法是修改ssh的config

```sh
cat ~/.ssh/config
Host *
    IPQoS lowdelay throughput
```<file_sep>---
layout: post
title: 自定义微信小程序flyio响应拦截
categories: Develop
description: 在微信小程序中使用flyio进行网络通讯，编写flyio拦截器，实现response的错误自动处理。
keywords: 微信小程序, http, fly, flyio, mpvue
---

本文通过实现fly的拦截器，实现自动发送身份验证凭证跟自动错误处理。  
整体设计思路请参见[RESTFUL错误返回设计]({% post_url 2019-11-13-RESTFUL异常返回定义 %})  

# flyio 介绍
fly.js是一个支持所有JavaScript运行环境的基于Promise的、支持请求转发、强大的http请求库。可以让您在多个端上尽可能大限度的实现代码复用。一个支持所有JavaScript运行环境的基于Promise的、支持请求转发、强大的http请求库。可以让您在多个端上尽可能大限度的实现代码复用。  
[flyio官网](https://wendux.github.io/dist/#/doc/flyio/readme)

# 在mpvue中引入fly
由于本人项目是mpvue框架，所以这里说明的是mpvue的导入方式，如果你使用的是其它框架，请参考flyio官方说明。  

## 使用npm安装fly.js
```sh
npm install flyio --save
```

## 调用fly
通过npm安装的软件统一安装在了node_modules里面，可以使用以下代码调用fly对象。  

```js
var Fly = require("flyio/dist/npm/wx");
var fly = new Fly();
```

# 编写request.js工具类
这个工具类主要就是实现了两个拦截器，请求拦截跟响应拦截。请求拦截主要是在头部添加了统一的登录凭证access_token，响应拦截主要是向用户提示错误信息，并记录到本地storage。  

```js
var Fly = require("flyio/dist/npm/wx");
var fly = new Fly();

const host = 'http://10.11.15.43:81/ihu/public'

// 添加请求拦截器
fly.interceptors.request.use((request) => {
  try {
    var loginStatus = wx.getStorageSync("login_status");
    // 在头部添加access_token
    request.headers["Authorization"] = "Bearer " + loginStatus.access_token
  } catch(err){

  }
  
  wx.showLoading({
    title: '加载中',
    mask: true
  })

  return request
})

// 添加响应拦截器
fly.interceptors.response.use(
  (response) => {
    wx.hideLoading()
    return response; // http code == 200时，请求成功之后将返回值返回
  },
  // http ！= 200时，向用户输出错误信息，并记录日志
  (err) => { 
    wx.hideLoading();
    console.log(err.response.data);
    wx.showToast({
      title: err.response.data.error_message,
      icon: 'none',
      duration: 3000
    });
  }
)

fly.config.baseURL = host

export default fly
```

这段代码仍然不够完善，在响应拦截那里，并没有把错误信息记录下来，只是向用户提示了错误信息。  

# 使用request.js
因为fly支持promise，所以示例代码里使用了async ... await ... 这样的同步请求写法。  

```js
// 注意这里的相对地址，在我的项目中
// request.js位于 /src/utils/request.js
// 示例代码位于 /src/pages/my/index.vue
import fly from "../../utils/request";

async uploadDrugPlan() {
    var drugPlan = wx.getStorageSync("drug_plan");

    if (drugPlan) {
    try {
        await fly.post("/drug-used-plan/upload", {
        drug_plan: JSON.stringify(drugPlan)
        });

        wx.showToast({
        title: "上传成功"
        });
    } catch (error) {}
    } else {
    wx.showToast({
        title: "本地暂无计划",
        icon: "none"
    });
    }
}
```
<file_sep>---
layout: post
title: wordpress增加阅读数（含接口改造）
categories: Develop
description: wordpress增加阅读数（含接口改造）
keywords: git
---

wordpress默认是没有阅读数的，本文通过使用wordpress内部的set_post_meta方法来实现。在主题文件的functions.php增加以下代码：

```php
/**
 * 增加浏览数
 **/
function getPostViews($postID){
    $count_key = 'post_views_count';  
    $count = get_post_meta($postID, $count_key, true);  
    if($count==''){
        delete_post_meta($postID, $count_key);  
        add_post_meta($postID, $count_key, '0');  
        return "0";   
    }  
	return $count;
}  

function setPostViews($postID) {  
    $count_key = 'post_views_count';  
    $count = get_post_meta($postID, $count_key, true);  
    if($count==''){  
        $count = 0;  
        delete_post_meta($postID, $count_key);  
        add_post_meta($postID, $count_key, '0');  
    }else{  
        $count++;  
        update_post_meta($postID, $count_key, $count);  
    }  
}
/**
 * 在接口增加 post_view 字段
 **/ 
add_action( 'rest_api_init', function () {
    register_rest_field( 'post', 'post_view_count', array(
        'get_callback' => function($post) {
            return getPostViews($post['id']);
        },
    ) );
} );
```

在single.php文件放入以下代码，这样每次打开都会把阅读数加一。

```php
<?php setPostViews(get_the_ID()); ?>
```

在需要现实阅读数的地方，加入以下代码

```php
<?php echo getPostViews(get_the_ID()); ?>
```

最后一段函数是把阅读数加入wp rest api的返回。key=post_view_count。  
官方说明[https://developer.wordpress.org/rest-api/extending-the-rest-api/modifying-responses/](https://developer.wordpress.org/rest-api/extending-the-rest-api/modifying-responses/)
<file_sep>---
layout: page
home-title: Welcome to zhonger's blog!
description: Writing, writing, writing ...
---

# 本站使用条款

&emsp;&emsp;H2O-ac（[https://h2o-ac.pages.dev](https://h2o-ac.pages.dev)），以下简称“本站”。

## 隐私政策

&emsp;&emsp;本站非常重视用户隐私和个人信息保护。在您使用本站的过程中，本站可能会收集或使用您的相关信息。在此，将向您说明本站如何收集、使用、保存、共享和转让这些信息。

### 如何收集和使用有关信息

&emsp;&emsp;有关信息包括**网络身份标识信息（浏览器 UA、IP 地址）、地理位置信息（国别或地区）、个人网址**

&emsp;&emsp;在您访问时，仅出于以下目的，使用您的个人信息：

- **阿里 CDN （iconfont）**会收集您的访问信息
- **JSDelivr（前端依赖）**会收集您的访问信息
- **Google Analytics** 会收集您的访问信息
- **Umami（监控）**会收集您的访问信息
- **Disqus（评论）**会收集您的访问信息和评论内容
- **Waline（评论）**会收集您的访问信息和评论内容
- **Busuanzi（页面统计）**会收集您的访问信息

&emsp;&emsp;本站仅出于以下目的，使用您的个人信息：

- 恶意访问识别与攻击排查，用于维护网站
- 网站点击情况监测，用于优化网站内容
- 浏览访问数据的展示

### 如何使用 Cookie 和本地 LocalStorage 存储

&emsp;&emsp;由于本站会基于 Cookie 中的相关信息来为您提供“深色模式切换”、“同意使用 Cookie 提示”功能，该部分 Cookie 会保存在您的浏览器本地。除此之外，本站提供的 PWA 功能会将您访问过的本站页面和图片缓存在设备本地，以便提升您的使用体验。对于这里所有的 Cookie 和 LocalStorage，您都可以随时清除。

&emsp;&emsp;至于如何清除这些内容，您可以根据您所使用的浏览器类型查找相应的方法。

### 如何共享和转让有关信息

&emsp;&emsp;本站不会与任何公司、组织和个人共享或者转让您的个人信息。

## 版权声明

&emsp;&emsp;本站采用 [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.zh) 协议共享所有内容，但如对本站进行转载、复制、翻译等行为，请务必注明原文链接地址以及遵守协议规定的其他条件。

## 链接到本站

&emsp;&emsp;原则上，您可以自由链接到本站的任何页面，但本站可能会根据您网站的内容和链接方式，酌情拒绝链接到本站。链接时请注意以下事项：

- 请指明它是指向本站的链接。
- 禁止使用框架显示本站等使用本站难以被理解为是由本站运营的显示方式。
- 禁止链接到本站作为您网站的广告或者业务。
- 禁止来自违反法律或者公序良俗和道德的网站链接。
- 本站页面的 URL 可能会更改或取消，恕不另行通知。
- 对于因链接而可能发生的任何问题，本站概不负责。

## 链接到其他网站

&emsp;&emsp;本站包含一些外部网站的链接，但本站不对这些独立第三方的网站和服务承担任何义务或责任。

## 关于本站的问题或意见

&emsp;&emsp;如发现某些文章内容存在问题或有什么意见，欢迎在主站使用评论的方式直接发表。也可以通过首页公开的私人邮箱发送邮件联系，或者创建 [Github Issue](https://github.com/zhonger/jekyll-theme-H2O-ac/issues/new) 的方式联系。但是，考虑到某些情况，您所反映的问题或者意见可能不会公开发表或补充在本站。

## 免责声明

&emsp;&emsp;通过使用本站，我们认为您已同意上述内容。由于各种情况，此页面可能随时会部分修改或添加内容。请注意，如果您在更新本页面内容后继续使用本站，则视为您已知悉并且认可了更新内容。**不过，如果有非常重要的更改，本站将会在首页明确指出使用条款已更新。**
<file_sep>---
layout: post
title: 使用kettle生成GeoHash
categories: Develop
description: 通过给kettle添加第三方库，编写java脚本生成GeoHash，并将结果写入数据库
keywords: kettle, spoon, geohash
---

关于GeoHash是什么，有什么用，可以参考我的另一篇文章[使用GeoHash查找附近的药店]({% post_url 2019-11-14-使用GeoHash查找附近的药店 %})。或者自行Google。  
# 必备技能
要想使用GeoHash，首先得想办法，给数据库中的经纬度生成GeoHash。好消息是，很多数据库软件都提供对应的函数，比如mysql的[12.16.10 Spatial Geohash Functions](https://dev.mysql.com/doc/refman/8.0/en/spatial-geohash-functions.html)。不过坏消息是，它家亲戚MariaDB并没有对应的方法。对于没有提供原生支持的数据库，这里给出的答案就是用kettle做数据清洗，把GeoHash补写进数据库。  

# 给kettle添加第三方jar包
kettle生成GeoHash只能通过编写脚本实现，鉴于网上已经有很多java脚本可以用了，所以我们导入kettle，就不自己写了。  
## 将.java打包成.jar
### 下载Class文件 
[GeoHashHelper.java](https://github.com/GongDexing/Geohash/blob/master/src/cn/net/communion/GeoHashHelper.java)   
### 编译Class文件
```sh
javac GeoHashHelper.class
```
### 创建MANIFEST.MF描述文件
```sh
echo Main-Class: GeoHashHelper > test.mf
```
### 打包jar
```sh
jar cvfm GeoHashHelper.jar test.mf GeoHashHelper.class
```
大功告成

## 将jar放入kettle
将GeoHashHelper.jar放到kettle的安装目录/lib下面，重新启动kettle。恭喜你，你已经成功的添加了第三方jar包，现在可以在转换中使用了。


# 编写java脚本生成GeoHash
![kttle geohash java](/images/kettle-geohash.jpg)  

```java
public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException {
  if (first) {
    first = false;

    /* TODO: Your code here. (Using info fields)

    FieldHelper infoField = get(Fields.Info, "info_field_name");

    RowSet infoStream = findInfoRowSet("info_stream_tag");

    Object[] infoRow = null;

    int infoRowCount = 0;

    // Read all rows from info step before calling getRow() method, which returns first row from any
    // input rowset. As rowMeta for info and input steps varies getRow() can lead to errors.
    while((infoRow = getRowFrom(infoStream)) != null){

      // do something with info data
      infoRowCount++;
    }
    */
  }

  Object[] r = getRow();

  if (r == null) {
    setOutputDone();
    return false;
  }

  // It is always safest to call createOutputRow() to ensure that your output row's Object[] is large
  // enough to handle any new fields you are creating in this step.
  r = createOutputRow(r, data.outputRowMeta.size());

  /* TODO: Your code here. (See Sample)

  // Get the value from an input field
  String foobar = get(Fields.In, "a_fieldname").getString(r);

  foobar += "bar";
    
  // Set a value in a new output field
  get(Fields.Out, "output_fieldname").setValue(r, foobar);

  */
    String latitudeStr = get(Fields.In, "latitude").getString(r);
    String longitudeStr = get(Fields.In, "longitude").getString(r);
    Double latitude = Double.parseDouble(latitudeStr);
    Double longitude = Double.parseDouble(longitudeStr);

    GeoHashHelper  geoHashHelper = new GeoHashHelper();
    String geoHash = geoHashHelper.encode(latitude, longitude);

    get(Fields.Out, "geohash").setValue(r, geoHash);

  // Send the row on to the next step.
  putRow(data.outputRowMeta, r);

  return true;
}

```<file_sep>---
layout: post
title: shadowsocks服务器安装
categories: Operation
description: shadowsocks服务器安装
keywords: Linux, Porxy
---

为了防止哪天无法查询官方文档，特在此处记录。

#### 安装服务器

```sh
$ pip install shadowsocks
```

#### 单一用户配置文件

```json
{
    "server":"my_server_ip",
    "server_port":8388,
    "local_port":1080,
    "password":"<PASSWORD>!",
    "timeout":600,
    "method":"chacha20-ietf-poly1305"
}
```

#### 多用户配置文件

```json
{
    "server": "0.0.0.0",
    "port_password": {
        "<PASSWORD>": "<PASSWORD>",
        "<PASSWORD>": "<PASSWORD>"
    },
    "timeout": 300,
    "method": "aes-256-cfb"
}
```

#### 启动脚本

```sh
ssserver -c config.json -d start
```

#### 停止脚本

```sh
ssserver -d stop
```<file_sep>---
layout: post
title: VMware在Linux客户机中装载共享文件夹
categories: Operation
description: VMware转载共享文件夹
keywords: linux，vmware
---

官网原文地址 [https://docs.vmware.com/cn/VMware-Workstation-Pro/14.0/com.vmware.ws.using.doc/GUID-AB5C80FE-9B8A-4899-8186-3DB8201B1758.html](https://docs.vmware.com/cn/VMware-Workstation-Pro/14.0/com.vmware.ws.using.doc/GUID-AB5C80FE-9B8A-4899-8186-3DB8201B1758.html)

# 1、在vmware中启用共享文件夹

![共享文件夹](/images/共享文件夹.jpg)

# 2、使用以下脚本挂载

```sh
/usr/bin/vmhgfs-fuse .host:/share /home/share -o subtype=vmhgfs-fuse,allow_other
```

这样就可以了，共享文件夹挂到了/home/share下面。  
至于其它问题，比如软件版本的问题，请查询官方文档。<file_sep>---
layout: post
title: GIT Bash Solarized 主题
categories: Develop
description: docker安装及常用脚本
keywords: Windows, GIT, Bash, Solarized
---

其实GIT Bash这玩意挺好用的，只要修改一下UI，生活就会变得美好起来。不过前提是安装的时候你选择的是mintty这个终端。

#### 下载Solarized主题配色文件

原始地址 [https://github.com/mavnn/mintty-colors-solarized](https://github.com/mavnn/mintty-colors-solarized){:target="_blank"}

#### 编辑配置文件

文件地址 ~/.minttyrc， 如果没有这个文件的话就新建一个。填入你想要的主题，以Solarized Dark为例，文件内容如下：

```text
ForegroundColour=131, 148, 150
BackgroundColour=  0,  43,  54
CursorColour=    220,  50,  47

Black=             7,  54,  66
BoldBlack=         0,  43,  54
Red=             220,  50,  47
BoldRed=         203,  75,  22
Green=           133, 153,   0
BoldGreen=        88, 110, 117
Yellow=          181, 137,   0
BoldYellow=      101, 123, 131
Blue=             38, 139, 210
BoldBlue=        131, 148, 150
Magenta=         211,  54, 130
BoldMagenta=     108, 113, 196
Cyan=             42, 161, 152
BoldCyan=        147, 161, 161
White=           238, 232, 213
BoldWhite=       253, 246, 227
```

#### 修改一下字体

其实这还不够，修改一下字体，效果就个更完美了。右键单击标题栏->Options

![GIT-Bash-options-text](/images/GIT-Bash-options-text.jpg)

#### 重启GIT Bash

下面是效果图:

![GIT-Bash-Solarized](/images/GIT-Bash-Solarized.jpg)

示例配置文件
[.minttyrc]({{ site.url }}/downloads/minttyrc)
> 下载后请把文件改成 .minttyrc<file_sep>---
layout: post
title: 修正word样式黑框
categories: 日常操作
description: 修正word样式黑框
keywords: Windows, word, 样式，字体
---

一个困扰我很久的问题，就是样式前面的编码，有时会显示不出来，就一个黑色的方块，不过大纲那边是没问题的。修改多级列表的时候，提示“数字必须介于1和600之间”。这里记录知乎Samuel Chen的答案，原文地址：[https://www.zhihu.com/question/38985919](https://www.zhihu.com/question/38985919){:target="_blank"}  

这里本质上是因为模板里的字体文件损坏了，执行下面的宏代码就可以修复了

```vb
Sub repair()

For Each templ In ActiveDocument.ListTemplates

For Each lev In templ.ListLevels

lev.Font.Reset

Next lev

Next templ

End Sub
```

2016的用户要执行宏，首先要打开“开发工具”选项卡。可以在word选项的自定义功能区打开。
<file_sep>---
layout: page
home-title: 一年四季
description: 不以物喜，不以己悲，感时花溅泪，恨别鸟惊心
cover: 'https://images.unsplash.com/photo-1475924156734-496f6cac6ec1'
cover_author: '<NAME>'
cover_author_link: 'https://unsplash.com/@quinoal'
---

# info
> citation "H&Y"
> 灿霞落雁，琳宇秋芳，真显好。素纱画梁，君兰春色，更是佳。<file_sep>---
layout: post
title: promise微信小程序api
categories: Develop
description: 将微信小程序api promise化，使用async ... await ...的方式对api进行同步调用。
keywords: 微信小程序, http, fly, flyio, mpvue
---

# why promisify
微信小程序API提供的大部分是异步接口，只有极少数同时提供两个接口，例如数据缓存的set storage  

```js
// 异步接口
wx.setStorage({
  key:"key",
  data:"value",
  success(res){

  },
  fail(err){

  }
})
// 同步接口
try {
  wx.setStorageSync('key', 'value')
} catch (e) { }
```

## 异步代码示例

使用异步接口，总有一个success回调。当我们在操作一个顺序执行的业务场景时，这代码看起来就相当繁琐且不好处理。差不多像这样

```js
// 获取当前的经纬度，并记录到本地缓存，然后请求接口数据
function test(){
  wx.getLocation({
  type: 'wgs84',
  success (res) {
      const latitude = res.latitude
      const longitude = res.longitude
      const speed = res.speed
      const accuracy = res.accuracy
      wx.request({
        url: 'test.php', //仅为示例，并非真实的接口地址
        data: {
          latitude: latitude,
          longitude: longitude
        },
        header: {
          'content-type': 'application/json' // 默认值
        },
        success (res) {
          wx.setStorage({
            key:"key",
            data:"value",
            success(){
              console.log("all right");
            }
          })        
        }
      })
    }
  })
}
```

是不是让人眼花缭乱，下面我们就将接口写成promise的方式，使用 async ... await ... 来改写这段代码

# 编写promisify.js

下面这段代码是google到的，并作了一定的修改。本人的项目采用mpvue框架，使用了import export，如果使用其他框架，请做适当修改。  

```js
function wxPromisify(api) {
  return (options, ...params) => {
      return new Promise((resolve, reject) => {
          api(Object.assign({}, options, { success: resolve, fail: reject }), ...params);
      });
  }
}

export default wxPromisify
```

## 使用promisify
```js
// 注意这里的相对地址，在我的项目中
// promisify.js位于 /src/utils/promisify.js
// 示例代码位于 /src/pages/drugstore/index.vue
import wxPromisify from "../../utils/promisify";

var wxGetLocation = wxPromisify(wx.getLocation);
var res = await wxGetLocation({});
```

# 同步代码示例

这里将上文的异步代码改写成同步代码

```js
import wxPromisify from "../../utils/promisify";

async function test(){
  var wxRequest = wxPromisify(wx.request);
  var wxGetLocation = wxPromisify(wx.getLocation);
  var wxSetStorage = wxPromisify(wx.setStorage);

  var res1 = await wxGetLocation({});
  var res2 = await wxRequest({latitude:res1.latitude, longitude:res1.longitude});
  var res3 = await wxSetStorage({key: "location", value: {} });
}
```<file_sep>---
layout: blog
home-title: 一年四季
description: 不以物喜，不以己悲，感时花溅泪，恨别鸟惊心
pagination: 
  enabled: true
cover: 'https://images.unsplash.com/photo-1475924156734-496f6cac6ec1'
cover_author: '<NAME>'
cover_author_link: 'https://unsplash.com/@quinoal'
--- <file_sep>---
layout: post
title: js php rsa 长文本加解密
categories: Develop
description: js php rsa 长文本加解密
keywords: js, php, rsa, openssl
---

公开的rsa加解密工具的加密长度都是有限制，文本过长加密的结果就会变成null。这篇文章主要是依托目前主流的js，php加解密程序，通过编写自定义的加解密函数，实现长文本的加解密。  

# 1 javascript的rsa长文本加解密

# 1.1 首先下载JSEncrypt这个库
我们需要基于这个库写一个长文本的加密函数。
[http://travistidwell.com/jsencrypt/](http://travistidwell.com/jsencrypt/)  

# 1.2 修改jsencrypt.js文件，加入新的加解密函数
下载后的压缩包里有很多目录，我们进入bin目录后，有两个js文件，选择jsencrypt.js这个文件，这是个未压缩的版本，方便我们将代码插进去。  

以下便是用于加解密的两个函数，请插入到文件中合适的位置（可以搜索“JSEncrypt.prototype”，你就会发现底下有一大堆函数，把我们的新函数也放在这里就可以了）。

```js
//分段加密的方法
// The right encryption code
JSEncrypt.prototype.encryptLong = function(string) {  
  var k = this.getKey();
  var maxLength = (((k.n.bitLength()+7)>>3)-11);
  // var maxLength = 117;
 
  try {
    var lt = "";
    var ct = "";
 
    if (string.length > maxLength) {
      lt = string.match(/.{1,117}/g);
      lt.forEach(function(entry) {
        var t1 = k.encrypt(entry);
        ct += t1 ;
      });
      return hex2b64(ct);
    }
    var t = k.encrypt(string);
    var y = hex2b64(t);
    return y;
  } catch (ex) {
    return false;
  }
};

//分段解密的方法
// The error decryption code
JSEncrypt.prototype.decryptLong = function(string) {
  var k = this.getKey();
  var maxLength = ((k.n.bitLength()+7)>>3);
  // var maxLength = 128;
  try {
    var string = b64tohex(string);
    var ct = "";
    if (string.length > maxLength) {
      var lt = string.match(/.{1,128}/g);
      lt.forEach(function(entry) {
        var t1 = k.decrypt(entry);
        ct += t1;
      });
    }
    var y = k.decrypt(b64tohex(string));
    return y;
  } catch (ex) {
    return false;
  }
};
```

## 1.3 使用
修改后的用法跟原来的JSEncrypt的用法一样，只是把原来使用的加密函数encrypt换成encryptLong就可以了。下面是示例代码：
```js
  var encrypt = new JSEncrypt();
  encrypt.setPublicKey(public_key);//设置公有key
  data = encrypt.encryptLong(data);
```

# 2 php的rsa长文本加解密

```php
//长文本加密方法
function encrypt($originalData){
  $key_content = "-----BEGIN PUBLIC KEY-----

  -----END PUBLIC KEY-----
  ";  

  $rsaPublicKey = openssl_get_publickey($key_content);  

  foreach (str_split($originalData, 117) as $chunk) {
    openssl_public_encrypt($chunk, $encryptData, $rsaPublicKey);

    $crypto .= $encryptData;
  }

  return base64_encode($crypto);
}

//长文本的解密方法
function decryptLong($encryptData){
  $key_content = "-----BEGIN RSA PRIVATE KEY-----

-----END RSA PRIVATE KEY-----
";

  $rsaPrivateKey    = openssl_get_privatekey($key_content);

  $crypto = '';

  foreach (str_split(base64_decode($encryptData), 128) as $chunk) {

    openssl_private_decrypt($chunk, $decryptData, $rsaPrivateKey);

    $crypto .= $decryptData;
  }

  return $crypto;
}
```<file_sep>---
layout: post
title: php-fpm-xdebug-docker phpstorm联调
categories: Develop
description: php-fpm-xdebug-docker phpstorm联调
keywords: php, fpm, xdebug, docker, idea
---

之前我写过一篇文章，如何配置phpstorm xdebug联调，当时没有用到docker，是用本地的php跟apache。[phpstorm xdebug 调试](/develop/2019/06/18/phpstrom-xdebug-调试.html)  
正常开发这样应该就够了，但如果你手上有不少项目，而且还是用不同的php版本运行的。搭建多个本地的调试环境估计会把你搞得晕头转向。  
在一台机子分割多个运行环境，我们会想到使用容器，自由调用不同版本的php，我们会想到使用php-fpm。当然还有开发利器phpstrom。将docker，xdebug，php-fpm, phpstorm结合起来就是这篇文章宗旨。

# xdebug remote 原理
有不少同学不理解xdebug.remote是什么意思，它是xdebug提供的一种远程调试模式，也就是服务器跟你调试的代码不在一个地方。idekey就是一个标志，当你在querystring中加入?XDEBUG_SESSION_START=PHPSTORM，xdebug就会往remote_host:remote_port发送调试信息。一般来说，我们会在phpstorm配置监听，接收来自xdebug的信息，这样就可以实现服务器联调了。  
这也可以解答很多同学的疑问——docker容器跟代码不在同个环境，要怎么调试？答案就是他们不需要在同个地方，因为xdebug会把debug信息送出来，remote_host:remote_port可以是任何地方。当然还有一个问题，就是phpstrom接受到debug信息后如何跟项目代码对应，这里面有一个map的配置，下面会说明map要怎么做。

# 制作php-fpm-xdebug-docker镜像

请阅读我之前写的这篇文章[制作php-fpm-xdebug-docker镜像](/develop/operation/2019/11/27/php-fpm-xdebug-docker.html)。这篇文章还包括了怎么配置docker-cmpose跟nginx。

# 配置phpstorm
以下这几个步骤跟[phpstorm xdebug 调试](/develop/2019/06/18/phpstrom-xdebug-调试.html)一样，所以我使用了那篇文章的截图。只是在配置server那里多了一个选项，要打开Use path mappings。

## 配置 debug port
打开 File -\> Settings -\> PHP -\> Debug  
确认debug port跟phpinfo -\> xdebug.remote\_port 一致
![phpstorm-settings-php-debug](/images/phpstorm-settings-php-debug.jpg)

## 配置 DBGp Proxy
打开 File -\> Settings -\> PHP -\> Debug -\> DBGp Proxy  
填入nginx站点的信息,截图是引用之前文章的，这一次我们用的是85端口。
![phpstorm-settings-php-dbgp-proxy](/images/phpstorm-settings-php-dbgp-proxy.jpg)

## 配置 Debug Configurations
打开 Run -\> Edit Configurations  
添加 PHP WEB Page  
![phpstorm-php-debug-configurations](/images/phpstorm-php-debug-configurations.jpg)

## 配置 Servers
打开 File -\> Settings -\> PHP -\> Servers  
添加 nginx站点
![phpstorm-xdebug-servers](/images/phpstorm-xdebug-servers.jpg) 

这里我们写了一些map。  
/usr/share/nginx/html/pingjia/data 是nginx那边的目录，还记得nginx的配置吗```root   /usr/share/nginx/html/pingjia```。  
data是phpstorm项目的代码。  
如果你没法下断点，提示phpstorm找不到对应的代码，你就来这里添加映射就可以了。

# 开始调试
![phpstorm-php-xdebug-start](/images/phpstorm-php-xdebug-start.jpg)

其实你只要打开监听就可以了。然后自己打开浏览器，比如你要调试页面是 http://192.168.204.129:85/member/login.php，访问地址加上 ?XDEBUG_SESSION_START=PHPSTORM，变成 http://192.168.204.129:85/member/login.php?XDEBUG_SESSION_START=PHPSTORM 就可以了。


<file_sep>---
layout: post
title: RESTFUL错误返回设计
categories: Develop
description: 本文描述一种接口交互方案，主要用于协调前后端的数据交互，提高系统容错性。
keywords: java, php, exception, restful
---

# 区分正确返回与错误返回
大部分平台像微博，腾讯，都会把接口返回分为正常返回跟异常返回，在这里定义：  
1、正常返回根据业务情况不同返回不同的数据结构  
2、错误返回提供固定的数据结构用于向用户或开发人员提供异常的具体信息。  

# 基于HTTP的错误识别 
利用HTTP的状态码，可以很容易的区分接口返回是否错误。在这里定义：  
1、http code = 200，正确返回  
2、http code != 200，错误返回  
为什么要这样区分？因为非200的返回会**触发**http客户端的**异常**机制，方便前端编写代码对错误进行统一处理。

# 静态错误跟动态错误
静态错误：系统主动抛出的错误，说明客户端请求不正确，需要进行调整，方可获取正确的结果。例如获取用户资料的接口，返回用户未登录的错误，客户端在执行登录后便可获取正确的返回。  
动态错误：系统被动抛出的错误，同时将错误详细说明记录到系统日志，说明服务器端遭遇无法处置的错误，需要修bug了。例如获取用户资料的接口，返回索引超出的错误，客户端自己无法修复该错误。
## 错误代码
静态错误跟动态错误都有对应的错误代码。区别是静态错误代码是已知的，平台会提供错误代码表备查，客户端可以根据错误代码表的说明自行处置。动态错误代码是动态生成的，客户端接收到动态错误后，需要将代码提供给后台开发人员，开发人员可以在系统日志中找到对应的错误说明，修复问题。  
静态错误代码格式：语义化的字符串，例如 User.01  
动态错误代码格式：一串不重复的数字，例如 201911111212   
## 错误代码表
平台提供错误代码表（静态错误代码表）供客户端备查。  

# 错误返回的数据结构
```json
{
    "path": "http://192.168.204.129:80/ihu/public/drug-used-plan/upload",
    "error_code": "User.02",
    "error_message": "用户未登录。",
    "error_detail": "access_token:<KEY>",
    "query_string": "XDEBUG_SESSION_START=PHPSTORM",
    "content": "drug_plan=%E6%96%99%E7%AD%92"
}
```
错误返回一共有四个字段：  
1、path：发生错误的接口地址。    
2、error_code：错误代码，这里分静态错误代码跟动态错误代码。  
静态错误代码可以在错误代码列表中找到，是系统主动抛出的错误，非后台程序异常。前端只要调整请求方式，便能避免该错误。  
动态错误代码属于后台程序异常，需要进行修复才能正常使用，对应的错误详情可以在系统日志中找到。  
3、error_message：错误信息，该信息较为简短，是提供给用户参考信息或提示用户下一步的操作。  
4、error_detail：错误详细，该信息提供必要的调试信息供开发人员调试错误。比如示例中返回错误，用户未登录，这里提供access token供开发人员确认系统是否有问题。  
5、query_string, content：这里将request的对应query string跟body的内容返回，方便排查错误。  

# 前后端交互整体架构图
![交互架构图](/images/错误返回设计.png)

# 案例
本人开发的项目，后台是thinphp5.1，前台是微信小程序(flyio)。  
根据这套设计规则，自定义了thinkphp5.1的异常处理机制，用来返回特定的错误信息。在flyio定义了拦截机制，对错误进行统一处理。详见以下两篇文章。  
[自定义thinkphp5.1异常]({% post_url 2019-11-13-自定义thinkphp5异常 %})  
[自定义微信小程序flyio响应拦截]({% post_url 2019-11-14-自定义微信小程序Flyio响应拦截 %})
<file_sep>---
layout: post
title: fedora31 cgroupsv2 disable
categories: Develop Operation
description: 关闭cgroupsv2
keywords: linux fedora cgroups
---

fedora31 开启了 CGroupsV2 ，然后 docker 还不支持。

要回到V1版本，需要将 systemd.unified_cgroup_hierarchy=0 加入内核启动选项

打开 /etc/default/grub 在 GRUB_CMDLINE_LINUX 加入这个选项

更新grub配置 grub2-mkconfig -o /boot/grub2/grub.cfg

重启计算机<file_sep>---
layout: post
title: 如何给jekyll markdown里的table加入样式
categories: Develop
description: 给markdown里的table加入样式的语法，目前只在jekyll测试过。其它引擎是否可用，尚未知。
keywords: jekyll, markdown, table, style
---

下面的代码示例是在markdown中写一个表格，并且给这个表格应用t1样式。这段代码在jekyll中可以正常使用，其它引擎就不知道了。  

```markdown 
||||
|---:|:---:|:---|
|NorthWest|North|NorthEast|
|West|<span style="color:green">Me</span>|East|
|SouthWest|South|SouthEast|
{:.t1}

<style>
.t1{
    width:auto;
    border: none;
}
.t1 td{
    border: none;
    padding:10px;
}
.t1 th{
    border:none;
}
</style>
```<file_sep>---
layout: post
title: Jenkins安装
categories: Operation
description: Jenkins安装
keywords: Linux, Jenkins, Docker
---

Jenkins是一款免费的持续集成跟持续交付软件。关于他的安装，网络上有不少。这次我们用dokcer来安装。

我们运行jenkins bule的镜像，然后配置HOST机为jenkins的一个节点。

关于持续集成跟持续交付，我们通过在项目GIT仓库里放置Jenkinsfile文件。每次执行集成，Jenkins都会把项目拉下来，执行里面的jenkinsfile脚本。

####  1、运行Jenkins Blue Ocean

##### 编辑docker compose文件

```yaml
version: '2'

services:
  jenkins:
    image: jenkinsci/blueocean:latest
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - /home/docker/jenkins-compose/jenkins_home:/var/jenkins_home
      - /etc/localtime:/etc/localtime:ro
    environment:
     - TZ=Asia/Shanghai
    container_name: jenkins
```

##### 启动Jenkins

```bash
$ docker-compose up -d
```

#### 2、添加主机为Jenkins从节点

浏览器访问 localhost:8080, 第一次进入管理配置页面，需要密码，可以通过 docker logs jenkins 找到。

第一步选择安装建议的插件，这样可以省下不少麻烦。

##### 添加SSH私钥

首先要有一份SSH证书，可以根据 [https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/](https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/){:target="_blank"} 生成一份。

在首页的左侧依次单击 Credentials >> System >> Global credentials(unrestricted) >> Add Credentials

* Kind 选择 SSH Username with private key
* Scope 选择 Global(Jenkins, nodes, items, all child items, etc)
* Username 填写证书可以登录的用户名，我是把证书分配给root用户，所以这里填root
* Private Key 选择 Enter directly，然后把私钥用记事本打开，把里面的文本填进去
* Passphrase 如果私钥有加密，就填私钥的密码，如果没有，就不用填
* ID jenkins会自己生成一个
* Description 可以不填

最后点OK就可以了

##### 新建节点

因为Jenkins运行在Docker里面，很多命令都无法执行，特别是Docker build命令，所以我们需要一台环境完整的机子来执行持续集成。

> **注意：** _这台机子需要安装JDK_

在首页的左侧依次单击 系统管理 >> 管理节点 >> 新建节点

* 节点名称 填 localhost （你喜欢的任何名称)
* 选择 Permanent Agent

点击 OK 进入节点配置

* 描述 可以不填
* of executors 配置该节点可以同时运行多少个任务
* 远程工作目录 配置jenkins在那台机子上使用的目录，我是填 /home/jenkins_home
* 标签 用来给节点归类，jenkinsfile文件可以指定在哪个node执行[例如 node(docker)，docker就是标签]，如果有多个标签，用空格隔开就可以了。
* 用法 选择 尽可能的使用这个节点。因为我只有这一个节点。
* 启动方法 选择 Launch slave agents via SSH。Credentials选择刚才那一个。需要把SSH公钥安装到节点那台机子上。Host Key Verification Strategy 选择 Manually trusted key Verification Strategy (自动信任)
* Availability 选择 Keep this agent online as much as possible

#### 3、添加持续集成项目

在Jenkins首页选择 _新建_ >> _Mutibranch Pipeline_ 填个名字，点击OK开始配置

主要填以下GIT仓库的位置，保证可以访问就可以了，在 _Build Configuration_ 选择 by Jenkinsfile， _Script Path_ （我是填 jenkins/Jenkinsfile）是相对于项目的根目录的。

##### 项目配置说明

以下是我的项目的目录结构

```bash
[root@bogon simple-service-webapp]# ls
jenkins  pom.xml  src  target
[root@bogon simple-service-webapp]# tree jenkins/
jenkins/
├── build.sh
├── docker-build.sh
├── Dockerfile
├── Jenkinsfile
└── simple-service-webapp.war

0 directories, 5 files
[root@bogon simple-service-webapp]# cat jenkins/Jenkinsfile 
node('docker') {
	stage('Build') {
		def commitHash = checkout(scm).GIT_COMMIT
		sh 'sh ./jenkins/build.sh'
	}
	stage('Docker Build'){
		sh 'sh ./jenkins/docker-build.sh'
	}
}
```

我自己编写了编译命令 build.sh 跟打包上传docker的命令 docker-build.sh，然后通过配置Jenkinsfile来执行这些命令。
需要注意的是，开始执行之前要执行 checkout scm，不然Jenkins是找不到我在项目中包含的这些命令的。

> 剩下的就自己研究了。
<file_sep>---
layout: post
title: docker容器因文件损坏无法删除的处理办法
categories: Operation
description: docker容器因文件损坏无法删除的处理办法
keywords: linux，docker
---

容器文件损坏，经常导致容器无法操作。正常的docker命令已经无法操控这台容器了，无法关闭、重启、删除。

如果操作容器时，遇到类似的错误 b'devicemapper: Error running deviceCreate (CreateSnapDeviceRaw) dm_task_run failed'

可以通过以下操作将容器删除，重建

### 1、关闭docker

```sh
service docker stop
```

### 2、删除容器文件

在/var/lib/docker/containers里找到你的容器，把整个文件夹删了

### 3、重新整理容器元数据

```sh
thin_check /var/lib/docker/devicemapper/devicemapper/metadata
thin_check --clear-needs-check-flag /var/lib/docker/devicemapper/devicemapper/metadata
```

### 4、重启docker

```sh
service docker start
```<file_sep>---
layout: post
title: 自定义thinkphp5.1异常
categories: Develop
description: 本文通过建立自定义的Exception跟Handle，从而达到自定义错误返回的效果，及拦截系统运行过程中的随机异常。  
keywords: thinkphp5.1, php, exception
---

本文通过建立自定义的Exception跟Handle，从而达到自定义错误返回的效果，及拦截系统运行过程中的随机异常。  
整体设计思路请参见[RESTFUL错误返回设计]({% post_url 2019-11-13-RESTFUL异常返回定义 %})  

# 新建异常处理目录
在 application->common 新建目录 exception。  

# 定义Base Exception Class
在exception目录下，新建PHP类 BaseException.php  

```php
<?php


namespace app\common\exception;


use think\Exception;

class BaseException extends Exception
{
    public $httpCode;
    public $errorCode;
    public $errorMessage;
    public $errorDetail;

    public function __construct($httpCode, $errorCode, $errorMessage, $errorDetail)
    {
        $this->httpCode = $httpCode;

        $this->errorCode = $errorCode;

        $this->errorMessage = $errorMessage;

        $this->errorDetail = $errorDetail;
    }
}
```

# 定义 Custom Exception Class 继承 BaseException  
这里用UserException作为示例。为什么要取名UserException，是为了将不同的异常拆分到不同的类文件中，这样使用的时候比较方便，不会所有异常都塞在同个文件中。  
在exception目录下，新建PHP类 UserException.php  

```php
<?php


namespace app\common\exception;


class UserExcepiton extends BaseException
{
    /**
     * UserExcepiton constructor.
     * @param $httpCode
     * @param $errorCode
     * @param $errorMessage
     * @param $errorDetail
     */
    public function __construct($httpCode, $errorCode, $errorMessage, $errorDetail)
    {
        parent::__construct($httpCode, $errorCode, $errorMessage, $errorDetail);
    }

    // 微信授权登录失败

    /**
     * @param $wxErrcode
     * @param $wxErrmsg
     * @throws UserExcepiton
     */
    public static function wexinAuthFail($wxErrcode, $wxErrmsg)
    {
        throw new UserExcepiton('400', 'User.01',
            '微信授权失败。', '微信错误代码：' . $wxErrcode . '微信错误信息：' . $wxErrmsg);
    }

    /**
     * @param $accessToken
     * @throws UserExcepiton
     */
    public static function notLogin($accessToken){
        throw new UserExcepiton('400', 'User.02','用户未登录。',
            'access_token:'.$accessToken);
    }
}
```

这里定义了两个方法，下面我们会讲到怎么抛出这两个异常，并被处理成错误返回。

# 定义Handle类
这个是很关键，它通过继承thinkphp提供的Exception/Handle,从而在response前拦截到所有异常并做进一步的处理。  
在exception目录下，新建PHP类 CustomHandle.php  

```php
<?php


namespace app\common\exception;


use Exception;
use think\exception\Handle;

class CustomHandle extends Handle
{
  private $httpCode;
  private $errorCode;
  private $errorMessage;
  private $errorDetail;

  public function render(Exception $e)
  {
    if ($e instanceof BaseException) {
      $this->errorCode = $e->errorCode;
      $this->errorMessage = $e->errorMessage;
      $this->httpCode = $e->httpCode;
      $this->errorDetail = $e->errorDetail;
    } else {
      if (config('app_debug')) {
        return parent::render($e);
      }

      $this->httpCode = 500;
      $this->errorCode = "000000";
      $this->errorMessage = "服务器未知错误";
      $this->errorDetail = $e->getMessage();
    }

    $path = "http://" . request()->host() . ":" . request()->port() . request()->baseUrl();
    $content = request()->getContent();
    $queryString = request()->query();

    $result = [
      'path'          => $path,
      'error_code'    => $this->errorCode,
      'error_message' => $this->errorMessage,
      'error_detail'  => $this->errorDetail,
      'query_string'  => $queryString,
      'content'       => $content
    ];

    return json($result, $this->httpCode);
  }
}
```

说明：  
1、这里通过 ``` if ($e instanceof BaseException) ``` 判断是否是我们自定义的异常，如果不是。那是系统抛出，说明我们遇到了未知错误（BUG）。  
2、如果遇到了未知错误，还要继续 ``` if (config('app_debug')) ``` 判断当前是否处于调试模式，如果是就不处理，返回thinkphp的调试信息，如果不是就处理成统一的错误返回。  
3、这部分程序还没有最终完成，按照设计要求，当遇到未知异常时，应该返回动态错误代码并记录系统日志，这里并没有，而是简单的返回000000。这样并不利于后期追溯问题。  
# 最终的目录结构
![thinkphp异常目录结构](/images/thinkphp异常目录结构.jpg)  

# 抛出自定义异常
定义完上面这三个类后，我们就可以在程序中任意位置抛出这些异常，无论是db层，service层，还是controller层，都会得到响应，并且终止当前程序，向客户端返回错误信息。  
下面示例是请求一个数据上传接口，在access token检验不通过时，返回用户未登录的错误信息。注意，这里的错误返回是service层check_login抛出的，而不是controller层。这也是采用异常处理机制最大的好处，就是你可以在任何地方终止掉程序。

```php
-- Controller
/**
* @route('/drug-used-plan/upload')
*/
public
function upload()
{
    $accessToken = explode(' ', $this->request->header('Authorization'))[1];
    $loginStatus = $this->userService->check_login($accessToken);
    $userId = $loginStatus->user_id;

    $drug_plan = $this->request->post('drug_plan');

    $this->drugUsedPlanService->uploadPlan($userId, $drug_plan);

    return json(CustomResponse::commonResponse());
}

-- Service
public function check_login($accessToken)
{
    $loginStatus = $this->getLoginStatusFromCache($accessToken);

    if ($loginStatus) {
        return $loginStatus;
    } else {
        // 抛出异常
        throw UserExcepiton::notLogin($accessToken);
    }
}
```

<file_sep>---
layout: post
title: 修改Docker的时区为本地时间
categories: Develop
description: 修改Docker的时区为本地时间
keywords: Linux, Docker
---

容器的时间跟主机的时间的保持一致的,不过它的时区是用UTC,并不是本地的时区。

有三种方式可以修改容器的时区：

#### 1、读取本地的时区配置文件

##### *命令行*

```bash
$ docker run -v /etc/localtime:/etc/localtime:ro
```

##### *docker compose*

```text
    volumes:
     - /etc/localtime:/etc/localtime:ro
```

#### 2、设置环境变量

##### *命令行*

```bash
$ docker run -env TZ=Asia/Shanghai
```

##### *docker compose*

```text
    environment:
     - TZ=Asia/Shanghai
```

#### 3、合并1跟2的配置

之前遇到tomcat容器，容器时区已经改变了，java应用的时区还不变。所以一般两种方式一起做。

##### *命令行*

```bash
$ docker run -v /etc/localtime:/etc/localtime:ro --env TZ=Asia/Shanghai
```

##### *docker compose*

```text
    volumes:
     - /etc/localtime:/etc/localtime:ro
    environment:
     - TZ=Asia/Shanghai
```<file_sep>---
layout: post
title: thinkphp5.1使用memcahced
categories: Develop
description: thinkphp5.1使用memcahced
keywords: thinkphp, php, memcahed, cache
---

如何在thinkphp5.1中使用缓存可以参考[官方文档](https://www.kancloud.cn/manual/thinkphp5_1/354116)  
/config/cache.php 
```php
return [
    // 驱动方式
    'type'   => 'memcached',
    // 缓存保存目录
    'path'   => '',
    // 缓存前缀
    'prefix' => '',
    // 缓存有效期 0表示永久缓存
    'expire' => 0,
    'host'   => '192.168.204.128',
    'port'   => '11211'
];
```
看到没有? ```'type'   => 'memcached'```，虽然官方文档写的是```内置支持的缓存类型包括file、memcache、wincache、sqlite、redis和xcache。```，不过memcached也是支持的，敢敢的写上去。<file_sep><div align="center">

# jekyll-theme-H2O-ac

[![Gem Version](https://badge.fury.io/rb/jekyll-theme-h2o-ac.svg)](https://badge.fury.io/rb/jekyll-theme-h2o-ac)
[![pages-build-deployment](https://github.com/zhonger/jekyll-theme-H2O-ac/actions/workflows/pages/pages-build-deployment/badge.svg?branch=gh-pages)](https://github.com/zhonger/jekyll-theme-H2O-ac/actions/workflows/pages/pages-build-deployment)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/60e1e5fb75b8411da3df2fbed7243aa6)](https://www.codacy.com/gh/zhonger/jekyll-theme-H2O-ac/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=zhonger/jekyll-theme-H2O-ac&amp;utm_campaign=Badge_Grade)
[![GitHub license](https://img.shields.io/github/license/zhonger/jekyll-theme-H2O-ac)](https://github.com/zhonger/jekyll-theme-H2O-ac/blob/master/LICENSE)
![Jekyll Version](https://img.shields.io/badge/Jekyll-4.2.1-blue)
![Ruby Version](https://img.shields.io/badge/Ruby-3.1.0-blue)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fzhonger%2Fjekyll-theme-H2O-ac.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fzhonger%2Fjekyll-theme-H2O-ac?ref=badge_shield)

基于 Jekyll 和 [H2O](https://github.com/kaeyleo/jekyll-theme-H2O) 主题、适合学术研究人员和运维开发人员的博客主题模板。

A Jekyll theme for researchers and maintainers based on Jekyll and [H2O](https://github.com/kaeyleo/jekyll-theme-H2O) theme.

</div>

### Preview

#### [在线预览 Live Demo →](https://h2o-ac.pages.dev/)

![vgy.me](https://i.vgy.me/pICzcE.png)
![vgy.me](https://i.vgy.me/0kmQ1j.png)

如果你喜欢这个博客模板，请在右上角star一下，非常感谢～

If you like this theme or using it, please give a ⭐️ for motivation ;)

如果想体验手机浏览效果，可以扫一下二维码：

![vgy.me](https://i.vgy.me/XGUDp6.png)

Using your smartphone to scan the QR Code

### New Features compared with H2O， 与 H2O 不同的新特性

#### CN

- 学术首页
- 归档页
- 系统日志页
- 社交图标扩展
- 查看大图
- 代码高亮优化
- 字数统计及阅读时间估计
- 配置项
- 前端自动构建工作流优化
- 文章页侧边导航
- Waline 评论系统
- 深色模式切换按钮
- 时间本地化
- PWA
- 提示框支持
- 文章置顶
- 版权显式声明

#### EN

- Academic Home Page
- Archive Page
- System Logs Page
- An extened SNS icon set
- Enlarge any pictures in articles
- Optimize the code highlighting
- Words Counts and evaluate needed time
- New Settings
- Optimize the automatic workflow for static files
- The TOC for posts
- Waline comments
- Dark mode switch button
- Time localization
- PWA
- Premonition
- Top article pin
- Copyright statement for articles

### Usage 快速开始

请访问 [H2O-ac theme for Jekyll](https://lisz.me/tech/webmaster/new-theme-h2o-ac.html) 查看详情。

Please refer to [H2O-ac theme for Jekyll](https://lisz.me/tech/webmaster/new-theme-h2o-ac.html) for details.

### Contribution 贡献

Any types of contribution are welcome. Thanks.

接受各种形式的贡献，包括不限于提交问题与需求，修复代码。等待您的 ```Pull Request```。

### Thanks 感谢参与 H2O 代码贡献的伙伴

- [kaeyleo](https://github.com/kaeyleo/)
- [Ray-Eldath](https://github.com/Ray-Eldath)
- [sctop](https://github.com/sctop)
- [bananaappletw](https://github.com/bananaappletw)
- [moycat](https://github.com/moycat)
- More

### Contributors 贡献者

![Contributors](https://contrib.rocks/image?repo=zhonger/jekyll-theme-H2O-ac)

### License 许可证

Jekyll-Theme-H2O-ac is licensed under [MIT](https://github.com/zhonger/jekyll-theme-H2O-ac/blob/master/LICENSE).

[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fzhonger%2Fjekyll-theme-H2O-ac.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fzhonger%2Fjekyll-theme-H2O-ac?ref=badge_large)

![Alt](https://repobeats.axiom.co/api/embed/c3257de0cec8e91520debc3232103da52e4727b4.svg "Repobeats analytics image")
<file_sep>var gulp = require('gulp'),
    concat = require('gulp-concat'), //合并css文件
    uglify = require('gulp-uglify'), // 压缩js文件
    sass = require('gulp-sass')(require('sass')), // 编译sass
    cleanCSS = require('gulp-clean-css'), // 压缩css文件
    rename = require('gulp-rename'); // 文件重命名

gulp.task('scripts', (cb) => {
    gulp.src('dev/js/index.js')
        .pipe(uglify())
        .pipe(rename({suffix: '.min'}))
        .pipe(gulp.dest('assets/js'));
    cb();
});

gulp.task('sass', (cb) => {
    gulp.src('dev/sass/app.scss')
        .pipe(sass())
        .pipe(gulp.dest('dev/sass'))
        .pipe(cleanCSS())
        .pipe(rename({suffix: '.min'}))
        .pipe(gulp.dest('assets/css'));
    cb();
});

gulp.task('css', (cb) => {
    gulp.src(['dev/sass/github-markdown.css', 'dev/sass/share.min.css'])
        .pipe(concat('plugins.min.css'))
        .pipe(cleanCSS())
        .pipe(gulp.dest('assets/css'));
    cb();
});

gulp.task('cv', (cb) => {
    gulp.src('dev/sass/cv.scss')
        .pipe(sass())
        .pipe(gulp.dest('dev/sass'))
        .pipe(cleanCSS())
        .pipe(rename({suffix: '.min'}))
        .pipe(gulp.dest('assets/css'));
    cb();
});

gulp.task('watch', function(done) {
    gulp.watch('dev/sass/*.scss', gulp.series('sass'));
    gulp.watch(['dev/sass/github-markdown.css', 'dev/sass/share.min.css'], gulp.series("css"));
    gulp.watch('dev/sass/cv.scss', gulp.series('cv'));
    gulp.watch('dev/js/*.js', gulp.series("scripts"));
    done();
});

gulp.task('default', gulp.series('scripts', 'sass', 'css', 'cv', 'watch'));<file_sep>---
layout: post
title: win10大量程序无法执行
categories: 日常操作
description: win10大量程序无法打开
keywords: windows
---

今天不知道怎么了，大量的exe程序无法执行，提示找不到文件。其中就包括了pdf阅读器，找了好久，终于在adobe的论坛找到答案。
原文地址 ： https://forums.adobe.com/thread/2262502

```text
1- Close Reader

2- Hold down Windows key and press R on your keyboard, this will bring up Run dialogue box.

3- Type regedit in the run dialogue box and hit Enter, click Yes when User Account Control pop-up appears.

4- Navigate to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options, delete the key AcroRd32.exe

5- Relaunch Reader
```

原文的意思是打开注册表，找到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options这个节点，然后节点下的AcroRd32.exe节点删除就可以了。

依样画葫芦，不止adobe reader，我电脑上的photoshop，好压，vmware都没法启动，只要把对应的节点删除就可以了。<file_sep>---
layout: post
title: 使用GeoHash查找附近的药店
categories: Develop
description: 为每一个地点生成GeoHash，通过比对GeoHash的值来搜索附近的建筑物
keywords: 微信小程序, http, fly, flyio, mpvue
---

# 如何查找附近的药店

查找附近的药店应该是一个最基本的基于地理位置的应用，但要怎么做呢？  
1、通过计算药店与我之间的距离，来确定是否它是否在附近。  
2、通过对比药店跟我两个位置的GeoHash值来确定我们是否处于同个区域。

本文说明的是第二种方式，因为它更靠谱一点。

# GeoHash介绍
这里推一篇维基百科供参考[GeoHash](https://en.wikipedia.org/wiki/Geohash)  
还有知乎的一篇文章[GeoHash](https://zhuanlan.zhihu.com/p/35940647)  
GeoHash应该是一种位置压缩算法，用来把二维的位置信息变成一维的信息。我们表达一个位置需要两个值——经度、维度。这两个值可以通过GeoHash算法压缩成一个字符串。大家可以在这个网站体验一下[geohash.co](http://geohash.co/)  

# GeoHash的特点
两个GeoHash相同说明他们是同个区域。随着GeoHash变长，它所表示的区域也就越小。   
网上可以找到GeoHash长度跟误差（区域大小）的对照表，比如5位的GeoHash大概表达的是5平方公里这样的面积。  

|geohash length	|lat bits	|lng bits	|lat error	|lng error	|km error|
|---------------|-----------|-----------|-----------|-----------|--------|
|1|2|3|±23|±23|±2500|
|2|5|5|±2.8|±5.6|±630|
|3|7|8|±0.70|±0.70|±78|
|4|10|10|±0.087|±0.18|±20|
|5|12|13|±0.022|±0.022|±2.4|
|6|15|15|±0.0027|±0.0055|±0.61|
|7|17|18|±0.00068|±0.00068|±0.076|
|8|20|20|±0.000085|±0.00017|±0.019|

# 使用GeoHash实现查找附近的药店
## 计算出所有药店的GeoHash
给所有位置都计算出GeoHash，当然长度尽可能的长，这样后期使用的时候，可以灵活调整精度。示例：  

|latitude|longitude|geohash   |
|---|---|---|
|30.789748|121.339645|wtqr1jhg|
|30.257383|120.177994|wtmknsny|
|30.236452|120.044845|wtmk58mu|
|30.236452|120.044845|wtmk58mu|
|34.71191|113.65735|ww0v6r4g  |  
  
    
Geohash的代码实现可以google到，也可以参考我的另外一篇文章[使用kettle生成GeoHash]({% post_url 2019-11-15-使用kettle生成GeoHash %})。

## 计算当前位置的以及周边的8个GeoHash
因为GeoHash表达的是一个正方形的区域，你当前位置不一定就是该区域的中心，所以四周的8个区域也是你搜索的目标。  
这里推荐一个php的实现[https://github.com/chency147/geohash](https://github.com/chency147/geohash)

||||
|---:|:---:|:---|
|NorthWest|North|NorthEast|
|West|<span style="color:green">Me</span>|East|
|SouthWest|South|SouthEast|
{:.t1}

||||
|---:|:---:|:---|
|ws4uztw22x|ws4uztw22z|ws4uztw23p|
|ws4uztw22w|<span style="color:green">ws4uztw22y</span>|ws4uztw23n|
|ws4uztw22t|ws4uztw22v|ws4uztw23j|
{:.t1}

<style>
.t1{
    width:auto;
    border: none;
}
.t1 td{
    border: none;
    padding:10px;
}
.t1 th{
    border:none;
}
</style>

## 与数据库中的GeoHash对比
以下代码示例使用thinkphp5.1 mysql写的。通过查找前N位一样的值来实现查找附近的药店。  

```php
function filterGeohash($geohash, $geohashNeighbors, $topN)
{
  $list = DrugStoreModel::where(
    'LEFT(geohash, :topN) = LEFT(:geohash, :topN) or
      LEFT(geohash, :topN) = LEFT(:North, :topN) or
      LEFT(geohash, :topN) = LEFT(:NE, :topN) or
      LEFT(geohash, :topN) = LEFT(:East, :topN) or
      LEFT(geohash, :topN) = LEFT(:SE, :topN) or
      LEFT(geohash, :topN) = LEFT(:South, :topN) or
      LEFT(geohash, :topN) = LEFT(:SW, :topN) or
      LEFT(geohash, :topN) = LEFT(:West, :topN) or
      LEFT(geohash, :topN) = LEFT(:NW, :topN)',
    [
      'topN'    => $topN,
      'geohash' => $geohash,
      'North'   => $geohashNeighbors['North'],
      'NE'      => $geohashNeighbors['NorthEast'],
      'East'    => $geohashNeighbors['East'],
      'SE'      => $geohashNeighbors['SouthEast'],
      'South'   => $geohashNeighbors['South'],
      'SW'      => $geohashNeighbors['SouthWest'],
      'West'    => $geohashNeighbors['West'],
      'NW'      => $geohashNeighbors['NorthWest']
    ])
    ->hidden(['id', 'search_content'])->select();

  return $list;
}
```<file_sep>---
layout: post
title: 安装docker kong
categories: Operation
description: docker安装及常用脚本
keywords: Linux, docker
---

kong 是一个开源的API管理工具。官网提供了docker的安装方式，不过不够简单。这里我提供一份docker compose文件，并简要说明安装的过程。

#### docker compose 文件

```yaml
version: '2.0'

services:

    kong-database:
        image: postgres:9.4
        volumes:
            - /etc/localtime:/etc/localtime:ro
            - ./pgdata:/var/lib/postgresql/data
        environment:
            - TZ=Asia/Shanghai
            - POSTGRES_USER=kong
            - POSTGRES_DB=kong
        ports:
            - "5432:5432"
        container_name: kong-database

    kong:
        image: kong:latest
        volumes:
            - /etc/localtime:/etc/localtime:ro
        environment:
            - TZ=Asia/Shanghai
            - KONG_DATABASE=postgres
            - KONG_PG_HOST=kong-database
            - ONG_CASSANDRA_CONTACT_POINTS=kong-database
            - KONG_PROXY_ACCESS_LOG=/dev/stdout
            - KONG_ADMIN_ACCESS_LOG=/dev/stdout
            - KONG_PROXY_ERROR_LOG=/dev/stderr
            - KONG_ADMIN_ERROR_LOG=/dev/stderr
        ports:
            - "8000:8000"
            - "8443:8443"
            - "8001:8001"
            - "8444:8444"
        links:
            - "kong-database: kong-database"
        container_name: kong

    kong-dashboard:
        image: pgbi/kong-dashboard
        volumes:
            - /etc/localtime:/etc/localtime:ro
        environment:
            - TZ=Asia/Shanghai
        ports:
            - "8080:8080"
        links:
            - "kong:kong"
        command:
            start --kong-url http://kong:8001 --basic-auth admin=admin
        container_name: kong-dashboard
```

文件的具体内容还是得读者自己阅读了。这里简要说明一下，这里一共启用了三个容器，一个数据库Postgres, 一个Kong服务，一个UI项目。UI项目基于Kong接口做的，与数据库无关。

#### 初始化数据库

根据官方文档的要求，第一次使用需要初始化Postgres数据库。

```sh
#启动Postgres数据库
$ docker-compose up -d kong-database
#运行初始化脚本
$ docker run --rm \
    --link kong-database:kong-database \
    -e "KONG_DATABASE=postgres" \
    -e "KONG_PG_HOST=kong-database" \
    -e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
    --net kongcompose_default \
    kong:latest kong migrations up
```

需要注意的是，这里有一个 --net 的参数，需要指明一个docker网络。因为数据库的初始化需要跟Postgres在同个docker网络中。可以通过执行```$ docker network ls ```查看所有的docker网络。

#### 启动Kong

```sh
$ docker-compose up -d kong
```

测试服务是否可用 

```sh
$ curl localhost:8001
```

#### 启动kong dashboard

```sh
$ docker-compose up -d kong-dashboard
```

这里需要注意的是，如果kong服务还没起来，8001无法访问，那么kong dashboard会启动失败，容器自动退出。

dashboard的访问地址 http://localhost:8080 账户名 admin 密码 <PASSWORD><file_sep>---
layout: post
title: mpvue引入第三方UI库报错
categories: Develop
description: mpvue引入第三方UI库报错
keywords: mpvue, 微信小程序
---

mpvue引入第三方库很容易，只需要在src目录下建个文件夹，然后在需要用到页面的main.js里面import进来就好了。  
需要注意目录路径，防止找不到文件。
```js
import './lib/css/weui.min.css'
```

完整的引入weiui.min.css会报错，因为里面用到了CSS选择器"~"，这个是微信小程序不支持。只要把对应的css样式删除就好了。<file_sep>---
layout: post
title: Excel 批量生成折线图
categories: Operation
description: Excel 批量生成折线图
keywords: Office, Excel, VBA
---

以下代码可以用来在Excel里批量生成折线图

```vb
Function getcolname(ByVal intcol As Long)
    intcol = intcol - 1
    getcolname = IIf(intcol \ 26, Chr(64 + intcol \ 26), "") & Chr(65 + intcol Mod 26)
End Function
  
Sub 批量生成图表()
'
' 批量生成折线图
' by 余灿琳
  
    sheetName = "Sheet1"
    x = 0
    y = 0
    '表格宽度
    chartWidth = 400
    '表格高度
    chartHeight = 200
    Sheets.Add After:=Sheets(Sheets.Count)
    
    'ActiveSheet.Name = "Bug图表"
    '获取sheet1中总的列数
    intcol = Sheets(sheetName).Range("IV1").End(xlToLeft).Column
    strCol = getcolname(intcol)
    '生成图表
    For i = 2 To 6
        ActiveSheet.Shapes.AddChart.Select
        ActiveChart.ChartType = xlLineMarkers
        '数据范围 Ei:Hi
        ActiveChart.SetSourceData Source:=Sheets("Sheet1").Range("E" & i & ":H" & i)
        'xvalue $E$i:$H$i
        ActiveChart.SeriesCollection(1).XValues = "=Sheet1!$E$1:$H$1"
        '表格标题 Sheet1!$C$i
        ActiveChart.SeriesCollection(1).Name = "=" & sheetName & "!$C$" & i
        '设置x轴的类型为文本坐标轴，而不是时间坐标轴
        ActiveChart.Axes(xlCategory).Select
        ActiveChart.Axes(xlCategory).CategoryType = xlCategoryScale
    Next i
    '调整每个图表的位置
    For Each Chart In ActiveSheet.ChartObjects
        Chart.Activate
        ActiveChart.ChartArea.Select
            Chart.Top = y * (chartHeight + 6)
            Chart.Left = x * (chartWidth + 6)
            Chart.Height = chartHeight
            Chart.Width = chartWidth
        x = x + 1
        If x >= 1 Then
            x = 0
            y = y + 1
        End If
    Next Chart
      
End Sub

```<file_sep>---
layout: post
title: phpstorm xdebug 调试
categories: Develop
description: phpstorm xdebug 调试
keywords: phpstorm, xdebug
---

# 1、安装LAMP

首先需要安装php(含xdebug模块), apache(nginx)。这部分按官网说明安装就可以。  
php安装  
xdebug安装 <https://xdebug.org/docs/install> 可以使用pecl的方式安装，比较简单。

## 调整xdebug的部分参数

打开php.ini文件，增加下面两个配置

``` text
xdebug.idekey=PHPSTORM
xdebug.remote_enable=ON
```

## 检查是否正常安装

打开页面phpinfo.php

``` php
<?php
    phpinfo();
?>
```

![phpinfo-xdebug](/images/phpinfo-xdebug.jpg)

# 配置PhpStorm

## 配置settings

打开 File -\> Settings -\> PHP  
![phpstorm-php](/images/phpstorm-php.jpg)

打开 File -\> Settings -\> PHP -\> Debug  
确认debug port跟phpinfo -\> xdebug.remote\_port 一致
![phpstorm-settings-php-debug](/images/phpstorm-settings-php-debug.jpg)

打开 File -\> Settings -\> PHP -\> Debug -\> DBGp
Proxy  
填入Apache站点的信息  
![phpstorm-settings-php-dbgp-proxy](/images/phpstorm-settings-php-dbgp-proxy.jpg)

## 配置 Debug Configurations

打开 Run -\> Edit Configurations  
添加 PHP WEB
Page  
![phpstorm-php-debug-configurations](/images/phpstorm-php-debug-configurations.jpg)

## 开始调试

![phpstorm-php-xdebug-start](/images/phpstorm-php-xdebug-start.jpg)

此时phpstrom会打开浏览器，类似这样的网址
<http://192.168.233.129/tp5/public/?XDEBUG_SESSION_START=18718> 请留意这段尾巴
XDEBUG\_SESSION\_START=18718 ，你可以改成 XDEBUG\_SESSION\_START=PHPSTORM，
效果是一样，其中PHPSTORM就是那个idekey。  
只需要在访问页面的时候加上XDEBUG\_SESSION\_START=PHPSTORM，phpstorm的断点就会生效了。

可能你也注意到了，你的软件要放在apache下面，首要条件是他得能通过浏览器访问。
<file_sep>source "https://rubygems.org"
gem "jekyll"
gem "jekyll-paginate-v2"
gem "jekyll-feed"
gem "jekyll-sitemap"
gem "webrick"
gem "premonition"
gem "h2o-ac-jekyll-extlinks"
gem "nokogiri"<file_sep>---
layout: post
title: 安装docker gitlab
categories: Operation
description: 安装docker gitlab
keywords: Linux, gitlab
---

gitlab安装之前是挺麻烦，数据库，缓存，文件系统一个不能少，不过官方最近推出了docker镜像。

生活又变得美好起来了。

#### docker-compose 配置

```yaml
version: '2'

services:
  gitlab:
    image: 'gitlab/gitlab-ce:latest'
    restart: always
    hostname: 'gitlab.example.com'
    environment:
      - TZ=Asia/Shanghai
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'http://192.168.1.134'
        # Add any other gitlab.rb configuration here, each on its own line
        gitlab_rails['gitlab_shell_ssh_port'] = 2224
        gitlab_rails['time_zone'] = 'Asia/Shanghai'
    ports:
      - '80:80'
      - '443:443'
      - '2224:22'
    volumes:
      - './gitlab/config:/etc/gitlab'
      - './gitlab/logs:/var/log/gitlab'
      - './gitlab/data:/var/opt/gitlab'
      - /etc/localtime:/etc/localtime:ro
    container_name: gitlab
```

external_url 填写你本机的ip地址。

大功告成，执行 docker-compose up -d 就可以了。

#### 更新gitlab

```bash
$ docker-compose stop
$ docker-compose rm
$ docker-compose up -d
```

docker 会自动拉取最新的latest镜像运行<file_sep>---
layout: post
title: 制作php-fpm-xdebug docker镜像
categories: Develop Operation
description: 制作php-fpm-xdebug docker镜像
keywords: php, fpm, xdebug, docker
---

如果你还不理解docker跟docker镜像制作，请自行google学习。

# Dockerfile
给php-fpm镜像安装php扩展可以参考[How to install more PHP extensions](https://hub.docker.com/_/php/)   
由于内地的网络通讯问题，这里提供第二种方式，先下载[安装文件](https://pecl.php.net/package/Xdebug)，放在Dockerfile的同级目录下，然后再执行安装。  
温馨提示：因为新版xdebug已经不支持php5，如果你用的是php5，请选择xdebug-2.5.5。   

```text
FROM php:5-fpm
# 安装mysql扩展
RUN docker-php-ext-install mysql

# 安装并启用xdebug
# 方法一，使用pecl直接安装
#RUN pecl install xdebug-2.5.5
#RUN docker-php-ext-enable xdebug
# 方法二，下载文件安装
RUN mkdir /home/software
COPY xdebug-2.5.5.tgz /home/software/
RUN pecl install /home/software/xdebug-2.5.5.tgz
RUN docker-php-ext-enable xdebug
```

# 生成镜像

```sh
docker build -t php5-fpm-xdebug .
```

# docker-compose.yml
温馨提示：   
1、不要把/usr/local/etc/php/conf.d/这个目录映射出来，因为你使用docker-php-ext-install的时候，会往里面写配置文件。你只需要把你自定义的ini文件映射到容器里面就好了。 
2、这里没有把php-fpm默认的9000端口映射到host机，因为没有必要，容器间通讯，我们可以使用php-fpm:9000。在下面的nginx站点配置，你会看到我们就是怎么写的。而且9000我们用作了xdebug的remote_port，所以调试的时候，9000就是phpstrom的监听端口。为了避免冲突，我们不把9000端口映射host机。

```yml
version: '3' 

services:
  php-fpm:
    image: php5-fpm-xdebug
    volumes:
     - /etc/localtime:/etc/localtime:ro
     - ./html:/usr/share/nginx/html:rw
     - ./php/php.ini:/usr/local/etc/php/php.ini
     - ./php/conf.d/docker-php-ext-xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
    container_name: "php-fpm"
  nginx:
    image: nginx:1.15
    ports:
      - "85:85"
    depends_on:
      - "php-fpm"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:rw
      - ./html:/usr/share/nginx/html:rw
    container_name: "nginx"
```

## docker-php-ext-xdebug.ini

```ini
zend_extension=xdebug.so
xdebug.remote_enable=on
xdebug.remote_host=192.168.204.129
xdebug.remote_port=9000
xdebug.idekey=PHPSTORM
```

有不少同学不理解xdebug.remote是什么意思，它是xdebug提供的一种远程调试模式，也就是服务器跟你调试的代码不在一个地方。idekey就是一个标志，当你在querystring中加入?XDEBUG_SESSION_START=PHPSTORM，xdebug就会往remote_host:remote_port发送调试信息。一般来说，我们会在phpstorm配置监听，接收来自xdebug的信息，这样就可以实现服务器联调了。我会在这篇章做详细说明。

## default.conf

这是nginx的配置文件，放在./nginx/conf.d/下面。  
温馨提示：     
1、 root   /usr/share/nginx/html/pingjia; 跟   
fastcgi_param  SCRIPT_FILENAME  /usr/share/nginx/html/pingjia$fastcgi_script_name;  
要写一样的路径。  
2、 docker-compose.yml 文件中两个serice的 /usr/share/nginx/html 都映射到了 ./html。  

```conf
server {
    listen       85;
    server_name  192.168.204.129:85;

    location / {
        root   /usr/share/nginx/html/pingjia;
        index  index.html index.htm index.php;
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location ~ \.php$ {
        root           html;
        fastcgi_pass   <PASSWORD>;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /usr/share/nginx/html/pingjia$fastcgi_script_name;
        include        fastcgi_params;
    }
}
```

## 整个compose目录结构

```sh
[root@localhost php-nginx-compose]# tree ./ -L 3
./
├── docker-compose.yml
├── html
│   ├── index.html
│   └── phpinfo.php
├── nginx
│   └── conf.d
│       └── default.conf
└── php
    ├── conf.d
    │   └── docker-php-ext-xdebug.ini
    ├── php.ini
    ├── php.ini-development
    └── php.ini-production

```

# 打开phpinfo，检查xdebug是否正常安装
![phpinfo-xdebug](/images/phpinfo-xdebug.jpg)<file_sep>---
layout: post
title: docker xfs 卡死
categories: Operation
description: 频繁重启docker导致xfs文件系统挂载错误
keywords: Linux, GIT
---

docker是一个快速更新的开源项目，今天就碰到一个棘手的问题。运行docker的linux内核出错，docker整个卡死，不接受任何命令，无法停止。
查了下系统日志，发现是文件系统错误。

```text
May 15 09:12:00 localhost dockerd-current: time="2018-05-15T09:12:00.282507381+08:00" level=info msg="Container 86f8559b50d7 failed to exit within 10 seconds of kill - trying direct SIGKILL"
May 15 09:12:00 localhost kernel: XFS (dm-18): Unmounting Filesystem
```

借助万能的google，发现docker在xfs文件系统中确实会出现这种现象。过于频繁create/destory container、pull/push image，当thin pool满时，DeviceMapper后端默认文件系统xfs会不断retry 失败的IO，导致进程挂起。  
解决方法有两个：
1、不用xfs文件系统
2、加入启动参数 dm.xfs_nospace_max_retries=0  

对于加入启动参数这件事，docker官方文档也有说明 [https://docs.docker.com/engine/reference/commandline/dockerd/](https://docs.docker.com/engine/reference/commandline/dockerd/){:target="_blank"}  

```text
By default XFS retries infinitely for IO to finish and this can result in unkillable process.   
To change this behavior one can set xfs_nospace_max_retries to say 0 and XFS will not retry IO after getting ENOSPC and will shutdown filesystem.

Example
$ sudo dockerd --storage-opt dm.xfs_nospace_max_retries=0
```

新版本的docker可以通过修改 /etc/docker/daemon.json 改变启动参数。

```json
{
  "storage-driver": "devicemapper",
  "storage-opts": [
    "dm.xfs_nospace_max_retries=0"
  ]
}
```<file_sep>---
layout: post
title: Fedora 添加快捷方式
categories: 日常操作
description: Fedora 添加快捷方式
keywords: Fedora
---

Fedora增加快捷方式很简单，只需增加一个文件就可以了。Fedora开始菜单的快捷方式都存放在这个目录
```text
/usr/share/applications
```

新增文件pycharm.desktop，内容如下：

```text
[Desktop Entry]
Name=PyCharm
GenericName=pycharm ide
Exec=/usr/local/pycharm/bin/pycharm.sh
Icon=/usr/local/pycharm/bin/pycharm.png
Terminal=false
Type=Application
Categories=Development;
```

Name：快捷方式名称

GenericName：说明

Exec：程序位置

Icon：图标位置

Terminal：是否从终端运行

Type：类型

Categories：存放在哪个分类<file_sep>---
layout: post
title: 让GIT打印中文文件名
categories: Develop
description: 让GIT打印中文文件名
keywords: Linux, GIT
---

GIT对于非ASCII的文件名，GIT是默认打印八进制。

把它关了，你就能看到可爱的中文了

#### 执行如下GIT配置

```bash
git config [--global] core.quotepath off
```<file_sep>---
layout: post
title: Fedora Sublime3 中文输入
categories: Develop
description: Fedora Sublime3 中文输入
keywords: Linux, Fedora, Sublime
---

Fedora安装sublime3无法输入中文，应该困扰了不少人。

一个原因是Fedora自带的输入框架iBus不支持，第二个是sublime3原生不支持，得弄个共享库。

#### 1、卸载iBus，安装Fcitx。

```bash
dnf remove ibus
dnf install fcitx fcitx-pinyin fcitx-configtool fcitx-cloudpinyin im-chooser fcitx-libpinyin
```
安装完，别忘了注销后重新登录

####  2、编译sublime-imfix共享库

新建 sublime-imfix.c 文件，内容如下

```text
/*
sublime-imfix.c
Use LD_PRELOAD to interpose some function to fix sublime input method support for linux.
By <NAME>

gcc -shared -o libsublime-imfix.so sublime-imfix.c `pkg-config --libs --cflags gtk+-2.0` -fPIC
LD_PRELOAD=./libsublime-imfix.so subl
*/
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
typedef GdkSegment GdkRegionBox;

struct _GdkRegion
{
  long size;
  long numRects;
  GdkRegionBox *rects;
  GdkRegionBox extents;
};

GtkIMContext *local_context;

void
gdk_region_get_clipbox (const GdkRegion *region,
            GdkRectangle    *rectangle)
{
  g_return_if_fail (region != NULL);
  g_return_if_fail (rectangle != NULL);

  rectangle->x = region->extents.x1;
  rectangle->y = region->extents.y1;
  rectangle->width = region->extents.x2 - region->extents.x1;
  rectangle->height = region->extents.y2 - region->extents.y1;
  GdkRectangle rect;
  rect.x = rectangle->x;
  rect.y = rectangle->y;
  rect.width = 0;
  rect.height = rectangle->height;
  //The caret width is 2;
  //Maybe sometimes we will make a mistake, but for most of the time, it should be the caret.
  if(rectangle->width == 2 && GTK_IS_IM_CONTEXT(local_context)) {
        gtk_im_context_set_cursor_location(local_context, rectangle);
  }
}

//this is needed, for example, if you input something in file dialog and return back the edit area
//context will lost, so here we set it again.

static GdkFilterReturn event_filter (GdkXEvent *xevent, GdkEvent *event, gpointer im_context)
{
    XEvent *xev = (XEvent *)xevent;
    if(xev->type == KeyRelease && GTK_IS_IM_CONTEXT(im_context)) {
       GdkWindow * win = g_object_get_data(G_OBJECT(im_context),"window");
       if(GDK_IS_WINDOW(win))
         gtk_im_context_set_client_window(im_context, win);
    }
    return GDK_FILTER_CONTINUE;
}

void gtk_im_context_set_client_window (GtkIMContext *context,
          GdkWindow    *window)
{
  GtkIMContextClass *klass;
  g_return_if_fail (GTK_IS_IM_CONTEXT (context));
  klass = GTK_IM_CONTEXT_GET_CLASS (context);
  if (klass->set_client_window)
    klass->set_client_window (context, window);

  if(!GDK_IS_WINDOW (window))
    return;
  g_object_set_data(G_OBJECT(context),"window",window);
  int width = gdk_window_get_width(window);
  int height = gdk_window_get_height(window);
  if(width != 0 && height !=0) {
    gtk_im_context_focus_in(context);
    local_context = context;
  }
  gdk_window_add_filter (window, event_filter, context);
}
```

编译共享库，生成libsublime-imfix.so文件

```bash
gcc -shared -o libsublime-imfix.so sublime-imfix.c `pkg-config --libs --cflags gtk+-2.0` -fPIC
```

生成完后，可以把文件拷贝到sublime程序所在的目录， 这样就不会丢了

#### 3、新建命令 /usr/local/bin/sublime3

```bash
#!/bin/bash
LD_PRELOAD=/usr/local/sublime_text_3/libsublime-imfix.so /usr/local/sublime_text_3/sublime_text
```

保存后，就可以直接用sublime3命令启动了。我们还可以新建快捷方式，这样就不用每次都从命令行启动了。

#### 4、新建快捷方式

新建文件 /usr/share/applications/sublime_text.desktop，内容如下：

```text
[Desktop Entry]
Version=1.0
Type=Application
Name=Sublime Text
GenericName=Text Editor
Comment=Sophisticated text editor for code, markup and prose
Exec=sublime3
Terminal=false
MimeType=text/plain;
Icon=/usr/local/sublime_text_3/Icon/128x128/sublime-text.png
Categories=TextEditor;Development;
StartupNotify=true
Actions=Window;Document;

[Desktop Action Window]
Name=New Window
Exec=/opt/sublime_text/sublime_text -n
OnlyShowIn=Unity;

[Desktop Action Document]
Name=New File
Exec=/opt/sublime_text/sublime_text --command new_file
OnlyShowIn=Unity;
```

<file_sep>---
layout: post
title: docker安装及常用脚本
categories: Operation
description: docker安装及常用脚本
keywords: Linux, docker
---

#### Centos安装docker的命令

```bash
$ yum install docker
```

#### 安装docker compose

最新的docker compose 安装命令可以从以下网站找到

[https://github.com/docker/compose/releases](https://github.com/docker/compose/releases){:target="_blank"}

下面是个示例：

```bash
$ curl -L https://github.com/docker/compose/releases/download/1.17.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
$ chmod +x /usr/local/bin/docker-compose
```

#### 安装docker常用脚本

下面这段脚本用来安装流行的docker辅助命令，里面就包括了经常要用到的docker enter

```bash
$ wget -P ~ https://github.com/yeasy/docker_practice/raw/master/_local/.bashrc_docker
$ echo "[ -f ~/.bashrc_docker ] && . ~/.bashrc_docker" >> ~/.bashrc; source ~/.bashrc
```

原版的脚本有错误，在新版的docker ce中获取pid会出错，下面是修改过的脚本，请下载后保存为 ~/.bashrc_docker

[.bashrc_docker]({{ site.url }}/downloads/bashrc_docker)

#### 采用国内镜像加速

修改 /etc/docker/daemon.json 文件，增加镜像地址

```text
{
  "registry-mirrors": ["https://registry.docker-cn.com"]
}
```

#### 打开系统的ip_forward

如果你没有执行这个操作，那docker的端口映射是没有效果的。

具体操作可以参考这篇文章 [http://www.yucanlin.cn/2017/10/18/docker-ports-%E7%AB%AF%E5%8F%A3%E6%98%A0%E5%B0%84%E5%A4%B1%E8%B4%A5/](http://www.yucanlin.cn/2017/10/18/docker-ports-%E7%AB%AF%E5%8F%A3%E6%98%A0%E5%B0%84%E5%A4%B1%E8%B4%A5/){:target="_blank"}
<file_sep>---
layout: post
title: linux创建大文件
categories: Operation
description: linux创建大文件
keywords: linux
---

Linux创建大文件主要有三种方式。 

# 使用DD命令
dd命令，可以从标准输入或指定的文件中读取数据，拷贝至新文件。  

示例：创建1G的文件，写入0，大小是1G
```sh
dd if=/dev/zero of=my_new_file count=1 bs=1G
```

dd写入是很慢的，取决于磁盘的写入速度，它是真的有往磁盘写数据。  
dd还有一个seek的参数，可以指定游标的位置，从而创建稀疏文件，就是直接写个文件结束符就完事了。这样的文件，用ls查是很大，用du查就很小了。因为它本质上不占用磁盘空间。

示例：创建100G的文件
```sh
dd if=/dev/zero of=my_new_file count=0 bs=1G seek=100G
```

# 使用truncate命令
这个命令创建的也是稀疏文件，实际不占用磁盘空间。

示例：创建100G的文件
```sh
truncate -s 100G my_new_file
```

# 使用fallocate命令
这个命令会保留你想要的空间，但不会往磁盘写入任何东西，执行速度还是相当快的。用du查看能发现它的确占用了磁盘空间。  

示例：创建100G的文件
```sh
fallocate -l 100G my_new_file
```<file_sep>---
layout: post
title: git忽略权限变更
categories: Develop
description: git忽略权限变更
keywords: git
---

默认情况下，对文件权限的更改也会被git跟踪。即使文件内容没有变化，也会被定义成modify。我们可以通过配置项来让git忽略权限变更。

```sh
git config core.filemode false
```

注意，这个操作有全局命令，但不会对以存在的项目有效。只有新建项目的时候会生效。所以对已存在的项目，请逐一执行上面这条指令。

<file_sep>---
layout: post
title: thinkphp5.1 pdomysql 允许使用同名的参数
categories: Develop
description: 通过修改pdo mysql连接参数，允许参数绑定的时候，出现同名的占位符。
keywords: thinkphp, mysql, pdo, bind param
---

本文说明的是如何在thinkphp5.1通过修改pdo mysql连接参数，最终实现参数绑定的时候允许使用同名参数。

# pdomysql 默认不支持绑定相同的参数名

如果参数名不一样，默认配置下是没有问题的  
```php
// 预处理 SQL 并绑定参数 正确示例
$sql = 'SELECT name, colour, calories
    FROM fruit
    WHERE calories < :calories AND colour = :colour';
$sth->execute(array(':calories' => 150, ':colour' => 'red'));
```  

如果采用同名的参数，默认配置就会报错  
```php
// 预处理 SQL 并绑定参数 默认配置下报错，可通过修改连接参数允许该操作
$sql = 'SELECT name, colour, calories
    FROM fruit
    WHERE  colourA = :colour and colourB = :colour';
$sth->execute(array(':colour' => 'red'));
```

# 添加连接参数支持绑定相同的参数名
## 修改连接参数的原生代码
```php
<?php
  $db = new PDO('mysql:host=myhost;dbname=mydb', 'login', 'password', 
  array(PDO::ATTR_EMULATE_PREPARES => true));
?>
```
## 在thinkphp5.1修改连接参数
参考[官方文档](https://www.kancloud.cn/manual/thinkphp5_1/353998)连接参数那一节。   
在 /config/database.php 里的params增加一个项目  
```php
<?php
return [
    // 数据库类型
    'type'            => 'mysql',
    // 服务器地址
    'hostname'        => '127.0.0.1',
    // 数据库名
    'database'        => 'ihu',
    // 用户名
    'username'        => 'root',
    // 密码
    'password'        => '<PASSWORD>',
    // 端口
    'hostport'        => '3306',
    // 连接dsn
    'dsn'             => '',
    // 数据库连接参数
    'params'          => [
      \PDO::ATTR_EMULATE_PREPARES => true
    ],
]
```

# 应用案例
下面代码摘自我的个人项目，里面使用了model做查询，用DB原生查询也是一个道理。  
这段代码使用一组geohash跟数据库中的geohash进行对比，每次都是各取前N位，用来确定他们是否属于同个区域。  
关于geohash的原理，可以参考我的另一篇文章。
```php
function filterGeohash($geohash, $geohashNeighbors, $topN)
{
  $list = DrugStoreModel::where(
    'LEFT(geohash, :topN) = LEFT(:geohash, :topN) or
      LEFT(geohash, :topN) = LEFT(:North, :topN) or
      LEFT(geohash, :topN) = LEFT(:NE, :topN) or
      LEFT(geohash, :topN) = LEFT(:East, :topN) or
      LEFT(geohash, :topN) = LEFT(:SE, :topN) or
      LEFT(geohash, :topN) = LEFT(:South, :topN) or
      LEFT(geohash, :topN) = LEFT(:SW, :topN) or
      LEFT(geohash, :topN) = LEFT(:West, :topN) or
      LEFT(geohash, :topN) = LEFT(:NW, :topN)',
    [
      'topN'    => $topN,
      'geohash' => $geohash,
      'North'   => $geohashNeighbors['North'],
      'NE'      => $geohashNeighbors['NorthEast'],
      'East'    => $geohashNeighbors['East'],
      'SE'      => $geohashNeighbors['SouthEast'],
      'South'   => $geohashNeighbors['South'],
      'SW'      => $geohashNeighbors['SouthWest'],
      'West'    => $geohashNeighbors['West'],
      'NW'      => $geohashNeighbors['NorthWest']
    ])
    ->hidden(['id', 'search_content'])->select();

  return $list;
}
```

哦，对了。如果你把上面代码中的:NE换成:NorthEast，或者把:SE换成:SouthEast，保证你Debug到怀疑人生。  
这是thinkphp5.1标签转换的一个bug，因为已经有North这个参数，像NorthEast,NorthWest,NorthABC这些前缀一样的参数都会引起混乱。<file_sep>---
layout: categories
home-title: 一年四季
description: 不以物喜，不以己悲，感时花溅泪，恨别鸟惊心
permalink: /categories.html
cover: 'https://images.unsplash.com/photo-1682007049179-ea0223283a90'
cover_author: '<NAME>'
cover_author_link: 'https://unsplash.com/@pramodtiwari'
---<file_sep>---
layout: post
title: CentOS7安装DockerCE
categories: Operation
description: CentOS7安装DockerCE
keywords: Linux, Docker
---

DockerCE是新版本的Docker，由于晚于CentOS7发布，所以并不在官方仓库里，这里采用官方文档的做法，通过加入新的yum仓库来安装docker-ce。   
官方文档地址：[https://docs.docker.com/install/linux/docker-ce/centos/#upgrade-docker-ce](https://docs.docker.com/install/linux/docker-ce/centos/#upgrade-docker-ce)

#### 1、安装yum工具
```sh
$ sudo yum install -y yum-utils \
  device-mapper-persistent-data \
  lvm2
```

#### 2、下载docker-ce仓库
```sh
$ sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
```

#### 3、安装docker-ce
```sh
$ sudo yum install docker-ce
```<file_sep>---
layout: sw
---<file_sep>---
layout: post
title: 安装Docker Registry
categories: Operation
description: 安装Docker Registry
keywords: Linux, Docker
---

要将docker应用于项目部署，肯定少不了私有仓库。总不能整天把images文件copy来copy去吧，太麻烦了。

#### 编辑docker-compose文件

新建文件夹registry-compose, 在里面新建文件 docker-compose.yml 内容如下：

```yaml
version: '2'

services:
  registry:
    image: registry:2
    ports:
      - "5000:5000"
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./registry:/var/lib/registry
    environment:
      - TZ=Asia/Shanghai
      - REGISTRY_STORAGE_DELETE_ENABLED=true
    container_name: registry
  registry-web:
    image: hyper/docker-registry-web
    ports:
      - "8000:8080"
    volumes:
      - /etc/localtime:/etc/localtime:ro
    environment:
      - TZ=Asia/Shanghai
      - REGISTRY_AUTH_ENABLED=false
      - REGISTRY_URL=http://registry-srv:5000/v2
      - REGISTRY_READONLY=false
    links:
      - "registry:registry-srv"
    container_name: registry-web
```

#### 启用http协议

Docker registry默认使用https，官方也建议使用https，不过我搞不定，所以得启用http。

编辑 /etc/docker/daemon.json 文件，加入

```json
{
  "insecure-registries" : ["myregistrydomain.com:5000"]
}
```

配置完别忘了重启Docker

```bash
$ systemctl daemon-reload
$ systemctl restart docker
```

#### 启动registry及对应的管理界面

```bash
$ docker-compose up -d
```

启动完后，私有仓库运行在5000端口，管理界面运行在8000端口，仓库文件存放在 registry-compose/registry

#### 上传镜像示例

##### 下载镜像

```bash
$ docker pull ubuntu
```

##### 重新打标签

```bash
$ docker tag ubuntu localhost:5000/myfirstimage
```

##### 上传镜像

```bash
$ docker push localhost:5000/myfirstimage
```

#### 查看仓库内容

打开浏览器 http://localhost:8000

#### 清理仓库

通过管理界面可以delete镜像，不过这个操作只是删除对应的标签而已，实际内容并没有删除。

可以进入容器，执行如下命令对没有用的blobs进行清理

```bash
$ registry garbage-collect /etc/docker/registry/config.yml
```<file_sep>---
layout: post
title: 升级python的sqlite版本
categories: Operation
description: 升级python的sqlite版本
keywords: Linux, Python, Sqlite
---

如果之前有用yum安装过，需要先删除yum的版本

```sh
yum remove sqlite3
```

从sqlite官网下载源代码 sqlite-autoconf-3270200.tar.gz [https://www.sqlite.org/download.html](https://www.sqlite.org/download.html)

```sh
tar -xvf sqlite-autoconf-3270200.tar.gz
cd sqlite-autoconf-3270200
./configure
make && make install
```

测试安装结果

```sh
# sqlite3
SQLite version 3.26.0 2018-12-01 12:34:55
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> 
Ctrl+D 退出
```

如果没有效果，把sqlite-autoconf-3270200/sqlite3 复制到 /usr/bin/sqlite3 就可以了

# 重新编译Python3

```sh
cd Python-3.7.2
LD_RUN_PATH=/usr/local/lib ./configure LDFLAGS="-L/usr/local/lib" CPPFLAGS="-I/usr/local/include"  
LD_RUN_PATH=/usr/local/lib make
make install
```

在执行编译前加入/usr/local/的路径，这样就能找到你新安装的sqlite3了，不然它还是会用系统的就版本。

测试安装结果

```sh
[root@bogon charlieyu.github.io]# python3
Python 3.7.2 (default, Mar 21 2019, 10:09:12) 
[GCC 8.3.1 20190223 (Red Hat 8.3.1-2)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sqlite3
>>> sqlite3.sqlite_version
'3.26.0'
>>> 
Ctrl+D 退出
```
<file_sep>---
layout: post
title: nginx下部署thinkphp5.1
categories: Develop
description: nginx下部署thinkphp5.1，修正路径重定向的问题
keywords: thinkphp, php, nginx
---

按照要求，部署的时候只开放/public目录的访问。需要打开重定向，apache2的话，因为官方已经提供了.htaccess，启用mod_rewrite就可以了。  
关于在nginx下的重定向问题，在[官方文档](https://www.kancloud.cn/manual/thinkphp5_1/353955)URL设计中有说明。  
下面是那个重定向的关键配置，摘录自官方文档。

```text
location / { // …..省略部分代码
   if (!-e $request_filename) {
   		rewrite  ^(.*)$  /index.php?s=/$1  last;
    }
}
```

请特别留意这段配置中有一个```s=/$1```，在/thinkphp/convention.php中可以找到如下代码

```php
// PATHINFO变量名 用于兼容模式
'var_pathinfo'           => 's',
```

一开始也是没搞清楚，被坑了好久。<file_sep>---
layout: post
title: 新家
categories: Something
description: 博客搬新家
keywords: 日常
---

博客搬新家，听说github page 好有爱
free and fast
<file_sep>---
layout: post
title: Windows共享wifi
categories: 日常操作
description: Windows共享wifi
keywords: Windows, wifi
---

之前使用猎豹免费Wifi这样的工具来实现共享wifi，得装个软件，比较麻烦。win10自带了无线热点的功能，可以直接使用。  

#### 1、启用hostednetwork
```sh
netsh wlan set hostednetwork mode=allow ssid=JayFan key=12345678
```

#### 2、配置适配器共享网络
在网络与共享中心，选择一个正常联网的网卡，配置共享网络
![共享Wifi](/images/共享wifi.jpg)

#### 3、启用共享wifi
```sh
netsh wlan start hostednetwork
```

<file_sep>---
layout: post
title: docker posts 端口映射失败
categories: Operation
description: docker posts 端口映射失败
keywords: Linux, Docker
---

docker映射端口的命令是 -p 比如可以用如下命令将容器的8080端口映射到主机的80端口。

```bash
$ docker run -p 80:8080 tomcat
```

不过端口映射生效的前提是，系统必须启用ip_forward。

#### 1、编辑 /etc/sysctl.conf 文件，加入

```text
net.ipv4.ip_forward=1
```

#### 3、立即生效

```bash
$ sysctl -p /etc/sysctl.conf
```

#### 3、检查是否生效

```bash
$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
```

#### 4、重启Docker

```bash
$ service docker restart
```

结束

> 题外:

如果你使用了firwalld, 可以通过打开asquerade达到相同的目的

```bash
$ firewall-cmd --add-masquerade
$ firewall-cmd --add-masquerade --permanent
$ firewall-cmd --list-all
FedoraWorkstation (active)
  target: default
  icmp-block-inversion: no
  interfaces: ens33
  sources: 
  services: dhcpv6-client ssh samba-client mdns
  ports: 1025-65535/udp 1025-65535/tcp
  protocols: 
  masquerade: yes
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 
``` 