谁说鱼和熊掌不能兼得?傲腾16G组raid0装win10系统,让你的系统极致的流畅

最近逛闲鱼发现傲腾M10 16G价格居然单条不到10块了,也难怪,这么小的容量能做什么呢?给机械硬盘加速?现在固态硬盘这么便宜了谁还愿意这么做?做系统盘?容量太小,连个win10都装不下. 并且一般主板上M.2槽的数量很有限,对于这些朋友来说这么小的容量都嫌占位置

但这么个鸡肋的东西却有着4K随机读写爆表的性能,

傲腾m10跑分

img

三个傲腾m10组成的raid0跑分

img

4k随机速度没有变化,顺序读写速度 x N,这不是重点,重点是空间大了,可以装的下正常的win10系统了!

下载支持raid的驱动

以我的这台华硕z390i为例,去官网支持界面下载驱动Intel Rapid Storage Technology Driver software V17.9.4.1017 For Windows 10 64-bit

装系统

(未完)

PCIe拆分卡DIY

我们相当大一部分用户的使用的是核显,并没有使用独立显卡,这样主板上的直连CPU的PCIe x16槽处于空闲状态,而其他所有的硬盘网卡只能通过南桥x4一个通道却处于空闲的状态,就像走小路堵的要死,而旁边的高速公路却关门不让使用,真是巨大的浪费,即使我们把平时的pcie扩展卡(如网卡)插在x16槽上,因为这些设备根本用不了16个通道,所以也是很大的浪费,然后就有了如何拆分x16通道问题,目前的解决方案一般有三种:

1) 主板BIOS设备支持PCIe拆分

其实PCIe是否能拆分是由cpu和主板芯片组共同决定的,只是有的主板厂商没有在bios中放开此功能,对于bios中放开了此功能的主板来说, 只要设置下就可以了,一般的家用主板有x8+x4+x4, x4+x4+x4+x4,x8+x8几种情况(Interl 11代以后还会有更多的通道)

2) 主板BIOS设备不支持PCIe拆分

对于此种情况,可以短接CPU触点的方式强制拆分,参照此文章intel部分桌面级CPU的pcie通道拆分另类低成本实现

3) 使用PLX拆分卡

此种拆分卡使用了plx芯片,性能上会有损耗,比不上cpu直通,主要是价格超贵,算了吧

对于1,2种情况,我们配置好拆分以后,需要一个pcie拆分卡,这也是我们今天文章的主角,

首先还是在闲鱼上看到有现成的pcb裸板,价格还挺便宜,还是镀金的,虽然嘉立创可以免费打板,但我不会画,哈哈

img

按卖家提供的bom清单采购

电容电阻
img
时钟芯片
img
pcie x8插槽
img
焊接
img

初步完成
img

增加12v转3.3v模块(中兴ZXDN10),增加此模块不影响功能,只是提高负载能力, 原板子上没有留位,自己刮铜箔吧
img
调节下最右边对地电阻380欧时输出3.3v左右
img

插上三块硬盘测试
img

阻断原3.3v接口,以防出现意外电压对主板产生影响或损坏
img

顺便晒下自己焊的另一个板子,当然自己只是纯焊接
img

两个openwrt路由器下异地组建wireguard内网

已有以下A,B两个openwrt, 期望组建10.0.0.0/24网段内网:

OpenWrt路由器A IP:192.168.0.1, 10.0.0.1
OpenWrt路由器B IP:192.168.6.1, 10.0.0.2

首先,在两个openwrt路由器上上安装wireguard, 以下以A为例(B和A一样,只是把对应信息改为B的,只有个别不同)

如果没有安装wireguard要先安装wireguard, 系统 -> 软件包,过滤器输入wireguard,选择luci-app-wireguard,点击对应安装, 成功后重启(一定要重启,否则下面接口选择协议的时候会找不到WireGuard VPN协议)

防火墙设置

网络 -> 防火墙 , 添加,名称wg,下面的入站数据,出站数据,转发都选择允许, 下面允许转发到目标区域, 以及允许来自源区域的转发都选择lan.
然后切换到自定义规则页,增加以下规则:
iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o br-lan -j MASQUERADE
保存

协议接口及对端设置

接口 -> 添加新接口, 名称wg0, 协议选择WireGuard VPN, 点击创建接口,进入wg0配置界面,
img
点击生成新的密钥对生成私钥和公钥(为方便后面配置可先记到记事本中),IP地址192.168.0.1/24,10.0.0.1/24(如果是B路由器则填写192.168.6.1/24,10.0.0.2/24). 然后切换到防火墙tab页,
img
创建/分配防火墙区域选择wg,再切换到对端页添加对端,
img

设置静态路由

网络 -> 路由, 添加,
img
接口选择wg0, 路由类型unicast, 目标为B路由器网段192.168.6.0/24, 网关为A路由器WireGuardm网段地址10.0.0.1

如无意外,此时已经组网成功,状态 -> WireGuard查看

img

在pve lxc容器中安装openwrt

相比虚拟机中运行openwrt,在lxc容器中运行性能损耗极小,接近物理机,以我的8600T CPU测试为例,虚拟机中运行openwrt时cpu占用动不动就是20-30%, 而lxc中运行openwrt, 绝大多数时间都是1%以下, 效率如此之高,那还等什么,出发!

第一步:

下载一个还有rootfs.tar.gz字样的openwrt镜像,如:openwrt-23.05.1-x86-64-rootfs.tar.gz
上传到pve主机中/var/lib/vz/template/cache(这是pve lxc镜像默认目录)目录下,

第二步:

执行以下操作(id请更换为一个不存在的id)

1
2
3
4
5
6
7
8
9
pct create 222 local:vztmpl/openwrt-23.05.1-x86-64-rootfs.tar.gz \
--rootfs local-lvm:1 \
--ostype unmanaged \
--hostname openwrt-LXC \
--arch amd64 \
--cores 6 \
--memory 1024 \
--swap 0 \
-net0 bridge=vmbr0,name=eth0 -net1 bridge=vmbr1,name=eth1

第三步:

增加勾子脚本

1
2
3
4
5
6
7
8
mkdir /var/lib/vz/snippets
cp /usr/share/pve-docs/examples/guest-example-hookscript.pl /var/lib/vz/snippets/hookscript.pl
vim /var/lib/vz/snippets/hookscript.pl

//post-start处增加
system("lxc-device add -n $vmid /dev/ppp");
system("lxc-device add -n $vmid /dev/net/tun");
print "$vmid started successfully.\n";

第四步:

修改对应容器配置文件
vim /etc/pve/lxc/222.conf,增加以下配置

1
2
3
4
5
6
7
8
9
10
11
lxc.include: /usr/share/lxc/config/openwrt.common.conf
lxc.cgroup.devices.allow: c 108:0 rwm
lxc.cgroup.devices.allow: a
lxc.cgroup2.devices.allow: a
lxc.mount.auto: proc:mixed sys:ro cgroup:mixed
lxc.mount.entry: /dev/net/tun dev/net/tun none rw,bind,create=file 0 0
lxc.mount.entry: /dev/ppp dev/ppp none rw,bind,optional,create=file 0 0
lxc.net.1.type: veth
lxc.net.1.link: vmbr1
lxc.net.1.flags: up
lxc.net.1.name: eth1

第五步:

在pve控制台中启动对应的容器,进入容器对应控制台,修改网络参数

1
vim /etc/config/network

修改对应的ip地址,

1
2
3
4
5
6
7
config interface 'lan'
option device 'br-lan'
option proto 'static'
option ipaddr '192.168.1.1'
option netmask '255.255.255.0'
option ip6assign '60'

第六步:

用电脑网络连接软路由,修改以太网ip,和以前ip地址处于一个网段内,不要重复,以便在电脑上能访问后台地址

第七步:

进入到192.168.1.1, 用户名root,密码空登录,然后更改密码,再点击网络 接口 wan编辑常规设置,修改协议为PPPoE,保存,填写你的上网帐号及密码,保存并应用,
如没有意外,等待一会就可以看到有公网ip生成了

另外,为了能在接入此openwrt路由网络的设备中能访问pve,需要把pve的网关设置为192.168.1.1

pve显卡直通

在pve上实现显卡直通

第一步:

更新grub

1
2
3
vi /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on pcie_acs_override=downstream,multifunction iommu=pt video=efifb:off,vesafb:off pci=assign-busses"

生效

1
update-grub

第二步

modprobe设置
修改/etc/modules, 增加内容

1
2
3
4
vfio 
vfio_iommu_type1
vfio_pci
vfio_virqfd

第四步

屏蔽驱动
vi /etc/modprobe.d/pve-blacklist.conf

1
2
3
4
5
6
7
8
9
10

blacklist nvidiafb
blacklist snd_soc_skl
blacklist snd_sof_pci
blacklist snd_hda_intel
blacklist snd_hda_codec_hdmi
blacklist snd_hda_codec
blacklist snd_hda_core
blacklist i915
blacklist snd_sof_pci_intel_cnl

配置生效

1
update-initramfs -u -k all

第四步

根据lspci -n -s 00:02查得显卡id,我的核显是00:02.0 0300: 8086:3e92',将查得的id8086:3e92配置写入/etc/modprobe.d/vfio.conf`,如下:

1
echo "options vfio-pci ids=8086:3e92" >> /etc/modprobe.d/vfio.conf

kvm配置

1
echo "options kvm ignore_msrs=1" >> /etc/modprobe.d/kvm.conf

信任设备允许不安全中断

1
'echo "options vfio_iommu_type1 allow_unsafe_interrupts=1" > /etc/modprobe.d/iommu_unsafe_interrupts.conf'

重启

1
reboot

查看是否生效

1
lsmod | grep vfio

使用nodejs和阿里云api实现动态域名解析

利用家里的宽带和树莓派建了个nas,也跟电信要了个公网ip,绑了个域名,但头痛的是电信的ip老是变化,重启路由会变化,就算不重启也时不时会变化,每次发现无法访问就得重新查下新ip重新解析下域名,也是挺麻烦的,后来发现阿里云有操作域名的api,就干脆使用api写个动态域名解析算了

原理:

1,获取当前所在网络环境的公网ip
2,调用阿里云域名解析的api,将ip绑定到域名

第一步:

因为我的树莓派是通过路由桥接在网络中的,无法直接获取当前的公网ip,可以使用外网接口还获取,我这里使用的是搜狐的http://pv.sohu.com/cityjson,请求返回格式为:

1
var returnCitySN = {"cip": "1.1.1.1", "cid": "110000", "cname": "北京市"};

其中的cip就是当前网络所使用的公网ip,我们可使用正则来获取

1
2
3
4
5
6
7
8
/**
* 获取当前公网ip
* @returns {String} ip
*/
async function getPublicIP() {
var url = 'http://pv.sohu.com/cityjson';
return (await axios.get(url)).data.match(/(?:\d+\.){3}\d+/)[0];
}

第二步

要调用阿里云的api,首先要创建AccessKey(该AccessKey是阿里云API的密钥,要妥善保管),创建后会生成一个AccessKey ID以及对应的Access Key Secret

阿里云提供了操作api的npm包@alicloud/pop-core,我们可以能过它来更方便的操作api

1
2
3
4
5
6
7
const Core = require('@alicloud/pop-core');
var client = new Core({
accessKeyId: '你自己的accessKeyId',
accessKeySecret: '你自己的accessKeySecret',
endpoint: 'https://alidns.aliyuncs.com',
apiVersion: '2015-01-09'
});

查得修改解析记录的api为UpdateDomainRecord, 文档地址如下
https://help.aliyun.com/document_detail/29774.html?spm=a2c4g.11186623.6.647.29d25eb4T4xhWt

需要传递:

1,RecordId(解析记录的ID)
2,RR(主机记录)
3,Type(记录类型)
4,Value(记录值)
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

/**
* 根据RecordId更新记录值
* @param {*} RecordId 记录id
* @param {*} RR 主机记录
* @param {*} value 记录值
* @param {*} type 记录类型
* @returns {Promise}
*/
async function UpdateDomainRecord(RecordId, RR, value, type) {

var params = {
"RecordId": RecordId,//"3359310782893056",
"RR": RR,//"www",
"Type": type, //"A",
"Value": value, //"bonjs.com"
}

var requestOption = {
method: 'POST'
};

return new Promise(function(resolve, reject) {

client.request('UpdateDomainRecord', params, requestOption).then((result) => {
resolve(result);
}, (ex) => {
console.log(ex.message);
})
})

}

其中RecordId我们需要通过另一个解析列表的api(DescribeDomainRecords)还获取

DescribeDomainRecords:

1,DomainName(域名)
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

/**
* 根据域名获取解析记录
* @param {String} domain
* @returns {Promise} 解析记录列表
*/
async function DescribeDomainRecords(domain) {

var params = {
"DomainName": domain
}

var requestOption = {
method: 'POST'
};

return new Promise(function(resolve, reject) {

client.request('DescribeDomainRecords', params, requestOption).then((result) => {
resolve(result.DomainRecords.Record)
}, (ex) => {
console.log(ex);
})
})

}

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
async function main() {

// 获取当前网络的公网ip
var ip = await getPublicIP();
var domain = 'bonjs.com';
console.log('ip: ' + ip)
console.log('domain: ' + domain)

// 获取指定域名下的解析记录列表
var recordsList = await DescribeDomainRecords('bonjs.com');

// 从列表中获取指定的RecordId
var RecordId = recordsList.find(function(it) {
return it.RR == '@';
}).RecordId;

var result = await UpdateDomainRecord(RecordId, '@', ip, 'A');

console.log(result);
}

main();

我们可以在该文件顶端加上#!/usr/bin/env node做为linux脚本,然后设置下linux定时任务,每隔一定时间执行
编辑/etc/crontab文件,增加:

1
*/30 * * * *   root    /home/pi/workspace/ddns/index.js

表示每隔30分钟执行一次

require-alias-node

require-alias-node Build Status

A tool which could set an alias for the module’s path which is required in the require of nodejs or import of ES6

It’s the alias of require-import-alias

Install

1
npm install require-alias-node

Sometimes, we may use a module with a long path to be required, and the path may contain many level, it’s not very intuitive !

1
2
const component = require('../../../src/component');
const moduleA = require('../../../src/component/render/moduleA');

Now we can use this tool require-alias-node to set alias for the moduleA, is looks like:

1
2
3
4
5
6
7
const requireAliasNode = require('require-alias-node');
requireAliasNode.setAlias({
'component': '../../../src/component',
'moduleA': '../../../src/component/render/moduleA'
});
const moduleA = require('moduleA');
const moduleB = require('component/main/moduleB')'

and you can also use it with import in ES2015(set an alias is a module, and use the alias in anthor module)

setAlias

1
2
3
4
5
import requireAliasNode from 'require-alias-node'
requireAliasNode.setAlias({
'component': '../../../src/component',
'moduleA': '../../../src/component/render/moduleA'
});

using it

1
2
3
import component from 'component'
import moduleA from 'moduleA'
import moduleB from 'component/render/moduleB'

It’s so graceful !

caller-dir

caller-dir Build Status

A tool which could get the dirpath of a function method’s caller

1
npm install caller-dir

Usually, if you want to get the current path, you can use __dirname in Nodejs, if you want to get the caller’s dirpath, you can call the function with a parameter which is __dirname, but it’s not graceful.

Now you can use this little tool caller-dir to get the caller’s dirpath gracefully.

eg.
file A /src/module/a.js

1
2
3
4
5
6
const getCallerDir = require('caller-dir');

function A() {
console.log(getCallerDir());
}
module.exports = A;

file B /src/b.js

1
2
var A = require('./module/a');
A();

output: /src

stringify-parse

stringify-parse Build Status

A tool like JSON.stringify and JSON.parse but could convert the type Function and RegExp in the js object.

Install

1
npm install stringify-parse

eg.

1
2
3
4
5
6
7
let o = {
name: 'stringify-parse',
method: function() {
console.log('this is a function');
},
reg: /\w+/
}

Now, I want to convert the object into string, if we use JSON.stringify, it will lost the property method and reg

1
{"name":"demo","reg":{}}

This is not what we want. Now we can use the tool stringify-parse

1
2
const stringifyParse = require('stringify-parse');
console.log(stringifyParse(o));

output:

1
2
3
{"name":"demo","method":function() {
console.log('this is a function');
},"reg":/\w+/

Then we can use the method stringifyParse.parse to convert the json string into object.

1
2
3
4
5
let str = `{"name":"demo","method":function() {
console.log('this is a function');
},"reg":/\w+/
`;
console.log(stringifyParse.parse(str));

raspberrypi-nas

之前曾经使用lg g3手机挂载硬盘搭建nas,但是存在着严重的问题;

1, 在家里访问还可以,在外面访问太慢,因为家里宽带没有外网独立IP, 只能使用内网穿透,但是穿透的速度的瓶颈在于使用的外网的服务器的速度,我使用的腾讯云,1m的速度实在太慢
2, 系统是手机上安装linux deploy软件搭建的centos, 很不稳定,经常访问不了,只能重启才能恢复.安卓系统本身就比较耗内存,再开一个虚拟机软件内存更是吃紧,非常的卡

正因为以上原因,用手机建nas只能玩玩,真正用的话是很不实用的,也曾入手个专业的nas系统,但是价格昂贵,功耗也很大,家用的话又太浪费,一直没有合适的方案

后来玩起了树莓派,功能很强大,考虑用它做nas的方案

树莓派本身就是一个linux的小电脑,说它小,体积小(比手机还小),功耗小(5v输入,挂个硬盘还不到5w),但麻雀虽小,五脏俱全,接口齐全(40针脚的GPIO接口,一个tf插槽,四个usb,一个网口,一个hdmi, 一个micro-usb, 一个3.5mm耳机孔)

功能很强大,一个连接软件和硬件的桥梁,但我们今天说的是使用它来做nas,原因:
1, 功能小,刚才也提到了,在挂一个硬盘的情况下功率还不到5w, 24小时全天运行再也不怕耗电了!
2, 自身树莓系统基于debian,装软件想怎么玩就怎么玩
3, 千兆以太网

但缺点也是有的,性能较低,usb2.0接口,鱼和熊掌不可兼得啊

系统的问题解决了,但是外网访问呢?靠内网穿透的我的那个腾讯云的那1m速度吗?慢的要死人,提高腾讯的带宽吗?贵的要死人,我又将方向转向了宽带公网ip.

因为现在的电信宽带运营商为了节省越来越紧张的ip资源,使用的是nat方案,使得分配到家庭光猫上的ip不再是公网ip, 而变成了内网ip,这样多个用户(比如一个小区)处在一个公网ip组的内网中.这样对多大数上网并无影响,但是从外网也不能直接通过ip访问到具体用户了

(未完待续)

regex-debugger

工作上一直想完全放弃win,投入mac和linux的阵营,但是因为有几个工具在mac和linux上没有合适的替代者而一直无法完全放弃win,其中之一就是正则的调试工具。

在win平台上正则的调试工具比较多,也比较完善,功能最强大的当数RegexBuddy,甚至可以调试查看正则的匹配过程,但是也较为庸肿,除非需要优化一个正则,大部分情况我都在用一个简洁的正则小工具.

win平台上正则相关工具比较完善,究其原因:大部分正则工具的作者只是使用开发语言本身的正则(除非自己实现正则引擎),而各语言对正则各特性的支持是不一样的,以js和C#为例,js不支持负向前瞻(ps:新版本v8引擎下已经支持),而平衡组只是C#等微软系统语言的特性,其他语言没有支持,盘点各门语言,以C#对正则的支持最为全面,因此使用C#来开发正则工具,在不自己实现正则引擎的情况下使用C#语言原生对正则的支持就可以实现较为完善的工具,但是,因为C#依赖.net framework,无法实现跨平台.

mac与linux上正则工具这么少,为何不能自己做个呢?自己动手,丰衣足食,并且平衡组大多也用不到,对于大多数调试足够了,nodejs新版本也对正则提供了更多的支持,作为js开发者,如果能用js来开发mac平台下的正则工具实为万幸,终于,我遇到了electron, nodejs + v8引擎,使用html就可以开发界面,支持mac, linux, win,还有啥说的

终于1.0问世了,界面仿的win下的一个正则工具的界面,习惯了这个界面,简洁好用
支持match, replace(开发中), split(开发中)
支持单行模式(开发中)
支持多行模式
近期开放下载

新版chrome正则支持负向前瞻

js的正则一直不支持负向前瞻而被我们所诟病,很多能通过负向前瞻就可以简单实现的功能因为js不支持负向前瞻只能过另类的方式来实现,今日偶然发现在chrome中可以使用负向前瞻

要知道在以前的chrome版本中,此项功能是作为实验性功能出现在chrome中的,默认是关闭状态,需要手动打开才行,而现在默认就支持

但是在nodejs(8.9.4)中仍然没有支持

相信因为google的引领,新版本中的nodejs也将很快支持负向前瞻,一旦nodejs支持此项特性,相比浏览器端,nodejs因为运行在服务器端,将会很快在我们的系统中被应用,无需考虑兼容问题
而浏览端因为涉及到旧版本的兼容问题,目前还不能在真正系统中使用


续:经测试:现在官网最新LTS版本8.11.4已经支持此项特性

git提交时对提交信息进行规范校验

我们的开发属于敏捷式开发,并且所有的开发任务都是通过jira来管理的,我们也有自己的一套git提交信息规范,规范为:

1
jira号 功能任务简称

如果是在此功能上的测试bug修改,则格式为:

1
jira号 功能任务简称-bug描述

如:

1
PTENTOW-5619 热图优化
1
PTENTOW-5619 热图优化-修复xxBug

但规范只是口头约定,总有些异类不按套路出牌,胡乱的提交信息,给以后的查阅信息带来的不小的隐患,有没有办法在提交的时候去校验下提交是否规范,如果不规范就阻止提交呢?答案是肯定的,我们可以使用git hooks.

我们先看git-scm上对git hooks的描述

1
和其它版本控制系统一样,Git能在特定的重要动作发生时触发自定义脚本。 有两组这样的钩子:客户端的和服务器端的。 客户端钩子由诸如提交和合并这样的操作所调用,而服务器端钩子作用于诸如接收被推送的提交这样的联网操作。 你可以随心所欲地运用这些钩子。

git hooks可以让用户编写在git特定动作下执行的脚本,基中有一个commit-msg,以用来在提交通过前验证项目状态或提交信息,我们可以使用该hooks.

git hooks所在路径为.git/hooks下,有几个以.sample结尾的例子,默认是不生效的,如果要生效,将.sample后缀去掉,我们将commit-msg.sample改名为commit-msg,将内容清空,编写如果下代码:

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
#!/usr/bin/env node

'use strict';

var fs = require('fs');

var exec = require('child_process').exec;
var spawn = require('child_process').spawn;

var gitEmail = spawn('git', ['config', 'user.email']);
gitEmail.stdout.on('data', function (data) {
var email = firstLineFromBuffer(data);

var commitMsgFile = process.argv[2];

fs.readFile(
commitMsgFile, function (err, buffer) {
var message = firstLineFromBuffer(buffer);

if (!validateMessage(message)) {

var address = function() {
if(email.match(/\b(?:observable|alex)\b/)) {
return '兄dei';
} else {
return '妹砸';
}
}();

var errorMsg = [
'******************************* Error *******************************',
address + ',貌似你不按套路出牌,再看看是不是哪里写的不合要求.',
'示例:PTENTOW-5619 热图优化-修复xxBug',
].join('\n ');

error(errorMsg);
error(' 你提交的是: \\c', 7);
error(message, 3)

process.exit(1);
} else {
process.exit(0);
}
});

});


var error = function (msg, color) {
var color = color === undefined ? 1 : color;
spawn('echo', ['-e', '\\033[3' + color + 'm' + msg + '\\033[0m'], {
stdio : 'inherit'
});
};

var validateMessage = function () {

var reg = /^PT[A-Z]{5}-\d{1,5}\s+.+/;
return function(message) {
return reg.test(message);
};
}();

var firstLineFromBuffer = function (buffer) {
return buffer.toString().match(/.*/)[0];
};


然后我们随便编辑我们的工作中的文件,然后提交,提交信息为一个不规范的信息,如:随便,提交后有以下提示:

ok,成功!可以看到我们的不规范的提交已被阻止,并且给出了友好的提示.

但是因为该文件处于.git文件夹中,不受git版本控制,需要开发者手动设置,比较费事,我们可以将该文件放进工作区中,然后设置npm勾子,使npm install的时候
设置该文件到.git/hooks/commit-msg的软链接,这样就可以实现自动化.

1
2
3
"scripts": {
"postinstall": "node git-commit-hooks"
},

git-commit-hooks.js文件为设置软链接的脚本

1
2
3
4
5
var exec = require('child_process').exec

exec('rm -f ./.git/hooks/commit-msg')
exec('ln -s ../../commit-msg.js ./.git/hooks/commit-msg')

github: https://github.com/bonjs/git-commit-valid

打完收工

使用lg g3组建nas

几年前入了美版lg g3手机,被它的屏幕给惊到了,不得不说lg的屏幕就是牛叉,5.5寸的2k分辨率没有丝毫颗粒感,白平衡做的非常好,没有三星a屏的那种过分的浓妆艳抹的不真实感,但是高通801的处理器及3g内存运行起来相比比较吃力,再加入尿崩的续航及不支持qc快充种种缺点,直到后来入了nexus6之后终于使它退居二线,但退居二线后总觉得是不是让他发挥点余热,想想还有个闲置硬盘,于是蒙生了一个使用g3搭建nas的想法.

虽然android也是基于linux,但是改动改的连他爸linux都不认识他了,还是想办法装个发行版的linux吧,这里用到一个软件linux depoy,相当于虚拟机软件,

挂载硬盘(待续)

内网穿透(待续)

给小台灯改microUSB口并增加锂电池

近日浏览数码之家时,看到有个小台灯的帖子,样子不错,开关为感应式开关,三档亮度,领券后13.9包邮,入手玩玩还是不错的

img

果断拆机,电池为一颗18650锂电池,很轻,容量估计不到2000mah

img

电路板,一颗打磨型号的芯片,充电电路也很简单

img

看看说明书,电池容量为1200mah, 电路没有过放保护功能,这让人用起来怎么放心的下?

img

看这个空间能容下三颗18650, 如若改为三颗2600mah,续航能力能提升好几倍,并且增加底座稳定性

img

找了个用镍片连接的18650电池组, 空间正好,只需要底部需要削削

img

削后的底座

img

准备增加使用TP4056充电板,具有过充过放保护功能,保护电池不防损坏,microUSB口,方便使用一般的手机充电器充电

img

原来的电路板,把原来的圆充电口用电烙铁焊掉

img

撮合一下,对比

img

削了削边角,以更好的适应底座

img

先放上看看,口需要改大一点,以适合microUSB口

img

神器出山相助

img

改后的口

img

两电路板之间使用热融胶粘固定在一起

img

连接两个电路板

img

反面线路

img

焊接电池

img

装机效果

img

恩,换个角度

img

连接灯线以及感应天线

img

效果图

img

打完收工

德生icr-110增加18650锂电池

一年之前自己入了部德生icr-110,支持AM及FM, 音质不错,支持插卡及录音,还有一个让我选择它的很重要的原因是,使用18650锂电池供电.这货体积挺大,但是却很轻,近日拆机发现,内部有很大的空间,考虑这货比pl880耗电,自己还有一堆18650电池无用武之间,就琢磨着是不是给它再加点电池,说干就干:
1, 调研:这货内部空间大,正好容下两节18650电池座,可将18650电池座使用热融胶或双面胶固定在后壳上,然后和原机的电池并联,使用原机的充电电路充电.
2, 风险:
1, 安装时要并联的电池和原来的电池之间电压可能不一样,并联时会有一个电池之间的充放电过程,最终达到平衡,因为锂电池内阻很小,如果电压相差太大,这个充电放电流可能也会较大,因此电压不能相差太大.
2, 安装后使用阶段,因为两节是安装在内部的,已经固定死,只有原电池仓的电池可以拆卸,平时使用如果安装原电池仓的电池时,万不可将正负极装反,否则就和固定在机内的两节电池形成短路,因为18650电池内阻很小只有几十毫欧,产生的电流很大,甚至起火!所以平时尽量也不要拆卸原机仓电池, 只使用原机带的充电口充电.

img
img

git




















box-shadow

遇到过这样一个需求:因为项目的性质,我们需要用程序抓取用户的网页,然后在一些符合条件的dom添加一层内阴影及一段浮层文字,因为要最大程度的保证用户网页的原有的功能和界面,我们不能采用添加遮罩dom的形式,而采用了添加class样式,借助beforeafter
幸好css3提供了实现内阴影的功能,就是使用box-shadow,如

1
box-shadow:inset 0 0 44px 12px #1c1fc2;

而浮层文字可以使用beforeafter伪类中的content;

对一般的dom都没什么问题,但是用在img上却发现没有效果,img根本就不支持box-shadow的inset效果,并且也不支持beforeafter伪类,上网查得beforeafter伪类是在内部的前和后插入内容,换句话说,这个dom首先得是个容器,而img本身不可存放其他元素,不是容器,所以不能使用这两个伪类。

后来经过多次调试发现,img在没有src的情况下可以设置inset方式的box-shadow,并且也可以使用beforeafter伪类,那我使用js动态的去删除src属性然后设置它的background-image,再设置box-shadowbefore,after伪类不就可以了吗?效果如下:

完整代码如下:

HTML

1
<img class="shadow" alt="" style="width:316px; height: 194px">

CSS

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
.shadow {
position: relative;
display: block;
overflow: hidden;

background:url(/image/cat.jpg) no-repeat;
background-size: 316px 194px;

box-shadow:inset 0 0 44px 12px #1c1fc2;
-webkit-box-shadow:inset 0 0 44px 12px #1c1fc2;
-moz-box-shadow:inset 0 0 44px 12px #1c1fc2;
}

.shadow:before, .shadow:after {
position:absolute;
top:0;
left:0;
width: 100%;
height: 100%;
}

.shadow:before {
content:"";
}

.shadow:after {
content: "我是浮层文字";
text-align: center;
color: white;
text-shadow: 0px 0px 2px blue;
top:50%;
line-height:0;
}
.shadow:hover:before{
background-color:rgba(0, 115, 27, 0.3);
}
.shadow:hover:after{
color:white;
}

notepadPlusPlusMacroReg

我们在开发中经常会遇到在js中拼html字符串的情况,如:

1
2
3
4
5
6
7
8
9
10
var html = '<div class="block">' +
'<ul>' +
'<li>balibali</li>' +
'<li>balibali</li>' +
'<li>balibali</li>' +
'<li>balibali</li>' +
'</ul>' +
'</div>';

$('#outerDiv').html(html);

这种方式,因为在低版本下IE浏览器中的效率问题而备受诟病,效率不如通过数组进行join,如以下写法:

1
2
3
4
5
6
7
8
9
10
11
var html = [
'<div class="block">',
'<ul>',
'<li>balibali</li>',
'<li>balibali</li>',
'<li>balibali</li>',
'<li>balibali</li>',
'</ul>',
'</div>'
].join('');
$('#outerDiv').html(html);

虽然后来随着chrome,firefox浏览器的兴起,这些浏览器对前者这种写法进行了优化,使用这种写法的效率有了大幅度的提高,甚至在一些浏览器中比后者略高,但是前者这种拼字符串的方式个人觉得实在是丑,因此个人在开发过程中都使用的第二种方式.
虽说后者相比优雅了不少,但是对大篇幅的html来,仍然相当头痛,因为我们拿到html模板以后仍然要在每行的前后加引号逗号来组成数组,对于大篇幅的html来仍然非常痛苦,难道我们就只能这么拼下去安于现状吗?
NoNo,我们可以让程序来代替我们做这个脏活. 我在本文以及后续介绍两种方式.

第一种:使用nodepad++.
第二种:编写gulp插件.

本文介绍第一种

首先,我们是要把html转化成字符串,我们打算使用单引号作为作为字符串的边界,但是这个html中原先可以存在单引号,所以,我们首先要对html中的单引号和反斜线做转义处理,即在单引号和反斜线前加反斜线,使用的正则是:

1
(?=['\\])

替换为反斜线\。
今下来,我们要做的是,在每行的开始第一个非空字符上加单引号,在第行的结尾加上单引号和一个逗号,正则如下:
每行非空字符串加单引号:

1
(?<=^)(\s*)

替换成 

1
$1'

结尾加单引号和逗号:

1
(?!^)(?=\r)

替换成

1
',

然后我们使用notepad++查找替换,使用正则表达式模式,

(未完待续)

自己实现的A*寻路算法(未完待续)

折腾了好几天,终于把这个寻路算法给实现了,为了更方便大家了解其中的过程,做了一个演示版本,加上了标识.

展示地址:http://bonjs.github.io/wayFinding 源码:https://github.com/bonjs/wayFinding.git

chrome下禁止中文输入的新方案

现实中我们经常会遇禁止输入中文的场景,常见的处理方式

1, 使用css样式ime-mode:disabled
2, 使用js在keyup事件中将输入的中文替换为空

但是第一种方式ie,firefox都可以使用,但我大谷歌居然不兼容,谷歌也有不兼容的时候..
而第二种方式怎么样呢,输入过程会有汉字选择框,并且汉字在上屏后突然消失,感觉好滑稽的样子,体验上不够友好,并且在chrome, firefox下,汉字在输入过程中上屏之前,input的value是对应按钮的字母的值.我们来看下:

1
<input id="test" />

测试,在keyup事件中打印当前input的value

1
2
3
$('#test').on('keyup', function() {
console.log(this.value);
});

切换到中文输入法下,打开f12, 在input输入,注意在输入过程中汉字上屏之前,在命令行下已经打印了字符,是我们键入的英文字母的值,此时不但触发了keyup事件,value也被填充了值,这和ie下是不同的.

这也会带来一些问题,比如对一些即时输入即将结果显示的场景,在汉字上屏之前会将输入的英文字母发送到给后台进行查询,而这些查询是没有用的

因此,对于这种方式来禁止中文输入,也是不够好的.

下面介绍第三种方法,该方法也是我调试的时候发现的,就是利用html5的新事件input, 用户在输入过程中会触发该事件,在该事件中将当前值赋给当前input

1
2
3
$('#test').on('input', function() {
this.value = this.value;
});

经测试,在chrome中中文输入法下输入时,不会产生输入选择框,也不会输入中文,实际使用中再配合第一种方法,基本上可以实现对所有浏览器兼容.

redPacket

过年放假前做的,使用的是jquery.draggabilly, 当时有个多图上传的需求,可能要做成拖动排序,先写个例子上上手,因为当时大家都抢五福抢的很带劲,就以五福为主题做了个例子,呵呵

展示地址:https://bonjs.github.io/drag
源码:https://github.com/bonjs/drag.git

我遇到的正则表达式的小坑(split篇)

经过几天的努力, bonTemplate v1.0.5终于发布了, 改进了if标签中的表达式逻辑, 心中一块石头算是落了地, 这几天, if标签的边界问题方案是修了改, 改了修, 哥的ifext分枝不带这么玩的.. 哎呀废话不多说, 我还记得我是来干嘛的, 来说说这次遇到的怪问题.

先看看问题正则

1
<each\s+([\w.]+)\=['"]?(\w+)['"]?(?:\s+([\w.]+)\=['"]?(\w+)['"]?)*\s*>

我去, 这是个毛啊

好吧, 咱就抓住主要矛盾,忽略次要矛盾, 来个简单的小场景:

取出下面字符串所有的字符串, 子字符串只能含有字母

1
abcdetestfgh,hello,bonTemplate

ok, 小case, 直接按逗号split就行了, 正则都不用写了
abcdetestfgh
hello
bonTemplate

哥正在沾沾自喜, 产品又来了说需求要改, 说子字符串不能包含逗号,也不能包含test这个字符串.
看来只能写个正则了, 这也不是什么问题.

1
(?:test|,)

哥想这下没问题了, 可不一样会, 产品又来了, 说子字符也不能包含abc, 我去!

那就改改吧, 这还能难的倒哥吗?
1
(test|,|abc)

..等等, 不对啊, 我是按test逗号abc这三个split,怎么会出现在结果中!?
上一个结果还是正常的, 这次就加了一个abc,怎么就不正常了呢?
哦.. 还有个差别是一个是捕获性分组, 一个是非捕获性分组, 难道还和这个有关?
于是改了下,改成非捕获性分组

果然啊, 捕获性分组还是非捕获性分组居然影响到了split的结果.. ,要知道在其他用法中, 如match, exec, test, 在这些用法中, 分组是捕获性还是非捕获性是不会影响到结果的,只是用来取值用的, 但在split中却影响匹配结果!

我遇到的正则表达式的小坑(html5 pattern篇)

html5 pattern

最近帮一个朋友写一个正则表达式, 要求匹配非5-8位数字的字符串,也就是说除了5-8位数字以外的字符串都可以. 我给的的正则是

1
^(?!\d{5,8}$)

但他说不好使, 看到他的代码我才发现他是在input中的pattern这个属性中来使用的正则

1
<input type="text" name="country_code" pattern="^(?!\d{5,8}$)"

测试了下, 确实不好使, 对pattern这种方式也不熟悉, 于是上网查到w3school上pattern的例子

1
2
Country code: <input type="text" name="country_code" pattern="[A-z]{3}"
title="Three letter country code" />

这个的需求是只能允许输入大小写字母,但是这个正则却是只要输入的字符串中包括大小写就可以, 有问题啊,但是运行的效果却是能允许输入大小写字母, 难道使用pattern这种方式会自动在首尾加上^$? 于是我上面写的那个正则改成结尾带$的(头部已有^), 但不能直接加, 因为直接加意思就变了, 修改了下,变成

1
^(?!\d{5,8}$).*$

为了验证pattern是不是会在首尾加^$, 我把首尾的^$去掉, 再测试

1
2
Country code: <input type="text" name="country_code" pattern="(?!\d{5,8}$).*"
title="Three letter country code" />

一切ok.

结论: 在html5的pattern属性中使用正则时,会在正则的前后自动加上^$, 如果你的正则加上^$意义有改变的话, 要修改下正则,使之前后加上^$也能保持原来的意思, 这样才能在pattern属性中使用你的正则.

对https跨域登录的处理

最近公司网站使用ssl加密,但非全程加密,只对登录注册界面加密,要求在http界面操作时如果没有登录就弹出登录界面进行登录。因为http和https属于不同的协议,因此涉及到跨域问题,开始时考虑jq jsonp,但jsonp只能get提交,后来改用嵌套多层iframe的方式,具体方式如下:

三个界面:

1
2
3
4
协议	页面
http index.html
https loginForm.html
http loginProxy.html

思路:

主界面index.html,点击登录弹出层嵌套https协议的loginForm.html,loginForm.html存放提交表单,在loginForm.html点击提交按钮提交表单,同时在loginForm.html中创建一个子iframe,src为和index.html同域的loginProxy.html,在loginProxy.html中调用parent.parent就可以调用到index.html中定义的函数从而对index.html操作;

因为本地测试时没有ssl环境,改为不同域名的方式来实现跨域,编码,测试,上stg环境,一切ok。

但是上正式环境的时候却出了问题,报Mixed Content的错误, 无法加载iframe内网页,原来https下无法加载http的界面,涉及https的跨域比其他类型(不同服务器,不同域名,不同端口)更严格.

后来想到了一个解决方案,其实很简单,使用http协议的loginProxy.html,在loginForm.html里使用ajaxSubmit提交表单,成功之后使用

1
window.location.href = 'http://localhost/loginProxy.html';

重定向到http协议的loginProxy.html,这样iframe内外就处在同一个域,从而绕过了跨域问题。

formEncrypt

jquery.form.js中是对原生form表单验证提交进行了封装的一个插件,使用起来非常的方便,但是我们经常有对密码域加密码的需求,而jquery.form.js并没实现这一功能,下面我们来对它进行扩展来实现这一功能.

启动

1
2
git clone https://github.com/bonjs/formEncrypt.git
node app

源码

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
/**
* 对使用ajaxSubmit提交的表单中添加encrypt=true的表单域进行加密, 重写ajaxSubmit方法
* @author Alex
* @date 二○一六年九月二十八日
* @version 1.0
* 使用方式: 对要加密的表单域标签中添加encrypt=true,通过ajaxSubmit提交表单时自动会对该域加密
*/
$.fn.ajaxSubmit = function() {
var f = $.fn.ajaxSubmit;

// 公钥
var FD_f1342fFDFdsaf = 'MIGfMA0GCSqGSIb3DQfdsafdsafdsDCBiQKBgQDkAh06uqqrA8qIsyd98/E1p4oL0GAzUifdsafdsaOZpCwAdrh+I77Ws14u2UJWz4cBNnZBnS5hX/kWeUizGkPbW2AfdsafdsakuFfdsafdsanTJUQIDAQAB';
var encrypt = new JSEncrypt();
encrypt.setPublicKey(FD_f1342fFDFdsaf);

return function(opt) {
var form = this;
var beforeSubmit = opt.beforeSubmit;
opt.beforeSubmit = function(fields) {
var result = beforeSubmit && beforeSubmit.apply(this, arguments);

fields.forEach(function(fieldObj, i) {
if(!!form[0][fieldObj.name].getAttribute('encrypt') == true) {
fieldObj.value = encrypt.encrypt(fieldObj.value);
}
});

return result;
};
return f.apply(this, arguments);
};
}();

使用方式

表单(在要加密码的表单域中添加encrypt=true)

1
2
3
4
5
<form id=f>
<input type="text" name="username" /><br>
<input type="password" name="password" encrypt=true /><br>
<button type="button">提交</button>
</form>

调用

1
2
3
4
5
6
7
$('button').click(function() {
$('#f').ajaxSubmit({
success: function() {
console.log('ok');
}
})
})

通过ajaxSubmit提交表单时,添加了encrypt=true的字段发送的数据会自动进行加密

傅立叶变换演示

设置正弦波数量

设置正弦波数量

高效,轻量,易用的模板引擎~~

bonTemplate Build Status

  • 高效(100条数据执行10000次一共耗时50多ms,我本机上的测试结果,视电脑配置)
  • 轻量(压缩前也才3K多)
  • 支持循环
  • 支持条件判断
  • 支持嵌套
  • 支持表达式
  • 支持自定义格式化函数
  • 使用简单,易懂

https://github.com/bonjs/bonTemplate

安装和启动

1
2
3
git clone https://github.com/bonjs/bonTemplate.git
npm install
node app

或者

1
2
npm install bonTemplate --save-dev
node app

或者

1
2
npm install bon-template --save-dev
node app

访问http://127.0.0.1:3000

模板

1
2
3
4
5
<script id=tpl type="html">
<div>{name}</div>
<div>{sex}</div>
<div>{email}</div>
</script>

数据

1
2
3
4
5
var data = {
name : 'bonTemplate',
sex : 'm',
email : 'ske@163.com'
}

调用方式

1
2
3
var html = document.getElementById('tpl').innerHTML
var str = bon.render(html, data);
a.innerHTML = str;

可嵌套的循环标签

1
2
3
4
5
6
7
8
<each userList=u>
<div>{u.name}</div>
<div>{u.sex}</div>
<div>{u.email}</div>
<each u.hobbys=h>
<label>{h}</label>
</each>
</each>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
userList: [
{
name : 'bonTemplate',
sex : 'm',
email : 'ske@163.com',
hobbys: [
'吃', '喝', '玩', '乐'
]
}, {
name : 'bonTemplate',
sex : 'm',
email : 'ske@163.com'
hobbys: [
'吃', '喝', '玩', '乐'
]
}
]
}

条件标签

1
2
3
4
5
6
7
8
<div>
<div>{name}</div>
<div>{sex}</div>
<div>{email}</div>
<if sex == 'm'>
爱好数码
</if>
</div>
1
2
3
4
5
{
name : 'bonTemplate',
sex : 'm',
email : 'ske@163.com'
}

表达式

1
2
3
4
5
<div>
<div>{name}</div>
<div>{sex == 'm' ? '男' : '女'}</div>
<div>{email}</div>
</div>
1
2
3
4
5
{
name : 'bonTemplate',
sex : 'm',
email : 'ske@163.com'
}

自定义格式化函数

1
2
3
4
5
bon.addFun({
myFun : function(v) {
return v == 'm' ? '男' : '女';
}
});
1
2
3
4
5
<div>
<div>{name}</div>
<div>{sex:myFun}</div>
<div>{email}</div>
</div>
1
2
3
4
5
{
name : 'bonTemplate',
sex : 'm',
email : 'ske@163.com'
}

全家桶

1
2
3
4
5
bon.addFun({
formateEmail: function(email) {
return 'Email: ' + email;
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<each userList=u>
<div>{u.name}</div>
<div>{u.sex == 'm' ? '男' : '女'}</div>
<div>{u.email:formateEmail}</div>
<each u.hobbys=h>
<label>{h}</label>
</each>
<if u.sex == 'm'>
爱好数码
</if>
<if u.sex == 'f'>
爱好买衣服
</if>
</each>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
userList: [
{
name : 'bonTemplate',
sex : 'm',
email : 'ske@163.com',
hobbys: [
'吃', '喝', '玩', '乐'
]
}, {
name : 'she',
sex : 'f',
email : 'fdsafs@163.com'
hobbys: [
'吃', '喝', '玩', '乐'
]
}
]
}

callbackNoHell

callback, but no hell !!!

启动

1
2
3
git clone https://github.com/bonjs/callbackNoHell.git
npm install
node app

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
var callbackNoHell = function(fns) {
var _promise = arguments.callee;

var sliceProto = Array.prototype.slice;

var params = [function(result) {
result && _promise.apply(null, [fns].concat(sliceProto.apply(arguments)));
}].concat(sliceProto.call(arguments, 1));

var fn = fns.shift();
fn && fn.apply(null, params);
};

调用方式

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
function f1(f) {
$.get('data/step1.json', {}, function(d) {
if(d.success) {
console.log('step1成功');
f(true, d.step2Id); // 下个函数请示可能依赖这次的请求结果
} else {
f(false);
}
});
}
function f2(f) {
$.get('data/step2.json', {}, function(d) {
if(d.success) {
console.log('step2成功');
f(true, d.step3Id); // 下个函数请示可能依赖这次的请求结果
} else {
f(false);
}
});
}
function f3(f) {
$.get('data/step3.json', {}, function(d) {
if(d.success) {
console.log('step3成功');
f(true, 'step3成功');
} else {
f(false);
}
});
}

callbackNoHell([f2, f1, f3]);

制作一个迷你版的Ext.js精简策略(转)

转自http://www.makaidong.com/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E9%97%AE%E7%AD%94/29171.shtml

ext 是我顶喜欢的一款js框架, 原因不仅仅只是因为它有着很专业的ui组件, 更要的是它有着很漂亮的类管理机制(尤其是ext4), 它为js程序员们定义了一整套编码规则, 使我们可以把js写得像java一样, ext 自身的代码就是用的这种编码规则, 所以大几万代码依然管理得井井有条. 如果你只是用js写些表单验证之类的小动作, 那么你肯定体会不到编码规则的重要性. js这种语言, 如果是团队开发, 制作较为复杂的富客户端应用, 如果不强制统一编码风格, 维护起来那将会是场灾难.
所以我有时自己写js玩的时候, 为了使自己的代码更漂亮, 也会用到ext. 当然, 是精简后的ext.
构建一个最简易的ext其实非常简单. 我用的是当前最新的版本: ext-4.0.7-gpl.

此文转自: http://www.makaidong.com

  1. 首先下载源码, 解压, 找到以下文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /src/core/src/ext.js
    /src/core/src/lang/number.js
    /src/core/src/lang/string.js
    /src/core/src/lang/date.js

    /src/core/src/lang/object.js
    /src/core/src/lang/array.js
    /src/core/src/lang/function.js
    /src/core/src/class/base.js
    /src/core/src/class/class.js
    /src/core/src/class/classmanager.js
    /src/core/src/class/loader.js
    /src/core/src/lang/error.js
  2. 把这些文件复制出来, 然后在一个页面中依次引用, 注意顺序不要乱, 然后用编辑器打开error.js , 在文件的最后部分注释掉一行代码:
    …….

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function poll () {
    timer = win.setinterval(notify, 1000);
    }

    // window.onerror sounds ideal but it prevents the built-in error dialog from doing
    // its (better) thing.
    //poll(); //把这一行注释掉
    })();
    //</debug>
  3. 然后, 我们可以随便写一些代码了, 如果不报错, 那么就成功了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    ext.define("ext.foo",{ 
    name: null,

    constructor: function(){
    this.name = "big-foo";
    },

    say: function(){
    alert("my name is "+ this.name);
    }
    });

    new ext.foo().say();

这些文件其实也可以按顺序一起打包压缩, 加入到自己项目中, 我上面列出的那些文件压缩后只有50几kb…嘿嘿…袖珍版ext. 因为代码没有

涉及到浏览器对象操作, 所以这个小框架甚至可以移植到 node.js 环境下使用, 只是由于ext这个对象会被自动加在顶层名称空间上, 所以在模块引用的时候会和node的习惯上有些不同, 当然这都是题外话了, 有兴趣的童鞋可以去玩玩, 有新发现记得告诉我.

我之前说的没错吧, 操作很简单的. 不过这里面仅仅只启用了ext最核心最基础的一部分功能, 也就是类管理. 从这些 js文件的名字和在源码中得摆放目录的名子中就可以看出来, 放在 lang 目录下都是些对js 基础数据类型的扩展, class 目录下的都是类管理模块. 这么看来, 其实只要了解ext组件间的依赖关系(都可以在api中查到的), 按顺序加入相应的文件, 要随意地div 自己的ext 也不是一件难事.

还有一点值得说明, 我注释掉的那一行代码, 如果不注释, 浏览器会不停地报错. 代码中的注释说这是一个用于错误收集的机制, 我看并不影响ext核心功能的使用, 所以就注释了. 我不太能理解ext为什么要一开始就注册一个定时器去定时执行一些代码, 有知道的童鞋能告诉我么?

对js中观察者模式的一点理解

在观察者模式中, 当被观察者做出某动作时, 会通知关注它的观察者, 观察者收到通知后会做出相应的动作. 以歌星和粉丝为例, 歌星具有唱歌发布唱片的职能, 当歌星发布唱片时会通知关注他的粉丝们,粉丝们收到通知以后会做出相应的反应(购买唱片或是不买, 还上网收听, 由粉丝自己决定), 在这个过程中, 歌星只具有唱歌并通知他的粉丝的的职能, 而粉丝们则只能收到消息后做出自己的反应(购买或是不买, 自己决定), 这就是所谓的单一职责原则,由于明星和粉丝之间的联系只有通知,没有其他的关联, 大大降低了两者之间的耦合, 这就是解耦

首先定义一个明星类, 并创建一个集合来存储关注他的粉丝

1
2
3
var Star = function() {                // 明星
this.fansList = []; // 明星的粉丝
}

该明星具有唱歌的职能, 唱歌时需要通知关注他的粉丝们(会调用粉丝们的action方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Star.prototype = {
constructor: Star,
song: function(song) {
this.notify(song);
},
notify:function(song) { // 明星有活动时通知粉丝

console.log('明星发布了唱片' + song);
this.fansList.forEach(function(fans, i) {
console.log('明星向粉丝发布发布唱片<<' + song + '>> 的消息');
fans.action.call(this, song);
});
}
}

创建粉丝类

1
2
3
var Fans = function() {     // 粉丝

}

粉丝具有关注哪个明星的功能(把自己加入所关注明生的粉丝列表中), 也有收到明星消息后作出反应的功能

1
2
3
4
5
6
7
8
9
10
Fans.prototype = {
constructor: Fans,
action:function(song) { // 粉丝的行动
// do something

},
follow: function(star) { // 关注哪个明星
star.fansList.push(this);
}
}

创建一个明星s

1
var s = new Star();

创建一个粉丝f1并关注明星s, 同时定义该粉丝收到消息后会做出什么样的动作

1
2
3
4
5
var f1 = new Fans();
f1.follow(s);
f1.action = function(song) { // 至于收到消息后做什么,由粉丝自己决定 
console.log('我是铁杆粉丝f1,我去购买唱片<<' + song + '>>');
}

再创建一个粉丝f2

1
2
3
4
5
var f2 = new Fans();
f2.follow(s);
f2.action = function(song) {
console.log('我是铁杆粉丝f2,但买不起唱片,只能去借别人的唱片<<' + song + '>>');
}

明星发布唱片(看来此明星是华仔)

1
s.song('一起走过的日子');

而在js中最经典的应用就是对事件的处理, 包括浏览器原生事件和自定义事件

以jq为便, 页面中有一个id为btn 的button, 通过jq为该按钮添加监听事件

1
2
3
$('#btn').on('click', function(e) {
alert('我被点击了!')
});

然后通过程序触发

1
$('#btn').trigger('click');

可能有人会有疑问, 在这里, 到底谁是观察者, 谁是被观察者呢?

因为在多数教程中, 观察者和被观察者往往是被设计的很分明的, 比如定义一个被观察者的类或对象, 具有发布消息的功能, 再定义一个观察者的类或对象, 具有接受被观察者通知并做出反应的功能, 在这里,两者的定义是非常明确的, 很好理解,但是在上面的这个jq的例子中, 到底谁是观察者, 谁又是被观察者呢?

我们先来看下观察者, 观察者, 首先要有订阅的功能, 反映在这里就是监听click事件什么时候被触发, 那在这里, 谁在监听呢, jq中的on是添加监听的意思, 所以执行on方法的对象就是观察者, 即btn按钮.

谁又是被观察者呢? 我们再来看下被观察者的功能,被观察者具有发布通知的功能,反映在这里就是触发click事件,而触发click事件的方法的执行者,又是$(‘#btn’)! 所以,在这里,观察者和被观察者都是按钮本身,按钮扮演着观察者和被观察者两个角色,监听事件的时候,按钮是观察者,触发事件的时候,按钮是被观察者,这正是事件处理中观察者模式与其他地方不一样的地方.

但是,为什么要这么做呢?为什么要让一个按钮扮演观察者和被观察者两个不同的角色呢? 因为, 我们最终关心的是谁被注册了什么事件, 以及事件被触发时做了什么动作, 而不关心监听和触发的对象是不是不同的, 所以, 在观察者和被观察者的设计上我们有了更灵活的空间, 我们可以设计将观察者和被观察者设计成不同的对象,也可以设计成一个对象, 很明显, 在这里我们没有必要将他们设计成两个不同的对象

对js闭包的一点认识

先看一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = [];
var i = 0;
for(; i < 2; i ++) {
a[i] = function() {
console.log(i);
}
}

a.forEach(function(fn, i) {
fn();
})

结果:
2
2

可能很多人对这段代码无法理解,程序的设计者设计的初衷是想给数组a放置打印下标的function, 可实际执行结果是,数组a里存放的function打印的不是对应的下标,而全都是2!?!? 无法理解? 不要急,等我一步一步的调试下,可能会对你有所帮助呢

1,下面这段代码相信都没有疑问

1
2
3
4
5
6
7
8
var i = 0;
var test = function() {
console.log(i);
}
test();

结果:
0

2,这一步

1
2
3
4
5
6
7
8
9
var i = 0;
var test = function() { // 声明的时候, 此时i=0,但是调用的时候,i已经变成了1
console.log(i);
}
i++;
test();

结果:
1

test声明的时候, 此时i=0,但是调用的时候,i已经变成了1

3,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var i = 0;

var test = function() {
console.log(i);
}
i++;

var test2 = function() {
console.log(i);
}
i++;

test();
test2();

结果:
2
2

再加一个函数test2, 同时再次i++, 函数内容同样是打印i, 结果是两次打印都是2, 因为这两个函数中的i是同一个值,并且打印时i的值不是声明时的值,而是和调用时的值保持一致,

4,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = [];
var i = 0;
a.push(function() {
console.log(i);
});
i++;
a.push(function() {
console.log(i);
});
i++;
a[0]();
a[1]();

结果:
2
2

一样的, 只是把函数存进了数组

5, 然后还是一样的, 只是放进了循环中,但现在, 已经无缝过渡到了开头的那一段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var a = [];
var i = 0;
for(; i < 2; i ++) {
a[i] = function() {
console.log(i);
}
}

a.forEach(function(fn, i) {
fn();
})

结果:
2
2

6, 怎么让程序按我们期望的方式输出呢? 一个解决办法是, 将函数嵌套在一个匿名函数里,通过匿名函数来传i值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var a = [];
var i = 0;
for(; i < 2; i ++) {
a[i] = (function(v) {
return function() {
console.log(v);
}
})(i);
}

a.forEach(function(fn, i) {
fn();
})

结果:
0
1

原因是,通过匿名函数传值后,匿名函数里的v是局部变量,是函数中非公用的活动对象,不会受外面i的影响,因此能够将我们期望的下标值打印出来

结论: 其实这就是一个闭包引起的副作用(该闭包没有外部函数,因为外部函数不是必需的(是不是可以暂称此类闭包为开包openure)), 当前函数的活动对象的活动对象其实就是全局变量对象, 因为没有外部函数, 这个数组里的所有函数共用一个活动对象, 包括a和i, 所有这里的函数共用一个i, 所以每个函数里的i值都是最后一个值2

欢迎指教和探讨!

Zabbix架构的改进方案(三)

由于此次改进涉及不少内部框架的, 如果都介绍出来篇幅太长,所以这里只介绍下对zabbix的封装的新控制器BaseController.php, 原理也很简单,就是根据传来的method参数名, 去执行对应的方法

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
class BaseController extends CController {

protected $data = [];
protected $title = '';
protected function init() {
$this->disableSIDValidation();
}

protected function checkInput() {
return true;
}

protected function checkPermissions() {
return ($this->getUserType() >= USER_TYPE_ZABBIX_ADMIN);
}

protected function doAction() {
$method = getRequest('method');

if($method == null) {
$this->sendResponse();
return;
}

if((is_callable(array($this, $method)))) {
$this->$method();
$this->sendResponse();
} else {
throw new Exception('cant find this method'.$method);//, $code, $previous)
}
}

protected function sendResponse() {
$response = new CControllerResponseData($this->data);
$response->setTitle($this->title);
$this->setResponse($response);
}

}

在该文件中, 重写了父类CController 中的doAction方法

接收地址传来的method

1
$method = getRequest('method');

对$method非空处理

1
2
3
4
if($method == null) {
$this->sendResponse();
return;
}

处理当前控制器中存在从地址传来的方法, 刚去执行该方法, 并sendResponse, 否则给出无法找到方法的报错

1
2
3
4
5
6
if((is_callable(array($this, $method)))) {
$this->$method();
$this->sendResponse();
} else {
throw new Exception('cant find this method'.$method);//, $code, $previous)
}

在权限的处理上, 是重写了父类中的checkPermissions(父类是是默认false,无权限), 这里默认是当前用户类型大于等管理员,如果权限和需求不一定, 请在子类控制器中重写该方法

1
2
3
protected function checkPermissions() {
return ($this->getUserType() >= USER_TYPE_ZABBIX_ADMIN);
}

打完收工

Zabbix架构的改进方案(二)

比如要做一个用户管理模块UserManager, 通过UserManager.do访问主页面, 而新增用户的地址为UserManager!add.do, 删除用户的后台请求地址为UserManager!delete.do

1, 建一个后台php文件继承BaseController,名为UserManagerController.php

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
// 
class UserManagerController extends BaseController {

function userList() { // 用户列表,通过UserManager!userList.do来访问
$this->data = [
[
'username' => 'sun',
'sex' => 'f'
], [
'username' => 'aaa',
'sex' => 'f'
], [
'username' => 'aaa',
'sex' => 'f'
], [
'username' => 'aaa',
'sex' => 'f'
]
];
}

function add() { // 新增用户,通过UserManager!add.do来访问
$this->data = [
'success'=> true,
'msg'=>'add success'
];
}

function delete() { // 删除用户,通过UserManager!delete.do来访问
$this->data = [
'success'=> true,
'msg'=>'delete success'
];
}
}

如果需要权限控制,则在该Controller中重写父类方法(默认是管理员及以上可以访问)

1
2
3
function checkPermissions() {
return ($this->getUserType() >= USER_TYPE_ZABBIX_ADMIN);
}

效果:
访问 http://localhost/dam/UserManager!userList.do时返回数据如下

1
2
3
4
5
6
[
{"username":"sun","sex":"f"},
{"username":"aaa","sex":"f"},
{"username":"aaa","sex":"f"},
{"username":"aaa","sex":"f"},
]

2, 建一个对应前端的js文件,名为UserManager.js, 继承自TSWidget或TSWidget的子类

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
// demo
define("js/widgets/UserManager", [
"ts/widgets/TSDataGrid",
"ts/widgets/TSGridColumn",
"ts/widgets/TSButton",
'ts/widgets/GenericDialog',
], function(TSDataGrid, TSGridColumn, TSButton, Dialog){

var i18n=TSDataGrid.prototype.i18n.createBranch({});
function UserManager(opts){
TSDataGrid.call(this);

this.dataSource = 'UserManager!userList.do';
defineProperties.call(this);
}

function defineProperties(){
var self=this;
var username=new TSGridColumn({
dataField:"username",
headerText:'username',
width:250,
});

var sex=new TSGridColumn({
dataField:"sex",
headerText:'sex',
width:250,
});

this.columns=[username, sex];
this.actions = [new TSButton({
name:"测试",
buttonName:'测试',
iconClass:"glyphicon glyphicon-plus",
click:function(){
Dialog.create({
title: 'fdafds'
})

}
})];
this.usePager=true;
this.checkable=false;
this.showToolbar=true;
this.showFooter=true;
}

ExtendClass(UserManager, TSDataGrid);

return UserManager;

});

如果想加到菜单里,则在menu.inc.php中配置下

1
2
3
4
5
], [
'label' => _('测试demo'),
'url' => 'UserManager.do',
'active_if' => ['UserManager'],
],[

Zabbix架构的改进方案(一)

最近要使用Zabbix做二次开发, 实在无力吐槽..

1, 界面丑陋(3版本的还好些), 二版本的简直就是上个世纪的界面, 看着就没有做的心情.

2, 用户体验差,选择个下列框界面都要刷一次, 几乎所有的操作都是用提交本页刷新来完成.

3, ui框架是用php后台做的, 面向对象的类继承结构,这个思想倒是不错, 让我想起了java的zk框架,但是做的远没有zk完善, 特别是对事件的处理上, 绝大多数还是要依赖嵌入的js文件来对dom操作. 组件的功能绝大多只是对对html元素做了个封装,实用的功能很少. 基本上也是鸡肋. 这也造成了前端后台耦合非常的深, 非常不利于前后端分离开发.

4,后台控制器实现繁琐. 系统中的后台请求分两种模式, 一种是在单个php文件中根据参数的不同用if来判断走不同的分枝,非常的乱,再加上php的调试模式, 调试起来非常的麻烦. 另一种是写一个类文件继承CController, 一个请求要对应一个php文件,还要在CRouter中配置, 也比较繁琐

针对以上情况,我们采取的方式是,原有功能仍保持zabbix原来的模式,新增加的功能则使用前后台分离的模式,要采用我们自己的前端js框架,前端只负责前端界面展示及操作,后台只负责后台操作,然后返回给前端json数据,两者之间只有数据的交互. 为了使用这种模式,除了引入前端的框架,还要对后台的mvc架构进行改进, 原先的后台模式分两种,一种是非mvc模式,直接在php文件中根据传来的参数简单的判断,非常的乱. 另一种是mvc模式,缺点一个请求要对应一个控制器文件,还要在手配置文件中手动的配置一下,开发比较繁琐. 针对以上两种模式的缺点, 在第二种mvc模式基础之上改进了一个新的mvc模式,即一个模块只要建一个前端js文件和后台的一个继承了BaseController的控制器即可,该模块里所有的操作都是做为该控制器的成员方法存在的,类似java的struts2.

新模式中,一个模块就是一个页面,包括头,尾和中间内容部分,头和尾都是固定的, 中间界面部分需要建一个对应的js来渲染界面,该js继承要自TSWidget. 访问时通过 模块名.do来访问.该模块中还会有很多其他请求,比如新增, 删除等,这些请示对应的后台是继承自BaseController的一个文件名为 模块名 + Controller.php的文件, 该模块中有对应操作的各方法, 在各方法中操作数据库, 并将结果赋值给$this->data即可,访问时通过 模块名!方法名.do来访问,返回一个由Controller中方法中$this->data转成的json字符, 返回到前台处理.

下节再见

SSH整合:使用DAO框架(BaseDao),DAO不写或少写,小弟我们更加专注业务(转)

转自:http://www.myexception.cn/software-architecture-design/417462.html

SSH整合:使用DAO框架(BaseDao),DAO不写或少写,我们更加专注业务
这里我们新建一个BaseDao,让普通Dao(如:UserDao)继承它,
这样普通Dao什么都不写就可以自动实现基本的增删改查操作,也可以在UserDao中扩建方法…
BaseDao.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
package com.kaishengit.dao;

import java.io.Serializable;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.kaishengit.util.ReflectUtil;

public class BaseDao<T,PK extends Serializable> {

private SessionFactory sessionFactory;
//Class<?>:防止报黄线,意思是传入任意类型都行,无所谓,也可以使用Object
private Class<?> entityClass;

public BaseDao(){
//new出一个子类对象,父类中的this是子类中的this
entityClass = ReflectUtil.getGenericParameterType(this.getClass());
}

public void saveOrUpdate(T t){
getSession().saveOrUpdate(t);
}

public void del(PK pk){
getSession().delete(findById(pk));
}

public void del(T t){
getSession().delete(t);
}

@SuppressWarnings("unchecked")
public T findById(PK pk){
return (T) getSession().get(entityClass, pk);
}

@SuppressWarnings("unchecked")
public List<T> findAll(){
Criteria criteria = getSession().createCriteria(entityClass);
return criteria.list();
}

@SuppressWarnings("unchecked")
public List<T> findByPage(int start,int count){
Criteria criteria = getSession().createCriteria(entityClass);
criteria.setFirstResult(start);
criteria.setMaxResults(count);
return criteria.list();
}

//get
public Session getSession(){
return sessionFactory.getCurrentSession();
}

//set
@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
}

这里面我用到自己写的一个工具类,通过反射实现返回的实际参数化类型T
ReflectUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.kaishengit.util;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class ReflectUtil {
public static Class<?> getGenericParameterType(Class<?> clazz){
//获得父类的泛型类类型
Type type = clazz.getGenericSuperclass();
//强制转化为参数化类型:Collection<String>
ParameterizedType pt = (ParameterizedType) type;
//返回实际参数化类型
Type[] types = pt.getActualTypeArguments();
return (Class<?>) types[0];
}
}

这样就ok啦,我们新建一个UserDao继承它,这样UserDao就自动拥有了增删改查功能,
是不是很令人兴奋呀,里面又扩建了一个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.kaishengit.dao;

import org.hibernate.Query;
import org.springframework.stereotype.Repository;

import com.kaishengit.pojo.User;

@Repository
public class UserDao extends BaseDao<User,Integer>{
public User findByNameAndPassword(User user){
String hql = "from User where username = ? and password = ?";
Query query = getSession().createQuery(hql);
query.setParameter(0, user.getUsername());
query.setParameter(1, user.getPassword());
return (User) query.uniqueResult();
}
}