需求

需要构建的 SQL 条件如下:

1
where field1 in (1,2) and field2 not in (1,3) and (field3 like "%test%" or field4 like "%test%")

非动态

如果是非动态其实还是比较好实现的

1
2
where = Q(field1__in=(1,2)) & ~Q(field2__in=(1,3)) & (Q(field3__icontains="test") | Q(field4__icontains="test"))
MyObject.objects.filter(where)

动态实现

1
2
3
4
5
6
7
8
9
10
Q1 = Q()
if request.query_params.get("group_id") is not None:
Q1.add(Q(cid_id__in=[1,2]), Q.AND) # 1,2 is the group_id
Q1.add(~Q(id__in=[1,2]), Q.AND)
Q2 = Q()
if request.query_params.get("name") is not None:
Q2.add(Q(schema__icontains=request.query_params.get("name")), Q.OR)
Q2.add(Q(cid__host__icontains=request.query_params.get("name")), Q.OR)
Q2.add(Q(cid__env__name__icontains=request.query_params.get("name")), Q.OR)
MyObject.objects.filter(Q1 & Q2)

上面实现中 cid 为外键 cid_id 为外键表 id 字段,cid__env__namecid 外键表的外键表 env 的字段 name

前记

最近在用 Go 做一个小程序,需要生成一个 6 位数随机验证码,遂记录一下实现

实现

Go 生成特定长度的随机数

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
package main

import (
"fmt"
"math/rand"
"strings"
"time"
)

func main() {
fmt.Println(RandomNumber(6))
}

// width 表示需要的位数
func RandomNumber(width int) string {
numeric := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
r := len(numeric)
rand.Seed(time.Now().UnixNano())

var sb strings.Builder
for i := 0; i < width; i++ {
fmt.Fprintf(&sb, "%d", numeric[rand.Intn(r)])
}
return sb.String()
}

Colima 也已经大半年了,作为 Docker Desktop 的替代者用起来还是觉得不错的

目前最新版 0.4.2

项目地址

安装

这里只以 Homebrew 为例

1
brew install colima

大概有如下依赖

1
dependencies for colima: gdbm, ca-certificates, openssl@1.1, sqlite, python@3.9, glib, libtool, guile, nettle, libnghttp2, unbound, gnutls, libslirp, libusb, qemu and lima

更新

删除现有的 VM 实例

1
colima delete

Homebrew 更新

1
2
brew update
brew upgrade colima

start

1
colima start

使用

使用这块可以参考官方文档,底层的 runtime 默认是 Docker

kubernetes 为例,需要先安装 kubectl

1
brew install kubectl

然后再启动 colima

1
2
3
4
5
6
7
8
9
10
11
colima start --with-kubernetes

INFO[0000] starting colima
INFO[0000] runtime: docker+k3s
INFO[0000] preparing network ... context=vm
INFO[0000] starting ... context=vm
INFO[0030] provisioning ... context=docker
INFO[0030] starting ... context=docker
INFO[0035] provisioning ... context=kubernetes
INFO[0036] starting ... context=kubernetes
INFO[0040] done

查看状态

1
2
3
4
5
6
7
8
colima status

INFO[0000] colima is running
INFO[0000] arch: x86_64
INFO[0000] runtime: docker
INFO[0000] mountType: sshfs
INFO[0000] socket: unix:///Users/jakehu/.colima/default/docker.sock
INFO[0000] kubernetes: enabled

前记

最近在用 gin 开发一款小程序后端需要用到小程序登陆授权,看看如何实现

小程序登陆流程图

核心代码

核心代码实现,这里主要是利用了 silenceper/wechat

InitWechat 函数主要是初始化 Wechat 同时去设置缓存

MiniProgramLogin 函数主要是去调用 Code2Session 进行授权,同时将 code 换成 session

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
package utils

import (
"github.com/gin-gonic/gin"
"github.com/silenceper/wechat/v2"
"github.com/silenceper/wechat/v2/cache"
miniConfig "github.com/silenceper/wechat/v2/miniprogram/config"
)

func InitWechat(c *gin.Context) *wechat.Wechat {
wc := wechat.NewWechat()
redisOpts := &cache.RedisOpts{
Host: "127.0.0.1:3306",
Database: 0, // redis db
MaxActive: 10, // 连接池最大活跃连接数
MaxIdle: 10, //连接池最大空闲连接数
IdleTimeout: 60, //空闲连接超时时间,单位:second

}
redisCache := cache.NewRedis(c, redisOpts)
wc.SetCache(redisCache)
return wc
}

func MiniProgramLogin(c *gin.Context, code string) (map[string]interface{}, error) {
wc := InitWechat(c)
cfg := &miniConfig.Config{
AppID: "APPID",
AppSecret: "APPSECRET",
}
miniprogram := wc.GetMiniProgram(cfg)
auth := miniprogram.GetAuth()
session, err := auth.Code2Session(code)

result := map[string]interface{}{}
if err != nil {
return result, err
}
result["openid"] = session.OpenID
result["session_key"] = session.SessionKey
result["unionid"] = session.UnionID
return result, nil
}

小程序

小程序需要调用 wx.login 获取到 code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
App({
globalData: {},
onLaunch: function () {
var token = wx.getStorageSync('token'); // 获取本地缓存
if (!token) {
wx.login({
success: function (_a) {
var code = _a.code; // 获取到登陆code
if (code) {
console.log(code);
// 在这里请求API进行登陆
}
}
});
return;
}
}
});

调用

1
utils.MiniProgramLogin(c, code)

cgin 上下文对象 *gin.Context

code 为前端传入小程序 wx.login 获取到的 code


纵有千古,横有八荒;前途似海,来日方长。

单次构造

这种方式打包环境和运行环境都在同一个 Golang 底层镜像中

1
2
3
4
5
6
7
8
9
FROM golang:1.17-alpine
WORKDIR /build # 工作目录
ADD . ./ # 复制文件到工作目录
ENV GO111MODULE=on \ # 设置Go Module模式,并修改代理
GOPROXY=https://goproxy.cn
RUN go mod download # 下载go.mod里面所用到的包
RUN go build -o hejiusha . # 编译
EXPOSE 8888 # 暴露端口
ENTRYPOINT ["./hejiusha"] # 运行

多段构造

这种方式可以把编译环境和运行环境进行分割,极大的减小了运行镜像的大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#----------build----------#
FROM golang:1.17-alpine AS builder
WORKDIR /build # 工作目录
ADD . ./ # 复制文件到工作目录
ENV GO111MODULE=on \ # 设置Go Module模式,并修改代理
GOPROXY=https://goproxy.cn
RUN go mod download # 下载go.mod里面所用到的包
RUN go build -o hejiusha . # 编译

#----------run----------#
FROM alpine:latest # 运行环境换为alpine
WORKDIR /build/ # 采用相同的目录作为工作目录
COPY --from=builder /build . # 把golang镜像中编译好的二进制文件复制到运行镜像中
EXPOSE 8888 # 暴露端口
ENTRYPOINT ["./hejiusha"] # 运行

俗世洪流,站得住脚已是千辛万苦,想要出人头地,恐怕比登天还难。

前记

最近在做监控 Spring Boot /actuator/health 的时候,总是会出现一些莫名其妙的网络超时中断,于是想到了用重试机制来进行重试请求。

下面看看 Python 的第三方库 Tenacity

安装

1
pip install Tenacity

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
from tenacity import retry, stop_after_attempt, wait_fixed

# reraise 表示是否抛出原异常,默认为 False
# stop_after_attempt 表示最大尝试次数
# wait_fixed 表示等待时间
@retry(reraise=True, stop=stop_after_attempt(2), wait=wait_fixed(2))
def main():
print("Start Try " + str(main.retry.statistics["attempt_number"]))
resp = requests.get("https://httpstat.us/200?sleep=5000", timeout=3)
print(resp.status_code)

try:
main()
except Exception as e:
print(str(e))

输出:

1
2
3
Start Try 1
Start Try 2
HTTPSConnectionPool(host='httpstat.us', port=443): Read timed out. (read timeout=3)

上面示例中,设定超时为 3s 请求却 sleep=5000 此请求必然会超时

tenacity 只要遇见 raise 就会触发重试,上面代码中 requests 底层已经 raise,所以即使 main 函数中没有 raise 依然会重试

参考文档

Celery

Celery 是一个基于 Python 开发的分布式异步消息任务队列,通过它可以轻松的实现任务的异步处理

看看如何 CelerySanic 一起使用

使用

使用的组件

1
2
3
4
sanic==21.12.1
celery==5.2.3
redis==4.1.4
flower==1.0.0

tasks/task.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import time

from celery import Celery

REDIS_URI = "127.0.0.1"
REDIS_PORT = 6379
REDIS_DB = 0

app = Celery(
"tasks",
broker=f"redis://{REDIS_URI}:{REDIS_PORT}/{REDIS_DB}",
backend=f"redis://{REDIS_URI}:{REDIS_PORT}/{REDIS_DB}",
)


@app.task
def run_task(name):
time.sleep(5)
return "Hello, {}!".format(name)

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from sanic import Sanic
from sanic.response import text

from tasks.task import run_task

app = Sanic("MyHelloWorldApp")


@app.post("/start_task")
async def start_task(request):
result = run_task.delay("jakehu")
print(result)
return text("Task started.")


if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True, auto_reload=True)

worker.py

1
from tasks.task import app

运行 worker 进程

1
celery -A worker:app worker --loglevel=info --concurrency=1

Flower

Flower 是基于 web 的监控和管理 Celery 的工具

使用

1
celery --broker=redis://127.0.0.1:6379/0 flower --port=5566

Celery 参考文档
Flower 参考文档

前记

当使用 Sqlalchemy 查询数据通过 ujsonjson 工具 dumps 时会报以下错误

1
TypeError: datetime.datetime(2022, 2, 20, 14, 17, 23) is not JSON serializable

解决

可以通过在 SQL 查询的时候先行进行格式化

1
2
3
4
5
6
7
8
select(
order.c.id,
func.date_format(order.c.time, "%Y-%m-%d %H:%i:%s"),
)
.select_from(order)
.where(
order.c.uid == 1,
)

路线

升级前明确升级路线
参考

备份

利用 gitlab-backup 命令进行备份

1
docker exec -t <container name> gitlab-backup create

复制备份至宿主机

1
2
cd ~/gitlab/backups
docker cp <container name>:/var/opt/gitlab/backups/xxx_gitlab_backup.tar .

升级

停止现有容器

1
docker stop <container name>

删除现有容器

1
docker rm <container name>

拉取新的容器镜像

1
docker pull gitlab/gitlab-ce:13.12.15-ce.0

启动新的容器

1
docker-compose -f gitlab.yml up -d

docker-compose 配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
web:
image: 'gitlab/gitlab-ce:13.12.15-ce.0'
restart: always
hostname: 'git.xxx.com'
environment:
GITLAB_OMNIBUS_CONFIG: |
gitlab_rails['gitlab_shell_ssh_port'] = 2224
ports:
- '8081:80'
- '2224:22'
volumes:
- '/opt/gitlab/config:/etc/gitlab'
- '/opt/gitlab/logs:/var/log/gitlab'
- '/opt/gitlab/data:/var/opt/gitlab'

安装

1
2
3
wget --no-check-certificate https://dlcdn.apache.org/kafka/3.0.0/kafka_2.13-3.0.0.tgz  
tar -xzf kafka_2.13-3.0.0.tgz
cd kafka_2.13-3.0.0

配置

配置参数前假设我们的节点为:10.200.128.106

配置文件路径:config/kraft/server.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 节点角色
process.roles=broker,controller

# 节点的ID,唯一
node.id=1

# 连接的集群地址字符串,每个服务器一样,单机就是本机
controller.quorum.voters=1@10.200.128.106:9093

# IP:Port,每个服务器不一样
listeners=PLAINTEXT://10.200.128.106:9092,CONTROLLER://10.200.128.106:9093
inter.broker.listener.name=PLAINTEXT

# IP:Port,每个服务器不一样
advertised.listeners=PLAINTEXT://10.200.128.106:9092

# 日志目录
log.dirs=/tmp/kraft-combined-logs

还有很多参数就不一一列举了

生成集群 ID

1
2
# bin/kafka-storage.sh random-uuid
VV7Jb8bWT-Oz_F99BNXvAQ

格式化日志目录

如果是集群应在每台机器上运行格式化命令,并且应该使用同一集群 ID

1
2
# bin/kafka-storage.sh format -t VV7Jb8bWT-Oz_F99BNXvAQ -c ./config/kraft/server.properties
Formatting /tmp/kraft-combined-logs

启动

1
bin/kafka-server-start.sh ./config/kraft/server.properties

也可以加上 -daemon 实现后台守护

停止

1
bin/kafka-server-stop.sh stop

创建 Topics

1
2
# bin/kafka-topics.sh --create --partitions 1 --replication-factor 1 --topic topic-name --bootstrap-server 10.200.128.106:9092
Created topic topic-name.

生产者

1
2
3
4
bin/kafka-console-producer.sh --topic topic-name --bootstrap-server 10.200.128.106:9092
>test1
>test2
>test3

可以按 Ctrl+C 结束生产者进程

消费者

1
2
3
4
# bin/kafka-console-consumer.sh --topic topic-name --from-beginning --bootstrap-server 10.200.128.106:9092
test1
test2
test3

可以按 Ctrl+C 结束消费者进程