九江襄阳西安洛阳亳州南京十二日游

总结

时间:12 天

行驶里程:2961 公里

途经省份:6 个,江西、湖北、陕西、河南、安徽、江苏

城市:6 个,九江、襄阳、西安、洛阳、亳州、南京

住宿:1089 元/人

门票:354 元/人

油费:1985 元/车

过路费:1435 元/车

杭州 - 江西,九江

第一天,9 点 30 分出发,下午 4 点到达九江。晚上游鄱阳湖边。

🚗 5 小时 40 分钟,498 公里

油费 320 元,过路费 220 元

鄱阳湖


庐山 门票 180 元



🏨 九江锦绣之星精品酒店 139

九江 - 湖北,襄阳

第二天,上午 9 点 30 分出发,下午 5 点到达襄阳。晚上游古城墙。

🚗 6 小时,561 公里

油费 400 元,过路费 285 元

襄阳古城墙


护城河


🏨 老河口天河大酒店 220


襄阳 - 陕西,西安

第三天,上午 9 点 30 分出发,下午 5 点到达西安。
第四天,游兵马俑。
第五天,游汉长安城、唐大明宫。

🚗 6 小时,461 公里

油费 340 元,过路费 300 元

西安古城墙 门票 54 元


秦始皇兵马俑博物馆 门票 150 元


汉长安城遗址

唐大明宫含元殿遗址


🏨 锦江之星品尚西安东二环矿山路酒店 179


西安 - 河南,洛阳

第六天,上午 9 点 30 分出发,下午 4 点到达洛阳。
第七天,游龙门石窟。
第八天,游白马寺。

🚗 4 小时 30 分钟,372 公里

油费 240 元,过路费 180 元

龙门石窟 门票 100 元


洛阳白马寺 门票 50 元


汉魏洛阳故城遗址


🏨 洛阳王府井亚朵酒店 275


洛阳 - 安徽,亳州

第九天,上午9点30分出发,下午2点到达亳州,游曹操公园。

🚗 3 小时 50 分钟,374 公里

油费 240 元,过路费 150 元

曹操公园

北关老街

🏨 亳州君亭华丰酒店 248


亳州 - 江苏,南京

第十天,上午 9 点 30 分出发,下午 3 点到达南京,晚上游夫子庙。
第十一天,游明故宫。

🚗 4 小时 20 分钟,420 公里

油费 270 元,过路费 150 元

夫子庙


明故宫



🏨 南京晴舍酒店 209


南京-杭州

第十二天,上午 9 点 30 分出发,下午 3 点到达杭州。

🚗 3 小时 10 分钟,275 公里

油费 175 元,过路费 150 元

Installing OwnCloud on UbuntuMate RaspberryPi

OS environment:

uname -a
Linux raspberrypi 4.1.19-v7+ #858 SMP Tue Mar 15 15:56:00 GMT 2016 armv7l armv7l armv7l GNU/Linux

cat /etc/issue
Ubuntu 16.04.1 LTS \n \l

Install LAMP on the UbuntuMate

sudo apt-get install php
sudo apt-get install apache2
sudo apt-get install libapache2-mod-php
sudo apt-get install php-gd
sudo apt-get install php-json
sudo apt-get install php-mysql
sudo apt-get install php-curl
sudo apt-get install php-intl
sudo apt-get install php-mcrypt
sudo apt-get install php-imagick
sudo apt-get install php-zip
sudo apt-get install php-dom
sudo apt-get install php-mbstring
sudo apt-get install mysql-server

Get the archive file

wget https://download.owncloud.org/community/owncloud-9.1.0.zip
unzip owncloud-9.1.0.zip

Deploy apache and configure

sudo cp -r owncloud /var/www
sudo nano /etc/apache2/sites-available/owncloud.conf

Alias /owncloud "/var/www/owncloud/"

<Directory /var/www/owncloud/>
  Options +FollowSymlinks
  AllowOverride All

 <IfModule mod_dav.c>
  Dav off
 </IfModule>

 SetEnv HOME /var/www/owncloud
 SetEnv HTTP_HOME /var/www/owncloud
 Satisfy Any
</Directory>

sudo ln -s /etc/apache2/sites-available/owncloud.conf /etc/apache2/sites-enabled/owncloud.conf

sudo a2enmod rewrite
sudo a2enmod headers
sudo a2enmod env
sudo a2enmod dir
sudo a2enmod mime

sudo chown -R www-data:www-data /var/www/owncloud/

Start service

sudo service apache2 restart

Finish the installation

Open the url “_http://**{your_machine_host}**/owncloud/_“ with a browser and type below.

Administrator info:

user:   admin
password:   admin

Data dir info:

/var/www/owncloud/data

Database info:

user:   root (only root required)
password:   root
database:   owncloud
host:   localhost

Finish!

Running UbuntuMate on Raspberrypi3 with a USB disk

1. Why UbuntuMate?

Raspberrypi’s officail support OS is Raspbian, but I find that it is not very well in supporting Chinese charset.

So I use UbuntuMate 16.04 LTS instead, it seems did more works in international.

2. Prepare

First, Download Win32DiskImager, and the UbuntuMate 16.04 image. It only works under Windows10, I tried it on Mac OSX but failed.

Prepare one USB HDD and one SD card. The SD card needs, not very large, 64M space and above, and mine is 2G.

When the Raspberrypi is booting, you need to connect the USB HDD and put the SD card in too. It seems a waste but this is the only way I found to run a raspberrypi on UbuntuMate.

Connect the USB HDD, use Win32DiskImager to copy the UbuntuMate image on the USB HDD. It will makes the disk divide into 3 partitions. And only the first partition is availible on Win10, you can’t see the others two.

Insert the SD card, copy every file from the HDD to SD card.

Edit the file “cmdline.txt” on SD card:

dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait

Change to:

dwc_otg.lpm_enable=0 console=tty1 root=/dev/sda2 rootfstype=ext4 elevator=deadline rootwait

Change the “mmcblk0p2“ to “sda2“, and save.

3. Install

Insert the SD card in and connect the USB HDD on the raspi, and power on. It will starts the UbuntuMate install program if everything goes fine.

After the UbuntuMate installed, you will find the second partition, we mentioned above, only has 8G space and the rest has gone.

We need expand the USB HDD’s file system.

sudo fdisk /dev/sda
d,2
n,2,[Enter],[Enter]
w

sudo reboot

sudo resize2fs /dev/sda1

This will makes your HDD have the full space in the second partition.

4. Shadowsocks

Install Shadowsocks-Qt5
sudo add-apt-repository ppa:hzwhuang/ss-qt5

sudo apt-get update

sudo apt-get install shadowsocks-qt5
Start Shadowsocks-Qt5
Install GenPAC and gfwlist.txt
sudo apt-get install python-pip python-dev build-essential

sudo pip install --upgrade pip

sudo pip install --upgrade virtualenv

sudo pip install genpac

cd ~; mkdir shadowsocks; cd shadowsocks

wget http://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt

genpac -p "SOCKS5 127.0.0.1:1080" --gfwlist-proxy="SOCKS5 127.0.0.1:1080" --output="autoproxy.pac" --gfwlist-local=gfwlist.txt
Add a proxy

System -> Preference -> Internet and Network -> Network Proxy

Select Automatic Proxy

Set Automatic configure URL as:

file:///home/{USERNAME}/shadowsocks/autoproxy.pac

5. Ohters

Googlepinyin
sudo apt-get install fcitx fcitx-googlepinyin
Mysql
sudo apt-get install mysql-server
sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf

Remember, comment bind-address = 127.0.0.1 using the # symbol

6. Finish

我的常用软件

习武之人总得有几件趁手的兵器,瓷器匠人也要选择合适的金刚钻。

1. 开发:

1.1 android studio

android 开发,按住 alt 键选中多行时是列模式,终于可以取代臃肿的 ultra editor 了。

1.2 eclipse

java 开发。

1.3 fiddler

http 抓包神器。

1.4 chrome + vpn

访问 google。

2. 博客:

2.1 github - git shell

下载 github 上的代码。

2.2 dropbox

同步博客文档。

2.3 hexo

博客网站框架,跟 github 绝配。

3. 文本:

3.1 sublime text

sublime text3,格式化代码专用,HTML/JS/CSS 需要安装 Prettify 插件。有时也用来代替 python 的 idle。

3.2 notepad ++

日常文字。

3.3 winmerge

比较文本,无可替代。

3.4 markdown pad2

md 编辑器,windows 下的 mou。

4. 笔记:

4.1 有道云笔记

日常文字。

5. 服务器:

5.1 SecureCRT

操作服务器。用惯了,缺点是安装略烦。

5.2 winscp

上传、下载文件。

5.3 Sqlyog

操作 mysql。
优点:直接写 sql,相对 navicat 操作更方便。
缺点:多条 sql 查询起来比较麻烦,不能导出建表语句。

6. 画图:

6.1 dia

做 uml、画流程图。

6.2 xmind

思考、做测试用例。

6.3 axure

画原型。

6.4 OpenProj

画甘特图。

6.5 MarkMan

视觉稿尺寸标注。

6.6 Photoshop

改 psd。

7. 文档:

7. wps office

处理 word、excel、pdf。

8. 其它:

8.1 qq

8.2 synergy

用笔记本的键盘操作台式机,就用它。不过好像现在收费了。

8.3 好压

解 war、 jar 和 tar。

8.4 tower.im

项目协作,分配任务,沟通交流,共享文件,自动存档。

8.5 搜狗输入法

除了不能方便输入 ,其他都好。

使用 xUtils 上传文件至阿里云 OSS 时遇到的错误

项目中使用 xUtils,在上传 OSS 时遇到问题,参数全都正确,但服务器却返回“400 Bad request”错误。
用 Fiddler 比对正常的请求,发现上传失败时的 RequestBody 中每一项,除了文件 file 以外,都多了 Content-Type: text/plain; charset=UTF-8Content-Transfer-Encoding: 8bit 这两项。
阿里云 OSS 服务端返回的 ErrorCode:400 MalformedPOSTRequest “Post 请求的 body 格式非法”。提示:The body of your POST request is not well-formed multipart/form-data
文件 file 需要 Content-Type: image/png,至于 Content-Transfer-Encoding: binary 可有可无。

尝试移除上述两个参数后,上传请求被正常接受。
/src/com/lidroid/xutils/http/client/multipart/FormBodyPart.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package com.lidroid.xutils.http.client.multipart;
import com.lidroid.xutils.http.client.multipart.content.ContentBody;
public class FormBodyPart {
private final String name;
private final MinimalFieldHeader header;
private final ContentBody body;
public FormBodyPart(final String name, final ContentBody body) {
super();
if (name == null) {
throw new IllegalArgumentException("Name may not be null");
}
if (body == null) {
throw new IllegalArgumentException("Body may not be null");
}
this.name = name;
this.body = body;
this.header = new MinimalFieldHeader();
generateContentDisposition(body);
generateContentType(body);
// generateTransferEncoding(body); // 改动一:注释掉,不然无法上传到OSS。
}
public FormBodyPart(final String name, final ContentBody body, final String contentDisposition) {
super();
if (name == null) {
throw new IllegalArgumentException("Name may not be null");
}
if (body == null) {
throw new IllegalArgumentException("Body may not be null");
}
this.name = name;
this.body = body;
this.header = new MinimalFieldHeader();
if (contentDisposition != null) {
addField(MIME.CONTENT_DISPOSITION, contentDisposition);
} else {
generateContentDisposition(body);
}
generateContentType(body);
// generateTransferEncoding(body); // 改动二:注释掉,不然无法上传到OSS。
}
public String getName() {
return this.name;
}
public ContentBody getBody() {
return this.body;
}
public MinimalFieldHeader getHeader() {
return this.header;
}
public void addField(final String name, final String value) {
if (name == null) {
throw new IllegalArgumentException("Field name may not be null");
}
this.header.addField(new MinimalField(name, value));
}
protected void generateContentDisposition(final ContentBody body) {
StringBuilder buffer = new StringBuilder();
buffer.append("form-data; name=\"");
buffer.append(getName());
buffer.append("\"");
if (body.getFilename() != null) {
buffer.append("; filename=\"");
buffer.append(body.getFilename());
buffer.append("\"");
}
addField(MIME.CONTENT_DISPOSITION, buffer.toString());
}
protected void generateContentType(final ContentBody body) {
StringBuilder buffer = new StringBuilder();
buffer.append(body.getMimeType()); // MimeType cannot be null
if (body.getCharset() != null) { // charset may legitimately be null
buffer.append("; charset=");
buffer.append(body.getCharset());
return; // 改动三:如果非空就返回,这样file的ContentType任然是保留的。
}
addField(MIME.CONTENT_TYPE, buffer.toString());
}
protected void generateTransferEncoding(final ContentBody body) {
addField(MIME.CONTENT_TRANSFER_ENC, body.getTransferEncoding()); // TE cannot be null
}
}

坑了四个小时。

如何生成阿里云 OSS 访问控制签名

最近项目为降低运维成本,采用了阿里云的 OSS、RDS、SLB。其他服务对开发来讲已足够傻瓜,要点三十二个赞。相对来讲只有 OSS 还有点门槛,但没办法,存储业务使然。
我们通过 JS 来上传文件到 OSS,采用 PostObject。因为控制访问(只读或读写都限制的情况),Post 时需要带上 policy 和 Signature。但是这里官方文档有些歧义,没明确说明用于生成 Signature 的 policy 字符串是否是需要 base64 加密,也没有附带例子要自己试错,没人性啊就算是 5 分钟,用户的时间也是时间啊。PD 偷懒了啊,要知道文档写得好,老婆不会跑啊。云计算是一把手项目,看来此言非虚。
另外因为 RFC2045 规定了 Base64 一行最长 76 个字符(也有可能 64 个字就换行的哦),生成 Signature 时要注意把换行符去掉。
还有注意时区要用 GMT。

Java 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public StorageAccessTokenDo generateStorageAccessToken(String fileName, Integer userId,
Integer expireSeconds) {
// 默认600秒后过期
if (null == expireSeconds || expireSeconds < 0) {
expireSeconds = 600;
}
// 获得GMT时间
TimeZone defaultTimeZone = TimeZone.getDefault();
TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT");
TimeZone.setDefault(gmtTimeZone);
Calendar calendar = Calendar.getInstance(gmtTimeZone);
calendar.add(Calendar.SECOND, expireSeconds);
Date expire = calendar.getTime();
TimeZone.setDefault(defaultTimeZone);
// 开始组装policy
String expiration = DateUtils.formatDateTime(DateUtils.nextSecond(expire),
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
String policyOrigin = "{\"expiration\": \"" + expiration
+ "\", \"conditions\": [{\"bucket\": \"" + bucketName + "\"}]}";
String policy = base64Encode(policyOrigin.getBytes());
// 生成签名
String signature = getSignature(policy.getBytes(), accessKeySecret.getBytes());
// 生成key(含目录路径)
String key = DateUtils.formatDateDirectory(new Date()) + userId + "/" + fileName;
// 返回storageAccessToken
StorageAccessTokenDo sat = new StorageAccessTokenDo(accessKeyId, policy, signature, key);
return sat;
}
/**
* base64加密
*
* @param origin
* @return
*/
@SuppressWarnings("restriction")
private static String base64Encode(byte[] origin) {
if (null == origin) {
return null;
}
return new sun.misc.BASE64Encoder().encode(origin).replace("\n", "").replace("\r", "");
}
/**
* 使用HmacSHA1加密方式生成签名数据
*
* @param data
* @param key
* @return
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
*/
private static String getSignature(byte[] data, byte[] key) {
SecretKeySpec signingKey = new SecretKeySpec(key, HMAC_SHA1);
try {
Mac mac;
mac = Mac.getInstance(HMAC_SHA1);
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(data);
return base64Encode(rawHmac);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
return null;
}

Python2 实现

1
2
3
4
5
6
7
8
9
10
11
import base64
import hmac
import sha
accessKeySecret = "NmgYoieU2AC5hnI4tsQq2zRgZvx2Lq"
policy = '''{"expiration": "2015-07-01T12:00:00.000Z", "conditions": [{"bucket": "test-bucket"}]}'''
print('policy = ' + base64.encodestring(policy)) #注意,76 个字符后会换行
signature = hmac.new(accessKeySecret, base64.encodestring(policy).strip().replace("\n", ""), sha)
print('signature = ' + base64.encodestring(signature.digest()).strip())

A CSS Problem

今天遇见一个奇怪的 CSS 问题。
IE6 & 7 下样式不见了,用其他浏览器访问都没问题。
我开始以为是测试服务器缓存的问题,查了半天不是。
后来对比 SVN 才发现:

1
2
3
4
5
6
.list-filter .wholesalesearch span a{
cursor: pointer;
padding-left:5px;
font-family:'微软雅黑';
color: #FF9E35;"
}

原来是前端工程师不小心多加了一个引号,被这个问题坑了三十分钟。
开发确实需要从 CSS 和 HTML 中解脱出来。


sublime text3 + Prettify 插件,格式化 css 文件,可更方便地排查此类问题。
2015-7-22 10:36:42

How to Config a Hexo Blog

After built the Hexo blog, you need change into something more comfortable.

###1. Change to a new theme
Pick one from http://hexo.io/themes/, I chose “the light”.

1
$ git clone https://github.com/hexojs/hexo-theme-light themes/light

###2. Edit the theme’s _config.yml, not Hexo’s

1
$ vi themes/light/_config.yml

1
2
3
4
5
6
7
widgets:
- search
- category
- tag
- tagcloud
- archive
- recent_posts

Attention, this theme has no archive widget. Write one yourself if you want it.
Learn the last theme’s widget first.

1
$ cat themes/landscape/layout/_widget/archive.ejs

Write a new one.

1
$ vi themes/light/layout/_widget/archive.ejs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<%
var archiveDir = 'archives';
var posts = site.posts.sort('date', -1);
var data = [];
var length = 0;
function link(item){
var url = archiveDir + '/' + item.year + '/';
if (item.month < 10) url += '0';
url += item.month + '/';
return url_for(url);
}
posts.forEach(function(post){
// Clone the date object to avoid pollution
var date = post.date.clone();
date = date.tz('Asia/Shanghai');
date = date.locale('zh-CN');
var year = date.year();
var month = date.month() + 1;
var name = date.format('MMMM YYYY');
var lastData = data[length - 1];
if (!lastData || lastData.name !== name){
length = data.push({
name: name,
year: year,
month: month,
count: 1
});
} else {
lastData.count++;
}
});
var item, i, len;
%>
<% if (site.posts.length){ %>
<div class="widget tag">
<h3 class="title"><%= __('archive_a') %></h3>
<ul class="entry">
<% for (i = 0, len = data.length; i < len; i++){ %>
<% item = data[i]; %>
<li><a href="<%- link(item) %>"><%= item.name %></a><small><%= item.count %></small></li>
<% } %>
</ul>
</div>
<% } %>

###3. Edit Hexo’s _config.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# Hexo Configuration
## Docs: http://hexo.io/docs/configuration.html
## Source: https://github.com/hexojs/hexo/
# Site
title: Newborn
subtitle:
description:
author: Cherokee G
language:
- zh-CN
- en
timezone: Asia/Shanghai
# URL
## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'
url: http://{username}.github.io
root: /
permalink: :year/:month/:day/:title/
permalink_defaults:
# Directory
source_dir: source
public_dir: public
tag_dir: tags
archive_dir: archives
category_dir: categories
code_dir: downloads/code
i18n_dir: :lang
skip_render:
# Writing
new_post_name: :title.md # File name of new posts
default_layout: post
titlecase: false # Transform title into titlecase
external_link: true # Open external links in new tab
filename_case: 0
render_drafts: false
post_asset_folder: false
relative_link: false
future: true
highlight:
enable: true
line_number: true
auto_detect: true
tab_replace:
# Category & Tag
default_category: uncategorized
category_map:
tag_map:
# Date / Time format
## Hexo uses Moment.js to parse and display date
## You can customize the date format as defined in
## http://momentjs.com/docs/#/displaying/format/
date_format: YYYY-MM-DD
time_format: HH:mm:ss
# Pagination
## Set per_page to 0 to disable pagination
per_page: 25
pagination_dir: page
# Extensions
## Plugins: http://hexo.io/plugins/
## Themes: http://hexo.io/themes/
##theme: landscape
theme: light
# Deployment
## Docs: http://hexo.io/docs/deployment.html
deploy:
type: git
repo: https://github.com/{username}/{username},github.io.git
branch: master

###4. Test on my server

1
$ hexo s

###5. Generate Again

1
$ hexo g

###6. Deploy Again

1
$ hexo d

OK. As you see, it is easy to config a Hexo blog, enjoy it :)

How to Use GitHub Pages and Hexo to Build a Blog

###1. Install Nodejs, npm and Git
In Mac, download Nodejs and Git from their offical website. And Nodejs contains npm already.
https://nodejs.org
https://git-scm.com
In Ubuntu you can install by apt-get.

1
2
3
$ apt-get install nodejs
$ apt-get install npm
$ apt-get install git

###2. Install Hexo

1
$ npm install -g hexo-cli

I want to use GitHub for remote host, so also need to install this plugin:

1
$ npm install hexo-deployer-git --save

###3. Setup Hexo

1
2
3
$ hexo init {folder}
$ cd {folder}
$ npm install

###4. Configuration

1
2
3
4
5
6
7
$ vi _config.yml
# Deployment
## Docs: http://hexo.io/docs/deployment.html
deploy:
type: git
repo: https://github.com/{username}/{username}.github.io.git
branch: master

###5. Start for a test

1
$ hexo server

Visit http://{your IP address}:4000/ in your browser. If everything is OK, you will see it.

###6. Deploy to remote GitHub

1
2
3
4
$ git config --global user.name "{username}"
$ git config --global user.email "{email}"
$ git config --global credential.helper store
$ hexo deploy

The 3rd line configure makes git remember your username and password.
Visit http://{username}.github.io/ for test.

###7. Create a new post

1
$ hexo new "My New Post"

###8. Generate

1
$ hexo generate

###9. Deploy the new post again

1
$ hexo deploy

OK, It’s done. Next time I will show you how to config a Hexo blog.