0%

(本文由腾讯混元模型辅助编写)

在软件开发中,业务逻辑的实现位置一直是一个值得探讨的话题。传统的做法是将业务逻辑放在应用层,例如 Python、Java 等编程语言中。然而,将部分业务逻辑实现在数据库中,如使用 PostgreSQL 的 PL/pgSQL 函数,也有其独特的优势。本文将以一个具体的例子来说明在数据库中实现业务逻辑在效率、安全性和可维护性等方面的优势。

示例场景

假设我们有一个系统,需要限制不同级别的用户在指定时间段内使用某个功能的次数。我们可以根据用户的会员级别来控制使用上限。下面是一个使用 PostgreSQL 函数实现该逻辑的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CREATE OR REPLACE FUNCTION public.update_usage(uid uuid, idx integer, lim integer, cnt integer)
RETURNS TABLE(used integer, "limit" integer)
LANGUAGE plpgsql
AS $function$
BEGIN
-- 插入或更新数据,确保操作是原子的
RETURN QUERY
INSERT INTO usage (user_id, index, used, "limit")
VALUES (uid, idx, cnt, lim)
ON CONFLICT (user_id, index)
DO UPDATE SET
used = usage.used + EXCLUDED.used
,
"limit" = EXCLUDED."limit"
WHERE usage.used + EXCLUDED.used <= usage."limit"
RETURNING usage.used, usage."limit";
END;
$function$;

效率优势

  1. 减少网络开销:在数据库中实现业务逻辑可以减少网络传输的数据量。例如,直接在数据库中进行插入或更新操作,而不是在应用层进行多次数据库交互。

安全性优势

  1. 原子性操作:使用数据库事务和锁机制,可以确保操作的原子性,避免并发问题。
  2. 权限控制:数据库层可以更细粒度地控制权限,确保只有授权的用户才能执行特定的操作。
  3. 防止 SQL 注入:在数据库层实现业务逻辑,可以使用参数化查询,有效防止 SQL 注入攻击。

可维护性优势

  1. 集中管理:将业务逻辑集中在数据库层,可以减少应用层的代码复杂度,便于维护。
  2. 一致性:数据库层的业务逻辑可以确保数据的一致性,避免因应用层逻辑不一致导致的数据错误。

对比示例

直接在 Python 代码中进行 SQL 查询

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

def update_usage(uid, idx, lim, cnt):
conn = psycopg2.connect(database="yourdb", user="youruser", password="yourpass", host="yourhost", port="yourport")
cur = conn.cursor()

try:
cur.execute("BEGIN")
cur.execute("INSERT INTO usage (user_id, index, used, limit) VALUES (%s, %s, %s, %s) ON CONFLICT (user_id, index) DO UPDATE SET used = usage.used + EXCLUDED.used, limit = EXCLUDED.limit WHERE usage.used + EXCLUDED.used <= usage.limit RETURNING usage.used, usage.limit", (uid, idx, cnt, lim))
result = cur.fetchone()
conn.commit()
except Exception as e:
conn.rollback()
raise e
finally:
cur.close()
conn.close()

return result

直接调用 PostgreSQL 函数

以 supabase 为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from supabase import create_client
import os

SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_KEY = os.getenv("SUPABASE_KEY")

supabase = create_client(SUPABASE_URL, SUPABASE_KEY)


def update_usage(uid, idx, lim, cnt):
result = supabase.rpc(
"update_usage", {"uid": uid, "idx": idx, "lim": lim, "cnt": cnt}
)
return result

结论

在数据库中实现业务逻辑,如使用 PostgreSQL 的 PL/pgSQL 函数,可以在效率、安全性和可维护性等方面带来显著优势。通过减少网络开销、确保操作的原子性、细粒度的权限控制、防止 SQL 注入以及集中管理和一致性保证,数据库层的业务逻辑实现可以大大提升系统的整体性能和可靠性。当然,具体选择哪种方式还需根据实际业务需求和系统架构进行权衡。

近期,我们项目组的小程序成功上线,为了方便社群运营,将小程序链接集成至企业微信群的欢迎语中。小程序链接由后端接口动态生成,且设置了最长30天的有效期限制。为简化链接获取流程,我专门编写了一段 Deno 脚本,实现链接自动获取并通过飞书机器人同步至群聊。鉴于 Deno 运行时对定时任务的内置支持,此次我选择直接采用 Deno Cron 进行任务调度,而非传统的 crontab 管理方式。

Deno Cron 简介

Deno 是一个现代的 JavaScript 和 TypeScript 运行时,它内置了对 cron 表达式的支持,使得定时任务的编写和管理变得非常简单。Deno 的 Deno.cron 函数允许你定义定时任务,并指定执行的时间间隔。

与 Crontab 的对比

传统的 crontab 是 Unix/Linux 系统中用于定时执行任务的工具。与 Deno cron 相比,crontab 更加底层,需要手动管理任务的执行时间和脚本路径。而 Deno cron 提供了更高层次的抽象,使得任务的定义和执行更加直观和便捷。

使用 Docker 运行 Deno Cron

作为一个长期运行的后台进程,我使用了Docker容器作为定时任务的运行时。以下是一个示例命令,展示了如何使用 Docker 运行 Deno cron 任务:

1
docker run -v $(pwd):/app -w /app --name deno-schedule -d denoland/deno deno run -A --unstable-cron task.ts

这个命令做了以下几件事:

  1. 使用 denoland/deno 镜像。
  2. 将当前目录挂载到 Docker 容器的 /app 目录。
  3. 设置工作目录为 /app
  4. 后台运行容器,并命名为 deno-schedule
  5. 运行 task.ts 文件,并启用 unstable cron 功能。

示例代码解析

下面是一个简单的 Deno 定时任务示例,它每周一上午 11 点生成一个小程序链接并报告到飞书:

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
async function getAccessToken() {
const resp = await fetch(
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=XXX&secret=XXX",
);
const data = await resp.json();
return data.access_token;
}

async function genMiniAppURL() {
const accessToken = await getAccessToken();
const resp = await fetch(
`https://api.weixin.qq.com/wxa/generate_urllink?access_token=${accessToken}`,
{
method: "POST",
body: JSON.stringify({
"is_expire": true,
"expire_type": 0,
"expire_time": new Date().getTime() + 1000 * 60 * 60 * 24 * 30,
}),
},
);
const data = await resp.json();
return data.url_link;
}

async function reportToFeishu() {
const url = await genMiniAppURL();
await fetch(
"https://open.feishu.cn/open-apis/bot/v2/hook/XXX",
{
method: "POST",
body: JSON.stringify({
msg_type: "interactive",
card: {
config: {
wide_screen_mode: true,
enable_forward: true,
},
header: {
title: {
content: "小程序链接",
},
},
elements: [
{
tag: "div",
text: {
content: `小程序链接: ${url}`,
},
},
],
},
}),
},
);
}

async function work(){
console.log("Fetch Miniapp URL.");
reportToFeishu();
}

Deno.cron("Fetch Miniapp URL", "0 11 1 * *", async () => {
await work();
});

work();

在这个示例中,我们定义了一个每周一上午 11 点执行的任务,它会生成一个小程序链接并通过飞书报告。

总结

总的来说,Deno 在服务端领域表现出色,其内置的 cron 支持让定时任务管理变得简单高效,相比传统 crontab 更直观便捷。示例代码清晰展示了其强大的功能和易用性,极大地简化了开发流程,提升了系统的稳定性和可维护性。

微软近期在其Windows Terminal的Canary版本中引入了Terminal Chat功能,这一创新允许用户与AI服务(例如GitHub Copilot)进行交互,从而获得与上下文紧密相关的智能建议。经过初步体验,我发现其效果颇为显著。

要开始使用,可以下载Canary版本的Windows Terminal,并在设置页面的Terminal Chat选项卡中完成对GitHub Copilot的配置。这样就可以在不离开终端的情况下,轻松地与Copilot进行对话,获取命令建议与解释。

作为一名后端工程师,知晓如何在PowerShell中查询使用特定端口的进程、可执行文件的路径以及该进程的运行命令是很有价值的技能。目前,我所知的方法仅限于使用netstat来查询使用特定端口的进程PID。

以下是我与GitHub Copilot的对话记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
User:
找到使用8000端口的进程、可执行文件地址和运行命令
Assistant:
To find the process, executable file path, and running command for the process using port 8000 in Windows Terminal with pwsh.exe, you can use the `Get-NetTCPConnection` and `Get-Process` cmdlets. Please follow the steps below:
1. Open Windows Terminal and launch pwsh.exe.
2. Run the following command to find the process using port 8000:
Get-NetTCPConnection | Where-Object {$_.LocalPort -eq 8000}
3. Note the value in the `OwningProcess` column. This is the Process ID (PID) of the process using port 8000.
4. Run the following command to find the executable file path of the process:
(Get-Process -Id [PID]).Path
Replace `[PID]` with the actual Process ID obtained in the previous step.
5. Run the following command to find the running command of the process:
(Get-Process -Id [PID] | Select-Object -ExpandProperty CommandLine)
Replace `[PID]` with the actual Process ID obtained in the first step.
These commands will allow you to identify the process, executable file path, and running command for the process using port 8000 in Windows Terminal with pwsh.exe.

为了验证这些步骤,我首先使用miniserve运行了一个使用8000端口的静态HTTP服务:

1
miniserve -p 8000 .

随后,我严格按照Copilot的指示操作,并成功地获取了进程的PID、可执行文件的路径以及该进程的运行命令。

terminal-chat

总的来说,这次体验相当令人满意。

在 SQL 的世界里,递归公共表表达式(Recursive Common Table Expressions,简称 CTE)是一种强大的工具,它允许我们在查询中执行递归操作,从而解决一些传统 SQL 难以处理的问题。

什么是递归 CTE

递归 CTE 通过定义一个初始查询和一个递归查询,不断地将结果集与自身进行联合,直到满足某个终止条件。

递归 CTE 的基本语法如下:

1
2
3
4
5
6
7
8
9
10
11
WITH RECURSIVE cte_name (column_list) AS (
-- 初始查询
SELECT ...
UNION ALL
-- 递归查询
SELECT ...
FROM cte_name
WHERE ...
)
SELECT ...
FROM cte_name;

递归 CTE 有两个主要部分:

  1. 初始查询:定义递归的起始点。
  2. 递归查询:定义如何将当前结果集与自身联合以生成下一层的结果。

递归 CTE 有一些限制:

  • 递归查询必须引用 CTE 自身。
  • 必须有一个明确的终止条件,以防止无限递归。
  • UNION ALL用于合并初始查询和递归查询的结果,因为UNION会去除重复行,而递归过程中可能会产生重复。

使用递归 CTE 实现一些常见的算法

递归 CTE 在数据分析中的常见用法是查询层次结构的数据,例如,可以查询一个组织结构中的所有员工及其上级,或者查询一个文件系统中的所有文件及其父目录。

以下是一个简单的递归 CTE 示例,用于查询一个组织结构中的所有员工及其上级:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
WITH RECURSIVE org_hierarchy AS (
-- 初始查询:选择顶层员工
SELECT employee_id, manager_id, employee_name
FROM employees
WHERE manager_id IS NULL

UNION ALL

-- 递归查询:选择每个员工的下属
SELECT e.employee_id, e.manager_id, e.employee_name
FROM employees e
INNER JOIN org_hierarchy oh ON e.manager_id = oh.employee_id
)
SELECT * FROM org_hierarchy;

另一方面,递归 CTE 使 SQL 成为了一门图灵完备的语言,因而也可以实现几乎所有算法。

模拟 for 循环

递归 CTE 最简单的算法应用就是生成一系列数字,用于模拟 for 循环:

1
2
3
4
5
6
7
8
9
WITH RECURSIVE count(n) AS (
SELECT 0
UNION ALL
SELECT n + 1
FROM count
WHERE n < 20
)
SELECT n
FROM count;

阶乘计算

在公共表中的每一行维护多列数据,就可以在循环的基础上进行更多的操作,例如计算阶乘。

1
2
3
4
5
6
7
8
9
WITH RECURSIVE factorial(n, fact) AS (
SELECT 1, 1
UNION ALL
SELECT n + 1, (n + 1) * fact
FROM factorial
WHERE n < 10
)
SELECT n, fact
FROM factorial;
Read more »

JavaScript 标准提供了 EventSource 接口,用于与服务器发送事件(Server-Sent Events,SSE)接口进行通信。

1
2
3
4
5
6
7
8
9
var evtSource = new EventSource("sse.php");
var eventList = document.querySelector("ul");

evtSource.onmessage = function (e) {
var newElement = document.createElement("li");

newElement.textContent = "message: " + e.data;
eventList.appendChild(newElement);
};

上面的代码片段来源于 MDN,展示了 EventSource 的基本用法:通过后端接口 "sse.php" 接收事件,并在 onmessage 回调中处理事件(本例中是将事件数据添加到页面的列表中)。

然而,EventSource 存在一些局限性,最明显的是它仅支持连接到 GET 接口,无法使用 POST 等其他 HTTP 方法,也无法通过 HTTP Body 传输数据。这对于一些需要通过 POST 请求发送参数的应用场景会受到限制。

在生成式 AI 应用中,OpenAI 的对话接口支持流式传输数据,这种传输方式通常实现为 SSE。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import OpenAI from "openai";

const openai = new OpenAI();

async function main() {
const stream = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Say this is a test" }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content || "");
}
}

main();
Read more »

Deno 2.0发布

Deno 2.0已经正式发布,其最大的亮点是大幅增强了与 Node.js 的兼容性(支持 package.json 和 node_modules 目录)。这使得从 Node.js 迁移到 Deno 变得更加简便,特别是在处理旧有项目时,极大地降低了迁移成本。

最近我也在项目中开始使用 Deno,尤其是作为 Supabase Edge Functions 的运行时环境。相比 Node.js,Deno 更加轻量,适合无服务器(serverless)架构中的后端 JavaScript 运行时。

Non GIL Python的性能提升

Youtube 视频How Much FASTER Is Python 3.13 Without the GIL?讨论了无GIL的Python 3.13在多线程场景下带来的性能提升以及在无GIL的Python版本中编写代码的注意事项。

2024年诺贝尔物理学奖

2024年诺贝尔物理学奖授予了约翰·霍普菲尔德和杰弗里·辛顿,以表彰他们在人工神经网络实现机器学习方面的基础性发现和发明。

只能说,Physics的cs是Compute Science。

2024年诺贝尔化学奖

2024年诺贝尔化学奖授予了美国华盛顿大学的戴维·贝克(David Baker)、英国伦敦谷歌DeepMind公司的德米斯·哈萨比斯(Demis Hassabis)和约翰·江珀(John M. Jumper),以表彰他们在蛋白质设计和蛋白质结构预测领域的贡献。

Chemistry的cs也是Computer Science。

工作吐槽

  • GPU云服务器的价格着实有点高。

Python 3.13 正式发布

Python 3.13 于 10 月 7 日正式发布,带来的新特性有:

  • REPL 体验优化:增加了多彩输出、支持多行编辑,并且 exit 和 help 命令可以直接使用,无需加括号。
  • 异常提示增强:更智能地提示错误,例如在导入与第三方模块同名的本地模块时提供清晰的警告。
  • 非 GIL 版本:解锁了多线程能力,但需要独立编译 Python,同时使用的 C 扩展库也需额外适配和编译。
  • JIT 支持:引入了即时编译技术(JIT),提升性能,但需单独编译。

Tauri 2.0 稳定版发布

Tauri 2.0 稳定版于 10 月 2 日正式发布,新特性有:

  • 移动支持:扩展了单一体验,现在包括 iOS 和 Android 平台的支持。
  • 插件系统:构建了一个更先进的插件系统,允许社区更容易地贡献和维护插件。
  • 系统权限管理:引入了基于权限、范围和能力的灵活但简单的访问控制系统。
  • 性能优化:改进了 IPC 层,支持 Raw Payloads,提高了数据传输效率。
  • 多平台开发体验:改进了开发工具和流程,使得桌面和移动平台的开发体验更加一致。

其他消息

  • 鸿蒙 Next 开启公测,腾讯提供了一个适配鸿蒙 Next 的微信版本(目前看来不支持小程序)。

工作吐槽

  • 苹果开发者账号注册有些麻烦,要求使用 Apple ID 在 Mac OS 或者 iOS 上的苹果开发者 App 中注册,还需要每年一百美元的费用。

在编程中,循环是一个常见的结构,用于重复执行某个动作直到满足特定条件。本文以一个简单的猜数字游戏为例,介绍了三种不同的循环方式以及如何使用 iter 函数来优化代码。

1. 使用 while True 循环

最基本的方式是使用 while True 循环,这种方法通过在循环内部使用 break 语句终止循环。例如:

1
2
3
4
5
6
7
8
9
10
while True:
guess = int(input("Enter an integer(1-100) : "))

if guess == NUMBER:
print("Congratulations, you guessed it.")
break
elif guess < NUMBER:
print("No, it is a little higher than that.")
else:
print("No, it is a little lower than that.")

虽然这种方法很常见,但它有一个“坏味道”,因为 while True 暗示了一个无限循环,可能会导致代码难以理解和维护。

2. 使用赋值表达式优化 while 循环

Python 3.8 引入了赋值表达式(即“海象运算符”:=),可以将输入和条件检查合并,避免使用 while True:

1
2
3
4
5
6
while (guess := int(input("Enter an integer(1-100) : "))) != NUMBER:
if guess < NUMBER:
print("No, it is a little higher than that.")
else:
print("No, it is a little lower than that.")
print("Congratulations, you guessed it.")

这种方式减少了代码的冗余,使得循环条件更加明确。

3. 使用 iter 函数和 for 循环

iter 函数可以将一个可调用对象转换为生成器,并通过传入一个哨兵值来控制生成器的终止。使用 iter 函数,我们可以用 for 循环来替代 while 循环:

1
2
3
4
5
6
7
for guess in iter(lambda: int(input()), NUMBER):
if guess < NUMBER:
print("No, it is a little higher than that.")
else:
print("No, it is a little lower than that.")

print("Congratulations, you guessed it.")

与 while True 循环相比,for 循环在代码审查中更具有说服力,因为它更清晰地表达了循环的结束条件。

使用 iter 函数实现附加功能

iter 函数的另一个优势是它可以很容易地结合 enumerate 函数来实现附加功能,例如统计猜测次数:

1
2
3
4
5
6
7
8
for times, guess in enumerate(iter(lambda: int(input()), NUMBER), start=1):
if guess < NUMBER:
print(f"No, it is a little higher than that. Wrong Guess Times: {times}")
else:
print(f"No, it is a little lower than that. Wrong Guess Times: {times}")


print(f"Congratulations, you guessed it. Wrong Guess Times: {times}")

相比之下,使用 while 循环则需要手动维护一个计数变量,这增加了代码的复杂性。

1
2
3
4
5
6
7
8
9
times = 0
while (guess := int(input("Enter an integer(1-100) : "))) != NUMBER:
times += 1
if guess < NUMBER:
print(f"No, it is a little higher than that. Wrong Guess Times: {times}")
else:
print(f"No, it is a little lower than that. Wrong Guess Times: {times}")

print(f"Congratulations, you guessed it. Wrong Guess Times: {times}")

总结

本文通过实例介绍了如何在 Python 中使用不同的循环结构,特别是如何利用 iter 函数优化代码。通过减少对 while True 循环的依赖,代码将更易读、更易维护。希望读者在未来的编程中能够灵活应用这些技巧。

大语言模型的函数调用能力是指其能够调用外部工具或API来实现特定功能的能力。这种能力使得大语言模型能够超越自身的限制,访问实时信息、执行复杂计算或操作外部系统,从而增强其解决问题的能力。

Openai的对话模型通过其API支持函数调用功能,其实现方式为:

  • API参数设置:在调用OpenAI的API时,可以通过tools参数提供函数调用的规范。这包括函数的名称、描述和参数。
  • 模型输出处理:模型不会直接执行函数调用,而是生成一个包含函数名称和参数的JSON对象。开发人员需要使用这个对象来实际调用函数。
  • 函数调用的响应:如果模型决定调用函数,其输出将包含在finish_reason为tool_call的响应中,包含要调用的函数和参数信息。
  • 手动调用函数后,创建一个role为tool的消息添加到历史消息列表中,再调用一次对话模型。模型会根据函数调用的结果返回相应的信息。

llm-function-call

Read more »