开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

用更合适的文档库!打造专属品质,尽显码农风采! 前言 一、调

发表于 2024-04-28

作者:易师傅 、github

声明:本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

前言

这是一个《实现 Headless UI无头组件的系列专栏》之文档篇。

承接上文,我们已经知道了如何去实现一个 headless ui 无头组件的测试用例环节,那么我们继续往下走;

一个合格的组件工具等库,我们不仅要会知其然,更要知其所以然;

同时我们也要 高效的传播知识和技能 和 促进大家交流和协作,达到让别人开箱即用,尽量降低使用成本,提升大家的研发使用效率;

那么一个文档就必不可少了,而且还要一看秒懂的文档。

一、调研&对比

随着技术的发展,到了 4202 年,市面上已经有了许许多多的文档开源仓库可以开箱即用了;

有一键生成的,有高度可定制的,有支持多语言的;

我想选择哪个呢,你有你的决定,我这里就分析对比一下现在市面上用的较多的库。

  1. Storybook

Storybook 是一个开源的 UI 组件开发与文档生成工具,广泛应用于 React、Vue、Angular、Svelte 等多种前端框架的组件开发环境;它可以让你独立于主应用程序创建和展示 UI 组件,并生成详细的文档。

优点:

  • 支持多种前端框架,高度可定制。
  • 提供交互式的组件演示和测试环境。
  • 强大的故事(stories)管理功能,利于组件的迭代和复用。
  • 丰富的插件生态系统,支持多种附加功能如可视化测试、性能监控等。

缺点:

  • 对于小型项目或简单组件集,搭建 Storybook 可能显得较为繁琐。
  • 学习曲线对于初次使用者可能稍陡峭。
  1. Docz

Docz 是一个专门为 React 组件编写的静态站点生成器,旨在简化组件文档的创建过程,并提供一种优雅的方式来演示和记录组件库。它利用 Markdown 和 MDX(Markdown 扩展)语法,允许开发者在撰写文档的同时直接嵌入 React 组件,从而实现对组件功能、API 和使用示例的可视化展示。

优点

  • 组件驱动:非常适合展示 React 组件库及其 API,让开发者能够快速理解和使用组件。
  • 即时反馈:内置热重载功能,编辑文档和组件时可立即在预览窗口看到变化。
  • 定制化:提供主题定制能力,可以根据项目需求打造个性化的文档界面。
  • 高性能:基于 Gatsby 构建,因此性能优化较好,生成的文档站点加载速度快。

缺点

  • 框架局限:相较于其他通用文档生成工具,Docz 更专注于 React 生态系统,对于非 React 组件的支持相对有限。
  • 社区支持与更新:随着类似工具的发展,Docz 社区活跃度和更新速度可能会随着时间变化而有所波动。
  • 配置灵活性:尽管 Docz 提供了一些高级配置选项,但对于一些极其复杂的定制需求,可能不如其他更成熟的静态站点生成器灵活。
  1. VuePress

VuePress 是一个由Vue驱动的静态网站生成器,虽然主要用于写博客和文档,但它也可以用于构建组件文档库,特别是对于Vue.js项目。

VuePress 是由 Vue.js 作者尤雨溪创建的一个静态网站生成器,早期版本的 VuePress 使用 webpack 作为构建工具,支持丰富的插件体系和高度定制化。

优点:

  • 基于Vue.js,因此如果你的组件库是Vue的,那么集成起来相当自然。
  • 支持Markdown和Vue组件混合编写文档。
  • 提供了丰富的主题和插件系统,可以轻松定制文档样式。

缺点:

  • 对于非Vue项目的组件文档支持需要更多的自定义配置。
  1. Vitepress

VitePress 是一个基于 Vite 构建的静态网站生成器,旨在提供快速、简单和现代的文档编写和展示体验。它结合了 Vite 的快速开发能力和 Vue 3 的特性,适合用于构建各种类型的静态网站。

VitePress 是 VuePress 的“继承者”,由尤雨溪针对 Vue 3 和 Vite 技术栈设计的新一代静态网站生成器。

相比于 VuePress,VitePress 的配置更为简洁,去除了部分较为复杂的全局配置项,更注重开箱即用和轻量级体验。

优点

  • 快速开发体验:基于 Vite 的 HMR(Hot Module Replacement)技术,实现了近乎实时的页面刷新和代码更改同步。
  • 轻量级和高性能:精简的架构带来更小的构建体积和更快的加载速度,适合现代浏览器。
  • 易于使用:对于简单的静态站点和文档站点,VitePress 提供了直观易懂的配置和内容编写流程。
  • 原生模块支持:利用浏览器原生支持的 ES 模块导入,无需预先编译,简化了工作流程。
  • 内置 Vue 支持:无缝集成 Vue.js,允许在 Markdown 文件中使用 Vue 组件,增强文档的表现力。

缺点

  • 有限的可扩展性:VitePress 不像 VuePress 那样支持广泛的插件系统,这意味着如果需要非常特定的功能或者深度定制,可能需要自行修改底层代码或等待官方支持。
  • 成熟度与生态:相较于 VuePress,VitePress 的社区资源和插件生态相对较小,因为它是相对较新的项目。
  • 兼容性问题:VitePress 针对现代浏览器进行了优化,可能无法完美兼容所有旧版浏览器,特别是那些不支持原生 ES 模块的浏览器。

小结

选择哪个库取决于你的具体需求,比如使用的前端框架、是否需要实时预览、项目规模以及团队的技术栈。上述各工具都有各自的社区和成熟的文档支持,可以根据实际情况权衡选择。

因为我们主要是实现基于 Vue 的一个无头组件库,所以咱们最好的选择是 Vuepress 和 Vitepress ,但是它俩选哪个呢?

我们知道 VitePress 是一个面向未来的、轻便高效的静态网站生成器,特别适合那些重视开发效率、喜欢简洁工具链和技术栈现代化的开发者。

而对于需要大量自定义和拓展功能的大型项目, VuePress 或其他具有丰富插件生态的静态站点生成器可能是更好的选择。

所以这里咱们以 Vitepress 来作为咱们的文档基础库。

二、安装&使用vitepress

1) 安装

在之前的文章中,我们已经详细的告诉过大家为什么要使用文档了,而且最基本的文档库是如何实现的,详细的安装&使用过程可参见《实现一个 headless ui 无头组件库的配置docs文档部分》

为避免同学们难得跑一趟,我直接简单交代一下:

1
bash复制代码pnpm i vitepress -Dw

2) 初始化的 2 种方法(可二选一或自定义)

2.1 npx vitepress init 初始化

初始化:

1
bash复制代码npx vitepress init

回答几个简单的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bash复制代码┌  Welcome to VitePress!
│
◇ Where should VitePress initialize the config?
│ ./test
│
◇ Site title:
│ My Awesome Project
│
◇ Site description:
│ A VitePress Site
│
◆ Theme:
│ ● Default Theme (Out of the box, good-looking docs)
│ ○ Default Theme + Customization
│ ○ Custom Theme
└

结果:

image.png

运行

1
复制代码pnpm docs:dev

image.png

2.2 自定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bash复制代码# 1.创建 docs 文档
mkdir docs

# 2.初始化
cd docs && pnpm init

# 3.安装
pnpm i vitepress tailwindcss -D

# 4.配置 package.json
"scripts": {
"docs:dev": "vitepress dev",
"docs:build": "vitepress build",
"docs:preview": "vitepress preview"
},

# 5. 运行
pnpm docs:dev

那么我们实现完之后在根目录下就有一个 docs 目录:

image.png

小结:两种方式建议:

建议两种方式结合;

因为第一种方式,会把 vitepress 相关的安装包全部安装到根目录,会显得整个根目录的 package.json 文件显得非常臃肿;

这个当你在docs文档中所需插件较多时,更加明显;

所以最终方案如下:

  1. 先使用第一种方式创建 docs 文件;
  2. 再在 docs 目录下 pnpm init 初始化;
  3. 安装vitepress相关包在 docs 目录下安装即可;

结果如下:

image.png

根目录配置 scripts:

1
2
bash复制代码"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",

运行效果会发现和第一种方式一样

好了,到这里,我们就初步搭建好了文档的基本目录,接下来我们就要开始一步一步的来分析使用了。

三、分析开源文档库(vite)

我们先看一下 vite 官方文档的主要页面:

image.png

image.png

根据图片咱们可以看得出来,其实一个文档库,可以主要分为如下部分:

  • 首页
    • 介绍
    • 导航
    • 等等
  • 主页面
    • 左边导航
    • 右边导航
    • 顶部导航
    • 内容部分
    • 搜索部分
    • 等等

所以我们根据以上各个部分,就可以来进行各个模块的配置;

正好,在 vitepress 中有一个叫做 config.ts 就很完美的诠释了相关配置;

接下来,我们就着手准备配置了。

四、改造首页index.md

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
bash复制代码---
# https://vitepress.dev/reference/default-theme-home-page
layout: home
sidebar: false

titleTemplate: 一个用于在 Vue 中构建高质量、可访问的 Headless UI的组件库

hero:
name: YI UI
text: Vue 集成的无头组件库
tagline: 一个用于在 Vue 中构建高质量、可访问的 Headless UI的组件库
image: # 这里放你的logo图片
src: /logo.svg
alt: YI UI
actions:
- theme: fast
text: 快速开始
link: /guide/index
- theme: alt
text: API Examples
link: /api-examples
- theme: GitHub
text: View on GitHub
link: https://github.com/jeddygong/yi-ui

features:
- title: Vue, Nuxt supported.
details: 支持以 Vue 构建的任何框架
icon: <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path fill="#41b883" d="M24.4 3.925H30l-14 24.15L2 3.925h10.71l3.29 5.6 3.22-5.6Z"/><path fill="#41b883" d="m2 3.925 14 24.15 14-24.15h-5.6L16 18.415 7.53 3.925Z"/><path fill="#35495e" d="M7.53 3.925 16 18.485l8.4-14.56h-5.18L16 9.525l-3.29-5.6Z"/></svg>
- title: 节约时间,运行速度快
details: 节约时间,运行速度快
icon: 🚀
- title: 开箱即用的辅助功能
details: 符合 WAI-ARIA 标准。支持键盘导航和焦点管理。
icon: ⚡
- title: 强类型支持
details: 使用TypeScript编写,良好类型支持
icon: 🦾
- title: 开发体验完美
details: 无样式,很容易自定义,非常适合高度自定义且设计要求较高的应用程序
icon: 🛠
- title: 轻量级
details: 体积小巧
icon: ☁️
- title: 更多插件支持
details: (建设中...)
icon: 🔌
- title: 更多功能
details: (持续充电建设中...)
icon: 🔋
---

新建 public 文件:主要放一些静态资源

1
2
3
arduino复制代码mkdir public  

touch logo.svg

注意:放置在 public 中的资源将按原样复制到输出目录的根目录中,例如可以直接访问 logo.svg,而不需要 public/logo.svg。

运行后的效果:

image.png

五、配置 guide

根据上面的 index.md 文件中,我们有配置一个 action,并且路由地址 /guide;

因为 vitepress 是基于文件的路由,这意味着生成的 HTML 页面是从源 Markdown 文件的目录结构映射而来的。例如,给定以下目录结构:

1
2
3
4
5
6
bash复制代码.
├─ guide
│ ├─ getting-started.md
│ └─ index.md
├─ index.md
└─ prologue.md

生成的 HTML 页面会是这样:

1
2
3
4
bash复制代码index.md                  -->  /index.html (可以通过 / 访问)
prologue.md --> /prologue.html
guide/index.md --> /guide/index.html (可以通过 /guide/ 访问)
guide/getting-started.md --> /guide/getting-started.html

所以我们先配置一个简单的 guide:

  1. 运行命令:
  2. mkdir guide
  3. touch guide/index.md

编辑 guide/index.md 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
markdown复制代码---

title: 快速开始
description: 快速让你掌握 YI UI 的教程
name: popover
---

# 快速开始

<Description>

快速让你掌握 YI UI 的教程。

</Description>

## 安装

npm i @yi-ui/vue
pnpm i @yi-ui/vue
yarn add @yi-ui/vue

## 简单使用
...

但是我们运行结束后打开 guide 路由:

image.png

到这里,我们就配置好了最基本的新手指引了,内容这些就完全在于你的自定义了。

细心的同学,可能会发现,该页面的左边导航和顶部的导航,我们还没有处理,所以为了更加完善,我们需要一个一个的处理。

在完善前,我们先简单的都熟悉一下 .vitepress/config.ts 的基本配置

六、配置 config.ts

在配置 config.ts 之前,我们要先熟悉一下 vitepress config 的一些最基本的 API,这样咱们接下来配置才会事半功倍。

熟悉一下基本的常用的 API:

  1. title:站点的标题。使用默认主题时,这将显示在导航栏中。
  2. titleTemplate:允许自定义每个页面的标题后缀或整个标题。
  3. description:站点的描述。这将呈现为页面 HTML 中的 <meta> 标签。
  4. head:要在页面 HTML 的 <head> 标签中呈现的其他元素。用户添加的标签在结束 head 标签之前呈现,在 VitePress 标签之后。
  5. lang:站点的 lang 属性。这将呈现为页面 HTML 中的 <html lang="en-US"> 标签。
  6. base:站点将部署到的 base URL。如果计划在子路径例如 GitHub 页面)下部署站点,则需要设置此项。
  7. cleanUrls:当设置为 true 时,VitePress 将从 URL 中删除 .html 后缀。
  8. srcDir:相对于项目根目录的 markdown 文件所在的文件夹。
  9. outDir:项目的构建输出位置,相对于项目根目录。
  10. assetsDir:指定放置生成的静态资源的目录。
  11. appearance:是否启用深色模式
  12. lastUpdated:是否使用 Git 获取每个页面的最后更新时间戳。
  13. markdown:配置 Markdown 解析器选项。
  14. vite:将原始 Vite 配置传递给内部 Vite 开发服务器 / bundler。
  15. themeConfig:自定义主题
    • logo:导航栏上显示的 Logo
    • siteTitle:可以自定义此项以替换导航中的默认站点标题 (应用配置中的 title)
    • nav:顶部导航菜单项的配置。
    • sidebar:侧边栏菜单项的配置。
    • outline:显示标题的级别
    • socialLinks:可以定义此选项以在顶部右侧导航栏中展示带有图标的社交帐户链接,例如 github。
    • editLink:编辑链接可让显示链接以编辑 Git 管理服务 (例如 GitHub 或 GitLab) 上的页面。
    • lastUpdated:允许自定义上次更新的文本和日期格式。
    • algolia:搜索站点文档

当然还有一些其它的配置 API,但是也不常用,所以大家只需要知道即可;

如果想知道全部,可参见全部配置 API

基本的简单配置:

到这里我们知道了基本的一个配置 API,所以接下来我们就开始一个最基本的简单配置吧

1
2
3
4
5
6
7
8
ts复制代码import { defineConfig } from 'vitepress'
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: "YI UI",
titleTemplate: ':title - headlessui.dev',
description: '一个用于在 Vue 中构建高质量、可访问的 Headless UI的组件库',
cleanUrls: true,
})

运行后的结果:

image.png

当然这最基本的配置很显然不满足我们的需求;

我们上面有分析一个开源库有顶部导航、有侧边导航等等,所以我们接下来就要着手配置顶部导航和侧边导航了。

七、改造顶部导航 Nav 部分

如何配置顶部导航,我们上面有讲解配置 API 的时候有一个 themeConfig 的 API,它有一个属性叫 nav,这个属性非常重要,也是配置导航的最重要的工具;

编辑config.ts:

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
ts复制代码import { defineConfig } from 'vitepress'
import { version } from '../../package.json'

// https://vitepress.dev/reference/site-config
export default defineConfig({
title: "YI UI",
titleTemplate: ':title - headlessui.dev',
description: '一个用于在 Vue 中构建高质量、可访问的 Headless UI的组件库',
cleanUrls: true,
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: '指南', link: '/guide/' },
{ text: '文档', link: '/markdown-examples' },
{
text: `v${version}`,
items: [
{
text: 'Release Notes ',
link: 'https://github.com/jeddygong/yi-ui/releases',
},
],
},
],
}
})

运行结果:

image.png

八、改造左边导航 sidebar

编辑config.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ts复制代码themeConfig: {
sidebar: [
{
text: '简介',
collapsed: false,
items: [
{ text: '介绍', link: '/markdown-examples' },
{ text: '快速开始', link: '/api-examples' },
{ text: '无障碍', link: '/api-examples' },
{ text: '发布', link: '/api-examples' },
],
},
{
text: '指南',
collapsed: false,
items: [
{ text: '贡献指南', link: '/api-examples' },
{ text: '参考指南', link: '/api-examples' },
],
},
],
}

运行结果:

image.png

总结

到这里,我们文档的一些基本配置就介绍完毕了;

但是我们我们还可以拓展,这取决于每个文档的功能点,例如:

  • 搜索功能
  • 页脚功能
  • 中英功能
  • md文档插件
  • 等等

这些功能和项目的耦合度都比较高,就不在这篇文章详细介绍了,大家可以自行了解;

我们只需要知道能实现我们想要的效果即可。


接下来的安排:

  • 构建版本发布
  • 支持 Nuxt 调试

Headless UI 往期相关文章:

  1. 在 2023 年屌爆了一整年的 shadcn/ui 用的 Headless UI 到底是何方神圣?
  2. 实战开始 🚀 在 React 和 Vue3 中使用 Headless UI 无头组件库
  3. 无头组件库既然这么火 🔥 那么我们自己手动实现一个来完成所谓的 KPI 吧
  4. 泰裤辣 🚀 原来实现一个 Popover 无头组件比传统组件简单辣么多!!
  5. 手把手教你写一个 headless 无头组件的单元测试、集成测试、E2E 测试

Headless UI (1).png

如果想跟我一起讨论技术吹水摸鱼, 欢迎加入前端学习群聊

如果想一起讨论技术:欢迎加入技术讨论群

如果想一起吹水摸鱼:欢迎加入吹水摸鱼群

如果想一起老司机吹水摸鱼:欢迎加入老司机吹水摸鱼群(懂得都懂)

如果扫码人数满了,可以扫码添加个人 vx 拉你:JeddyGong

感谢大家的支持,码字实在不易,其中如若有错误,望指出,如果您觉得文章不错,记得 点赞关注加收藏 哦 ~

关注我,带您一起搞前端 ~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

基础商务原语:构建数字商务 开源日报 No237

发表于 2024-04-28

picture

medusajs/medusa

Stars: 22.4k License: MIT

medusa 是数字商务的构建模块。

  • 允许构建丰富、可靠和高性能的商务应用程序,无需重新发明核心商务逻辑。
  • 可定制化并用于构建高级电子商店、市场或任何需要基础性商务原语的产品。
  • 所有模块都是开源且在 npm 上免费提供。

hyperium/hyper

Stars: 12.1k License: MIT

hyper 是一个用于 Rust 的 HTTP 库。

  • 支持 HTTP/1 和 HTTP/2
  • 异步设计
  • 高性能
  • 经过测试和验证的正确性
  • 广泛应用于生产环境中

如果你正在寻找方便的 HTTP 客户端,可以考虑使用 reqwest。而如果不确定选择哪个 HTTP 服务器,可以考虑 axum 或 warp,后者采取了更加函数式的方法。这两个库都是构建在 hyper 之上。

HeyPuter/puter

Stars: 6.0k License: AGPL-3.0

static.osguider.com/subject/git…

puter 是一个在浏览器中的互联网操作系统和桌面环境。
该项目的主要功能、关键特性、核心优势:

  • 设计为功能丰富、速度异常快和高度可扩展。
  • 可用于构建远程桌面环境或作为云存储服务、远程服务器、Web 主机平台等界面。
  • 使用原生 JavaScript 和 jQuery 构建,以提升性能并避免复杂抽象。
  • 提供友好开源社区,可学习有关 Web 开发、云计算和分布式系统等知识。

UFund-Me/Qbot

Stars: 5.6k License: MIT

picture

Qbot 是一个 AI 自动量化交易机器人平台,赋能 AI 技术于量化投资领域。

  • 支持多种机器学习建模范式,包括监督学习、市场动态建模和强化学习。
  • 提供多种交易方式:在线回测、模拟交易和实盘自动化交易。
  • 多种提示方式:邮件、飞书、弹窗和微信。
  • 提供全闭环的量化投研流程,从数据获取到最终实盘交易。
  • 强调机器学习与强化学习的 AI 策略,并结合多因子模型提高收益比。
  • 提供 GUI 前端/客户端,并且后端负责数据处理和交易调度,实现事件驱动的完整交易流程。

xiaomabenten/system_architect

Stars: 1.0k License: MIT

system_architect 是一个备考资源库和免费刷题软件,为准备 2024 年系统架构设计师(软考高级)考试的人提供全面的学习资料和辅助工具。

  • 提供最新题库、历年真题、案例解析等多种学习资源
  • 包含教材、视频教程、论文技巧等丰富内容
  • 免费 PC 版刷题软件可用于练习
  • 项目从 2020 年开始免费维护至今,已帮助三万多人参加考试

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

11个Python循环技巧

发表于 2024-04-28

本文分享自华为云社区《Python中的循环技巧指南》,作者:柠檬味拥抱。

当我们处理数据时,有时候需要创建多个列表以存储不同类型或不同条件下的数据。在Python中,我们可以利用循环来快速、高效地创建这些列表。本文将介绍如何使用循环在Python中创建多个列表,并提供代码实例。

python用循环新建多个列表

在Python中,我们可以使用列表推导式或循环结合条件语句来创建多个列表。下面是一些常见的场景和对应的代码示例:

1. 创建固定数量的空列表

假设我们需要创建多个空列表,可以使用列表推导式和循环:

1
2
3
4
5
ini复制代码# 使用列表推导式创建多个空列表
num_lists = 5
empty_lists = [[] for _ in range(num_lists)]

print(empty_lists)

这将创建一个包含5个空列表的列表。

2. 根据条件创建多个列表

有时候,我们需要根据特定条件创建不同的列表。例如,我们想将奇数和偶数分别存储在两个列表中:

1
2
3
4
5
6
7
8
9
10
11
12
13
css复制代码# 创建奇数和偶数列表
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
odd_numbers = []
even_numbers = []

for num in numbers:
if num % 2 == 0:
even_numbers.append(num)
else:
odd_numbers.append(num)

print("奇数列表:", odd_numbers)
print("偶数列表:", even_numbers)

这将根据数字的奇偶性创建两个列表。

3. 根据字符串长度创建多个列表

有时候,我们需要根据字符串的长度来将字符串分类并存储在不同的列表中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
css复制代码# 根据字符串长度创建多个列表
words = ["apple", "banana", "orange", "pear", "grape", "kiwi"]
short_words = []
medium_words = []
long_words = []

for word in words:
if len(word) < 5:
short_words.append(word)
elif len(word) < 7:
medium_words.append(word)
else:
long_words.append(word)

print("短单词列表:", short_words)
print("中等长度单词列表:", medium_words)
print("长单词列表:", long_words)

这将根据字符串的长度将单词分别存储在三个不同的列表中。

4. 根据数据类型创建多个列表

有时候,我们需要根据数据的类型将其存储在不同的列表中。例如,我们想将整数、浮点数和字符串分别存储在不同的列表中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
css复制代码# 根据数据类型创建多个列表
data = [1, 2.5, "apple", 4, "banana", 6.7, "orange", 8, 9, "pear"]
integers = []
floats = []
strings = []

for item in data:
if isinstance(item, int):
integers.append(item)
elif isinstance(item, float):
floats.append(item)
elif isinstance(item, str):
strings.append(item)

print("整数列表:", integers)
print("浮点数列表:", floats)
print("字符串列表:", strings)

这将根据数据的类型将其存储在三个不同的列表中。

5. 根据条件动态创建多个列表

有时候,我们需要根据动态变化的条件来创建多个列表。例如,我们想根据用户输入的数量创建相应数量的列表:

1
2
3
4
5
python复制代码# 根据用户输入动态创建多个列表
num_lists = int(input("请输入要创建的列表数量: "))
lists = [[] for _ in range(num_lists)]

print("已创建", num_lists, "个空列表:", lists)

这将根据用户输入的数量动态创建相应数量的空列表。

6. 创建包含特定范围的数字的多个列表

有时候,我们需要根据特定范围的数字来创建多个列表。例如,我们想要将0到9之间的数字按照个位数分别存储在十个列表中:

1
2
3
4
5
6
7
8
9
10
scss复制代码# 创建包含特定范围的数字的多个列表
num_lists = 10
range_lists = [[] for _ in range(num_lists)]

for num in range(10):
range_lists[num % num_lists].append(num)

print("按照个位数存储的列表:")
for i, lst in enumerate(range_lists):
print(f"列表{i}:", lst)

这将按照个位数将0到9之间的数字分别存储在十个列表中。

7. 根据键的哈希值创建多个列表

有时候,我们想根据键的哈希值将数据分组存储在多个列表中。例如,我们有一组键值对,我们想根据键的哈希值将其存储在不同的列表中:

1
2
3
4
5
6
7
8
9
10
11
12
ini复制代码# 根据键的哈希值创建多个列表
data = {"apple": 3, "banana": 5, "orange": 2, "pear": 4, "grape": 6}
num_lists = 3
hash_lists = [[] for _ in range(num_lists)]

for key, value in data.items():
hash_index = hash(key) % num_lists
hash_lists[hash_index].append((key, value))

print("根据键的哈希值存储的列表:")
for i, lst in enumerate(hash_lists):
print(f"列表{i}:", lst)

这将根据键的哈希值将键值对存储在三个不同的列表中。

8. 根据列表中元素的属性创建多个列表

有时候,我们需要根据列表中元素的属性值来创建多个列表。例如,假设我们有一组学生对象,我们想根据他们的成绩将他们分别存储在及格和不及格的两个列表中:

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
python复制代码# 根据学生对象的成绩创建多个列表
class Student:
def __init__(self, name, score):
self.name = name
self.score = score

students = [
Student("Alice", 85),
Student("Bob", 60),
Student("Charlie", 75),
Student("David", 40),
Student("Emma", 95)
]

passing_students = []
failing_students = []

for student in students:
if student.score >= 60:
passing_students.append(student)
else:
failing_students.append(student)

print("及格学生列表:")
for student in passing_students:
print(f"{student.name}: {student.score}")

print("\n不及格学生列表:")
for student in failing_students:
print(f"{student.name}: {student.score}")

这将根据学生对象的成绩将学生分别存储在及格和不及格的两个列表中。

9. 根据索引范围创建多个列表

有时候,我们需要根据索引范围将列表切分成多个子列表。例如,我们有一个包含一组数字的列表,我们想将其分割成若干个小的子列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
ini复制代码# 根据索引范围创建多个列表
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
num_lists = 3
split_lists = []

for i in range(num_lists):
start_index = i * len(numbers) // num_lists
end_index = (i + 1) * len(numbers) // num_lists
split_lists.append(numbers[start_index:end_index])

print("切分后的列表:")
for i, lst in enumerate(split_lists):
print(f"列表{i + 1}:", lst)

这将根据索引范围将原始列表切分成三个子列表。

10. 根据列表元素的值进行分组创建多个列表

有时候,我们需要根据列表元素的值进行分组,并将它们存储在不同的列表中。例如,假设我们有一组整数,我们想根据它们的奇偶性将它们分别存储在两个列表中:

1
2
3
4
5
6
7
8
9
10
11
12
13
css复制代码# 根据列表元素的值进行分组创建多个列表
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
odd_numbers = []
even_numbers = []

for num in numbers:
if num % 2 == 0:
even_numbers.append(num)
else:
odd_numbers.append(num)

print("奇数列表:", odd_numbers)
print("偶数列表:", even_numbers)

这将根据列表元素的奇偶性将其分别存储在奇数和偶数列表中。

11. 根据元素是否满足条件创建多个列表

有时候,我们需要根据元素是否满足特定条件来将它们存储在不同的列表中。例如,假设我们有一组数字,我们想将大于等于5和小于5的数字分别存储在两个列表中:

1
2
3
4
5
6
7
8
9
10
11
12
13
css复制代码# 根据元素是否满足条件创建多个列表
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
greater_than_5 = []
less_than_5 = []

for num in numbers:
if num >= 5:
greater_than_5.append(num)
else:
less_than_5.append(num)

print("大于等于5的数字列表:", greater_than_5)
print("小于5的数字列表:", less_than_5)

这将根据数字是否大于等于5来将其存储在两个不同的列表中。

总结

总的来说,本文介绍了在Python中利用循环创建多个列表的多种方法,并通过具体的代码示例进行了展示和解释。从根据固定数量、条件、数据类型、属性、索引范围、哈希值等不同角度出发,我们探讨了如何灵活地利用循环结合列表推导式或条件语句来创建多个列表。这些方法不仅提高了代码的灵活性和可维护性,还能够加速开发过程并提高程序的性能。

通过本文的学习,读者可以掌握在Python中处理数据并组织成多个列表的技巧,从而更加有效地进行数据操作和管理。同时,灵活运用循环和列表推导式等Python特性,能够使代码更加简洁、清晰和优雅。在实际项目中,根据具体需求选择合适的方法来创建多个列表将会成为编程中的重要技能,帮助提高代码的质量和效率。

点击关注,第一时间了解华为云新鲜技术~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

RLocalCachedMap优化和建议

发表于 2024-04-28

关于juejin.cn/post/736132… (基于Redission高级应用5-RLocalCachedMapt原理及实战应用) 中提到RLocalCachedMapt的使用,有老铁提出该map里面的每个key都开一个定时任务监听,map的key变多会导致内存溢出,由此想着如何优化上篇文章内容:
RLocalCachedMapRLocalCachedMap 是 Redisson 提供的一个本地缓存客户端,它在本地存储了远程 Redis 中的数据副本,以减少网络开销和提高性能。然而,如果不正确使用,它可能会导致资源消耗问题,如您提到的,如果键值对非常多,每个键都设置了过期监听器,就可能导致大量的定时任务占用内存,最终导致OOM(内存溢出)。

为了避免RLocalCachedMap的这个缺点,可以采取以下策略:

  1. 限制本地缓存大小:通过配置localCacheSize参数限制本地缓存的大小,以避免过多的键值对被缓存到本地。
  2. 合理设置过期时间:如果所有键都设置了过期时间,可以考虑增加过期时间或者不为每个键设置监听器,而是使用定时任务批量清理过期键。
  3. 使用弱引用:配置RLocalCachedMap使用弱引用存储键值对,这样一旦内存不足,这部分内存可以被垃圾收集器回收。
  4. 监听器优化:如果需要监听过期事件,可以考虑使用单个监听器来处理多个键的事件,而不是为每个键单独设置监听器。
  5. 按需使用:只对热点数据使用本地缓存,不是所有数据都需要本地缓存。
  6. 缓存清理策略:定期清理不常用或低优先级的缓存数据,可以手动或使用定时任务。
  7. 内存监控:实施内存使用监控,当达到一定阈值时触发报警或自动清理策略。
  8. 备选方案:如果RLocalCachedMap的缺点难以克服,可以考虑使用其他缓存策略,如只使用Redis缓存,或者采用其他本地缓存库(如Caffeine)与Redis缓存组合使用。
    请参考:juejin.cn/post/736125…

这是一个配置RLocalCachedMap的例子,它使用了一些上述策略:
基于Redission高级应用5-RLocalCachedMapt原理及实战应用

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");

// 创建RLocalCachedMap的配置对象
LocalCachedMapOptions<String, SomeObject> localCachedMapOptions = LocalCachedMapOptions.<String, SomeObject>defaults()
.cacheSize(1000) // 限制本地缓存的大小
.timeToLive(60 * 60 * 1000) // 设置默认的存活时间
.maxIdle(30 * 60 * 1000) // 设置最大空闲时间
.evictionPolicy(LocalCachedMapOptions.EvictionPolicy.LFU) // 设置淘汰策略
.weakEntries(true); // 使用弱引用

// 使用配置创建RLocalCachedMap
RLocalCachedMap<String, SomeObject> localCachedMap = redissonClient.getLocalCachedMap("myMap", localCachedMapOptions);

在这个配置中,我们限制了本地缓存的大小为1000个键值对,设置了默认的存活时间和最大空闲时间,并且启用了弱引用来存储条目。

总之,RLocalCachedMap可以显著提升性能,但必须谨慎使用并合理配置以避免资源消耗问题。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

基于阿里Paraformer模型打造中文语音识别

发表于 2024-04-28

openai-whisper是流行的语音识别解决方案,支持数十种语言,对中文支持效果也不错,不过在使用低于large尺寸的模型时,效果仍是略差。相比而言,国内阿里系专注于中文识别的模型效果就好了不少。

因此基于Paraformer搞了一个中文语音识别项目,可以方便的将中文音频视频识别为srt字幕,并支持在 pyVideoTrans 中也接口形式使用。

阿里魔塔 Paraformer语音识别-中文-通用-16k-离线-large-长音频版 www.modelscope.cn/models/iic/…

项目开源地址 github.com/jianchang51…

该项目仅支持中文语音识别。对于非中文语音,您可以利用基于 OpenAI Whisper 和 Faster-Whisper 的项目,如 pyvideotrans 或 stt 来进行识别,目的是为了弥补国外模型在中文支持方面的不足。

使用源码部署

  1. 首先安装 python3.10 / 安装 git git-scm.com/downloads ,安装ffmpeg,Windows上 下载ffmpeg.exe后放到本项目的ffmpeg文件夹内,Mac执行命令 brew install ffmpeg安装,Linux上执行yum install ffmpeg或 apt-get install ffmpeg
  2. 创建个空英文目录,Window上在该目录下打开cmd(Macos和Linux打开终端),执行命令 git clone https://github.com/jianchang512/zh_recogn ./
  3. 继续执行 python -m venv venv创建虚拟环境后,Windows中执行命令 .\venv\scripts\activate激活环境,Macos和Linux中执行 source ./venv/bin/activate激活环境
  4. 继续执行 pip install -r requirements.txt --no-deps 安装依赖
  5. Windows和Linux如需cuda加速,继续执行, pip uninstall torch torchaudio先卸载2个包, 再执行pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu118 安装cuda支持。
  6. 启动项目 python start.py

下载预打包版/仅支持win10 win11

github.com/jianchang51…

  1. 下载后解压到英文目录,双击 start.exe
  2. 为减小打包体积,预打包版不支持CUDA,若需cuda加速,请源码部署

在 pyvideotrans项目中使用

首先升级 pyvideotrans 到v1.62+,然后左上角设置菜单-zh_recogn中文语音识别菜单点开,填写地址和端口,默认 “http://127.0.0.1:9933“, 末尾不要加/api

API

api地址 http://ip:prot/api 默认 http://127.0.0.1:9933/api

python代码请求api示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
arduino复制代码import requests

audio_file="D:/audio/1.wav"
file={"audio":open(audio_file,'rb')}
res=requests.post("http://127.0.0.1:9933/api",files=file,timeout=1800)

print(res.data)
#
[
{
line:1,
time:"00:00:01,100 --> 00:00:03,300",
text:"字幕内容1"
},
{
line:2,
time:"00:00:04,100 --> 00:00:06,300",
text:"字幕内容2"
},
]

注意事项

  1. 第一次使用将自动下载模型,用时会较长
  2. 仅支持中文语音识别
  3. set.ini文件中可修改绑定地址和端口

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

CSS实现卷轴滚动效果

发表于 2024-04-28

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

欢迎关注我的公众号:前端侦探

「庆余年2」 马上就要开播了~最近起点读书APP内上架了庆余年典藏书,最大的特色是里面新加入了全新的阅读皮肤,一个拟物化的卷轴滚动效果,效果如下

1029_1714047027.gif

就是在拖动页面时,卷轴会随着页面的滚动而展开或卷起,就像在拖动真的画布一样,非常舒适,录屏可能看着不是很清晰,强烈建议去端内自行体验。

当时看到这个效果时就在思考,如何在 web 中也实现这样一个效果呢?🤔

经过一番琢磨,发现仅使用 CSS 就能完成这样的效果。下面是我复刻的效果

Kapture 2024-04-25 at 20.26.16.gif

这是如何实现呢?一起看看吧

一、CSS卷轴滚动的原理

首先 CSS 中并没有真正的 3d 滚动,立方体还可以勉强拼接,这种圆形的不行,因此我们需要用其他方式来实现。

这里其实是一个最简单的平移动画,只需要将纹理上下无缝平移,结合渐变和阴影,就能得到看似滚动的效果了。

先简单布局一下

1
2
3
html复制代码<div class="reel">
<div class="reel-bg"></div>
</div>

这里要用到卷轴的素材图片,是这样的

image-20240425232227600

两边保留,中间拉伸的一个自适应结构,可以用到border-image,划分出需要拉伸的地方即可

image-20240427112215829

有关border-image的详细教程可以自行搜索,这里就不展开了,具体实现如下

1
2
3
4
5
6
css复制代码.reel{
position: relative;
height: 28px;
margin: 0 15px;
border-image: url("卷轴.png") fill 42 36/14px 12px/0 12px;
}

这样就能得到卷轴的结构了

image-20240425232923219

接下来就是纹理了,素材是这个

image.png

我们把这个素材放在卷轴容器了,做上下无限平移动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
css复制代码.reel-bg{
position: absolute;
left: 0;
width: 100%;
height: 368px;
background: url("纹理.jpg") 50% 0/auto 50%;
animation: scroll 3s linear forwards infinite;
}
@keyframes scroll {
0%{
transform: translateY(-50%);
}
100% {
transform: translateY(-0%);
}
}

效果如下

Kapture 2024-04-25 at 23.07.03.gif

现在看着非常扁平,没有立体感,主要是纹理没有融入到背景之中。

如何将纹理完美的融合到后面的背景呢?没错,需要用到混合模式,这里用正片叠底就行了

1
2
3
4
css复制代码.reel-bg{
/**/
mix-blend-mode: multiply;
}

效果就好很多了

Kapture 2024-04-25 at 23.38.28.gif

最后给卷轴加点阴影,超出隐藏

1
2
3
4
5
css复制代码.reel{
/**/
overflow: hidden;
box-shadow: 0 5px 10px 5px rgba(0,0,0,.3), 0 10px 20px 10px rgba(0,0,0,.5);
}

这样就比较真实了

Kapture 2024-04-25 at 23.43.12.gif

滚动效果出来了,如何和页面滚动关联起来呢?接着往下看

二、CSS滚动驱动动画

又是滚动,又是动画,自然会想到 CSS 滚动驱动动画,有关滚动驱动动画之前出过一篇比较详细的教程,这里就不详细介绍了,有兴趣回顾之前这篇文章

CSS 滚动驱动动画终于正式支持了~

回到这里,先把整个布局完善一下

1
2
3
4
5
6
7
8
9
10
html复制代码<body>
<div class="reel">
<div class="reel-bg"></div>
</div>
<article>
<p>范慎很困难地撑着上眼皮,看着指头算自己这辈子做过些什么有意义的事情,结果右手五根瘦成筷子一样的指头还没有数完,他就叹了一口气,很伤心地放弃了这个工作。病房里的药水味总是这么刺鼻,旁边那床的老爷子前两天已经去地藏王菩萨那里报道了,大概再过几天就轮到自己吧。他得了某种怪病,重症肌无力,就是特别适合言情小说男主角的那种病。据说没得医,将来嗝屁的那天什么都动不了,只有眼泪可以流下来。痛!</p>
<p>范慎很困难地撑着上眼皮,看着指头算自己这辈子做过些什么有意义的事情,结果右手五根瘦成筷子一样的指头还没有数完,他就叹了一口气,很伤心地放弃了这个工作。病房里的药水味总是这么刺鼻,旁边那床的老爷子前两天已经去地藏王菩萨那里报道了,大概再过几天就轮到自己吧。他得了某种怪病,重症肌无力,就是特别适合言情小说男主角的那种病。据说没得医,将来嗝屁的那天什么都动不了,只有眼泪可以流下来。痛!</p>
<!--很多文本-->
</article>
</body>

简单修饰一下,由于卷轴要固定到顶部,可以采用sticky布局

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
css复制代码html{
background-color: #22312D;
font-family: cursive;
}
body{
margin: 0;
}
article{
background-color: #F5EBD4;
padding: 1em 0.5em;
border-left: 10px solid #405C53;
border-right: 10px solid #405C53;
margin: 0 15px;
}
p{
margin: 0;
padding: 0.2em 0;
color: #2C402E;
line-height: 150%;
text-indent: 2em;
}
h1{
text-align: center;
color: #F5EBD4;
}

效果如下

image-20240427113116986

不过这里还有点问题,由于是整个页面在滚动,内容滚到顶部会漏出来,如下

image-20240427113205559

所以还需要找个东西遮挡一下,这里我们直接用伪元素实现,设置相同的背景色就行了

1
2
3
4
5
6
7
8
css复制代码html::before{
content: '';
display: block;
height: 30px;
background: inherit;
position: sticky;
top: 0;
}

由于是sticky,也不用关注层级问题,效果如下

Kapture 2024-04-27 at 11.36.36.gif

现在就让卷轴滚动和页面滚动联动起来, 非常简单,只需要添加animation-timeline属性就行了,设置滚动时间线为root,如下

1
2
3
4
css复制代码.reel-bg{
animation: scroll 1s linear forwards;
animation-timeline: scroll(root);
}

这样在页面滚动时卷轴也跟着“转动”了

Kapture 2024-04-27 at 11.43.13.gif

但看着卷轴转地是不是有点慢了?

确实是这样,这个时候表示页面从头滚到到底部,执行一次动画,也就是滚动一圈,所以页面内容越多,滚动距离越长,那么卷轴转的也就越慢。

下面来修复这个问题

三、优化卷轴滚动速度

简单来处理,可以给个合适的动画次数,比如

1
2
3
4
css复制代码.reel-bg{
animation: scroll 1s linear forwards 5;
animation-timeline: scroll(root);
}

这样表示页面从头滚到到底部,会执行5次动画,也就是相当于会滚动5圈,所以看着速度就变快了

Kapture 2024-04-27 at 11.59.02.gif

现在就舒服多了。

不过这种处理方式有个问题,动画次数是跟内容长度强相关的,如果在向下滚动时动态加载内容,就需要更新这个值,稍显麻烦。

那么,有没有办法让滚动速度保持均衡呢?也就是无论内容多少,速度都是一致的。

这就需要用到 CSS 动画范围区间了,也就是animation-range,简单来讲就是设置滚动区间。

image-20240427120555429

默认情况下,滚动区间就是从页面顶部滚动到底部,我们可以手动改变这个范围,比如我们设置一个比较大的值

1
2
3
4
5
6
css复制代码.reel-bg{
--s: 999999;
animation: scroll 1s linear forwards;
animation-timeline: scroll(root);
animation-range: 0 calc(var(--s) * 1px);
}

这个表示页面滚动从0滚动到999999px时,上面的卷轴会滚动一圈。这显然不行,我们需要动态去计算滚动的圈数,就是动画播放次数

1
2
3
css复制代码.reel-bg{
animation-iteration-count: calc(var(--s)/184/3.14);
}

为啥是这个值呢?简单解释一下,184是这个卷轴上下的平移距离,然后再除以圆周率3.14,这样看着会更加自然。

这样就能完美实现卷轴滚动了,无论内容长短,滚动速度都是一致的

Kapture 2024-04-25 at 20.26.16转存失败,建议直接上传图片文件

核心实现其实就这几行,是不是非常简单

1
2
3
4
5
6
css复制代码.reel-bg{
--s: 999999;
animation: scroll 1s linear forwards calc(var(--s)/184/3.14);
animation-timeline: scroll(root);
animation-range: 0 calc(var(--s) * 1px);
}

四、不支持浏览器兼容

虽然 CSS 实现很简单,但是兼容性还不行,截至目前(2024年4月27日)仅支持Chrome 115+

image-20240427121817612

所以实际生成中不能直接使用,需要降级处理

简单的降级是不支持的不执行动画,这个只需要用@supports查询一下就行了

1
2
3
4
5
6
7
8
css复制代码@supports (animation-timeline: scroll()) {
.reel-bg{
--s: 999999;
animation: scroll 1s linear forwards calc(var(--s)/184/3.14);
animation-timeline: scroll(root);
animation-range: 0 calc(var(--s) * 1px);
}
}

这样在不支持的浏览器,比如 Safari 下就不会自动播放动画了

如果也想实现卷轴滚动效果,那就需要借助JS的力量,其实实现也不难,就是找到页面的scrollTop和纹理的上下平移关系,做个映射就好了,具体实现如下

1
2
3
4
5
6
7
8
js复制代码if (!CSS.supports('animation-timeline: scroll()')){
console.log('不支持animation-timeline')
const bg = document.querySelector('.reel-bg')
window.addEventListener('scroll',function(){
console.log(this.scrollY)
bg.style.transform = `translateY(${this.scrollY / Math.PI % 184 / 368 * 100 - 80}%)`
})
}

下面是在Safari中的效果,也能完美支持了

Kapture 2024-04-27 at 12.25.45.gif

你也可以访问以下链接查看真实效果

  • CSS QYN book (codepen.io)

也可扫码体验

image-20240427135434807

五、总结一下

以上就是本文的全部内容了,一个有趣的交互效果,你学到了吗,下面总结一下本文重点

  1. CSS卷轴滚动的原理其实是一个最简单的平移动画,只需要将纹理上下无缝平移,结合渐变和阴影,就能得到看似滚动的效果了
  2. 简单的平移动画看着非常扁平,没有立体感,主要是纹理没有融入到背景之中。
  3. 利用混合模式正片叠底,可以让平移动画看着像滚动动画了
  4. CSS 滚动驱动动画可以让页面滚动和卷轴滚动联动起来
  5. 默认情况下,页面从头滚到到底部,执行一次动画,也就是滚动一圈,所以页面内容越多,滚动距离越长,卷轴转的也就越慢
  6. 可以通过改变动画重复次数来调整卷轴滚动速度,局限是需要根据页面滚动长度来计算合适的数值
  7. 用足够大的 CSS 动画范围区间(animation-range)配合动画重复次数可以实现卷轴滚动速度保持均衡
  8. 目前兼容性还有些差,可以用CSS.supports做兼容处理

关注我,学习更多有趣的前端交互小技巧。最后,如果觉得还不错,对你有帮助的话,欢迎点赞、收藏、转发 ❤❤❤

欢迎关注我的公众号:前端侦探

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

《HelloGitHub》第 97 期

发表于 2024-04-28

兴趣是最好的老师,HelloGitHub 让你对编程感兴趣!

简介

HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。

github.com/521xueweiha…

这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等,涵盖多种编程语言 Python、Java、Go、C/C++、Swift…让你在短时间内感受到开源的魅力,对编程产生兴趣!


以下为本期内容|每个月 28 号更新

C 项目

1、freeswitch:运营商级的开源电话平台。这是一个支持 SIP、WebRTC 等多种通信协议的开源通信框架,它作为一个多用途的软交换机,可以用来创建各种语音和消息服务,比如企业电话系统、虚拟呼叫中心、视频会议服务等。

2、NetHack:一款历史悠久的 Roguelike 游戏。这是一款经典的 Roguelike 单机游戏,玩家将扮演一位地下城的探险者去寻找神秘的 Yendor 项链。游戏的界面由 ASCII 字符组成,具有自由度高、难度大、地图随机生成和永久死亡的特点,被玩家称为可以玩一辈子的游戏。

3、rt-thread:小而美的物联网操作系统。这是一个由国人发起的开源物联网操作系统,具有优秀的可裁剪性和可扩展性。可以运行在 ARM Cortex-M0 芯片上的极小内核,到中等的 ARM Cortex-M3/4/7 系统,甚至是多核 64 位的 ARM Cortex-A、MIPS32/64 等不同架构的处理器。同时还提供了功能丰富的组件和软件包,可帮助开发者快速构建嵌入式系统产品。来自 @阿基米东 的分享

C# 项目

4、g-helper:一款轻量级的华硕奥创中心替代品。该项目是华硕笔记本奥创控制中心(游戏本控制软件)的开源替代品。它拥有简洁的界面和极低的系统资源占用等特点,支持无缝切换显卡模式、调节屏幕刷新率、风扇曲线、电源功耗、键盘灯光等功能。

5、WingetUI:带界面的 Windows 包管理器。该项目是一个为 Windows 常用的命令行包管理工具设计的用户界面,如 Winget、Scoop、Pip、NPM、.NET Tool 等。它的界面友好、设计美观、支持中文,通过它你可以轻松下载、安装、更新和卸载包管理器上发布的任何软件以及其它日常应用,如浏览器、PDF 阅读器等。

C++ 项目

6、cppinsights:从编译器的视角看 C++ 的代码。这是一个基于 Clang 的开发工具,它可以将源代码转化成编译器的推导结果,让用户从编译器的视角深入了解代码的内部机制,可以用来展示和讲解抽象语法树(AST)和 C++ 语言的新特性。

7、stellarium:一款开源的天象模拟软件。该项目是天文爱好者必备神器,它能够精确地模拟/展示出头顶星空的景象,包括恒星、星座、行星、彗星等天体,支持选择时间和地点、放大观察、图解星座等功能,提供了 Windows、Linux、macOS、iOS 和 Android 在内的多个平台客户端。

8、VideoPipe:跨平台的视频结构化和分析框架。这是一个用于视频分析和结构化的框架,采用 C++ 编写、依赖少、易上手。它就像一个管道每个节点相互独立,可自行搭配构建出不同类型的视频分析管道,适用于视频结构化、图片搜索、人脸识别、安防领域的行为分析(如交通事件检测)等场景。来自 @周智 的分享

Go 项目

9、freeze:生成代码图片的终端工具。该项目可以将代码片段和终端输出,转换成 PNG、SVG 和 WebP 格式的图片,它采用 Go 语言开发,特点是安装简单和易于使用,支持一条命令生成图片,也可以通过交互模式生成定制的图片。

1
2
3
4
5
6
7
8
bash复制代码# macOS or Linux
brew install charmbracelet/tap/freeze

# Arch Linux (btw)
pacman -S freeze

# Nix
nix-env -iA nixpkgs.charm-freeze

10、jaeger:开源的分布式链路追踪平台。该项目是受到 Dapper 和 OpenZipkin 的启发,由 Uber 创建并开源的分布式调用链追踪平台。它可用于监控和诊断分布式应用(微服务),功能包括支持多语言、全链路追踪、上下文传递、性能指标、可视化和告警等。来自 @塔咖 的分享

11、opengist:GitHub Gist 的开源替代品。该项目将代码或文本片段存储于 Git 仓库中,用户可以通过 Git 命令或 Web 查看和修改内容,类似 GitHub 的 Gist 服务。它开源、可自托管,支持创建公开/私人片段、语法高亮、搜索代码、嵌入其它网站和修改历史等功能。

Java 项目

12、CompreFace:免费、开源的人脸识别系统。该项目提供了用于人脸识别、检测、验证、头部姿势检测、性别和年龄识别的 REST API 服务,不用懂机器学习就能轻松集成到任何系统中。它后端采用 Java 编写,人脸识别是基于 FaceNet 和 InsightFace 实现,同时支持 Docker 部署。

13、fdroidclient:免费、开源的 Android 应用商店。该项目是 F-Droid 的 Android 客户端,专门收集各类开源安卓软件(FOSS)的应用商店。它里面大部分是免费且无广告的应用,如遇到资源加载慢的情况,可通过设置镜像源解决。

JavaScript 项目

14、cmdk:快速、无样式的命令菜单 React 组件。该项目可以帮助开发者轻松实现一个直观且功能丰富的命令菜单,类似于 ⌘K 快捷键唤起的交互式菜单,从而提升用户的交互体验,适用于各种 Web 应用。来自 @Daaihang Wong 的分享

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typescript复制代码import { Command } from 'cmdk'

const CommandMenu = () => {
return (
<Command label="Command Menu">
<Command.Input />
<Command.List>
<Command.Empty>No results found.</Command.Empty>

<Command.Group heading="Letters">
<Command.Item>a</Command.Item>
<Command.Item>b</Command.Item>
<Command.Separator />
<Command.Item>c</Command.Item>
</Command.Group>

<Command.Item>Apple</Command.Item>
</Command.List>
</Command>
)
}

15、outline:开源的文档和团队知识库管理工具。这是一款用 React 和 Node.js 开发的在线文档编辑和协作工具,它界面美观、功能丰富、兼容 Markdown 的特点,支持中文和 Docker 部署。此外,它还提供了 Windows、macOS、iOS 和 Android 客户端,可作为私人 wiki 或中小型公司的内部文档和知识库平台。

16、Photo-Sphere-Viewer:用于显示 360° 球体全景的 JavaScript 库。这是一个基于 Three.js 开发的全景照片查看器,提供了友好的交互和丰富的功能。它支持多种全景图格式和功能,包括 2:1 全景图、六面体全景图、全景图分片、显示文本、视频全景等。来自 @wanzij 的分享

17、strapi:全球领先的开源无头 CMS。这是一款完全免费、采用 JavaScript/TypeScript 开发的无头内容管理系统。它拥有开箱即用的 API 和友好的管理面板,自带权限管理、默认安全、SEO 友好等特点。Strapi 作为目前 GitHub 上最流行的开源内容管理系统之一,已成为多家世界 500 强公司的首选 CMS。来自 @greatYe 的分享

18、tailwind-landing-page-template:免费、开源的落地页模板。该项目是基于 TailwindCSS、React 和 Next.js 构建的落地页模板,它界面美观、代码简单、设计在线,适用于快速制作公司主页、活动落地页等。

1
2
3
4
arduino复制代码git clone 项目
yarn install
yarn dev
# http://localhost:3000

Kotlin 项目

19、breezy-weather:极简风格的 Android 天气应用。这是一款非常注重设计的天气应用程序,采用了 Material Design 设计语言。它简约干净、免费无广告、同时支持中文,可显示未来一小时的降水情况、空气质量、小时预报、实况天气、花粉、湿度等各种天气数据。

Python 项目

20、dangerzone:将不可信文档转化为安全 PDF 的工具。这是一个确保文档安全的开源项目,它可以将存在潜在危险(来路不明、电子邮件附件)的 PDF 文件、办公文档或图片,在隔离的沙盒环境中进行转换,生成安全的 PDF 文件。

21、great-tables:用 Python 制作漂亮的表格。这个 Python 库可以用来制作实用且美观的表格。它提供了一套表格组件,通过组合不同的表格部分,如表头、表尾、行标签(stub)以及跨列标签(spanner labels)等,帮助 Python 开发者轻松制作漂亮的数据表格。来自 @Ted LI 的分享

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
python复制代码from great_tables import GT
from great_tables.data import sp500

# Define the start and end dates for the data range
start_date = "2010-06-07"
end_date = "2010-06-14"

# Filter sp500 using Pandas to dates between `start_date` and `end_date`
sp500_mini = sp500[(sp500["date"] >= start_date) & (sp500["date"] <= end_date)]

# Create a display table based on the `sp500_mini` table data
(
GT(sp500_mini)
.tab_header(title="S&P 500", subtitle=f"{start_date} to {end_date}")
.fmt_currency(columns=["open", "high", "low", "close"])
.fmt_date(columns="date", date_style="wd_m_day_year")
.fmt_number(columns="volume", compact=True)
.cols_hide(columns="adj_close")
)

22、python-miio:用于控制小米智能家电的 Python 库。该项目提供了一个 Python 库和命令行工具,可以用来控制使用小米的 miIO 和 MIoT 协议的设备。借助它用户可以轻松地与小米智能设备进行通信和远程控制,包括扫地机器人、灯泡、空气净化器等,非常适合喜欢 DIY 智能家居系统的开发者。

23、undetected-chromedriver:绕过反爬检测的 Python 库。这是一个经过优化的 Selenium WebDriver 补丁,专门用于防止浏览器自动化过程中,触发反机器人机制。它能够隐藏浏览器特征(指纹),使用起来十分方便,就像一个 Python 的第三方库一样。

1
2
3
4
python复制代码import undetected_chromedriver as uc
driver = uc.Chrome(headless=True,use_subprocess=False)
driver.get('https://nowsecure.nl')
driver.save_screenshot('nowsecure.png')

24、wereader:功能全面的微信读书笔记助手。这是一个基于 PySide6 编写的微信读书桌面客户端,不仅支持阅读书籍的功能,还有笔记模式,可查看你的全部读书笔记和热门划线等内容。

Rust 项目

25、grex:自动生成正则表达式的命令行工具。当你不知道如何写正则的时候,或许它能派上用场。该项目是用 Rust 语言开发的命令行工具,它可以根据用户提供的示例内容返回对应的正则表达式,可以作为库或命令行工具使用。来自 @size_t 的分享

26、sqlx:强大的 Rust 异步 SQL 工具包。这是一个真正的异步 SQL 库,完全使用 Rust 语言开发,支持 PostgreSQL、MySQL、MariaDB、SQLite 数据库。它提供了编译时检查查询语句和自动缓存语句等功能,让 Rust 语言和 SQL 数据库之间的交互变得轻松且高效。

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
rust复制代码use sqlx::postgres::PgPoolOptions;
// use sqlx::mysql::MySqlPoolOptions;
// etc.

#[async_std::main] // Requires the `attributes` feature of `async-std`
// or #[tokio::main]
// or #[actix_web::main]
async fn main() -> Result<(), sqlx::Error> {
// Create a connection pool
// for MySQL/MariaDB, use MySqlPoolOptions::new()
// for SQLite, use SqlitePoolOptions::new()
// etc.
let pool = PgPoolOptions::new()
.max_connections(5)
.connect("postgres://postgres:password@localhost/test").await?;

// Make a simple query to return the given parameter (use a question mark `?` instead of `$1` for MySQL/MariaDB)
let row: (i64,) = sqlx::query_as("SELECT $1")
.bind(150_i64)
.fetch_one(&pool).await?;

assert_eq!(row.0, 150);

Ok(())
}

27、zero-to-production:用 Rust 从零构建一个电子邮件通信服务。该项目仅是《Zero To Production In Rust》一书的配套项目源码,并没有提供书。这本书是介绍如何用 Rust 从零构建一个电子邮件通信服务,包含开发环境配置、持续集成流程、后端 API 开发、错误处理以及部署的全过程。

Swift 项目

28、pika:适用于 macOS 的原生颜色选择器。这是一个专为 macOS 设计的颜色选择工具,它免费、快速、易用,让你能够在屏幕上快速找到色值。

29、reminders-menubar:极简的 macOS 菜单栏提醒工具。这是一款使用 SwiftUI 开发的小工具,能够在 macOS 菜单栏查看/提醒待办事项。它体积小、交互简单、界面清爽,支持开机启动、多语言(包括中文)、菜单栏显示计数、快捷键等功能。

其它

30、cat-catch:开源的媒体嗅探浏览器插件。这个名为「猫爪」的浏览器插件,可以帮你列出当前页面的音乐和视频资源的下载地址。

31、containers:各种开源软件的容器镜像集合。这个项目是由 BitNami 维护和开源的 Docker 容器集合,包含各种开源软件、系统和 Web 应用。它们都经过了优化和漏洞扫描,可实现 Docker 一键部署,轻松部署应用,无需繁琐的安装和配置过程。

1
2
ini复制代码docker pull bitnami/APP
docker pull bitnami/APP:[TAG]

32、ServiceLogos:超可爱的 Logo 集合。这里是用来存放 Sawaratsuki 制作的各种 logo 的仓库,这些 logo 制作精美、画风可爱,包括编程语言、框架、工具和各大社交媒体的商标™️。

33、ungoogled-chromium:更纯净的 Chrome 浏览器。该项目是一个移除了 Chrome 浏览器中谷歌服务的浏览器,保留了完整的浏览器功能。

34、web-activity-time-tracker:追踪你的上网活动时间的浏览器插件。这是一款无广告、开源的浏览器插件,能记录你在不同网站上的浏览时间,并提供直观的统计数据。它支持按天和小时统计浏览时间、过滤干扰网站、导出数据等功能。

开源书籍

35、how-to-learn-robotics:机器人学自学指南。这本指南专为非科班的小伙伴而设计,旨在指导他们如何学习机器人学。它包含了必备知识、入门教材推荐、实践项目以及进阶方法等内容,帮助读者逐步成长为一名优秀的机器人工程师。

36、llm-universe:《动手学大模型应用开发》。该项目是专为小白开发者设计的大模型应用开发教程。内容涵盖了大模型的概念介绍、如何调用大模型API、知识库的搭建、构建 RAG(Retrieval-Augmented Generation)应用以及评估方法等内容。

机器学习

37、langchain:构建基于 LLM 应用的框架。LLM 是基于大数据预先训练好的大型深度学习模型,该项目可让 LLM 无需再训练就能访问新的数据集。它提供了开发 LLM 应用所需的各种模块,包括连接上下文来源和基于 LLM 进行推理的功能,从而轻松开发由 LLMs 支持的各种应用,比如聊天机器人、知识库和信息提取等。

38、LLaMA-Factory:专门针对 LLM 做微调的框架。这是一个让微调大型语言模型变得轻松的开源项目,它支持 LLaMA、Mistral、ChatGLM 等多种模型,以及增量预训练、奖励模型训练和精度等级等功能。引入了先进的算法和实用的微调技巧,同时提供了便捷的监控工具,方便用户追踪实验进度。

39、ollama:本地运行各种 LLM 的工具。这是一个用 Go 语言写的工具,用于在本地一条命令安装、启动和管理大型语言模型,支持 Llama 3、Gemma、Mistral 等大模型,适用于 Windows、macOS、Linux 操作系统。

40、vanna:与你的 SQL 数据库聊天。该项目使用 LLM+RAG+数据库技术,让用户能够通过自然语言查询 SQL 数据库,用生成的 SQL 回答你的问题。

最后

感谢参与分享开源项目的小伙伴们,欢迎更多的开源爱好者来 HelloGitHub 自荐/推荐开源项目。如果你发现了 GitHub 上有趣的项目,就点击这里分享给大家伙吧!

本期有你感兴趣的开源项目吗?如果有的话就留言告诉我吧~如果还没看过瘾,可以点击阅读往期内容。

感谢您的阅读,如果觉得本期内容还不错的话 求赞、求分享 ❤️

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

神奇的 SQL 之谓词 → 尽量让你们理解 EXISTS

发表于 2024-04-28

开心一刻

十年前,我:我交女票了,比我大两岁

妈:不行!赶紧分!

八年前,我:我交女票了,比我小两岁,外地的

妈:你就不能让我省点心?

五年前,我:我交女票了,市长的女儿

妈:别人还能看上你?分了吧!

今年,我挺着大肚子踏进家门

妈:闺女啊,你终于开窍了!

不服气吗.gif

谓词

SQL 中的谓词指的是:返回值是逻辑值的函数

我们知道函数的返回值有可能是数字、字符串或者日期等等,但谓词的返回值全部是逻辑值(true/false/unknown),谓词是一种特殊的函数

此时你们是不是有疑问:逻辑值不是只有 true 和 false 吗,哪来的 unknown ?

那不巧了吗,我正好有说明:神奇的 SQL 之温柔的陷阱 → 为什么是 IS NULL 而非 = NULL ?

640 (7).jpg

里面就讲到了三值逻辑,你们一定要去阅读,方便后续的理解

SQL 中的谓词有很多,如 =、>、<、<> 等,我们来看看 SQL 具体有哪些常用的谓词

比较谓词

创建表与初始化数据

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
sql复制代码-- 1、表创建并初始化数据
DROP TABLE IF EXISTS tbl_student;
CREATE TABLE tbl_student (
id INT(8) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
sno VARCHAR(12) NOT NULL COMMENT '学号',
name VARCHAR(5) NOT NULL COMMENT '姓名',
age TINYINT(3) NOT NULL COMMENT '年龄',
sex TINYINT(1) NOT NULL COMMENT '性别,1:男,2:女',
PRIMARY KEY (id)
);
INSERT INTO tbl_student(sno,name,age,sex) VALUES
('20190607001','李小龙',21,1),
('20190607002','王祖贤',16,2),
('20190608003','林青霞',17,2),
('20190608004','李嘉欣',15,2),
('20190609005','周润发',20,1),
('20190609006','张国荣',18,1);

DROP TABLE IF EXISTS tbl_student_class;
CREATE TABLE tbl_student_class (
id int(8) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
sno varchar(12) NOT NULL COMMENT '学号',
cno varchar(5) NOT NULL COMMENT '班级号',
cname varchar(20) NOT NULL COMMENT '班级名',
PRIMARY KEY (`id`)
) COMMENT='学生班级表';
INSERT INTO tbl_student_class VALUES
('1', '20190607001', '0607', '影视7班'),
('2', '20190607002', '0607', '影视7班'),
('3', '20190608003', '0608', '影视8班'),
('4', '20190608004', '0608', '影视8班'),
('5', '20190609005', '0609', '影视9班'),
('6', '20190609006', '0609', '影视9班');

SELECT * FROM tbl_student;
SELECT * FROM tbl_student_class;

相信你们对 =、>、<、<>(!=)等比较运算符都非常熟悉,它们的正式名称就是 比较谓词,使用示例如下

1
2
3
4
5
6
sql复制代码-- 比较谓词示例
SELECT * FROM tbl_student WHERE name = '王祖贤';
SELECT * FROM tbl_student WHERE age > 18;
SELECT * FROM tbl_student WHERE age < 18;
SELECT * FROM tbl_student WHERE age <> 18;
SELECT * FROM tbl_student WHERE age <= 18;

LIKE

对于 LIKE,我相信你们也非常熟悉

当我们想用 SQL 做一些简单的模糊查询时,都会用到 LIKE 谓词,分为 前一致、中一致和后一致,使用示例如下

1
2
3
4
sql复制代码-- LIKE谓词
SELECT * FROM tbl_student WHERE name LIKE '李%'; -- 前一致
SELECT * FROM tbl_student WHERE name LIKE '%青%'; -- 中一致
SELECT * FROM tbl_student WHERE name LIKE '%青'; -- 后一致

如果 name 字段上建了索引,那么前一致会利用索引,而中一致、后一致会全表扫描

BETWEEN

当我们想进行范围查询时,往往会用到 BETWEEN 谓词,示例如下

1
2
3
sql复制代码-- BETWEEN谓词
SELECT * FROM tbl_student WHERE age BETWEEN 15 AND 22;
SELECT * FROM tbl_student WHERE age NOT BETWEEN 15 AND 22;

BETWEEN 和它之后的第一个 AND 组成一个范围条件

BETWEEN 会包含临界值 15 和 22

BETWEEN 可以 比较谓词 等价替换

1
2
3
sql复制代码SELECT * FROM tbl_student WHERE age BETWEEN 15 AND 22;
-- 等价于
SELECT * FROM tbl_student WHERE age >= 15 AND age <= 22;

若不想包含临界值,那就需要这么写了

1
sql复制代码SELECT * FROM tbl_student WHERE age > 15 AND age < 22;

IS NULL 和 IS NOT NULL

关于 NULL,不是一言两语能说清的,她的水很深,深的让你又爱又恨!

1714184737810.jpg

依旧很巧,我对她已经进行了很深入的研究:神奇的 SQL 之温柔的陷阱 → 为什么是 IS NULL 而非 = NULL ?

你们一定要去仔细观摩,“姿势” 很丰富哟!

IN

有这样一个需求:查询出年龄等于 15、18以及20的学生,我们会用 OR 来查

1
2
sql复制代码-- OR
SELECT * FROM tbl_student WHERE age = 15 OR age = 18 OR age = 20;

用 OR 来查没问题,但是有一点不足,如果选取的对象越来越多,SQL 会变得越来越长,阅读性会越来越差,此时我们可以用 IN 来代替

1
2
sql复制代码-- IN
SELECT * FROM tbl_student WHERE age IN(15,18,20);

IN 有一种其他谓词没有的使用方法:使用子查询作为其参数

这个在平时项目中也是用的非常多的,例如:查询出影视7班的学生信息

1
2
3
4
5
6
7
8
9
10
11
sql复制代码-- IN 可以实现,但不推荐
SELECT * FROM tbl_student
WHERE sno IN (
SELECT sno FROM tbl_student_class
WHERE cname = '影视7班'
);

-- 联表查,推荐
SELECT ts.* FROM
tbl_student_class tsc LEFT JOIN tbl_student ts ON tsc.sno = ts.sno
WHERE tsc.cname = '影视7班';

很多情况下,IN 是可以用联表查询来替换的

上面讲的 谓词,你们肯定都会,而且觉得非常简单

640 (11).jpg

但接下来要讲的,你们还会觉得简单吗

EXISTS

首先 EXISTS 也是 SQL 谓词,那为什么不放到 谓词 那一章节下来讲?

因为 EXISTS 是主角嘛,主角,你们懂吗

我是主角.jpg

主角最大嘛,戏份必须给足!

关于 EXISTS,我们平时用的不多,甚至不用,不是说它适用场景少,而是它走的 海王海女 这种高端路线,我们很难驾驭!

它用法与其他谓词不一样,而且不好理解,另外很多情况下可以用 IN 来代替

但今天,我也带你们高端一回,体验下 海王海女 的感觉

在真正讲解 EXSITS 示例之前,我们先来了解下理论知识:实体的阶层 、全称量化与存在量化

实体的阶层

关于 阶,我只能说很润,润到你心坎的那种

很润.gif

不信的话,你们可以看看:神奇的 SQL 之层级 → 为什么 GROUP BY 之后不能直接引用原表中的列

SQL 严格区分阶层,不能跨阶层操作

就用我们常用的谓词来举例,同样是谓词,但是与 = 、BETWEEN 等相比,EXISTS 的用法还是大不相同的,概括来说,区别在于 谓词的参数可以取什么值

例如 x = y 或 x BETWEEN y 等谓词可以取的参数是像 21 或者 李小龙 这样的单一值,我们称之为 标量值,而 EXISTS 可以取的参数究竟是什么呢?从下面这条 SQL 语句来看,EXISTS 的参数不像是单一值

1
2
3
4
5
sql复制代码SELECT * FROM tbl_student ts
WHERE EXISTS (
SELECT * FROM tbl_student_class tsc
WHERE ts.sno = tsc.sno
);

我们可以看出, EXISTS 的参数是行数据的集合

之所以这么说,是因为无论子查询中选择什么样的列,对于 EXISTS 来说都是一样的

在 EXISTS 的子查询里, SELECT 子句的列表可以有下面这三种写法

1
2
3
html复制代码通配符:SELECT *
常量:SELECT '1'
列名:SELECT tsc.id

也就是说如下 3 条 SQL 查到的结果是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sql复制代码-- SELECT *
SELECT * FROM tbl_student ts
WHERE EXISTS (
SELECT * FROM tbl_student_class tsc
WHERE ts.sno = tsc.sno
);
-- SELECT 常量
SELECT * FROM tbl_student ts
WHERE EXISTS (
SELECT 1 FROM tbl_student_class tsc
WHERE ts.sno = tsc.sno
);
-- SELECT 列名
SELECT * FROM tbl_student ts
WHERE EXISTS (
SELECT tsc.sno FROM tbl_student_class tsc
WHERE ts.sno = tsc.sno
);

用个图来概括下 一般谓词 与 EXISTS 的区别

谓词区别.png

从上图我们知道,EXISTS 的特殊性在于输入值的阶数(输出值和其他谓词一样,都是逻辑值)

谓词逻辑中,根据输入值的阶数对谓词进行分类,= 或者 BETWEEEN 等输入值为一行的谓词叫作 一阶谓词,而像 EXISTS 这样输入值为行的集合的谓词叫作 二阶谓词,是不是高端起来了?

有点东西.gif

全称量化和存在量化

谓词逻辑中有量词(限量词、数量词)这类特殊的谓词,我们可以用它们来表达一些这样的命题:所有的 x 都满足条件 P 或者 存在(至少一个)满足条件 P 的 x ,前者称为 全称量词,后者称为 存在量词,分别记作 ∀(A的下倒)、∃(E的左倒)

SQL 中的 EXISTS 谓词实现了谓词逻辑中的 存在量词,然而遗憾的是, SQL 却并没有实现 全称量词

但是没有全称量词并不算是 SQL 的致命缺陷,因为全称量词和存在量词只要定义了一个,另一个就可以被推导出来,具体可以参考下面这个等价改写的规则(德·摩根定律)

1
2
html复制代码∀ x P x = ¬ ∃ x ¬P(所有的 x 都满足条件 P =不存在不满足条件 P 的 x )
∃ x P x = ¬ ∀ x ¬Px(存在 x 满足条件 P =并非所有的 x 都不满足条件 P)

因此在 SQL 中,为了表达全称量化,需要将 所有的行都满足条件 P 这样的命题转换成 不存在不满足条件 P 的行

不知道你们看懂了,我反正已经讲晕了

太绕.gif

对理论晕了,你们先别慌,我们结合具体的实际案例来看看 EXISTS 的妙用

查询表中“不”存在的数据

tbl_student 中的学生都分配到了具体的班级,假设新来了两个学生(刘德华、张家辉),他们暂时还未被分配到班级,我们如何将他们查询出来(查询未被分配到班级的学生信息)

1
2
3
4
sql复制代码-- 新来、未被分配到班级的学生
INSERT INTO tbl_student(sno,name,age,sex) VALUES
('20190610010','刘德华',55,1),
('20190610011','张家辉',46,1);

我们最容易想到的 SQL 肯定是下面这条

1
2
sql复制代码-- NOT IN 实现
SELECT * FROM tbl_student WHERE sno NOT IN(SELECT sno FROM tbl_student_class);

其实用 NOT EXISTS 也是可以实现的

1
2
3
4
5
sql复制代码-- NOT EXISTS 实现
SELECT * FROM tbl_student ts
WHERE NOT EXISTS (
SELECT * FROM tbl_student_class tsc WHERE ts.sno = tsc.sno
);

肯定 ⇔ 双重否定 转换

EXISTS 谓词来表达全称量化,这是 EXISTS 的用法中很具有代表性的一个用法

但是需要我们打破常规思维,习惯从全称量化 所有的行都×× 到其双重否定 不××的行一行都不存在 的转换

假设我们有学生成绩表:tbl_student_score

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sql复制代码-- 学生成绩表
DROP TABLE IF EXISTS tbl_student_score;
CREATE TABLE tbl_student_score (
id INT(8) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
sno VARCHAR(12) NOT NULL COMMENT '学号',
subject VARCHAR(5) NOT NULL COMMENT '课程',
score TINYINT(3) NOT NULL COMMENT '分数',
PRIMARY KEY (id)
);
INSERT INTO tbl_student_score(sno,subject,score) VALUES
('20190607001','数学',100),
('20190607001','语文',80),
('20190607001','物理',80),
('20190608003','数学',80),
('20190608003','语文',95),
('20190609006','数学',40),
('20190609006','语文',90),
('20190610011','数学',80);

SELECT * FROM tbl_student_score;

所有科目分数都在 50 分以上

查询所有科目分数都在 50 分以上的学生,这个 SQL 怎么写?

是不是有点懵,懵就对了,不然你们不会往下看了呀!

我们需要转换下命题,将查询条件 所有科目分数都在 50 分以上 转换成它的双重否定 没有一个科目分数不满 50 分,然后用 NOT EXISTS 来表示转换后的命题

1
2
3
4
5
6
7
8
sql复制代码-- 没有一个科目分数不满 50 分
SELECT DISTINCT sno
FROM tbl_student_score tss1
WHERE NOT EXISTS -- 不存在满足以下条件的行
( SELECT * FROM tbl_student_score tss2
WHERE tss2.sno = tss1.sno
AND tss2.score < 50 -- 分数不满50 分的科目
);

是不是很简单?

数学分数在80分及以上且语文分数在50分及以上

查询出数学分数在 80 分以上(包含80)且语文分数在 50 分以上(包含)的学生,这 SQL 又该如何写?

这个条件是 全称量化 的条件吗

直观感觉不是,但如果改成:某个学生的所有行数据中,如果科目是数学,则分数在 80 分及以上;如果科目是语文,则分数在 50 分及以上

这是不是就是 全称量化 条件了?

接下来怎么办,肯定是进行双重否定转换呀,条件则是:某个学生的所有行数据中,如果科目是数学,则分数不低于 80;如果科目是语文,则分数不低于 50

那么我们就可以按如下顺序逐步写入满足条件的 SQL

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
sql复制代码-- 1、CASE 表达式,肯定
CASE WHEN subject = '数学' AND score >= 80 THEN 1
WHEN subject = '语文' AND score >= 50 THEN 1
ELSE 0
END;

-- 2、CASE 表达式,单重否定(加上 NOT EXISTS才算双重)
CASE WHEN subject = '数学' AND score < 80 THEN 1
WHEN subject = '语文' AND score < 50 THEN 1
ELSE 0
END;

-- 3、结果包含了 20190610011 的 SQL
SELECT DISTINCT sno
FROM tbl_student_score tss1
WHERE subject IN ('数学', '语文')
AND NOT EXISTS
(
SELECT *FROM tbl_student_score tss2
WHERE tss2.sno = tss1.sno
AND 1 = CASE WHEN subject = '数学' AND score < 80 THEN 1
WHEN subject = '语文' AND score < 50 THEN 1
ELSE 0
END
);

-- 4、20190610011 没有语文成绩,剔除掉
SELECT sno
FROM tbl_student_score tss1
WHERE subject IN ('数学', '语文')
AND NOT EXISTS
(
SELECT * FROM tbl_student_score tss2
WHERE tss2.sno = tss1.sno
AND 1 = CASE WHEN subject = '数学' AND score < 80 THEN 1
WHEN subject = '语文' AND score < 50 THEN 1
ELSE 0
END
)
GROUP BY sno
HAVING COUNT(*) = 2; -- 必须两门科目都有分数

我相信你们肯定没看懂,但你们也不用纠结,如果工作中你们真的遇到这样的需求,可以用如下方式实现

  • 用编程语言,在内存中实现过滤嘛
  • 把提需求的人干掉(个人不太推荐)

886918-20240108165944088-804234746.png

嵌套 EXISTS

有三张表

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
sql复制代码DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`Sno` char(9) DEFAULT NULL,
`Sname` char(20) DEFAULT NULL,
`Ssex` char(2) DEFAULT NULL,
`Sage` int(11) DEFAULT NULL,
`Sdept` char(20) DEFAULT NULL,
UNIQUE KEY `Sno` (`Sno`)
) ENGINE=InnoDB;
INSERT INTO `student` VALUES ('200215121', '李勇', '男', '20', 'CS');
INSERT INTO `student` VALUES ('200215122', '刘晨', '女', '19', 'CS');
INSERT INTO `student` VALUES ('200215123', '王敏', '女', '18', 'MA');
INSERT INTO `student` VALUES ('200215124', '张立', '男', '19', 'IS');

DROP TABLE IF EXISTS `course`;
CREATE TABLE `course` (
`Cno` char(4) NOT NULL,
`Cname` char(40) DEFAULT NULL,
`Cpno` char(4) DEFAULT NULL,
`Ccredit` smallint(6) DEFAULT NULL,
PRIMARY KEY (`Cno`)
) ENGINE=InnoDB;
INSERT INTO `course` VALUES ('1', '数据库', '5', '4');
INSERT INTO `course` VALUES ('2', '数学', '', '2');
INSERT INTO `course` VALUES ('3', '信息系统', '1', '4');
INSERT INTO `course` VALUES ('4', '操作系统', '6', '3');
INSERT INTO `course` VALUES ('5', '数据结构', '7', '4');
INSERT INTO `course` VALUES ('6', '数据处理', '', '2');
INSERT INTO `course` VALUES ('7', 'PaSCal语言', '6', '4');

DROP TABLE IF EXISTS `sc`;
CREATE TABLE `sc` (
`Sno` char(9) DEFAULT NULL,
`Cno` char(4) DEFAULT NULL,
`Grade` smallint(6) DEFAULT NULL
) ENGINE=InnoDB;
INSERT INTO `sc` VALUES ('200215121', '1', '92');
INSERT INTO `sc` VALUES ('200215121', '2', '85');
INSERT INTO `sc` VALUES ('200215121', '3', '88');
INSERT INTO `sc` VALUES ('200215122', '2', '90');
INSERT INTO `sc` VALUES ('200215122', '3', '80');

如下 SQL 是查什么?

1
2
3
4
5
6
7
8
9
10
11
12
sql复制代码SELECT Sname
FROM student
WHERE NOT EXISTS
(
SELECT * FROM course
WHERE NOT EXISTS
(
SELECT * FROM sc
WHERE sc.Sno = student.Sno
AND sc.Cno = course.Cno
)
);

另外,如下两个需求,SQL 该怎么写

  • 查询被所有学生选修的课程的课名
  • 查询选修了 200215122 学生选修的全部课程的学生学号

感兴趣的可以试试,答案在:SQL 中的 EXISTS 到底做了什么? 或者 《数据库系统概论(第4版)》

学吧,学无止尽.png

总结

  • SQL 中的谓词分两种:一阶谓词和二阶谓词(EXISTS),区别主要在于接收的参数不同,一阶谓词接收的是 行,而二阶谓词接收的是 行的集合
  • SQL 中没有与 全称量词 相当的谓词,需要进行 双重否定 转换,然后用 NOT EXISTS 实现
  • EXISTS 之所以难用(不是不好用,而是不会用),主要是 全称量词 的命题转换(肯定 ⇔ 双重否定)比较难,实际工作中往往会舍弃 EXISTS,寻找它的替代方式,可能是 SQL 的替代,也可能是业务方面的转换,所以说,EXISTS 掌握不了没关系,但是能掌握那是最好了

参考

《SQL基础教程》

《SQL进阶教程

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

身份证/手机号解析服务 简介 HutoolIdcardUt

发表于 2024-04-28

个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview


简介

本篇文章是风控系统之数据服务,名单、标签、IP、设备、地理信息、征信等的扩展。

既然是上篇文章的扩展,自然离不开风控系统的主题。身份证和手机号解析作为风控事件接入的靠前的步骤是重要的数据处理过程,也是相对最简单的处理过程(与IP、GPS解析相比的话)。

Hutool.IdcardUtil

身份证工具-IdcardUtil | Hutool

Hutool的IdcardUtil现在支持大陆15位、18位身份证,港澳台10位身份证。

工具中主要的方法包括:

  1. isValidCard 验证身份证是否合法
  2. convert15To18 身份证15位转18位
  3. getBirthByIdCard 获取生日
  4. getAgeByIdCard 获取年龄
  5. getYearByIdCard 获取生日年
  6. getMonthByIdCard 获取生日月
  7. getDayByIdCard 获取生日天
  8. getGenderByIdCard 获取性别
  9. getProvinceByIdCard 获取省份

此工具中包含了国内身份证的规定和验证说明。基本的身份证解析功能都具备了,缺点就是关于身份证归属地只到省级别。

中国行政区划

身份证的前6位表示省/市/县,Hutool也已经说明了,而且将省份及其对应编码放入了静态Map中了,可能出于内存和行政区划变动的原因,并没有将市/县囊括在内。

www.mca.gov.cn/mzsj/xzqh/2…

GitHub - modood/Administrative-divisions-of-China: 中华人民共和国行政区划:省级(省份)、 地级(城市)、 县级(区县)、 乡级(乡镇街道)、 村级(村委会居委会) ,中国省市区镇村二级三级四级五级联动地址数据。

非常建议通过这个Github项目来更新中国行政区划数据,该项目提供了json和csv格式的数据,而且数据也比较新。

以下是通过内存加载省/市/县的示例,因为到县级别数量也不是很大,就放在内存中吧,快嘛。

使用到了provinces.csv、cities.csv、areas.csv都可以通过Github下载。

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
java复制代码/**
* <a href="https://github.com/modood/Administrative-divisions-of-China">...</a>
* @author wnhyang
* @date 2024/4/24
**/
@Slf4j
public class AdocUtil {

private static final Map<String, Province> PROVINCE_MAP = new HashMap<>(32);

private static final Map<String, City> CITY_MAP = new HashMap<>(512);

private static final Map<String, Area> AREA_MAP = new HashMap<>(4096);

private static final String DONT_KNOW = "未知";

private static final int AREA_CODE_LENGTH = 6;

private AdocUtil() {
}

public static void init() {
long start = System.currentTimeMillis();
try {
List<Province> provinceList = CsvUtil.getReader().read(
ResourceUtil.getUtf8Reader("provinces.csv"), Province.class);
provinceList.forEach(province -> PROVINCE_MAP.put(province.getCode(), province));

List<City> cityList = CsvUtil.getReader().read(
ResourceUtil.getUtf8Reader("cities.csv"), City.class);
cityList.forEach(city -> CITY_MAP.put(city.getCode(), city));

List<Area> areaList = CsvUtil.getReader().read(
ResourceUtil.getUtf8Reader("areas.csv"), Area.class);
areaList.forEach(area -> AREA_MAP.put(area.getCode(), area));

} catch (Exception e) {
throw new RuntimeException(e);
}
log.info("init success, cost:{}ms", System.currentTimeMillis() - start);
}

}

手机号的解析

手机号码的归属地可以通过号码的前七位进行判断。前三位如170/150/136等是网络识别号,中间四位是地区编码(归属地),最后四位是用户号码。因此,正确念读手机号的方法是:1xx-xxxx-xxxx。中国大陆地区手机号码格式为:1**####$$$$。星号部分(*)用于区分不同的业务服务提供商,例如移动、联通和电信等。前七位(1**####)可以用来查出手机的归属地。

可参考以下项目。

GitHub - fighting41love/funNLP

GitHub - ls0f/phone: 手机号码归属地库

GitHub - xluohome/phonedata: 手机号码归属地信息库、手机号归属地查询 phone.dat 最后更新:2023年02月

GitHub - fengjiajie/phone-number-geo: 手机号码归属地本地解析(Java)

GitHub - EeeMt/phone-number-geo: 离线查询手机号归属地

GitHub - AfterShip/phone: With a given country and phone number, validate and reformat the mobile phone number to the E.164 standard. The purpose of this is to allow us to send SMS to mobile phones only.

关于开源项目的使用,一般要从代码质量文档、许可证、使用社区活跃度、性能多方面来考虑。

以下是GitHub - EeeMt/phone-number-geo: 离线查询手机号归属地的一个简单示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码/**
* @author wnhyang
* @date 2024/4/26
**/
@Slf4j
public class PhoneNumberUtil {

private static final PhoneNumberLookup LOOKUP = new PhoneNumberLookup();

private static final String DONT_KNOW = "未知";

private PhoneNumberUtil() {
}

public static PhoneNumberInfo lookup(String phoneNumber) {
return LOOKUP.lookup(phoneNumber).orElseGet(() -> getNotFound(phoneNumber));
}

public static PhoneNumberInfo getNotFound(String phoneNumber) {
return new PhoneNumberInfo(phoneNumber, new Attribution(DONT_KNOW, DONT_KNOW, DONT_KNOW, DONT_KNOW), ISP.UNKNOWN);
}
}

写在最后

拙作艰辛,字句心血,望诸君垂青,多予支持,不胜感激。


个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

加密的艺术:密文的创建和校验

发表于 2024-04-28

概述

在我很喜欢的一部(根据真实事件改编)的电影《模仿游戏》里面:

模仿游戏
著名的科学家图灵带领他的团队,花费两年的时间,费劲九牛二虎之力,在找到德军的话术口令后才得以破解了德军通讯加密装置 “英格玛”,为第二次世界大战取得胜利打下的坚实的基础。那么德军使用的通讯加密究竟是一种怎样的技术,这是我们今天要探讨的数据加密技术。数据的保密是对数据加密、解密的统称,用学院派的说法就是,**使用某种算法改变了信息原本的形态,使攻击者即使窃取了信息也因为没有对应的解密的方法也无法获取当信息的真实内容。**这就是信息保密的目的,对于信息的保密,可以在三个环节进行,分别是:

  1. 在客户端进行保密
  2. 在传输时进行保密(最复杂,也最有效)
  3. 在服务端进行保密

加密的强度

在安全领域大家都知道安全是区分等级的,不同应用的敏感信息重要性不同,所以需要的安全等级也不同,这个世界上没有绝对的安全,安全等级不可能无止境的拉满,任何安全手段都可以破解(只要花费足够的成本),想要更高级别的安全等级,就要付出更高的成本(工作量,算力)等。例如常见的加密技术可以说明这一点。加密的强度从低到高,分别有:

一:哈希算法:最常见的加密手段,对明文密码使用 MD5 等哈希摘要算法进行不可逆的哈希计算进行加密,示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5Hash {
public static void main(String[] args) {
String text = "yourPassword";
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hashBytes = md.digest(text.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
hexString.append(String.format("%02x", b));
}
System.out.println("MD5 Digest: " + hexString.toString());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}

输出结果:

1
sh复制代码MD5 Digest: 65a8e27d8879283831b664bd8b7f0ad4

这种方式,安全等级低,弱密码容易被彩虹表(预先进行摘要好的哈希表,进行反向破译)破击。

二:哈希算法加盐:增强了基础的哈希算法,加上 salt 盐值混淆哈希计算,可以有效防御彩虹表的攻击,示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码private static final String SALT = "YourFixedSalt";  // 固定盐值

private static String getSecurePassword(String passwordToHash) {
String generatedPassword = null;
try {
MessageDigest md = MessageDigest.getInstance("MD5");
// 添加固定盐值
md.update(SALT.getBytes());
byte[] bytes = md.digest(passwordToHash.getBytes());
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}
generatedPassword = sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return generatedPassword;
}

这种方案的缺点是,但如果盐值泄露,那么破译所以密文也是一件很容易得事情,而且弱密码即使加了盐值,在强大算力的彩虹表面前,破译也不是一件难事。

三:动态盐加哈希:动态盐值有一个特点,就是每个盐值只使用一次,这种方式有点像就像我喜欢吃的那家酸菜鱼,他们家宣传的口号就是:油每次只用一次,本质上就是花费更高的成本换来更高的安全。示例:

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
java复制代码public static void main(String[] args) {
// 待加密的密码
String passwordToHash = "yourPassword";
// 生成动态盐值
byte[] salt = getSalt();
// 获取带盐的安全密码
String securePassword = getSecurePassword(passwordToHash, salt);
System.out.println("Secure Password: " + securePassword);
System.out.println("Salt: " + bytesToHex(salt));
}

// 使用MD5加密密码,并结合盐值
private static String getSecurePassword(String passwordToHash, byte[] salt) {
try {
// 创建MD5摘要算法的 MessageDigest 对象
MessageDigest md = MessageDigest.getInstance("MD5");
// 将盐值添加到摘要中
md.update(salt);
// 完成密码的哈希计算
byte[] hashedBytes = md.digest(passwordToHash.getBytes());
// 将哈希值转换为十六进制字符串
return bytesToHex(hashedBytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}

// 生成一个随机的盐值
private static byte[] getSalt() {
SecureRandom sr = new SecureRandom();
byte[] salt = new byte[16];
sr.nextBytes(salt);
return salt;
}

// 将字节数组转换为十六进制字符串
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}

动态盐值可以解决固定盐值带来的风险,如果由客户端动态生成盐值给服务端进行计算,那么 客户端如果安全的把动态盐值传输给服务端 就是另外一个问题,既然通信的信道是安全可靠的,那么传输动态盐值就没有意义,既然通信信道是不安全的,那么传输动态盐值也有被窃听的风险,也没有意义。这简直就是一个 “先有鸡,还是先有蛋” 的问题。

四:启动 HTTPS 信道:HTTPS 加密传输是目前的主流方案,但是启动 HTTPS 后安全信道后也并不能高枕无忧,也会带来一系列的问题,例如因为会遇到服务端使用自签名证书导致信息泄露风险,服务端证书更新不及时,证书过期的问题,还有 TLS 版本过低或密码学套件选用不当产生加密强度不足的风险。

五:外置的 MFA:例如银行等机构在涉及金额交易的时候,会要求客户使用外置的 U 盾,虚拟 MFA,手机验证码,人脸识别等外置设备来加强安全等级。一些关键企业或者军事机构甚至会开辟一条与公网隔绝的独立的内部网络进行信息通信来保证信息的安全。

u盾

通过以上示例是想要证明,对于安全和保密而言:这个世界上是没有绝对的安全,想要更高级别的安全等级,就要付出更高的成本 ,当然有人会挑刺的说,那我拔掉网线不联网最安全,虽然有一定的合理性,但这样封闭式的安全没有意义,所以不在我们讨论的范围之内。

客户端加密

对于大多数应用而言,要保证信息通信的安全,客户端只有启用 HTTPS 这一个方案可以选择。而且对于密码这样的敏感信息而言,个人认为最好是在客户端就可以尽快处理掉,以绝后患,原因如下:

  1. 服务端存储明文密码,数据库被攻破导致用户密码泄露的新闻已经屡见不鲜的,而且被拖库最严重的还是国内某最大的技术社区。。。
  2. 服务端把密码输入到日志,日志文件泄露或者被采集,导致用户密码泄露等等
  3. 避免中间人攻击,就算网络设备被劫持,信息被窃取,至少明文密码不会泄露

总之,明文密码最好在客户端就被消灭掉,越早处理越好,不要把明文传到服务端,传输的风险大,在防御上客户端除了启用 HTTPS 外,还要对明文密码进行摘要处理,从而保证敏感的安全。至于客户端应该如何进行加密,我们接下来开始讨论。

密文的创建和校验

之前说了在信息安全领域没有绝对的安全,需要多高的安全等级就要消耗多大的安全成本。对于大多数普遍的应用而言,启动 HTTPS 加密通信是在安全等级和安全成本之间的一个合适的平衡点。所以结合实际情况选择合适的方案就好。

BCrypt 算法

上面介绍无论如何对明文进行哈希计算,就算加盐都有被彩虹表暴力破解的可能。为了解决这个问题,引入慢哈希函数来解决可能是一个更理想的方案。慢哈希,就是在哈希计算和 salt 盐值之外增加一个计算时间 cost 的参数,慢哈希通过延长哈希计算时间和消耗的资源来有效的避免诸如彩虹表等暴力破解的攻击,提供系统的安全性,BCrypt 算法就是一个具有代表性的慢哈希函数。示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码public class BCryptExample {

public static void main(String[] args) {
// 创建 BCryptPasswordEncoder 实例,可以指定工作因子,默认是 10
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

// 加密密码
String originalPassword = "yourPassword";
String encodedPassword = encoder.encode(originalPassword);
System.out.println("Encoded Password: " + encodedPassword);

// 校验密码
boolean isMatch = encoder.matches(originalPassword, encodedPassword);
System.out.println("Password matched: " + isMatch);
}
}

如果我们把慢哈希计算的 cost 设置为 0.1 秒的时间,那么对所有由10位大小写字母和数字组成的弱密码(共62种字符)进行哈希计算一次,大约需要 8.39×10168.39×1016 秒。这等于大约 971.4 亿天,或者大约 2661 百万年的时间。这表明使用 BCrypt 和适当的工作因子可以极大增加破解密码的难度,使得暴力破解方法变得不可行。但是需要注意的是:

BCrypt 存在对计算资源和时间有很大的消耗,会明显降低服务端性能,只建议在客户端进行慢哈希处理

密文的创建

对于敏感信息加密阶段,可以参考以下方案进行处理:

密文的创建

  1. 用户创建密码,客户端接收用户的明文密码
  2. 客户端对密码使用固定盐值 + BCrypt 慢哈希进行加密后发给服务端
  3. 服务端接收密文,然后生成随机盐值,对密文进行二次加密
  4. 服务端将随机盐和二次密文存储到数据库

密文的校验

在对密文进行校验阶段,可以参考以下方案进行处理:

密文的校验
说明:

  1. 用户输入密码,客户端收到用户的明文密码
  2. 客户端对密码使用固定盐值 + BCrypt 慢哈希进行加密后发给服务端
  3. 服务端接收客户端密文,然后从数据库取出随机盐和二次密文
  4. 服务端使用随机盐对客户端密文进行加密,然后和自身的二次密文进行对比
  5. 密文内容相同,则表示密码校验通过

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1234…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%