QtCreator 中重构 Widget 的名字

QtCreator 中创建的 Qt Designer Form Class 包含三个文件: .h, .cpp, .ui,例如我们创建了一个 Form Class Widget,则包含下面三个文件:Widget.h, Widget.cpp, Widget.ui,其中的类名为 Widget,如果想要把其重命名为 MyWidget,则可以按照下面几步进行:

  • 文件重命名为 MyWidget.h, MyWidget.cpp, MyWidget.ui
  • 修改 MyWidget.ui 中的 objectName
  • 重构 MyWidget.h 中的类名 Ui::Widget 和 Widget,同时也可修改 #ifndef 的名字
  • 修改 MyWidget.cpp 中的 #include

签名验证

签名验证

签名验证涉及到客户端(比如一个 Web 应用)和服务器端,每个客户端在服务器上有一个对应的 app_idapp_key,大致步骤如下:

  1. 客户端使用 app_id + app_key + 其他参数生成签名字符串 sign

  2. 把 app_id、其他参数 和 sign 一起发送给服务器(app_key 不发送)

  3. 服务器接收到请求后,根据参数中的 app_id 查找到对应的 app_key,然后根据签名算法生成签名字符串 sign2

    客户端和服务器端使用同样的签名算法生成签名字符串。

  4. 字符串比较参数中的 sign 和服务器生成的 sign2,如果相等则签名没问题,放行访问,否则签名无效,拒绝访问

自定义随机函数

JavaScript 已经自带了随机数生成函数,为什么我们还需要弄一个随机数的生成工具呢?

例如 Web 的考试系统里,加载试卷后,需要把试卷的题目顺序打乱,如果用 JS 的随机数函数的话,每次打乱的顺序都是不一样的,因为每次生成的随机数序列都不一样。问题出现了,同一个学员刷新试卷后,题目的顺序和上一次的竟然不一样,这不符合实际要求,应该是不同学员的题目顺序不一样,但是同一个学员的题目顺序永远是一样的。这样就不能使用直接使用原生的随机函数了,下面定义一个随机数生成函数,随机数种子是一个字符串,这样就可以用学员的编码来作为随机数种子生成随机数打乱题目的顺序了,因为每次刷新时同一个学员的编码都是一样的,所以生成的随机数序列都相同,就保证了同一个学员的试卷题目顺序一直都是一样的。

Velocity 语法

Velocity 比较接近脚本语言,例如 JS

1
2
3
4
5
6
7
8
9
#if ($foo < 10)
...
#elseif ($foo == 10)
...
#elseif ($foo == 12)
...
#else
...
#end

比较一下 Freemarker

1
2
3
4
5
6
7
8
9
<#if foo < 10>
...
<#elseif foo == 10>
...
<#elseif foo == 12>
...
<#else>
...
</#if>

使用 Velocity 生成静态页面

Velocity 可以作为 SpringMVC 的 View 使用,也可以用来生成邮件,静态页面等。

Velocity 模版中可以直接调用对象的方法,这点比 Freemarker 好用,if else foreach 等语句也更舒服。

Gradle 依赖

1
2
compile 'org.apache.velocity:velocity:1.7'
compile 'org.apache.velocity:velocity-tools:2.0'

集成 Velocity

JSP 和 Velocity 都用于显示层,但是都有自己的优缺点。

Velocity 比 Freemarker 快,而且语法也更舒服。

Velocity 的优点:
  1. 不能编写 Java 代码,可以实现严格的 MVC 分离,可维护性好
  2. 性能不错,比 JSP 快
  3. 对 JSP 标签支持良好
  4. 内置大量常用函数
  5. 宏定义非常简单(类似 JSP 标签)
  6. 使用表达式语言
  7. 美工和技术的工作分离(例如命名为 .htm 的格式,不需要经过 Server 就能在浏览器里看到效果,JSP 这一点不太方便)
Velocity 的缺点:
  1. 不是官方标准
  2. 用户群体和第三方标签库没有 JSP 多

JS 绘制椭圆

Canvas 还没有提供直接绘制椭圆的功能,下面使用 bezierCurveTo() 来绘制椭圆。

圆也是使用 arc 来绘制的,在新版的 JS 中提供了 ellipse 来绘制椭圆,但是很多浏览器都还不支持

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<style media="screen">
canvas {
border: 1px solid grey;
}
</style>
</head>
<body>
<canvas id="canvas" width="500" height="300">Your browser does not support canvas.</canvas>
<script>
var canvas = $('#canvas').get(0);
var ctx = canvas.getContext('2d');
drawEllipse(ctx, 100, 100, 80, 120);
drawEllipse(ctx, 200, 200, 200, 80);
function drawEllipse(context, centerX, centerY, width, height) {
context.beginPath();
context.moveTo(centerX, centerY - height / 2);
context.bezierCurveTo(
centerX + width / 2, centerY - height / 2,
centerX + width / 2, centerY + height / 2,
centerX, centerY + height / 2
);
context.bezierCurveTo(
centerX - width / 2, centerY + height / 2,
centerX - width / 2, centerY - height / 2,
centerX, centerY - height / 2
);
context.closePath();
context.stroke();
}
</script>
</body>
</html>

Canvas 像素数据处理

Canvas 的 context 调用 getImageData() 获取 canvas 中图片的像素数据,处理好后再调用 putImageData() 设置回 canvas。

1
2
3
4
5
6
7
8
var canvas = $('#canvas').get(0);
canvas.width = 500; // canvas 的实际宽度,默认是 300
canvas.height = 300;
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); // 获取像素数据
grayscale(imageData); // 处理像素数据
ctx.putImageData(imageData, 0, 0); // 设置回 canvas

测试 Java 生成 UUID 是否重复

本文的目的是为了测试 Java 的 UUID.randomUUID() 生成 UUID 是否重复,使用了 2 种方式:

  1. 多线程加 ConcurrentSkipListSet
  2. 多线程加 MySQL
    1. 多线程生成 UUID
    2. 保存 UUID 到文件
    3. 导入文件中的 UUID 到 MySQL
    4. 使用 GROUP BY 和 HAVING 查找重复的 UUID

结果:尝试了多次,生成 1 万个,10 万,100 万个 UUID 都没有发现重复的情况。

MySQL 基于条件判断的数据插入

在编写程序时,我们经常会遇到一些基于条件判断的逻辑,比如:判断该条数据是否已经在数据库中存在,如果不存在,则插入。

技巧一:使用 ignore 关键字

如果是用主键 primary 或者唯一索引 unique 区分了记录的唯一性,避免重复插入记录可以使用 insert ignore into

当插入数据时,如出现错误时,如重复数据,将不返回错误,只以警告形式返回。所以使用 ignore 请确保语句本身没有问题,否则也会被忽略掉。

JDBC 和 MyBatis 性能比较

以下为 JDBC 和 MyBatis 的性能比较参考,MyBatis 的性能比 JDBC 大概慢三分之一,看上去像差挺大的,不过网络才是对效率影响最大的因素,局域网中有 200 多倍的影响,这么比较起来,MyBatis 和 JDBC 本身的效率差距可以忽略不计了。

测试时,向本机的数据库插入 66720 条记录,向局域网中其他机器上的数据库插入 20000 条记录,每条记录有 7 个字段

在向局域网中其他机器上不停的插入数据时,2 台机器的 CPU 占用都很小,也就百分之几,因为大多数时候都在等待 IO。

Vue 后台管理简单框架(三)- 多页

Vue 后台管理简单框架(一)Vue 后台管理简单框架(二) 中介绍的都是单页 SPA 的实现,但是实际系统中后台管理的功能很可能是需要多页的,例如要开发一个学习系统,学生和老师的管理功能完全不一样,如果非要把它们放在一起使用 SPA 的方式也可以,左边菜单栏根据角色是老师或则学生来动态显示也是可以的,但是这样会导致管理页的代码很多,功能都放在一起,开发的时候可能不够清晰,增加开发难度,如果把它们分开,使用多页的方式来实现,功能模块就很清晰了,不失为一个好办法。还有例如 PC 的网页和移动设备的网页实现不同,如果放在同一个页面就需要做各种判断来确定对应设备显示的内容也会把很简单的逻辑搞的很复杂,使用不同的页面的话就会很清晰了。

下面就来介绍把 vue-cli 创建的工程改造为支持多页:

  • 不同页面的文件放在不同的文件夹下

    每个页面都有自己的 router, store

  • 修改 3 个配置文件:

    • webpack.base.conf.js: 修改入口文件 entry
    • webpack.dev.conf.js: 修改 HtmlWebpackPlugin
    • webpack.prod.conf.js: 修改 HtmlWebpackPlugin,删除 CommonsChunkPlugin

Vue 后台管理简单框架(一)- 单页

vue-cli 简单搭建项目框架 后我们知道了怎么使用 vue-cli 创建一个项目,但是页面比较简单,一般后台管理功能的界面会像下面这样子:

下面介绍怎么实现一个这样的界面,UI 的框架使用 Element,命令行进入前面创建的项目目录,安装 Element:

1
npm install element-ui --save

然后在 main.js 中引入 Element 就能使用 Element 了,具体参考 main.js:

1
2
3
4
import Element from 'element-ui';
import 'element-ui/lib/theme-default/index.css';
Vue.use(Element);

任务队列

可以使用 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
package com.xtuer.util;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 任务队列,同时执行任务的数量由构造函数的参数 concurrentTaskCount 指定。
*/
public class TaskQueue {
private ExecutorService executor;
/**
* 创建任务队列,concurrentTaskCount 指定同时执行任务的数量。
* 有些情况下任务需要排队一个执行完后再执行另一个,此时 concurrentTaskCount 传入 1。
*
* @param concurrentTaskCount 同时执行任务的数量
*/
public TaskQueue(int concurrentTaskCount) {
executor = Executors.newFixedThreadPool(concurrentTaskCount);
}
/**
* 添加任务,根据不同的业务逻辑定义一个任务类,继承自 Runnable,
* 可以在属性中存储任务相关的数据,在 run() 中实现任务逻辑。
* 当然也可以重载 addTask() 函数实现添加不同的任务。
*
* @param task
*/
public void addTask(Runnable task) {
executor.submit(task);
}
/**
* 下面的实现是为了测试使用
*
* @param n 任务内容
* @param delay 任务消耗的时间,单位为秒,为了测试用的
*/
public void addTask(int n, int delay) {
addTask(() -> {
// 模拟任务执行消耗时间
try {
Thread.sleep(delay * 1000);
} catch (InterruptedException e) {
}
System.out.println(n + " started at " + System.currentTimeMillis() + " and elapsed " + delay * 1000);
});
}
/**
* 销毁任务队列,不再接受新的任务。
* Spring bean 的 destroy-method 函数。
*/
public void destroy() {
executor.shutdown();
}
public static void main(String[] args) throws Exception {
TaskQueue taskQueue = new TaskQueue(1);
taskQueue.addTask(1, 1);
taskQueue.addTask(2, 1);
taskQueue.addTask(3, 1);
taskQueue.addTask(4, 1);
taskQueue.addTask(5, 1);
taskQueue.destroy();
}
}

可以如下使用 Spring bean 来生成任务队列的对象

1
2
3
4
5
6
7
8
9
<!--单任务队列-->
<bean id="singleTaskQueue" class="com.xtuer.util.TaskQueue" destroy-method="destroy">
<constructor-arg value="1"/>
</bean>
<!--多任务队列-->
<bean id="multiTaskQueue" class="com.xtuer.util.TaskQueue" destroy-method="destroy">
<constructor-arg value="222"/>
</bean>

然后在 Controller 中如下使用

1
2
3
4
5
6
7
8
9
10
11
@Resource(name="singleTaskQueue")
private TaskQueue singleTaskQueue;
@GetMapping("/tasks/{taskId}")
@ResponseBody
public Result task(@PathVariable int taskId) {
Random rand = new Random();
singleTaskQueue.addTask(taskId, rand.nextInt(4) + 1); // 任务执行时间为 1 到 4 秒
return Result.ok("" + taskId);
}

完全自己实现的话,任务队列继承 Thread,用一个 list 存储任务,在 run() 函数中用循环查看是否有任务可执行,如果没有则调用 wait() 等待,当调用 addTask() 添加新的任务后调用 notify() 让 while 循环中可获取一个任务执行,获取和添加任务时还要锁住队列等,如果同时允许执行多个任务则还要用一个计数器记录正在执行的任务数,需要处理好各种细节。使用 Executors.newFixedThreadPool() 后,这些细节都不需要我们关心了。

Doc docx xls 等转为 PDF 和 HTML

下面介绍使用 JodConverter + LibreOffice 把 Windows Office 的 doc,docx,xls 等文档转换为 PDF 和 HTML:

  • HTML:
    • 优点: 用浏览器打开方便,便于实现 doc 等在线预览
    • 缺点: 相对于 PDF 大不少,图片是独立文件,格式也没有 PDF 的漂亮
  • PDF:
    • 优点: 比 HTML 格式小,格式比较接近于原文档
    • 缺点: 相对于 HTML 在线预览不够方便,也可以借助 pdf.js + HTML5 实现在线预览

HttpServletResponse 下载文件

实现点击按钮下载文件以及点击 a 标签下载文件,注意一下几个问题:

  • 浏览器中点击链接下载文件没啥好说的,但是点击按钮怎么实现下载呢?

    1
    调用 window.open(url) 就可以了
  • 服务器端需要设置响应头表明是以流的形式下载文件

    1
    response.setContentType("application/octet-stream");
  • 文件名有中文时需要处理乱码问题

    1
    2
    String filename = new String(paper.getOriginalName().getBytes("UTF-8"), "ISO8859_1"); // 解决乱码问题
    response.setHeader("Content-Disposition", "attachment;filename=" + filename);

允许其他机器访问 MySQL

A 机器上的 MySQL 默认只能 A 机器上的软件访问,即 localhost,如果 B 机器上的软件想访问 A 机器上的 MySQL,需要 MySQL 对 B 机器的 IP 进行授权。

方式一

  • 任意主机以用户 root 和密码 root 连接到 MySQL 服务器

    1
    2
    GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'root' WITH GRANT OPTION;
    FLUSH PRIVILEGES;
  • 指定 IP 为(如192.168.10.186)的主机以用户 alice 和密码 Passw0rd 连接到 MySQL 服务器

    1
    2
    GRANT ALL PRIVILEGES ON *.* TO 'alice'@'192.168.10.186' IDENTIFIED BY 'Passw0rd' WITH GRANT OPTION;
    FLUSH PRIVILEGES;

方式二

网上还看到说直接修改 user 表中 User root 的 Host 为 %,最好别这么干,不小心会哭的:

1
2
3
4
USE mysql;
SELECT user, host FROM user;
UPDATE user SET host='%' WHERE user='root';
FLUSH PRIVILEGES;

按照上面的修改 host 为 % 后外网可以访问了,但是本地却访问出错:

1
2
3
mysql -uroot -p
提示
Access denied for user 'root'@'localhost' (using password: YES) when trying

可用按下面的方式补救:

1
2
3
4
5
6
7
8
9
10
1. 启动 mysqld_safe
mysqld_safe --user=mysql --skip-grant-tables --skip-networking &
2. 登陆修改
mysql -u root mysql
use mysql
UPDATE user SET host='localhost' WHERE user='root';
FLUSH PRIVILEGES;
quit
这时可以看到 user 中关于 root 的记录会多一条

前 16 名 Java 实用工具类

从 GitHub 随机选择的 50,000 个开源 Java 项目中统计出最常用的 16 个 Java 实用工具类类及其最常用的方法,类列表和方法列表都按人气排序。

  1. org.apache.commons.io.IOUtils
    • closeQuietly ( )
    • toString ( )
    • copy ( )
    • toByteArray ( )
    • write ( )
    • toInputStream ( )
    • readLines ( )
    • copyLarge ( )
    • lineIterator ( )
    • readFully ( )

拖拽普通 Element 到 zTree

zTree 不支持 jQuery ui 的拖拽操作,因为 drop 事件阻止了 mouse up 事件的冒泡以致 zTree 不能调用 onMouseUp 的回调函数。为了拖拽普通的 element 到 zTree 上,需要自己实现拖拽功能,Drag With Other DOMs 演示了具体的实现,但是代码太多,不易于理解,这里把拖拽相关的核心代码提取出来,就能快速的理解拖拽的实现。

Vue DOM 更新完成后再执行函数

Vue 的数据变化后会更新 DOM,DOM 只能保证在当前 tick 里面的代码全部执行完毕后更新,不能保证数据一变化后就能用 document.querySelector() 获取到最新的 DOM。要保证在 DOM 更新以后执行某一块代码,就必须把这块代码放到下一次事件循环里面,比如 setTimeout(fn, 0),这样 DOM 更新后,就会立即执行这块代码。

有些时候 DOM 更新完成后执行某些操作是有必要的,这时就可以使用 Vue.nextTick() 注册一个函数放到 Vue 的事件队列里,使其在下一个 tick 被执行。

例如使用 Vue + Semantic Ui 创建 Popup,新创建的 Popup 需要执行 popup() 后才会生效,此时在 DOM 更新完成后需要执行一下 popup() 函数。

一般有 3 种方式调用 Vue.nextTick():

  • 普通事件处理函数中,下面的 [[1]],当有多个地方修改同一个变量时,每个地方都需要执行一次
  • 监听指定的数据变化时,下面的 [[2]],粒度细,只与数据是否变化有关,和修改数据的地方无关
  • updated() 回调中,下面的 [[3]],只要 DOM 变化了都会调用,无关的数据变化时都会调用,最省事,但是需要小心测试看看是否有副作用

Semantic Ui Tips

JS and CSS

一下代码都是基于 jQuery、Semantic Ui、Layer、Vue 来写的:

1
2
3
4
5
<link rel="stylesheet" href="http://cdn.staticfile.org/semantic-ui/2.2.7/semantic.min.css">
<script src="http://cdn.bootcss.com/jquery/1.9.1/jquery.min.js"></script>
<script src="http://cdn.staticfile.org/semantic-ui/2.2.7/semantic.min.js"></script>
<script src="http://cdn.staticfile.org/vue/2.0.3/vue.js"></script>
<script src="http://cdn.staticfile.org/layer/2.3/layer.js"></script>

分页计算工具

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
package com.xtuer.util;
/**
* 分页时需要计算某一页的起始位置,或则使用记录总数计算共有多少页,PageUtils 的任务就是计算分页时的数据:
* PageUtils.offset(pageNumber, pageSize) 用于计算起始位置
* PageUtils.pageCount(recordCount, pageSize) 用于计算共有多少页
*/
public class PageUtils {
/**
* 根据传入的页数、每页上的最多记录数计算这一页面的开始位置 offset
*
* @param pageNumber 页数
* @param pageSize 每页上的最多记录数
* @return 开始的位置 offset
*/
public static int offset(int pageNumber, int pageSize) {
// 校正参数,pageNumber 从 1 开始,pageSize 最小为 1
pageNumber = Math.max(1, pageNumber);
pageSize = Math.max(1, pageSize);
int offset = (pageNumber -1) * pageSize; // 计算此页开始的位置 offset
return offset;
}
/**
* 根据传入的记录总数、每页上的最多记录数计算总页数 pageCount
*
* @param recordCount 记录的总数
* @param pageSize 每页上的最多记录数
* @return 总页数
*/
public static int pageCount(int recordCount, int pageSize) {
// 校正参数,recordCount 最小为 0,pageSize 最小为 1
recordCount = Math.max(0, recordCount);
pageSize = Math.max(1, pageSize);
int page = (recordCount-1) / pageSize + 1;
return page;
}
}

修改 Semantic UI 的默认字体

Semantic Ui 默认使用的是谷歌提供的字体,并且是直接使用了谷歌的官方链接。谷歌网站在国内访问速度很差,甚至根本无法访问,需要对 Semantic UI 的源文件进行一下手动修改:

  1. 使用 Nodejs 下载 Semantic Ui 源码: http://www.semantic-ui.cn/introduction/getting-started.html

    • 安装 Nodejs
    • 安装 gulp: npm install -g gulp
    • 下载 Semantic Ui: npm install semantic-ui –save
  2. 修改 src\themes\default\globals\site.variables

    • 修改文件中的 @fontName 的值来设置 Semantic UI 的默认字体,这里使用了微软雅黑: Microsoft YaHei
    • 修改文件中的 @importGoogleFonts 为 false 禁止使用 Google 的字体
  3. 使用命令 gulp build 编译一下 Semantic UI

  4. 复制生成的 dist 目录中的文件到项目里即可

Vue 动态显示编辑按钮和计算 Class

下面介绍使用 vue

  • 动态的显示编辑按钮

    当鼠标移动到 segment 上时显示编辑按钮(@mouseenter 事件),鼠标离开 segment 时隐藏编辑按钮(@mouseleave 事件),需要定义一个属性如 editable 表示当前 segment 是否可编辑,使用 v-show 进行判断显示和隐藏

  • 动态的计算 class

    复杂情况下不同元素的 class 不一样,此时可以使用函数动态计算 class,在 :class 中使用此函数,不同的参数计算出来的 class 不一样

Vue Todo

实现一个简单的 Todo 来介绍不同的 Vue 对象共享同一个 data 数组、Todo 的增加、删除、编辑:

左边的 Todo 和右边的 Dropdown 分别用一个 Vue 对象来渲染,它们共享使用同一个数组 todos,当左边的 Todo 修改了 todos 的数据后,右边的 Dropdown 的数据也会同时自动更新。点击编辑按钮,在 todo 原来的地方显示一个 input 进行编辑。

Semantic Ui Grid

Semantic-Ui 是一套类似于 bootstrap 的 ui framework,相比 Bootstrap 有如下优点:

  • 组件采用语义化的组织方式,容易理解,容易记忆
  • 组件库非常丰富,几乎不需要引入第三方的组件,统一性强
  • 由于采用语义化的方式命名,所有组件都有自己的命名空间,相互不受干扰,侵入性弱,对自定义 css 干扰小,bootstrap 则改写了很多元素的默认样式,复写困难
  • 自定义容易,semantic ui 的源码中各个组件相互独立,依赖清晰,自定义方便

Fundamental Concepts

  • container
    A fixed width container 具有固定宽度的容器

  • grid
    网格

  • column
    网格的列

  • row
    网格的行

SpringMvc 响应 JSONP

SpringMvc 中处理 JSONP 需要注意响应的 Content-Type,如果为 text/plain 时在某些浏览器下就不能正确的执行 JSONP 的回调函数,认为其是不可执行的格式。

SpringMvc 返回对象时会把其自动的转换为 JSON 字符串,并且设置 Content-Type 为 application/json,如果返回 String 的话则 Content-Type 会设置为 text/plain,使用 JSONP 时需要返回 JSONP 格式的 String,这时例如在高版本的 Chrome 中就会出错,因为 Content-Type 是 text/plain,而不是 application/javascript

zTree 右键菜单

zTree 使用右边的添加、编辑、删除按钮时容易误操作(踩过坑了),所以下面使用右键菜单来编辑 zTree。

既然需要对树进行编辑,说明数据是需要保存到服务器的,所以树的加载也是需要使用动态加载,不过事例中写死是为了无服务器时便与演示(注释中有动态访问服务器的代码)。

实现时需要注意几点:

  • 重命名操作需要在 zTree 的回调函数 beforeRename 中进行
  • 创建新节点时
    • 节点没有展开,先展开它,这时如果还没有从服务器加载过数据,则会先从服务器中加载子节点数据,展开完成后会调用回调函数 onExpand,这时在 onExpand 中创建新节点。节点没有展开,且没有从服务器加载过子节点数据时不在 onExpand 中创建新节点,就会很有可能看到 2 个一样新建的节点,因为调用 window.tree.addNodes 创建了一个节点,服务器返回的数据中很可能也已经有这个节点的数据(因为是异步的),所以重复了,不过在数据库中并没有重复,只是浏览器本地的 zTree 中内存数据重复
    • 是根节点,或者非根节点且已经展开了,则直接创建新节点,这时不会从服务器加载子节点数据

使用时需要修改 TODO 相关的部分:

  • 配置 setting.async 从服务器加载数据,并删除 nodes 初始化代码

  • 完成函数 createNode(), removeNode(), renameNode(),已有相关的注释作为提示

    $.rest.syncCreate(), $.rest.syncRemove(), $.rest.syncUpdate() 是对 $.ajax() 的简单封装,为了简化 Ajax 请求操作,可以自己实现或则参考 jQuery 的 REST 插件

图片和 Base64 字符串互转

把图片的二进制数据转换为 Base64 编码的字符串表示,格式为 data:image/png;base64,iVBORw0KGgoAAAA…

  • /; 之间的内容为图片的格式
  • , 后面的内容为图片的二进制数据的Base64 编码的字符串

解析 Base64 编码字符串表示的图片为二进制数据,根据上面的算法反过来解析即可。

zTree

zTree is an advanced jQuery ‘tree plug-in’. The performance is excellent, it is easy to configurw (with a full set of options), and has many advanced features (that usually only come with paid software).

zTree is open source and uses the MIT license.

主页是 http://www.treejs.cn

你刚才在淘宝上买了一件东西

你发现快要过年了,于是想给你的女朋友买一件毛衣,你打开了 www.taobao.com。这时你的浏览器首先查询 DNS 服务器,将 www.taobao.com 转换成 IP 地址。不过首先你会发现,你在不同的地区或者不同的网络(电信、联通、移动)的情况下,转换后的 IP 地址很可能是 不一样的,这首先涉及到负载均衡的第一步,通过 DNS 解析域名时将你的访问分配到不同的入口,同时尽可能保证你所访问的入口是所有入口中可能较快的一个 (这和后文的 CDN 不一样)。

你通过这个入口成功的访问了 www.taobao.com 的实际的入口IP地址。这时你产生了一个 PV,即 Page View,页面访问。每日每个网站的总 PV 量是形容一个网站规模的重要指标。淘宝网全网在平日(非促销期间)的PV大概是 16-25 亿之间。同时作为一个独立的用户,你这次访问淘宝网的所有页面,均算作一个 UV(Unique Visitor 用户访问)。12306.cn 的日PV量最高峰在 10 亿左右,而 UV 量却远小于淘宝网十余倍,这其中的原因我相信大家都会知道。

字节序 Endian

Endian 一词来源于乔纳森·斯威夫特的小说格列佛游记。小说中,小人国为水煮蛋该从大的一端(Big-End)剥开还是小的一端(Little-End)剥开而争论,争论的双方分别被称为 Big-Endians 和 Little-Endians。

1980 年,Danny Cohen 在其著名的论文 “On Holy Wars and a Plea for Peace“ 中为平息一场关于字节该以什么样的顺序传送的争论而引用了该词。

Endian 翻译为“字节序”,又称端序尾序。在计算机科学领域中,字节序是指存放多字节数据的字节(byte)的顺序,典型的情况是整数在内存中的存放方式和网络传输的传输顺序。Endianness 有时候也可以用指位序(bit)。

一般而言,字节序指示了一个 UCS-2 字符的哪个字节存储在低地址。如果 LSByte 在 MSByte 的前面,即 LSB 为低地址,则该字节序是小端序;反之则是大端序。在网络编程中,字节序是一个必须被考虑的因素,因为不同的处理器体系可能采用不同的字节序。在多平台的代码编程中,字节序可能会导致难以察觉的 bug

BIG Endian:最低位地址存放高位字节,可称高位优先,内存从最低地址开始按顺序存放(高数位数字先写)。最高位字节放最前面。

LITTLE Endian:最低位地址存放低位字节,可称低位优先,内存从最低地址开始按顺序存放(低数位数字先写)。最低位字节放最前面。

Big Endian 解释

最低位地址存放高位字节,可称高位优先,内存从最低地址开始按顺序存放(高数位数字先写)。最高位字节放最前面。

例如“汉”字的 Unicode 编码是 6C49。如果将 6C 写在前面,就是 big endian,如果将 49 写在前面,就是 little endian。

Java NIO Buffer

Java NIO 由以下几个核心部分组成:

  • Channel
  • Buffer
  • Selector

虽然 Java NIO 中除此之外还有很多类和组件,但在我看来,Channel,Buffer 和 Selector 构成了核心的API。
Buffer 缓冲区,以及缓冲区如何工作,是所有 I/O 的基础。所谓输入/输出讲的无非就是把数据移进或移出缓冲区。

滚动插件 Animation Scroll

AnimationScroll 是一个 jQuery 的滚动插件,支持很多缓冲动画效果

AnimateScroll is a jQuery plugin which enables you to scroll to any part of the page in styleby just calling the animatescroll() function with the Id or Classname of the element where you want to scroll to.

Basic usage: $('body').animatescroll();

可到 https://plugins.jquery.com/animatescroll/ 下载,主页为 http://plugins.compzets.com/animatescroll/

Dubbo Hello World

本文介绍 Dubbo 的入门程序:

  • Dubbo Provider: Dubbo + SpringMvc
  • Dubbo Consumer: Dubbo + JUnit

使用传统 RPC 时服务器之间是星形结构,紧耦合的,Dubbo (SOA) 时服务器之间通过调度中心调度,典型的中介者模式,实现了解耦,服务的注册和查找通过中心的 ZooKeeper 使用接口实现,不需要配置 URL,服务器之间互相不知道对方的存在。Spring Http Remote Invoker 也能实现远程方法调用,但是需要配置 URL,而且也是星形结构。

Spring Http 远程方法调用

Spring Http 远程调用 (Spring Http Remote Invocation) 是 Spring 提供的一种特殊的允许通过 HTTP 进行 Java 串行化的远程调用策略,支持任意 Java 接口,相对应的支持类是 HttpInvokerServiceExporter (服务器端) 和 HttpInvokerProxyFactoryBean (客服端)。

远程调用两部分:

  • 服务端
  • 客户端

调用逻辑:

RequireJS 加载非 AMD 的 JS

RequireJS 加载的 JS 要求是 AMD 规范的,但是非 AMD 规范的 JS 文件也能够加载,下面就以 Util.js 中定义了类 Rect,Circle 和普通函数 greeting() 为例,演示 RequireJS 对于非 AMD 规范的 JS 的配置,加载以及使用。

可以看到,使用 RequireJS 加载的 JS 中的类和函数,与使用 <script src="/js/Util.js"> 加载时的使用方式没有区别,如果要以 AMD 的方式使用非 AMD 的 JS,可以参考 http://www.bubuko.com/infodetail-671521.html

用 RequireJS 统一管理 JS 和 CSS

如果页面很多,每个页面里都使用 <script> <link> 来加载 JS, CSS,就会显得很分散,开发环境和生产环境中想要修改多处 JS, CSS 的路径时不够方便,例如生产环境使用七牛等 CDN 加载 jQuery,而测试环境不能访问外网,这时 CDN 就玩了(不要问我为什么,就是有人这么干,我被坑的不要不要的),只能把 jQuery 放到本地了,为此需要到所有页面里修改代码然后测试,很不方便。使用 RequireJS 后就可以集中的管理 JS, CSS,修改起来比较方便,也可以使用 Build 工具根据不同的环境进行修改。

下面介绍使用 RequireJS 加载 jQuery, Layer, Vue, SemanticUi。

PHP 快速入门

开发环境:可以使用 MAMP Download

经典的 Hello World

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<?php
echo "Hello World!";
?>
</body>
</html>
  • PHP 脚本可放置于文档中的任何位置
  • PHP 脚本以 <?php 开头,以 ?> 结尾
  • 语句以分号结尾 ;
  • 注释:# 单行注释// 单行注释/* 多行注释 */
  • 变量名对大小写敏感:$color$Color 是不同的变量
  • 用户定义的函数关键字(例如 if、else、echo 等等)都对大小写不敏感:Echo "Ok"echo "Ok" 是一样的效果
  • 输出内容到网页上用 echo
  • var_dump():会返回变量的数据类型和值,调试的时候很有用: var_dump(“text”): string(4) "text";
  • print_r:Prints human-readable information about a variable
|