Macbook Minikube 配置

配置步骤

安装 kubectl

brew install kubernetes-cli

安装 Hypervisor

brew install hyperkit

安装 Minikube

brew install minikube

启动 Minikube

minikube start --vm-driver=hyperkit --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers

检查集群的状态

minikube status

1
2
3
4
5
6
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

查看 Minikube ip

minikube ip

配置环境变量

export no_proxy="127.0.0.1,{minikube ip}"

测试部署

kubectl create deployment hello-minikube --image=registry.cn-hangzhou.aliyuncs.com/google_containers/echoserver:1.10

将部署暴露为服务

kubectl expose deployment hello-minikube --type=NodePort --port=8080

查看服务

kubectl get pod

获取服务URL

minikube service hello-minikube --url

查看仪表盘

minikube dashboard

删除服务

kubectl delete services hello-minikube

删除部署

kubectl delete deployment hello-minikube

停止Minikube集群

minikube stop

删除Minikube集群

minikube delete

K8s 配置 Nginx

deployment 文件

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
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 1 # tells deployment to run 2 pods matching the template
template:
metadata:
labels:
app: nginx
spec:
shareProcessNamespace: true

containers:
- name: nginx
image: nginx:1.8
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
type: NodePort

Flask入坑指南

Flask

Session

Flask默认 Session 是基于浏览器 Cookie 的,这样很多从 J2EE 转过来的程序员第一次必然掉坑。Flask 官方给了多种解决方案,具体可以参考 Sessions。其中基于Redis和 mongodb 的方案比较适合分布式部署。当然你也可以参考里面的实现,按照自己的情况来处理 Session。

Json返回值

  1. FLask出于安全原因不允许返回 Array,只能返回 Object。具体原因参见 json-security
  2. 默认无法序列化 Decimal。
    解决办法:安装 simplejson

Flask-SQLAlchemy/SQLAlchemy

同一个事物中 Query 会自动触发 commit

这个问题严格上说不是 Flask 的,实际是SQLAlchemy的。默认 DB 的 Session 是Autoflash的,这样当你在同一个事物中,既有 update 又有 select 操作的时候,就会出现各种灵异错误。解决办法是使用no_autoflush.

1
2
with db.session.no_autoflush:
room = Room.query.get(pre_order_info.roomId)

Model无法序列化成 json

model 类继承AutoSerialize,然后转json时调用get_public方法

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
class AutoSerialize(object):
'Mixin for retrieving public fields of model in json-compatible format'
__public__ = None

def get_public(self, exclude=(), extra=()):
"Returns model's PUBLIC data for jsonify"
data = {}
keys = self._sa_instance_state.attrs.items()
public = self.__public__ + extra if self.__public__ else extra
for k, field in keys:
if public and k not in public: continue
if k in exclude: continue
value = self._serialize(field.value)
if value:
data[k] = value
return data

@classmethod
def _serialize(cls, value, follow_fk=False):
if isinstance(value, datetime.datetime):
ret = value.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(value, Decimal):
ret = str(value)
elif hasattr(value, '__iter__'):
ret = []
for v in value:
ret.append(cls._serialize(v))
elif AutoSerialize in value.__class__.__bases__:
ret = value.get_public()
else:
ret = value

return ret

Flask-Auth

许久不更新,与最新版的 Flask 不兼容。这个包本身的设计比较轻巧,此外对于权限的配置是以 Resource+Operation 方式来配置,比较灵活,还是值得一用的。具体修改如下:

Flask-WTF/WTForms

  1. 注意表单类的基类From,要引用flask_wtf中,其在原有WTForms增强了功能。
  2. 对于SelectField类型,无法传空值。会导致validate无法通过。

hexo+github page搭建博客

hexo安装

安装 nvm

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.29.0/install.sh | bash

安装 Node

1
2
nvm install node && nvm alias default node
nvm use --delete-prefix v4.2.1 --silent

切换taobao 源

1
2
npm config set registry https://registry.npm.taobao.org
npm config set disturl https://npm.taobao.org/dist

安装 hexo

1
npm install -g hexo

hexo 使用

初始化

1
2
3
4
mkdir hexo
cd hexo
hexo init
npm install

生成文件

1
hexo generate


1
hexo g

清理文件

1
hexo clean

本地预览

1
hexo server


1
hexo s

spring动态数据源

核心思想

通过使用Spring提供的AbstractRoutingDataSource替代原有 dataSource,在每次操作数据库的时候通过类中的determineTargetDataSource()方法获取当前数据源。这样我们就可以通过切面技术,在不同的切面,切入不同的数据源名称,使Spring获取的时候拿到的是不同的数据源。就相当于在多个 dataSource前面增加了一个路由层。

基础用法

原理

通过查看AbstractRoutingDataSource的determineTargetDataSource实现,可以发现基本原理就是通过determineCurrentLookupKey()获取当前数据源的 key,然后从resolvedDataSources获取对应数据源并返回。无法获取则返回默认配置数据源。而resolvedDataSources是通过targetDataSources初始化的。也就是说,我们在使用中配置两个地方。

  1. defaultTargetDataSource
  2. targetDataSources
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public void afterPropertiesSet() {
    if (this.targetDataSources == null) {
    throw new IllegalArgumentException("Property 'targetDataSources' is required");
    }
    this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
    for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
    Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
    DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
    this.resolvedDataSources.put(lookupKey, dataSource);
    }
    if (this.defaultTargetDataSource != null) {
    this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
    }
    }
1
2
3
4
5
6
7
8
9
10
11
12
   protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}

步骤1

通过 ThreadLocal 记录获取当前使用的数据源 Key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DataSourceHolder {
//线程本地环境
private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
//设置数据源
public static void setDataSource(String customerType) {
dataSources.set(customerType);
}
//获取数据源
public static String getDataSource() {
return (String) dataSources.get();
}
//清除数据源
public static void clearDataSource() {
dataSources.remove();
}
}

步骤2

实现AbstractRoutingDataSource

1
2
3
4
5
6
7
public class DynamicDataSource extends AbstractRoutingDataSource {

@Override
protected Object determineCurrentLookupKey() {
return DataSourceHolder.getDataSource();
}
}

步骤3

配置 spring 文件

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
<bean id="adamDataSource1" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/am?useUnicode=true&amp;characterEncoding=utf8"/>
<property name="username" value="root" />
<property name="password" value="root" />
<property name="filters" value="stat" />
<property name="maxActive" value="500" />
<property name="initialSize" value="10" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="10" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
</bean>
<bean id="adamDataSource2" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/am2?useUnicode=true&amp;characterEncoding=utf8"/>
<property name="username" value="root" />
<property name="password" value="root" />
<property name="filters" value="stat" />
<property name="maxActive" value="500" />
<property name="initialSize" value="10" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="10" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
</bean>

<bean id="dataSource" class="com.guall.dao.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="adamDataSource1" value-ref="adamDataSource1"></entry>
<entry key="adamDataSource2" value-ref="adamDataSource2"></entry>
</map>
</property>
<!-- 默认目标数据源为你主库数据源 -->
<property name="defaultTargetDataSource" ref="adamDataSource"/>
</bean>
<bean id="adamJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"/>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
</bean>

步骤4

前三步已经完成了所有的准备工作,剩下的就是在采取不同的方式,根据需要来切换数据源了。可以用到的方式,如 Filter、Interceptor、AOP 等。

高级用法

不预先配置多种数据源,通过重写determineTargetDataSource方法,按照自己的方式进行数据源的创建和获取操作。

微信公众号摇一摇开发

前言

看了那么多“扫一扫”,不妨“摇一摇”——微信“摇一摇周边”功能开发实录的文章,玩了一次开发。结果发现有些微信已经提供 web 方式操作,还有些逻辑说的不甚明了,所以记录下过程。

先决条件

订阅号或者服务号完成认证

开通摇一摇周边功能。

开通方式:添加功能插件->摇一摇周边->使用公众号登录->申请开通
填写相关信息及证明文件后,等待审核。



iBeacon设备

购买设备iBeacon设备。

这个可以从淘宝上购买,也可以通过认证后,从微信官方渠道进行购买。

新增设备

从周边后台添加新增设备

由于我是自行购买设备,所以选择了方式2。之后填写需要的数量和理由后,完成申请。

申请之后的设备在未激活设备处可以看到。

查看设备信息,可以得到对应的UUID、Major、Minor信息

配置设备

使用厂家提供的工具将上面获得的UUID、Major、Minor写入设备

激活设备

保证设备电源处于开启状态,打开手机蓝牙,打开微信摇一摇功能,选择周边,然后摇,能够发现设备,点击后,可完成激活。完成后,可在周边后台看到设备状态。具体可以参考设备配置指引 如何激活设备?

页面配置

点击页面管理、新建页面、选择配置设备

Python Java AES

AES

密码学中的高级加密标准(Advanced Encryption Standard,AES),又称高级加密标准Rijndael加密法,
是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界
所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院 (NIST)于2001年11月26日
发布于FIPS PUB197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密
中最流行的算法之一。该算法为比利时密码学家Joan Daemen和VincentRijmen所设计,结合两位作者的名
字,以Rijndael之命名之,投稿高级加密标准的甄选流程。(Rijdael的发音近于 “Rhinedoll”。)

加密填充模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
算法/模式/填充                16字节加密后数据长度        不满16字节加密后长度
AES/CBC/NoPadding 16 不支持
AES/CBC/PKCS5Padding 32 16
AES/CBC/ISO10126Padding 32 16
AES/CFB/NoPadding 16 原始数据长度
AES/CFB/PKCS5Padding 32 16
AES/CFB/ISO10126Padding 32 16
AES/ECB/NoPadding 16 不支持
AES/ECB/PKCS5Padding 32 16
AES/ECB/ISO10126Padding 32 16
AES/OFB/NoPadding 16 原始数据长度
AES/OFB/PKCS5Padding 32 16
AES/OFB/ISO10126Padding 32 16
AES/PCBC/NoPadding 16 不支持
AES/PCBC/PKCS5Padding 32 16
AES/PCBC/ISO10126Padding 32 16

ECB(Electronic Code Book电子密码本)模式

ECB模式是最早采用和最简单的模式,它将加密的数据分成若干组,每组的大小跟加密密钥长度相同,然后每组都用相同的密钥进行加密。
优点:

  • 模式操作简单;
  • 有利于并行计算;
  • 误差不会被传送;

缺点:

  • 不能隐藏明文的模式,明文中的重复内容在密文中暴露出来;
  • 可能对明文进行主动攻击;

因此,此模式适于加密小消息。

Java

1
2
3
4
5
6
7
8
9
10
11
String key = "1234567890123456";

SecretKey secretKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
String data = "abcd";
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encrypted = cipher.doFinal(data.getBytes());
System.out.println(Encodes.encodeBase64(encrypted));

cipher.init(Cipher.DECRYPT_MODE, secretKey);
System.out.println(new String(cipher.doFinal(encrypted)));

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
from Crypto.Cipher import AES
from itsdangerous import base64_encode,base64_decode

BS = AES.block_size
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-ord(s[-1])]

# key = os.urandom(16) # the length can be (16, 24, 32)
key = b'1234567890123456'
text = 'abcd'
cipher = AES.new(key)
print base64_encode(cipher.encrypt(pad(text)))
print cipher.decrypt(base64_decode(base64_encode(cipher.encrypt(pad(text)))))

CBC(Cipher Block Chaining,加密块链)模式

为了克服ECB模式的安全缺陷,设计了密码分组链接模式,它使得当同一个明文分组重复出现时产生不同的密文分组。对每个分组使用相同的密钥,加密函数的输入是当前的明文分组和前一个密文分组的异或。从效果上看,将明文分组序列的处理连接起来了。
为了产生第一个密文分组,要使用一个初始向量IV,IV必须被发送方和接收方都知道,为了做到最大程度的安全性,IV应该和密钥一样受到保护。
CBC模式的加加解密,要求 key 和 IV 都必须要一致
优点:

  • 不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。

缺点:

  • 不利于并行计算;
  • 误差传递;
  • 需要初始化向量IV

Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
String iv="1234567890123456";
String key = "1234567890123456";

SecretKey secretKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
String data = "abcd";
int blockSize = cipher.getBlockSize();
byte[] dataBytes = data.getBytes();
int plaintextLength = dataBytes.length;
if (plaintextLength % blockSize != 0) {
plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
}

byte[] plaintext = new byte[plaintextLength];
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec);
byte[] encrypted = cipher.doFinal(plaintext);
System.out.println(Encodes.encodeBase64(encrypted));

cipher.init(Cipher.DECRYPT_MODE, secretKey,ivspec);
System.out.println(new String(cipher.doFinal(encrypted)));

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from Crypto.Cipher import AES
import os
from itsdangerous import base64_encode, base64_decode

BS = AES.block_size
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s: s[0:-ord(s[-1])]

# key = os.urandom(16) # the length can be (16, 24, 32)
key = b'1234567890123456'
text = 'abcd'
IV = b'1234567890123456'
cipher = AES.new(key, AES.MODE_CBC, IV=IV)

data = base64_encode(cipher.encrypt(pad(text)))

print data
#
cipher = AES.new(key, AES.MODE_CBC, IV=IV)
print cipher.decrypt(base64_decode(data))

用 FFMPEG 合并 MP4 视频

ffmpeg + ts

重磅推出终极解决方案。这个的思路是先将 mp4 转化为同样编码形式的 ts 流,因为 ts流是可以 concate 的,先把 mp4 封装成 ts ,然后 concate ts 流, 最后再把 ts 流转化为 mp4。

1
2
3
ffmpeg -i 1.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 1.ts
ffmpeg -i 2.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 2.ts
ffmpeg -i "concat:1.ts|2.ts" -acodec copy -vcodec copy -absf aac_adtstoasc output.mp4

nginx-lua-redis

Ubuntu下安装

lua

1
2
3
apt-get install lua5.1
apt-get install liblua5.1-dev
apt-get install liblua5.1-socket2

如果liblua5.1-socket2安装不上,需要添加源

1
deb http://us.archive.ubuntu.com/ubuntu precise main universe

然后执行

1
sudo apt-get update

扩展

lua-redis-parser

1
2
3
4
5
git clone https://github.com/agentzh/lua-redis-parser.git
export LUA_INCLUDE_DIR=/usr/include/lua5.1
cd lua-redis-parser
make CC=gcc
make install CC=gcc

json

1
2
3
wget http://files.luaforge.net/releases/json/json/0.9.50/json4lua-0.9.50.zip
unzip json4lua-0.9.50.zip
cp json4lua-0.9.50/json/json.lua /usr/share/lua/5.1/

redis-lua

1
2
git clone https://github.com/nrk/redis-lua.git
cp redis-lua/src/redis.lua /usr/share/lua/5.1/

nginx

module

1
2
3
4
5
6
git clone https://github.com/simpl/ngx_devel_kit.git
git clone https://github.com/chaoslawful/lua-nginx-module.git
git clone https://github.com/agentzh/redis2-nginx-module.git
git clone https://github.com/agentzh/set-misc-nginx-module.git
git clone https://github.com/agentzh/echo-nginx-module.git
git clone https://github.com/catap/ngx_http_upstream_keepalive.git

编译依赖

1
2
apt-get install libpcre3 libpcre3-dev libltdl-dev libssl-dev libjpeg62 libjpeg62-dev libpng12-0 libpng12-dev libxml2-dev libcurl4-openssl-dev libmcrypt-dev autoconf libxslt1-dev  libgeoip-dev libperl-dev -y
apt-get install libgd2-noxpm-dev -y

下载

1
2
3
wget http://nginx.org/download/nginx-1.8.0.tar.gz
tar zxvf nginx-1.8.0.tar.gz
cd nginx-1.8.0

配置

1
2
3
4
5
6
7
8
9
10
11
12
./configure --prefix=/usr/local/nginx --with-debug --with-http_addition_module \
--with-http_dav_module --with-http_flv_module --with-http_geoip_module \
--with-http_gzip_static_module --with-http_image_filter_module --with-http_perl_module \
--with-http_random_index_module --with-http_realip_module --with-http_secure_link_module \
--with-http_stub_status_module --with-http_ssl_module --with-http_sub_module \
--with-http_xslt_module --with-ipv6 --with-sha1=/usr/include/openssl \
--with-md5=/usr/include/openssl --with-mail --with-mail_ssl_module \
--add-module=../ngx_devel_kit \
--add-module=../echo-nginx-module \
--add-module=../lua-nginx-module \
--add-module=../redis2-nginx-module \
--add-module=../set-misc-nginx-module

编译安装

1
2
make
make install

nginx被安装在/usr/local/nginx 下面,默认环境变量里面没有,需要增加连接

1
ln -s /usr/local/nginx/sbin/nginx /usr/sbin/nginx

配置

nginx.conf

注释掉 server块,增加引用

1
include /usr/local/etc/nginx/conf.d/*.conf;

demo.conf

1
2
3
4
5
6
7
8
9
10
11
lua_package_path "/usr/share/lua/5.1/?.lua;;";
server {
listen 80;
server_name demo.local;
root html;

location /demo {
default_type 'text/html';
access_by_lua_file "/opt/demo.lua";
}
}

demo.lua

1
ngx.say("hello,world")