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);
    }
    }