像 systemd 一样管理 MacOS 后台常驻任务

引言 想在 MacOS 后台跑一个 frp 服务,想到 MacOS 是类 Unix 系统所以应该跟 Linux 差不多,自然而然的想到使用 systemd 将 frp 服务作为守护进程管理。然鹅输入vim /etc/systemd/sys之后按了半天 tab 都不自动补全为vim /etc/systemd/system,一看/etc目录下压根就没有systemd这个目录。上网一搜才知道 MacOS 压根就没有 systemd,取而代之的是 launchd。既然都提到了,就简要介绍一下吧。 起源 传统的 Linux 系统使用 System V init 或 BSD init 作为系统的第一个进程,负责启动其他服务和进程。随着系统复杂性的增加,这些 init 系统暴露出一些缺点,比如串行启动服务、启动速度慢等。 随后,upstart 项目引入了并行启动服务等概念用以解决 System V init 存在的问题,但仍然存在一些限制。 接着,systemd 诞生了,它提供更高效、更灵活的系统启动和管理方式。但是由于 systemd 使用了 cgroups 等组件以实现其特性,所以只适用于Linux。 苹果系统是闭源的,但是 launchd 却是开源的,还挺有意思。其最早由 Dave Zarzycki 等人创建,并在Mac OS X Tiger(10.4版本)中首次引入,用于替代传统的 init 脚本、SystemStarter 以及其他一些服务管理工具,如 inetd、atd 和 crond 等。 systemd 实战入门 在本节中,我们首先通过一个小🌰来入门 systemd,然后接下来给出 service 文件(核心)的详细说明。 小例子:后台记录时间 一、首先我们需要编写一个需要后台运行的脚本,这里以每10秒输出一次时间的脚本/root/bin/systemd_test.sh为例,内容如下 1 2 3 4 5 6 7 #!/bin/bash while true; do CURRENT_TIME=$(date +"%Y-%m-%d %H:%M:%S") echo "$CURRENT_TIME" sleep 10 done 二、赋予脚本可执行权限 1 chmod +x /root/bin/systemd_test.sh 三、先执行一下看看有没有问题 四、创建 systemd 服务文件 在/etc/systemd/system/目录下创建一个服务文件,名字随意,但必须以.service结尾,本示例为/etc/systemd/system/record_time.service,内容如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 [Unit] Description=Record Time Service After=network.target [Service] Type=simple ExecStart=/root/bin/systemd_test.sh Restart=on-failure RestartSec=10s StandardOutput=append:/var/log/record_time/output.log StandardError=append:/var/log/record_time/error.log [Install] WantedBy=multi-user.target 五、执行如下命令即可启动服务 1 2 3 systemctl daemon-reload systemctl start record_time # 启动服务 systemctl enable record_time # 设置服务开机自动启动 成功运行示例如下 如果启动失败,可以使用命令journalctl -u record_time查看失败日志。比如我最开始就因为忘记创建/var/log/record_time目录导致启动失败了😂 service 文件详解 看到网上有一篇介绍 service 文件如何编写的文章 OpenSUSE: How to write a systemd service,已经写的很通俗很详细了,这里就不重复造轮子了。 用户级 systemd 上面我们给出的例子是需要管理员权限的,普通用户是无法在/etc/systemd/system目录下创建.service文件的 但是我们可以在自己的用户目录下创建,具体的目录为~/.config/systemd/user/xxx.service,service 文件的内容和普通 service 文件是相同的,相应的管理命令也稍有不同(需要加上--user参数),如下 1 2 3 systemctl --user daemon-reload systemctl --user start record_time # 启动服务 systemctl --user enable record_time # 设置用户登录后自动启动 有一点比较坑的是执行systemctl --user enable xxx后的后台守护进程只在用户登录后运行,用户退出登录后服务就停止了。有一个解决办法是设置用户登录会话为逗留状态,这样即使用户注销,其会话仍然会保持,也就允许后台服务或定时任务继续运行,命令如下 1 2 loginctl enable-linger <username> # 启动逗留状态 loginctl disable-linger <username> # 关闭逗留状态 launchd 实战入门 同上,我们仍然是先给出一个例子,然后再给出具体解释。 例子:后台记录时间 (没错,还是这个例子,我们主要关注操作方式与 systemd 的不同) 一、编写脚本~/Scripts/launchd_test.sh,内容如下 1 2 3 4 5 6 7 #!/bin/bash while true; do CURRENT_TIME=$(date +"%Y-%m-%d %H:%M:%S") echo "$CURRENT_TIME" sleep 10 done 二、赋予脚本可执行权限 1 chmod +x launchd_test.sh 三、先执行一下,看看有没有问题 四、创建 plist 配置文件 在~/Library/LaunchAgents目录下创建一个服务文件,名字随意,但必须以.plist结尾,本示例为~/Library/LaunchAgents/com.pushihao.record_time.plist,内容如下 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 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.pushihao.record_time</string> <key>ProgramArguments</key> <array> <string>/Users/itgrape/Scripts/launchd_test.sh</string> </array> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <true/> <key>StandardOutPath</key> <string>/Users/itgrape/logs/record_time/output.log</string> <key>StandardErrorPath</key> <string>/Users/itgrape/logs/record_time/error.log</string> <key>WorkingDirectory</key> <string>/Users/itgrape/Scripts/</string> </dict> </plist> 五、加载/卸载服务 旧: 1 2 launchctl load ~/Library/LaunchAgents/com.pushihao.record_time.plist # 加载服务 launchctl unload ~/Library/LaunchAgents/com.pushihao.record_time.plist # 卸载服务 新(更推荐): 1 2 launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.pushihao.record_time.plist # 在用户 GUI 域中注册服务 launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.pushihao.record_time.plist # 从用户 GUI 域中卸载服务 六、执行命令启动服务 1 2 3 4 5 launchctl list | grep com.pushihao.record_time # 检查服务是否被加载 launchctl start com.pushihao.record_time # 启动已加载服务 launchctl stop com.pushihao.record_time # 停止服务 launchctl enable gui/$(id -u)/com.pushihao.record_time # 设置用户登录自动启动(bootstrap 后默认开启) launchctl disable gui/$(id -u)/com.pushihao.record_time # 关闭用户登录自动启动 注意:因为 KeepAlive 设置为 true,所以手动 stop 后,launchd 可能很快会再次尝试启动。要想永久停止,需要使用上述卸载服务的 unload/bootout 命令。 成功运行示例如下 还会弹出个这玩意 plist 文件解释 这里去看 Apple 的官方文档即可 Daemons and Services Programming Guide,它介绍的已经非常详细了。这里就简要介绍一下上面例子中各配置项的作用。 Label: 服务的唯一标识符,launchctl命令会用到它。 ProgramArguments: 要执行的命令和参数。数组的第一个字符串是要执行脚本的绝对路径。 RunAtLoad: <true/>表示当服务被加载时立即启动。 KeepAlive: <true/>这是实现长期运行的关键!如果设为true,launchd会监视该进程,一旦它退出(无论是因为错误崩溃还是正常结束),launchd会立刻重新启动它。 StandardOutPath: (可选) 重定向标准输出(脚本中echo的内容)到指定文件。 StandardErrorPath: (可选) 重定向标准错误到指定文件。这对于排查脚本错误至关重要。 WorkingDirectory: (可选) 设置脚本运行前的工作目录。 系统级 launchd 注意看,这里我的介绍顺序是和 systemd 是反着的。systemd 我给出的例子是系统级,随后我又给出了用户级 systemd 的使用方法。而该 launchd 的例子我给出的是用户级,接下来我介绍一下系统级 launchd 的使用。 注意:上面我们已经提到了旧版和新版的加载/卸载命令,他们对应的用户级/系统级 launchd 的使用方式也是不同的。 旧版 load/unload 命令: 其区别主要在于 plist 文件的存放位置,系统级 launchd 的 .plist 文件放在/Library/LaunchDaemons/目录下,而用户级 launchd 的 .plist 文件放在~/Library/LaunchAgents/或/Library/LaunchAgents/目录下。他们之间的区别如下: ~/Library/LaunchAgents 启动时机:仅当特定用户登录时启动。 运行权限:以当前用户的权限运行。 访问GUI:可以。因为它们在用户的图形会话中运行,所以这些任务可以创建窗口、显示菜单栏图标或执行其他与用户界面交互的操作。 适用场景:适用于仅当前用户需要的服务,例如个人的定时任务或用户特定的后台程序。 /Library/LaunchAgents 启动时机:当任何用户登录时启动。 运行权限:以当前登录用户的权限运行。 访问GUI:可以。同上。 适用场景:适用于所有用户共用的用户级服务,例如某些需要在用户登录时启动的系统服务。 /Library/LaunchDaemons 启动时机:在系统启动时运行,无需用户登录。 运行权限:通常以root用户的权限运行,但也可以指定其他用户。 访问GUI:不可以。因为它们独立于任何用户,所以它们无法(也不应该)与用户界面进行任何交互。 适用场景:适用于需要在系统启动时运行的与用户无关的全局服务,例如网络服务、数据库服务等。 除此之外,/System/Library/LaunchDaemons/和/System/Library/LaunchAgents/目录下也会保存有一些.plist文件,这些文件是由苹果系统提供的系统守护进程或用户代理。一般不要动它们。 新版 bootstrap/bootout 命令: bootstrap 与旧的 load 命令最大的不同在于明确性。 launchctl load(旧方式)通过.plist文件的存放路径来推断我们想要将服务注册到哪个域。这种方式比较模糊,依赖于文件路径的约定,现在苹果官方已不推荐在新的脚本中使用。 launchctl bootstrap(新方式)强制要求我们必须明确指定要注册到哪个域,命令本身就包含了所有信息,不再依赖于文件路径来做判断。这种方式更清晰、更精确,减少了歧义,是目前官方推荐的方式。 通过域信息,我们就可以指定该任务是在哪个级别下运行了。域信息如下 system 域:类似于老方式的/Library/LaunchDaemons。 gui<uid> 域:在指定用户登录后创建的图形会话中运行,类似于老方式下的~/Library/LaunchAgents。 user<uid> 域,用于注册指定用户后台代理,但不提供GUI访问权限。 虽说使用了新版方式后理论上.plist文件可以放在任何位置,但是由于系统启动或用户登录时 launchd 进程只会去扫描一组预先定义好的、受信任的标准目录(也就是我们上面所说的5个目录)而并不会去扫描整个硬盘来寻找所有.plist文件。因此如果我们的.plist文件放在一个自定义目录(比如/Users/itgrape/Launchd/),那么在扫描阶段,launchd 根本就不会去查看那个目录,因此它永远发现不了我们的服务,这也就导致了 enable 可能会失效。兼容起见,还是放在上面我提到的三个目录其中之一里面吧。 在读完了上述章节之后,相信读者也已经对 systemd 和 launchd 的基本使用有了一定的了解。接下来我们在设计层面比较一下他们之间的差异。 两大体系对比 它们虽然干着类似的活儿——管理那些开机启动的、默默在后台运行的服务(守护进程),以及响应各种事件触发任务,但它们两个的设计理念以及工作方式却大不相同。 启动方式 Launchd:按需启动 Mac 上有很多服务(比如打印服务、文件共享服务等)。默认按需启动,也就是说 Launchd 让这些服务平时都在“睡觉”,节省资源。只有当真正有人或系统事件需要它时(比如我们点了“打印”,或者有个网络连接请求进来),它才会迅速唤醒对应的服务。服务干完活,如果没特别要求,它就又回去“睡觉”了。这样电脑就能又快(响应我们的操作时快)又省电。除非指定了 KeepAlive 为 true,服务才会一直运行。 Systemd:开机并行驱动 它首要目标就是让 Linux 开机速度大大加快。怎么做到?不是按需启动,而是并行启动。分析好各个服务之间的依赖关系(谁先启动谁后启动),然后尽可能多同时启动没有依赖冲突的服务。结果就是:系统整体启动嘎嘎快。 文件组织 Launchd:所有配置全放在.plist文件(一种 XML 或二进制格式的文件)里。这个文件描述了服务叫什么名字(Label)、启动命令是什么(ProgramArguments)、监听什么事件(Sockets, WatchPaths, StartCalendarInterval)、要不要一直跑(KeepAlive)等等。简单来说,就这一种文件。 Systemd:配置是按单元(Unit)分的(.ini风格),有不同的类型,每种单元单独写一个文件: .service:定义要运行的后台服务本身(启动命令、重启策略等)。 .socket:定义监听一个网络端口或 Unix Socket(用来按需启动服务)。 .timer:定义定时任务(替代 cron)。 .path:定义监控文件或目录变化(变化了启动服务)。 .target:定义一组单元(类似传统 Linux 的运行级别)。 … 其他(.mount, .slice 等)。 日志查看 Launchd:服务自己决定日志写哪。 Systemd:它自己带了个日志系统叫 journald。所有由 systemd 管理的服务的日志(包括它们的输出信息),默认都统一收拢到这里。然后用journalctl命令就能查、过滤、跟踪所有服务的日志。 快速参考备忘录 接下来我将用一个清晰的表格概括 systemd 和 launchd 的使用。 表格中...表示带后缀的配置文件的绝对路径。 功能systemd (Linux)launchd (macOS) 配置文件/etc/systemd/system/my-app.service~/Library/LaunchAgents/com.me.myapp.plist 启用并立即启动sudo systemctl enable –now my-applaunchctl load … | launchctl bootstrap … 禁用并停止sudo systemctl disable –now my-applaunchctl unload … | launchctl bootout … 启动服务sudo systemctl start my-applaunchctl start com.me.myapp 停止服务sudo systemctl stop my-applaunchctl stop com.me.myapp 查看服务状态sudo systemctl status my-applaunchctl list 查看日志sudo journalctl -u my-app -ftail -f /path/to/your.log 重载配置sudo systemctl daemon-reload需要先 unload 再 load 总结 systemd 使用于 Linux,而 launchd 适用于 MacOS,并非替代关系。

2025/6/14
articleCard.readMore

以ORM看封装的边界

引言 今天在看《Go语言高级编程》这本书,注意到作者在谈论 ORM 框架时提到了两句话 “喜欢强类型语言的人一般都不喜欢语言隐式地去做什么事情,例如各种语言在赋值操作时进行的隐式类型转换然后又在转换中丢失了精度的勾当,一定让你非常的头疼。所以一个程序库背地里做的事情还是越少越好,如果一定要做,那也一定要在显眼的地方做” “但这样的分析想证明的是,ORM 想从设计上隐去太多的细节。而方便的代价是其背后的运行完全失控。这样的项目在经过几任维护人员之后,将变得面目全非,难以维护” 我看到这两段话时第一时间想到的是面向对象程序设计语言中的封装以及高级编程语言对低级编程语言的抽象。封装实现的不就是隐藏功能的内部实现细节,只将使用方式暴露出来?而高级编程语言相较于底层编程语言(比如汇编)的进步不也就是对细节的隐藏? 但我仔细一想,也不是这么回事。经过一番查阅资料后,我觉得他们还是有本质区别的,关键在于“隐藏什么”以及“隐藏的代价”是否可控和透明。 辨析1: 封装 vs ORM 封装隐藏的是模块内部的实现细节(数据存储方式、具体算法步骤等)。它提供的是一个清晰、稳定、语义明确的接口。使用者通过接口调用功能,无需关心内部如何完成。接口本身是显式的、强类型的,调用行为是可控的、可预测的。封装的目的是降低耦合、提高内聚、增强安全性。它并没有试图改变或掩盖操作本身的本质语义。 ORM 的问题就在于它试图隐藏的往往是操作本身的本质和关键细节,甚至与数据库交互的语义都有可能被改变。《Go语言高级编程》这本书的作者还举了一个例子: 1 2 o := orm.NewOrm() num, err := o.QueryTable("cardgroup").Filter("Cards__Card__Name", cardName).All(&cardgroups) 他指出“很多 ORM 都提供了这种 Filter 类型的查询方式,不过在某些 ORM 背后可能隐藏了非常难以察觉的细节,比如生成的 SQL 语句会自动 limit 1000”。这确实是一件很可怕的事情。 抛开上面的例子不谈,数据库操作本来就有其固有的复杂性。ORM 试图用面向对象的简单模型去映射关系型数据库的复杂世界,这种映射本身就可能引入歧义或意外的行为。 当遇到性能瓶颈、复杂查询或需要精确控制数据库行为时,ORM 又迫使开发者不得不去理解它试图隐藏的东西。这时,当初为了方便而引入的抽象,反而增加了认知负担和调试难度。 辨析2: 高级语言的抽象 vs ORM 高级语言隐藏的往往是机器底层的细节,比如寄存器、内存地址、指令集等。它们使得开发人员可以用心关注业务逻辑的实现而非程序在计算机上的运行方式。这种抽象是基础性的、安全的。编译器/解释器只负责高效并且准确地将高级语言程序代码翻译成底层机器代码,这种翻译通常是可靠并且行为一致的。虽说大部分编译器会进行编译优化,但这并不会影响程序本身的语义。 反观 ORM,它是在一个已经存在的、成熟的、语义明确的领域(关系数据库)之上,叠加了另一层领域模型(对象模型)。这层抽象可不是基础性的,它更像是一个胶水层。它的目标是弥合两个不同范式的差异,但这种弥合本身就可能引入新的复杂性和不确定性。它的“翻译”(对象操作 -> SQL)过程比高级语言编译要复杂得多,充满了各种配置选项和潜在的陷阱,其行为往往不如编译器那样确定和透明。 总结 经过一番思考后,我还是比较赞同该书作者的看法的。作者批评的并不是“隐藏细节”本身,而是过度地、不透明地隐藏那些对理解系统行为、性能和可维护性至关重要的细节。封装的隐藏是为了程序解耦和;高级语言的抽象是为了提供更为强大的基础能力。而 ORM 的过度抽象,其危险在于它让开发者在一个“方便”的幻象下工作,却可能在后台进行着复杂、低效甚至难以追踪的操作,最终导致系统变得像一个黑盒,难以理解、调试和优化,这正是作者担忧的“运行完全失控”和“面目全非,难以维护”的根源。好的抽象应该让复杂的事情变简单,而不是让简单的事情背后变得复杂且不可见。 但是🤓,让数据的操作和存储的具体实现相剥离这件事情我认为还是很有进步意义的,对于大部分 CURD项目,ORM 确实能极大地提升开发效率(比如大部分 ORM 都会有代码生成器,给它一个数据库地址,一行命令就能从 DAO 层生成到 Controller 层。真的牛逼)。我本身也是个 ORM 的重度使用者。 最后,用导师曾经说过的一句话作为文章结尾: 你犯下的很多小错误在当下可能并不会显现出来,但在未来的某一刻它们极有可能会突然爆发,而且爆发的时候你还不知道问题出在哪。

2025/5/30
articleCard.readMore

Git Merge VS Git Rebase: 如何优雅地合并分支?

有关 Git 的常用基本操作,我在👉 Git 基本使用这篇文章里已经介绍过了,这里不多赘述。我们今天主要讲讲 Git Merge 与 Git Rebase 的基础概念与使用场景。 概念解释 虽说两者都可以用来将一个分支(为了表达清晰我们暂且称之为待合并分支)合并到另一个分支(我们称之为目标分支或当前分支),但设计理念却是完全不同的。我们首先介绍两者的基本概念以及使用方法,在随后的使用场景中我们再来比较两者的区别。 Git Merge 首先需要知道,Git Merge 有两种常见的合并方式。 快进合并(Fast-forward merge) 此合并方式发生的条件为目标分支自创建出待合并分支后没有任何提交。合并过程可以简单理解为目标分支将待合并分支的多出来的提交记录补到自己的后面,并移动自己的HEAD指针到最后一个提交记录。 特点: 保留两个分支的提交记录,并且提交记录为线性 最简单的一种情况,不会发生冲突 合并后的最后一次提交记录为待合并分支的提交,合并操作本身不会创建新的提交记录 举个栗子,场景如下:Dabby 从 main 分支的提交 Initial commit 拉取了 dev 分支,然后依次进行了提交 Add login 和 Fix bugs。 最后在 main 分支上执行 git merge dev 合并 dev 分支。 执行 git log --all --graph --oneline --decorate 查看拓扑结构如下(合并后): 普通合并(Recursive merge) 此合并方式发生的条件为目标分支自创建出待合并分支后目标分支和待合并分支都有提交。可以理解为是快进合并到升级版本。 特点: 保留两个分支的提交记录 可能会发生冲突 合并操作本身会创建一个新的合并提交记录 举个🌰,场景如下:Dabby 从 main 分支的提交 Initial commit 拉取了 dev 分支,然后进行了提交 Fix bugs,与此同时,main 分支也创建了一个 Update 提交。 最后在 main 分支上执行 git merge dev -m "Merge" 合并 dev 分支。 执行 git log --all --graph --oneline --decorate 查看拓扑结构如下(合并后): 除了知道这两种合并方式外,我们还要知道在合并过程中的冲突原因以及处理策略。 原因:当两个分支修改了同一文件的相同部分时,Git 无法自动合并,此时便会产生合并冲突。 解决步骤: 运行 git merge 后遇到冲突 使用 git status 查看冲突文件 手动编辑冲突文件(文件中有 <<<<<<<, ======= 和 >>>>>>> 标记),删除冲突标记并保留想要的代码 使用 git add <文件名> 标记冲突已解决 继续合并:git merge --continue 完成合并:git commit 如果不想合并了或者冲突无法解决,执行 git merge --abort 回到合并前的状态。 此外 git merge 命令本身还提供了一些常见的合并选项以及合并策略,我们这里简单介绍一下。 高级合并选项 使用方式为 git merge [option] branch_name 如 git merge --ff-only dev。 --no-ff 强制创建新的合并提交,即使合并可以通过快进合并完成 --ff-only 仅允许快进合并,若无法快进则合并失败 --squash 将待合并分支的多个提交压缩为单个新提交,不保留待合并分支的提交历史 --no-commit 合并代码到目标分支的工作区但不提交,允许手动检查或调整 合并策略参数 使用 -s 指定合并策略,不加 -s 参数时默认为 recursive 策略。大多数情况下用默认策略就足够了,使用方式如 git merge -s recursive dev。 常用合并策略有 recursive 基于递归三路合并算法,通过共同祖先、目标分支和待合并分支的提交进行三方合并,自动解决非冲突修改,冲突部分需手动处理 resolve 简化版的三路合并,仅寻找一个共同祖先,避免递归合并虚拟节点,可能减少自动合并但增加冲突概率 octopus 同时合并多个分支,如 git merge -s octopus feature1 feature2 feature3 subtree 将子目录作为独立仓库合并,适用于模块化项目或子仓库管理,如 git merge -s subtree module-folder ours 完全保留当前分支内容,忽略待合并分支的所有修改(即使无冲突),仅记录待合并分支的提交历史 冲突解决策略 使用 -X 参数指定冲突解决策略,不加 -X 参数时默认不会应用任何冲突解决策略。使用方式如 git merge -X ours。 常用的冲突解决策略有 ours 冲突时自动选择当前分支的代码,非冲突部分正常合并 theirs 冲突时自动选择待合并分支的代码,非冲突部分正常合并 ignore-space-change 忽略空格差异(如缩进或换行符),减少无意义冲突 patience 优化差异比对算法,更关注代码结构而非逐行匹配,提高合并准确性 综上,我们可以写出如下命令 1 git merge --no-ff -s recursive -X patience dev -m "Merge dev" Git Rebase Rebase(变基)本质上是将一系列提交从一个分支移动到另一个分支的顶端。注意:在变基操作中,"当前分支"通常为个人开发分支,如 dev 分支。细品接下来的 5 个步骤。 Git Rebase 都做了哪些事? Git 会先找到当前分支和待合并分支(如 main)的共同祖先提交,即分叉点 然后提取当前分支上所有分叉点之后的提交记录(保存为补丁) 丢弃当前分支分叉点之后的提交记录(第 2 步的补丁还在) 在当前分支的顶端应用待合并分支分叉点之后的提交记录 在当前分支的顶端根据第 2 步的补丁创建一组全新的提交 还没明白?我们举个例子,场景如下:Dabby 从 main 分支的提交 Initial commit 拉取了 dev 分支,然后进行了提交 C 和 D。与此同时,main 分支也创建了一个 A 提交。 最后在 dev 分支上执行 git rebase main 变基 main 分支。 因为是纯线性历史,这里就不放拓扑图了。以下是提交记录的线性关系表示。 变基前 main: Initial commit -> A dev: Initial commit -> C -> D 变基后 main: Initial commit -> A dev: Initial commit -> A -> C' -> D' 可以看到,1.变基后并不会修改待合并分支(main)。 2.变基后自身(dev)的提交历史发生改变。3.如果想让修改合并到 main 分支则必须切换到 main 分支后执行一次 Fast-forward 合并。 变基遇到文件冲突后我们可以解决: 运行 git rebase main 后遇到冲突 使用 git status 查看冲突文件 手动编辑冲突文件(文件中有 <<<<<<<, ======= 和 >>>>>>> 标记),删除冲突标记并保留想要的代码 使用 git add <文件名> 标记冲突已解决 执行 git rebase --continue 继续变基 或者执行 git rebase --abort 取消变基。 有个有意思的事,git rebase 命令还有一个 -i/--interactive 参数,可以开启交互式变基模式,允许用户编辑提交历史(如合并提交、修改提交信息、调整顺序等)。 使用场景 通过上面的学习,我们对 Git Merge 和 Git Rebase 的基础概念以及合并方式有了一个大致的了解,接下来我们需要了解他们的区别以及在各种场景下应该使用什么方式。 一张表格总结它们的核心区别: 对比维度Git MergeGit Rebase 合并方式创建新的合并提交,保留两个分支的提交历史将当前分支的提交应用到待合并分支的最新提交后 历史记录保留原始提交记录重写历史 适用分支公共分支(如main)以及需要保留合并历史的分支个人开发分支或本地分支 冲突处理合并提交时一次性解决冲突变基过程中逐个提交解决冲突(可能多次) 是否线性不一定为线性一定为线性 典型操作git switch main git merge devgit switch dev git rebase main 综上所述: 一般无特殊需求的情况下都用 git merge 是最稳妥的选择。 多人协作下的公共分支(如 main 分支)必须用 git merge。千万不要在公共分支上执行变基操作。 个人项目或者本地项目想用啥用啥。 优雅,实在是优雅。

2025/4/10
articleCard.readMore

修改Linux内核模块以支持WG

背景 我的服务器系统是OpenCloudOS 9.2,内核为6.6.34-9.oc9.x86_64。 我想在服务器上使用Wireguard,按理说5.6之后的Linux内核应该是内建该模块的,但是该系统中并没有。 于是通过重新编译内核的方式来启用Wireguard的支持。 编译安装内核 内核源码位置 可以使用原本的内核源码:/usr/src/kernels 下载新内核源码:https://www.kernel.org/ 如6.12.7的源码包:https://www.kernel.org/pub/linux/kernel/v6.x/linux-6.12.7.tar.gz 安装相应工具包 1 2 3 4 dnf groupinstall -y "Development Tools" dnf install -y gcc make ncurses-devel bison flex elfutils-libelf-devel openssl-devel bc perl dnf install -y kernel-devel-$(uname -r) kernel-headers-$(uname -r) dnf install -y pkgconfig libmnl-devel 配置内核模块 进入内核源码所在的文件夹,首先复制原内核的配置项 1 cp /boot/config-$(uname -r) .config 允许Wireguard内核 1 make menuconfig 输入命令后会进入一个配置菜单,Wireguard配置项在Device Drivers --> Network device support --> WireGuard secure network tunnel,输入M(module)或Y(built-in),也可以开启其后的Debugging checks and verbose messages(输入Y)。 其他模块同理,把自己想要的配置都配置完成后通过光标先Save再Exit即可。 通过grep命令查看模块是否被正确修改 1 grep WIREGUARD .config 编译安装内核 1 2 3 4 5 6 7 8 # 若之前编译过,先清理一下 make clean make mrproper # -j表示使用多个CPU核心进行编译,提高速度 make -j$(nproc) make modules_install make install 这步会比较慢,耐心等待即可。 更新引导程序 1 2 grub2-mkconfig -o /boot/grub2/grub.cfg reboot 重启后可以检查内核是否被成功安装 1 uname -r 检测WireGuard模块 检测wireguard模块是否被包含在内核中 1 ls /lib/modules/$(uname -r)/kernel/drivers/net/wireguard/ 加载该模块 1 modprobe wireguard 检测模块是否被加载 1 lsmod | grep wireguard 完成!

2024/12/31
articleCard.readMore

OpenLDAP折腾日记

折腾了几天OpenLDAP,终于是把它给部署上了。对于软件部署这件事,我的评价是不要完全相信网上任何一篇几年前的教程(包括这篇),可能一些操作是对的,但另一些操作已经过时了,这会导致一些莫名奇妙的错误。配置文件中配置项上一般会有注释,看懂了再改。 OpenLDAP服务端部署 Linux:RockyLinux9 安装相关软件 这步没啥说的,照着安装就行了 1 2 3 dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm dnf upgrade -y dnf install -y openldap openldap-servers openldap-clients nginx phpldapadmin 安装后直接启动,不用顾虑 1 systemctl start slapd 初始化 首先查看自己的配置文件命令,这很重要。执行ls /etc/openldap/slapd.d/cn\=config/ 然后生成密码,执行命令 slappasswd -s your_password,这会在下面生成一个加密后的密码,如{SSHA}+TszwnXVhnhH5HFGK5Nf4A0xZWGHrmRN,把这个记录下来。 创建以下三个文件 init.ldif 1 2 3 4 5 6 7 8 9 10 dn: olcDatabase={2}mdb,cn=config changetype: modify replace: olcRootDN olcRootDN: cn=admin,dc=pushihao,dc=com - replace: olcSuffix olcSuffix: dc=pushihao,dc=com - replace: olcRootPW olcRootPW: {SSHA}+TszwnXVhnhH5HFGK5Nf4A0xZWGHrmRN access.ldif 1 2 3 4 dn: olcDatabase={1}monitor,cn=config changetype: modify replace: olcAccess olcAccess: {0}to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" read by dn.base="cn=admin,dc=pushihao,dc=com" read by * none base.ldif 1 2 3 4 5 6 7 8 9 10 11 dn: dc=pushihao,dc=com o: PSH Organization dc: pushihao objectClass: top objectClass: dcObject objectclass: organization dn: cn=admin,dc=pushihao,dc=com cn: admin objectClass: organizationalRole description: Directory Manager 文件解读(前两个文件):第一行的dn指定了要修改的条目的唯一标识符,根据刚刚查看的名称来。changetype指定了对条目的修改类型。replace指定了要修改的字段名称,其接下来的一行表示修改后的值。记得把olcRootPW字段替换为刚刚生成的密码。 文件解读(第三个文件):第一段新建一个域,第二段新建了一个管理员。 文件创建好之后执行文件进行修改 1 2 3 ldapadd -Q -Y EXTERNAL -H ldapi:/// -f init.ldif ldapadd -Q -Y EXTERNAL -H ldapi:/// -f access.ldif ldapadd -x -D 'cn=admin,dc=pushihao,dc=com' -w 'your_password' -f base.ldif 导入基本的Schema 1 2 3 ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/cosine.ldif ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/nis.ldif ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/inetorgperson.ldif 使用PHPLDAPAdmin管理OpenLDAP 修改/etc/phpldapadmin/config.php 1 2 3 $servers->setValue('login','attr','dn'); $servers->setValue('login','anon_bind',false); $servers->setValue('login','allowed_dns',array('cn=admin,dc=pushihao,dc=com')); 修改解读:第一行表示使用完整的dn作为登录凭据,第二行表示不允许匿名访问,第三行表示仅允许指定的dn进行登录 修改/etc/nginx/nginx.conf,添加一个server 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 server { listen 8080; root /usr/share/phpldapadmin/htdocs; index index.php; location / { try_files $uri $uri/ =404; } location ~ \.php$ { include fastcgi_params; fastcgi_pass unix:/run/php-fpm/www.sock; fastcgi_param SCRIPT_FILENAME /usr/share/phpldapadmin/htdocs$fastcgi_script_name; include fastcgi_params; } location ~ /\.ht { deny all; } } 注意fastcgi_pass这个配置项:查看/etc/php-fpm.d/www.conf这个文件,把fastcgi_pass这个配置项和/etc/php-fpm.d/www.conf文件中的listen配置项保持一致(它监听9000端口你也监听,它若用套接字你也用套接字)。 配置完成后重启nginxnginx -s reload即可在浏览器输入ip:8080访问PHPLDAPAdmin的web界面,用户名为cn=admin,dc=pushihao,dc=com,密码为最开始设置的密码(输入未加密的)。 OpenLDAP SSH客户端部署 服务端起来之后就可以根据需求配置各种各样支持ldap的客户端了,ssh是一个常见的需求。这里使用轻量级的nslcd工具。 安装相关软件 没啥说的,装就完了 1 2 3 dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm dnf upgrade -y dnf install -y openldap-clients nss-pam-ldapd 配置nslcd 修改配置文件/etc/nslcd.conf,参考以下内容,如果之前的配置跟我不同(如密码),则灵活修改 1 2 3 4 5 6 7 8 9 uid root gid root uri ldap://ldap-node-01/ base dc=pushihao,dc=com binddn cn=admin,dc=pushihao,dc=com bindpw root ssl no 修改配置文件/etc/nsswitch.conf,参考以下内容,灵活修改 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 passwd: files ldap sss systemd shadow: files ldap group: files ldap sss systemd hosts: files dns myhostname services: files ldap sss netgroup: files ldap sss automount: files ldap sss aliases: files ethers: files gshadow: files networks: files dns protocols: files publickey: files rpc: files 配置pam 修改/etc/pam.d/password-auth 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #%PAM-1.0 # This file is auto-generated. # User changes will be destroyed the next time authselect is run. auth required pam_env.so auth sufficient pam_unix.so try_first_pass nullok auth sufficient pam_ldap.so use_first_pass # 这里 auth required pam_deny.so account required pam_unix.so account [default=bad success=ok user_unknown=ignore] pam_ldap.so # 这里 password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type= password sufficient pam_unix.so try_first_pass use_authtok nullok sha512 shadow password sufficient pam_ldap.so use_authtok # 这里 password required pam_deny.so session optional pam_keyinit.so revoke session required pam_limits.so -session optional pam_systemd.so session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid session required pam_unix.so session optional pam_ldap.so # 这里 修改/etc/pam.d/system-auth 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #%PAM-1.0 # This file is auto-generated. # User changes will be destroyed the next time authselect is run. auth required pam_env.so auth sufficient pam_unix.so try_first_pass nullok auth sufficient pam_ldap.so use_first_pass # 这里 auth required pam_deny.so account required pam_unix.so account [default=bad success=ok user_unknown=ignore] pam_ldap.so # 这里 password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type= password sufficient pam_unix.so try_first_pass use_authtok nullok sha512 shadow password sufficient pam_ldap.so use_authtok # 这里 password required pam_deny.so session optional pam_keyinit.so revoke session required pam_limits.so -session optional pam_systemd.so session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid session required pam_unix.so session optional pam_ldap.so # 这里 修改/etc/ssh/sshd_config 1 2 PasswordAuthentication yes UsePAM yes 重启相关软件 全部配置文件修改完成之后重启相关软件即可 1 systemctl restart sshd nslcd 然后就可以使用在ldap系统中新建的用户通过ssh连接到服务器了。 搞定! 2025.08.02 更新 使用 ldap 管理管理员用户 今天突然发现了一个问题,经过以上一通操作后。将用户添加到 wheel 组后仍然没有拥有管理员权限。 去看了一眼 sudoers 文件,发现 wheel 组确实是在里面的,并且没有被注释掉。 于是查看 super 用户的 uid、gid 等信息,发现了一件有点令人匪夷所思的事情,明明使用 id 命令查看 super 用户是在 wheel 组的,但是 getent 命令却查不到。 多次试验后我还是找到了问题所在:nsswitch.conf 的机制。 可以翻到上面看到我们配置的该文件有一行:group: files ldap sss systemd,也就是说如果在本地文件 (/etc/group) 找到了一个用户组,那就不会再往后面查找了。而 wheel 用户组又是 RockyLinux 系统默认就存在的组,所以 OpenLDAP 远程的 wheel 组会被覆盖掉。 问题找到了,那么解决方案就很清晰了。 解决方案1: 注释掉 /etc/group 中 wheel:x:10: 这一行。 解决方案2: 修改 /etc/nsswitch.conf 中 group 的配置,将 ldap 提到 files 前面,即 group: ldap files sss systemd。 这下才是真的搞定!

2024/12/12
articleCard.readMore

非特权模式容器 ssh 登录问题

一、问题描述 使用 podman 运行一个以 systemd 模式启动的容器,并且没有赋予 privileged 权限。sshd 正常启动,sshd_config 正常设置,但是使用 ssh 远程登录时出现: (非 root 用户也是如此) 二、查看原因 查看 /var/log/secure 文件,发现文件内容为空 使用 journalctl -u sshd 命令查看错误日志如下: 分析错误原因: 权限不足,无法将 uid 写入 /proc/self/loginuid 文件中 sshd 进程无法与 system 总线通信 三、解决 最佳方案:给予容器更多的权限 1 2 3 - AUDIT_WRITE - SYS_RAWIO - AUDIT_CONTROL 方案二:删除这些报错相关的 pam /etc/pam.d/sshd 1 #session required pam_loginuid.so /etc/pam.d/system-auth 和 /etc/pam.d/password-auth 1 #-session optional pam_systemd.so 方案三:以特权模式运行容器 1 --privileged

2024/12/9
articleCard.readMore

在 Linux 开发环境中使用网络代理

默认现在本机已经安装好了代理软件客户端,暴露出的 http 端口为7890,socks5 端口为7891。 为什么要这样默认?因为此域名是接受过国内备案的,不易多说 🤫 Linux Shell使用网络代理 当前终端使用代理 设置代理:export http_proxy=http://127.0.0.1:7890 && export https_proxy=http://127.0.0.1:7890 取消代理:unset http_proxy && unset https_proxy 全局使用代理 如果是root用户,修改/etc/profile。如果是普通用户,修改~/.bashrc。在文件最后添加如下内容 1 2 3 # >>> proxy settings >>> export https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 all_proxy=socks5://127.0.0.1:7891 # >>> proxy settings >>> 然后刷新配置文件:source /etc/profile或者source ~/.bashrc Docker使用网络代理 Docker本身使用代理(如docker pull) 1 mkdir -p /etc/systemd/system/docker.service.d 添加代理 1 vim /etc/systemd/system/docker.service.d/http-proxy.conf 1 2 3 [Service] Environment="HTTP_PROXY=http://127.0.0.1:7890" Environment="HTTPS_PROXY=http://127.0.0.1:7890" 重启docker 1 2 systemctl daemon-reload systemctl restart docker Docker容器使用代理 如果是在Dockerfile中,则直接设置 1 2 ENV http_proxy='http://127.0.0.1:7890' ENV https_proxy='http://127.0.0.1:7890' 如果是在docker run时想添加代理,则使用-e进行添加,如 1 docker run -d -e http_proxy=http://127.0.0.1:7890 -e https_proxy=http://127.0.0.1:7890 image Git使用网络代理 如果已经用上述 “Linux Shell使用网络代理” 配置过了,则 git 会默认使用上面配置的。如果只想单独给 git 配置代理,参考下面的方法。 方法一:编辑文件 编辑文件~/.gitconfig 1 vim ~/.gitconfig 1 2 3 4 [https] proxy = http://127.0.0.1:7890 [http] proxy = http://127.0.0.1:7890 方法二:命令行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 设置Git全局代理 git config --global http.proxy http://127.0.0.1:7890 git config --global https.proxy http://127.0.0.1:7890 # 或者 git config --global http.http://github.com.proxy socks5://127.0.0.1:7891 git config --global https.https://github.com.proxy socks5://127.0.0.1:7891 # 仅针对Github网站设置代理 git config --global http.http://github.com.proxy http://127.0.0.1:7890 git config --global https.https://github.com.proxy http://127.0.0.1:7890 # 取消代理 git config --global --unset http.proxy git config --global --unset https.proxy

2024/10/17
articleCard.readMore

白嫖 Aseprite 像素绘图软件

白嫖的原理是通过构建他在 Github 上开源出来的源代码,绝对官方正版。 前言 听说 Aseprite 是一款非常不错的像素绘图软件,最近闲的没事,想试一下。 但是一看,官网售价 19.99 USD,Steam 售价 70 RMB,实在是囊中羞涩,心有余而力不足。一看 Github,原来这个玩意儿是开源的,由 C++ 语言编写。 既然他是开源的,那我们就可以通过源代码自己编译出此软件。 当然,安装过程也可以查看官方给出的文档,看官方文档最大的好处就是不用担心时效性而且绝对准确 Windows 编译安装 环境准备 安装 Visual Studio 2022 并且记住安装位置 下载链接:https://visualstudio.microsoft.com/zh-hans/downloads/ 安装 Ninja 并配置进 Path 环境变量 下载链接:https://github.com/ninja-build/ninja/releases 安装 Skia 下载链接:Releases · aseprite/skia · GitHub 将解压出来的文件放到 C:\deps\skia 目录下 安装 Cmake 并配置进 Path 环境变量 下载链接:https://cmake.org/download/ 安装 Git 下载链接:Git - Downloading Package (git-scm.com) 一台可以访问 Github 的电脑 编译 首先在一个没有中文的路径下下载源码 1 git clone --recursive https://github.com/aseprite/aseprite.git 结果如下表示下载成功: 执行命令 1 call "D:\SoftWare\VisualStudio2022\IDE\Common7\Tools\VsDevCmd.bat" -arch=x64 # 注意:你的路径未必和我的一样 结果如下表示成功: 进入刚刚下载的 aseprite 文件夹 在 aseprite 文件夹下创建 build 文件夹 进入 build 文件夹 执行命令 1 cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DLAF_BACKEND=skia -DSKIA_DIR=C:\deps\skia -DSKIA_LIBRARY_DIR=C:\deps\skia\out\Release-x64 -DSKIA_LIBRARY=C:\deps\skia\out\Release-x64\skia.lib -G Ninja .. 结果类似于下面表示成功: 执行命令 1 ninja aseprite 结果类似于下面表示成功: 使用 上述命令全部执行成功后会在本目录下生成一个 bin 文件夹,这就是这个我们构建出来的软件目录。打开后会发现里面有一个 aseprite.exe 文件,就是他。 构建成功后,除此 bin 文件夹外,其他所有文件/文件夹均可删除。 使用愉快😁😁 2025.08.02 更新 使用 Github Actions 编译 最近换了电脑,需要重新安装这个软件。正好前段时间刚学会使用 Github Actions,想到使用这个会不会更方便点,一搜,还真有现成的 Actions YAML。那正好,直接拿来用。这里放个大佬写的目前能用的,就不重复造轮子了。 https://github.com/a1393323447/aseprite-builder

2022/11/21
articleCard.readMore

MongoDB 增删改查

数据库操作 查看所有数据库:show dbs; 切换数据库:use xxx; 没有xxx时会自动创建 新建集合(对应MySQL表):db.createCollection(‘xxx’); 常用文档操作命令(增删改查) 增 1 2 3 4 5 6 db.collection.insertOne( <document>, { writeConcern: <document> } ) 1 2 3 4 5 6 db.collection.insertMany( <document>, { writeConcern: <document> } ) 删 1 2 3 4 5 6 db.emp.deleteMany({}) //删除所有文档 db.emp.deleteMany({name: 'pillage'}) //删除所有name=pillage的文档 db.emp.deleteMany({age: {$gt: 20}}) //删除所有age>20的文档 db.emp.deleteOne({name: 'pillage'}) //删除name=pillage的第一个文档 db.emp.findOneAndDelete({xxx}) //返回这个文档然后删除 改 更新命令 1 2 3 4 5 6 7 8 db.collection.updateOne(query, update, options) db.collection.updateMany(query, update, options) query: 查询条件 update: 更新动作及新的内容 options: 可选,配置 upsert: boolean类型,默认false。设置为true后如果查询条件为空则插入 writeConcern: 一个写操作落到多少节点才算成功 更新操作符(update内容) 操作符格式描述 $set{$set: {field: value}}指定一个键(字段)更新值,如果键不存在则创建 $unset{$unset: {field: 1}}删除一个键 $inc{$inc: {field: number}}对数值类型值进行加操作,number可以为负 $rename{$rename: {oldFiled: newField}}修改键的名称 $push{$push: {field: value}}将值添加到数组中,数组不存在则初始化 $pull{$pull: {field: value}}从数组中删除指定元素 $addToSet{$addToSet: {field: value}}添加元素到set集合(无序不重复) $pop`{$pop: {field: 1-1}}` 查 1 2 3 4 db.collection.find(query, projection) query: 可选。查询条件 projection: 可选。选择要查询哪些键的值

2022/11/19
articleCard.readMore

Python数据分析工具包-Numpy

说明:通常所说的”数组”,”Numpy数组”,”ndarray”基本上都是指同一个东西,即ndarray对象。 Numpy常用函数以及用法 (1)创建ndarray数组 使用array函数 说明:他可以接收一切序列型的对象,然后产生一个新的含有传入数据的Numpy数组。除非用dtype自定义类型,否则他会根据你传入的数据类型自动帮你匹配合适的类型。 此类型规则为:如果有字符串,则优先字符串,如果没有字符串而有复数类型,则系统默认帮你判定为复数类型。然后依次为浮点数和整数。即优先级为”字符串>复数>浮点数>整数”。 代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import numpy as np array = np.array(['Hello', 1+2j, 5.20, 5]) for i in array: print(i, ':', type(i)) print('===============================') array = np.array([1+2j, 5.20, 5]) for i in array: print(i, ':', type(i)) print('===============================') array = np.array([5.20, 5]) for i in array: print(i, ':', type(i)) print('===============================') array = np.array([5]) for i in array: print(i, ':', type(i)) 输出结果为: 1 2 3 4 5 6 7 8 9 10 11 12 13 Hello : <class 'numpy.str_'> (1+2j) : <class 'numpy.str_'> 5.2 : <class 'numpy.str_'> 5 : <class 'numpy.str_'> =============================== (1+2j) : <class 'numpy.complex128'> (5.2+0j) : <class 'numpy.complex128'> (5+0j) : <class 'numpy.complex128'> =============================== 5.2 : <class 'numpy.float64'> 5.0 : <class 'numpy.float64'> =============================== 5 : <class 'numpy.int32'> 使用ones和zeros函数 除此之外,还可以使用ones函数和zeros函数来创建一个全是1或者全是0的数组,我们只需要传递给他们一个需要创建的数组的形状即可。 代码示例如下: 1 2 3 4 5 6 import numpy as np array = np.ones((3, 3)) print(array) print('=================') array = np.zeros((3, 3)) print(array) 输出结果: 1 2 3 4 5 6 7 [[1. 1. 1.] [1. 1. 1.] [1. 1. 1.]] ================= [[0. 0. 0.] [0. 0. 0.] [0. 0. 0.]] empty函数 函数说明:empty函数可以创建一个没有任何具体指的函数。我们在使用empty的时候只需要传入一个表示形状的元祖就可以了。值得注意的是,empty创建出来的数组初始值是不确定的随机值。 例如: 1 2 3 import numpy as np array = np.empty((3, 3)) print(array) 输出结果如下(完全没有规律): 1 2 3 [[7.01573217e-322 0.00000000e+000 0.00000000e+000] [0.00000000e+000 0.00000000e+000 1.14623230e-321] [1.24610926e-306 1.61271680e-312 0.00000000e+000] arange函数 函数说明:arange函数用法与range函数类似。不同的是arange生成的是ndarray对象,即numpy数组。 例如运行如下代码: 1 2 3 4 import numpy as np array1 = np.arange(10) print(array1, type(array1)) 输出结果如下: 1 [0 1 2 3 4 5 6 7 8 9] <class 'numpy.ndarray'> linspace函数 函数说明:此函数与arange函数有些相似。调用方法为 np.linspace(start= ,stop= ,num= ,endpoint= ,retstep= ,dtype= ,axis= ) 参数说明: start是数组起始数字 stop是数组结束数字 num(可选)控制结果中共有多少个元素 endpoint(可选)决定了中止值(stop)是否包含在内。若值为True,则包含stop,否则不包含。如果不写默认是True retstep(可选)默认是False,如果指定为True,则结果会返回步长以及序列数组,从而产生一个元组作为输出 dtype(可选)为生成的数组的类型,可以自定义数据类型,不写则按那个规则 字符串>复数>浮点数>整数 axis(可选)默认是0 。很多函数都会有这个参数,这是个轴的意思。具体定义如下图: 运行如下代码: 1 2 3 4 5 6 7 import numpy as np array1 = np.linspace(1,10,10) print(array1) print('='*30) array2 = np.linspace(start = 1, stop = 10, num = 10, endpoint=True, dtype=int, retstep=True) print(array2) 输出结果如下: 1 2 3 [ 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.] ============================== (array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 1.0) logspace函数 函数说明:返回在对数刻度上均匀间隔的数字。此函数尚未理解 full函数 函数说明:返回给定形状,给定值的数组。 运行代码: 1 2 3 4 import numpy as np array = np.full(shape=(3, 3), fill_value=10, dtype=int) print(array) 输出结果如下: 1 2 3 [[10 10 10] [10 10 10] [10 10 10]] repeat函数 函数说明:建立每个元素重复N次的数组。 运行代码: 1 2 3 4 import numpy as np array = np.repeat(a=[1, 1, 2, 3], repeats=3, axis=0) # a的每个元素重复三次 print(array) 运行结果如下: 1 [1 1 1 1 1 1 2 2 2 3 3 3] 另外还有一些数组创建函数如下表所示: 函数说明 array将输入数据(列表,元组,数组或者其他的序列类型)转换为ndarray。要么推断出dtype,要么在创建时即指定dtype。默认直接复制输入数据 asarray将输入转换为ndarray,如果输入本身就是一个ndarray就不进行复制 arange类似于内置的range,但返回的是一个ndarray而不是列表 ones、ones_likeones是根据指定的dtype和形状创建一个全1数组,ones_like是根据给定的数组的形状创建一个全1数组 zeros、zeros_like与上类似,只不过是全0数组 empty、empty_like与上类似,只不过数据是随机的 eye、identity创建一个N * N的单位矩阵,对角线元素为1,其余为0 full、full_like创建一个给定形状,给定值的数组 (2)操作数组 1. 数组属性的获取 调用方法作用 .ndim返回数组的维数 .shape返回数组的形状 .size返回数组元素个数 .dtype返回数组元素类型 .itemsize返回数组元素字节大小 2. 数组属性的操作 调用方法作用 .reshape改变数组形状 .all数组元素都是0返回True,否则返回False .any数组元素有非0值返回True,否则返回False .copy复制数组副本(并不是引用) .astype改变数组元素数据类型 3. 数组的对接与分割 vstack()函数 函数说明:vstack()函数可以实现数组的垂直对接。 运行代码: 1 2 3 4 5 6 7 8 9 10 import numpy as np array1 = np.zeros((2, 3), dtype=int) array2 = np.ones((4, 3), dtype=int) print(array1) print('=' * 20) print(array2) print('=' * 20) print(np.vstack((array1, array2))) 运行结果如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 [[0 0 0] [0 0 0]] ==================== [[1 1 1] [1 1 1] [1 1 1] [1 1 1]] ==================== [[0 0 0] [0 0 0] [1 1 1] [1 1 1] [1 1 1] [1 1 1]] hstack() 函数说明:hstack()函数可以实现数组元素的水平对接。 运行代码: 1 2 3 4 5 6 7 8 9 10 import numpy as np array1 = np.zeros((2, 6), dtype=int) array2 = np.ones((2, 1), dtype=int) print(array1) print('=' * 20) print(array2) print('=' * 20) print(np.hstack((array1, array2))) 运行结果如下: 1 2 3 4 5 6 7 8 [[0 0 0 0 0 0] [0 0 0 0 0 0]] ==================== [[1] [1]] ==================== [[0 0 0 0 0 0 1] [0 0 0 0 0 0 1]] vsplit() 函数说明:vsplit(array, N)函数可以实现数组的垂直分割(就是分割的垂直,以水平方向分割垂直),array为需要分割的数组,N为分割数。 代码示例如下: 1 2 3 4 5 6 import numpy as np array1 = np.array(([1, 2, 3], [4, 5, 6], [7, 8, 9]), dtype=int) print(array1) print('=' * 30) print(np.vsplit(array1, 3)) 运行结果如下: 1 2 3 4 5 [[1 2 3] [4 5 6] [7 8 9]] ============================== [array([[1, 2, 3]]), array([[4, 5, 6]]), array([[7, 8, 9]])] hsplit() 函数说明:hsplit(array, N)函数可以实现数组元素的水平分割(相当于以垂直方向分割水平,实现水平分割),array为要分割的数组,N为分割的份数。 代码示例如下: 1 2 3 4 5 6 import numpy as np array1 = np.array(([1, 2, 3], [4, 5, 6], [7, 8, 9]), dtype=int) print(array1) print('=' * 30) print(np.hsplit(array1, 3)) 运行结果如下: 1 2 3 4 5 6 7 8 9 10 11 [[1 2 3] [4 5 6] [7 8 9]] ============================== [array([[1], [4], [7]]), array([[2], [5], [8]]), array([[3], [6], [9]])] (3)多维数组的索引与切片(主要区别与一维数组不同的用法) 1. 索引省略用法 说明:当能确定维度时,索引方法和普通数组一样,如array[1], array[1, 2], array[1, 2, 3]。当确定不了维度时,可以通过下标右边…省略号或直接省略下标数,来读取数组。 示例: 1 2 3 4 5 6 7 8 9 10 11 12 import numpy as np array = np.arange(1, 13).reshape((2, 2, 3)) print(array) print('=' * 30) print(array[1]) print('=' * 30) print(array[1, ]) print('=' * 30) print(array[1, ...]) print('=' * 30) print(array[1, ..., 2]) # 此时不能用 array[1, 2],会报错为: IndexErrorindex 2 is out of bounds for axis 1 with size 2 运行结果如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [[[ 1 2 3] [ 4 5 6]] [[ 7 8 9] [10 11 12]]] ============================== [[ 7 8 9] [10 11 12]] ============================== [[ 7 8 9] [10 11 12]] ============================== [[ 7 8 9] [10 11 12]] ============================== [ 9 12] 2. 二维数组切片 说明:默认的切片方式,如[0:2:1]获得第1行到第2行元素,这种切的是行,操作方式与一维数组相同,如果要切片二维数组的行和列,则 操作方式为:数组名[行切片, 列切片] 格式为:array[start:stop:step, start:stop:step] 如:array[:, 2]就是截取所有行,第三列的子数组。array[:, :]就是获取所有行所有列。 示例: 1 2 3 4 5 6 7 import numpy as np array = np.arange(1, 13).reshape((3, 4)) print(array) print('=' * 30) # 获取从第1行到第2行步长为1的所有行,再从这些行中获得第2列到第4列步长为2的所有元素 print(array[0:2:1,1:4:2]) 运行结果如下: 1 2 3 4 5 6 [[ 1 2 3 4] [ 5 6 7 8] [ 9 10 11 12]] ============================== [[2 4] [6 8]] 3. 三维数组切片 说明:操作方式与二维数组相类型,无非就是多了一维而已。 操作方式为:数组名[页切片, 行切片, 列切片] 格式为:array[start:stop:step, start:stop:step, start:stop:step] 示例: 1 2 3 4 5 6 7 import numpy as np array = np.arange(1, 13).reshape((2, 2, 3)) print(array) print('=' * 30) # 获取第2页的第2行的从第2列到第3列步长为1的所有元素 print(array[1, 1, 1:3:1]) 运行结果如下: 1 2 3 4 5 6 7 [[[ 1 2 3] [ 4 5 6]] [[ 7 8 9] [10 11 12]]] ============================== [11 12] 4. 花式索引 整数数组索引 (1)一维数组的整数数组索引 说明:利用整数数组的所有元素的下标值进行索引,又叫数组索引。 示例: 1 2 3 4 5 6 7 import numpy as np names = np.array(['zhaoer', 'zhangsan', 'lisi', 'wangwu', 'maliu', 'tangqi']) index = np.array([1, 2, 4, 5]) print(*names) print('=' * 60) print(*names[index]) 运行结果如下: 1 2 3 zhaoer zhangsan lisi wangwu maliu tangqi ============================================================ zhangsan lisi maliu tangqi (2)二维数组的数组索引 说明:对二维数组输出时如果只索引一个一维数组,则只操作行,如果索引为两个一维数组,则第一个一维数组指定结果的x坐标,第二个一维数组指定结果的y坐标。 示例如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import numpy as np # 以下是利用一个整数数组进行索引 names = np.array([['tom', 1], ['jack', 2], ['lucy', 3], ['blackCat', 4], ['win', 5], ['father', 6]]) index = np.array([1, 2, 4, 5]) print(*names) print('=' * 60) print(*names[index]) print('=' * 60) # 以下用两个一维整数数组进行索引 array = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) index1 = np.array([[0, 1, 2]]) # 注意:这是其实是一个二维数组的第一维 index2 = np.array([0, 1, 2]) print(array) print('=' * 60) print(array[index1, index2]) 运行结果如下: 1 2 3 4 5 6 7 8 9 ['tom' '1'] ['jack' '2'] ['lucy' '3'] ['blackCat' '4'] ['win' '5'] ['father' '6'] ============================================================ ['jack' '2'] ['lucy' '3'] ['win' '5'] ['father' '6'] ============================================================ [[1 2 3] [4 5 6] [7 8 9]] ============================================================ [[1 5 9]] bool数组索引 说明:准备一个bool数组,用该数组索引其他数组时,只会输出True位置对应的元素。一维或者多维数组都适用。 示例: 1 2 3 4 5 6 7 import numpy as np array = np.arange(1, 10).reshape((3, 3)) index = np.array([[True, False, True], [True, False, False], [False, False, True]]) print(array) print('=' * 20) print(array[index]) 运行结果如下: 1 2 3 4 5 [[1 2 3] [4 5 6] [7 8 9]] ==================== [1 3 4 9] (4)基本数学计算 1. 普通计算 说明:计算的对象可以是两个数组,也可以是一个数组一个数字,所有运算对于复数也同样适用。如下表: 运算方式符号函数 数组加法+add 数组减法-subtract 数组乘法*multiply 数组除法/divide 数组求余%mod 数组求幂**power 数组整除//floor_divide 示例: 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 import numpy as np array1 = np.arange(1, 10).reshape((3, 3)) array2 = np.array([10, 10, 10]) print('第一个数组:') print(array1) print('第二个数组:') print(array2) print('=' * 30) print('数组加法:') print(np.add(array1, array2)) print('=' * 30) print('数组减法:') print(np.subtract(array1, array2)) print('=' * 30) print('数组乘法:') print(np.multiply(array1, 2)) print('=' * 30) print('数组除法:') print(np.divide(array1, 10)) print('=' * 30) print('数组求余:') print(np.mod(array1, array2)) print('=' * 30) print('数组求幂:') print(np.power(array1, 3)) print('=' * 30) print('数组整除:') print(np.floor_divide(array1, 2)) 运行结果如下: 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 第一个数组: [[1 2 3] [4 5 6] [7 8 9]] 第二个数组: [10 10 10] ============================== 数组加法: [[11 12 13] [14 15 16] [17 18 19]] ============================== 数组减法: [[-9 -8 -7] [-6 -5 -4] [-3 -2 -1]] ============================== 数组乘法: [[ 2 4 6] [ 8 10 12] [14 16 18]] ============================== 数组除法: [[0.1 0.2 0.3] [0.4 0.5 0.6] [0.7 0.8 0.9]] ============================== 数组求余: [[1 2 3] [4 5 6] [7 8 9]] ============================== 数组求幂: [[ 1 8 27] [ 64 125 216] [343 512 729]] ============================== 数组整除: [[0 1 1] [2 2 3] [3 4 4]] 2. 比较运算 说明:可以数组与数组比较,也可以数组和单个数字标量进行比较,结果是与第一个数组形状相同的数组,满足条件的位置为True,不满足的为False,如下表: 运算方式符号 等于比较== 不等于比较!= 大于比较> 小于比较< 大于等于>= 小于等于<= 示例(此示例仅展示等于比较运算,其他运算方式相似): 1 2 3 4 5 6 7 import numpy as np array1 = np.arange(1, 10).reshape((3, 3)) array2 = np.array([1, 1, 1]) print(array1 == array2) print('=' * 30) print(array1 == 1) 结果: 1 2 3 4 5 6 7 [[ True False False] [False False False] [False False False]] ============================== [[ True False False] [False False False] [False False False]] 3. 数组位运算 说明:运算对象可以是数组与数组,也可以是数组和单个数字标量,结果是与第一个数组形状相同的数组,满足条件的位置为True,不满足的为False,如下表: 运算方式符号 与& 或| 非~ 左移<< 右移>> 示例(次示例仅展示与运算,其他运算方式类似): 1 2 3 4 5 6 7 import numpy as np array1 = np.arange(1, 10).reshape((3, 3)) array2 = np.array([[True, False, True], [True, False, True], [True, False, True]]) print(array1 & array2) print('=' * 30) print(array1 & 10) 运算结果如下: 1 2 3 4 5 6 7 [[1 0 1] [0 0 0] [1 0 1]] ============================== [[0 2 2] [0 0 2] [2 8 8]] (5)数组通用函数 说明:这里的函数和数学概念中的函数是一一对应的 1. 三角函数 反三角函数 说明: numpy中自带圆周率,用numpy.pi就可以使用 传递给函数的数是以弧度计算,对于角度可以用array * numpy.pi / 180 转换为弧度 调用反三角函数时可以用numpy.degrees()函数将结果中的弧度转换为角度 函数如下表: 功能函数 正弦sin 余弦cos 正切tan 反正弦arcsin 反余弦arccos 反正切arctan 以正弦为示例,其他类似: 1 2 3 4 5 6 7 8 9 import numpy as np array = np.array([0, 30, 45, 60, 90]) sin = np.sin(array * np.pi / 180) print('正弦计算结果为:') print(sin) print('=' * 60) print('对结果执行反正弦并转换为角度:') print(np.degrees(np.arcsin(sin))) 运行结果如下: 1 2 3 4 5 正弦计算结果为: [0. 0.5 0.70710678 0.8660254 1. ] ============================================================ 对结果执行反正弦并转换为角度: [ 0. 30. 45. 60. 90.] 2. 舍入函数 函数:numpy.around(array, decimals) 函数说明: 其中array为数组,decimals为要保留的小数位数,默认值为0,如果为负数,则将整数四舍五入保留到小数点左侧位置 结果是科学计数法的形式,如1.23e + 2就是1.23*10^2 示例如下: 1 2 3 4 5 6 7 8 9 import numpy as np array = np.array([12, 12.123456, 12345.12]) print('将decimals默认:') print(np.around(array)) print('保留到小数点后一位:') print(np.around(array, 1)) print('保留到小数点左一位:') print(np.around(array, -1)) 运行结果: 1 2 3 4 5 6 将decimals默认: [1.2000e+01 1.2000e+01 1.2345e+04] 保留到小数点后一位: [1.20000e+01 1.21000e+01 1.23451e+04] 保留到小数点左一位: [1.000e+01 1.000e+01 1.235e+04] 3. 取整函数 说明: numpy.ceil(array)是返回大于或者等于指定表达式的最小整数,即向上取整。 numpy.floor(array)是返回小于或等于指定表达式的最大整数,即向下取整。 参数中array是数组名。 4. 以e为底的指数函数和对数函数 说明: numpy.exp(array)是以e为底,以array为指数的结果。 numpy.log(array)是以e为底,以array为真数的结果。 numpy.log2(array)是以2为底,以array为真数的结果。 numpy.log10(array)是以10为底,以array为真数的结果。 其中array既可以是数组,也可以是单个数字标量。 5. 随机函数 说明:常用随机函数如下表 函数参数解释 numpy.random.rand(shape)shape用于指定生成数组的形状函数产生[0, )范围内的浮点随机数,生成一个数组 numpy.random.randn(shape)shape用于指定生成数组的形状函数产生标准正态分布随机数,生成一个数组 numpy.random.randint(low=None, high=None, size=None, dtype=None)随机数范围[low, high),size是随机数的个数,dtype是随机数类型生成的type类型的size个范围内的随机数,返回一个数组 numpy.random.normal(loc=None, scale=None, size=None)产生正态分布随机数 numpy.random.uniform(low=None, high=None, size=None)产生均匀分布随机数 numpy.random.poisson(lam=None, size=None)产生泊松分布随机数 numpy.random.permutation(array)array可以是数组,也可以是单独数字标量当array为数组时,打乱数组中的所有数,当array为数字标量时,返回[0, array)范围内的数的乱序组成的数组 numpy.random.shuffle(array)array为数组与上述区别是直接对array进行打乱,无返回值

2022/11/1
articleCard.readMore

解决 CLion 中文乱码问题

前言 CLion 在输出中文时会发生乱码,找了网上的一些教程,都是说更改编码为 GBK。这样虽然能解决问题,但是很麻烦,因为每次都要更改 用下述方法,只需设置一次,不用像其他教程重复设置 方法 快捷键Ctrl+Shift+Alt+/,弹出如下界面: 然后,取消第一项的勾选,也就是run.processes.with.pty 这样就成功解决了,完美! 测试一下: 1 2 3 4 5 6 #include <stdio.h> int main() { printf("你好世界!\n"); return 0; }

2022/10/29
articleCard.readMore

搭建 RLCraft 服务器

前言 RLCraft 是一款好评如潮的 Minecraft 整合包。在原生 Minecraft 的基础上做了多样的扩展,可玩性非常高。关于此扩展包的具体介绍可在如下网站查看:https://www.curseforge.com/minecraft/modpacks/rlcraft 将它运行在服务器上,就可以和好朋友一起玩了。 需要注意的是 RLCraft 只是一个代表性的例子。此方法适用于大部分 Forge Server 的搭建,不过具体的细节还是要看官网的介绍。 前期准备 JDK8 一定要是 JDK8,新版的 JDK 17 无法运行我们需要的旧版本的 Forge1.12.2 服务器。下载地址:https://www.oracle.com/sg/java/technologies/javase/javase8-archive-downloads.html Forge1.12.2 - 14.23.5.2860 下载地址:https://maven.minecraftforge.net/net/minecraftforge/forge/1.12.2-14.23.5.2860/forge-1.12.2-14.23.5.2860-installer.jar RLCraft2.9.1 下载地址:https://mediafilez.forgecdn.net/files/3655/676/RLCraft+Server+Pack+1.12.2+-+Release+v2.9.1c.zip Forge 和 RLCraft 要放在同一个文件夹内 完成效果图(举例): 安装服务器 将 RLCraft 解压缩 1 unzip RLCraft+Server+Pack+1.12.2+-+Release+v2.9.1c.zip 修改 server.properties 配置文件 必须要设置如下几个部分,其他部分在知情的情况下可自行修改: 1 2 3 4 allow-flight=true difficulty=3 max-tick-time=-1 enable-command-block=true 安装 Forge 服务器 1 /usr/local/jdk/jdk1.8.0_202/bin/java -jar forge-1.12.2-14.23.5.2860-installer.jar --installServer 结尾出现如下字样即为安装成功 启动 Forge 服务器 1 /usr/local/jdk/jdk1.8.0_202/bin/java -jar forge-1.12.2-14.23.5.2860.jar nogui 不出意外的话程序会报错并退出 原因:我们没有同意什么用户协议什么的 同意用户协议 修改 eula.txt 文件,将 eula 设置为 true 启动服务器 1 /usr/local/jdk/jdk1.8.0_202/bin/java -jar forge-1.12.2-14.23.5.2860.jar nogui 这步需要等待较长时间,而且中间会各种爆红、卡顿什么的,只要不闪退就不用管他,耐心等待就好。出现如下字样即为启动成功 至此,服务端就搭建完成了! 放开端口 如果没做修改的话,Forge 服务器默认运行在 25565 端口,在防火墙中开启此端口即可 开始游戏 打开 Minecraft 启动器,点击多人游戏,输入名称和公网IP地址,连接服务器,一气呵成 祝您游戏愉快!

2022/10/17
articleCard.readMore

SpringBoot读取配置文件

前言 配置文件一般存放一些系统变量或用户变量,例如数据库数据源的配置。它可以实现在不改变程序源代码的情况下修改程序的变量的值。通过配置文件可以使程序开发变得更加灵活。接下来我将介绍几种常见的在 SpringBoot 中获取配置文件的方式。 我的示例配置文件(userinfo.yml)位置如下: 1 2 3 4 5 6 7 8 9 10 11 12 my-profile: name: grape age: 6 users: - name: 张三 age: 20 - name: 李四 age: 21 - name: 王五 age: 22 通过 @value 读取简单信息 通过在变量前加上注解 @value("${xxx}") 可以将配置信息注入到变量中 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.pushihao.controller; import com.pushihao.bean.YmlConfigFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @PropertySource(value = {"classpath:userinfo.yml"},encoding="UTF-8",factory = YmlConfigFactory.class) public class UserinfoController { @Value("${my-profile.name}") private String name; @Value("${my-profile.age}") private String age; @RequestMapping("/u1") public String u1() { return "name:" + name + "|age:" + age; } } 注意: @PropertySource 注解可以指定要读取的配置文件的位置。不写此注解默认读取SpringBoot默认配置文件application.properties/application.yml/application.yaml。 因为 @PropertySource 注解默认是读取 properties 文件,所以如果是读取 properties 文件,注解可以写成 @PropertySource(value = {“classpath:userinfo.properties”},encoding=”UTF-8”)。 本例中读取的是 yml 文件,需要重写 DefaultPropertySourceFactory,让其加载 yml 文件。然后在 PropertySource 注解中加入factory = YmlConfigFactory.class。 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 package com.pushihao.bean; import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.io.support.DefaultPropertySourceFactory; import org.springframework.core.io.support.EncodedResource; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.Properties; @Component public class YmlConfigFactory extends DefaultPropertySourceFactory { @Override public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException, IOException { String sourceName = name != null ? name : resource.getResource().getFilename(); if (!resource.getResource().exists()) { assert sourceName != null; return new PropertiesPropertySource(sourceName, new Properties()); } else { assert sourceName != null; if (sourceName.endsWith(".yml") || sourceName.endsWith(".yaml")) { Properties propertiesFromYaml = loadYml(resource); return new PropertiesPropertySource(sourceName, propertiesFromYaml); } else { return super.createPropertySource(name, resource); } } } private Properties loadYml(EncodedResource resource) throws IOException { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResources(resource.getResource()); factory.afterPropertiesSet(); return factory.getObject(); } } 通过 Environment 读取配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.pushihao.controller; import com.pushihao.bean.YmlConfigFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @PropertySource(value = {"classpath:userinfo.yml"},encoding="UTF-8",factory = YmlConfigFactory.class) public class Userinfo2Controller { @Autowired private Environment environment; @RequestMapping("/u2") public String u2() { String name = environment.getProperty("my-profile.name"); String age = environment.getProperty("my-profile.age"); return "name:" + name + "|age:" + age; } } 通过 @ConfigurationProperties 读取配置文件 @ConfigurationProperties 注解可以将配置文件映射成一个类,而配置文件中的每个键就对应类中的每个属性 映射类代码如下: 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 package com.pushihao.bean; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; @Component @Data @PropertySource(value = {"classpath:userinfo.yml"},encoding="UTF-8",factory = YmlConfigFactory.class) //注意:prefix指定的是键的前缀,而不是文件名的前缀 @ConfigurationProperties(prefix = "") public class UserinfoProperties { //注意:此处的变量名称要和配置文件中的键的名称相对应,采用驼峰命名法 private User myProfile = new User(); private List<User> users = new ArrayList<>(); @Data public static class User { String name; Integer age; } } 使用: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.pushihao.controller; import com.pushihao.bean.UserinfoProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class Userinfo3Controller { @Autowired private UserinfoProperties userinfoProperties; @RequestMapping("/u3") public String u3() { return "name:" + userinfoProperties.getMyProfile().getName() + "|age:" + userinfoProperties.getMyProfile().getAge() + "|users:" + userinfoProperties.getUsers().toString(); } } 其他 配置文件优先级 位置决定优先级: Config data files are considered in the following order: Application properties packaged inside your jar ( and YAML variants).application.properties Profile-specific application properties packaged inside your jar ( and YAML variants).application-{profile}.properties Application properties outside of your packaged jar ( and YAML variants).application.properties Profile-specific application properties outside of your packaged jar ( and YAML variants).application-{profile}.properties 文件后缀名决定优先级: yml 读取优先级 > properties 读取优先级

2022/9/30
articleCard.readMore

Centos 配置 LNMP 环境

手把手教你配置 LNMP 环境,其中大部分学习过程中需要用到的模块也在安装过程中一并安装了,或许是初学者的福音😁😁 版本说明 请注意不同版本号的软件的安装方式和软件兼容性可能有差别!!! 软件名版本号官网下载链接 LinuxCentos8.0https://www.centos.org// Nginx1.23.1https://www.nginx.com/https://nginx.org/download/nginx-1.23.1.tar.gz MySQL8.0.30https://www.mysql.com/https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.30-1.el8.x86_64.rpm-bundle.tar PHP8.0.23https://www.php.net/https://www.php.net/distributions/php-8.0.23.tar.gz 编译安装 Nginx 下载 1 2 3 4 5 mkdir /usr/local/nginx cd /usr/local/nginx wget https://nginx.org/download/nginx-1.23.1.tar.gz 安装依赖 1 yum -y install gcc-c++ pcre pcre-devel zlib zlib-devel openssl openssl-devel 编译 1 2 3 4 5 tar -zxvf nginx-1.23.1.tar.gz cd nginx-1.23.1/ ./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-http_sub_module --with-http_gzip_static_module --with-pcre 安装 1 make && make install 添加环境变量(可选) 1 vim /etc/profile 将光标移动到最后一行,添加如下内容(按 i 进入输入模式,输入完成之后按 esc 进入命令模式,输入 :wq 回车即可保存并退出):export PATH=$PATH:/usr/local/nginx/sbin 1 source /etc/profile 检验 1 nginx -V 至此,Nginx 安装完毕,离成功进了一步! 安装 MySQL 首先检查系统中是否已经有了 MySQL 检查: 1 2 rpm -qa | grep mysql rpm -qa | grep mariadb 运行后如果没有反应那就是没事,如果有的话,就运行 1 rpm -e *** (***为对应的软件名称) 安装依赖包 1 yum -y install ncurses-devel libaio-devel libaio 下载MySQL 1 2 3 4 5 mkdir /usr/local/mysql cd /usr/local/mysql wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.30-1.el8.x86_64.rpm-bundle.tar 解压MySQL 1 tar -xvf mysql-8.0.30-1.el8.x86_64.rpm-bundle.tar 安装MySQL 依次每条执行以下命令(顺序很重要): 1 2 3 4 5 6 7 8 9 10 11 rpm -ivh mysql-community-common-8.0.30-1.el8.x86_64.rpm rpm -ivh mysql-community-client-plugins-8.0.30-1.el8.x86_64.rpm rpm -ivh mysql-community-libs-8.0.30-1.el8.x86_64.rpm rpm -ivh mysql-community-client-8.0.30-1.el8.x86_64.rpm rpm -ivh mysql-community-icu-data-files-8.0.30-1.el8.x86_64.rpm rpm -ivh mysql-community-server-8.0.30-1.el8.x86_64.rpm 至此,MySQL 就安装完毕了,我们离成功又近了一步。 启动 MySQL 1 systemctl start mysqld 重置 MySQL 的 root 密码 MySQL 会有一个初识密码,通过命令获得临时密码: 1 grep "password" /var/log/mysqld.log 登录(密码是不会显示的,放心输入): 1 mysql -u root -p 输入如下命令进行改密码: 1 2 3 alter user user() identified by "******"; (******为密码,需包含大小写字母、数字以及符号,不然报错密码过于简单) flush privileges; 设置数据库可以以 root 身份远程访问 以 root 身份登录 MySQL,然后执行如下命令: 1 2 3 4 5 6 7 use mysql; create user 'root'@'%' identified by '******'; //******为自己设置的密码 grant all privileges on *.* to 'root'@'%' with grant option; flush privileges; 马上就要成功了,只差最后一个PHP!!! 编译安装 PHP 下载 1 2 3 4 5 mkdir /usr/local/php cd /usr/local/php wget https://www.php.net/distributions/php-8.0.23.tar.gz 安装依赖 1 yum install -y libtool automake sqlite* gcc gcc-c++ make zlib zlib-devel pcre pcre-devel libjpeg libjpeg-devel libpng libpng-devel freetype freetype-devel libxml2 libxml2-devel glibc glibc-devel glib2 glib2-devel bzip2 bzip2-devel ncurses ncurses-devel curl curl-devel e2fsprogs e2fsprogs-devel krb5* openldap* nss_ldap 有个依赖需要单独安装 1 2 3 4 5 6 7 8 9 wget https://github.com/kkos/oniguruma/archive/v6.9.4.tar.gz -O oniguruma-6.9.4.tar.gz tar -zxvf oniguruma-6.9.4.tar.gz cd oniguruma-6.9.4 ./autogen.sh && ./configure --prefix=/usr make && make install 编译 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 cd /usr/local/php tar -zxvf php-8.0.23.tar.gz cd php-8.0.23/ ./configure \ --prefix=/usr/local/php \ --with-config-file-path=/usr/local/php \ --enable-mbstring \ --with-openssl \ --enable-ftp \ --with-gd \ --with-jpeg-dir=/usr \ --with-png-dir=/usr \ --with-mysql=mysqlnd \ --with-mysqli=mysqlnd \ --with-pdo-mysql=mysqlnd \ --with-pear \ --enable-sockets \ --with-freetype-dir=/usr \ --with-zlib \ --with-libxml-dir=/usr \ --with-xmlrpc \ --enable-zip \ --enable-fpm \ --enable-xml \ --enable-sockets \ --with-gd \ --with-zlib \ --with-iconv \ --enable-zip \ --with-freetype-dir=/usr/lib/ \ --enable-soap \ --enable-pcntl \ --enable-cli \ --with-curl 安装 1 make && make install 使用默认的配置文件 1 2 3 4 5 6 7 8 9 cp php.ini-production /etc/php.ini cp /usr/local/php/etc/php-fpm.conf.default /usr/local/php/etc/php-fpm.conf cp /usr/local/php/etc/php-fpm.d/www.conf.default /usr/local/php/etc/php-fpm.d/www.conf cp sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm chmod +x /etc/init.d/php-fpm 增加用户和组 1 2 3 4 5 6 7 8 9 10 11 adduser www #增加用户 passwd www #为用户设置密码 groupadd www #增加组 # 以下两条命令二选一 usermod -G www www #将用户移动到指定组,删除用户之前的组,前面是username,后面是groupname gpasswd -a www www #将用户移动到指定组,不删除该用户之前的组,前面是username,后面是groupname 配置用户和用户组 1 vim /usr/local/php/etc/php-fpm.d/www.conf 修改如下内容 添加环境变量(可选) 1 vim /etc/profile 将光标移动到最后一行,添加如下内容(按 i 进入输入模式,输入完成之后按 esc 进入命令模式,输入 :wq 回车即可保存并退出):export PATH=$PATH:/usr/local/php/bin 1 source /etc/profile 验证 1 php --version 至此,恭喜您 LNMP 环境搭建完毕!接下来愉快的敲代码吧😘😘

2022/9/15
articleCard.readMore

部署项目时遇到的坑

前言 前端时间学习过程中写了几个小 Demo,但都是在本机开发环境下运行的。本以为部署到服务器就是简单把文件上传就可以。结果踩了一些很可笑的坑🤣🤣 部署普通 JavaEE 项目 这个比较简单,用 Maven 把项目打成 War 包,然后上传至 Tomcat 目录的 webapps 目录下,并且启动 Tomcat 服务即可。Tomcat 会自动将 War 包进行解压缩。然后访问 ip:port/project_name-project_version 即可看到项目首页。 需要注意的是:项目中的所有涉及到路径跳转的 url 都尽量使用相对路径 部署 Spring Boot 项目 问题说明 我使用的 IDE 是 Intellij IDEA,开发时项目在本地运行正常。使用 Maven 将其打包后,运行 java -jar test-2.0-SNAPSHOT.jar 出现错误: 翻译过来就是:这个 jar 包中没有主清单属性 原因解读 出现此问题的原因是打包后的 jar 文件中的 MANIFEST.MF 缺少项目启动项。我们用压缩软件打开 jar 包,查看 META-INF 下的 MANIFEST.MF 文件 第一时间想到的是没有 Start-Class 配置项 解决方案 在项目中添加 spring-boot-maven-plugin 打包插件。 pom.xml 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 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <artifactId>spring-boot-starter-parent</artifactId> <groupId>org.springframework.boot</groupId> <version>2.6.7</version> </parent> <groupId>com.pushihao</groupId> <artifactId>test</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>2.3.2</version> </dependency> </dependencies> <!--此处即为添加上述插件--> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 再次打包,查看 META-INF 下的 MANIFEST.MF 文件 会发现多了一些相关配置属性,此时再运行 java -jar test-2.0-SNAPSHOT.jar 一切正常 部署 vue 项目 问题说明 构建环境我使用的是 vue3 + vite 为了解决跨域问题,我使用了 vite 提供的代理设置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' // https://vitejs.dev/config/ export default defineConfig({ plugins: [vue()], server: { proxy: { "/api": { target: "http://152.136.233.95:8090/", changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ""), }, }, disableHostCheck: true }, }) 项目在本地运行正常。通过 yarn run build 构建打包,上传到服务器,通过 nginx 创建服务器 1 2 3 4 5 6 7 8 9 server { listen 10989; server_name localhost; location / { root html/test2; index index.html; } } 浏览项目地址。发现项目的 ajax 请求出现 404 错误 原因解读 vite 提供的代理服务器在实际项目中不起作用,仅在开发阶段可以使用 解决方案 使用 nginx 进行代理 其中使用 nginx 在后端代理的方式在我的上一篇博客浅谈 xhr 请求跨域问题已经提到了,这里主要介绍使用 nginx 在前端进行反向代理 1 2 3 4 5 6 7 8 9 10 11 12 13 server { listen 10989; server_name localhost; location / { root html/test2; index index.html; } location /api/ { proxy_pass http://152.136.233.95:8090/; } } 重启 nginx 服务,前端请求正常,问题解决!

2022/9/9
articleCard.readMore

浅谈 xhr 请求跨域问题

问题引出 我在 http://localhost:8080 放了一台后端服务器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.pushihao.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/hello") public class HelloController { @RequestMapping("/h1") public String h1() { return "Hello, world!"; } } 通过调用 http://localhost:8080/hello/h1 可以获得一个字符串 我又在 http://localhost:5173 放了一台前端前端服务器,通过 xhr 请求调用后端的接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <script setup> import axios from 'axios' axios.defaults.timeout = 5000 axios.get("http://localhost:8080/hello/h1").then( response => { console.log(response) }, error => { console.log(error) } ) </script> <template></template> <style scoped></style> 结果这时,浏览器报错 以上便是 xhr 请求跨域问题的一个经典例子 为什么会有请求跨域问题 什么导致的跨域请求问题?简单来说,浏览器的 同源策略 导致了跨域请求问题。 为什么要制定同源策略?官方是这样解释的:同源策略是一个重要的安全策略,它用于限制一个 origin 的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。 什么样的 url 才算是同源呢?当两个 url 的**协议(protocol)、域名(host)、端口(port)**三者都相同时才算是同源。只要其中有一个不同,那么 url 就不算同源,进行的 http 请求就称为跨域请求。 跨域请求有什么限制?无法读取非同源网页的 cookie、localstorage 等、无法接触非同源网页的 DOM 和 js 对象、无法向非同源地址发送 Ajax 请求。 解决跨域请求问题 我们知道,同源策略是一个重要的安全策略,它可以减少我们被攻击的风险。但是对于正常的请求,我们希望可以接触跨域带来的一些限制。解决跨域请求问题的方法有很多,以下给出一些常用的解决方案。 后端使用 nginx 解决跨域问题 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 server { listen 3500; server_name localhost; location / { # 给 Nginx 配置 Access-Control-Allow-Origin * 后,表示服务器可以接受所有的请求源(Origin),即接受所有跨域的请求。也就是说,表示接受任意域名的请求。这里为 * 是最简单粗暴的方式,但是出于安全考虑,一般会设置为前端域名,如:add_header Access-Control-Allow-Origin 'www.pushihao.com';(只能指定一个域) add_header Access-Control-Allow-Origin *; # 如果是上述设置为 * 的话,浏览器将不会发送 cookie 数据(如果需要携带 cookie 数据,则需要进行此设置 add_header Access-Control-Allow-Credentials true; # 防止出现 Content-Type is not allowed by Access-Control-Allow-Headers in preflight response. 错误 add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; # 防止出现 Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response. 错误 add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; # 防止在发送 post 请求时 Nginx 依然拒绝访问的错误。发送"预检请求"时,需要用到方法OPTIONS,所以服务器需要允许该方法 if ($request_method = 'OPTIONS') { return 204; } # 反向代理地址 proxy_pass http://localhost:8080; } } 启动 Nginx 服务后,3500 端口被代理到 8080 端口,并且开启了允许所有域进行跨域请求 此时,修改前端请求的 url 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <script setup> import axios from 'axios' axios.defaults.timeout = 5000 axios.get("http://localhost:3500/hello/h1").then( response => { console.log(response) }, error => { console.log(error) } ) </script> <template></template> <style scoped></style> 启动服务后,请求响应正常 使用前端框架提供的代理服务器 注意:这种方法仅在调试阶段有效,部署到服务器上之后就没用了 使用原理: 只有前端在发送 Ajax 请求时会触发跨域请求,而两台后端服务器进行通信时是不会存在跨域的问题的,因此我们可以在前端与后端之间设置一台代理服务器,这台代理服务器与我们的前端项目保持同源,即:localhost:5173 ,我们的前端项目只负责请求这台代理服务器,由代理服务器向后端接口请求数据。 对于 webpack + vue 的前端项目,可以使用 devServer 配置代理解决跨域问题 vue.config.js 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 const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, //开启代理服务器(方式一) // devServer: { // proxy: "http://localhost:8080" // } //只能由一个代理服务器,而且会优先请求本地资源,无法决定请求资源的位置 //开启代理服务器(方式二) devServer: { proxy: { '/api': { target: 'http://localhost:8080', //重写请求路径,把/api去掉 pathRewrite: { '^/api': '' }, ws: true, //用于支持websocket //等于true时会有跨域伪造,即:如果请求的目标服务器为8080,则请求时也假装自己是8080端口的 //等于false时面对目标服务器就如实回答 changeOrigin: true }, //proxy可配置多个 '/teacher': { target: 'http://localhost:8082', pathRewrite: { '^/teacher': '' }, ws: true, changeOrigin: true } }, } }) AxiosTest.vue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <script setup> import axios from 'axios' axios.defaults.timeout = 5000 // 由于我们制定了重写规则,所以此处的 /api 会在发送请求时被略去 axios.get("/api/hello/h1").then( response => { console.log(response) }, error => { console.log(error) } ) </script> <template></template> <style scoped></style> 对于 vite + vue 的前端项目 vite.config.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' // https://vitejs.dev/config/ export default defineConfig({ plugins: [vue()], server: { proxy: { "/api": { target: "http://localhost:8080", changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ""), }, }, }, }) AxiosTest.vue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <script setup> import axios from 'axios' axios.defaults.timeout = 5000 axios.get("/api/hello/h1").then( response => { console.log(response) }, error => { console.log(error) } ) </script> <template></template> <style scoped></style> 至此,问题解决!

2022/9/7
articleCard.readMore

JavaScript 学习笔记

引入 JavaScript 是一种轻量级、跨平台和解释编译的编程语言,也称为网页脚本语言。它以开发网页而闻名,但是许多非浏览器环境也可以使用它。JavaScript 可用于客户端开发以及服务器端开发。它既是命令式又是声明式语言。作为前端三件套之中唯一有编程逻辑的语言,JavaScript 常常用于页面交互、实现一些复杂的动画以及数据传输等。 浏览器工作原理 浏览器分为两个部分:渲染引擎和 JS 引擎 渲染引擎:用来解析HTML和CSS,俗称内核,比如 chrome 的blink,老版本的 webkit JS 引擎:也成为 JS 解释器。用来读取网页中的 JavaScript 代码,对其进行后台处理,比如 chrome 浏览器的 v8 注意:浏览器本身不会执行 JS 代码,而是通过内置 JavaScript 引擎(解释器)来执行JS代码,JS 引擎执行代码时会逐行解释每一行源码(转化为机器语言),然后由计算机去执行 JS 组成 ECMAScript ECMAScript 是由 ECMA 国际(原欧洲计算机制造商协会)进行标准化的一门编程语言,它规定了 JS 的编程语法和基础核心知识,是所有浏览器厂商共同遵守的一套 JS 语法工业标准 DOM DOM(Document Object Model,文档对象模型)是 W3C 组织推荐的处理可扩展标记语言(HTML或XML)的标准编程接口。通过此接口我们可以很容易的对页面上的元素的属性进行操作 BOM BOM(Browser Object Model,浏览器对象模型)是一种独立于内容的,与浏览器窗口进行互动的对象结构。通过 BOM 可以操作浏览器窗口,比如弹出框、浏览器跳转、获取窗口大小、滚动条、分辨率等 JS 数据类型及运算符 JS 是一种弱类型的语言,也就是说某一个变量被定义类型之后,该变量可以根据环境变化自动进行转换,不需要强制转换 字符串 使用单引号或双引号包裹 \ 为转义字符,通过 \ ,可以打印 Unicode Ascll 字符等 多行字符串编写 1 2 3 4 5 var msg = ` hello world 你好 世界` 模板字符串 1 2 3 4 5 6 7 let name = 'Pillage' let age = 19 let msg = `你好啊, ${name}` console.log(msg) > 你好啊, Pillage 字符串长度 1 str.length 可变性:不可变 大小写转化(对中文没有影响) 1 2 .toUpperCase(); .toLowerCase(); 寻找索引 1 .indexOf('t') 截取字符串 1 2 .substring(1) //从下标为1开始截取,包括下标为1的,一直截到最后 .substring(1, 3) //前闭后开 数组 注意:for in 是遍历下标, for of 才是遍历数组具体的值 Array 可以包含任意的数据类型 1 let array = [1, 2, 3, "Hello"] 长度 1 array.length 注意:此属性可读可写,即可以通过赋值修改数组的大小 寻找索引 1 array.indexOf(1) 截取数组,返回一个新数组 1 array.slice() 类似于 String 中的 substring 操作元素(在尾部) 1 2 array.push() array.pop() 操作元素(在头部) 1 2 array.unshift //压入到头部 array.shift //弹出头部的一个元素 排序 1 array.sort() 元素反转 1 array.reverse() 数组拼接 1 array.concat([1, 2, 3]) 注意:concat() 并不会修改原数组,只会返回一个连接后的新数组 连接数组 打印拼接数组,使用特定的字符串连接 1 array.join('') 多维数组 类似于 Python。例如二维数组就是一个普通一维数组中的每一个元素又是一个一维数组 对象 定义: 1 2 3 4 5 6 7 8 9 10 11 12 var 对象名 = { 属性名:属性值, 属性名:属性值, ... 属性名:属性值 } var person = { name: "pillage", age: 18, score: 100 } JS 中,大括号可以表示一个对象,用键值对描述属性,多个属性之间用逗号隔开,最后一个不需要逗号 JS 中,所有的键都是字符串,值可以是任意类型 对象赋值 1 person.age = 19 动态增删属性 1 2 3 4 5 //动态删减 name 属性 delete person.name //动态增加 sex 属性,只需要给新属性赋值即可 person.sex = "男" 判断某个属性值是否在这个对象中 xxx in xxx 注意:因为继承机制的存在,所以父类有的属性,子类也会被判定为有 如: 1 2 3 4 5 'toString' in person > true 'age' in person > true 判断某个属性是否在这个对象本身中 hasOwnProperty 1 2 3 4 5 person.hasOwnProperty('toString') > false person.hasOwnProperty('age') > true 注意:Map 和 Set 是 ES6 新特性 Map 定义: 1 var map = new Map([['tom', 100], ['jerry', 99]]) 用二维数组的形式可以直接 new 出来 元素操作: 1 2 3 var name = map.get('tom') //通过键获取值 map.set('admin', 1000) //修改值,也可以增加新值 map.delete('tom') //删除操作 Set 无序不重复集合,Set 会自动进行去重操作 定义: 1 var set = new set([1, 2, 3]) 操作元素: 1 2 3 set.add(4) //增加元素 set.delete(1) //删除元素 set.has(1) //查找元素是否存在 遍历 forEach 循环 1 2 3 4 let array = [5, 6, 7] array.forEach(function(value) { console.log(value) }) iterator 迭代器 遍历数组: 1 2 3 4 5 6 7 8 9 10 let array = [5, 6, 7] //遍历下标,输出结果为 0 1 2 for (let i in array) { console.log(i) } console.log("==========") //遍历数组的值,输出结果为 5 6 7 for (let i of array) { console.log(i) } 遍历 Map 和 Set: 1 2 3 4 5 6 7 8 let map = new Map([['Tom', 100], ['Jerry', 99]]) for (let i of map) { console.log(i) } let set = new Set([3, 4, 5]) for (let i of set) { console.log(i) } 变量转换 通过 typeof 变量名 可以输出变量的类型 String 转数字: 1 2 parseInt();//转为整数 parseFloat();//转为浮点数 强制类型转换 1 2 3 Boolean(value);//把给定的值转换成Boolean型; Number(value);//把给定的值转换成数字(可以是整数或浮点数); String(value);//把给定的值转换成字符串。 注意:用这三个函数之一转换值,将创建一个新值,存放由原始值直接转换成的值。这可能会造成意想不到的后果。 逻辑运算符 与 && 或 || 非! 比较运算符: 1 2 3 = //赋值运算符 == //等于(类型不一样,值一样,也会为 true) === //绝对等于(必须类型一样,值也一样,结果才为 true) 注意: NaN 与任何数值都不相等,包括自己 1 NaN === NaN //false 只能通过 isNaN(NaN) 这个方法来判断一个数是否为 NaN JS 常用输入输出方法 方法名方法的作用参数解释 alert(msg)浏览器弹出带有信息的警示框msg:要显示的信息 console.log(meg)浏览器控制台打印信息msg:要打印的信息 console.dir(v)控制台打印元素对象,从而查看里面的属性和方法v:变量名 prompt(msg)浏览器弹出带有一段消息的输入框,获取输入msg:显示消息,返回值:用户输入内容 confirm(msg)浏览器弹出带有一段消息的确认框msg:显示消息,返回值:bool类型用户是否确认 DOM 基础 基本操作 关于 dom 的基操,主要内容有 创建、增、删、改、查、属性操作、事件操作 创建 1 2 3 document.write innerHTML createElement 增 1 2 appendChild insertBefore 删 1 removeChild 改 1 2 3 4 5 主要修改 dom 的元素属性,dom 元素的内容、属性,表单的值等 1、修改元素属性:src、href、title 等 2、修改普通元素内容:innerHTML、innerText 3、修改表单元素:value、type、disable 等 4、修改元素样式:style、className 查 1 2 3 4 主要获取查询 dom 的元素 1、DOM 提供的 API 方法:getElementById,getElementByTagName 古老用法 不推荐 2、H5提供的新方法:querySelector,querySelectorAll 3、利用节点操作获取元素:父(parentNode)、子(children)、兄(previousElementSibling、nextElementSibling) 属性操作 1 2 3 4 主要针对于自定义属性 1、setAttribute 2、getAttribute 3、removeAttribute 事件操作 给元素注册事件,采取 事件源.事件类型 = 事件处理程序 鼠标事件触发条件 onclick鼠标点击左键触发 onmouseover鼠标经过触发 onmouseout鼠标离开触发 onfocus获得鼠标焦点触发 onblur失去鼠标焦点触发 onmousemove鼠标移动触发 momouseup鼠标弹起触发 onmousedown鼠标按下触发 DOM 获取元素 1 2 3 4 5 6 7 8 9 10 11 12 document.getElementById();//属性名 document.getElementByTagName();//标签名,如p,div,li 返回的是一个伪数组 element.getElementByTagName();//返回element为父元素的指定标签的对象,父元素必须为单个元素 //获取特殊元素 document.body;//获取body标签 document.documentElement;//获取HTML标签 //HTML5新增 ie678不支持 document.getElementByClassName();//通过类名获得对象 document.querySelector();//通过CSS选择器获得对象,返回指定选择器的第一个元素对象 document.querySelectorAll();//返回所有满足选择器的对象,伪数组 事件 事件三要素:事件源、事件类型、事件处理程序 执行事件的步骤: 获取事件源 注册事件(绑定事件) 添加事件处理程序(采取函数赋值形式) 1 2 3 4 5 6 7 <button id="btn">按钮</button> <script> var btn = document.querySelector("#btn"); btn.onclick = function() { console.log("click事件"); } </script> this 可以指向事件函数的调用者 操作元素 改变元素的内容 1 2 对象.innerText = ; 对象.innerHTML = ;//使用最多,是W3C推荐标准 这两个属性也能读取对象内的内容 区别: innerText 不能识别 HTML 标签,innerHTML 能识别 innerText 在读取内容的时候,会自动去除空格和换行,而 innerHTML 会保留 操作元素属性 1 对象.属性名 = ; 也就是可以直接修改 修改表单属性时需要用到表单特有的属性(比如要修改输入框的内容用 inner HTML 就不好使了): 1 type value checked selected disabled 样式属性的操作 通过 JS 可以修改元素的大小、颜色、位置等属性。 1 2 1. element.style //行内样式操作 如:div.style.backgroundColor = "purple"; 2. element.className //类名样式操作 element.style 属性采用的是驼峰命名法,与CSS不同,如 fontSize backgroundColor JS 修改 style 样式时,产生的是行内样式,权重比 CSS 更高 如果要修改的样式比较少,或者功能比较简单的时候,可以这样用 element.className 修改元素的类名 可以提前把要修改的内容写入 CSS 中,需要改变时应用该 CSS 会直接更改类名,也就是覆盖掉以前的类名 如果要保留以前的类名,可以在要修改的类名前加上”以前的类名 “(别忘了空格)。(其实原理就是一个元素可以又多个类名) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <style> div{ width: 10px; height: 10px; background-color: purple; } .change{ width: 200px; height: 400px; background-color: pink; margin: auto 0; } </style> <script> window.onload = function() { var div = document.querySelector("div"); div.onclick = function() { div.className = "change"; } } </script> 鼠标事件 焦点事件 鼠标点击焦点事件 onfocus 失去鼠标焦点事件 onblur (鼠标焦点是指鼠标点一下,失去焦点是指鼠标点一下其他地方) 1 2 3 4 5 6 7 8 var input = document.querySelector("input"); input.onfocus = function() { console.log("得到了焦点"); } input.onblur = function() { console.log("失去了焦点"); } 移入移出事件 鼠标移入事件 onmouseover 鼠标移出事件 onmouseout 1 2 3 4 5 6 7 8 9 window.onload = function() { var box = document.querySelector(".box"); box.onmouseover = function() { this.style.backgroundColor = "red"; } box.onmouseout = function() { this.style.backgroundColor = "aqua"; } } 总结 小思想-排他思想 当用 for 循环对多个元素绑定同一事件时,如果想要某一个元素实现某个样式,则可以: 清除所有元素的样式 给当前的元素设置样式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <button>按钮1</button> <button>按钮2</button> <button>按钮3</button> <button>按钮4</button> <button>按钮5</button> <script> var btns = document.querySelectorAll("button"); for (var i = 0; i < btns.length; i++) { btns[i].onclick = function() { //先清除其他元素的格式,再给自己的元素设置格式 for (var j = 0; j < btns.length; j++) btns[j].style.backgroundColor = ""; this.style.backgroundColor = "pink"; } } </script> 操作自定义属性 元素自定义属性通常以 data- 开头,如:<p data-time=”20”></p> 获取元素的属性 1 2 3 4 5 element.属性 - 获取元素的内置属性 element.getAttribute('属性') - 获取自定义属性和内置属性 设置元素属性 1 2 element.属性 = "值" element.setAttribute('属性', '值') 移除元素属性 1 element.removeAttribute('属性') H5 新增获取自定义属性 1 2 3 4 5 6 7 8 如果有一个元素为:<a data-index="1"></a> 1. 兼容性获取:element.getArribute('data-index'); 2. H5 新增:element.dataset.index 或者 element.dataset['index'] - 只能获取 data- 开头的 - 开头的 data- 省略,如果后面有多个 - 连接的单词,则获取的时候采取驼峰命名法 - ie11 才开始支持 节点操作 简述 一般的,节点至少拥有 nodeType(节点类型)、nodeName(节点名称)和 nodeValue(节点值)这三个基本属性 元素节点 nodeType 为 1 属性节点 nodeType 为 2 文本节点 nodeType 为 3(文本节点包含文字,空格,换行等) 节点操作的主要对象是元素节点 获取父子节点 父级节点 1 node.parentNode parentNode 返回元素节点的最近的一个父节点(亲爸爸) 如果没有找到返回 null 子节点 1 parentNode.childNodes 返回值包含了元素的所有子节点,包括元素节点和文本节点 如果只想获取元素节点,则应 专门处理 1 2 3 4 5 6 var ul = document.querySelector('ul'); for (var i = 0; i < ul.childNodes.length; i++) { if (ul.childNodes[i].nodeType === 1) { console.log(ul.childNodes[i]); } } 子元素节点 1 parentNode.children children 是一个只读属性,只返回子元素节点,其余节点不返回 非标准的,但是得到了很多浏览器的支持 第一个/最后一个 子节点 1 2 3 4 5 //第一个子节点 parentNode.firstChild //最后一个子节点 parentNode.lastChild 节点包含元素节点和文本节点 第一个/最后一个 子元素节点 1 2 3 4 //第一个子元素节点 parentNode.firstElementChild //最后一个子元素节点 parentNode.lastElementChild 这两个方法有兼容性问题,ie9 以上才支持 所以可以这样写 1 2 3 4 //第一个子元素节点 parentNode.children[0] //最后一个子元素节点 parentNode.children[parentNode.children.length - 1] 获取兄弟关系 兄弟节点 1 2 3 4 //获取元素的下一个兄弟节点,包含所有节点 node.nextSibling //获取元素的上一个兄弟节点,包含所有节点 node.previousSibling 兄弟元素节点 1 2 3 4 //下一个兄弟元素节点 node.nextElementSibling //上一个兄弟元素节点 node.previousElementSibling 兼容性问题,ie9 以上才支持 创建节点 1 document.createElement('tagName') document.createElement() 方法创建由 tagName 指定的 HTML 元素。也称为动态创建元素节点 只是创建并没有添加 添加节点 在后面追加 1 node.appendChild(child) 将一个节点添加到指定父节点的子节点列表的末尾,类似于 css 中的 after 伪元素 也称为追加元素 插入节点 1 node.insertBefore(child, 指定元素) 将一个节点插入到父节点的指定子节点的前面 删除节点 1 node.removeChild(childNode) 复制节点 1 node.cloneNode() 克隆相当于新建,克隆后需要添加操作 如果括号参数为空或者 false,则是浅拷贝,即只克隆复制节点本身,不克隆里面的子节点 如果括号里面为 true,则是深拷贝,既复制节点本身,也复制里面的子节点 三种动态创建元素的区别 document.write() 是直接将内容写入页面的内容流,但是会导致页面全部重绘 element.innerHTML 是将内容写入某个 DOM 节点,不会导致页面全部重绘 创建多个元素效率更高(不要采取拼接字符串,采取数组形式拼接),结构稍微复杂 document.createElement() 创建多个元素效率会稍微低一些,但是结构更清晰 DOM 高级 注册事件(绑定事件) 概述 给元素添加事件,称为注册事件或者绑定事件 注册事件有两种方式:传统方式和方法监听注册方式 传统注册方式 利用 on 开头的事件,如 onclick <button onclick = “alert(‘hi’)”></button> btn.onclick = function() { } 特点:注册事件的唯一性 同一个元素同一个事件只能设置一个处理函数,最后注册的处理函数将会覆盖前面注册的处理函数 方法监听注册方式 w3c 标准推荐方式 addEventListener() 是一个方法 ie9 之前的 ie 不支持此方法,可使用 attachEvent() 代替 特点:同一个元素同一个事件可以注册多个监听器 按注册顺序依次执行 addEventListener 事件监听方式 1 eventTarget.addEventListener(type, listener, [useCapture]) 此方法会将指定的监听器注册到 eventTarget(目标对象)上,当该对象触发指定的事件时,就会执行事件处理函数 同一个元素,同一个事件可以添加多个侦听器 该方法接收三个参数: type:事件类型字符串(别忘了加引号),比如 click, mouseover, 等,注意不用带 on listener:事件处理函数,事件发生时,会调用该监听函数 useCapture:可选参数,是一个布尔值 当为 true 时,事件处于捕获阶段 当为 false 或者 省略 时,事件处于冒泡阶段 1 2 3 btn2.addEventListener('click', function() { alert("使用addEventListener添加点击事件") }) attachEvent 事件监听方式(了解) 此方法只有 ie9 及以前的浏览器才支持,chrome 等浏览器不支持 1 eventTarget.attachEvent(eventNameWithOn, callback) 参数: eventTarget:目标对象 eventNameWithOn:事件类型字符串,比如 onclick, onmouseover,等,要带 on callback:事件处理函数 删除事件(解绑事件) 传统方式删除事件 .onclick = null; 1 2 3 4 5 //如:想让一个div点击一下以后就不能再次点击 div.onclick = function() { alert("111"); div.onclick = null; } 删除方法监听注册的事件 eventTarget.removeEventListener(type, listener, [useCapture]) 1 2 3 4 5 6 7 //如:想让一个div点击一下以后就不能再次点击 div.addEventListener("click", fn) //fn不需要加小括号 function fn() { alert("222") div.removeEventTarget("click", fn); } 删除 attachEvent 事件 eventTarget.detachEvent(eventNameWithOn, callback) 1 2 3 4 5 6 7 //如:想让一个div点击一下以后就不能再次点击 div.attachEvent("onclick", fn); function fn() { alert("333") div.detachEvent("onclick", fn); } DOM 事件流 事件发生时会在元素节点之间按照特定的顺序进行传播,这个传播过程叫做 DOM 事件流 JS 代码只能执行捕获或者冒泡中的一个阶段 onclick 和 attachEvent 只能得到冒泡阶段 addEventListener 的第三个参数如果为 true 表示在捕获阶段调用事件处理程序,如果为 false 或 不写则表示在冒泡阶段调用事件处理程序 有些事件是没有冒泡的,如:onblur、onfocus、onmouseenter、onmouseleave

2022/9/2
articleCard.readMore

Eclipse配置Web开发环境

前言 老师们似乎都倾向于使用 Eclipse 作为 Java 开发的 IDE。其实作为工具效果都是一样的,但是使用方式却不太相同。所以只能浅浅折腾一下 Eclipse 的相关配置。 前期准备–相关软件下载 注意:以下 “最新官网下载链接” 提供的均为64位windows环境下的下载链接。Linux/Mac 请前往官网下载相对应软件 软件名官网地址最新版官网下载链接 Eclipsehttps://www.eclipse.org/https://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/2022-06/R/eclipse-jee-2022-06-R-win32-x86_64.zip Oracle JDKhttps://www.oracle.com/https://download.oracle.com/java/17/latest/jdk-17_windows-x64_bin.exe Tomcathttps://tomcat.apache.org/https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.65/bin/apache-tomcat-9.0.65-windows-x64.zip Mavenhttps://maven.apache.org/https://dlcdn.apache.org/maven/maven-3/3.8.6/binaries/apache-maven-3.8.6-bin.zip 配置 Oracle JDK 点击 Window -> Preferences -> Java -> Installed JREs 点击 Add -> Standard VM -> Next 点击 Directory… -> 选择你的Java安装位置 -> Finish 勾选你刚配置的环境 -> Apply and Close 至此,OracleJDK 环境配置完毕! 配置 Tomcat 点击 Window -> Preferences -> Server -> Runtime Environments -> Add 点击 Apache -> Apache Tomcat v[版本号] -> Next 点击 Browse… -> 选择你的 Tomcat 安装位置 -> JRE勾选刚刚配置的 -> Finish 点击 Apply and Close 至此,Tomcat 的环境配置完毕! 配置 Maven 并修改配置文件位置 点击 Window -> Preferences -> Maven -> Installations -> Add 点击 Directory… -> 选择你的Maven安装位置 -> Finish 点击 Window -> Preferences -> Maven -> User Settings -> Browse… -> 选择配置文件位置 -> Update Settings -> Apply and Close 注意:图一图二展示了配置文件所在位置 (都在Maven安装目录下) (图一) (图二) 至此,Maven 环境配置完毕!接下来进行验证 验证. 点击 Window -> Show View -> Other… -> Maven -> Maven Repositories -> Open 可以看到,Maven的本地仓库以及中央仓库都是配置文件中所设置的 配置编辑器字体 点击 Window -> Preferences -> General -> Appearance -> Colors and Fonts -> Java -> Java Editor Text Font -> Edit 编辑字体 -> 确定 至此,编辑器字体修改完成! 配置控制台字体 点击 Window -> Preferences -> General -> Appearance -> Colors and Fonts -> Basic -> Text Font -> Edit 编辑字体 -> 确定 至此,控制台字体修改完成! 大功告成!

2022/8/30
articleCard.readMore

Vue2 基本知识

Vue是什么 Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。 引入 使用 CDN: 1 2 3 4 5 开发版本: <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> 生产版本: <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script> 下载并自行引入: 1 2 https://cdn.jsdelivr.net/npm/vue@next/dist/ https://unpkg.com/browse/vue@3.2.31/dist/ 第一个Vue应用 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 <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>Hello Vue</title> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> </head> <body> <div id="app"> <h1>{{message}}</h1> </div> <script> const vm = new Vue({ el: "#app", data: { message: "Hello" } }); </script> </body> </html> 常用指令 插值表达式 1 2 {{name}} {{school.name}} 通过插值表达式可以直接在页面中使用变量的值 v-html 向标签中插入一段html代码 1 2 3 4 5 6 7 8 9 10 11 12 <div id="app"> <div v-html="strong"></div> </div> <script> const vm = new Vue({ el: '#app', data: { strong: '<b>strong</b>' } }) </script> v-text 向标签中插入一段纯文本 1 2 3 <span v-text="message"></span> <!--等同于--> <span>{{message}}</span> v-for 循环指令,用于遍历数组等,在遍历数组时还可以加上index获取下标 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <div id="app"> <h1 v-for="(item, index) in items"> {{item.name}} ===> {{index}} </h1> </div> <script> var vm = new Vue({ el: "#app", data: { items: [ {name: "Pillage"}, {name: "Daisy"}, {name: "Steven"} ] } }); </script> v-if 条件语句,可以结合使用 v-else v-else-if,值为布尔类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <div id="app"> <h1 v-if="flag">Yes</h1> <h1 v-else>No</h1> =================================== <h1 v-if="tag == 'A'">A</h1> <h1 v-else-if="tag == 'B'">B</h1> <h1 v-else-if="tag == 'C'">C</h1> <h1 v-else>D</h1> </div> <script> var vm = new Vue({ el: "#app", data: { flag: true, tag: "A" } }); </script> v-show 根据判断条件来决定是否展示该标签 1 2 3 4 5 6 7 8 9 10 11 12 13 <div id="app"> <div v-show="show"><h1>展示</h1></div> <div v-show="notShow">未展示</div> </div> <script> const vm = new Vue({ el: '#app', data: { show: true, notShow: false } }) </script> v-bind 为属性绑定一个值或变量 1 2 3 4 5 6 7 8 9 10 11 <div id="app"> <a v-bind:href="hao123">hao123</a> </div> <script> const vm = new Vue({ el: '#app', data: { hao123: 'https://www.hao123.com/' } }) </script> 此指令具有简写形式(v-bind: 可以简写为 :) 1 <a v-bind:href="hao123">hao123</a> => <a :href="hao123">hao123</a> v-on 为标签绑定事件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <div id="app"> <button v-on:click="click">Click Me</button> </div> <script> var vm = new Vue({ el: "#app", data: { message: "pillage" }, methods: { click: function() { alert(this.message) } } }); </script> 修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault(): 1 <form v-on:submit.prevent="onSubmit">...</form> 此指令也具有简写形式(v-on: 可以简写为 @) 1 2 <button v-on:click="click">Click Me</button> => <button @click="click">Click Me</button> v-model v-model可以实现对数据的双向绑定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <div id="app"> 输入一段文字: <input type="text" v-model="message"> <br><br><br> 您输入的文字为:{{message}} </div> <script> const vm = new Vue({ el: "#app", data: { message: "" } }); </script> v-once 仅加载一次数据,以后数据即使改变也不会影响 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <div id="app"> <p>姓名为:<span v-once>{{name}}</span></p> <button @click="changeName">修改姓名</button> </div> <script> const vm = new Vue({ el: '#app', data: { name: 'pillage' }, methods: { changeName() { this.name = '小白' } } }) </script> v-cloak 当 Vue 实例编译结束,页面渲染后,此属性会立刻消失 作用:可以用来解决网速慢时页面出现一堆 的情况 基本使用: 1 2 3 4 5 6 7 8 9 10 11 <style> [v-cloak] { display: none; } </style> <body> <div id="app" v-cloak> {{info.name}} </div> </body> 计算属性和侦听器 计算属性 计算属性使用了缓存机制,所以它执行的效率要比 methods 方法要高 计算属性也有两种写法: 类似于函数的简写形式 1 2 3 4 5 6 7 8 9 10 11 12 13 <div id="app"> {{date}} </div> <script> const vm = new Vue({ el: '#app', computed: { date() { return Date.now() } } }) </script> 这样写默认只有getter方法,没有setter方法 对象形式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <div id="app"> {{name}} </div> <script> const vm = new Vue({ el: '#app', data: { currentName: 'pillage' }, computed: { name: { get() { return this.currentName }, set(newValue) { this.currentName = newValue } } } }) </script> 此时运行 vm.name = '小白' 时,setter 会被自动调用,currentName 的值被更新 侦听器 虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。 他也有两种写法(简写形式和对象形式) 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 <div id="app"> <p v-text="university"></p> <p>{{person}}</p> </div> <script> const vm = new Vue({ el: "#app", data: { university: "东北林业大学", person: { name: "pillage", age: 19 } }, //watch 监听 watch: { //监听university属性值,newValue代表新值,oldValue代表旧值 university(newValue, oldValue) { console.log("newValue:" + newValue + " oldValue:" + oldValue) }, //监听person对象的值,对象的监控只能获取新值 person: { //开启深度监控;监控对象中的属性值变化 deep: true, //获取到对象的最新属性数据(obj代表新对象) handler(obj) { console.log("name:" + obj.name + " age:" + obj.age) } } }, }); </script> 基本使用:收集表单数据 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 <div id="app"> <form action=""> 账号:<input type="text" v-model="account"> <br><br> 密码:<input type="password" v-model="password"> <br><br> 性别: 男<input type="radio" name="sex" value="boy" v-model="sex"> 女<input type="radio" name="sex" value="girl" v-model="sex"> <br><br> 爱好: 敲代码<input type="checkbox" value="code" name="hobby" v-model="hobby"> 吃包子<input type="checkbox" value="eat" name="hobby" v-model="hobby"> 刷视频<input type="checkbox" value="video" name="hobby" v-model="hobby"> <br><br> 所在省份: <select name="province" v-model="province"> <option value="">请选择</option> <option value="henan">河南</option> <option value="tianjin">天津</option> <option value="beijing">北京</option> <option value="chongqing">重庆</option> </select> <br><br> 其他信息: <textarea v-model="other"></textarea> <br><br> <input type="checkbox" v-model="accept">我已阅读并接受<a href="">《隐私声明》</a> <br><br> <button>提交</button> </form> </div> <script> const vm = new Vue({ el: "#app", data: { account: '', password: '', sex: '', hobby: [], province: '', other: '', accept: false, } }) </script> 生命周期钩子 如下图: (图片来源于网络) 其中 mounted 最常用,常用于发送 ajax 请求获取数据

2022/8/29
articleCard.readMore

Ribbon 简单使用

什么是负载均衡 负载均衡(load balancing)是一种电子计算机技术,用来在多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。 使用带有负载均衡的多个服务器组件,取代单一的组件,可以通过冗余提高可靠性。负载均衡服务通常是由专用软件和硬件来完成。主要作用是将大量作业合理地分摊到多个操作单元上进行执行,用于解决互联网架构中的高并发和高可用的问题。 简单来说,就是将所有请求先集中在一起,然后再根据特定的算法将这些请求分配出去,使各个服务器的效率都能最大化。 常见的负载均衡算法有: 随机法(Random) 加权随机法(Weight Random) 轮询法(Round Robin) 加权轮询法(Weight Round Robin) 平滑加权轮询法(Smooth Weight Round Robin) 地址哈希法(Hash) 最小连接数法(Least Connections) Ribbon 简介 Ribbon 是一个由 Netflix 创建并开源(目前已闭源)的客户端负载均衡器。 使用 Ribbon 导入 Ribbon 由于 Nacos 注册与发现中心已默认集成了 Ribbon,因此我们不必再手动导入。 Ribbon 负载均衡策略 各负载均衡策略的关系继承图如下: 主要提供了如下几种负载均衡策略 名称策略解释 RoundRobinRule轮询策略 WeightedResponseTimeRule服务实例的平均响应时间越短则权重越大,那么服务实例被选中执行的概率也就越大 RandomRule随机策略 RetryRule在轮询的基础上进行重试(超过重试时间服务实例仍无效则进行轮询) NacosRule根据Nacos设置的权重进行挑选 ClientConfigEnabledRoundRobinRule和RoundRobinRule一致,轮询策略 BestAvailableRule过滤掉失效的服务实例然后找出并发请求最小的服务实例进行使用 AvailabilityFilteringRule先过滤掉故障实例然后选择并发较小的服务实例 ZoneAvoidanceRule默认负载均衡策略。根据判断server所在区域的性能和server的可用性来过滤服务实例。过滤成功后,使用轮询策略从过滤结果中选择服务实例 使用 Ribbon 默认负载均衡策略 只需要在 RestTemplate Bean类上添加一个 @LoadBalanced 注解即可使用 Ribbon 的默认负载均衡策略。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.pushihao.orderRibbon.config; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class RestConfig { @LoadBalanced @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder.build(); } } 修改 Ribbon 负载均衡策略 通过配置类进行修改 注意:配置类不能放在 @SpringBootApplication 注解的 @ComponentScan 能扫描到的地方 RibbonRandomRuleConfig.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.pushihao.ribbon; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RibbonRandomRuleConfig { //注意:方法名一定叫 iRule @Bean public IRule iRule() { return new RandomRule(); } } RestConfig.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.pushihao.orderRibbon.config; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.cloud.netflix.ribbon.RibbonClients; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; import com.pushihao.ribbon.RibbonRandomRuleConfig; @Configuration @RibbonClients(value = { @RibbonClient(name = "stock-service", configuration = RibbonRandomRuleConfig.class) }) public class RestConfig { @LoadBalanced @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder.build(); } } 通过配置文件进行修改 application.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 server: port: 9002 spring: cloud: nacos: server-addr: 127.0.0.1:8848 application: name: order-service # 第一行是服务名,负载均衡策略配置的值为类的全路径 stock-service: ribbon: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule 自定义负载均衡策略 通过实现 IRule 接口或继承 AbstractLoadBalancerRule 抽象类可以自定义负载均衡策略,主要的逻辑代码在 choose 方法中。 比如我要自己实现一个随机策略的负载均衡器 CustomRuleConfig.java 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 package com.pushihao.ribbon; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.Server; import java.util.List; import java.util.concurrent.ThreadLocalRandom; public class CustomRuleConfig extends AbstractLoadBalancerRule { @Override public Server choose(Object key) { //获得当前请求的服务的实例 ILoadBalancer loadBalancer = this.getLoadBalancer(); List<Server> reachableServers = loadBalancer.getReachableServers(); int randomNum = ThreadLocalRandom.current().nextInt(reachableServers.size()); Server server = reachableServers.get(randomNum); if (server.isAlive()) { return server; } else { return null; } } @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } } 然后通过配置类或配置文件修改使用自己的负载均衡策略,例如 application.yml 1 2 3 stock-service: ribbon: NFLoadBalancerRuleClassName: com.pushihao.ribbon.CustomRuleConfig 配置 Ribbon 负载均衡预加载 Ribbon 的负载均衡器默认是懒加载,也就是当第一次访问时才会加载,那么就会导致第一次访问时间较长甚至出现网络连接超时的情况。我们可以通过开启预加载(启动服务时就加载)来解决这个问题。 application.yml 1 2 3 4 5 6 #预加载配置,默认为懒加载 ribbon: eager-load: enabled: true # 需要开启预加载的服务名 clients: stock-service1,stock-service2

2022/8/21
articleCard.readMore

Nacos 简单使用

Nacos 是什么 Nacos(Naming and Configuration Service)是阿里巴巴的一个开源项目,也是 Spring Cloud Alibaba 的一个重要组件。专注于服务发现和配置管理领域。 Nacos 官方文档 注意:如果中途发现操作过程一样但还是报错,很有可能是版本号不一致(新版本不一定兼容老版本)。 部署 Nacos 服务器 Nacos 作为 Spring Cloud Alibaba 的一个组件,版本号自然也一定要选择正确,参考 官方版本说明 Spring Cloud Alibaba VersionSentinel VersionNacos VersionRocketMQ VersionDubbo VersionSeata Version 2021.0.1.0*1.8.31.4.24.9.22.7.151.4.2 2.2.7.RELEASE1.8.12.0.34.6.12.7.131.3.0 2.2.6.RELEASE1.8.11.4.24.4.02.7.81.3.0 2021.1 or 2.2.5.RELEASE or 2.1.4.RELEASE or 2.0.4.RELEASE1.8.01.4.14.4.02.7.81.3.0 2.2.3.RELEASE or 2.1.3.RELEASE or 2.0.3.RELEASE1.8.01.3.34.4.02.7.81.3.0 2.2.1.RELEASE or 2.1.2.RELEASE or 2.0.2.RELEASE1.7.11.2.14.4.02.7.61.2.0 2.2.0.RELEASE1.7.11.1.44.4.02.7.4.11.0.0 2.1.1.RELEASE or 2.0.1.RELEASE or 1.5.1.RELEASE1.7.01.1.44.4.02.7.30.9.0 2.1.0.RELEASE or 2.0.0.RELEASE or 1.5.0.RELEASE1.6.31.1.14.4.02.7.30.7.1 Nacos 软件直接上 GitHub 下载即可,项目地址为 Releases · alibaba/nacos (github.com) 下载完毕后解压,进入 bin 目录,然后运行单机模式 1 2 3 4 sh startup.sh -m standalone //Linux bash startup.sh -m standalone //Ubuntu startup.cmd -m standalone //Windows 启动成功后,浏览器输入 http://localhost:8848/nacos 即可打开 Nacos 管理面板,账号和密码默认都是 nacos 搭建 Nacos 客户端 由于 Spring Cloud Alibaba 版本控制器中默认集成了 Nacos,所以在导入时不需要提供版本号 1 2 3 4 5 <!--导入Nacos注册与发现中心--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> 修改 application.yml 配置文件 1 2 3 4 5 6 7 8 9 10 11 12 server: port: 9002 spring: application: name: stock-service #模块名 cloud: nacos: server-addr: 127.0.0.1:8848 #nacos地址 discovery: username: nacos #用户名 password: nacos #密码 namespace: public #命名空间 当有了此配置后,当服务启动时就会自动注册进入 Nacos 注册中心 增加配置类 当有消费者要调用注册中心的服务时,必须配置一个负载均衡策略,Spring Cloud Alibaba 默认的负载均衡器是 Ribbon,只需要在 RestTemplate 的 Bean 上加上 @LoadBalanced 注解即可开启它的负载均衡器 1 2 3 4 5 6 7 8 @Configuration public class RestConfig { @Bean @LoadBalanced public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { return restTemplateBuilder.build(); } } 此时就可以通过服务名调用 Nacos 注册中心中已经注册的服务了 在 controller 中调用注册中心的模块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @RestController @RequestMapping("/order") public class OrderController { @Autowired RestTemplate restTemplate; @GetMapping("add") public String add() { System.out.println("下单成功"); String msg = restTemplate.getForObject("http://stock-service/stock/reduct", String.class); //注意此处不是ip地址,是服务名 return "下单成功<hr/>" + msg; } } 至此,Nacos-Client 环境搭建成功 Nacos 配置参数 Nacos 参数的配置主要在 application.yml 中进行,文档可参考 Nacos-discovery/wiki,主要配置项如下: 配置项Key默认值说明 服务端地址spring.cloud.nacos.discovery.server-addr无Nacos Server 启动监听的ip地址和端口 服务名spring.cloud.nacos.discovery.service${spring.application.name}给当前的服务命名 服务分组spring.cloud.nacos.discovery.groupDEFAULT_GROUP设置服务所处的分组 权重spring.cloud.nacos.discovery.weight1取值范围 1 到 100,数值越大,权重越大 网卡名spring.cloud.nacos.discovery.network-interface无当IP未配置时,注册的IP为此网卡所对应的IP地址,如果此项也未配置,则默认取第一块网卡的地址 注册的IP地址spring.cloud.nacos.discovery.ip无优先级最高 注册的端口spring.cloud.nacos.discovery.port-1默认情况下不用配置,会自动探测 命名空间spring.cloud.nacos.discovery.namespace无常用场景之一是不同环境的注册的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。 AccessKeyspring.cloud.nacos.discovery.access-key无当要上阿里云时,阿里云上面的一个云账号名 SecretKeyspring.cloud.nacos.discovery.secret-key无当要上阿里云时,阿里云上面的一个云账号密码 Metadataspring.cloud.nacos.discovery.metadata无使用Map格式配置,用户可以根据自己的需要自定义一些和服务相关的元数据信息 日志文件名spring.cloud.nacos.discovery.log-name无 集群spring.cloud.nacos.discovery.cluster-nameDEFAULT配置成Nacos集群名称 接入点spring.cloud.nacos.discovery.enpointUTF-8地域的某个服务的入口域名,通过此域名可以动态地拿到服务端地址 是否集成Ribbonribbon.nacos.enabledtrue一般都设置成true即可 是否开启Nacos Watchspring.cloud.nacos.discovery.watch.enabledtrue可以设置成false来关闭 watch Nacos 集群部署 之前使用的 ./startup.sh -m standalone 命令就是以单机模式启动 nacos 服务器。但是当我们用到微服务架构时,往往是项目规模达到了一定的程度。所以在真正使用的时候,大部分情况下都是以集群方式进行部署的,参考文档 下载若干个 nacos 服务器 这里以三个为例,给他们重命名,建议在名字后面加上端口号方便区分: 注意:单机集群启动的时候不能使用连续的端口了,会报错 修改 application.properties 配置文件 在这一步需要配置数据源为 MySQL,因为 Nacos 内部使用的数据源 Hikari。我们要想搭建集群,就必须使用同一数据源(类似于 Scrapy 的分布式爬虫) 如下配置: 1 2 3 4 5 6 7 8 9 10 11 #*************** Config Module Related Configurations ***************# ### If use MySQL as datasource: spring.datasource.platform=mysql ### Count of DB: db.num=1 ### Connect URL of DB: db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC db.user.0=root db.password.0=Pu2003.. 修改三个 nacos 的端口: 1 2 ### Default web server port: server.port=[8840/8850/8860] 修改 cluster.conf.example 先将其复制一份为 cluster.conf,然后再进行修改 主要就是添加 nacos 集群节点 例如: 1 2 3 4 5 #it is ip #example 192.168.0.108:8849 192.168.0.108:8850 192.168.0.108:8851 创建 MySQL 数据库 按照其要求创建 MySQL 数据库,并执行他所提供的 sql 文件(nacos-mysql.sql) 执行完之后表结构大致如下: 修改项目 application.yml 文件 官方推荐集群部署后使用VIP或者域名访问,不过也可以使用直连模式: 1 2 3 4 spring: cloud: nacos: server-addr: 127.0.0.1:8840, 127.0.0.1:8850, 127.0.0.1:8860 至此,就实现了 Nacos 的集群部署。接下来,我们也可以使用负载均衡软件如 Nginx 来进行集群的负载均衡。 使用 nginx 进行反向代理 修改 /etc/nginx/nginx.conf 文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # Nacos upstream nacoscluster { server 127.0.0.1:8840; server 127.0.0.1:8850; server 127.0.0.1:8860; } server { listen8847; server_namelocalhost; location /nacos/{ proxy_passhttp://nacoscluster/nacos/; } } 启动所有 nacos 服务器,并启动 nginx,理论上打开浏览器 http://localhost:8847/nacos 登录后即可看到如下效果: nginx 默认的负载均衡机制是轮询方式,所以理论上请求时就会在三台服务器之间来回跳 至此,Nacos 集群环境就搭建好了,修改项目的 Nacos 地址配置文件: 1 2 3 4 spring: cloud: nacos: server-addr: 192.168.220.1:8847 启动对应服务,OK,日常报错: 查看官方文档,发现 2.x 版本和 1.x 版本还不太一样: Nacos2.0版本相比1.X新增了gRPC的通信方式,因此需要增加2个端口。新增端口是在配置的主端口(server.port)基础上,进行一定偏移量自动生成。 端口与主端口的偏移量描述 98471000客户端gRPC请求服务端端口,用于客户端向服务端发起连接和请求 98481001服务端gRPC请求服务端端口,用于服务间同步等 使用VIP/nginx请求时,需要配置成TCP转发,不能配置http2转发,否则连接会被nginx断开。 (图片来源于网络,侵删) 注意:使用 Nginx 配置 tcp 转发时需要用到 stream 模块,而 Nginx 在安装时是默认不编译 stream 模块的,需要我们手动安装 成功安装之后查看: 接下来配置 nginx 的 tcp 转发: 修改 nginx.conf,在最后一行加上 include /usr/local/nginx/tcp.d/*.conf;,注意不要写在 http 里 新建 /usr/local/nginx/tcp.d/tcp.conf 编辑 tcp.conf 文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # tcp.conf stream { # Nacos集群配置grpc upstream nacoscluster { server 127.0.0.1:9840; server 127.0.0.1:9850; server 127.0.0.1:9860; } server { listen9847; proxy_passnacoscluster; } } 修改项目的 Nacos 地址配置文件: 1 2 3 4 spring: cloud: nacos: server-addr: 192.168.220.1:8847 启动服务,完成!

2022/8/20
articleCard.readMore

Spring Cloud Alibaba 环境搭建

微服务概述 微服务(Microservices)是一种软件架构风格,它是以专注于单一责任与功能的小型功能区块(Small Building Blocks)为基础,利用模块化的方式组合出复杂的大型应用程序,各功能区块使用与语言无关 (Language-Independent/Language agnostic)的API集相互通信。 简单来说,就是将一整套服务流程拆分成更小的模块,每个模块各司其职。各个模块之间通过 HTTP API 进行通信。 Spring Cloud 概述 Spring Cloud 为开发者提供了工具来快速构建分布式系统中的一些常见模式(例如配置管理、服务注册与发现、熔断器、智能路由等)。Spring Cloud 可以使开发人员更快速地开发实现这些模式的服务和应用程序。它们在任何分布式环境中都能很好地工作,包括开发人员自己的笔记本电脑、裸机数据中心以及 Cloud Foundry 等托管平台。 简单来说,Spring Cloud 是 Spring 为微服务架构提供的一整套的解决方案。而 Spring Cloud Alibaba 是国内阿里巴巴团队自研发的一套解决方案。 分布式环境搭建 说明:可以手动从一个 Maven 项目进行搭建,后期也可以利用 Idea 工具直接从阿里云官网进行快捷搭建,网址为Aliyun Java Initializr,类似于 SpringBoot 的初始化向导 Spring Initializr 创建父级 Spring Boot 项目 pom.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <!--parent中使用的版本管理器一般是自己公司内部的版本管理器,对于第三方的版本管理器一般放在dependencyManagement中--> <groupId>com.pushihao</groupId> <artifactId>test</artifactId> <version>0.0.1-SNAPSHOT</version> <name>test</name> <description>test</description> <properties> <java.version>11</java.version> <spring.boot.version>2.6.7</spring.boot.version> </properties> <dependencyManagement> <dependencies> <!--spring-boot版本管理器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>${spring.boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 新建两个模块 新建子模块时最好新建 Maven 项目,因为可以设置父项目。如果新建 Spring Boot Initializr 则默认父项目是 spring-boot-starter-parent 这里以订单模块(order)和仓库模块(stock)为例 假设仓库模块为生产者,订单模块为调用者。当调用订单模块时,订单模块调用仓库模块,使库存减一 项目结构如下: 具体文件: pom.xml (两个模块的 pom.xml 几乎相同) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>SpringCloud</artifactId> <groupId>com.pushihao</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <!--此处为stock或order(模块名)--> <artifactId>stock</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project> StockApplication.java 1 2 3 4 5 6 7 8 9 10 11 package com.pushihao; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class StockApplication { public static void main(String[] args) { SpringApplication.run(StockApplication.class, args); } } StockController.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.pushihao.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/stock") public class StockController { @GetMapping("reduct") public String reduct() { System.out.println("库存减一"); return "success!"; } } OrderApplication.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.pushihao; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder.build(); } } OrderController.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.pushihao.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController @RequestMapping("/order") public class OrderController { @Autowired private RestTemplate restTemplate; @GetMapping("add") public String add() { System.out.println("订单加一"); //result为返回结果 String result = restTemplate.getForObject("http://localhost:9001/stock/reduct", String.class); return "success!"; } } 至此,一个简单的分布式环境就搭建好了,使用浏览器调用 http://localhost:9002/order/add 就可以看到结果 Spring Cloud Alibaba 环境搭建 可以直接在原有的分布式环境上直接引用 Spring Cloud Alibaba 即可 项目结构如下: 导入 Spring Cloud Alibaba 和 Spring Cloud 的坐标 注意:版本号一定要选对(按照要求)参考 版本说明 稳定版本依赖关系 Spring Cloud Alibaba VersionSpring Cloud VersionSpring Boot Version 2021.0.1.0Spring Cloud 2021.0.12.6.3 2.2.7.RELEASESpring Cloud Hoxton.SR122.3.12.RELEASE 2021.1Spring Cloud 2020.0.12.4.2 2.2.6.RELEASESpring Cloud Hoxton.SR92.3.2.RELEASE 2.1.4.RELEASESpring Cloud Greenwich.SR62.1.13.RELEASE 2.2.1.RELEASESpring Cloud Hoxton.SR32.2.5.RELEASE 2.2.0.RELEASESpring Cloud Hoxton.RELEASE2.2.X.RELEASE 2.1.2.RELEASESpring Cloud Greenwich2.1.X.RELEASE 2.0.4.RELEASE(停止维护,建议升级)Spring Cloud Finchley2.0.X.RELEASE 1.5.1.RELEASE(停止维护,建议升级)Spring Cloud Edgware1.5.X.RELEASE 组件版本关系(一般由Spring Cloud Alibaba 版本管理器直接控制,我们不用关心) Spring Cloud Alibaba VersionSentinel VersionNacos VersionRocketMQ VersionDubbo VersionSeata Version 2021.0.1.0*1.8.31.4.24.9.22.7.151.4.2 2.2.7.RELEASE1.8.12.0.34.6.12.7.131.3.0 2.2.6.RELEASE1.8.11.4.24.4.02.7.81.3.0 2021.1 or 2.2.5.RELEASE or 2.1.4.RELEASE or 2.0.4.RELEASE1.8.01.4.14.4.02.7.81.3.0 2.2.3.RELEASE or 2.1.3.RELEASE or 2.0.3.RELEASE1.8.01.3.34.4.02.7.81.3.0 2.2.1.RELEASE or 2.1.2.RELEASE or 2.0.2.RELEASE1.7.11.2.14.4.02.7.61.2.0 2.2.0.RELEASE1.7.11.1.44.4.02.7.4.11.0.0 2.1.1.RELEASE or 2.0.1.RELEASE or 1.5.1.RELEASE1.7.01.1.44.4.02.7.30.9.0 2.1.0.RELEASE or 2.0.0.RELEASE or 1.5.0.RELEASE1.6.31.1.14.4.02.7.30.7.1 这里使用最新稳定版即可 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <!--parent中使用的版本管理器一般是自己公司内部的版本管理器,对于第三方的版本管理器一般放在dependencyManagement中--> <groupId>com.pushihao</groupId> <artifactId>test</artifactId> <packaging>pom</packaging> <version>0.0.1-SNAPSHOT</version> <modules> <module>order</module> <module>stock</module> <module>order-nacos</module> <module>stock-nacos</module> </modules> <name>test</name> <description>test</description> <properties> <java.version>11</java.version> <spring.boot.version>2.3.12.RELEASE</spring.boot.version> <spring.cloud.version>Hoxton.SR12</spring.cloud.version> <spring.cloud.alibaba.version>2.2.7.RELEASE</spring.cloud.alibaba.version> </properties> <dependencyManagement> <dependencies> <!--spring-boot版本管理器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>${spring.boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!--spring-cloud版本管理器--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring.cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!--spring-cloud-alibaba版本管理器--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring.cloud.alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 新建 stock-nacos 模块 pom.xml 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 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>SpringCloud</artifactId> <groupId>com.pushihao</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>stock-nacos</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--注册与发现--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>11</source> <target>11</target> </configuration> </plugin> </plugins> </build> </project> application.yml 1 2 3 4 5 6 7 8 9 10 11 12 server: port: 9001 spring: application: name: stock-service cloud: nacos: server-addr: 127.0.0.1:8848 discovery: username: nacos password: nacos cluster-name: public StockNacosApplication.java 1 2 3 4 5 6 7 8 9 10 11 package com.pushihao; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class StockNacosApplication { public static void main(String[] args) { SpringApplication.run(StockNacosApplication.class, args); } } StockController.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.pushihao.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/stock") public class StockController { @GetMapping("reduct") public String reduct() { System.out.println("库存减一"); return "success!"; } } 新建 order-nacos 模块 pom.xml 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 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>test</artifactId> <groupId>com.pushihao</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>order-nacos</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>11</source> <target>11</target> </configuration> </plugin> </plugins> </build> </project> application.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 server: port: 9001 spring: application: name: order-service cloud: nacos: server-addr: 127.0.0.1:8848 discovery: username: nacos password: nacos namespace: public # ephemeral: false #是否是临时实例 默认是true(临时实例) 永久实例:哪怕宕机了也不会删除实例 OrderNacosApplication.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.pushihao; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class OrderNacosApplication { public static void main(String[] args) { SpringApplication.run(OrderNacosApplication.class, args); } //加上@LoadBalanced就配上了默认的负载均衡器Ribbon @Bean @LoadBalanced public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { return restTemplateBuilder.build(); } } OrderController.java 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 package com.pushihao.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController @RequestMapping("/order") public class OrderController { @Autowired RestTemplate restTemplate; @GetMapping("add") public String add() { System.out.println("下单成功"); //这里就可以把域名替换成对应的服务名,调用时就会启用默认的负载均衡机制 String msg = restTemplate.getForObject("http://stock-service/stock/reduct", String.class); return "success!"; } } 至此 Spring Cloud Alibaba 环境就搭建完毕了 依次启动 nacos 服务器、stock-nacos、order-nacos 浏览器输入 http://localhost:8848/nacos 在服务管理一栏即可查看注册的微服务 浏览器输入 http://localhost:9001/order/add 即可查看运行结果 成功! 使用 Aliyun Java Initializr 快速构建 可以直接在网页上 Aliyun Java Initializr 进行配置,然后下载初始代码并导入编辑器工具中 不过更多情况下,都是使用 Idea 工具进行快速构建,如下:

2022/8/19
articleCard.readMore

什么是RSS?什么是Feed?它们有什么关系?

什么是RSS? RSS(英文全称:RDF Site Summary 或 Really Simple Syndication),可以翻译为“简易信息聚合”或“聚合内容”,是一种消息来源格式规范,用以聚合多个网站更新的内容并自动通知网站订阅者。将文章标题、摘要、内容按照用户需求推送给用户便是RSS的目的。 RSS 的第一个版本为 RDF Site Summary,在1999年三月由美国网景公司的 Guha 为了用在My.Netscape.Com 门户网站而开发。在 Netscape 放弃该标准后,软件制造商 UserLand 开始着手开发。随着 2002 年 RSS 2.0.1 规范的发布,UserLand 冻结了该标准并将版权转让给哈佛大学的伯克曼互联网与社会中心。Web社区对进一步发展的渴望导致了另一种称为 Atom 的联合标准的创建。 总结:RSS 和 Atom 其实就是一种提供消息来源的格式规范。Atom 是在 RSS 的基础上发展而来的,弥补了 RSS 的一些不足。他们本质上都是供机器阅读的 xml 文件,订阅者可以通过 RSS 阅读器阅读文章内容。 什么是Feed?它和RSS有什么关系? Feed(英语:web feed、news feed、syndicated feed),中文可以译为消息来源,是一种资料格式,网站可以透过它将最新文章或者消息传播给订阅者。常用的 Feed 格式有 RSS、Atom 两种,有些网站也会同时提供两种格式的订阅链接。Feed常常被博客以及新闻网站广泛应用。普通用户可以通过客户端软件订阅Feed地址,这类软件一般被称为聚合器、RSS阅读器、Feed阅读器等。 网站中通常以或图标为 Feed 地址标识。 总结:Feed 是一种资料格式,常用的有 RSS 和 Atom 两种规范。用户可以通过阅读器软件订阅 Feed 获取网站的最新消息。

2022/8/18
articleCard.readMore

Docker基本使用

什么是 docker 作为一个开源的应用容器引擎,docker主要用于开发和运行应用。docker容器和虚拟机有些类似,但二者在原理上不同。容器是将操作系统层虚拟化,而虚拟机则是虚拟化硬件。因此容器相较于虚拟机来说更加便携、轻量以及高效。 所以我们可以简单的将docker容器理解为一个轻量版的虚拟机。在docker之上我们可以高效地运行各式各样的应用,应用之间相互独立而又可以互相协调。 docker组成 其中,容器和镜像的概念一定要分清。他俩的关系就像是Java中的类与对象。镜像是一个只读模板,用于指示创建容器,而容器是镜像的可运行实例。一个镜像也可以创建很多个容器,这些容器可以相互独立运行。 Docker注册中心(Docker registry)也被称为Docker仓库,主要用于存储docker镜像。Docker Hub是一个公共的注册中心,任何人都可以在此处下载镜像。 docker基本常用命令 镜像相关 检索镜像 1 docker search [关键字] 获取镜像(默认从Docker Hub下载) 1 docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签] 列出镜像 1 2 docker image ls docker images 导入镜像 1 2 3 docker load [文件] # 举例:docker load < image.tar 导出镜像 1 2 3 4 docker save [镜像名/镜像ID] [输出] # 举例:docker save 0fdf2b4c26d3 > image.tar # 也可以将多个image打包成一个文件:docker save -o images.tar postgres:9.6 mongo:3.4 删除镜像 1 docker rmi [镜像ID] 容器相关 启动容器 1 2 3 4 5 # 创建并启动容器 docker run [选项] [镜像名/镜像ID[:TAG]] [命令] [参数] # 启动目前终止的容器 docker start [容器名/容器ID] docker run常用选项: -d: 后台运行容器 -i: 以交互模式运行容器,通常与 -t 同时使用; -P: 随机端口映射,容器内部端口随机映射到主机的端口 -p: 指定端口映射,格式为:主机(宿主)端口:容器端口 -t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用 –name: 为容器指定一个名称 -e: 设置环境变量 –env-file: 从指定文件读入环境变量 –net: 指定容器的网络连接类型 –link: 添加链接到另一个容器 –expose: 开放一个端口或一组端口 –volume, -v: 绑定一个卷,将本地文件夹与容器文件夹共享 举例: 1 2 3 docker run -it -p 80:80 -p 443:443 --name lnmp -v ~/docker/lnmp/data:/root/data centos:7 /bin/bash # 以bash为交互方式运行一个centos7容器,放行80和443端口,取名为lnmp,并将本机(宿主机)的 ~/docker/lnmp/data 目录与容器内的 /root/data 目录绑定 查看容器 1 2 3 4 5 # 查看正在运行的容器 docker ps # 查看所有容器 docker ps -a 终止容器 1 docker stop [容器名/容器ID] 重启容器 1 docker restart [容器名/容器ID] 进入容器 1 2 3 4 5 # 退出后容器终止 docker attach [容器名/容器ID] # 退出后容器正常运行 docker exec -it [容器名/容器ID] /bin/bash 导入容器 1 2 3 docker import [选项] [文件路径/URL] [REPOSITORY[:TAG]] # 举例:docker import ./myredis.tar.gz myredis:1 导出容器 1 2 3 docker export [容器名/容器ID] [输出] # 举例:docker export myredis > myredis.tar.gz 注:用户既可以使用 docker load 来导入镜像存储文件到本地镜像库,也可以使用 docker import 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。 删除容器 1 docker rm [容器ID/容器名] 复制文件 1 2 3 4 # 从主机复制到容器 docker cp [主机路径] [容器ID]:[容器路径] # 从容器复制到主机 docker cp [容器ID]:[容器路径] [主机路径] 参考: 【1】:Docker - 维基百科 【2】:Docker 教程 | 菜鸟教程 【3】:Docker - 从入门到实践 【4】:Docker Engine overview | Docker Documentation 【5】:Docker - 实现本地镜像的导出、导入(export、import、save、load) 【6】:一张脑图整理Docker常用命令 - 腾讯云开发者社区

2022/8/15
articleCard.readMore

TensorFlow启用GPU加速

前言 据官网介绍,对于 tensorflow1.15 及更早版本,CPU和GPU软件包是分开的。 1 2 pip install tensorflow==1.15 # CPU pip install tensorflow-gpu==1.15 # GPU 而之后的版本,我们可以直接在CPU版本的基础上开启GPU加速。 参考:GPU支持 | TensorFlow (tensorflow官方网站,国内访问可能较慢) 安装TensorFlow最新版 1 pip install tensorflow 如果嫌弃安装速度太慢,也可以使用国内豆瓣源 1 pip install tensorflow -i https://pypi.doubanio.com/simple/ 启用GPU加速 前提 硬件要求 支持以下带有 GPU 的设备: CUDA® 架构为 3.5、5.0、6.0、7.0、7.5、8.0 或更高的 NVIDIA® GPU 卡。请参阅支持 CUDA® 的 GPU 卡列表。 如果 GPU 采用的 CUDA® 架构不受支持,或为了避免从 PTX 进行 JIT 编译,亦或是为了使用不同版本的 NVIDIA® 库,请参阅在 Linux 下从源代码编译指南。 软件包不包含 PTX 代码,但最新支持的 CUDA® 架构除外;因此,如果设置了 CUDA_FORCE_PTX_JIT=1,TensorFlow 将无法在旧款 GPU 上加载。(有关详细信息,请参阅应用兼容性。) 软件要求 必须在系统中安装以下 NVIDIA® 软件: NVIDIA® GPU 驱动程序 - CUDA® 11.2 要求 450.80.02 或更高版本。 CUDA® 工具包:TensorFlow 支持 CUDA® 11.2(TensorFlow 2.5.0 及更高版本) CUDA® 工具包附带的 CUPTI。 cuDNN SDK 8.1.0 cuDNN 版本。 (可选)TensorRT 6.0,可缩短用某些模型进行推断的延迟时间并提高吞吐量。 下载相应软件 cuda: https://developer.nvidia.com/cuda-downloads?target_os=Windows&target_arch=x86_64&target_version=10&target_type=exe_local cuDNN: https://developer.nvidia.com/rdp/cudnn-archive 注:cuDNN下载需要登录Nvidia账号,如果嫌弃官网下载太慢,可以直接从我的百度网盘下载: 链接:https://pan.baidu.com/s/1Aw9mvoeEcOhsJ0qphJ_58Q 提取码:1234 配置环境 不知道如何配置Path环境变量?参考:草履虫都能看懂的系统环境变量配置 - IT-Small-White 将下载好的cudnn压缩包进行解压,备用 安装cuda(安装过程中一定要记下安装路径) 配置环境变量Path,添加如下内容: 1 2 3 4 [你的cuda安装路径]\bin [你的cuda安装路径]\extras\CUPTI\lib64 [你的cuda安装路径]\include [你的cudnn解压路径]\bin 至此,安装启用完毕 测试 1 2 import tensorflow as tf print(tf.config.list_physical_devices('GPU')) 输出结果如下,表明安装成功 后记 使用GPU其实并不一定比CPU更快,这个要看网络结构大小。网络结构比较小的时候,CPU与GPU进行数据传输过程耗时很大,这个时候其实只使用CPU会更快。网络结构比较庞大的时候,GPU的提速效果就比较明显了。

2022/7/7
articleCard.readMore

如何进行内网穿透

什么是内网穿透 百度百科:内网穿透,也即 NAT 穿透,进行 NAT 穿透是为了使具有某一个特定源 IP 地址和源端口号的数据包不被 NAT 设备屏蔽而正确路由到内网主机。 维基百科:在电脑科学中,NAT穿越(NAT traversal)涉及 TCP/IP 网络中的一个常见问题,即在处于使用了NAT装置的私有 TCP/IP 网络中的主机之间建立连接的问题。 我的理解:内网穿透其实就是把局域网内的资源或服务映射到公网,从而达到通过互联网访问内网的效果。 根据我的理解,可以做出如下图: 因为内网穿透服务器是暴露在互联网(公网)的,所以它必须拥有公网IP地址。用户服务器既可以是公司内部的服务器集群,也可以是PC个人电脑。 一些常见的内网穿透工具的使用 Ngrok 官方文档:https://ngrok.com/docs ngrok是一款非常便捷简单的内网穿透工具。内网穿透服务器由他们来提供,我们只需要指定要穿透的端口即可实现一键内网穿透 下载安装 从官方下载地址下载对应版本的ngrok 下载好后进行解压,会得到如下文件(先别急着运行) 注册一个账号,注册地址:https://dashboard.ngrok.com/get-started/setup 登录,查看自己的token 在刚刚文件的解压目录下打开cmd窗口 至此,安装配置完成 使用 在刚刚文件的解压目录下打开cmd窗口,输入ngrok http [要穿透的端口号]回车即可 红框圈住的即为穿透后的地址,通过互联网就可以访问。需要注意的是,免费版的分配的域名地址是随机的,每次穿透可能结果都不一样。 Frp 官方文档:https://gofrp.org/docs/ Frp是GitHub上的一个开源项目,使用go语言编写,速度很快,支持点对点内网穿透,功能非常强大 先决条件 一台具有公网IP的服务器 知道一丁点Linux相关知识 下载 在Release v0.43.0 · fatedier/frp · GitHub下载对应版本的Frp。内网穿透服务器和本机服务器都要由一份。 使用frp实现http内网穿透 为了方面描述,内网穿透服务器简称为服务器,要穿透的本机服务器简称为本机 服务器端配置 打开服务器端 frps.ini 配置文件,修改如下 1 2 3 [common] bind_port = 7000 vhost_http_port = 8080 放行 7000 端口和 8080 端口 运行命令 1 ./frps -c frps.ini 本机配置 打开本机 frpc.ini 配置文件,修改如下 1 2 3 4 5 6 7 8 [common] server_addr = [服务器ip地址] server_port = 7000 [web] type = http local_port = [要代理的端口号] custom_domains = [域名,没有解析域名的话可以填ip地址] 运行命令 1 ./frpc.exe -c ./frpc.ini 访问 域名.8080 即可看到本机的4000端口对应的资源/服务

2022/7/1
articleCard.readMore

Hello World!

什么是 “Hello, World!” “Hello, World!” 程序是编程世界的第一个仪式。 当你在新语言中写下这行代码并看到它成功输出时,就完成了与这门语言最初的握手。它简单到近乎平淡,却又至关重要——既是初学者验证语法规则的入门练习,也是开发者确认环境配置无误的快速检查。 这个传统始于1972年贝尔实验室的《A Tutorial Introduction to the Language B》,如今已成为跨越所有编程语言的共同起点。它不只是一段代码,更像是程序员之间的默契:如果连 “Hello, World!” 都跑不通,那更复杂的挑战也不必继续了。 “Hello, World!” 的 N 种写法 Ada 1 2 3 4 5 6 with Ada.Text_IO; use Ada.Text_IO; procedure Hello is begin Put_Line ("Hello, World!"); end Hello; ALGOL60 1 BEGIN DISPLAY("Hello, World!") END. ALGOL68 1 2 3 begin printf(($gl$,"Hello, World!")) end AppleScript 1 say "Hello, World!" 1 display alert "Hello, World!" Assembly Language (Linux x64) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 section .text global _start _start: ; sys_write(1, msg, len) mov rax, 1 mov rdi, 1 lea rsi, [rel msg] mov rdx, len syscall ; sys_exit(0) mov rax, 60 xor rdi, rdi syscall section .rodata msg: db "Hello, World!", 0xA len: equ $ - msg BASIC 1 PRINT "Hello, World!" Batchfile 1 2 @echo off echo Hello, World! Bash 1 echo "Hello, World!" C 1 2 3 4 5 6 #include <stdio.h> int main() { printf("Hello, World!"); return 0; } C++ 1 2 3 4 5 6 #include <iostream> int main() { std::cout << "Hello, World!" << std::endl; return 0; } c# 1 Console.WriteLine("Hello, World!"); Clojure 1 (println "Hello, World!") COBOL 1 2 3 4 5 6 IDENTIFICATION DIVISION. PROGRAM-ID. HELLO-WORLD. * simple hello world program PROCEDURE DIVISION. DISPLAY 'Hello, World!'. STOP RUN. D 1 2 3 4 5 import std.stdio; void main() { writeln("Hello, World!"); } Dart 1 2 3 void main() { print('Hello, World!'); } Elixir 1 IO.puts("Hello, World!") Ezhil 1 2 3 பதிப்பி "உலகே வணக்கம்" பதிப்பி "Hello, World!" exit() Forth 1 ." Hello, World!" CR Fortran 1 2 3 program Hello print *, "Hello, World!" end program Hello Go 1 2 3 4 5 6 package main import "fmt" func main() { fmt.Println("Hello, World!") } Haskell 1 2 main :: IO () main = putStrLn "Hello, World!" Java 1 2 3 4 5 public class Main { public static void main(String[] args) { System.out.println("Hello, World!"); } } JavaScript 1 console.log("Hello, World!"); 1 document.write("Hello, World!"); Julia 1 println("Hello, World!") Kotlin 1 2 3 fun main() { println("Hello, World!") } Lisp 1 (print "Hello, World!") Logo 1 print [Hello, World!] Lua 1 print("Hello, World!") Objective-C 1 2 3 4 5 #import <stdio.h> int main() { printf("Hello, World!\n"); } 1 2 3 4 5 6 7 8 #import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"Hello, World!"); } return 0; } OCaml 1 print_endline "Hello, World!" Pascal 1 2 3 4 program Hello; begin writeln ('Hello, World!'); end. Perl 1 print "Hello, World!\n"; Perl6 1 say 'Hello, World!' PHP 1 2 3 <?php echo 'Hello, World!'; ?> PowerShell 1 'Hello, World!' Prolog 1 main() :- write("Hello, World!"), nl. Python2 1 print "Hello, World!" Python3 1 print("Hello, World!") R 1 print("Hello, World!") Racket 1 2 #lang cli (displayln "Hello, World!") Ruby 1 puts "Hello, World!" Rust 1 2 3 fn main() { println!("Hello, World!"); } Simula 1 2 3 4 Begin OutText ("Hello, World!"); Outimage; End; Smalltalk 1 Transcript show: 'Hello, World!'. Standard ML 1 print "Hello, World!\n" Swift 1 print("Hello, World!") Tcl 1 puts "Hello, World!" TI-BASIC 1 :Disp "Hello, World!" VBScript 1 WScript.Echo "Hello, World!" WebAssembly Text Format 1 2 3 4 5 6 7 (module (import "console" "log" (func $log (param i32))) (memory 1) (data (i32.const 0) "Hello, World!\00") (func (export "hello") (call $log (i32.const 0)) ) 易语言 1 信息框(“Hello, World!”,0,,)

2022/7/1
articleCard.readMore

Git基本使用

前言 曾经稍微学习过一点git,对git稍微有一点认识,但也仅仅是知道他是干嘛的。本笔记主要用来记录一下git的常用命令和功能以及我的一些个人理解。 什么是git 对于什么是git,官方文档是这样介绍它的: Git is a fast, scalable, distributed revision control system with an unusually rich command set that provides both high-level operations and full access to internals. 总结下来,git是一个可扩展的、分布式的版本控制系统。 安装git windowsLinux 通过官方下载网站下载相应安装包正常安装,当鼠标右键出现Git Bash Here时即为安装成功 对于Debian/Ubuntu等一些常见的Linux发行版,可以直接通过默认的包管理工具进行安装,参照官方Linux安装指南 也可以通过源码进行安装,源码安装方式如下: 在该镜像网站下载对应版本的git源码,然后./configure prefix=/usr/local/git/,然后make && make install,最后将git指令添加到环境变量即可 安装完git之后,必须配置用户名和邮件,配置方式如下(二选一即可): 使用命令行 1 2 git config --global user.name "Your Name" git config --global user.email "Your Email" 编辑 ~/.gitconfig 或者 /etc/gitconfig 文件 1 2 3 [user] name = Your Name email = Your Email 创建git仓库 进入到要创建仓库的目录下 输入git init即可创建一个空仓库 输入git add [文件名]即可把文件写入暂存区(文件名支持通配符,如*.cpp) 输入git commit -m [提交原因]即可把暂存区修改写入当前分支 注:可以add多次后进行commit,一次commit即可把暂存区全部修改提交到当前分支版本库 版本控制 状态查看 git status命令可以查看工作区的状态,也可以查看哪些文件被修改了 git diff命令可以查看文件修改的具体内容,但是无法查看二进制文件如word文档,图片等的修改内容 版本回退 git log命令可以查看提交历史commit id(版本号)、作者、邮箱、日期以及commit的原因,如果只想查看版本号以及commit原因,可以加上一个参数,使用如下命令git log --pretty=online,常用于撤销操作 git reflog命令可以查看命令历史的commit id等相关信息,常用于撤销后的重做操作 git reset --hard commit-id命令可以使文件在不同版本之间跳转,需要注意的是,在git中 HEAD 表示当前版本,那么 HEAD^ 就表示上一个版本,HEAD^^ 就表示上上一个版本,如git reset --hard HEAD^就表示回到上一个版本 撤销修改 git checkout -- [文件名]命令可以丢弃工作区的修改,使用此命令会产生两种情况 该文件自修改后还未放到暂存区,那么执行此命令后该文件就回到了当前版本库(最后一次commit)的状态 该文件已经添加到了暂存区,那么执行此命令后文件就回到了暂存区(最后一次add)的状态 小提示:此命令也可用于在工作区误删文件后的文件恢复 git restore --worktree [文件名]命令可以将文件回到暂存区状态 git reset HEAD [文件名]命令清除所有暂存区的内容,对工作区没有影响 删除文件 git rm [文件名]命令可以将文件从版本库中删除 分支管理 一个分支下的修改不会影响到其他分支,所以往往使用一个dev分支做具体项目开发,该分支下又有多个子分支来实现不同的功能和细节,每当有一个具体的新版本发布,就将dev分支合并到master分支。master分支往往不会进行具体的操作,只是负责发布稳定版本 git branch命令可以查看当前分支 git checkout -b [分支名]创建并切换到新分支 = git branch [分支名]创建新分支 + git checkout [分支名]切换到一个已有分支 注意:此命令与上述撤销修改命令有些相似,可以会难以区分,推荐使用下述命令 git switch -c [分支名]创建并切换到新分支 git switch [分支名]切换到一个已有分支 git merge [分支名]把指定分支名的修改合并到当前分支,默认以Fast-forword模式合并 git branch -d [分支名]删除分支 git merge --no-ff -m [提交原因] [分支名]以普通模式将dev合并到当前分支,并进行一次提交 标签管理 标签其实就是给某次commit打上一个记号,常用于版本发布时的版本号。有点类似于给commit id起个名字 git tag查看所有标签 git tag [标签名] ([commit id])给指定commit打上一个标签,默认是HEAD git tag -a [标签名] -m [描述信息] ([commit id])打上一个标签,并添加描述信息 git show [标签名]查看标签信息 git tag -d [标签名]可以删除一个标签 远程仓库 远程仓库就是一个搭建在服务器上的git仓库。通过git工具,我们可以很方便的将远程仓库的内容拉取到本地,也可以将本地的内容推送到远程仓库 ssh-keygen -t rsa -C [邮箱地址]输入命令后一直回车即可。此命令可以生成一个rsa公钥,默认存放在 C:/user/用户名/.ssh 目录下,id_rsa是密钥,id_rsa.pub是公钥。将公钥放到远程仓库的 SSH Keys 中即可实现对远程私有仓库的读写,类似于一个身份证 git clone [仓库地址]将远程仓库克隆到本地,仓库地址有以git开头的ssh协议和以http/https开头的http/https协议以及一些其他的如github-cli协议 git remote add [仓库名称] [仓库地址]添加远程仓库,仓库名称通常命名为origin,这是默认的叫法 git push -u origin main把本地的master分支推送到远程的origin仓库,只需要在第一次加上 -u 参数 git push origin [标签名]将一个本地标签推送到远程服务器 git push origin --tags将所有标签推送到远程服务器 git push origin :refs/tags/[标签名]删除一个远程标签 git fetch [仓库地址] ([分支名])将远程仓库的指定分支取回到本地,不填分支名则默认是所有分支 git pull从远程仓库获取最新版本并合并到本地 = git fetch + git merge 实战:拉取远程仓库的dev分支并关联到本地dev分支 1 2 3 4 5 6 7 8 # 新建本地仓库 git init # 添加远程仓库 git remote add origin https://github.com/itgrape/study.git # 拉取远程指定分支 git fetch origin dev # 新建本地dev分支并关联到远程dev分支 git switch -c dev origin/dev git子模块的使用 有种情况我们经常会遇到:某个工作中的项目需要包含并使用另一个项目。 也许是第三方库,或者你独立开发的,用于多个父项目的库。 现在问题来了:你想要把它们当做两个独立的项目,同时又想在一个项目中使用另一个 git 子模块就是在一个 git 仓库中嵌套另一个/多个 git 仓库,有点像套娃。我们可以对每一个子模块单独进行管理 添加子模块 git submodule add [仓库地址] [存放路径] 在父项目中添加一个子模块 git submodule 查看所有子模块 git submodule update --remote [子模块名] 更新指定子模块 git submodule update --remote 更新所有子模块 git push origin HEAD:master 子模块向远程仓库提交修改 克隆一个含有子模块的项目 方法一:先克隆父项目,然后克隆子项目 git clone [父项目地址] 克隆父项目 git submodule init 初始化本地子模块配置文件 git submodule update 从该项目中抓取所有数据并检出父项目中列出的合适的提交 方法一简化:命令合二为一 git submodule update --init = git submodule init + git submodule update 如果还要初始化、抓取并检出任何嵌套的子模块,可以使用 git submodule update --init --recursive 方法二:递归克隆父项目和子项目 git clone --recurse-submodules [父项目地址] 自动初始化并更新仓库中的每一个子模块, 包括可能存在的嵌套子模块 删除子模块 删除子模块文件夹 rm -rf [子模块名] 删除 .gitmodules 文件中的子模块信息 删除 .git/config 文件中的子模块信息 删除 .git/module/ 目录下的子模块的目录 rm .git/module/[子模块名] 清除子模块缓存 git rm --cached [子模块名] 参考资料 【1】:Git-Reference 【2】:廖雪峰的官方网站 【3】:阮一峰的网络日志

2022/6/27
articleCard.readMore

Butterfly常用标签外挂

前言 我也是最近刚了解到Hexo的标签外挂,同时也觉得他很方便。于是想写一篇笔记记录一下它的基本语法,看了一些网上的教程,结果hexo g的时候控制台一阵爆红。果然,看教程还是看官方文档比较靠谱。 本文参考:標籤外掛(Tag Plugins) 什么是标签外挂? 标签外挂也叫外挂标签,我的理解就是Hexo对于markdown的扩展语法(其实就是通过CSS代码修改文章特定语法的文字的样式),它是Hexo博客框架所独有的。通过使用标签外挂,可以让markdown语法具备更强的表达能力。一般不同的主题或者CSS样式提供的标签外挂语法也是不一样的。我这里介绍的主要是Butterfly主题所提供的标签外挂语法。 常用标签外挂语法 分区标签tabs 代码展示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 {% tabs Tags %} <!-- tab 标题1 --> 内容1 <!-- endtab --> <!-- tab 标题2 --> 内容2 {% tabs Tags %} <!-- tab 标题2.1 --> 内容2.1 <!-- endtab --> <!-- tab 标题2.2 --> 内容2.2 <!-- endtab --> {% endtabs %} <!-- endtab --> {% endtabs %} 效果展示: 标题1标题2 内容1 内容2 标题2.1标题2.2 内容2.1 内容2.2 时间轴标签timeline 代码展示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 {% timeline 2020, green %} <!-- timeline 09 --> 计算机小萌新 <!-- endtimeline --> {% endtimeline %} {% timeline 2021, green %} <!-- timeline 03 --> 尝试上云 <!-- endtimeline --> <!-- timeline 09 --> 第一次搭建个人博客 <!-- endtimeline --> {% endtimeline %} 效果展示: 2020 09 计算机小萌新 2021 03 尝试上云 09 第一次搭建个人博客 相册图库gallery 代码展示: 1 2 3 <div class="gallery-group-main"> {% galleryGroup '壁纸' '收藏的一些壁纸' '/gallery/wallpaper' https://img.pushihao.com/typora/202505281847210.png %} </div> 效果展示: 壁纸 收藏的一些壁纸 相册gallery 代码展示: 1 2 3 4 5 6 7 8 9 10 {% gallery %} ![Fze9jchtnyJXMHN.jpg](https://img.pushihao.com/typora/202505281852188.jpg) ![ryLVePaqkYm4TEK.jpg](https://img.pushihao.com/typora/202505290840741.jpg) ![gEy5Zc1Ai6VuO4N.jpg](https://img.pushihao.com/typora/202505290840937.jpg) ![d6QHbytlSYO4FBG.jpg](https://img.pushihao.com/typora/202505290840435.jpg) ![6nepIJ1xTgufatZ.jpg](https://img.pushihao.com/typora/202505290839102.jpg) ![E7Jvr4eIPwUNmzq.jpg](https://img.pushihao.com/typora/202505290839657.jpg) ![mh19anwBSWIkGlH.jpg](https://img.pushihao.com/typora/202505290839323.jpg) ![2tu9JC8ewpBFagv.jpg](https://img.pushihao.com/typora/202505290839254.jpg) {% endgallery %} 效果展示: [{"url":"https://img.pushihao.com/typora/202505281852188.jpg","alt":"Fze9jchtnyJXMHN.jpg","title":""},{"url":"https://img.pushihao.com/typora/202505290840741.jpg","alt":"ryLVePaqkYm4TEK.jpg","title":""},{"url":"https://img.pushihao.com/typora/202505290840937.jpg","alt":"gEy5Zc1Ai6VuO4N.jpg","title":""},{"url":"https://img.pushihao.com/typora/202505290840435.jpg","alt":"d6QHbytlSYO4FBG.jpg","title":""},{"url":"https://img.pushihao.com/typora/202505290839102.jpg","alt":"6nepIJ1xTgufatZ.jpg","title":""},{"url":"https://img.pushihao.com/typora/202505290839657.jpg","alt":"E7Jvr4eIPwUNmzq.jpg","title":""},{"url":"https://img.pushihao.com/typora/202505290839323.jpg","alt":"mh19anwBSWIkGlH.jpg","title":""},{"url":"https://img.pushihao.com/typora/202505290839254.jpg","alt":"2tu9JC8ewpBFagv.jpg","title":""}] 标签隐藏标签 行内隐藏hideInline 代码展示: 1 门里站着一个人? {% hideInline 闪, 查看答案, orange, white %} 效果展示: 上辈子如果你是一种饮料,那一定是碳酸饮料 为什么?因为我一看见你就开心得冒泡呀 门里站着一个人? 查看答案闪 块隐藏hideBlock 代码展示: 1 2 3 4 5 6 7 8 9 10 11 12 {% hideBlock 查看隐藏内容, green, white %} {% gallery %} ![Fze9jchtnyJXMHN.jpg](https://img.pushihao.com/typora/202505281852188.jpg) ![ryLVePaqkYm4TEK.jpg](https://img.pushihao.com/typora/202505290840741.jpg) ![gEy5Zc1Ai6VuO4N.jpg](https://img.pushihao.com/typora/202505290840937.jpg) ![d6QHbytlSYO4FBG.jpg](https://img.pushihao.com/typora/202505290840435.jpg) ![6nepIJ1xTgufatZ.jpg](https://img.pushihao.com/typora/202505290839102.jpg) ![E7Jvr4eIPwUNmzq.jpg](https://img.pushihao.com/typora/202505290839657.jpg) ![mh19anwBSWIkGlH.jpg](https://img.pushihao.com/typora/202505290839323.jpg) ![2tu9JC8ewpBFagv.jpg](https://img.pushihao.com/typora/202505290839254.jpg) {% endgallery %} {% endhideBlock %} 效果展示: 查看隐藏内容 [{"url":"https://img.pushihao.com/typora/202505281852188.jpg","alt":"Fze9jchtnyJXMHN.jpg","title":""},{"url":"https://img.pushihao.com/typora/202505290840741.jpg","alt":"ryLVePaqkYm4TEK.jpg","title":""},{"url":"https://img.pushihao.com/typora/202505290840937.jpg","alt":"gEy5Zc1Ai6VuO4N.jpg","title":""},{"url":"https://img.pushihao.com/typora/202505290840435.jpg","alt":"d6QHbytlSYO4FBG.jpg","title":""},{"url":"https://img.pushihao.com/typora/202505290839102.jpg","alt":"6nepIJ1xTgufatZ.jpg","title":""},{"url":"https://img.pushihao.com/typora/202505290839657.jpg","alt":"E7Jvr4eIPwUNmzq.jpg","title":""},{"url":"https://img.pushihao.com/typora/202505290839323.jpg","alt":"mh19anwBSWIkGlH.jpg","title":""},{"url":"https://img.pushihao.com/typora/202505290839254.jpg","alt":"2tu9JC8ewpBFagv.jpg","title":""}] 折叠框hideToggle 代码展示: 1 2 3 4 5 6 {% hideToggle 我的博客信息 %} - 博客名称: 葡萄 - 博客链接: https://blog.pushihao.com - 博客头像: https://blog.pushihao.com/img/avatar.webp - 博客描述: 没事写两句 {% endhideToggle %} 效果展示: 我的博客信息 博客名称: 葡萄 博客链接: https://blog.pushihao.com 博客头像: https://blog.pushihao.com/img/avatar.webp 博客描述: 没事写两句 按钮标签btn 参数说明: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 {% btn [url],[text],[icon],[color] [style] [layout] [position] [size] %} [url] : 链接 [text] : 按钮文字 [icon] : [可选] 圖標 [color] : [可选] 按钮背景颜色(默认style时) 按钮字体和边框颜色(outline时) default/blue/pink/red/purple/orange/green [style] : [可选] 按钮样式 默认实心 outline/留空 [layout] : [可选] 按钮布局 默认为line block/留空 [position] : [可选] 按钮位置 前提是设置了layout为block 默认为左边 center/right/留空 [size] : [可选] 按钮大小 larger/留空 代码展示: 1 这是我的博客 {% btn https://blog.pushihao.com, 葡萄, far fa-hand-point-right, green %} 效果展示: 这是我的博客 葡萄 高亮文字标签label 代码展示: 1 2 3 对 {% label 潇潇暮雨洒江天 %},一番{% label 洗清秋 blue %}。渐霜风凄紧,{% label 关河冷落 pink %},{% label 残照当楼 red %}。是处红衰翠减,苒苒物华休。惟有长江水,无语东流。 {% label 不忍登高临远 purple %},望故乡渺邈,归思难收。叹年来踪迹,{% label 何事苦淹留 orange %}?想佳人、{% label 妆楼颙望 green %},误几回、天际识归舟。争知我,倚栏杆处,正恁凝愁! 效果展示: 对 潇潇暮雨洒江天,一番洗清秋。渐霜风凄紧,关河冷落,残照当楼。是处红衰翠减,苒苒物华休。惟有长江水,无语东流。 不忍登高临远,望故乡渺邈,归思难收。叹年来踪迹,何事苦淹留?想佳人、妆楼颙望,误几回、天际识归舟。争知我,倚栏杆处,正恁凝愁! 行内图片标签inlineImg 代码展示: 1 计算机学生专属发型 {% inlineImg https://img.pushihao.com/typora/202505290843430.jpg 70px %} 效果展示: 计算机学生专属发型 链接卡片标签flink 代码展示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 {% flink %} - class_name: 友情链接 class_desc: 那些值得关注的网站~ link_list: - name: 披萨盒 link: https://blog.pushihao.com avatar: /img/avatar.webp descr: 热水比冷水更快结冰 - name: Hexo link: https://hexo.io/zh-cn/ avatar: https://d33wubrfki0l68.cloudfront.net/6657ba50e702d84afb32fe846bed54fba1a77add/827ae/logo.svg descr: 快速、简单且强大的博客框架 - class_name: 常用网站 class_desc: 日常学习必用~ link_list: - name: Google link: https://www.google.com avatar: /img/icon/google.ico descr: 搜索平台 - name: Github link: https://www.github.com avatar: /img/icon/github.ico descr: 全球最大的同性交友网站 {% endflink %} 效果展示: 友情链接 那些值得关注的网站~ 披萨盒 热水比冷水更快结冰 Hexo 快速、简单且强大的博客框架 常用网站 日常学习必用~ Google 搜索平台 Github 全球最大的同性交友网站 引用标签note 参数说明: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 {% note [参数一] [参数二] %} {% endnote %} [参数一]: - 不填 - default - primary - success - info - warning - danger - [颜色] + 图标 如: blue 'fas fa-bullhorn' [参数二]: - simple - modern - flat - disable - no-icon 代码展示: 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 {% note simple %} 参数一不填,参数二simple效果 {% endnote %} {% note default modern %} 参数一默认,参数二modern效果 {% endnote %} {% note primary flat %} 参数一primary,参数二flat效果 {% endnote %} {% note success disable %} 参数一success,参数二disable效果 {% endnote %} {% note info no-icon %} 参数一info,参数二no-icon效果 {% endnote %} {% note warning flat %} 参数一warning,参数二flat效果 {% endnote %} {% note blue 'fas fa-bullhorn' flat %} 参数一blue 'fas fa-bullhorn',参数二flat效果 {% endnote %} 效果展示: 参数一不填,参数二simple效果 参数一默认,参数二modern效果 参数一primary,参数二flat效果 参数一success,参数二disable效果 参数一info,参数二no-icon效果 参数一warning,参数二flat效果 参数一blue ‘fas fa-bullhorn’,参数二flat效果 画图标签mermaid 通过使用mermaid标签可以绘制Flowchart(流程图)、Sequence Diagram(时序图)、Class Diagram(类别图)、State Diagram(状态图)、Gantt(甘特图)和Pie Chart(饼状图)等,具体可以查看 memaid文档 在使用之前需要修改主题配置文件: 1 2 3 4 5 6 7 8 # mermaid # see https://github.com/mermaid-js/mermaid mermaid: enable: true # built-in themes: default/forest/dark/neutral theme: light: default dark: dark 语法: 1 2 3 {% mermaid %} 内容 {% endmermaid %} 后记 这么多语法参数也很琐碎,不可能看一遍就全记下来,随时用随时翻看即可。

2022/6/24
articleCard.readMore

Jupyter设置代码自动补全

Jupyter Notebook是我们用Python进行数据分析和机器学习的不二之选,但是Jupyter Notebook默认是不带代码自动补全功能的,我们可以通过安装插件的方式使其具有这个功能 安装模块 打开 Anaconda Powershell Prompt (Anaconda) 输入pip install jupyter_contrib_nbextensions回车开始安装 注:此方法默认使用国外的源,如果出现安装失败,参考以下方法 方法一:使用阿里云的源pip install jupyter_contrib_nbextensions -i https://mirrors.aliyun.com/pypi/simple/ 方法二:使用conda包管理器conda install jupyter_contrib_nbextensions 配置 打开 Anaconda Powershell Prompt (Anaconda) (方法同上) 输入命令jupyter contrib nbextension install --user并回车 打开Jupyter Notebook,找到顶部Nbextensions 启用相关插件 完成 这次,Jupyter自动补全功能就设置完成了,效果图如下

2022/6/23
articleCard.readMore