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

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


  • 首页

  • 归档

  • 搜索

系统整容纪:责任链设计模式的应用实战(爆灯了,研发工期由45

发表于 2024-04-28

本文通过介绍使用责任链设计模式的背景和经历,来使得读者加深对于此设计模式的印象,甚至受到一定的启发来对自己当下所参与、所负责的项目进行“整容”,从而提升系统的“美感”。分享工作中的点点滴滴。

一、背景

在下所负责的系统中有这么一个模块,分区模块,直接看这个词的话相信很多人都会疑惑甚至是误解,其实其真正的含义就是“路由”,接下来我简单描述一下何谓“路由”。

相信大家都有过网上购物的经验,每当我们下完订单后,我们都能随时随地的查看订单的物流跟踪状态,而上述的“路由”概念就是指:订单从A地到B地的运输路由线路,例如订单order1要从A运输到目的地F,其可以从A->B->D->F,也可以从A->D->F,至于具体应该走哪条线路,是靠系统中配置的路由以及对应的匹配规则进行筛选出来的。

很长的一段时间里,系统里的路由配置和规则都是静态的(所谓静态就是提前配置好并且几乎是固定不变的),这种做法的弊端很明显,就是成本无法控制,就如同上述所讲的例子,运输路线明明有机会可以减少甚至是可以直送(A地到F地的货物明明可以装满N车,却也不得不按照系统中制定的路线进行运输),但是却被系统中定死的路由规则所限只能多走一些道路,提高了人力作业和运输的成本。

基于此有位大牛发现了降本增效的商机:就是让这块路由线路和规则动起来,使得系统能够更灵活的兼容上述情况,达到资源的极限利用,举个例子:比如有很多订单都是从A要送达目的地F的,系统中静态线路配置的只有A->B->D->F,但是经过系统监测计算发现从A到F的货物量可以装满两车,那此时为这些订单临时生成一条新的线路从A直达F,同时在A场地进行收货、发货等凡是涉及到路由线路匹配的实操环节时,均兼容上此临时路由的场景,这样就能在不改变用户习惯的情况下将整体成本给降下来,并且运输到目的地的效率也得到了大大的提高。

这位大牛提出的方案很好,得到了大家的广泛认可和好评,于是在立项后进入了轰轰烈烈的开发阶段,而在下有幸被委以重任来主导完成此项目的开发交付。

值得一提的是,路由线路这块的改动贯穿了订单的整个实操流程以及一些边边角角的辅助查询、统计报表等功能,场景涉及众多,所以压力还是蛮大的,虽然期间也确实走了不少弯路,但最终的结果是好的,甚至在之后的又来了几个类似变动路由线路的需求,但是基于此次改造后,我们这边都能进行轻松应对,这个在文章后续的效果中会体现一下。

说了这么一大堆,有没有觉得其实都是废话的感觉,哈哈,确实有点啰嗦,接下来让我们来梦回路由,再现一下整个稀碎而又有成就感的改造过程。

二、思路和方法

后续文章中提到的分区是为路由匹配规则的含义

首先来看一张简易的实操流程示意图





在每个场地的实际操作的作业流程中都会涉及到分区匹配规则的情况,同时在一些辅助实操作业的查询功能或报表功能中也会涉及到路由匹配规则,所以一旦分区匹配规则要做变动,那么就会面临如下痛点

◦首先在业务层面上就会几乎贯穿整个流程,无论是评估、开发、测试等环节都会面临工作量巨大的问题

◦再次从系统层面上来看,这部分的代码现状也非常不友好,主要体现在:

▪分区匹配核心业务规则一致,但是代码写法不一,并且分布散乱,让人难以阅读与维护,同时伴随场景遗漏的风险

▪分区匹配功能目前都是各个实例自行编写并且耦合在各个使用场景中,不具备扩展性,一旦规则变化,改动成本非常高

基于上述痛点,在下决定下定决心将此模块进行重构整治一番,一来通过此次挑战来提高自我、二来也为以后此块规则再次变更的可能做好铺垫,那么具体应该怎么做才能解决上述所提到的痛点呢?我是这么做的

1.业务层面上做好充分的评估:体现在开发的详细设计上(这里由于在下对于业务规则非常熟,所以算是是本人的优势拉哈哈),像场景梳理啊、修改方案、甚至在设计中下沉到了具体功能(按钮点击啊、录入框输入后的回车触发啊等等这些细节)的修改逻辑方案以及代码位置,以便让所有参与开发人员以此为指导手册进行快速开发,测试人员以此为指导进行用例编写、产品人员以此为字典加深其自身对于此块业务的理解等等。听起来是不是很牛X,哇哈哈,这里有点吹嘘拉,本文中不着重介绍这里,主要介绍设计模式哈,咱们缓缓继续往下看

2.这里是我们的重头戏哈,系统层面上主要体现在代码上,毕竟说的再好,落实不到代码,看不到效果都是白搭嘛,何况咱又是职位,好了,废话不多说,继续看我表演哈哈

▪首先将分区匹配核心模块进行统一的收口,在系统中只保留一个实例进行服务提供,既能解决代码分散维护成本高的问题、又能避免场景遗漏的风险

看到这里或许会有人说,你这个会不会带来另外一个问题啊:虽然场景不会遗漏了,达到了一处代码变动,处处场景都会生效,但是会不会改动了某些场景原有的功能特性?能想到这里的同学,确实很细心哈,如果是硬性收口的话的确是会产生这个新问题,所以统一收口这块一定要支持扩展,预留好钩子便于支持各个场景的差异化处理。这么说比较抽象,举个例子:比如通用场景规则是所有单子都按照系统既定配置的分区规则进行运输,但是现在有一些商家开通了一些快速送到目的地的服务,那对于这些商家的单子就不能用现有的通用规则进行分区匹配运输了,要按照新的规则进行分区匹配从而达到快速运输的目的。这就是里边的差异化。

▪复用现有的数据结构,增加分区类型,并调整对应的sql和service服务(这里不是本篇重点就不展开讲了),在兼容现有生产逻辑的基础上支持分区规则的扩展

▪结合设计模式的思想调整分区匹配规则的代码结构:采用责任链模式(这里是本篇文章的重点内容)

那么到底什么是责任链模式呢?

大牛给出的定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。

结合着现有系统的实际业务来讲讲我是如何进行套用的:开篇背景中已经介绍过现有的分区匹配规则为静态分区匹配(如某某业务点到点、某某业务点到范围区域等等,具体业务细则就不展开讲了,只要知道这里边的匹配规则一大堆即可),现在要新增一种规则支持动态(也是各种匹配,就不展开讲了),这里我把每一种分区规则都定义为一种分区类型,而每一个分区类型都定义为一个分区节点,将这些节点穿成一条链,让每个请求都在这条链中找到匹配自己的线路进行运输。

由于业务细则比较敏感,文章中就不具体透露了,不影响重点设计模式的应用理解

结合定义以及上述分析,那么实际情况到底适不适合使用责任链设计模式呢?在下认为只要能够解决上述痛点并且总体的利大于弊,那么就是适用的。首先使用此模式改造后的优点如下:

◦将请求和处理分开了(从与业务实操的耦合中解脱出来,完全不用关心请求是怎么来的,只专注于分区规则的匹配)

◦提高了系统的灵活性和扩展性(再有新的规则,只需要增加节点即可)

当然了,此模式也有一定的缺点:当责任链比较长的时候,由于每个请求都会遍历整个链条,可能会有性能的问题,同时对于不理解业务的同学调试起来也会感觉比较复杂。

整体看下来,优点是解决了我们当下的痛点,并且利于后续的扩展,而缺点中的性能部分可通过结合模板模式中预留的钩子函数(如果当下请求不适合于当下分区节点规则则跳过)拉最大限度的降低性能问题的影响,同时基于开发人员了解业务也是应当的。那这样看来总体还是利大于弊的。

说了这么多我们先来看看改造前后的分区模块的简单对比示意图吧





虽然图非常简陋,但是其对比含义还是很明显的,改造前:分区匹配和业务处理是耦合在一块的;改造后:分区匹配是一条链并且里边没有业务逻辑处理,从耦合中解脱了出来,也支持扩展。看到这里会有人疑惑:上文中不是说只新增了一个动态匹配规则吗,怎么链中有这么多节点,而且还是两条链。这里我稍作解释下:当下的两条链是经历过许多需求版本变更的,当下看起来区别已是不大,主要是因为其提供的来源业务指定场景不同抽象出来的两条链,互不干扰各自运行,而里边多处本文介绍的那些节点也是后来新增的,直至当下还在系统中使用的节点规则(这也就是文章开篇我提的:万一后边还有规则变更呢。果然还是机智如我,“预言”应验了,在效果中我会讲出这里支持扩展对于缩短工期的重要性)

三、实践过程

相信有不少读者会发现,上面又是说统一收口、又是说结合模板模式最大程度规避性能影响,那你这个责任链一种设计模式是支持不了的吧。

没错,你说对了,大聪明,确实只是使用单一的责任链设计模式远远达不到上文中所说的效果,这里我们也确实将工厂、模板、责任链模式进行了结合使用,工厂用来获取链条的bean,模板用来设置通用方法、方法间的调用以及预留给子类实现的开关、前后置处理、差异化等方法,责任链用来组合各个节点,这里简单抽几个节点将类图展示出来如下





这段就比较简单了,毕竟就是撸码,上面的类图几乎代码中的实践应用了,也是代码中的核心部分,而在实际调用中均是通过工厂来获取bean链条进行具体分区匹配的。

四、对实践过程的思考和对效果的评价

改造过后的成果主要体现在后续的扩展和维护,就如文章开篇提到的一旦分区匹配规则要做变动就会面临两个痛点,最直接的体现就是在工期上面,第一次接此块的变动新增路由规则需求时,整体实际用的工期就研发侧来说是45天,就更别说一旦遇到BUG,那测试工期也就没保障了(对应上图节点中的动态节点)





但是后来没多久就又再次增加匹配规则的新需求(对应上图节点中的到仓拼车节点),同类的需求,工期缩短近半,优45天降低到了27天,这里还包括此需求中的其他非分区模块的改造,当然了第一版的改造确实有些完美的地方,经过这次需求由进行了优化





接下来的这两个就真正体现到了什么叫做工期消失术,一个是首板分区规则需求,一个是最近B网融合需求中的直发分区规则

◦直发分区工期2天

◦首板分区工期就1天

说实话,我在没回看这些数据的时候也没想到有这么大的效果,现在回头一看我也是惊呆了,谁能想到一个设计模式的应用竟能将工期由45天缩短至了1天,这太不可思议了。

当然了其中也还有一些可以改进升级的地方,目前责任链节点的装配都是手动指定的,可改动为自动装配(我在另个业务场景中的改造中已经实现过了,这里也会进行同步改造),再有一个就是要控制节点的数量,如果数量过大则可能需要考虑兼容方案了。

俗话说滴水穿石非一日之功,冰冻三尺非一日之寒,追求强大的工具、新颖的技术固然可行,但是也不要忘了日常工作中的一点一滴的小改动,短时之间可能看不出什么,一旦量变引起了质变,我相信那结果将是非常可观亮眼的。

本文转载自: 掘金

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

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

发表于 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

本文转载自: 掘金

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

1234…399

开发者博客

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