使用polipo实现socks5转http代理

为什么有这个需求

shadowsocks只支持socks5代理,Chrome可以完美兼容,但很多App不支持,比如说,Intellij IDEA,只支持http、socks4代理。

实现原理

使用polipo架设二级代理,转成http

实现步骤

  1. 安装 polipo

    brew install polipo
    
  2. 编辑配置文件

    vi ~/.polipo
    

    内容如下:

    socksParentProxy = "127.0.0.1:1080"
    socksProxyType = "socks5"
    proxyAddress = "127.0.0.1"
    proxyPort = 8123
    

    上级代理是shadowsocks的默认端口,本级代理是8123端口

  3. 开机启动
    链接launchctl脚本

    ln -sfv /usr/local/opt/polipo/*.plist ~/Library/LaunchAgents/
    

    加载:

    launchctl load ~/Library/LaunchAgents/homebrew.mxcl.polipo.plist
    

Done,现在试试设置http代理,127.0.0.1 端口号 8123,看看能不能访问。

使用shell脚本实现定时备份mysql数据库

在经历过一次惨痛教训后,意识到数据库备份的重要性,所以昨天晚上写了个小脚本,用以备份mysql数据库。

转载请注明出处(http://www.pocketdigi.com), 谢谢。

mysql本身可以从日志文件恢复数据,其原理是日志文件会记录指定时间段的sql操作记录。但我们不可能存储从数据库安装到当前的日志文件,日志早爆炸了,后面的日志会覆盖之前的日志,所以,想从日志恢复完整数据,那是不可能的。而通过自己备份数据库,再从日志中找到备份时间到当前时间的更改记录,就可以恢复所有数据。

下面的脚本生成指定数据库备份,打包压缩,并删除10天前备份。
mysql_back.sh

#!/bin/bash
#mysql 备份脚本
#保留最近10天备份
#备份目录
backupDir=/home/backup/database
#mysqlDump
mysqldump=/usr/local/mariadb/bin/mysqldump
#ip
host=127.0.0.1
#用户名
username=root
password=42342342


#今天日期
today=`date +%Y%m%d`
#十天前的日期
timeTenDayAgo=`date -d -10day +%Y%m%d`
#要备份的数据库数组
databases=(blog chinese_medicine)


# echo $databaseCount

for database in ${databases[@]}
  do
    echo '开始备份'$database
    $mysqldump -h$host -u$username -p$password $database | gzip > $backupDir/$database-$today.sql.gz
    echo '成功备份'$database'到'$backupDir/$database-$today.sql.gz
    if [ ! -f "$backupDir/$database-$timeTenDayAgo.sql.gz" ]; then
      echo '10天前备份不存在,无需删除'
    else
        rm -f $backupDir/$database-$timeTenDayAgo.sql.gz
        echo '删除10天前备份文件'$backupDir/$database-$timeTenDayAgo.sql.gz
    fi
  done

加入定时任务:
crontab -e

30 1  *   *  * /home/backup/mysql_back.sh

每天1点半执行一次

偷窃者,请尊重知识产权

最近发现博客被某站采集了,文章一篇不落,刚发布没多到就到他们网站上去了,比Google、百度都勤快。知识本应共享,没问题,但是作者辛苦码这么多字,被你几秒钟就偷走了,还不留出处,是不是有些无耻?今天已经给对方发了邮件,看他什么反应。从即日起,本站所有图片会加水印,对普通读者,表示抱歉,另外,文章也会加随机码。其实我很反感做这种事情,但总得做些什么,不能让他们太容易得手吧。这篇文章发在Android开发类目上,你们猜会不会出现在他们的网站?

使用ConstraintLayout(约束布局)构建响应式UI

ConstraintLayout(约束布局)是Google IO 2016推出的Android新布局方式。

以下内容翻自官方文档。

ConstraintLayout允许您使用扁平的层级(不用嵌套View Group)创建大型复杂的布局。与RelativeLayout类似,它通过相邻的view和父layout的相对关系来确定位置,但比RelativeLayout更加灵活,更容易通过Android Studio的布局编辑器实现想要的效果。

ConstraintLayout的所有功能都可以直接通过Layout Editor可视化工具实现,因为布局API和Layout Editor互相做了特别优化。所以您 只需要拖放布不必编写一行代码构建ConstraintLayout布局。

image
图1. Layout Editor编辑ConstraintLayout

ConstraintLayout的Api库兼容 Android 2.3 (API level 9)或更高版本,新的Layout Editor需要Android Studio 2.2或更高版本

本文提供了在Android Studio中使用ContraintLayout构建布局的向导,如果您对Layout Editor更兴趣,参见 Build a UI with Layout Editor.

约束概览

ConstraintLayout中定义一个view的位置,您必须为这个view添加至少两条约束。每个约束代表与另一个view、父layout,或不可见的向导线的连系或对齐。每个约束沿着水平或垂直坐标定义了view的位置,所以每个view必须在每个轴上有一个最小约束,但通常需要多个。

当您把一个view拖进Layout Editor时,即便没有约束,它也会留在你释放时的位置。然而,这仅仅是让您更容易编辑,如果一个view没有约束,当您在设备中运行时,它始终会在左上角显示。

在图2中,布局在编辑器中看起来很好,但TextView B没有垂直约束。当这个布局在设备中运行时,TextView B会与ImageView的左右边缘水平对齐,但出现在屏幕的最顶部,因为它没有垂直约束.

image
图2: TextView B缺少垂直约束

image
图3:TextView B现在相对ImageView水平垂直约束

尽管缺少约束不会导致编译错误,Layout Editor还是会在工具栏上将缺少约束显示为错误。
image 查看错误和其他警告,点击错误数字图标。为了帮您避免缺少约束,Layout Editor可以使用Autoconnect和infer constraints 特性,帮您自动添加约束。

为您的项目添加ConstraintLayout

要在项目中使用ConstraintLayout,跟随以下步骤:
1.确保您有最新的约束布局库:
– 点击 Tools > Android > SDK Manager.
– 点击 SDK Tools 选项卡
– 展开Support Repository,勾选ConstraintLayout for AndroidSolver for ConstraintLayout,勾选Show Package Details,记住您下载的版本(下面会用到)
– 点击 OK
– 在module级别(默认app)的build.gradle里添加ConstraintLayout库依赖

    dependencies {
        compile 'com.android.support.constraint:constraint-layout:1.0.0-beta4'
    }

您下载的版本可能更高,所以确保版本号与下载的匹配.

  • 在工具栏或同步通知里,点击Sync Project with Gradle Files

现在,您已经准备好使用ConstraintLayout构建布局了

转换布局

转换现有布局到ConstraintLayout,跟随以下步骤:
1. 在Android Studio中打开layout,在编辑器的底部,点击Design标签,
2. 在Component Tree窗口,右击layout,点击 Convert layout to ConstraintLayout
image
图4:转换布局菜单

创建新布局

创建一个新的ConstraintLayut,跟随以下步骤:
1. 在Project窗口任意处点击,选择File > New > XML > Layout XML.
2. 输入layout文件名,android.support.constraint.ConstraintLayout作为Root Tag
3. 点击Finish

添加约束

Palette拖动一个view到编辑器里开始。当您添加一个view到ConstraintLayout里时,它展示成一个包围的盒子,四角带方形大小调整把手,四边的中间带圆形约束把手。

点击View选中它,点击并拖住一个约束把手,拖动到可用的锚点上(另一个view、layout、引导线的边缘),当您释放时,约束就生成了,两个view间带一个默认的margin值

演示视频

创建约束时,记住以下准则:

  • 每个view至少有两个约束,一个水平,一个垂直
  • 同一平面上,您可以只在一个约束把手和一个锚点间创建约束。所以一个view的垂直平面(左右边) 可以只被另一个垂直平面约束;基线可以只被其他基线约束
  • 每个约束把手只能创建一个约束,但是同一个锚点上可以创建多个约束(不同的view)

如果要删除约束,选中view,点击约束把手

当您添加了相反的约束时,约束线会变成弯弯曲曲的弹簧状。
image
这个效果当view的size设置成fixedwrap_content时最明显,它会让view展示在约束的中间,如果您希望view扩展它的尺寸到约束上, 把尺寸切到any size,或者如果您 想保持当前大小,但是移动view,以便它不居中,可以
调整约束偏移

有很多方法可以限制一个视图,但是下面的约束类型提供的基本构建块。

父级约束

连接view的一边到相应的layout边缘,在图5中,view的左边缘连到了父级的左边缘。
image

图5 相对父级的水平约束

位置约束

为两个view水平或垂直方向定义外观规则。图6中,一个Button被约束在一个ImageView下面,间隔24dp
image

图6 垂直位置约束

对齐约束

一个view的边缘与另一个view的同一边缘对齐

图7中,Button的左边缘与ImageView的左边缘对齐

您可以通过向内拖动view调整对齐(增加margin,不支持向外拖,即margin不能为负值)

image

图7 水平对齐约束

image

图8 水平对齐约束 offset

基线对齐约束

对齐一个view的文本基线到另一个view的文本基线。

图9中,TextView的第一行与Button的文本对齐

创建一个基线约束,您可以选中view,点击B,基线就会显示,鼠标拖到另一个view的基线上即可(原文描述有点不一样,可能是Android Studio老版本)

image

与引导线约束

您可以添加水平或垂直的引导线,基于它创建约束。您可以在layout内部用dp单位或者百分比放置引导线,相对layout边缘。

创建引导线:点击工具栏上的Guidelines图标,选择Add Vertical GuidelineAdd Horizontal Guideline

点击引导线顶部的小圆点,切换百分比以及相对边缘(上下,或左右)

引导线对用户不可见

使用Autoconnect和Infer Constraints

Autoconnect会为您添加到layout里的每一个view创建两个或更多约束。Autoconnect默认关闭,您可以点击Layout Editor工具栏上的Turn on Autoconnect图标开启。

当开启后,Autoconnect会在您添加view后创建约束,它不会为已经存在的view(开启前添加)创建约束。如果您在约束创建后拖动view,约束不会改变(虽然margin会),所以如果您要大幅度改变view位置,您必须删除约束。

作为另一种选择,您也可以点击Infer Constraints来为layout里的所有view创建约束.

Infer Constraints(推断约束)是一次性的动作,它通过扫描整个layout来推断所有view的最有效设置,所以它可能会为两个很远的view创建约束。Autoconnect只与最近的元素创建约束。这两种情况下,你都可以通过点击约束把手删除约束,创建新的约束.

调整视图大小

您可以使用view每个角上的把手调整大小,但这么做会硬编码(Hard codes)宽和高,这是您应该避免的,因为硬编码无法适配不同的内容的屏幕尺寸。要选择不同的动态尺寸模式,或定义指定大小,点击view,打开编辑器右边的属性窗口,在窗口的顶部是视图查看器,如图10。

image

图10 属性窗口,包含视图尺寸(1),边距(2),约束偏移(3)

灰色的区域展示了选中的view,距形内的符号展示了宽高设置:

  • image Wrap Content View会根据内容的需要自动展开
  • image Any Size View会自动展开来匹配约束。实际值是0dp,因为view没有期望的尺寸,但它会为匹配约束重置大小。然而,如果给定的尺寸只有一个约束,视图会扩展以适应内容。对此的另一种理解是match constraints(而不是match_parent),因为它在计算约束和边距的限制后,尽可能多地展开视图。
  • image Fixed 您在编辑器里通过调整view大小,在下面的文本框里指定了尺寸

要在这几种模式间切换,在上面的图形上单击。

==注意:您不应该在ConstraintLayout上使用match_parent,用Any Size 0dp 代替==

调整约束偏移

当您给一个view的两边都添加了约束(并且view的尺寸是都是fixed或者wrap content),默认情况下,view会在两个锚点间居中。当一个view居中时,偏移是50%.您可以通过拖动属性窗口中的偏移滑动条或拖动view来调整。

如果您想要view扩展它的尺寸到匹配约束, 把尺寸切到any size

调整view间距

确保您的view间隔均匀,点击工具栏上的Margin 8,为您添加的每个view选择默认的间距。这个按钮会展示您当前的默认间距设置。任何对默认间距的修改,仅对修改后新增的约束生效。

image

您可以通过属性窗口单独修改每个view的间距,(在图10中,边距被设为16dp).

工具中提供的所有边距都是8的倍数,以帮助您的view符合Material Design的8dp square grid recommendations

Angular2 Material Design开发环境搭建

Angular2和Angular1有什么区别?就是Java和Javascript,雷锋和雷锋塔的区别。刚会用Angular1做些小项目,发现Angular2来了,据说性能上提升还是蛮多,所以还是从0开始吧。Angular2虽然还是支持用es5代码开发,但连官方教程都已经用TypeScript,对于之前没有接触过的人来说,学习还是有些成本的。

作为一个新手,搭建环境还是折腾了很久,所以记录下来方便其他新手。

流程:

  1. 使用angular-cli创建项目
  2. 集成Angular Material

angular-cli和Angular Material目前都还在beta阶段,毕竟Angular2正式发布也没有很久,用于生产请自己斟酌。

创建Angular2项目

  1. 安装angular-cli
    npm install -g angular-cli
    
  2. 在适当的位置创建项目
    ng new angular2Demo
    cd angular2Demo
    ng serve
    

创建过程会自动使用npm下载依赖,ng serve即启动内置的服务器,默认端口4200。使用浏览器打开 http://localhost:4200,可以看到项目已经启起来了。

此时打开chrome的network,可以发现有个vendor.bundle.js特别大,有2M多,图上4M多是因为我已经加了Material库。这是webpack把所有依赖的库都打包在一起了,而且没有任何压缩。

image

显然这么大的文件放到生产环境中是不合适的。这是因为默认情况下,启动的是开发模式。

生成生产环境代码:

ng build --prod --env=prod

执行后,会在项目根目录下生成dist目录,所有优化后的代码

以生产模式启动服务器

ng serve --prod

再看network,发现所有资源自动打上了版本号vendor.xxx.bundle.js也被压到了200多k。

集成Material

  1. 安装

Material是以组件的形式开发的,所以安装也使用npm

npm install --save @angular/material

2.修改app.module.ts

打开src/app/app.module.ts,引入material模块

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { MaterialModule } from '@angular/material';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    MaterialModule.forRoot()
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
  1. 引入css样式

在src目录下新建material-design.scss:

@import '~@angular/material/core/theming/all-theme';

// NOTE: Theming is currently experimental and not yet publically released!
@include md-core();
$primary: md-palette($md-deep-purple);
$accent:  md-palette($md-amber, A200, A100, A400);
$theme: md-light-theme($primary, $accent);
@include angular-material-theme($theme);
.m2app-dark {
  $dark-primary: md-palette($md-pink, 700, 500, 900);
  $dark-accent:  md-palette($md-blue-grey, A200, A100, A400);
  $dark-warn:    md-palette($md-deep-orange);
  $dark-theme: md-dark-theme($dark-primary, $dark-accent, $dark-warn);
  @include angular-material-theme($dark-theme);
}

打开根目录下的angular-cli.json,styles配置增加刚刚的文件

"apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "main.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.json",
      "prefix": "app",
      "mobile": false,
      "styles": [
        "styles.css",
        "material-design.scss"
      ],
      "scripts": [],
      "environments": {
        "source": "environments/environment.ts",
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ],
  1. 配置MaterialDesign字体

index.html增加:

    <link href="//fonts.lug.ustc.edu.cn/icon?family=Material+Icons" rel="stylesheet">

使用了中科大的google font镜像

  1. 如果需要用到md-slider(可拖动的进度条)和md-slide-toggle(带滑动动画的开关),还需要安装HammerJS,否则到此环境搭建已完成。

    npm install –save hammerjs
    npm install –save-dev @types/hammerjs

修改src/app/app,module.ts,引入hammerjs

import 'hammerjs';

修改src/tsconfig.json,配置types增加hammerjs

{
  "compilerOptions": {
    "baseUrl": "",
    "declaration": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": ["es6", "dom"],
    "mapRoot": "./",
    "module": "es6",
    "moduleResolution": "node",
    "outDir": "../dist/out-tsc",
    "sourceMap": true,
    "target": "es5",
    "typeRoots": [
      "../node_modules/@types"
    ],
    "types": [
      "hammerjs"
    ]
  }
}

所有配置到此结束,我们写一段简单的代码测试一下。
编辑src/app/app.component.html:

<md-progress-spinner mode="indeterminate" ></md-progress-spinner>

启动服务器,如果所有配置没有错,页面上应该显示旋转的进度圈。

使用SSH实现内网穿透

前面写过一篇使用ngrok实现外网访问内网的文章,但实现起来稍复杂,而且我在实际使用过程中也发现经常断线,ngrok+nginx 实现内网穿透 共用80端口,ngrok的优点在于独立于任何其他服务,需要转发什么域名直接改客户端配置。

今天介绍的是通过SSH穿透内网

SSH内网穿透优点

  1. 无需额外安装服务端客户端(ssh你机器上肯定有)
  2. 可以转发到所以你的机器能直接访问的机器上(你可以给在同一局域网的前端妹子配个外网可访问域名,不用在她的电脑上配置,如果觉得浪费了搭讪机会,可以在她电脑上随便敲点啥)

缺点

如果要将这个功能开放给别人,要注意配置ssh账号权限,自己用无所谓。

一条命令实现

ssh -CfnNT -R 6666:192.168.10.120:8080 fff@server1.pocketdigi.com -p 4356
  • 6666:外网服务器的端口号,外网服务器所有6666的请求会转发
  • 192.168.10.120 转发目标机器,本机就是localhost
  • fff ssh账号
  • server1.pocketdigi.com 外网服务器域名
  • 4356 外网服务器的ssh端口号,默认是22,总有一班人天天扫,所以改了

此时所有访问server1.pocketdigi.com:6666的请求会转发到192.168.10.120:8080,可在外网服务器上执行

curl http://localhost:6666

查看,如果192.168.10.120:8080启了http服务,应该能看到.
鉴于ssh会超时断开,建议使用autossh,断开会自动重连

配置nginx

参考 ngrok+nginx 实现内网穿透 共用80端口

开机自启

  1. 增加启动文件 /usr/local/bin/nat_forward

    !/bin/bash

    autossh -M 5678 -CfnNT -R 6666:localhost:8080 fff@server1.pocketdigi.com -p 4356

2.增加执行权限

sudo chmod +x /usr/local/bin/nat_forward

3.增加启动项

偏好设置–用户与群组–登录项,添加/usr/local/bin/nat_forward

阿里云oss grunt上传插件

最近在研究前端工程化,准备把项目里的静态文件全传到阿里云 oss上,构建工具我们选的是grunt,npm里也有两三个oss上传插件,但是更新时间都比较久了,一一试过后都不能用,于是自己写了个插件。托管在 https://github.com/pocketdigi/grunt-aliyun-oss , 欢迎体验。
支持ContentType自动识别,Cache Control设置,上传路径设置。

grunt-aliyun-oss

aliyun oss plugin for grunt

Getting Started

This plugin requires Grunt ~0.4.5

If you haven’t used Grunt before, be sure to check out the Getting Started guide, as it explains how to create a Gruntfile as well as install and use Grunt plugins. Once you’re familiar with that process, you may install this plugin with this command:

npm install grunt-aliyun-oss --save-dev

Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:

grunt.loadNpmTasks('grunt-aliyun-oss');

The “aliyun_oss” task

Overview

In your project’s Gruntfile, add a section named aliyun_oss to the data object passed into grunt.initConfig().

grunt.initConfig({
  aliyun_oss: {
    options: {
      accessKeyId: 'accessKeyId',
      secretAccessKey: 'secretAccessKey',
      endpoint: 'http://oss-cn-hangzhou.aliyuncs.com',
      bucketName:'bucket',
      cacheControl:'no-cache'
    },
    your_target: {
      // Target-specific file lists and/or options go here.
    },
  },
});

AliYun OSS Api doc

Options

options.accessKeyId

Type: String

AliYun OSS accessKeyId

options.secretAccessKey

Type: String

AliYun OSS secretAccessKey

options.endpoint

Type: String

AliYun OSS endpoint

options.bucketName

Type: String

AliYun OSS bucketName

options.bucketName

Type: String Default value:no-cache

Cache Control in the Http Header,default value is no-cache

See Http Cache

Usage Examples

Default Options

In this example, the default options are used to upload all file expect html file in dist directory to oss bucket1 bucket,and the oss root path is static/

grunt.initConfig({
   aliyun_oss: {
              default_options: {
                  options: {
                      accessKeyId: 'xxxx',
                      secretAccessKey: 'xxxx',
                      endpoint: 'http://oss-cn-hangzhou.aliyuncs.com',
                      bucketName:'bucket1',
                      cacheControl:'no-cache'
                  },
                  files: [
                      {
                          expand: true,
                          cwd: 'dist',
                          src: ['**/*','!**/*.html'],
                          dest:'static/'
                      }
                  ]
              }
          }
});

Release History

  • v0.2.0 fix upload bug,support ContentType,cache control.
  • v0.1.0 first version

AngularJs表单形式发送POST请求

AngularJS 的POST请求 默认Content-Type是”application/json”,并不是表单形式的”application/x-www-form-urlencoded”提交,服务端接收到的是对象,而不是字段,当然对于RESTful接口来说,正好需要对象,SpringMVC通过@RequestBody注解就可以得到相应对象,很是方便。

如果需要使用传统方式,可以设置Content-Type

    $http.post('/login/in', $.param({username: $scope.username, password: $scope.password}),
        {headers: {'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'}})
        .then(function (response) {
            if (response.data.success) {
                console.log("登录成功");
            } else {
                console.log(response.data.msg);
            }
        });

$.param是JQuery方法,将js对象转成username=username&password=password形式