Vuejs 和 CodeMirror 的绑定

实现 vuejs 和 codemirror 的相互绑定

Vue.directive("codemirror", {
    twoWay: true,
    bind: function () {
        // 请自行初始化CodeMirror, 可使用 $(this.el).data('codemirror')
        this.editor = editor;
        // model主动赋值标记
        this.silent = false;
        this.handler = function () {
            if (!this.silent) {
                this.set(this.editor.getValue(), true); // 加锁,防止相互赋值
            }
        }.bind(this);
        this.editor.on("change", this.handler);
    },
    update: function (value, oldValue) {
        this.silent = true;
        this.editor.setValue(this.vm.$data[this.raw]);
        this.silent = false;
    }
});

使用:

<!-- code 为 Vue data 中使用的变量名 -->
<textarea id="codepad" v-codemirror="code"></textarea>

golang中map的排序

在实现 golang 中发布订阅模式的时候,需要按照优先级排序回调函数;golang 中的 map 是无序的,需要手动取出 key,并对key进行排序,下面是排序一块的代码段:

[
    1 => [func1, func2]
    0 => [func5, func6]
    2 => [func3, func4]
]

...

[func5, func6, func1, func2, func3, func4]
func (ed *eventdispatcher) SortListeners(event string) {
    ed.sorted[event] = nil
    // 发布订阅模式中函数执行的优先级
    priorities := make([]int, 0)
    for priority, _ := range ed.listeners[event] {
        priorities = append(priorities, priority)
    }
    // 对优先级的数字进行排序
    sort.Ints(priorities)
    sorted := make([]func(interface {}) interface {}, 0)
    // 按照优先级顺序合并 map
    for _, priority := range priorities {
        sorted = append(sorted, ed.listeners[event][priority]...)
    }
    ed.sorted[event] = sorted
}

PopClip插件开发

配置文件

Actions 里面一个 dict 是一个图标,由于 PopClip 不支持直接执行可执行文件,所以要使用 shell 来执行一下。

自己给定的两个图标的颜色是没有关系的,PopClip 会自动修改图标的颜色。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Actions</key>
  <array>
    <dict>
      <key>After</key>
      <string>copy-result</string>
      <key>Image File</key>
      <string>id.png</string>
      <key>Regular Expression</key>
      <string>(?s)1\w+$</string>
      <key>Title</key>
      <string>Url2Id</string>
      <key>Shell Script File</key>
      <string>url2id.sh</string>
    </dict>
    <dict>
      <key>After</key>
      <string>copy-result</string>
      <key>Image File</key>
      <string>url.png</string>
      <key>Regular Expression</key>
      <string>(?s)\d+$</string>
      <key>Title</key>
      <string>Id2Url</string>
      <key>Shell Script File</key>
      <string>id2url.sh</string>
    </dict>
  </array>
  <key>Apps</key>
  <array>
    <dict>
      <key>Link</key>
      <string>http://tool.lu/</string>
      <key>Name</key>
      <string>在线工具</string>
    </dict>
  </array>
  <key>Credits</key>
  <array>
    <dict>
      <key>Link</key>
      <string>mailto:245565986@qq.com</string>
      <key>Name</key>
      <string>xiaozi</string>
    </dict>
  </array>
  <key>Extension Description</key>
  <string>Convert ids for mogujie.</string>
  <key>Extension Identifier</key>
  <string>lu.tool.popclip.extension.id-converter</string>
  <key>Extension Image File</key>
  <string>id.png</string>
  <key>Extension Name</key>
  <string>Id Converter</string>
  <key>Version</key>
  <integer>1</integer>
</dict>
</plist>

代码

PopClip 操作的文本是直接放在环境变量 POPCLIP_TEXT 里面的,所以下面的代码可以当做是 go 的一个插件模板

package main

import (
    "fmt"
    "os"
)

func main() {
    text := os.Getenv("POPCLIP_TEXT")
    fmt.Print(text)
}
go build -o IdConverter .

发布

mv IdConverter/ IdConverter.popclipext
zip -r IdConverter.popclipext.zip IdConverter.popclipext
mv IdConverter.popclipext.zip IdConverter.popclipextz

以库的形式调用YUI Compressor

import com.yahoo.platform.yui.compressor.CssCompressor;
import org.apache.commons.io.IOUtils;
import java.io.*;

public class YUIProcessor {
    public String compressCss(String code) {
        Reader in = new InputStreamReader(IOUtils.toInputStream(code));
        try (Writer out = new StringWriter()) {
            CssCompressor compressor = new CssCompressor(in);
            compressor.compress(out, -1);
            out.flush();
            return out.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }
}

追加:

yui compresser 很久没有更新,有很多的不足,已经废弃使用。

单锁的分拆降低冲突概率

      +-----------+
-->   | counter 1 |
      +-----------+
-->   | counter 2 |
      +-----------+   -> sum(subCounter)
-->   | counter 3 |
      +-----------+
-->   | counter 4 |
      +-----------+

JAVA 中的 ConcurrentHashMap 和 LongAdder 的应用

在内部都有多个计数器,这样在并发更新对象的时候,随机选择一个,就可以将锁冲突的概率降低到 1/n

mysql 计数器的应用

拿文章的喜欢计数来做例子,当有n个人同时点击喜欢的时候,INNODB会锁住这条记录,这篇文章的喜欢计数器将是串行增长的。

pre_posts

+-------+-----+----------+
|  id   | ... |   favs   |
+-------+-----+----------+
| 10000 | ... |   8001   |
+-------+-----+----------+

再看下面的,可以将喜欢数从 posts 表拆分出来,一个文章 id,对应多个子计数器,在更新计数器的时候,可以随机选择一条记录增加,在取文章喜欢数的时候,可以sum一下累加起来;这样就降低了并发时候的冲突。

pre_post_favs

+-----+----------+----------+
| id  | post_id  |   favs   |
+-----+----------+----------+
|  1  |  10000   |   2001   |
|  2  |  10000   |   1994   |
|  3  |  10000   |   2010   |
|  4  |  10000   |   1898   |
+-----+----------+----------+

对于计数器的缓存

显然上面的数据解决了数据并发写入的问题,但是查询的代价却是提高了,想到的办法就是对计数器进行缓存。

读取的时候被动缓存的话,就会产生计数器的延迟;所以这里可以采用 canal 监听 mysql binlog 主动触发 cache 的更新。

那么问题又来了,既然是存到 redis 中,该用什么类型的;如果使用大量的 string 的话,key 就会占用大量的内存,可以部分改用 hash,但是 redis 的设定中,只有 数量 < hash-max-ziplist-entries 的时候才会采用优化的存储方式;根据多位大牛的研究 1000 是比较合适的。

favs:10
    000 -> 8001
    001 -> 0
    ...
    999 -> 0

线上数据无缝迁库

当 数据量 和 qps/tps 达到一定的时候,将某块业务的一系列的表迁移到另外一个库是必然需要的。

    +----------------------------------------------+
A   |                                              |
    +----------------------------------------------+

    ++
B   ||
    ++

双写

    +----------------------------------------------+------------+
A   |                                              |            |
    +----------------------------------------------+------------+

    ++                                             +------------+
B   ||                                             |            |
    ++                                             +------------+

数据 dump

可以一次dump 10条数据 (拍脑袋),对于更新比较频繁的表可以适当减少,这里会有少量的数据是不一致的 (表的并发更新之类,dump 不能保证原子性),不用担心,我们可以在后面校验数据的时候直接修复掉

对 id取模,多进程dump 加快速度

    +----------------------------------------------+------------+
A   |                                              |            |
    +----------------------------------------------+------------+

    +-------------+                                +------------+
B   |             |                                |            |
    +-------------+                                +------------+

数据校验

这块要一条一条数据的进行对比 (扫表)

切读

在业务代码里面,将 db 的连接改到新库 B 上

去写

到这一步,代码里面的双写就可以直接去掉了;完美迁库。(旧库中的表不急忙删除,你懂的)

私信的设计

  1. 发件人和消息的关系 (一对多)

  2. 消息和收件人的关系 (多对多)

pre_messages

+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(11) unsigned | NO   | PRI | NULL    | auto_increment |
| sender_id  | int(11) unsigned | NO   |     | 0       |                |
| message    | varchar(255)     | NO   |     |         |                |
| type       | varchar(50)      | NO   |     |         |                |
| expires_at | datetime         | YES  |     | NULL    |                |
| created_at | datetime         | NO   |     | NULL    |                |
| updated_at | datetime         | NO   |     | NULL    |                |
| deleted_at | datetime         | YES  |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+

pre_message_recipients

+--------------+---------------------+------+-----+---------+----------------+
| Field        | Type                | Null | Key | Default | Extra          |
+--------------+---------------------+------+-----+---------+----------------+
| id           | int(11) unsigned    | NO   | PRI | NULL    | auto_increment |
| recipient_id | int(11) unsigned    | NO   |     | 0       |                |
| message_id   | int(11) unsigned    | NO   |     | 0       |                |
| status       | tinyint(1) unsigned | NO   |     | 0       |                |
| created_at   | datetime            | NO   |     | NULL    |                |
| updated_at   | datetime            | NO   |     | NULL    |                |
| deleted_at   | datetime            | YES  |     | NULL    |                |
+--------------+---------------------+------+-----+---------+----------------+

php中通配符的实现

该方法摘自Laravel5的Event getWildcardListeners

作用如下:

`item.*` -> `item.new`
         -> `item.edit`
class Str {
    public static function is($pattern, $value)
    {
        if ($pattern == $value) return true;

        $pattern = preg_quote($pattern, '#');
        $pattern = str_replace('\*', '.*', $pattern) . '\z';
        return (bool) preg_match('#^' . $pattern . '#', $value);
    }
}

该功能使用正则实现

  • * 替换成 .* 匹配所有字符

  • \z 这个转义序列从来没用过,查了手册是说不受修正符的影响,其实这边应该和$是一样的

PHP: Escape sequences - Manual

撸 php 源码

快速定位某个 php 函数在源码中的位置

# 所有php函数
ag 'PHP_FUNCTION\(\w+\)'
# 指定php函数
ag 'PHP_FUNCTION\(array_flip\)'

自定义个 shell 函数,方便搜索

# search php funtion in c source
function phpsrc()
{
    if [ $# -eq 0 ]; then
        echo 'usage: phpsrc <function>[ <dirname>]'
        return
    fi
    dirname="$2"
    if [ -z "$dirname" ]; then
        dirname=$(pwd)
    fi
    ag 'PHP_FUNCTION\('"$1"'\)' "$dirname"
}

快速定位 php 中的语言结构

# Zend/zend_compile.c
ag 'void zend_do_' Zend/zend_compile.c

使用命令行获取app的图标

先在桌面上建一个目标文件夹 mkdir -p ~/Desktop/appIcons/48/

#!/bin/zsh

APPNAME=$1

if [[ -z "$APPNAME" ]]; then
    echo '请先指定一个appname'
    exit 0
fi

# 获取图标名称
ICON=$(/usr/libexec/plistbuddy -c Print:CFBundleIconFile: "/Applications/${APPNAME}.app/Contents/Info.plist")
if [[ -z "$ICON" ]]; then
    echo '找不到icon文件'
    exit 0
fi

if [[ "$ICON" == *.icns ]] ICON=${ICON%.*}
ICONPATH="/Applications/${APPNAME}.app/Contents/Resources/${ICON}.icns"
# 转换格式
sips -s format png "${ICONPATH}" --out ~/Desktop/appIcons/48/"${APPNAME}".png -Z 48

为了方便使用,可以直接闹个别名 alias icon='~/Documents/geticon.sh'

访问可视化(续)

之前写过一片文章:访问可视化,但是局限性很大,于是利用周末的时间,改造了一番。

演示地址:http://tool.lu/visitor

打点的改造

老版本:在php里面进行打点

新版本:使用js加载空伪1px gif图片

数据分析和存储的改造

老版本:请求过来的时候,php分析,存进mysql

新版本:请求的时候,js分析,加载1px gif,nginx记录日志,python分析,存进数据库

数据的访问实时dump

老版本:监听mysql的binlog,publish到redis

新版本:分析nginx,存进数据库的同时,publish到redis

IP归属地 和 经纬度的查询

老版本:纯真数据库 + 百度地图地址反解

新版本:ip17mon + 腾讯地图行政区划latlag

预览

QQ20150209-1.png

1. js的打点日志

具体可以参考:网站统计中的数据收集原理及实现

不过我使用的方法由文章简化而来。

2. nginx的日志记录

server {
    listen 80;
    listen 443 ssl;
    server_name analytics.tool.lu;

    ssl_certificate vhosts/tool.lu.chained.crt;
    ssl_certificate_key vhosts/tool.lu.key;

    root /data/html/analytics.tool.lu/public;

    access_log off;
    error_log off;

    location / {
        index index.html;
        try_files $uri $uri/ =404;
    }

    location /__utm.gif {
        expires -1;
        if_modified_since off;
        # add_header Last-Modified "";
        empty_gif;
        access_log /data/log/nginx/analytics.tool.lu.access.log;
    }
}

3. nginx日志的分析

这里有几点要注意的:

  1. nginx日志我是使用logrotate切分的,要达到实时读取nginx日志的目的,需要保证读取的文件是最新切分出来的文件,而且在切分文件的时候要保证之前的文件已经处理完毕。

  2. 记录日志文件处理的offset,又是由于logrotate切分nginx日志的原因,如何保证记录的offset是唯一的,且能对应上日志文件,这里我使用了log文件创建的时间作为标识(在linux下是不储存文件的创建时间的,需要自己记录实现)。

  3. nginx log中时间格式的解析,基本上就是抓瞎了(不是所有版本的python strptime都支持timezone的)

  4. 如何通过腾讯地图 api 返回的数据建立索引,方便快速查找到 地址对应的经纬度

为了安全性考虑,这边就不贴 nginx log 解析的完整代码了。

ua-parser返回version的拼接

    def version(self, vs):
        v = ''
        if not vs['major']:
            return v
        v += vs['major']
        if not vs['minor']:
            return v
        v += '.' + vs['minor']
        if not vs['patch']:
            return v
        v += '.' + vs['patch']
        if not 'patch_minor' in vs or not vs['patch_minor']:
            return v
        v += '.' + vs['patch_minor']
        return v

腾讯地图行政区划数据索引的建立

        # IP17MON返回的数据带有,省市,可以优先市级别的索引,再省级别的索引
    def index(self):
        for province in self.data['result'][0]:
            self.indexed_provinces[province['name']] = province['location']
        for city in self.data['result'][1]:
            self.indexed_cities[city['name'] if 'name' in city else city['fullname']] = city['location']

python

list 的 shift

l = [1, 2]
first = l.pop(0)

parse nginx log的正则

self.pattern = re.compile(r'(?P<ipaddress>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) - - \[(?P<dateandtime>\d{2}\/[a-z]{3}\/\d{4}:\d{2}:\d{2}:\d{2}) [+-]\d{4}\] ((\"(GET|POST) )(?P<url>.+)(http\/1\.1")) (?P<statuscode>\d{3}) (?P<bytessent>\d+) (["](?P<referrer>(\-)|(.+))["]) (["](?P<useragent>.+)["])', re.I)

nginx log时间的解析

datetime.datetime.strptime(fields['dateandtime'], '%d/%b/%Y:%X')

PS: 好久不写python已有些生疏

数字点阵的动画实现

预览

未标题-1.gif

原理

数字0的js数组点阵,7x5个 span 元素拼出一个数字出来,同样的方法实现 1-9 的数字

动画效果使用 css3 的 keyframes 实现

[
    [0, 1, 1, 1, 0],
    [1, 1, 0, 1, 1],
    [1, 1, 0, 1, 1],
    [1, 1, 0, 1, 1],
    [1, 1, 0, 1, 1],
    [1, 1, 0, 1, 1],
    [0, 1, 1, 1, 0]
]

QQ20150119-1.png

源码

numb.zip

java Integer中highestOneBit和bitCount的实现原理

highestOneBit

图中的黑色位都可以不关注,然后基本原理就是这样的 (以8位作为例子)

QQ20150110-1.png

bitCount

0x55555555
    01010101010101010101010101010101
0x33333333
    00110011001100110011001100110011
0xf0f0f0f
    00001111000011110000111100001111
0x3f
    00000000000000000000000000111111

QQ20150110-2.png