Content Table

线段拟合曲线

QPainter 提供了绘制线段、矩形、椭圆、圆、圆弧、路径等的函数,如果想绘制正弦 (y=sin(x))、余弦 (y=cos(x)) 的曲线,QPainter 没有提供相应的绘制函数,应该怎么办呢?

李小龙的武术哲学: 以无法为有法,以无限为有限。

数学曲线是连续的,计算机的世界却是离散的,离散的世界使用极限的方式就可以模拟出连续的效果。可以把曲线想象成是一条一条线段连起来形成的图形,这些线段越短,连成的图形就越逼近曲线,这种方法就是线段拟合曲线,学过微积分的同学是不是感觉这个方法很熟悉?

下面以绘制正弦 (y=sin(x)) 曲线为例进行介绍:

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 FittingCurveWidget::paintEvent(QPaintEvent *) {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.translate(10, 150);

// 绘制坐标轴
painter.setPen(QPen(Qt::gray, 1, Qt::DashLine));
painter.drawLine(0, 0, 700, 0);
painter.drawLine(0, -200, 0, 200);
painter.setPen(QPen(Qt::black, 1));

// 计算正弦的坐标点,绘制线段
qreal prex = 0, prey = 0;

// [0, 314] 归一为 [0, PI]
for (int i = 0; i <= 628; ++i) {
qreal x = i;
qreal y = qSin(i/314.0*M_PI) * 100;

painter.drawLine(prex, prey, x, y);

prex = x;
prey = y;
}
}

Nginx 验证 Token

为了提高效率,常把 Nginx 作为静态文件服务器,把视频文件,JS,CSS 等放到 Nginx 上。例如我们要开发一个视频网站,免费视频不需要访问权限验证,收费视频就需要对用户的权限进行验证,验证通过了才能够继续访问,Nginx 可以借助 Lua 来实现访问验证,用户信息使用 token 表示

Nginx 简单的验证代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
location ~ /private/.+\.mp4$ {
root html;

access_by_lua '
-- 应用的 ID 和 key,和应用服务器上的一致
local appIdKeys = {["app_1"] = "key_1", ["app_2"] = "key_2"};

local args = ngx.req.get_uri_args();
local appId = args["appId"];
local appKey = appIdKeys[appId];

local token1 = args["token"]; -- 参数中 token
local token2 = ngx.md5(appId .. appKey); -- 用应用的 ID 找到对应的 key,然后根据算法计算 token

-- 如果参数中的 token 和计算得到的 token 不相等,则说明访问非法,禁止访问,否则放行访问
if token1 ~= token2 then
ngx.exit(ngx.HTTP_FORBIDDEN);
end
';
}

Nginx 和应用服务器上同时存储 appId 和 appKey,这样就能根据参数中的 appId 查找到对应的 appKey。至于使用 Lua 的变量存储,或者使用数据库,还是文件,根据具体的情况而定(Nginx 中 Lua 能够访问数据、Redis 等)。

上面的验证规则比较简单,如果其他人得到了 token,就可以无限制的访问了,为了增强安全性,可以使用更多的参数生成 token,例如用户 id,限制 URL 期限的时间戳等。

Nginx 默认没有安装 Lua 模块,需要自己安装,可参考 http://qtdebug.com/mac-nginx-lua

Nginx 安装 Lua 支持

Nginx 支持 Lua 需要安装 lua-nginx-module 模块,一般常用有 2 种方法:

  • 编译 Nginx 的时候带上 lua-nginx-module 模块一起编译

  • 使用 OpenResty: Nginx + 一些模块,默认启用了 Lua 支持(推荐使用此方式)

    OpenResty is just an enhanced version of Nginx by means of addon modules anyway. You can take advantage of all the exisitng goodies in the Nginx world.

    OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

    OpenResty® 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。

    OpenResty® 的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。

HTML5 使用 MQTT

HTML5 中也能使用 MQTT:

  1. ActiveMQ 启用 MQTT,可参考 http://qtdebug.com/misc-activemq/

  2. 启动 ActiveMQ: activemq start

  3. 使用 MQTT 的 HTML 如下:

    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
    <html>

    <head>
    <title>Test Ws mqtt.js</title>
    </head>

    <body>
    <script src="./browserMqtt.js"></script>
    <script>
    // 虽然使用的是 MQTT,但底层还是使用 WebSocket 实现的,所以这里的端口需要使用 ActiveMQ 里 WS 的端口 61614,而不是 MQTT 的端口 1883
    var client = mqtt.connect('ws://127.0.0.1:61614'); // you add a ws:// url here
    client.subscribe('foo'); // 订阅 Topic

    client.on('message', function(topic, payload) {
    console.log([payload].join('')); // 提取消息需要使用 [].join()
    })

    client.publish('foo', 'Hello World!'); // 发送消息

    // 不停的发送消息进行测试
    setInterval(function() {
    client.publish('foo', 'Time: ' + new Date().getTime());
    }, 1000);
    </script>
    </body>

    </html>
  4. 写一个 Java 的 MQTT 发布和订阅的程序一起测试,可参考 http://qtdebug.com/misc-mqtt/

下载 browserMqtt.js,也可以自己编译(新版本好像有问题,只能发消息,不能订阅消息),详细文档请参考 https://github.com/mqttjs/MQTT.js

简单的 Mock 工具 RestServerMock

前后端分离,如果前端需要等到服务器端接口开发完成后才能继续的话,效率太低,使用 Mock 工具能够更好的使得前后端分离各自开发,RestServerMock 一个是简单的静态 Web 服务器的 Mock 工具:

A Simple REST HTTP server that serves the configured JSON responses rest-server-mock.

Currently it servers JSON responses with status code 200. Future versions will support more options including : status codes, headers, encodings, etc.

Enum 注入

注入 Enum 可以使用字符串直接注入,或者借助 org.springframework.beans.factory.config.FieldRetrievingFactoryBean,下面以注入 FastJson 的 SerializerFeature 和自定义 enum 为例.

1
2
3
4
5
6
7
8
9
10
package com.alibaba.fastjson.serializer;

public enum SerializerFeature {
QuoteFieldNames,
UseSingleQuotes,
WriteMapNullValue,
WriteEnumUsingToString,
WriteEnumUsingName,
...
}
1
2
3
public enum Color {
RED, GREEN, BLUE
}

Spring Boot 热更新

尽快看到修改后的效果在开发中对于提高效率是非常重要的,一般有 2 种方式:

  • 热启动:自动重启整个项目,速度还是比较慢
  • 热加载:只加载变化的内容,例如重新编译的 class,速度相对很快

热启动

Spring Boot 的 org.springframework.boot:spring-boot-devtools 可以用来实现热启动,在 build.gradle 中添加依赖 implementation 'org.springframework.boot:spring-boot-devtools' 即可。

但是 Devtools 的热启动比较慢,例如数据库链接要重新建立。

热加载

热加载的工具有:

  • 免费:Springloaded
  • 收费:JRebel

但是在 Spring Boot 2 中不能再使用 Springloaded 了,热加载方式只能用 JRebel 了,使用 JRebel 可以参考官方文档 JRebel with Spring Boot,下面是简要的步骤:

  • 在 ~/.gradle/gradle.properties 配置 JRebel 的路径:

    • Win:rebelAgent=-agentpath:C:/jrebel/lib/libjrebel64.dll
    • Mac:rebelAgent=-agentpath:/usr/local/jrebel/lib/libjrebel64.dylib
    • Linux:rebelAgent==-agentpath:/usr/local/jrebel/lib/libjrebel64.so
  • 在 build.gradle 中添加:

    1
    2
    3
    if (project.hasProperty('rebelAgent')) {
    bootRun.jvmArgs += rebelAgent
    }

执行 gradle bootRun 启动项目,看到 JRebel 输出的日志,说明 JRebel 生效了。

编译修改的类

不管是 DevTools 还是 JRebel,他们只是监听资源文件或者 class 的变化然后执行热启动或者热加载,并不负责编译被修改的类。当一个类变化后,可以使用以下几种方式进行编译:

  • 自动编译:终端进入项目目录,执行 gradle -t classes 启动一个监听任务,当发现项目中的 Java 类发生变化时进行自动编译,模版文件变化时自动复制到 build 对应的目录中
  • 自动编译: 可以使用 IDEA 的自动编译功能: Compiler -> Build project automatically
  • 手动编译: 修改类后在 IDE 中点击菜单项或者按下快捷键进行编译

提示:控制台中看到 JRebel 生效了,但修改类后却没有看到效果,应该是还没有编译。

更多相关信息请参考 Spring Boot 的文档 Hot Swapping 深入了解热更新。

Spring Boot Thymeleaf

SpringBoot 2 默认使用 Thymeleaf 3,需要引入下面的依赖:

1
implementation('org.springframework.boot:spring-boot-starter-thymeleaf')

在 application.properties 中配置 Thymeleaf:

1
2
3
4
5
spring.thymeleaf.mode=HTML5
spring.thymeleaf.cache=false
spring.thymeleaf.suffix=.html
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html

Controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.xtuer;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class XController {
@GetMapping("/hello")
public String hello(ModelMap model) {
model.put("username", "Alice");
return "hello.html";
}
}

resources/templates 目录中创建文件 hello.html:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8"/>
</head>

<body>
<span th:text="${username}">Thymeleaf</span>
</body>

</html>

访问 http://localhost:8080/hello 就可以看到页面了

Spring Boot Redis

Spring Boot 中使用 Redis 非常简单:

  1. 引入依赖

    1
    compile('org.springframework.boot:spring-boot-starter-data-redis')
  2. 在 application.properties 中配置 Redis

    1
    2
    3
    4
    5
    6
    7
    8
    9
    spring.redis.host=localhost
    spring.redis.port=6379
    spring.redis.password=
    spring.redis.database=0
    spring.redis.pool.max-active=8
    spring.redis.pool.max-wait=-1
    spring.redis.pool.max-idle=8
    spring.redis.pool.min-idle=0
    spring.redis.timeout=0
  3. 使用 StringRedisTemplate 访问 Redis

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @RestController
    public class HelloController {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping("/redis")
    public String redis() {
    return redisTemplate.opsForValue().get("user");
    }
    }

Spring Boot MyBatis

Spring Boot 使用 MyBatis 主要为以下 4 步:

  1. 引入依赖

    1
    2
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.0.0'
    runtime('mysql:mysql-connector-java:5.1.46')
  2. 配置数据源: 配置 application.properties

    1
    2
    3
    4
    spring.datasource.username=root
    spring.datasource.password=root
    spring.datasource.url=jdbc:mysql://localhost:3306/test
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
  3. 编写 Mapper: 使用注解 @Mapper 自动生成 Mapper 对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package com.xtuer.mapper;

    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Select;

    import java.util.Map;

    @Mapper
    public interface UserMapper {
    @Select("SELECT * FROM user WHERE username=#{username}")
    Map findUserByUsername(String username);
    }
  4. 使用 Mapper: 使用 @Autowired 装配 mapper

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package com.xtuer.controller;

    import com.xtuer.mapper.UserMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;

    import java.util.Map;

    @RestController
    public class HelloController {
    @Autowired
    private UserMapper userMapper;

    @GetMapping("/hello")
    public Map hello(@RequestParam String username) {
    return userMapper.findUserByUsername(username);
    }
    }