CKEditor 的使用

CKEditor 是一个非常优秀的 Web 服文本编辑器,提供了非常多的功能和丰富的文档

Constantly leading innovation in the field of rich text editing. Take full control of your content creation process with such unique features as Paste from Word, Advanced Content Filter, widgets, custom HTML formatting and many more.

CKEditor 还有一个很大的优势是它有一个非常强大的插件商店,里面基本都是免费的,推荐 2 个基础,但是很实用的插件:

下面将介绍:

  • 集成 CKEditor
  • 设置 CKEditor
  • 对话框中使用 CKEditor
  • CKEditor 上传图片
  • CKEditor 上传文件

Hexo 友言评论

在博客系统里增加评论功能,如果自己做的话,需要准备服务器端(PHP 和 Java 等实现)和数据库用于评论的提交、读取和保存。如果只是简单的使用评论功能,对评论的数据分析、安全性等要求不高,可以使用第三方提供的评论系统,例如 友言,集成起来也很简单,只要在页面的 HTML 代码里加入一小段代码即可。

Docx 转换为 HTML

上传 docx 文件到服务器上时,如果需要在浏览器中预览,可以把 docx 转为 pdf,然后使用 pdf.js 在线预览 pdf,也可以把 docx 转为 html,直接在浏览器中打开。Docx4j 可以很简单的把 docx 转为 html。

Semantic Ui 的 Behavior 和 Settings

学习 Semantic Ui 的时候,不少组件的文档里都有 behavior 和 settings,例如 Dimmer, Form Validation, Transition, Tab, Dropdown等,相信不少同学对 behavior 和 settings 应该怎么用摸不着头脑。

Behavior 就是行为,和函数是一回事,写个函数不就好了?但是 Semantic Ui 里偏偏不这样,而是传入 behavior 的名字,然后内部去调用。

Settings(设置) 更是不知道什么时候用啊,既然是设置,那么逻辑上就应该在 behavior 被调用之前先行设置好,这样在 behavior 被调用的时候才会生效,如果在 behavior 被调用后才使用 settings,那么就没有意义了。

下面就以 card 中使用 dimmer 为例,介绍 behavior 和 settings 的使用:

首字母放大缩进

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style media="screen">
body {
font-family: 'Arial'
}
div {
text-indent: 2em;
}
div:first-letter {
font-size: 3em;
color: gray;
}
</style>
</head>
<body>
<div>焚我残躯,熊熊烈火.生亦何欢,死亦何苦.为善除恶,惟光明故.喜乐悲愁,皆归尘土.怜我世人,忧患实多.怜我世人,忧患实多.</div>
<div>焚我残躯,熊熊烈火.生亦何欢,死亦何苦.为善除恶,惟光明故.喜乐悲愁,皆归尘土.怜我世人,忧患实多.怜我世人,忧患实多.</div>
</body>
</html>

jQuery 的 attr 和 prop

jQuery 中可以使用 attr 和 prop 获取的属性,它们的区别是什么呢?

The segregation of attr() and prop() should help alleviate some of the confusion between HTML attributes and DOM properties. $.fn.prop() grabs the specified DOM property, while $.fn.attr() grabs the specified HTML attribute.

HTML attributes 在网页的代码中可以看到,DOM properties 是内存数据,在 HTML 代码中看不到。attr 操作的是 HTML attributes, prop 操作的是 DOM properties,它们可以是重叠的,例如 img 的 src 既可以使用 attr 访问,也可以使用 prop 访问,但是 checkbox 的 checked 应该使用 prop 访问,attr 访问会有奇怪的问题。大多数时候 attr 和 prop 的效果都一样,但是访问 boolean 的属性时切记要使用 prop。

襁褓中的系统界面

是否还记得在开始的时候我们说过:

  • 给我一个 QPainter,我也能实现整个操作系统的图形界面
  • 操作系统的界面本质也是画(Hua)出来的
  • 图形界面的本质都是一样的,就是一张静态的画
  • 点击按钮,看到按钮动了

这里我们就用 QPainter 绘图来模拟实现一个系统的界面原型,为了简单说明问题,只绘制了 Button 和 CheckBox,其他的控件同理。当然 Button 是能够点击的,点击 CheckBox 也能够切换选中状态,没有点击到 Button 和 CheckBox 的时候它们不会接收到鼠标事件,点击一个控件也不会影响另一个控件,效果如下图:

异常处理

SpringMVC 提供了 3 种异常处理方法:

  1. 使用 @ExceptionHandler 注解实现异常处理
  2. 简单异常处理器 SimpleMappingExceptionResolver
  3. 实现异常处理接口 HandlerExceptionResolver,自定义异常处理器

通过比较,实现异常处理接口 HandlerExceptionResolver 是最合适的,可以给定更多的异常信息,可一自定义异常显示页面等,下面介绍使用这种方式处理异常。

服务器端参数验证

服务器端接收到前端传递过来的参数后,很多情况下如果不做校验就直接使用是非常危险的,例如造成 XSS 攻击,把无效数据保存到数据库导致业务出错等。传统的参数校验方式一般是取得每一个参数,然后一个一个的使用 if else 和规则比较进行校验,这样就会有大量的、简单重复的校验代码散布在代码中,不利于维护和阅读。这里介绍使用注解,根据 JSR-303 Validation 进行参数验证。

当我们在 SpringMVC 中需要使用到 JSR-303 的时候就需要我们提供一个对 JSR-303 规范的实现,Hibernate Validator 实现了这一规范,下面将以它作为 JSR-303 的实现来讲解 SpringMVC 对 JSR-303 的支持。

使用方法:

  • Bean 中使用 @NotNull 等定义验证规则
  • Controller 中使用 @Valid 进行参数校验
  • 如有参数错误,则返回错误信息给客户端

自定义类与 QVariant

QVariant 非常重要,可以存储很多种不同的类型,例如 int, QString, QRect, QPoint 等,其构造函数有很多个,参数是很多种不同的常用类型,还内置了可以直接转换 QVaraint 到某些类型的函数,如 toInt(), toString(), toPoint(), toSize() 等,还是 QObject 动态 property 机制的关键,除了支持内置的类型外,QVariant 还被设计成可以存储我们自己定义的类型。

关键术语:

  • Q_DECLARE_METATYPE
  • qRegisterMetaType
  • qRegisterMetaTypeStreamOperators
  • operator QVariant()

使用 LESS 代替 CSS

使用 Less 最直接的好处是可以使用层级的风格写样式,当然它还可以定义变量、函数等,比 CSS 好管理,而且语法和 CSS 差别不大,这里介绍 HTML 里使用 Less 代替 CSS:

方法一: less.js 解析 Less 文件为 CSS

  1. 写 Less 样式

  2. HTML 中引入 Less 文件

    1
    <link href="style.less" rel="stylesheet/less" type="text/css"/>
  3. HTML 中引入 less.js,它的作用是把上面引入的 Less 文件解析为 CSS,放到 HTML 的 head 中,less.js 会自动的把 Less 样式翻译为 css

    1
    <script src="https://cdn.staticfile.org/less.js/2.7.1/less.min.js"></script>

方法二: Atom 在保存 Less 文件时自动编译为 CSS 文件

使用 Atom 的插件 less-autocompile,在保存 less 文件时 Atom 会自动生成对应的 css 文件,那么就可以直接在 HTML 里引用 css 了,这样就不需要担心像上面这个方法造成的性能,网络访问等问题了,90% 的情况下没啥问题,只是理论上的问题而已。

需要在 Less 文件的第一行添加下面的内容,告诉生成文件的路径,规则等:

1
2
3
4
5
6
7
8
9
10
11
// out: style.css, sourcemap: false, compress: false
// 定义变量
@borderWidth: 2px;
@borderColor: #BBB;
.card {
padding: 10px;
border: @borderWidth solid @borderColor;
border-radius: 4px;
......

Semantic Ui Validation

Semantic Ui 自带了表单验证功能,使用起来很简单:

  • 可以使用预定义的校验函数
  • 可以自定义校验函数
    • 自定义校验函数里发送同步的 AJAX 请求使用服务器端进行参数校验
  • 可以自定义错误提示
  • 可以在 onSuccess 中禁止表单自动提交,然后使用 Ajax 提交

Semantic Ui Tab

Semantic Ui 中创建 Tab,需要 2 步:

  1. 定义 Tab 的 HTML 结构

    关键是 .item.tab 中的属性 data-tab 的定义,用于关联跳转

    有 class active 的 .item 和 .tab 是当前的 Tab,其他的隐藏

  2. JS 实现点击切换 Tab

Semantic Ui 侧边栏

侧边栏在页面打开后就一直存在需要使用 class visible,并且间隔和内容区域的间隔不要太大,可以使用 CSS 调整一下 pusher 的 translate3d 的位置,icon menu 的文本都是居中的,如果不想居中,也需要自己调整,效果如下

小程序 Tips

开发环境

腾讯官方提供的微信web开发者工具的编辑功能是至今见过最难用的编辑器之一,我们可以使用其他的编辑器例如 AtomSublimeText 等编辑,保存后微信web开发者工具会自动的发现文件变化了,然后自动刷新小程序,看到修改后的效果。

Spring Security 自动登录

前面的实现可以使用表单进行登陆了,但是某些时候需要自动登录,例如使用 QQ 的第三方登录,服务器收到登陆成功的回调后,需要在我们的系统中继续使用本地账号登陆才行,这时就会需要实现自动登录的功能,还有使用 AJAX 等也不能使用表单登陆,也是需要调用登陆的接口才可以。

为了提供登陆的接口,需要实现 AuthenticationProvider 进行登陆,不再使用 Spring Security 的默认实现。当然实现了自动登录后,并不会影响 Spring Security 的表单登陆。

实用正则表达式

Qt 里正则表达式使用 QRegularExpression,可以使用正则表达式查找字符串,QString 中可以使用正则表达式 QRegularExpression 进行字符串替换,拆分等。

Qt 显示 GIF

Qt 中,静态图片 PNG,JPG 等可以用其创建 QPixmap,调用 QLabel::setPixmap() 来显示,但是能够具有动画的 GIF 却不能这么做,要在 QLabel 上显示 GIF,需要借助 QMovie 来实现。

小程序轮播

微信小程序使用 Swiper 实现图片的轮播,Swiper 的结构如下:

1
2
3
4
5
6
7
8
9
<swiper indicator-dots="{{indicatorDots}}"
circular="{{circular}}"
autoplay="{{autoPlay}}"
interval="{{interval}}"
duration="{{duration}}">
<swiper-item><image src=""/></swiper-item>
...
<swiper-item><image src=""/></swiper-item>
</swiper>

swiper-item 的子标签可以是 image,或者 navigator 中放图片实现点击跳转等。


小程序滚动

微信小程序使用 scroll-view 实现滚动区域,有水平滚动和垂直滚动,scroll-view 的结构如下:

1
2
3
4
5
<scroll-view>
<view></view>
...
<view></view>
</scroll-view>

设置 WXSS 的时候,需要注意使用 font-size: 0 和 white-space: nowrap


QSS QCalendarWidget

QCalendarWidget 是一个比较复杂的 widget,由几个 QToolButton, QSpinBox, QMenu, QTableView 等组成,Qt 的帮助文档里没有其 QSS 的相关文档,当要修改其样式的时候应该怎么办呢?

我们这里采用的方法是分析组成 QCalendarWidget 的 widget 的 className 和 objectName,然后 QSS 每个 widget,最终达到修改 QCalendarWidget 样式的目的。

QSS Subcontrol

普通的 QSS 和 CSS 没什么区别,难度不大,但除此之外,想要使用好 QSS,还必须得掌握好 subcontrol,这个在 CSS 里没有,是 Qt 独有的。

什么是 subcontrol?一个复杂的 widget 由多个部分组成,它们可以是一个 widget,也可以是逻辑上的部件,例如 QCheckBox 由 icon 和 text 两个部分组成,不仅可以定义 text 的样式,还可以定义 icon 相关的样式,icon 部分就是 QCheckBox 的 subcontrol ::indicator

在 Qt 的帮助文档里有所有 subcontrol 的说明,但是相信很多人看了还是不明白每个 subcontrol 具体是什么,这一节将使用可视化的方式标记出 subcontrol,介绍使用 QSS 自定义有 subcontrol 的常用 widget,这里的重心是怎么去 QSS subcontrol 而不是样式效果,复杂漂亮的界面需要大量的图片和更多的 QSS,这里不作介绍,以免陷入细节,掩盖本节主题。只要知道了原理,结合已经掌握的 QSS,找美工提供一套界面切图,就能很容易实现出来很专业效果了。

Subcontrol 的 QSS 和大多数 widget 的差不多,也支持盒子模型,可以自定义 color, background, background-color, background-image, border, padding, margin, width, height 等,也支持 Pseudo-States。

Subcontrol 的绘制位置由 subcontrol-origin、subcontrol-position, top, left 来指定,就先从这几个属性开始入手。

Border Image

好多程序员有可能同时身兼数职,不仅要写代码,还要做美工,当 DBA,干运维,修电脑,搞得像孙悟空似的,什么都会变:


QSS 选择器

选择器决定了 style sheet 作用于哪些 widget,QSS 支持 CSS2 定义的所有选择器

QSS 的选择器有

  • 通用选择器 *
  • 类型选择器
  • 类选择器
  • ID 选择器
  • 属性选择器
  • 包含选择器
  • 子元素选择器
  • 伪类选择器
  • Subcontrol 选择器

很多时候,可以使用不同的选择器实现相同效果的样式,使用非常灵活。

盒子模型

每个 Widget 所在的范围都是一个矩形区域(无规则窗口也是一个矩形,只是有的地方是透明的,看上去不是一个矩形),像是一个盒子一样。QSS 支持盒子模型(Box Model),和 CSS 的盒子模型是一样的,由 4 个部分组成:content, padding, border, margin,也就是说,Widget 的矩形区域,用这 4 个矩形表示

  • content: 绘制内容的矩形区域(如绘制文本、图片),Qt 自带的 widget 都是在 content 区里绘制内容,这只是一个约定,只要你愿意,也可以在绘制到 padding, border, margin 区
  • padding: 内容区和边框之间的间隔
  • border: 边框,可视化的显示一个 widget 的逻辑范围,而不一定是 widget 所占矩形区域的实际大小
  • margin: 想像 widget 的矩形区域有一个隐形的边框,margin 就是 border 和这个隐形边框之间的间隔

QWidget 的 content, padding, border, margin 的矩形区域都是一样大的,也就是说,margin, border, padding 的值为 0,content 的矩形和 QWidget 的矩形一样大,但是 QPushButton 默认的 margin, border, padding 的值不为 0(可以试试 setFlat(true) 后再看看这几个值是什么)。

Margin,Border,Padding 都分为 4 个部分:上、右、下、左,它们的值可以不同:

加载 QSS

前一章节我们已经知道怎么写简单的 QSS 了,但是,应该把它们放在什么地方才能生效呢?

加载 QSS 有三种方式:

  1. Widget 的对象调用 setStyleSheet(qss) 函数加载 QSS,QSS 的作用域是 widget 自己和它的所有子 widget
  2. QApplication 的对象 setStyleSheet(qss) 函数加载 QSS,QSS 的作用域是整个程序里的所有 widget
  3. 在 Qt Designer 的 Change styleSheet... 打开的 QSS 编辑器中添加 QSS,在哪个 widget 上添加的,QSS 的作用域是那个 widget 自己和它的所有子 widget,其实和 1 是一样的,只不这里过是在 Qt Designer 里添加,不是我们自己手动写 C++ 代码添加而已。打开 ui 文件生成的代码(ui_xxxx.h),可以看到里面也是自动生成代码调用 setStyleSheet(qss) 添加 QSS 的,和我们写代码添加没有区别,只是在 Qt Designer 里添加的话,有时候方便一些,也可以实时看到 QSS 的效果

QSS 基础

如果你会 CSS,那么 QSS 对你来说将会非常简单,QSS 的语法和 CSS 的愈发非常相似,但也有些不同,有些 CSS 的东西在 QSS 里被去掉了,QSS 也加了些自己特有的东西,不过大多数还是差不多的,下面以修改 QLabel 的样式为例,学习 QSS 的基础语法。

QSS

Qt 提供的 widget 的默认外观很多时候都不符合项目的界面需求,必须要改,修改一个 widget 的外观(Look and Feel)有以下的方法:

  • 继承 Widget,然后在 paintEvent() 里绘制
  • 继承 QStyle
  • 使用 QSS(Qt Style Sheet)
  • 对于 item view 来说还有一种方式,还可以使用 Delegate

这几种方式里最简单灵活的是使用 QSS,虽然有人说 QSS 的效率低,具体有多低没测试过,但是在普通 PC 上从来没感觉出来,再说现在的硬件也不差这么点性能消耗,随便一个写的差点的函数的消耗就比这多的多,作为一个实用主义者,不追求理论上的效率完美,能满足需求的前提下什么好用用什么,QSS 就是修改 widget 外观的首选,什么效果不满意,修改一下 QSS 的文件就可以看到效果,甚至不需要重新编译、打包发布程序(如果把 QSS 放在文件中,并且实现动态加载 QSS)。

我们按下面的章节来介绍 QSS:

Perlin Noise

普通随机数算法生成的随机数真的很随意,不能做到在某个序列内是升序或者降序的,如果要生成某个范围内一序列的升序或者降序的随机数,例如用于游戏中生成随机地图,渲染海洋等,可以使用 Noise 随机数生成算法,Flow Noise 是 Noise 算法的 Java 实现,其 github 地址为 https://github.com/flow/noise#documentation

Noise generation library for Java, based on the libnoise C++ library. It is used to generate coherent noise, a type of smoothly-changing noise. It can also generate Perlin noise, ridged multifractal noise, and other types of coherent noise. https://flowpowered.com/noise

Https PKIX

使用 Gradle 下载 jar 包时,如果遇到 PKIX path building failed and unable to find valid certification path to requested target 错误,则说明是 ssl 证书的问题,把证书加入到 JVM 的 cacerts 文件即可(使用 Firefox 来下载网站的证书)

  1. 使用 Firefox 打开 https 的链接

  2. 点击地址栏中的小锁图标

  3. 点击 Security Connection 右边的向右箭头

  4. More Information > Security > View Certificate > Details > Export

  5. 例如上面 cert 文件保存为 repo1.maven.org.crt

  6. 打开终端,导入 cert 文件

    1
    sudo keytool -import -alias maven -keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/security/cacerts -file repo1.maven.org.crt

    cacerts 文件的路径和 JRE/JDK 安装的路径有关

  7. 输入 cert 文件的密码,默认的都是 changeit

  8. 重启系统

Qt5 乱码与 BOM

虽然 Qt5 强制要求源码必须使用 UTF-8 的编码,但是仍然会遇到乱码问题,例如在 MinGW 下编译没问题的工程使用 VS 的编译器编译时有可能报错,MinGW 编译的程序运行时中文正常显示,但是 VS 环境下却是乱码等,下面来解决 Qt5 的乱码问题。

数据库访问工具 DBUtl

数据库访问工具 DBUtil

DBUtil 用于简化数据库的访问,只要准备好配置文件,调用 DBUtil 的静态函数就能直接得到查询数据库的结果。

本文主要内容有:

  • 数据库访问的思考
  • DBUtil 实例
  • DBUtil 的 API
  • DBUtil 的实现
  • 把 SQL 语句放到文件里
  • ORMapping

数据库连接池

在前面的章节里,我们使用了下面的函数创建和取得数据库连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void createConnectionByName(const QString &connectionName) {
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", connectionName);
db.setHostName("127.0.0.1");
db.setDatabaseName("qt"); // 如果是 SQLite 则为数据库文件路径
db.setUserName("root"); // 如果是 SQLite 不需要
db.setPassword("root"); // 如果是 SQLite 不需要
if (!db.open()) {
qDebug() << "Connect to MySql error: " << db.lastError().text();
return;
}
}
QSqlDatabase getConnectionByName(const QString &connectionName) {
return QSqlDatabase::database(connectionName);
}

虽然抽象出了连接的创建和获取,但是有几个弊端:

  • 需要我们维护连接的名字
  • 获取连接的时候需要传入连接的名字
  • 获取连接的时候不知道连接是否已经被使用
  • 使用多线程的时候,每个线程都必须使用不同的连接,我们必须保证同一个连接不能在多个线程里被同时使用
  • 控制创建连接的数量比较困难,因为不能在程序里无限制的创建连接
  • 连接断了后不会自动重连
  • 删除连接不方便

数据库常用操作

前面的章节介绍了怎么使用 Qt 连接访问数据库 SQLite 和 MySQL,在这一节里将介绍访问数据库的常用操作,主要内容有:

  • QSqlDatabase
  • 查询
    • 使用 Prepared Query 查询
    • SQL 注入
    • 使用 LIKE 模糊查询
    • 解决列名冲突
  • 更新
  • 删除
  • 事务

访问 MySql

Qt 里访问 MySQL 是件很简单的事,但也有可能很不简单。说其简单是因为熟悉的人都知道只需要有 MySQL 的驱动插件和复制 MySQL 的动态链接库到程序可识别的环境变量的路径下即可,对于不熟悉的人来说,看到 Qt 给我们的错误信息简单到 QMYSQL driver not loaded,从这一句简单的提示里却找不到任何头绪,不知道应该怎么做,在网络上搜索了很多文章,照着做有的人问题解决了,有的人试过了很多种方法问题仍然存在。在这一节里,我们会一步一步的来分析,解决访问 MySQL 的问题。访问其他数据库也可以用同样的方法解决。

访问 SQLite

SQLite 是一个开源的嵌入式关系数据库,实现自包容、零配置、支持事务的SQL数据库引擎。 其特点是高度便携、使用方便、结构紧凑、高效、可靠。整个数据库(定义、表、索引和数据本身)都在宿主主机上存储在一个单一的文件中。SQLite 支持跨平台,同一个 SQLite 的数据库文件,可以在 Windows,Linux,Mac OS 中使用。SQLite 的使用非常广泛,例如 Firefox,Chrome,Android,iOS 等都使用了 SQLite 来存储数据。SQLite 默认不支持使用用户名密码来连接,如果我们的数据安全性要求不高,那么就可以使用 SQLite 来存储,这样做的好处是数据文件是一个单一的文件,可以和项目一起发布而不需要安装数据库软件就能拥有数据库的功能,这是多么美好的事(想像一下,如果需要模拟数据库的操作,手动用程序通过读写文件的方式向一个文件中插入,更新,删除一条记录需要付出多少的代价,而用 SQLite 的话,就是一条 SQL 语句的事情,用上 SQLite,这简直是鸟枪换炮了啊)。

Qt 默认已经提供了 SQLite 的驱动,直接在代码里就可以访问 SQLite。在 plugins/sqldrivers 目录下可以找到 SQLite 的数据库驱动插件。

数据库

Qt 是用 C++ 实现的,那么 C,C++ 访问数据库的方式也同样适用于 Qt,如要访问 MySQL,需要相应的头文件和库文件(例如 Windows 下的 dll 或者 lib,Linux 下的 .so 或者 .a),如果你安装了 MySQL,会在安装目录下找到这些库文件,如果没有安装,也可以在网上找到,在代码里 #include "MySQL Headers",然后用提供的函数访问 MySQL。访问 Sqlite,Oracle 等任何一个数据库,都和访问 MySQL 差不多,需要它们自己的头文件和库文件,使用它们提供的函数访问数据库。

组播

夜深人静,一个小黑屋子里发出神秘的嘀嘀嘀……嗒嗒嗒……嘀嘀嘀…… 的声音,谍战剧必不可少的画面

Multicast 和发电报有相似之处,一条消息发送给多个既定的目标群体。

广播

“安红,额想你” 这句话曾经红的一塌糊涂,以至于人们只记住了这句话而忘了出自这句话的电影《有话好好说》:张艺谋骑着个破三轮出场,用陕西话喊:“安红,俺想你,想你想得想睡觉”:

注意,老谋子手里有个大喇叭,对着喇叭一喊,那就炸了锅了,周围的人都听到了。Broadcast 和用喇叭喊话很相似,消息一发送,就能被同一个局域网里的所有电脑收到。对着喇叭喊话对应于发送 Broadcast 消息,周围对应于局域网,声音的传递是有范围的,Broadcast 的消息也只能是局域网内被收到。

Clip 实现复杂绘图效果

经常会遇到有人问,用 Pixmap 绘制图像已经知道了,但是怎么绘制一个圆的图像呢?就像 QQ 头像那样,即使上传的头像是一个矩形的,但是显示出来的效果是圆形的,也就是说,在圆的范围内绘制图像,图像超过圆范围的部分不绘制,就像下图的效果

单播

使用 Unicast 的时候,Source 一次只能给一个 Destination 发送消息,不能同时发给多个 Destination,如果 Source 要给多个 Destination 发一条内容相同的消息,就必须单独地分别给每个 Destination 发送这条消息。

UDP 编程

UDP 是 User Datagram Protocol 的简称,中文名是用户数据报协议,是一种无连接的不可靠协议,也既是说,数据发出去了,但是不能保证对方一定能接收到,就像寄信一样,虽然把信交给了邮局,中途有可能寄丢。

UDP 报文没有可靠性保证、顺序保证和流量控制字段等,可靠性较差。但是正因为 UDP 协议的控制选项较少,在数据传输过程中延迟小、数据传输效率高,适合对可靠性要求不高的应用程序,如果既要使用 UDP 高效的同时也想要保证数据的可靠性,那么就需要在应用程序中对收发的数据进行验证,这样的程序有很多,例如 DNS、TFTP、SNMP 等。

网络编程

网络通讯无处不在

  • 用 QQ 和朋友聊天,发文字,发图片,发文件,语音聊天,视频聊天,然而,对方的 QQ 是怎么接受到我们发送的数据呢?
  • 用浏览器上网,网页上的图片,JavaScript 文件,CSS 等是怎么下载来的呢?
  • 使用 FTP 工具下载服务器上的文件
  • 用 BT 软件下载电影
  • 在线看电影,听歌
  • 玩网络游戏
  • 软件在线升级

用到网络通讯的地方太多太多,举不胜举,如果没有网络,电脑不能上网,手机不能上网,不能玩网络游戏,不能淘宝,不能在 12306 买火车票,不能百度,不能在线学习,不能用百度地图,不能团购,不能……,这样的世界将会是多么的无趣!

网络编程大家应该都听过 UDP,TCP,也许你还知道如 FileZilla 是用 FTP 传输协议来上传下载文件的,浏览器使用 HTTP 协议和服务器交互,BT 软件使用 P2P 协议下载文件,QQ 有自己的通讯协议,不同的软件很有可能定制了自己的传输协议,从网络上搜素,可以看到各种各样的协议,还有程序之间使用 MQ 通讯,网络通讯的库 Netty,Mina,高性能 TCP/UDP Socket 组件 HP-Socket,也许看到别人介绍网络编程时可能用到 FlatBuffers 等。

看的头都大了,我就是想写个小程序,在 2 台电脑之间发个文字消息,难道要了解上面这些各种各样的东西?吓得赶快去写个单机程序压压惊!

其实上面说的形形色色的东西,他们底层数据的传输都只是用了 2 个东西:UDP 和 TCP,完成上面发消息的这个小程序,只要了解基础的 UDP,TCP 编程就够了。所谓的通讯协议如 HTTP,FTP,P2P 等没有什么神秘的,他们定义的只不过是一个数据结构,某几个字节表示的是什么数据,有的数据项多些,有的数据项少些而已,数据按照通讯协议组织后使用 UDP 或者 TCP 来传输,对方接收到数据后,根据协议定义的格式解析数据,协议就是网络通讯中用的语言,只有语言通了,才能交流,否则就是鸡同鸭讲,不知所谓。

网络编程我们会介绍 UDP,TCP 基础编程,自定义通讯协议,使用 FlatBuffers 简化数据的序列化和反序列化,还会介绍使用 MQ 在不同的语言编写的程序之间进行网络通讯。

单例的其他实现

单例还有没有其他的实现方式?
有,我们的实现,使用的就是传说中的懒汉方式,还有饿汉方式,实现也挺简单的

1
2
3
4
5
6
7
1. 把单例的简单实现中的
static QMutex mutex;
static ConfigUtil *instance; // ConfigUtil 全局唯一的变量
换成
static ConfigUtil instance;
2. getInstance() 直接返回这个 instance 即可,还没有线程同步的问题

此外,还可以直接在 getInstance() 函数里定义一个静态的 ConfigUtil 变量(具体的可以参考 Qt 的宏 Q_GLOBAL_STATIC 的实现)。

单例的智能指针+宏的实现

如果要创建一个单例的数据库连接池 ConnectionPool,那么实现单例部分的代码和 ConfigUtil 的几乎一样,声明 private 的构造函数,拷贝构造函数,析构函数,赋值操作符,QScopedPointer instance,friend struct QScopedPointerDeleter,几乎完全一样的 getInstance() 等,这些代码几乎都是重复的,每个单例的类这些内容都重复一遍,违背了代码的复用原则。为了实现代码复用,可以用继承、函数、宏定义、模版等。对于单例,继承很难达到目的,这里我们选择使用宏来实现代码复用的目的,后面的章节会介绍使用模版的实现。

单例的智能指针实现

前面提出了一个问题:可不可以不需要我们手动的调用 release() 函数,程序结束前自动的删除单例类的对象呢?答案是可以,使用智能指针可以达到这个目的,这里我们使用的是 Qt 的 QScopedPointer 来实现,也可以使用标准的 C++ 的智能指针。

单例的简单实现

用简单直观的方式来实现一个单例的类 ConfigUtil,这里不使用宏,模版等技术,先了解实现一个单例类的理论知识,然后在此基础之上进行思考,优化,最终让我们的实现真正的达到实用的目的,而不只是功能上可用,但是质量却很不好。

实现单例时,需要注意以下几点:
  • C++ 的书里经常强调:一个类,至少要提供构造函数拷贝构造函数析构函数赋值运算操作符,尤其是有成员变量是指针类型,保存指针的数组或集合时更是需要注意(实现深拷贝)
  • 要限制创建和删除 ConfigUtil 的对象
    • 构造函数定义为 private 的,是为了防止其他地方使用 new 创建 ConfigUtil 的对象
    • 析构函数定义为 private 的,是为了防止其他地方使用 delete 删除 ConfigUtil 的对象
    • 拷贝构造函数定义为 private 的,是为了防止通过拷贝构造函数创建新的 ConfigUtil 对象
    • 赋值运算操作符定义为 private 的,是为了防止通过赋值操作创建新的 ConfigUtil 对象
  • 通过 ConfigUtil::getInstance() 获取 ConfigUtil 的对象
  • 当程序结束的时候调用 ConfigUtil::release() 删除它的对象,否则会造成内存泄漏。虽然程序结束了,内存会被系统回收,但是理论上还是要保证谁分配的内存谁回收

单例

单例的意图是为了保证一个类只能创建一个对象(栈对象或者堆对象都可以),并提供访问它的唯一全局访问点。只需要一个对象的场景,比如数据库连接池、线程池、系统日志的输出、读写配置等。

为了保证一个类只有一个对象,那么就不能随便地创建和删除这个类的对象。如果它的构造函数是 public 的,那么就可以随意地创建它的对象了,所以它构造函数不能是 public 的。如果它的析构函数是 public 的,那么也可以调用 delete 删除它的对象。所以对于要使用单例的类,它的构造函数和析构函数我们都定义为 private 的,同时要防止通过拷贝构造函数和赋值操作创建对象。

先思考一下,什么情况下能访问一个类的 private 成员变量和成员函数?单例的实现需要用到这个知识点。

Qt Tips

无边框对话框不在任务栏显示图标

1
2
QDialog *dlg = new QDialog(NULL, Qt::Dialog | Qt::Popup | Qt::FramelessWindowHint);
dlg->show();

Qt::Popup 是个小技巧,只有先点击对话框后才能点击对话框后面的 widget

使用 QChart 显示实时动态曲线

实时动态曲线 一节介绍了使用算法实现实时动态曲线,Qt 5.7 后提供了 charts 模块,使用 QSplineSeries 就能很轻松的实现平滑曲线了,而且效果很好,但是需要注意一点的是,免费版的 Qt 中 charts 模块是 GPL 协议的。

效果如下,随着时间变化,曲线会从右向左移动

实时动态曲线

在群里经常有朋友问:不停的从下位机,传感器接收到数据,怎么实时的把这些数据的曲线画出来?就像 Windows 的任务管理器 CPU 监控的动态曲线那样,曲线从左向右移动。

先分析一下这个问题:
  • 接收数据:与设备有关,不同的设备接收数据的方式不一样,有的用串口,有的用 TCP,UPD 等,不过这不是本章的重点,我们会用生成随机数模拟从设备接收到数据。
  • 随着程序运行的时间越来越长,接收到的数据从开始的几个到几百个,几千个,几万甚至几十上百万个,难道要把所有的数据都要显示出来?不需要,只要把最后接收到的例如 100 个数据显示出来就可以了。
  • 曲线怎么才能动起来?以只显示 100 个最新数据为例,存放在链表里,假设链表已经存满 100 个数据,当接收到一个新的数据时,把它放到链表尾部并删除链表的第一个数据,这样就保证了链表存储的都是最新的 100 个数据,前一次的 100 个数据里下标为 1 到 99 的数据和后一次数据里下标为 0 到 98 的数据是一样的,用他们绘制出来的 2 个曲线,后一次数据的曲线就像前一次数据的曲线向左移动了一点一样,这个过程不停的发生,曲线看上去就动起来了。

绘制平滑曲线

得到曲线上的点,画出曲线,这是一个很常见的需求。画曲线嘛,当然难不住我们,用 QPainter::drawLine() 把曲线上的点连起来不就好了?So easy,轻轻松松搞定,开开心心的交任务去了。

正在聚精会神炒股的老板一瞅,气不打一处来:“你这画的是什么鬼,这个线直来直去的,太不专业了”,抬头指着屏幕上的炒股软件,瞅着迷离的眼神:“看看人家的这个曲线,就像少女的皮肤般那么的柔顺、平滑”,口气马上一百八十度大转弯:“在看看你的,像八十岁老头的那样全是褶皱!” 擦完脸上的口水,赶快想办法去吧。

用画家的思维绘制图形

曾经遇到一个这样的需求:使用 代码 实现下面样式的按钮

观察按钮的样式,发现有多种渐变、有高光、有阴影、有不规则形状、有半透明效果,看到这么复杂的效果图,我的第一反应是用 Photoshop 做出效果图,使用 QSS 设置为按钮的背景图就可以了。但是,客户就是上帝,上帝说要用代码,那就用代码。有没有什么办法能直接绘制出这样的图形来呢?翻遍了 Qt 的帮助文档,没有,Qt 只提供了一些绘制简单图形的函数,如点、线、图片、填充、渐变、融合等,没有直接实现这种复杂需求的函数,那么,怎么用 QPainter 绘制出这样的图形呢?

Pixmap

Pixmap 的绘制有下面四种方式(每种方式都有几个重载的函数,没有全部列举出来):

  1. 在指定位置绘制 pixmap,pixmap 不会被缩放

    1
    2
    3
    /* pixmap 的左上角和 widget 上 x, y 处重合 */
    void QPainter::drawPixmap(int x, int y, const QPixmap & pixmap)
    void QPainter::drawPixmap(const QPointF &point, const QPixmap &pixmap)
  2. 在指定的矩形内绘制 pixmap,pixmap 被缩放填充到此矩形内

    1
    2
    3
    /* target 是 widget 上要绘制 pixmap 的矩形区域 */
    void QPainter::drawPixmap(int x, int y, int width, int height, const QPixmap &pixmap)
    void QPainter::drawPixmap(const QRect &target, const QPixmap &pixmap)
  3. 绘制 pixmap 的一部分,可以称其为 sub-pixmap

    1
    2
    3
    4
    5
    /* source 是 sub-pixmap 的 rectangle */
    void QPainter::drawPixmap(const QPoint &point, const QPixmap &pixmap, const QRect &source)
    void QPainter::drawPixmap(const QRect &target, const QPixmap &pixmap, const QRect &source)
    void QPainter::drawPixmap(int x, int y, const QPixmap &pixmap,
    int sx, int sy, int sw, int sh)
  4. 平铺绘制 pixmap,水平和垂直方向都会同时使用平铺的方式

    1
    2
    3
    4
    5
    6
    void QPainter::drawTiledPixmap(const QRect &rectangle,
    const QPixmap &pixmap,
    const QPoint &position = QPoint())
    void QPainter::drawTiledPixmap(int x, int y, int width, int height,
    const QPixmap & pixmap,
    int sx = 0, int sy = 0)

    drawTiledPixmap() 比我们自己计算 pixmap 的长宽,然后重复的绘制实现平铺的效率高一些:Calling drawTiledPixmap() is similar to calling drawPixmap() several times to fill (tile) an area with a pixmap, but is potentially much more efficient depending on the underlying window system.

使用上面这张图来演示 drawPixmap() 的各种用法,左上角绘制原始大小的 pixmap,右上角缩放绘制 pixmap 到指定的矩形内 QRect(225, 20, 250, 159),中间绘制 sub-pixmap,底部则使用平铺的方式绘制,最后结果如下图(文字是标记上去帮助理解的):

1
2
3
4
5
6
7
8
9
10
11
12
void PixmapWidget::paintEvent(QPaintEvent *) {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QPixmap pixmap(":/img/Butterfly.png"); // 从资源文件读取 pixmap
painter.drawPixmap(20, 20, pixmap); // 按原始尺寸绘制 pixmap
painter.drawPixmap(225, 20, 250, 159, pixmap); // 缩放绘制 pixmap
painter.drawPixmap(20, 133, pixmap, 128, 0, 57, 46); // 绘制 pixmap 的一部分
painter.translate(0, 199);
painter.drawTiledPixmap(0, 0, width(), height(), pixmap);
}

QPainter 的状态保存与恢复

实现这样的一个程序,把 QPainter 的坐标原点从左上角移动到 (100, 100),然后画出坐标轴,接下来顺时针旋转坐标轴 45 度,设置画笔,画刷,字体,画一个矩形和字符串,最后恢复 QPainter 到最开始的状态,即还原画笔,画刷,字体,逆时针旋转坐标轴 45 度,移动 QPainter 的坐标原点到左上角,再画一个矩形和字符串,就像下图这样:

画刷

画刷 QBrush 是用来填充图形用的

The QBrush class defines the fill pattern of shapes drawn by QPainter.
A brush has a style, a color, a gradient and a texture.

下图在来自 QBrush 的帮助文档内容,列出了 Qt 自带的 brush:

画刷还可以使用 QPixmap,渐变等来创建。

平铺绘制 QPixmap 可以使用 QPainter::drawTiledPixmap(),也可以像下面这样使用 texture pattern 实现平铺绘制:

1
2
3
4
5
6
7
8
9
void MainWidget::paintEvent(QPaintEvent *) {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QPixmap pixmap(":/resources/Paint-Base-Bufferfly.png");
QBrush brush(pixmap);
painter.setBrush(brush);
painter.drawRect(0, 0, width(), height());
}

蚂蚁线

蚂蚁线是一个典型的 QPen 自定义 style 的应用,这里将介绍怎么使用 Qt 实现蚂蚁线。

QPen 已经提供了一些默认的 style,如 SolidLine, DashLine 等,但是满足不了所有的需求,所以还提供了自定义 style 的接口 QPen::setDashPattern(),其参数是一个 QVector,vector 中下标为偶数的位置存储 dash 的长度,奇数位置存储空白的长度,如 vector 的数据为 [3, 4, 9, 4](偶数个元素)表示:画线时以 3 个 dash 开始,接着是4 个空白,接下来是 9 个 dash,4 个空白,此时 vector 的元素已经用完,则从头开始使用 vector 的元素,接着画 3 个 dash,4 个空白,9 个 dash,4 个空白,依此类推。

画笔

画笔 QPen 用来绘制轮廓,和画笔相关也有很多概念,要理解好画笔也需要下很多工夫的,先看个简单的例子,直观的理解一下什么是 Cap Style, Join Style, Pattern 等上图。:

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
void MainWidget::paintEvent(QPaintEvent *) {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.translate(50, 50);
QPainterPath path;
path.lineTo(100, 100);
path.quadTo(200, 100, 200, 0);
QPen pen1(Qt::darkGray, 20, Qt::SolidLine, Qt::RoundCap, Qt::MiterJoin);
painter.setPen(pen1);
painter.drawPath(path);
painter.drawRect(250, 0, 200, 100);
// 自定义 dash pattern
QPen pen2;
QVector<qreal> dashes;
qreal space = 4;
dashes << 3 << space << 9 << space << 27 << space;
pen2.setDashPattern(dashes);
painter.translate(-30, -30);
painter.setPen(pen2);
painter.drawRect(0, 0, 510, 160);
}

贝塞尔曲线

QPainterPath 可以用来画贝塞尔曲线,什么是贝塞尔曲线呢?开始学的时候,经常听到贝塞尔曲线,但一直不知道是什么东西,很神秘的样子,据说很复杂,一直没敢学,人类对陌生的东西总是有恐惧感,这一部分就来揭开贝塞尔曲线神秘的面纱(大部分内容都来自于网络)。

贝塞尔曲线(The Bézier Curves),是一种在计算机图形学中相当重要的参数曲线(3D的称为曲面)。贝塞尔曲线于1962年,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所发表,他运用贝塞尔曲线来为汽车的主体进行设计。

一般的矢量图形软件通过它来精确画出曲线,贝塞尔曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。贝塞尔曲线是计算机图形学中相当重要的参数曲线,在一些比较成熟的位图软件中也有贝塞尔曲线工具,如PhotoShop等。

绘制路径

路径 QPainterPath,先引用一段 Qt 帮助文档里对路径的描述吧:

A painter path is an object composed of a number of graphical building blocks, such as rectangles, ellipses, lines, and curves. Building blocks can be joined in closed subpaths, for example as a rectangle or an ellipse. A closed path has coinciding start and end points. Or they can exist independently as unclosed subpaths, such as lines and curves.

A QPainterPath object can be used for filling, outlining, and clipping.

也就是说,路径可以由多个图形组成,例如矩形、椭圆、线、曲线、贝塞尔曲线等,一个路径可以和另一个路径合并,也可以从一个路径里扣掉另一个路径,路径可以用来创建复杂的图形,也可以用来限制绘图的区域实现特殊的效果等。

绘制文本

绘制文本非常常见,QPushButton,QLabel,QTableView 等等都得用,看似简单,其实里面有很多的学问,要掌握好还是挺不容易的。

绘图基础

这一节介绍 Qt 的绘图基础知识,我们都知道,Qt 里绘图使用的是 QPainter,但是首先需要弄明白:在什么上绘图和在哪里绘图,然后才是怎么绘图,我们就围绕这几个问题来展开。

在什么上绘图

The QPaintDevice class is the base class of objects that can be painted on with QPainter.

A paint device is an abstraction of a two-dimensional space that can be drawn on using a QPainter. Its default coordinate system has its origin located at the top-left position. X increases to the right and Y increases downwards. The unit is one pixel.

The drawing capabilities of QPaintDevice are currently implemented by the QWidget, QImage, QPixmap, QGLPixelBuffer, QPicture, and QPrinter subclasses.

上面的内容来自于 Qt 的帮助文档,在 QPaintDevice 的子类里用 QPainter 绘图,最常见的就是在 QWidget, QPixmap, QPixture, QPrinter 上面绘图。

绘图

阿基米德说:给我一个支点,我将撬起整个地球。给我一个 QPainter,我也能实现整个操作系统的图形界面,看似有点夸张,但说明了一个核心问题,操作系统的界面本质也是画(Hua)出来的。你看到的,未必就是真实存在的,也可以说根本不存在什么按钮、Label 等,他们都是一幅画,有很多人认为按钮就是按钮,Label 就是 Label,是不同的控件,不能互相转换使用,其实我很多时候就会把按钮当 Label 用,例如要求显示左边是图标,右边是文字,不可点击,如果用 Label 来实现就比较麻烦,但用 QPushButton 来做的话很容易,只不过把点击事件给忽略就好,处理 Label 的鼠标点击事件是不是就可化身为按钮了呢?别说按钮等了,其实就是我们生存的空间,都不知道是否真实的存在,还是我们就生活在外星文明的游戏里,谁能说得清呢!

安装 CAS-Server

CAS 是中央认证服务 Central Authentication Service 的简称,最初由耶鲁大学的 Shawn Bayern 开发,后由 Jasig 社区维护,经过十多年发展,目前已成为影响最大、广泛使用的、基于 Java 实现的、开源 SSO 解决方案。

2012 年,Jasig 和另一个有影响的组织 Sakai Foundation 合并,组成 Apereo。Apereo 是一个由高等学术教育机构发起组织的联盟,旨在为学术教育机构提供高质量软件,当然很多软件也被大量应用于商业环境,譬如 CAS。目前 CAS 由 Apereo 社区维护。

CAS Server 的安装并不是很容易,因为官网不再提供二进制包下载,需要我们下载 CAS 源码并自己编译,最新版的 CAS 使用 Gradle 编译,下面介绍 CAS Server 的安装

Tomcat 启用 https

开发时需要用到 SSO 单点登录,一般都会用 Jasig 的 CAS,而 CAS Server 默认要求它运行的服务器启用 https(CAS Client 不强制要求使用 https),所以就有必要在 Tomcat 中启用 https 了。下面介绍在开发环境中自己生成 ssl 的证书,并在 Tomcat 里启用 https。

集成自定义类型到 MetaType 系统

Qt 提供的类型如 QString, QSize 和 QColor 等可以存储在 QVariant 里,并且作为 QObject 及其子类的属性使用,例如调用 obj.setProperty("name", QString("道格拉斯·狗")), obj.property("name"),也可以在信号槽中作为参数传递。我们自定义的类如果也想要这么使用,需要使用 Q_DECLARE_METATYPE 和 qRegisterMetaType 来注册后才行。

ActiveMQ 的 MQTT

MQTT 有很多开源的实现,org.fusesource.mqtt-client 是其中之一,使用起来也比较简单,Github 地址为 https://github.com/fusesource/mqtt-client,而且其实现了连接断开后自动重连的机制

下面的程序使用 MQTT-Client 实现:

  • Publisher
  • Subscriber
  • PublisherAndSubscriber
  • FutureMqttPublisher

实际使用中,即要向 ActiveMQ 发送消息,同时也要从 ActiveMQ 订阅消息,所以参考下面的 Demo PublisherAndSubscriber 更为实用.

OkHttp

在 Java 平台上,我们一般使用 Apache HttpClient 作为通常的 HTTP 客户端。Square 公司开源的 OkHttp 是一个更先进的专注于连接效率的 HTTP 客户端。OkHttp 提供了对 HTTP/2 和 SPDY 的支持,并提供了连接池,GZIP 压缩和 HTTP 响应缓存功能。OkHttp 的 API 接口也更加的简单实用。可以将 OkHttp 作为 Apache HttpClient 的升级与替换,本文将对其进行详细的介绍,更详细内容请参考 https://www.ibm.com/developerworks/cn/java/j-lo-okhttp

Spring 集成 Groovy

Groovy 是 用于 Java 虚拟机的一种敏捷的动态语言,很吸引人的一点是 Java 代码完全可以在 Groovy 里运行,Groovy 脚本可以动态的加载运行,例如网络游戏,为了游戏的平衡,每运行一段时间就会调整角色的攻击力计算公式(C, C++写的游戏一般会用 Lua 最为计算脚本),如果是写在 Java 代码里,每次更新都要把主程序重新编译发布,代价是很大的,但是如果使用 Groovy 来提供计算,则只需要更新发布 Groovy 脚本(文本文件) 即可,估计也就是几 K。

Ehcache

Ehcache 使用起来很简单,是一个 key/value 容器,和 Map 很像,只不过功能比 Map 丰富,能够限定缓存中元素的个数,自动删除超时的元素,把元素持久化到硬盘等:

  • 添加元素到缓存: cache.put(new Element("username", "Biao"))
  • 从缓存获取元素: cache.get("username")
  • 从缓存删除元素: cache.remove("username")
  • CacheManager 管理多个 Cache,每个 Cache 里又管理多个缓存的元素 Element

FlatBuffers 入门之 Java + Qt 版

FlatBuffers 是一个 Google 开源的跨平台序列化工具,支持 C, C++, C#, Go, Java, JavaScript, PHP, Python 等,好强大的感觉。

FlatBuffers is an efficient cross platform serialization library for C++, C#, C, Go, Java, JavaScript, PHP, and Python. It was originally created at Google for game development and other performance-critical applications.

什么时候使用 FlatBuffers?

  • FlatBuffers 的反序列化速度非常快,在交互很频繁的场景下,例如游戏服务器中它的优势非常大
  • 使用 TcpSocket 通讯时序列化消息的 payload,尤其时涉及到多语言,多平台时

Gradle 修改 Maven 仓库

Gradle 的默认仓库在国内下载太慢了,可以切换到国内的 Maven 镜像仓库,如阿里的 Maven 库,又或者是换成自建的 Maven 私服。

一个简单的办法,修改项目的 build.gradle,将 jcenter() 或者 mavenCentral() 替换掉即可:

1
2
3
4
5
allprojects {
repositories {
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
}
}

Spring O/X Mapping

Spring 3.0 的一个新特性是 O/X Mapper,下面简称 OXM,O 代表 Object,X 代表 XML,它的目的是实现 Java POJO 对象和 XML 文档之间的互相转换。

Spring O/X Mapper 定义统一的接口,实现由第三方框架提供。要使用 Spring 的 O/X 功能,您需要一个在 Java 对象和 XML 之间来回转换的库,Castor 就是这样一个流行的第三方工具,本文将使用这个工具,其他这样的工具包括 XMLBeans、Java Architecture for XML Binding (JAXB)、JiBX 和 XStream。

Spring Event

JMS 可用于在不同应用之间通讯,而应用系统内部对象之间的通讯可以使用 Spring Event 来实现(JMS 也能实现同样的效果)。

Spring Event 的关键对象为 事件事件发送者事件监听器:

  • 事件类需要继承 ApplicationEvent
  • 事件发送者需要实现接口 ApplicationEventPublisherAware,Spring 容器在创建事件发送者对象时因为发现它实现了接口 ApplicationEventPublisherAware,就会自动地注入 applicationEventPublisher,这个对象是真正的用于发送事件的对象
  • 事件监听器需要实现接口 ApplicationListener,在 Spring 容器里注册事件监听器(其实就是生成一个对象,Spring 会自动识别它是否为事件监听器)

JMS + ActiveMQ

JMS 的全称是 Java Message Service,即 Java 消息服务,ActiveMQ 实现了 JMS 的接口。它主要用于在生产者和消费者之间进行消息传递,生产者负责产生消息,而消费者负责接收消息(生产者和消费者可以在同一个应用中,也可以不在同一个应用中)。应用到实际的业务需求中的话我们可以在特定的时候利用生产者生成一消息,并进行发送,对应的消费者在接收到对应的消息后去完成对应的业务逻辑。一个典型的应用例如用户注册后需要发送验证邮件,因为发送邮件是一个耗时任务,如果在注册的逻辑代码中发送邮件的话系统的响应就会很慢,可以在用户注册后立即返回,并把发送邮件的任务通过 JMS 发送到消息队列中,然后另一个专门负责发送邮件的服务从 MQ 里获取发送邮件的消息发送邮件。

如果是同一个程序里通讯的话,可以使用 Spring Event。

消息有两种类型:

  • 点对点: 一个消息只能被一个消费者接收处理
  • 发布/订阅模式: 一个消息能同时被多个消费者接收处理

消息生产者使用步骤:

  • 配置 ConnectionFactory
  • 配置 Destination,也就是队列
  • 创建消息生产者对象
  • 发送消息

消息消费者使用步骤:

  • 配置 ConnectionFactory
  • 创建消息消费者对象
  • 配置消息监听容器
  • 有消息到达时消息的消费者的 onMessage() 方法会被自动调用

程序运行前当然要先启动 ActiveMQ

Markdown 入门

掌握以下几个标签就能使用 Markdown 随意的写出漂亮的文档了,而且可以导出为 HTML, PDF 等給其他人看:

  • 标题: #
  • 加粗: **
  • 斜体: *
  • 代码: ```
  • 列表: 1. *
  • 链接: [](URL) [文字](URL)
  • 图片: ![]()
  • 引用: >
  • 换行: 2 个空格 或者 <br>
  • 表格:

JNDI 数据源

本文基于 Spring 的 JdbcTemplate 来介绍使用 JNDI 数据源。

JNDI 有两种配置方式:

  • 全局 JNDI 配置 - 在 <tomcat>/conf/server.xml 里配置,所有项目都能使用
  • 局部 JNDI 配置 - 有两种方式,只有配置此 JNDI 的项目自己能使用
    • META-INF/context.xml 里配置
    • <tomcat>/conf/Catalina/localhost/<projectRelated>.xml 里配置

Annotation - 注解

注解的目的是给类,属性,参数等提供一些元数据信息,例如标记类的用途(都是静态的数据,写程序的时候写死在代码里)。

注解有 4 个类型,最常用的是 @Target@Retention:

  • @Target - Annotation 修饰的对象范围,即被修饰的对象可以用在什么地方,常用的有类、接口、属性和参数
    • ElementType.TYPE - 类、接口
    • ElementType.FIELD - 属性
    • ElementType.PARAMETER - 参数
  • @Retention - 比较常用的是 RetentionPolicy.RUNTIME,运行时通过反射取得注解的元数据
  • @Documented
  • @Inherited

Qt 应用程序的图标

设置应用程序的图标,只需要修改 .pro 文件:

  • Mac 使用 icns 图标: ICON = AppIcon.icns
  • Windows 使用 ico 图标: RC_ICONS = AppIcon.ico

图标和 .pro 文件在同一个目录即可

qmake 时复制文件

有时在编译前需要准备一些文件,例如修改了 QtCreator 的编译输出目录: Build & Run > Default build directory,使用 Promote 后需要在编译前把相应 Widget 的头文件复制到 .o 文件所在的目录,这时就可以在 .pro 文件中使用复制文件的命令(其实就是执行系统命令),让 qmake 执行这些命令来复制文件,而不是手动的复制需要的文件。

可以使用 qmake 时能执行系统的命令的特性来做很多事情,不只是复制文件。

Single Application

如果限制一个程序同时只能启动一个实例,有几个可以使用的库

观察者模式的 NotificationCenter

Qt 的信号槽在解耦方面做的非常好,sender 和 receiver 不需要互相知道,但是在使用 QObject::connect() 建立信号槽连接的时候是必须要同时知道 sender 和 receiver 的,这在绝大多数时候都是非常好用的,但是如果只想发送通知后,对此通知感兴趣的监听者就能自动的收到通知,同时还不用 QObject::connect() 建立信号槽连接,就可以使用下面介绍的 NotificationCenter 来实现。此外,NotificationCenter 内部已经处理好了跨线程通讯,不需要再考虑不同线程间函数调用的头疼问题。

NotificationCenter 的使用:

  • 通知的监听器,不需要使用继承,只需要实现函数 notified,当有通知的时候,notified 函数会被自动调用

    1
    Q_INVOKABLE void notified(int notificationId, const QByteArray &data);
  • 监听某个通知,通知的标志是一个整数,可以根据业务随意定义

    1
    Singleton<NotificationCenter>::getInstance().addObserver(1, &foo);
  • 发送通知, 只需要和 NotificationCenter 交互就能发送和接收通知

    1
    2
    Singleton<NotificationCenter>::getInstance().notify(1, QString("Two").toUtf8()); // 同线程发送消息
    NOTIFY_CROSS_THREAD(2, QString("Two").toUtf8()); // 跨线程发送消息,也可同线程
  • 删除监听器,不再需要的时候,从 NotificationCenter 删除监听器,以免造成野指针异常

    1
    2
    Singleton<NotificationCenter>::getInstance().removeObserver(&foo);
    Singleton<NotificationCenter>::getInstance().removeObserver(1, &foo);

NotificationCenter 什么时候使用? 以 QTcpSocket 通讯为例:
使用信号槽和 NotificationCenter 都可以完成任务,但是 NotificationCenter 也是个不错的选择

  • 如果每一种消息都对应一个信号,则一般都会对应一个槽函数(对创建太多函数总有莫名的恐惧感)
  • 消息的种类会很多,就会需要太多的信号槽
  • 需要使用 connect() 給信号槽对应的创建连接
  • 如果要增加新的消息类型时,则要创建它的槽函数和建立连接
  • 也许你会说,所有的消息都用同一个信号和槽函数,因为可以在发射时传入消息的种类,在槽函数里也是能区别消息的。是的,这确实是一个不错的方法,这种做法和我们的 NotificationCenter 其实就差不多一样了,但是有点区别的是它会把所有的消息都发送到槽函数里,不需要的也会发送,例如有 100 种消息,即使只对其中 2 种消息感兴趣,但是它也会收到另外的 98 种消息。 而使用 NotificationCenter 时只会收到你感兴趣的 2 种消息。
  • 还有一点,NotificationCenter 中通知的监听器注册只需要关心通知的 ID,这个 ID 是根据业务需求定义的一个整数,不需要知道通知的发送者,但是建立信号槽连接时 sender 和 receiver 都需要知道,这也是相对方便的一点。

文中的 监听器观察者 指的是同一个意思;消息通知 也是同一个意思。

ForkLift 记住 FTP 密码

ForkLift 连接 FTP: 菜单 Go > Connect...,这种方式连接 FTP 不会自动记住密码,下次再连接时需要再次输入密码。

为了记住密码,可以使用 Favorites 来管理 FTP 的链接: Favorites > Show Favorites > +,不要勾选 Ask 就可以了

Qt 的 Json 读写工具类 Json

读取下面 Json 文件 x.json 中 admin 属性下的 roles 属性,使用工具类 Json,支持带 “.” 的路径格式,代码如下

1
2
Json json("x.json", true);
qDebug() << json.getStringList("admin.roles");

如果使用原生的 QJsonDocument 来读的话,则代码如下

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
QByteArray json; // json 的内容
QFile file("x.json"); // Json 的文件
// [1] 读取 Json 文件内容
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
json = file.readAll();
} else {
return -1;
}
// [2] 解析 Json 得到根节点的 QJsonObject
QJsonParseError error;
QJsonDocument jsonDocument = QJsonDocument::fromJson(json, &error);
QJsonObject root = jsonDocument.object();
if (QJsonParseError::NoError != error.error) {
qDebug() << error.errorString() << ", Offset: " << error.offset;
}
// [3] 按路径访问得到 roles 的数组
QJsonArray roles = root.value("admin").toObject().value("roles").toArray();
QStringList result;
// [4] 遍历 roles 数组得到数组中的所有字符串
for (QJsonArray::const_iterator iter = roles.begin(); iter != roles.end(); ++iter) {
QJsonValue value = *iter;
result << value.toString();
}
qDebug() << result;

相比之下,类 Json 简单很多,省去了很多繁杂的步骤。

绝对坐标布局

如图所使用的布局在管理系统、监控、统计平台以及对话框中经常使用

主要是使用绝对坐标设置 top, left, right, bottom 和高宽来布局。

弹出层 Layer

Layer 是一款近年来备受青睐的 web 弹层组件,她具备全方位的解决方案,致力于服务各水平段的开发人员,您的页面会轻松地拥有丰富友好的操作体验,只依赖于 jQuery,提供了多种的弹出层选择,对 iframe 支持友好,可去其官网体验一下具体的例子 http://layer.layui.com

  • LeanModal 是一个很简单的弹出层,虽然小,但是太过简陋
  • 使用 Bootstrap 的时候,BootstrapDialog 是不错的选择,但是 Layer 还提供了 tips,msg 等特有的弹出层方式,更简单的自定义等。

Qt 杂谈

我们所做的事,所写的代码,都已经被其他人 * 过无数次了,在这里只不过是用我们自己的方式再演绎一次,人生如戏,全靠演技。

我不擅长于写系统的资料,所以这里很多内容看上去都比较散,更像是技术点的集合、提示、思考与总结,目标人群是有一定 Qt 基础的 Qt 爱好者。

文笔也是我的一大缺点,很是粗糙,想到哪写到哪,不成体系,甚至不同章节风格迥异,一会来个漫画,一会来个网络段子,一会神棍杂谈,宇宙洪荒大道理瞎扯蛋,目的只是用散射的思维方式从侧面启发提示相似的道理,主要是我经常天马行空的乱想如斯,实是不知如何才能表达的更贴切,希望大家多多包涵,透过现象看本质。

线程一调用线程二中函数的正确姿势

Qt 里 线程一的上下文中 调用 线程二的上下文中 的函数的正确姿势是使用 信号槽 或者 QMetaObject::invokeMethod(),有意思的是 Qt 4 时线程一中直接调用线程二中的函数,程序会直接奔溃退出,很容易发现问题,但在 Qt 5 里有时候没问题,有时候会在控制台有警告,有时候程序会退出,很是莫名其妙,所以最好的办法就是不要直接调用,下面的程序展示了相关测试代码。

Because of limitations inherited from the low-level libraries on which Qt’s GUI support is built, QWidget and its subclasses are not reentrant. One consequence of this is that we cannot directly call functions on a widget from a secondary thread. If we want to, say, change the text of a QLabel from a secondary thread, we can emit a signal connected to QLabel::setText() or call QMetaObject::invokeMethod() from that thread. For example:

1
2
3
4
5
6
void MyThread::run()
{
...
QMetaObject::invokeMethod(label, SLOT(setText(const QString &)), Q_ARG(QString, "Hello"));
...
}

Vue 使用 v-for 和设置属性及事件处理

Vue 中使用 v-for 遍历数组,设置属性使用指令 v-bind,输入和数据绑定用 v-model,事件处理则用 v-on

插值时需要用 { { } },但是使用指令时不用,指令用 v- 作为前缀,例如

  • v-for
  • v-if
  • v-show
  • v-bind
  • v-on

v-bind 和 v-on 有简写形式 :@

  • 例如设置属性 data-name 使用指令 v-bind:data-name,其简写为 :data-name
  • 点击事件使用 v-on:click,其简写为 @click

鼠标放到 View 的 item 上时显示 tool tip

1
2
3
4
5
6
7
8
9
10
11
12
// [1] View 需要启用 mouse tracking
ui->listView->setMouseTracking(true);
// [2] 处理 mouse enter 事件
connect(ui->listView, &QListView::entered, [] (const QModelIndex &index) {
if (!index.isValid()) {
qDebug() << "Invalid index";
return;
}
QToolTip::showText(QCursor::pos(), index.data(Qt::UserRole + 1).toString());
});

Vue-ArtTemplate-jQuery 一起使用

Vue 和 ArtTemplate 绑定数据的格式都是一样的 { { } },但是 Vue 有个缺点,例如使用类选择器选择 element 时,只有第一个匹配的 element 会生效,还有如树的节点是 Ajax 动态加载的话,用 Vue 也不能多级的显示节点,而这些恰恰 ArtTemplate 能做到,当然 ArtTemplate 不是 MVVM 模式的,所以 Vue 的优点也是 ArtTemplate 不能比的,所以把它们一起结合起来灵活使用看起来不错。

不过,Vue 连 IE 8 都不兼容,移动端还好,PC 端的很多业务还是不能用 Vue 的,做做管理端的功能还行。

自定义 QListView

使用 QListView 实现如图效果:

  • 多行文本
  • 显示图标
  • 文本在图标下面
  • HTML 格式的 Tool Tip
  • 可以固定每个 item 的大小
  • 窗口大小变化时每行显示的 item 数自动调整

QSS 实现的扁平滚动条

使用 QSS 实现扁平滚动条,只有几个简单的颜色,并且去掉了箭头,圆角等,尽量的简约,简约而不简单

  • 滚动条的背景色
  • handle 的背景色
  • 鼠标放到 handle 上的背景色

HttpServletResponse 返回图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@GetMapping("/image/foo.jpg")
public void enrollRegPhoto(HttpServletResponse response) {
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream("/imageDir/foo.jpg");
out = response.getOutputStream();
IOUtils.copy(in, out);
} catch (Exception ex) {
logger.warn(ex.getMessage());
} finally {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
}
}

JS 关闭当前标签页

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
function closeWindow() {
var browserName = navigator.appName;
var browserVer = parseInt(navigator.appVersion);
if (browserName == "Microsoft Internet Explorer") {
var ie7 = (document.all && !window.opera && window.XMLHttpRequest) ? true : false;
if (ie7) {
// This method is required to close a window without any prompt for IE7 & greater versions.
window.open('', '_parent', '');
window.close();
} else {
// This method is required to close a window without any prompt for IE6
this.focus();
self.opener = this;
self.close();
}
} else {
// For NON-IE Browsers except Firefox which doesnt support Auto Close
try {
this.focus();
self.opener = this;
self.close();
} catch (e) {
}
// For Firefox
try {
window.location.replace("about:blank");
} catch (e) {
}
}
}

时间选择器 Laydate

LayDate 包含了

  • 日期范围限制
  • 开始日期设定
  • 自定义日期格式
  • 时间戳转换
  • 当天的前后若干天返回
  • 时分秒选择
  • 智能响应
  • 自动纠错
  • 节日识别
  • 快捷键操作

解析身份证

身份证号码位数的含意

  1. 1、2 位数字表示:所在省份的代码
  2. 3、4 位数字表示:所在城市的代码
  3. 5、6 位数字表示:所在区县的代码
  4. 7~14 位数字表示:出生年、月、日
  5. 15、16 位数字表示:所在地的派出所的代码(也有说不是,如图)
  6. 17 位数字表示性别:奇数表示男性,偶数表示女性
  7. 18 位数字是校检码:也有的说是个人信息码,用来检验身份证的正确性。校检码可以是 0~9 的数字,有时也用 x 表示(尾号是10,那么就得用 x 来代替),一般是随计算机的随机产生

Lean Modal

LeanModal 是一个用于创建模式对话框的超级简单 jQuery插件。可以展示隐藏的页面内容,整个插件大小只有不到 1K,可灵活变化高度和宽度,没有用到任何图片,支持在一个页面中创建多个实例,非常适合于创建:登录框,注册框,警告对话框等。

防止 XSS 攻击

可以使用 XSS Filter 防止 XSS 攻击,具体细节请访问 http://www.servletsuite.com/servlets/xssflt.htm

使用步骤:

  1. xssflt.jar 放到 WEB-INF/lib
  2. xssflt.jar 添加到 Gradle 依赖

    1
    compile fileTree(dir: 'src/main/webapp/WEB-INF/lib', include: ['*.jar'])
  3. 修改 web.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- 防止 XSS 攻击 -->
    <filter>
    <filter-name>XSSFilter</filter-name>
    <filter-class>com.cj.xss.XSSFilter</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>XSSFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>

Webuploader 上传文件

SpringMVC Ajax 拖拽上传文件 一文介绍了使用 jQuery File Upload 拖拽上传文件,也挺简单的,只不过使用的 js 文件比较多,这里介绍使用百度的 Webuploader 实现拖拽上传文件,只需要引入 2 个文件:

  • webuploader.css
  • webuploader.js

有下面一些特性:

  • 允许的类型
  • 允许的大小
  • 创建缩略图
  • 图片压缩
  • 上传进度
  • 拖拽上传
  • 自动上传
  • 手动上传: uploader.upload()
  • 更多信息请访问官网 http://fex.baidu.com/webuploader/,以及查看 API 文档。

Redis 集成

如果数据每次都从数据库查询,当并发大的时候,性能就会急剧的下降,可以采用 Redis 作为缓存,数据首先从 Redis 里查询,如果 Redis 里没有,然后才从数据库查询,并把查询到的结果放入 Redis。

乱码处理

字符集主要涉及 2 个方面

  • 文件本身的字符集(文件,数据库存储使用,返回给浏览器端的 html 内容)
  • 程序中编码解码时候使用的字符集(如解析 http 请求的数据)

为了防止乱码,我们规定:所有的字符集都用 UTF-8

Mybatis 语法

使用 MySQL 自动生成的主键

1
2
3
<insert id="insert" parameterType="Person" useGeneratedKeys="true" keyProperty="id">
INSERT INTO person(name, password) VALUES(#{name}, #{password})
</insert>

使用 Oracle 序列生成的主键

1
2
3
4
5
6
<insert id="insertEnrollment" parameterType="EnrollmentForm">
<selectKey resultType="long" order="BEFORE" keyProperty="enrollId">
SELECT S_ENR_ID.Nextval from DUAL
</selectKey>
INSERT INTO enrollment (id, address) VALUES (#{enrollId}, #{address})
</insert>

使用 LIKE 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
MySql:
SELECT * FROM user WHERE name like CONCAT('%',#{name},'%')
Oracle:
SELECT * FROM user WHERE name like CONCAT('%',#{name},'%') 或
SELECT * FROM user WHERE name like '%'||#{name}||'%'
SQLServer:
SELECT * FROM user WHERE name like '%'+#{name}+'%'
DB2:
SELECT * FROM user WHERE name like CONCAT('%',#{name},'%') 或
SELECT * FROM user WHERE name like '%'||#{name}||'%'

MyBatis 集成

SpringMVC 集成 MyBatis 需要以下几个文件

  • 配置文件
    • datasource.xml
    • mybatis.xml
    • web.xml
  • 查询需要的文件
    • 可选:bean(也可以不要,直接用 Map)
    • 必要:mapper xml
    • 必要:mapper interface

View Controller

有很多静态页面,里没有动态的内容,如果写 Controller 去做映射的话又感觉很麻烦,都是体力活,没什么意思,这时可以用 mvc:view-controller 进行映射达到相同的效果而又不需要写 Controller。

因为这个配置需要 Controller 的支持,所以 view 的文件需要放在模版所在文件夹。

使用 REST

web.xml 里加上 HiddenHttpMethodFilter

1
2
3
4
5
6
7
8
9
<!-- 浏览器的 form 不支持 put, delete 等 method, 由该 filter 将 /blog?_method=delete 转换为标准的 http delete 方法 -->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<servlet-name>springmvc</servlet-name>
</filter-mapping>

Freemarker 语法

FreeMarker 是一个用 Java 语言编写的模板引擎,它基于模板来生成文本输出。FreeMarker 与 Web 容器无关,即在 Web 运行时,它并不知道 Servlet 或 HTTP。它不仅可以用作表现层的实现技术,而且还可以用于生成 XML,JSP 或 Java 等。

处理 Ajax 请求

SpringMVC 处理 AJAX 请求很简单,只需要在方法的前面加上 @ResponseBody 即可。
Controller 的方法一般返回 String(可以是JSON, XML, 普通的 Text), 也可以是对象。

Freemarker 集成

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

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

Web 项目框架

项目结构:

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
├── build.gradle
└── src
├── main
│   ├── java
│   │   └── com
│   │   └── xtuer
│   │   └── controller
│   │   └── DemoController.java
│   ├── resources
│   │   └── config
│   │   └── spring-mvc.xml
│   └── webapp
│   └── WEB-INF
│   ├── static
│   │   ├── css
│   │   ├── img
│   │   │   └── favicon.ico
│   │   ├── js
│   │   └── lib
│   │   ├── jquery.js
│   │   └── template.js
│   ├── view
│   │   └── fm
│   │   └── hello.fm
│   └── web.xml
└── test
├── java
└── resources

Web 开发简介

Web 开发简介

目的:使用当前比较流行的技术开发一个具有完整功能的网站。

使用的技术

  • Web Server: Tomcat
  • Web 框架: SpringMVC
  • 显示层: Freemarker
  • 数据库: MySQL
  • 持久层: MyBatis
  • 事务: @Transactional
  • 日志: Logback
  • 风格: RESTful
  • 安全: Spring-Security(登录管理,权限管理)
  • 项目管理工具: Gradle(自带热加载功能)

Tomcat 部署

部署工程为 Tomcat 的默认工程,工程的 war 包为 web-mix.jar

  1. 创建目录 /Users/Biao/Desktop/data
  2. 复制 web-mix.jar 到目录 /Users/Biao/Desktop/data
  3. 解压 web-mix.jar 得到目录结构 (Tomcat 启动后不会自动解压)

    解压命令: rm -rf web-mix; unzip web-mix.war -d web-mix

    1
    2
    3
    4
    5
    6
    7
    data
    ├── web-mix
    │   ├── META-INF
    │   │   └── MANIFEST.MF
    │   └── WEB-INF
    │   ├── asset
    │   ├── ......
  1. <tomcat>/conf/Catalina/localhost 下创建文件 ROOT.xml,内容为

    1
    2
    3
    <Context path="/" docBase="/Users/Biao/Desktop/data/web-mix"
    debug="0" privileged="true" reloadable="false">
    </Context>

    docBase: 工程所在路径
    path: 工程的 context path

  2. 启动 Tomcat
  3. 访问 http://localhost:8080 显示的是上面工程的页面,而不是 Tomcat 默认的主页

    不用删除 <tomcat>/webapps/ROOT,继续放在那里就可以了

使用 Lombok 自动生成 Getter and Setter

根据 Java Bean 的规范,Bean 就是一个简单的类,主要是属性和访问函数 Getter and Setter 等,都是模版性的代码,虽然有 IDE 帮助我们自动生成,但是代码打开后全是一大堆的访问函数,看上去也不爽,更郁闷的是,有时候 Bean 有几十个属性,添加一个新的属性后也很容易忘了添加相应的访问函数,而 Java 的很多框架对属性的访问都是使用反射和访问函数来查找的,由于缺少访问函数导致有时候后端得到了数据,但是前端始终缺几个数据,逻辑看上去又没问题,很难得一下发现问题在哪里(已经发生过好几次)。现在好了,我们可以使用 Lombok 来自动的为 Bean 生成访问函数。

一般使用下面 3 个注解就足够了,没必要搞得更麻烦:

  • @Getter
  • @Setter
  • @Accessors(chain=true)

虽然 @Data 的功能很强大,但是阅读上不够直观,所以不推荐使用。

@Data is equivalent to @Getter, @Setter, @RequiredArgsConstructor, @ToString and @EqualsAndHashCode.

Lombok 不会影响程序的运行性能,它使用 javac 的插件机制在编译阶段生成访问函数到 class 的字节码里,和我们直接写没有什么区别,反编译一下生成的 class 文件就一目了然了。

jQuery 的 REST 插件

使用 REST 风格调用 jQuery.ajax() 更新用户名:

1
2
3
4
5
6
7
8
9
10
$.ajax({
url: '/users/1/username',
data: JSON.stringify({name: 'Bob'}),
type: 'PUT',
dataType: 'json',
contentType: 'application/json'
})
.done(function(result) {
console.log(result);
});

如果每个 REST 的请求都像上面这样写一遍: PUT, POST, DELETE 时需要 JSON.stringify(data), 请求不同时 type 也不同,dataType 和 contentType 是固定的,这么多限制,很容易出错。

为了方便使用和减少错误的发生,我们可以把它简单的封装为一个 jQuery 的插件,使用更有语义的函数进行 RESTful 风格的访问,例如:

1
2
3
$.rest.get({url: '/rest', data: {name: 'Alice'}, success: function(result) {
console.log(result);
}});

JS 解析 SRT 格式的字幕

把 SRT 格式的字幕文件解析为字幕的对象数组,格式为:

1
2
3
4
[
{sn: "0", startTime: 0.89, endTime: 7.89, content: "这里是一系列与Hadoop↵有关的其他开源项目"},
{sn: "1", startTime: 8.38, endTime: 14.85, content: "Eclipse是一个IBM贡献到开源社区里的集成开发环境(IDE)"}
]

输出效果:

SpringMVC 获取 Request 和 Response

SpringMVC 中在任意地方取得 HttpServletRequestHttpServletResponse

  1. 在 web.xml 中注册 RequestContextListener (SpringMVC 4 不需要这一步)

    1
    2
    3
    4
    5
    <listener>
    <listener-class>
    org.springframework.web.context.request.RequestContextListener
    </listener-class>
    </listener>
  2. 获取 HttpServletRequestHttpServletResponse

    1
    2
    3
    4
    5
    6
    public static String testRequestAndResponse() {
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
    return request.getParameter("name");
    }

升级 Xcode8 后 Qt 出错

错误提示:

Project ERROR: Xcode not set up properly. You may need to confirm the license agreement by running /usr/bin/xcodebuild.

解决办法:

  1. 打开 Qt_install_folder/5.7/clang_64/mkspecs/features/mac/default_pre.prf
  2. 修改

    1
    isEmpty($$list($$system("/usr/bin/xcrun -find xcrun 2>/dev/null")))

    1
    isEmpty($$list($$system("/usr/bin/xcrun -find xcodebuild 2>/dev/null")))

具体请看 http://stackoverflow.com/questions/33728905/qt-creator-project-error-xcode-not-set-up-properly-you-may-need-to-confirm-t

Qt 自定义日志工具

C++ 中比较不错的日志工具有 log4cxxlog4qt 等,但是它们都不能和 qDebug(), qInfo() 等有机的结合在一起,所以在 Qt 中使用总觉得不够舒服,感谢 Qt 提供了 qInstallMessageHandler() 这个函数,使用这个函数可以安装自定义的日志输出处理函数,把日志输出到文件,控制台等,具体的使用可以查看 Qt 的帮助文档。

Qt 访问网络的 HttpClient

Qt 使用 QNetworkAccessManager 访问网络,这里对其进行了简单的封装,访问网络的代码可以简化为:

1
2
3
HttpClient("http://localhost:8080/device").get([](const QString &response) {
qDebug() << response;
});

Qt 访问网络

Qt 中访问网络使用 QNetworkAccessManager,它的 API 是异步,这样在访问网络的时候不需要启动一个线程,在线程里执行请求的代码。

需要注意一点的是,请求响应的对象 QNetworkReply 需要我们自己手动的删除,一般都会在 QNetworkAccessManager::finished 信号的曹函数里使用 reply->deleteLater() 删除,不要直接 delete reply

本文的最终结果为实现调用一个函数就能访问网络:

1
2
3
4
5
QNetworkAccessManager *manager = new QNetworkAccessManager();
NetworkUtil::get(manager, "http://www.baidu.com", [](const QString &response) {
qDebug() << response;
});

Qt 创建圆角、无边框、有阴影、可拖动的窗口

程序窗口的边框,标题栏等是系统管理的,Qt 不能对其进行定制,为了实现定制的边框、标题栏、关闭按钮等,需要把系统默认的边框、标题栏去掉,然后使用 Widget 来模拟它们。这里介绍使用 QSS + QGraphicsDropShadowEffect 来创建圆角、无边框、有阴影、可拖动的窗口。

核心技术要点:

  • 启用 QSS: setAttribute(Qt::WA_StyledBackground, true)

    我们继承 QWidget 实现的 Widget 默认是不启用 QSS 的,为了启用 QSS,需要调用 setAttribute(Qt::WA_StyledBackground, true)

  • 使用 border-radius 创建圆角效果

    顶级窗口有些 QSS 不生效,例如 border-radius,所以把要显示圆角的 Widget 上放在另一个顶级 Widget 中,变为非顶级窗口

  • 顶级窗口需要去掉边框,背景设置为透明
    • 去掉边框: setWindowFlags(Qt::FramelessWindowHint);
    • 背景透明: setAttribute(Qt::WA_TranslucentBackground);
  • 使用鼠标事件实现拖动
  • 使用 QGraphicsDropShadowEffect 创建阴影

    很遗憾,QSS 不支持阴影

JUnit + Spring Test

Gradle 依赖

1
2
testCompile 'org.springframework:spring-test:4.3.0.RELEASE'
testCompile 'junit:junit:4.12'

Test 例子

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
import org.apache.commons.configuration.PropertiesConfiguration;
import org.junit.runner.RunWith;
import org.junit.Test;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.util.Properties;
@RunWith(SpringRunner.class)
@ContextConfiguration({"classpath:spring-beans-config.xml"})
public class TestYamlPropertiesAndPropertiesConfig {
@Resource(name = "yamlProperties")
private Properties yamlProperties;
@Resource(name = "propertiesConfig")
private PropertiesConfiguration propertiesConfig;
@Test
public void testYamlProperties() {
System.out.println(yamlProperties.getProperty("mysql.jdbc.url"));
System.out.println(yamlProperties.getProperty("username"));
}
@Test
public void testPropertiesConfig() {
System.out.println(propertiesConfig.getString("username"));
System.out.println(propertiesConfig.getInteger("age", 0));
}
}

@ContextConfiguration({“classpath:spring-beans-config.xml”}) 用于加载 Spring Bean 的配置文件

优点是不需要手动创建 ApplicationContext 了,也能使用 @Autowired 和 @Resource 等注入 Bean

Lambda 在 Qt 中的运用

传统的信号槽绑定时,需要先声明槽函数,然后实现槽函数(槽函数的声明和实现需要分别在 .h 和 .cpp 文件中),最后使用 connect() 绑定起来,而且在 connect() 的时候如果槽函数写错了编译时不会报错,只有在 Debug 模式下运行时才会提示槽函数不存在,Release 模式下运行时不会给予任何错误提示。Qt 5 使用 C++11 支持 Lambda 表达式,connect() 的时候如果函数名写错了就会在编译时报错,还有一点是 Lambda 表达式在需要的时候才定义,不需要声明,写起来比较简单。

Lambda 表达式可以理解为匿名函数,比如代码里有一些小函数,而这些函数一般只被调用一次(比如函数指针),这时就可以用 Lambda 表达式替代他们,这样代码看起来更简洁些,用起来也方便。

Mac 安装 Mysql 和 Nginx

Mac 上安装 MySQL 和 Nginx 有很多种方法,例如安装 MAMP 就可以了,也可以单独下载安装包安装,也可以使用 brew 在终端里安装,下面介绍使用 brew 安装的方法。

Mac Homebrew

Homebrew 可以很方便的通过终端安装许多软件,例如 Tomcat,Redis,Gradle,Tree 等,和 Ubuntu 下的 apt-get 很像,下面列出一些 Homebrew 常用命令,以安装 tomcat 为例。

MySQL 导入导出 CSV

有时需要把 MySQL 的数据导出为 CSV 格式的文件便于分析和传输,有的时候需要把 CSV 格式的内容导入到 MySQL,MySQL 支持对 CSV 格式的文件导入和导出。

限制 TIME_WAIT 的连接数

TIME_WAIT 连接出现在主动关闭连接一方(TCP 四次握手断开连接),在大并发的时候,如果配置不当,系统就会出现大量 TIME_WAIT 的连接,导致系统响应慢,甚至无响应,可以修改系统内核的参数对其进行限制。

JS 模版 artTempalte

使用模版就可以不用在 JS 里手动的使用字符串相加拼写 HTML 片段了。

JS 中有很多模版库,例如 artTempalte、doT、juicer、laytpl、Mustache、Underscore Templates、Embedded JS Templates、HandlebarsJS、Jade templating、baiduTemplate、kissyTemplate 等等,总的比较下来,语法上更喜欢的是 artTempalte,其性能也不错;laytpl 号称性能之王,比 artTemplate 和 doT 还快,如果追求性能,可以试试它 http://laytpl.layui.com

Spring 判断设备信息

虽然响应式设计现在很流行,设计的好的话同一个页面在桌面设备和移动设备都能显示的很好,但是源码都一样的,也就是说桌面设备和移动设备要下载的文件都是一样大小的,在用户体验,页面显示效果和加载速度上不是非常理想,所以有必要针对移动设备进行优化,例如 jQuery 在移动设备上应该使用 jQuery Mobile,图片进行相应的缩小,很多桌面端的内容不应该出现在移动设备中,例如广告,侧边栏,复杂的搜索,动态获取的第三方信息等。

为了检测页面访问的设备类型,可以使用 Spring Mobile,当然也可以自己读取 User Agent 来判断。

Java 发邮件

可以使用 Apache Commons Mail 或者 Spring Mail 发送邮件。

Spring 项目中推荐使用 Spring Mail 发送邮件,非 Spring 项目里可以使用 Apache Commons Mail 发邮件 (需要的 Jar 包相对少一些)。

Java 读取 Properties 和 Yaml Properties

Java 可以使用 PropertiesConfiguration 来读取 properties 属性文件,Spring 4.3 后还支持了 Yaml 格式的属性文件

  • PropertiesConfiguration: 读取时可以自动进行类型转换,可以给定默认值
  • Yaml 格式的属性文件: 可以使用树形结构,方便分组,比 .properties 属性文件更灵活,但是以普通的 java.util.Properties 来读取

IDEA 创建 Gradle Module

以创建名为 SpringIntegration 的 Gradle Module 为例介绍在 IDEA 创建 Gradle Module 的步骤。

IDEA 的 Maven Module 里修改 pom.xml 中的依赖后会自动下载相关的 Jar 包和源码,并添加到 classpath 里。

但是 IDEA 的 Gradle Module 里修改 build.gradle 中的依赖后却不会自动下载相关的 Jar 包和源码,并添加到 classpath 里,需要我们手动的在 Gradle 的工具窗口里点击刷新 Gradle Module 才会执行这些操作。

Spring + Fastweixin 微信开发

微信有两种模式,编辑模式和开发者模式,有些功能是互斥的,不可以同时使用,微信开发需要在开发者模式下进行(开发者模式下仍然可以去微信的网页上群发消息)。下面介绍的功能能满足大部分的需求,响应文本消息,图文消息,创建菜单,响应菜单消息等。

我们给微信提供服务有两种消息模式,被动和主动

  • 被动: 例如用户输入文本,点击菜单,微信服务器会访问我们的 Web 服务对应的 URL,我们返回对应的消息给微信服务器
  • 主动: 例如创建菜单,群发消息,这种模式需要我们主动去触发,给微信服务器发送消息,可以是执行某个定时任务触发,或者我们访问某个 URL 然后在其响应的代码里触发

Atom 常用插件和快捷键

Atom 以前很慢,所以一直不想用,在 1.0 版本后启动差不多需要 1.5 秒,已经快了很多,尝试了下,感觉很好,插件更好用,例如格式化插件 atom-beautify,jshint 等、界面更舒服,现在已经从 SublimeText 替换到 Atom 了,以下为常用的几个插件

本地服务映射为外网可访问

例如我们开发了一个网站,运行在我们自己的电脑上,本地访问地址是 http://localhost.com:8080,但是只能在自己的电脑和局域网访问,外网访问不了,例如在做微信公众号开发时如果不能被外网访问就很不方便。如果想要外网能访问我们的网站,则需要:

  • 购买一个域名和空间,把我们的网站部署上去
  • 使用工具把本地的网站服务映射为外网可访问的,例如 Ngrok,可支持 Mac,Windows,Linux

Spring 中配置 CORS

Ajax 以前要实现跨域访问,可以通过 JSONP、Flash 或者服务器中转的方式来实现,现在可以使用 CORS

跨域资源共享(CORS )是一种网络浏览器的技术规范,它为 Web 服务器定义了一种方式,允许网页从不同的域访问其资源,而这种访问是被同源策略所禁止的。CORS 系统定义了一种浏览器和服务器交互的方式来确定是否允许跨域请求。 它是一个妥协,有更大的灵活性,但比起简单地允许所有这些的要求来说更加安全。

Sublimetext 安装 jshint

一、安装 jshint 的依赖

  1. 由于 jshint 是依赖 Node.js 的,所以要先安装 Node.js,安装了 brew 的同学可以直接在 Terminal 使用以下命令

    1
    brew install node
  2. 安装 jshint

    1
    npm install -g jslint
  3. 测试 jshint: 写个 js 文件,例如某些行不用分号结束,乱赋值等,然后用下面的命令测试,会输出不规范的提示

    1
    jshint test.js

二、安装 SublimeLinter 及 jshint 插件

  1. 安装 SublimeLinter
  2. 安装 SublimeLinter-jshint
  3. 安装成功后就可以到 Sublimetext 里测试了,右键 SublimeLinter->Show All Erroes,就会有提示了,也会有实时的错误提示

三、参考

HTML 树的实现

树形结构的使用很广泛,例如用来显示文件夹和文件,组织机构的表示等,可以使用 zTree 来实现,这里我们将自己实现树,了解其原理。

实现下图中表示文件的树,主要是使用 <ul><li> 组织结构、CSS 调整显示效果、jQuery 实现点击的动态效果和 jQuery UI 实现拖拽操作 (主要的代码都在 HTML 和 CSS 上,JS 的代码只有 20 行)。

jQuery 表单验证插件 validate

jQuery Validate 插件为表单提供了强大的验证功能,让客户端表单验证变得更简单,同时提供了大量的定制选项,满足应用程序各种需求。该插件捆绑了一套有用的验证方法,包括 URL 和电子邮件验证,同时提供了一个用来编写用户自定义方法的 API,以及使用 Ajax 进行服务器端验证。所有的捆绑方法默认使用英语作为错误信息,且已翻译成其他 37 种语言。
该插件是由 Jörn Zaefferer 编写和维护的,他是 jQuery 团队的一名成员,是 jQuery UI 团队的主要开发人员,是 QUnit 的维护人员。该插件在 2006 年 jQuery 早期的时候就已经开始出现,并一直更新至今。

Bootstrap Progress Bar

1
2
3
<div class="progress">
<div class="progress-bar" style="width: 2%;"><span>2% 是不是显示不完整</span></div>
</div>

上面的代码显示出的进度条中由于进度只有 2%,只显示出了 2%是不是显示不完整 没有显示出来

Commons IO 例子

可以使用 Apache Commons-IO 操作文件:

功能 代码
获取文件名的后缀 FilenameUtils.getExtension(path)
获取文件名不包含后缀 FilenameUtils.getBaseName(path)
获取文件所在目录的路径 FilenameUtils.getFullPath(path)
复制文件 FileUtils.copyFileToDirectory()
复制文件夹 FileUtils.copyDirectory()
移动文件 FileUtils.moveFileToDirectory()
计算文件的 check sum FileUtils.checksumCRC32()
递归的创建目录 FileUtils.forceMkdir(new File("/Users/Biao/Desktop/a/b/c"))
还有更多文件相关的操作 ……

Commons-Lang3 例子

可以使用 Apache Commons-Lang3:

  • 日期格式化
  • 序列化和反序列化
  • 生成随机字符串
  • 字符串 join, 包含, 空判断, 缩写, 补全输出指定长度的字符串 (leftPad, center, rightPad) 等
  • 获取系统信息
  • 获取 Class 的信息
  • HTML escape and unescape
  • 数字相关, 如求数组中的最大最小值

jQuery 中 fadeIn() 和 slideDown() 同时执行

jQuery 没有直接提供 fadeIn() 和 slideDown() 同时执行的函数,但是可以像下面这样实现:

1
$elem.stop(true, true).fadeIn({ duration: 300, queue: false }).css('display', 'none').slideDown(300);

The answer is that once either effects activate, it takes the inline css property “display=none” off of the element. These hide effects require the display property to be set to “none”. So, just rearrange the order of methods and add a css-modifier in the chain between fade and slide.

fadeOut() 和 slideUp() 同时执行的代码如下:

1
$elem.stop(true, true).fadeOut({ duration: 300, queue: false }).slideUp(300);

Bootstrap 模态对话框

Bootstrap 的对话框用起来比较繁复,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">Modal title</h4>
</div>
<div class="modal-body">
<p>One fine body…</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->

使用 bootstrap-dialog 达到相同的效果只需要 BootstrapDialog.alert('I want banana!'),不用写 HTML 代码,插件里已经写了。

Apache Commons

commons-io

Class Name Description
FilenameUtils 文件名称一些操作,如获取文件名的后缀
FileUtils 文件工具类,内置提供了大量文件转换方法,如 readFileToString(File,Path)
IOUtils 主要提供了 IO 常见操作
Stream 转换,关闭 Stream 等操作
1
'commons-io:commons-io:2.5'

Java 使用 FTP

Java 使用 FTP 等用 Apache Commons Net 就可以了:

Apache Commons Net™ library implements the client side of many basic Internet protocols. The purpose of the library is to provide fundamental protocol access, not higher-level abstractions. Therefore, some of the design violates object-oriented design principles. Our philosophy is to make the global functionality of a protocol accessible (e.g., TFTP send file and receive file) when possible, but also provide access to the fundamental protocols where applicable so that the programmer may construct his own custom implementations (e.g, the TFTP packet classes and the TFTP packet send and receive methods are exposed).

微 Web 服务的 REST 框架 Spark Framework

需要点击网页上的一个按钮打开本机上的文件或者修改本地的配置文件,由于安全的限制浏览器不能直接访问本地文件系统。为了实现这个功能,可以本地启动一个 Web 服务,浏览器访问这个 Web 服务(localhost),然后使用 Web 服务中的代码来访问本地文件系统。为了启动一个 Web 服务,一般我们会选择 Tomcat,Jetty,Websphere 等作为 Web 服务器响应 HTTP 请求,对于我们这么小的一个需求,就有些重量级了,如果能像打开一个普通程序一样打开程序就能启动 Web 服务就好了。

jQuery 404 时调用的方法

jQuery 的 ajax 能给不同的响应状态码指定回调函数,例如当连不上服务器(服务器没启动,网络有问题等)时调用 statusCode0 的方法,连上了服务器,但是找不到要访问的 URL 则调用 statusCode404 的方法。

Future

Future 的作用

  • 作为 ExecutorService.submit(Callable|Runnable) 的返回结果
  • 得到了 Future,说明其相关的任务已经提交给线程池去执行了
  • 获取任务的结果(阻塞): Future.get()
  • 取消任务: Future.cancel()
  • 查看任务是否完成: Future.isDone()

如果线程池执行的任务没有返回结果,直接用 Runnable 就好了,不需要用 Callable (代码里加上无意义的返回语句有点奇怪),Callable 更多是任务执行后有结果返回。

jQuery ui 拖拽

jQuery ui 提供了拖拽元素,拖拽排序等功能,例如要让一个元素能够被拖拽,只要给它执行 draggable() 函数即可,要实现 ul li 拖拽排序,只要在 ul 上执行函数 sortable() 就能实现了,具体更多的例子请参考 jQuery UI 实例 - 拖动

Spring 异步调用

Spring 中让一个方法在新线程中运行,只需要给方法加上 @Async 注解就可以(当然也可以自己直接创建一个线程实现异步执行)。

典型的使用如用户注册后需要发送一封邮件进行验证,邮件发送完成的时间取决于很多因素,例如网络快的时候发的快一些,慢的时候需要多一些的时间。如果使用同步的方式发送邮件,有可能需要等很久邮件才发送完成然后用户才能得到注册成功的响应,体验不是很好,如果使用异步的方式发送邮件,在发邮件的同时用户就被告知注册成功,请去收件箱中查看邮件进行注册验证(异步的方式还可以使用 MQ)。

Spring Security Login Util

查看用户是否登陆,调用 SecurityUtil.isLogin() 即可。

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.security.core.context.SecurityContextHolder;
public class SecurityUtil {
/**
* 判断当前用户是否已经登陆
* @return 登陆状态返回 true, 否则返回 false
*/
public static boolean isLogin() {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
return !"anonymousUser".equals(username);
}
}

Solr 中文分词插件 SmartCN

为什么要使用中文分词呢?

Solr 使用的是内置的一元分词 StandardAnalyzer,像 “诺基亚N95” 这样的词都能搜索到,我觉得它已经很不错了啊,为什么还要别的中文分词呢?

SpringMVC 处理 Ajax 映射

SpringMVC 返回 Json 数据给前端是件很简单的事,但是 SpringMVC 的 Controller 接收前端 Ajax传来的 Json 数据却不那么容易,前端和后端都要很小心,有一点不对就会出错,需要分为 2 种情况处理:

  • GET
  • PUT, POST, DELETE

IDEA 删除 Gradle Module

在 IDEA 里删除 Gradle Module,最简单的方法是从 Gradle projects 视图里删除,一次就能删除干净,从 Project 视图或者 Project Structure 中都不能一次删除干净。

How to change default Java version

1. First run /usr/libexec/java_home -V which will output something like the following

1
2
3
4
5
Matching Java Virtual Machines (2):
1.8.0_77, x86_64: "Java SE 8" /Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home
1.7.0_45, x86_64: "Java SE 7" /Library/Java/JavaVirtualMachines/jdk1.7.0_45.jdk/Contents/Home
/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home

Gradle 编码

Gradle 默认使用系统字符编码(Windows 为 GBK,Linux, Mac 为 UTF-8),很多程序员都是使用 Windows,但是 Java 文件以及其他资源文件大多数都会使用 UTF-8(因为要跨平台使用),在 Windows 开发时编译运行容易出现乱码,报错等。

Spring Security 集群

集群的关键点是 session 共享,这里使用 spring-session-data-redis 把 session 存储到 Redis 实现集群里 session 的共享,全是配置,不需要修改一行 Java 代码就能实现 Session 的集群共享。

Spring Security 用户信息数据源

前面章节中用户名、密码、权限都是写在配置文件里的,不能动态的管理用户的权限,大多数时候显然是不行的。这里介绍从其他数据源读取用户的信息,例如从数据库,LDAP 等。只需要给 authentication-provider 提供接口 UserDetailsService 的实现类即可,使用这个类获取用户的信息:

  • 修改 spring-security.xml 中的 authentication-provider
  • 接口 UserDetailsService 的实现类 MyUserDetailsService
  • 类 User
  • 类 UserRole
  • 类 UserDao
  • 其他文件和前面的一样

Spring Security 入门

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
├── main
│   ├── java
│   │   └── com
│   │   └── xtuer
│   │   └── controller
│   │   └── HelloController.java
│   ├── resources
│   │   └── config
│   │   ├── spring-mvc.xml
│   │   └── spring-security.xml
│   └── webapp
│   └── WEB-INF
│   ├── view
│   │   └── fm
│   │   ├── admin.htm
│   │   └── hello.htm
│   └── web.xml
└── test
├── java
└── resources

Spring Security 入门

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
├── main
│   ├── java
│   │   └── com
│   │   └── xtuer
│   │   └── controller
│   │   ├── HelloController.java
│   │   └── LoginController.java
│   ├── resources
│   │   └── config
│   │   ├── spring-mvc.xml
│   │   └── spring-security.xml
│   └── webapp
│   └── WEB-INF
│   ├── view
│   │   └── fm
│   │   ├── admin.htm
│   │   └── hello.htm
│   └── web.xml
└── test
├── java
└── resources

Spotlight 快捷键

  • ⌘+D: 快速查字典
  • ⌘+L: 直接在 Spotlight 浏览名词解释
  • ⌘+B: 打开浏览器,在网页上搜索
  • ⌘+R: 打开档案所在的文件夹
  • ⌘+i: 查看档案详细信息

Java 解压 zip 文件

使用 Apache commons-compress 解压 zip 文件是件很幸福的事,可以解决 zip 包中文件名有中文时跨平台的乱码问题,不管文件是在 Windows 压缩的还是在 Mac,Linux 压缩的,解压后都没有再出现乱码问题了。

Java 访问 Redis

Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API,和 Memcached 类似,它支持存储的 value 类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set – 有序集合)和 hash(哈希类型)。

Hexo 多说评论

在博客系统里增加评论功能,如果自己做的话,需要准备服务器端(PHP 和 Java 等实现)和数据库用于评论的提交、读取和保存。如果只是简单的使用评论功能,对评论的数据分析、安全性等要求不高,可以使用第三方提供的评论系统,例如 多说,集成起来也很简单,只要在页面的 HTML 代码里加入一小段代码即可。

All Documents

Ajax

Cas

DB

Default

FE

Gradle

Hexo

Index

Java

Mac

Misc

PHP

Qt

QtBook

Redis

Semantic-Ui

Solr

Spring

Spring-Core

Spring-Mvc

Spring-Security

Spring-Web

Util