记录一下在 GitLab CI 中提交代码出现的错误。
在 CI 中 git push
提交代码,遇到如下的错误:
remote: You are not allowed to upload code.
fatal: unable to access 'https://gitlab-ci-token:[MASKED]@git.xxx.com/group/repo.git/': The requested URL returned error: 403
看起来是 403 权限不足,但是可以看到的是提交代码的时候,使用的 remote 地址是 https://gitlab-ci-token
开头的。这是因为 GitLab CI runner 在 HTTPS 协议下执行时,不支持 git push
操作。
必须配置使用 ssh
协议,然后需要使用 /root/.ssh
目录中配置的私钥,该私钥需要有代码访问权限。
首先需要将 SSH KEY 配置到 GitLab 后台,然后将私钥放到 CI 的镜像中。
mkdir -p ~/.ssh
cp "${CI_GIT_SSH_KEY}" ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan gitlab.com > ~/.ssh/known_hosts
在 CI 脚本中将仓库的地址修改为 SSH:
git remote rm origin && git remote add origin git@gitlab.com:$CI_PROJECT_PATH.git
然后再使用 git push
就没有问题了。
在 Twitter 的时间线上能看到越来越多的 Backend-as-a-Service 的产品发布,包括 [[Firebase]], [[Supabase]], [[Railway]], [[Fly.io]], [[Okteto]], [[Nhost]] 等等,这两天又发现一款叫做 [[Appwrite]]。Appwrite 宣称自己的是 Firebase 的开源辅助,可以代替大部分的 Firebase 功能。
看来创始人起名字的时候也非常直截了当,Appwrite 就是一款为前端和移动开发人员提供的可以自行搭建的后端服务,使用 PHP 编写,提供了构建一款应用需要的最基础的一些功能,比如注册,登录,K-V 数据存储,云函数等等功能。并且 Appwrite 提供了非常多的客户端支持,包括常用的 iOS,Flutter,Android,Swift 等等,也包括了大部分的后端常用语言的 SDK,Python,Php,Ruby 等等。
Appwrite 是一个开源的、自托管的,Backend-as-a-Service(BaaS,后端即服务),可以快速构建安全的、现代的应用程序。Appwrite 提供了用户身份验证,授权,会话管理,角色访问控制,数据库,对象存储等等基础组件。
Appwrite 官方提供了直接通过 Docker 命令来安装 Appwrite 的方法,见这里。但本人觉得 docker-compose 的方式执行扩展性和可配置性都比较好,所以这里就使用 docker-compose 来安装 Appwrite。
环境变量的含义见官网。
功能
为什么用 appwrite 替代 Firebase?
通过 Appwrite 提供的身份验证功能,可以轻松地集成三方的登录服务,包括 Facebook, GitHub, LinkedIn 等等。
借助 Appwrite 可以快速构建一个用户账户登录系统,并且 Appwrite 支持用户使用邮箱密码,Magic 链接等等方式来验证登录自己的账户。
Tasks 服务提供了定期执行任务的能力,不管是 contabs 或者长时间运行的守护程序都可以实现。
Appwrite Task 服务是设置定期计划作业的方法。无需使用复杂的 crontabs 或长时间运行的守护程序进行处理,不必担心诸如容错,监视和错误日志记录之类的事情,您所要做的就是提交带有任务的表单作为 HTTP 端点和类似 cron 的语法,以指示如何通常应该执行它。
Webhooks 允许快速集成后端任务的触发,比如在新用户注册的时候发送邮件通知,或者在应用文档更新时清除缓存都可以通过 Webhooks 方式实现。
Appwrite 存储服务是让您或您的应用程序用户,安全、简单地上传和管理文件的最简单方法。Appwrite Storage API 利用了与 Appwrite 数据库相同的简单读写权限机制。这使您可以轻松地决定是否所有用户,特定用户甚至用户团队都可以访问您的文件。 Appwrite Storage 服务提供的最有用的功能之一是能够预览文件内容并将其显示为应用程序或网站中的缩略图的功能。您还可以动态更改缩略图的大小,在不同的图像格式之间转换它们(支持 webp 格式),并更改其质量以改善网络性能。
Appwrite Teams 服务允许您和您的用户创建团队并共享对不同 API 资源(如文件或文档)的许可。每个团队成员还可以担任不同的角色,以使开发者拥有更大的灵活性。
Account API 在当前登录的用户下,通常是客户端集成。而 Users API 通常是集成到服务端,在管理员的权限下,用来操作所有用户。
当通过 JWT 验证的时候,Account API 中有一些方法也可以被服务端使用。这可以允许服务端来以用户的身份执行某些行为。
因为一直使用 assh 来管理我的 ssh config,整个 SSH config 都是用 Git 仓库来管理的,但是每次一更新了 config 文件,git pull
之后 config 的文件权限都会出错:
Bad owner or permissions on /path/to/.ssh/config
发现 git 拉取的文件丢失了权限,必须通过 sudo chmod 600 ~/.ssh/config
来修改才能使用。
于是就想要了解一下 Git 仓库中怎么来管理文件权限的。
Git 只有一位用来记录权限,可执行还是不可执行。
这意味着,下面的权限是不会被 Git 追踪的:
有了这个前提知识就能够解释为什么我们使用 git diff
命令的时候,Git 有些时候会显示类似如下的文件权限:
old mode 100644
new mode 100755
说明:
Git 会给予一个没有执行权限的文件 file mode 为 100644
,给一个可执行的文件 100755
,如果将文件的权限从 7xx 修改成 6xx,或者反过来(调整可执行权限),那么 Git 就会追踪到这个修改。
Git 中可以通过如下配置来忽略文件 mode
git config core.fileMode false
如果要全局忽略,可以添加 --global
参数:
git config --global core.fileMode false
之前收到 Google Domains 邮件,info 域名将在 10 月 26 号之后从 12 美元一年涨价到 22 美元一年,现在剩下的时间不多了,晚上回家处理一下。
刚收到 Google domain 的续费通知邮件,竟然发现 info 域名需要从 $12 涨价到 $22 ,域名注册局的生意一本万利啊!
— Ein Verne (@einverne) September 27,2022
通常不同的域名注册商都会提供动态的注册费用,比如我一直使用的 Google Domains 自我开始使用起 info 域名就是 12 美元一年,很多年没有变化过,但是有一些其他的域名注册提供商会提供更加低廉的第一次注册费用,但是往往续费和转入的费用要更高。
一个域名的费用可以分成好几部分。
域名注册费用组成:1
.com
,.net
,.org
等背后的公司。这些公司都从 ICANN 买断了对 TLD(顶级域名)的管理权,所以有权利决定以什么价格售卖域名。
Verisign 是 .com 的域名注册局,在 2018 年的时候与美国商务部达成协议,允许他在之后的十年中每年将价格提高 7%。2 同样的各大域名注册商也相继提高了 .info, .mobi, .pro 等等域名的注册费用。34
首先到 https://tld-list.com/ 网站上查询 .info
域名,然后发现对于 Transfer 来说 Google 提供的价格就是最便宜的。
综合来说,还是在 Google Domains 先续费 9 年再说吧。
https://news.gandi.net/en/2020/09/how-much-does-a-domain-cost-and-what-comes-with-it/ ↩
https://www.domainnameapi.com/blog/verisign-has-decided-to-increase-the-price-of-the-com ↩
https://news.gandi.net/en/2021/12/price-increase-on-info-mobi-and-pro-domains-on-january-14-2022/ ↩
https://www.techrepublic.com/article/youre-going-to-pay-more-for-org-and-info-domains-following-icanns-lifting-of-price-caps/ ↩
ID3 是一个元数据(metadata) 的容器,通常和 MP3 文件一起。
ID3 有两个版本:
ID3v1 比较简单,存放在 MP3 文件末尾,占用 128 个字节,使用任意一个 16 进制编辑器打开 MP3,就可以看到。
V1 版本以 TAG 字符开始,记录了 MP3 文件的歌手名,标题,专辑名称,年代,风格等信息。
字节 | 长度 | 说明 |
---|---|---|
1-3 | 3 | TAG 字符,说明 ID3v1 开始 |
4-33 | 30 | 歌曲名 |
34-63 | 30 | 歌手 |
64-93 | 30 | 专辑名 |
94-97 | 4 | 年份 |
98-127 | 30 | 附注 |
128 | 1 | 音乐类别,147 种1 |
ID3v2 版本位于 mp3 开头,长度可变。
ID3v2 有四个版本
因为在文件开头,所以对 ID3v2 版本的操作要比 ID3v1 慢。
以 ID3v2.3 为例,由一个标签头和若干标签帧组成,至少有一个标签帧,每个标签帧记录一种信息,比如标题,作曲家等等。
可以通过 vim 打开 mp3 文件,然后执行 %!xxd
以 16 进制查看。
文件开头长度 10 字节,结构如下:
char Header[3]; /*必须为“ID3”否则认为标签不存在*/
char Ver; /*版本号ID3V2.3 就记录3*/
char Revision; /*副版本号此版本记录为0*/
char Flag; /*标志字节,只使用高三位,其它位为0 */
char Size[4]; /*标签大小*/
说明:
标签帧
每个标签帧都有 10 个字节的帧和至少一个字节的不固定长度的内容组成。
帧头部:
char FrameID[4]; /*用四个字符标识一个帧,说明其内容,稍后有常用的标识对照表*/
char Size[4]; /*帧内容的大小,不包括帧头,不得小于 1*/
char Flags[2]; /*存放标志,只定义了 6 位*/
说明:
大体上分三个部分:
0="Blues";
1="ClassicRock";
2="Country";
3="Dance";
4="Disco";
5="Funk";
6="Grunge";
7="Hip-Hop";
8="Jazz";
9="Metal";
10="NewAge";
11="Oldies";
12="Other";
13="Pop";
14="R&B";
15="Rap";
16="Reggae";
17="Rock";
18="Techno";
19="Industrial";
20="Alternative";
21="Ska";
22="Deathl";
23="Pranks";
24="Soundtrack";
25="Euro-Techno";
26="Ambient";
27="Trip-Hop";
28="Vocal";
29="Jazz+Funk";
30="Fusion";
31="Trance";
32="Classical";
33="Instrumental";
34="Acid";
35="House";
36="Game";
37="SoundClip";
38="Gospel";
39="Noise";
40="AlternRock";
41="Bass";
42="Soul";
43="Punk";
44="Space";
45="Meditative";
46="InstrumentalPop";
47="InstrumentalRock";
48="Ethnic";
49="Gothic";
50="Darkwave";
51="Techno-Industrial";
52="Electronic";
53="Pop-Folk";
54="Eurodance";
55="Dream";
56="SouthernRock";
57="Comedy";
58="Cult";
59="Gangsta";
60="Top40";
61="ChristianRap";
62="Pop/Funk";
63="Jungle";
64="NativeAmerican";
65="Cabaret";
66="NewWave";
67="Psychadelic";
68="Rave";
69="Showtunes";
70="Trailer";
71="Lo-Fi";
72="Tribal";
73="AcidPunk";
74="AcidJazz";
75="Polka";
76="Retro";
77="Musical";
78="Rock&Roll";
79="HardRock";
80="Folk";
81="Folk-Rock";
82="NationalFolk";
83="Swing";
84="FastFusion";
85="Bebob";
86="Latin";
87="Revival";
88="Celtic";
89="Bluegrass";
90="Avantgarde";
91="GothicRock";
92="ProgessiveRock";
93="PsychedelicRock";
94="SymphonicRock";
95="SlowRock";
96="BigBand";
97="Chorus";
98="EasyListening";
99="Acoustic";
100="Humour";
101="Speech";
102="Chanson";
103="Opera";
104="ChamberMusic";
105="Sonata";
106="Symphony";
107="BootyBass";
108="Primus";
109="PornGroove";
110="Satire";
111="SlowJam";
112="Club";
113="Tango";
114="Samba";
115="Folklore";
116="Ballad";
117="PowerBallad";
118="RhythmicSoul";
119="Freestyle";
120="Duet";
121="PunkRock";
122="DrumSolo";
123="Acapella";
124="Euro-House";
125="DanceHall";
126="Goa";
127="Drum&Bass";
128="Club-House";
129="Hardcore";
130="Terror";
131="Indie";
132="BritPop";
133="Negerpunk";
134="PolskPunk";
135="Beat";
136="ChristianGangstaRap";
137="Heavyl";
138="Blackl";
139="Crossover";
140="ContemporaryChristian";
141="ChristianRock";
142="Merengue";
143="Salsa";
144="Trashl";
145="Anime";
146="JPop";
147="Synthpop";
见附录 ↩
本文总结一下将字幕文件压制到视频中的方式,(当然我个人是非常不喜欢直接将字幕压制到视频流中作为硬字幕压制的,但有些时候可能就是需要分享这样硬字幕的视频,比如视频网站,所以也会在下文总结一下)。
按照压制方式可以分成,将字幕嵌入视频流(也就是俗称的硬字幕)适合在视频网站分享,将字幕作为单独的字幕流和视频作为封装格式,需要用播放器播放。
压制方式(推荐程度从上到下):
如果要知道如何从 mkv 文件格式中提取字幕,可以参考 这篇文章 。
作为字幕流(内封字幕、软字幕)嵌入到视频容器中。字幕流和视频和音频流具有相同的地位。视频格式中的 mkv 就是一种封装格式,通过 ffmpeg -i video.mkv
就能在输出结果中看到视频流和字幕流是属于不同的 stream 的。
MKV 封装工具:[[MKVToolNix]] MKV 提取工具:gMKVExtractGUI、MKVExtractGUI
借助 MKVToolNix 提供的界面操作即可。
介绍一下如何使用 FFmpeg 将字幕作为单独的字幕流压制到视频中。
ffmpeg -i input.mkv -i subtitles.srt -c copy -c:s mov_text output.mp4
ffmpeg -i input.mkv -i subtitles.srt -c copy -c:s srt output.mkv
说明:
-c copy -c: s mov_text
告诉 FFmpeg 对于视频,音频,和字幕文件都直接 copy-c: v copy -c: a copy -c: s mov_text
假设原始输入文件没有字幕的情况下,也可以直接
ffmpeg -i input.mkv -i subtitles.srt -c copy output.mkv
FFmpeg 会自动识别字幕文件并做映射。
ffmpeg -i input.mp4 -sub_charenc 'UTF-8' -f srt -i input.srt -map 0:0 -map 0:1 -map 1:0 -c:v copy -c:a copy -c:s mov_text output.mp4
ffmpeg -i input.mp4 -sub_charenc 'UTF-8' -f srt -i input.srt -map 0:0 -map 0:1 -map 1:0 -c:v copy -c:a copy -c:s srt output.mkv
如果要指定语言:
ffmpeg -i input.mp4 -i subtitle.en.srt -c copy -c:s mov_text -metadata:s:s:0 language=eng ouptut_english.mp4
-metadata: s: s:0
设置 metadata 格式 Stream: Subtitle: Number
从 0 开始language=eng
设置字幕的语言,使用 ISO 639 3 位英文表示如果要设置多个字幕:
ffmpeg -i ouptut_english.mp4 -i subtitle.chi.srt -map 0 -map 1 -c copy -c:s mov_text -metadata:s:s:1 language=chi output_chi.mp4
在之前的基础之上,再添加一个中文字幕。
或者直接使用一行命令:
ffmpeg -i input.mp4 -i subtitle.en.srt -i subtitle.chi.srt -map 0 -map 1 -map 2 -c copy -c:s mov_text -metadata:s:s:0 language=eng -metadata:s:s:1 language=chi output_eng_chi.mp4
使用硬编码将字幕嵌入视频的方式会更加耗时,需要重新编码文件。
[[FFmpeg]] 要在视频流上面加上字幕,需要用一个叫做 subtitles 的滤镜,要使用这个滤镜,在命令中写上 -vf subtitles=字幕文件名
,推荐不管文件名如何都在字幕文件两边加上双引号,比如 -vf subtitles="字幕 文件名"
,因为如果文件名中包含空格或其他特殊字符,在不使用双引号的情况下 Shell 会解析失败。
# 使用 subtitles 滤镜为视频添加字幕,字幕文件在视频流中,输出文件不含字幕流
ffmpeg -i input.mkv -vf subtitles="subtitles.srt" output.mkv
说明:
-vf
是 -filter: v
参数的缩写subtitles="subtitle.srt"
则是 filter 的名字,后面是字幕文件将 mkv 文件中的字幕压制到 mp4
# 将 input.mkv 中的字幕(默认)嵌入到 output.mp4 文件
ffmpeg -i input.mkv -vf subtitles=input.mkv output.mp4
如果要将其他的字幕,可以指定,比如第二个字幕:
ffmpeg -i input.mkv -vf subtitles=input.mkv:si=1 output.mkv
subtitle 更多 用法
如果要处理 ass 格式的字幕文件,那么需要 FFmpeg 启用 libass
,可以通过执行 ffmpeg —version
来查看输出中是否有 --enable-libass
,如果没有这个选项那么可能需要重新安装,或者重新编译安装 FFmpeg 。
如果要嵌入 ass
格式字幕,可以:
brew install ffmpeg --with-libass
ffmpeg -i path/to/video.mp4 -vf "ass=subtitle.ass" out.mp4
ass 格式字幕文件提供了更多的格式选择,比如加粗,斜体,字体,颜色等等,可以使用更加专业的字幕制作软件生成。
[[Clash for Windows]] 使用过程中一直没有什么问题,但是昨天心血来潮把 Clash for Windows 从 0.18.8 升级到了最新版本(0.20.5) ,然后发现节点全部 timeout。但可以排除的是这些节点肯定是可以用的,因为在手机上是完全没有问题的。
先是看 Logs 日志里面,timeout 的节点有大量的错误:
22:06:18 WRN [UDP] dial failed error=new vmess client error: dial xxxx:7830 error: 404 Not Found proxy=GLOBAL rAddr=114.114.114.114:53
查询了一通之后发现可能与 Clash Core 版本 升级 有关系,
查看了一下 Clash 的 Release Note ,在 1.90.0 的 Change Logs 中有一行:
注意vmess下的 ws-headers 和 ws-path 选项已更新
原来 Clash Core 新版本中把配置文件的 ws-headers
和 ws-path
改了个名字
ws-path
ws-headers
这两个配置项变成了如下的结构:
ws-opts:
path: /path
headers:
Host: somehost.com
完整配置示例:
# VMess
- name: "v2ray"
type: vmess
server: xxx
port: 443
uuid: 8b0edc
alterId: 0
cipher: auto
# udp: true
tls: true
# skip-cert-verify: true
network: ws
ws-opts:
path: /xxx
headers:
Host: xxxx.com
JSON 格式:
proxies:
- { name: '美国', type: vmess, server: some.pw, port: 6000, uuid: ccfb9fb3, alterId: 0, cipher: auto, udp: true, network: ws, ws-opts: { path: /, headers: { Host: some.com } }, ws-path: /, ws-headers: { Host: some.com } }
如果不想自己配置,那么可以注册使用这个站点。
另外一个可能引起 timeout 的原因可能是 Clash 的配置中开启了 DNS
dns:
enable: true
ipv6: false
开启了 DNS 之后,clash 会将域名解析发送给配置的 nameserver 解析,如果域名解析失败也会发生 timeout 情况。
其他原因:
很早之前就在 Twitter 上看到有人分享了 Arc 浏览器的使用体验,说是非常惊艳,我就稍微的浏览了一下官网,抱持怀疑的态度先注册了一下体验,一直好奇到了 2022 年能够在浏览器上做出什么样的创新,自 Chrome 横空出世以来,快,安全迅速抢占了浏览器市场。剩下的一点点份额被 Firefox,Safari,Edge,[[Vivaldi]] 等等占据,早两年的时候我也写过一篇标题略微耸动的文章 —- 我可能要抛弃用了很多年的 Chrome 改用 Vivaldi ,但事实是 3 年多过去了,我日常用的还是 Chrome,虽然 Google 在浏览器插件,隐私等等问题上这两年来一直被诟病,但至少还没有彻底地激怒我这个用户。
但可能也是 Google 作为一家广告公司,这个背景实在会让人产生一些敬畏。所以这些年 Chrome 也面临了越来越多的挑战,不管是大企业的 Edge,Safari,亦或是初创企业的,比如本文的主角 Arc Browser ,或者注重隐私的 Orion ,或者在 UI 交互上做创新的 Sidekick , SigmaOS ,都给浏览器发展一些新的思路。这么多年过去 Chrome 还能克制能够抱持整体风格的简洁,并且让地址栏发展成为 Omnibox ,Chrome 几乎成为了我的 开机应用 ,并且借助 Chrome Extension 的生态,以及这些年 Web 技术的发展,在浏览器中能做的事情越来越多,很多 Native 的应用也越来越多地被替换成 Web 应用。这一次体验 Arc 最让我惊艳的就是一些应用,比如 Notion 在 Arc 中的体验就像是 Notion 本地应用一样(虽然 Notion 发布的本地应用就能就是套了一层壳而已)。
最开始让我想要了解的功能就是 Space,侧边栏通过双指滑动可以创建新的 Space, 也可以通过下方的 +
号来创建,但是使用之后体验上就感觉是分组的标签页。
Arc 内建了一个画板,可以做笔记,剪切网页图片,这就相当于内嵌了一个素材收集工具。在地址栏边上的截图小工具的交互设计做的确实不错,可以根据页面内容动态调整要截取的内容。
Easel
在侧边栏还有一个 Library 的概念,在 Library 中会展示下载,桌面等等文件夹,其中还包括了 Arc 独有的 存储 Easel 画板的地方,存储在 Arc 中抓取的网页截图。
Library
大部分的快捷键,和使用 Chrome 都是一致的,打开标签页 Cmd+T, 关闭标签页 Cmd+w,恢复关闭的标签页 Cmd+Shift+t, 更多
但 Arc 引入的新功能必然带来新的快捷键,这里就列举一些常见的,全部的快捷键还是查看其官网,或 Cmd+, 查看吧
⌘+s
锁定或隐藏侧边栏Ctrl+Tab
切换标签页Ctrl+Shift++
创建垂直分屏如果在注册的时候遇到 「Unknown server error」 的错误
那么可能是网络无法访问 Arc 的服务,这个问题我在之前体验 Warp 终端 的时候也遇到了,现在出现的这些产品都喜欢在开篇的时候让用户注册账号使用,但是在国内的这种网络环境下就会遇到各种奇葩的问题。解决办法也非常简单,直接开启 系统全局代理 ,或者去一个没有 GFW 的地方。这里推荐使用 Clash for Windows
在 Chrome 中打开网页,我会看去 Tab 上状态,在加载的时候会有 Loading 的转圈,而在 Vivaldi 中则是更加明显的地址栏中会有色彩进度条,我会明确知道这个页面是正在加载的状态,而使用 Arc 第一个感到不适的就是当网络环境比较慢的时候,会有很长一段时间整个页面是空白状态,如果隐藏侧边栏最上方的地址栏就完全不知道是这个网站出错了还是网络有问题。虽然 Arc 刻意隐藏了所有「不必要」的内容,但却也带来了一些使用上的不便。
相同的环境下,Chrome 从来没有发生过页面或交互中间出现卡顿的情况,但是 Arc 使用过程中隔一会儿就会出现。虽然看别人演示的时候都非常流畅,但是就我个人的使用来说这一点是无法忍受的,尤其是当我想打开标签页,快速输入一些内容进行查找时,这个延迟非常明显。
今天花了一段时间体验了一下 Arc,总体来说视觉上,交互上确实带来了一些新鲜感,但似乎目前还不能被我设为默认浏览器,虽然分屏,Space 等等确实给浏览器交互带来了一些新的启发,但对于我而言这些功能并没有那么不可代替。所以短时间内我还是会继续用 Chrome,不过时常回来关心一下 Arc,看能不能带来一些划时代的革新。
如果你也想体验,可以去 官网 申请。或者使用我的邀请 1,邀请 2,邀请 3。
邀请码似乎有数量的限制,我虽然会定期更新邀请,但也会存在更新不及时的情况邀请码失效,所以如果有遇到邀请码失效的朋友,可以通过页面下方或者 About 页面联系到我,我会单独给你发邀请。
说起 Java 语言下的 Web 框架那就非 [[Spring Framework]] 不可了,但是今天在和别人在聊天的过程中发现了一个新奇的项目 Javalin。 Javalin 是一个轻量的 Web 框架。支持 [[WebSocket]], HTTP2 和异步请求。简单的看了一下官方的说明文档,确实非常轻量,几行代码就可以启动一个 HTTP 服务。
[[Javalin]] 最初是 [[SparkJava]] 的一个分支,后来受到 JavaScript 框架 koa.js 的影响,逐渐独立成一个新的项目发展。
首先来看看一个比 Hello World 稍微复杂一些的例子:
var app = Javalin.create(config -> {
config.defaultContentType = "application/json";
config.autogenerateEtags = true;
config.addStaticFiles("/public");
config.asyncRequestTimeout = 10_000L;
config.dynamicGzip = true;
config.enforceSsl = true;
}).routes(() -> {
path("users", () -> {
get(UserController::getAll);
post(UserController::create);
path(":user-id"(() -> {
get(UserController::getOne);
patch(UserController::update);
delete(UserController::delete);
});
ws("events", userController::webSocketEvents);
});
}).start(port);
验证路径参数
var myQpStr = ctx.queryParam("my-qp"); // 没有验证,返回字符串或空
var myQpInt = ctx.pathParam("my-qp", Integer.class).get(); // 返回一个整数或抛出异常
var myQpInt = ctx.formParam("my-qp", Integer.class).check(i -> i > 4).get(); // 整数 > 4
// 验证两个依赖的查询参数 :
var fromDate = ctx.queryParam("from", Instant.class).get();
var toDate = ctx.queryParam("to", Instant.class)
.check(it -> it.isAfter(fromDate), "'to' has to be after 'from'")
.get();
// 验证一个json消息体:
var myObject = ctx.bodyValidator(MyObject.class)
.check(obj -> obj.myObjectProperty == someValue)
.get();
handler
//前置handler
app.before(ctx -> {
// 在所有请求之前运行
});
app.before("/path/*", ctx -> {
// 在/path/*请求之前运行
});
//端点handler
app.get("/", ctx -> {
// 一些代码
ctx.json(object);
});
app.get("/hello/*, ctx -> {
// 捕获所有对/hello/子路径的请求
});
//后置handler
app.after(ctx -> {
// 在所有请求之后运行
});
app.after("/path/*", ctx -> {
// 在/path/*请求之后运行
});
使用 AccessManager 接口来实现验证和授权。
如果要部署 Javalin 应用程序,开发人员只需创建一个包含了依赖(使用 maven-assembly-plugin)的 jar,然后用 java -jar filename.jar
发布该 jar。Javalin 自带一个嵌入式 Jetty 服务器,无需额外的应用程序服务器。
Javalin 还有 专门为教育工作者准备的页面 ,该页面强调学生可以从 Javalin 受益,因为 Javalin 提供了嵌入式的 Jetty 服务器,所以不需要 Servlet Container/Application 服务器配置就可以开始编码。
有一系列教程可供使用,如 Running on GraalVM 和 Kotlin CRUD REST API 。可以在 教程页面 找到完整的列表。
文档页面 提供了有关 Javalin 的更多细节。用户可以通过 maven 或从手动 maven中央库 下载 Javalin。
通过 mvn package
就可以打包一个 jar 文件,直接运行 java -jar xxx.jar
就可以启动。
因为我在 macOS 下启动 Javalin 程序,默认是使用的 7000 端口,但是起来的时候发现端口被占用了。
用 lsof
查看
❯ sudo lsof -nP -i4TCP |grep 7000
Password:
Swinsian 1563 einverne 36u IPv4 0xa107511eb4d4e74b 0t0 TCP 127.0.0.1:50677->127.0.0.1:7000 (CLOSED)
Swinsian 1563 einverne 37u IPv4 0xa107511eb4d4e74b 0t0 TCP 127.0.0.1:50677->127.0.0.1:7000 (CLOSED)
ControlCe 1578 einverne 29u IPv4 0xa107511eb42171fb 0t0 TCP *:7000 (LISTEN)
查看进程
❯ sudo ps aux | grep 1578
einverne 46918 0.7 0.0 34253900 968 s000 S+ 2:37PM 0:00.00 grep --color=auto 1578
einverne 1578 0.0 0.1 36594320 36324 ?? S Sun12PM 1:24.15 /System/Library/CoreServices/ControlCenter.app/Contents/MacOS/ControlCenter
发现竟然是系统的 ControlCenter 占用了本地 7000 端口,用如下的方法禁用。
[[javalin-database]]
之前折腾 GitHub Profile 的时候发现了 [[WakaTime]] 这样一款统计编码时间的工具,之后在读 waka-readme 项目的时候发现,还有两个完全开源的后端兼容版本,一个是 Golang 编写的 [[wakapi]] ,一个是 Huskell 编写的 hakatime 。这篇就来总结一下我使用 wakapi 的过程。
wakapi 是一个兼容 [[WakaTime]] 的可自行架设的后端程序,和 WakaTime 一样可以用来统计代码。
使用 docker-compose 安装。
直接 clone 项目,修改环境变量,然后启动即可。
git clone https://github.com/einverne/dockerfile.git
cd dockerfile/wakapi/
cp env .env
# edit .env setup SALT and WAKAPI_DATA
# SALT 可以执行命令 cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w ${1:-32} | head -n 1
# WAKAPI_DATA 配置一个本地可读写的路径
docker-compose up -d
我的配置中没有暴露 3000 端口,我是和 Nginx Proxy Manager 一起使用的,在 Nginx Proxy Manager 后台,配置一个 HOST,设置 wakapi:3000
,然后去 [[Cloudflare]] 后台将域名 wakapi.einverne.info
设置一个 A 记录指向 Nginx 所在的服务器。等待 DNS 生效,访问后台 wakapi.einverne.info
后台即可。
我个人会一直使用 https://wakapi.einverne.info 服务,所以如果你感兴趣,也可以直接使用这个服务。
服务启动之后,注册登录,然后就可以配置编辑器插件,把 IntelliJ IEDA,[[VSCode]],[[Vim]] 先配置上。这部分可以直接查看 WakaTime 的官方文档。
编辑客户端配置 ~/.wakatime.cfg
,因为使用 Self-hosted 的后端,所以需要设置 api_url
。api_key
则从后台获取即可。
[settings]
api_url=https://wakapi.einverne.info/api
api_key=b5b0xxx
proxy=
debug=false
status_bar_enabled=true
今天偶然在浏览 Obsidian 插件库的时候发现了 WakaTime 的插件,安装之后就和编程 IDE 一样,会直接使用 HOME 目录下的配置。所有的数据都可以完美的上传到 Wakapi 的后台。