终于用上了Windows 11,版本是22h2。记录一下针对开发人员的相关优化。

1 安装时跳过TPM限制

安装过程,在提示“这台电脑无法安装Windows11”的界面,按Shift + F10,弹出CMD窗口输入“regedit”,打开注册表编辑器。

在注册表编辑器进入[HKEY_LOCAL_MACHINE\SYSTEM\Setup],新建“项”,名为“LabConfig”(注意大小写一致)。

在“LabConfig”下,新建两个“DWORD (32位)值”,如下(记得注意大小写一致):

  • 数值名称:BypassTPMCheck,数值数据:00000001,基数:十六进制(H)
  • 数值名称:BypassSecureBootCheck,数值数据:00000001,基数:十六进制(H)

完成后关闭“注册表编辑器”、“CMD窗口”,按返回上一步的按钮,再按下一页,就通过系统限制检测。

2 安装时避免强制登录账户

按Shift + F10,弹出CMD窗口输入“regedit”,打开注册表编辑器。

在注册表编辑器进入[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE],新建一个“DWORD (32位)值”,如下(记得注意大小写一致),如下。

  • 数值名称:BypassNRO,数值数据:00000001,基数:十六进制(H)

退出注册表编辑器后,输入命令“logoff”,即可即可跳过强制联网登录账号。

如果不想编辑注册表,在CMD窗口输入命令“oobe\BypassNRO.cmd”,系统重启后即可。

3 创建本地用户

安装成功后,需要创建本地用户。最好不要登录微软账户。

最简单的是创建和使用管理员账号(Administrator)登录,避免访问其它硬盘的已存在文件时,提示需要管理员账号。

如果使用自定义账号(即拥有管理员权限的非Administrator账号),需要修改电脑上已存在文件的权限,改为新增账号拥有“完全控制”的权限。如果存在大量零碎文件,这个过程超级漫长。

4 解锁任务栏

在Windows 11中,用户无法通过“任务栏”使用“快速启动工具栏”,通过安装“ExplorerPatcher”解锁相关功能。

ExplorerPatcher - GitHub
https://github.com/valinet/ExplorerPatcher

我整理了一堆快捷方式,大多是常用的,利用“快速启动工具栏”,实现快速点击,类似开始菜单的效果。

5 优化内存占用

1)关闭不必要的自启动

依次进入:设置 -> 应用 -> 启动。把不需要自启软件关闭。

2)关闭动画效果

依次进入:设置 -> 辅助功能 -> 视觉效果。“动画效果”设置关闭。这个优化,大概省了1GB内存。

6 恢复旧的右键菜单

新的右键菜单过于简单,隐藏了太多的功能。使用管理员运行CMD,输入以下命令。执行成功后重启,即可看到效果。

reg.exe add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" /f /ve

7 安装 WinMerge

开源的文件对比和合并工具,用于替代Beyond Compare。

启用“高级菜单”,选择文件对比时更方便。运行WinMerge,依次进入“编辑”->“选项”->左侧菜单点“系统集成”,在“资源管理器”下,勾选“添加到上下文菜单”、“启用高级菜单”。

8 显示多个时区的时间

依次进入:设置 -> 时间和语言 -> 日期和时间 -> 相关链接 -> 附加时钟。设置完毕后按“确定”。建议设置以下时间:

  • (UTC) 协调世界时。

设置完成后,点击任务栏的时间,即可看到新增的时区时间。

9 Office软件

目前使用开源的LibreOffice,代替微软Office,用着还行。

10 安装WSL2

Window上运行Linux软件,包括GUI软件。这样连SSH客户端都不用安装了。

主要过程是,开启Windows功能、安装内核、安装Linux发行版。相关文档如下:

11 解决不能自动关机

关机时,会提示有进程正在运行,需要点按钮才能关机。这问题导致使用shutdown命令和远程桌面都不能关机。

解决方案:打开注册表,进入“\HKEY_USERS.DEFAULT\Control Panel\Desktop”,新建“字符串值(S)”,名称为“AutoEndTasks”,值为“1”。

经过一段时间的爆米花机烘焙咖啡豆,对烘豆有了进一步的认识和感受,于是决心升级一下装备。

1 烘豆的原因

首先还是要来个灵魂敲问,为什么要烘豆?

通过亲自烘焙咖啡豆,对咖啡的味道有很多的把控,而不局限于手冲的过程。另外,烘焙出来的豆子更多的了解,可以更好地针对目标味道而配合不同的手冲方法。

如果想再进一步对咖啡出品的把控,就需要参与到咖啡树的种植和咖啡生豆的处理。显然,对大多咖啡爱好者来说,都是遥不可及的事情。烘焙咖啡豆有适合个人玩家的器具、方式,处理好的咖啡生豆也容易买到,所以适合去玩。

不过,重点是喜欢和享受烘焙咖啡豆这个事情。

2 烘豆操作

烘焙咖啡豆,就是通过对咖啡生豆进行加热,制作出具有独特香气和味道的咖啡熟豆的过程。

碍于个人的认识和篇幅所限,只能点出重点,详细内容需要参考相关资料,或者亲自体会。

2.1 整个操作

主要操作是挑豆、暖机、进豆、加热和翻炒、下豆冷却、养豆。

挑豆是把咖啡生豆中的杂质和瑕疵豆挑选出来,避免咖啡冲煮出杂味。一般买到的咖啡生豆,都已经去除了杂质,包括石头、沙子等。瑕疵豆,包括带壳豆、发霉豆、发酵豆、未成熟豆、虫蛀豆、贝壳豆、残缺豆、黑豆、死豆等。

暖机是进豆前,把机器加热到一定的温度。一般认为,热风烘豆机加热速度快,不需要暖机。

进豆就是把生豆倒入机器。此时烘焙过程开始,并进行计时。

加热和翻炒,目的是均匀地给咖啡豆加热,避免某些豆子或豆子的某部分过度加热而碳化(就是变得焦黑)。这过程就是烘焙过程。

下豆冷却,是为了降低咖啡豆的梅纳反应,锁住咖啡豆风味。这个时间越短越好。一般带风晒的“冷却盘”,还能实现去掉银皮的效果。

养豆,是由于咖啡豆本身的梅纳反应还在继续,会排出二氧化碳。一般养豆3~7天,让其味道稳定下来。拿刚烘焙和养豆后的咖啡豆,冲煮对比一下,就能明显感受到区别。

2.2 烘焙过程

主要分为脱水、转黄、一爆、二爆这几个阶段。比较专业的划分是:

  • 脱水期,从开始烘焙到转黄,决定豆子是否容易夹生。
  • 梅纳反应期,又叫美娜德反应,从转黄到一爆开始,产生大部分芳香物质。
  • 发展期,从一爆到下豆,决定豆子的风味。

爆米花机一般10分钟左右下豆(完成烘焙),具体时间要看豆子的状态,以及烘焙师想要达到的烘焙度。

DTR,即Development Time Ratio,是指“发展期”占烘豆总时间的百分比。《咖啡烘豆的科學》一书中,作者Scott Rao认为,DTR应于20~25%之间。

3 上一版的不足

升级,就是为了弥补上一版的不足。上一版的缺点包括:

  • 1)只能烘焙50~60g生豆,份量太少。
  • 2)想要提升风力和热量,需要更换配件,比较折腾。
  • 3)热风采用侧风出口,不仅噪音大,还翻动不均匀。
  • 4)组装的零件多,主要是玻璃和不锈钢材质,烘焙过程由于爆米花机震动导致噪音大,影响一爆的声音。

4 新版的解决方案

针对上一版的缺点,解决方案如下:

  • 1)爆米花机改为“北欧欧慕(Nathome)NBM001”。
    热风采用底部直风出口,功率达到1400w,能够翻动100g生豆且受热均匀。其改装过程跟上一个一样,只需把直流电机的电源线,从主板上分离,再接上直流变压器的母头(注意正负极)。
    后记,这机器的发热丝不耐用,不够两年烧断过两次。
  • 2)采用透明玻璃酒瓶和过滤豆浆渣的布袋,作为爆米花机上盖,避免噪音。
    把酒瓶去底,直接插到爆米花机豆仓上。酒瓶出口就套上过滤豆浆渣的布袋,用于收集银皮。这个简化的装置,几乎没有噪音,而且能清晰观察豆子的烘焙程度。
  • 3)热风温度很高,能在冬天烘豆。
    发热管70%的电压,能吹出220°C热风。

5 总结

升级后的烘豆机,出品很均匀,能够实现不同程度的烘焙度。使用中深烘的云南廉价豆(练习豆),能逼出焦糖风味,甜感爆棚。也试过浅烘的“云南佐园经典日晒”,酸味很突出。

烘焙时,可以试试慢烘与快烘。慢烘是参照别人的烘焙曲线,通过调整发热管电压,实现爬温效果。快烘就是一直高温吹豆,大概3分钟出锅。

总结了几个使U盘更强大的工具。

1. 文件系统

选择exFat文件系统。支持超过4GB的大文件,Windows、Linux、Android都原生支持。注:Android 13才开始原生支持exFat。

2. 启动盘管理

以前一直用YUMI,实现一个U盘启动多个ISO系统镜像(同一时间只能启动一个ISO)。但是每次添加或删除ISO,都要用YUMI处理。后来发现Ventoy更强大,只要把IOS文件放到U盘,就能启动(前提是先把U盘弄成Ventoy启动盘)。

Ventoy默认把U盘划分两个分区。其中空间大很多的分区,默认格式化为exFat(制作完,可手工格式化为其它文件系统),可当作普通U盘使用,直接存放文件。用来启动电脑的ISO文件放进去,即可在启动时选择,超级方便!

3. WinPE启动盘

主要用于维护Windows的电脑,比如Ghost分区。其中这类WinPE当中,很多人推荐微PE

运行exe文件,可以生成ISO文件。U盘使用Ventoy处理后,直接把ISO文件拷贝进去即可。

4. 绿色工具管理

工作的电脑是Windows,有些软件不想装进去,于是找到PortableApps

PortableApps是一个管理“绿色”软件的软件,特别适合装在U盘上。其建立一个“市场”,里面有一大堆常用软件的“绿色版”,包括Chrome。于是,对已安装软件的升级也方便,只是网络比较慢(一般早上8点前,网速会好点)。

Nmon (Nigel's Monitor) 是AIX系统与Linux 系统上,开源免费的监控资源的工具。Njmon则是其下一代的形态。

Njmon的主要特点是:

  • 采用JSON保存数据。
  • 原生支持发送数据到InfluxDB。
  • 可配置不监控的数据。
  • 不支持终端显示数据。
  • 添加了相关工具,包括njmond、nmeasure、njmonchart等。

由于想玩玩InfluxDB,于是按照官方建议,部署了Njmon + InfluxDB + Grafana。然后总结以下缺点:

  • Njmon对Linux支持不足。比如Debian系统,需要自己编译。但是编译过程没什么困难。
  • Njmon虽然支持InfluxDB v2+,但Grafana上没找到能直接使用的模板。Grafana找到的模板是针对InfluxDB v1。
  • 相对Nmon,Njmon参考资料比较少。但是官方有提供Youtube视频教程。

Njmon + InfluxDB + Grafana的部署要点:

  1. njmon命令运行于“nimon”模式,直接连接到InfluxDB时,需要加参数-I
  2. njmon命令连接InfluxDB v2+,需要使用-O 组织名称 -T token这两个参数进行验证。

    # 示例,njmon连接到InfluxDB v2
    sudo ./njmon_Debian11_unknown_v80 -I -s 1 -i 127.0.0.1 -p 8086 -x bucket -O 组织名 -T xxxxxxxxxxx
  3. InfluxDB v2+同时提供v1接口时,需要创建对应数据库(Database)和保存策略(Retention Policies)。

    # 示例,InfluxDB v2创建对应的v1数据库和保存策略
    influx v1 dbrp create --db bucket-db --rp bucket-rp --bucket-id xxxxxx --default --org '组织名' --token 'xxxxxxxxxxx'
  4. InfluxDB可以使用Docker部署。

    # 示例,使用Docker部署InfluxDB v2.4.0
    docker run --name influxdb -p 8086:8086 -v /opt/influxdb/config.yml:/etc/influxdb2/config.yml --volume /opt/influxdb/data:/var/lib/influxdb2 influxdb:2.4.0
  5. Grafana如果没找到相关模板,只能找接近的,再自己修改一下。

总结。体验过InfluxDB + Grafana,挺爽的。只是,如果是用作监控,那么采集数据端不一定使用Njmon。

dstat是一个Linux监控工具。可定制采集数据,可设置采集频率,可输出字符界面和导出CSV。默认一秒一条监测数据。其中以top开头的参数,可以记录检测类型最大值的进程。比如--top-cpu记录CPU占用最大的命令,--top-cpu-adv还会记录CPU占用最大的进程ID等。参数--time的时间格式,需要通过环境变量DSTAT_TIMEFMT进行定义。

示例操作命令:

# 设置时间格式
export DSTAT_TIMEFMT='%Y-%m-%d %H:%M:%S'

# 执行监测,并导出CSV文件
dstat --time --cpu --mem --disk --io --net --sys --top-cpu-adv --top-mem --top-bio-adv --top-io-adv --output /opt/dstat_log/dstat_$(date +%Y%m%d).csv

使用时,可结合tmux,随时查看其采集数据,即时输出在终端。导出的CSV文件,需要下载到本地,并使用第三方工具生成图表。

在众多监控方案中,dstat不算优秀的解决方案,而且只有采集数据的功能。其记录数据,采用CSV格式。如果终端不够宽时,不能完整显示每行的采集数据。而且CSV格式不好扩展,比如--top-cpu-adv记录的数据,不适合机器理解。这里记录一下相关经验。

1 正式版的bug

安装过0.7.3和0.7.4两个版本,并使用Python3运行,都存在以下两个Bug。幸好是使用Python开发,可以直接修复。其安装路径为/usr/bin/dstat

a)在Debian 10以上使用Python3运行时,出现以下Bug:

/usr/bin/dstat:2619: DeprecationWarning: the imp module is deprecated in favour of importlib and slated for removal in Python 3.12; see the module's documentation for alternative uses
  import imp
Terminal width too small, trimming output.
Traceback (most recent call last):
  File "/usr/bin/dstat", line 2847, in <module>
    main()
  File "/usr/bin/dstat", line 2687, in main
    scheduler.run()
  File "/usr/lib/python3.10/sched.py", line 151, in run
    action(*argument, **kwargs)
  File "/usr/bin/dstat", line 2806, in perform
    oline = oline + o.showcsv() + o.showcsvend(totlist, vislist)
  File "/usr/bin/dstat", line 547, in showcsv
    if isinstance(self.val[name], types.ListType) or isinstance(self.val[name], types.TupleType):
NameError: name 'types' is not defined. Did you mean: 'type'?

解决办法,参考以下文档:

简单来说,改两行代码。如下:

# 第547行,改为:
if isinstance(self.val[name], (tuple, list)):

# 第552行,改为:
elif isinstance(self.val[name], str):

b)--top-mem参数统计错误的bug

参考文章:

修改方法def proc_splitline(filename, sep=None),改为:

if filename.startswith("/proc/") and filename.endswith("/stat") and filename != "/proc/stat":
    tmp = linecache.getline(filename, 1).split(sep)
    it = [i for i,c in enumerate(tmp) if c.endswith(')')]
    it = 2 if not it else it[-1]+1
    return tmp[0:1] + [' '.join(tmp[1:it])] + tmp[it:]
else:
    return linecache.getline(filename, 1).split(sep)

2 应用场景

感觉比较适合单机版,或者指定采集一些系统数据。不适合生产机大规模部署。如果非要用dstat不可,可以考虑 dstat + Fluentd + Influxdb + Grafana 这种组合方案。

最近因工作需要,研究了一下Linux服务器的监控方案,收获颇丰。

1 监控需求

服务器需要监控什么?可以分为硬件和软件,或者系统数据和业务数据。一般的监控解决方案,都是针对硬件、操作系统和常用软件(比如数据库、Docker之类)。涉及业务数据,需要二次开发。

2 监控方案设计

一般的监控方案,分为采集、存储、展示、告警,这四大模块或者功能。针对被监控服务器的数量,可灵活实施四个模块的部署方式。比较完善的整体解决方案,还包括“控制”模块,实现服务器集群的统一管理。

2.1 采集

数据采集程序,或者叫“探针”,一般是部署在被监控服务器上的程序。用于采集相关数据,要求占用系统资源小(主要是CPU、内存、磁盘、网络等),对系统影响小。

数据采集的方式,可以采用“推”(push)和“拉”(pull)模式。

“推”是数据采集程序主动把数据从采集端发到存储端。数据具有良好的实时性,方便内网部署并推送到外网服务器。但采集端太多,或者采集的数据比较大,需要考虑存储端的承受能力。遇到存储端没有收到数据时,不能确定是网络问题还是采集端问题。

“拉”是数据采集程序暴露出来,例如开放HTTP服务的端口,存储端去访问并获取数据。存储端也有更多的主动权,决定拉取频率,甚至决定采集样本,能降低带宽、减轻存储端的压力。采集不到数据时,能区分网络问题和采集端问题。如果对数据有实时需求,采集端需要保存未拉走的数据。

针对采集端有时只能部署在内网的情况,有的解决方案会提供“代理”或者“跳板”功能,实现采集端与存储断之间的数据连接。

2.2 存储

数据存储,就是把监控数据持久化,可以是文件(比如CSV文件),也可以是数据库(比如MySQL、InfluxDB)。目前主流的方案,基本采用时序数据库,例如InfluxDB。专门针对这种大量连续时间的数据,提供存储、查询、统计等功能。

2.3 展示

数据展示,一般是把监控数据生成图表,以便更直观地查看和分析。简单的方案是用Microsoft Excel之类的软件,根据导出CSV文件的数据,生成各种图表。主流和灵活的方案是使用可视化软件,例如Grafana,连接时序数据库并生成各种图表。Grafana能够实现实时展示和历史分析。

2.4 告警

通过检测采集的数据,发现超过指定危险指标时,向相关人员发送消息,就是告警。由于相关人员一般不会24小时盯着服务器,所以需要机器进行告警。InfluxDB、Prometheus、Grafana等都有告警功能。一般开源系统只提供邮件或Web Hook(调用钉钉接口)通知,商业系统(例如:阿里云的云监控)会有短信或电话通知。

3 解决方案

服务器监控的解决方案,像编程语言一样,没有一个万能方案,需要根据情况进行选择。这里列举一些相关软件或方案。

3.1 dstat

基于Python。默认一秒采集一条数据,数据定制性高。数据可显示在终端,也可导出CSV文件。需要使用第三方软件,例如Microsoft Excel之类,生成图表和分析数据。高级玩法是,搭配Fluentd,保存数据到InfluxDB,再用Grafana展示、分析。

优点是占用资源小,数据简单。缺点是由于使用CSV格式,复杂数据记录得不够好。而且当前版本(0.7.4)有bug,部署时需要自己修正。

3.2 njmon

C语言开发。nmon的升级版,可设置不收集的数据,数据格式采用JSON。不支持终端显示,原生支持导出文件和发送到InfluxDB。有官方工具处理保存的JSON文件。推荐的玩法是njmon + InfluxDB + Grafana。

优点是占用资源小,作者对整个监控方案考虑比较全面。缺点是对AIX较好但对Linux支持不足。比如Debian 11需要自己编译。Grafana的njmon模板大多针对AIX,而且仅有的Linux模板是针对InfluxDB v1,即使用InfluxQL而不是Flux。

3.3 glances

基于Python,开源跨平台,界面优秀。支持三种模式:单独运行、C/S、Web。提供XML-RPC服务、RESTful JSON接口,也可把数据保存到其它系统,包括InfluxDB。本身支持配置“Actions”,根据事件触发相应脚本,实现告警。

占用资源较大(包括CPU和磁盘空间),界面优秀,玩法多。适合桌面系统的监控。

3.4 InfluxDB

InfluxDB只是个时序数据库,但是该公司开发了Telegraf作为数据收集工具(采用“推”模式),并且InfluxDB新增了告警和图形化展示,形成一个完整的数据收集方案。

3.5 Prometheus

基于Go,监控、告警工具,使用“拉”模式采集数据。

3.6 Zabbix

企业级的开源的服务器监控管理系统,是完整的解决方案,基本可以替代阿里云的云监控之类的系统。Web控制台基于PHP,支持中文显示;采集端基于C,升级版改为Go;数据存储使用MySQL,未支持时序数据库是最突出的缺点。另外,告警功能不支持电话和短信通知。

非常适合企业内部管理服务器集群,便于运维人员使用。

3.7 阿里云的云监控

一般云主机的服务商都提供云监控功能,且基本监控免费,高级功能收费。阿里云的云监控,还能监控非阿里云的主机。使用这些云监控前,要确定是否可以把服务器监控数据发给云厂商,甚至安装云厂商的采集软件。

4 方案选型

根据不同的情况,总结一下各个方案的选型。

4.1 云主机

如果是购买云主机,可以考虑云服务提供商的云监控,一般免费提供基础监控功能,例如阿里云的云监控。但前提是,云主机可以安装云监控的采集端软件,并且接受相关数据上传到云服务提供商那边。另外,高级监控功能,需要付额使用。

4.2 运维管理的服务器集群

企业内部,有专门的运维人员管理服务器,针对硬件或操作系统相关数据的监控,则可以考虑Zabbix。

4.3 业务数据

node_exporter + Prometheus + Grafana,或者 Telegraf + InfluxDB + Grafana,这种方案适合收集业务日志。部署了InfluxDB,还能存储其它数据,个人觉得比较好玩。

近来项目遇到要显示SVG图像文件。本来SVG已不是新鲜事物,应该很好使用,现实并非如此。

设计说用AI(Adobe Illustrator)做出来的图,用Chrome打开会出现错误,比如图形位置不对、该显示的图形没显示等。用文本文件打开那图,会显示xml标签开头,而不是svg标签开头。

图片显示有误的问题,只能让设计去修正了。至于文件格式的问题,找到svgcleaner这个工具,转换一下就好了,体积还有所减少。虽然svgcleaner貌似不更新了,但工作正常。

以前一直用Inkscape代替曾经的Flash,设计矢量图,比如图标之类,还不错。不知道为什么大厂出品的AI会是这么多问题。

近来遇到关于库存与并发的问题。由于一直接触的系统都是没有考虑过商品库存的并发,加上解决过的并发问题,也只是简单直接地采用锁表的方式。所以导致踩坑。

1 问题1,商品基础数据与库存数量,设计在同一个表。

商品基础数据,包括库存数量,主要用于查询。但库存数量,还要解决经常变化,且可能出现并发的情况。如果简单使用锁,即使只锁一行数据,也会导致正在进行下单(涉及扣减库存)的商品不能被浏览(因为锁住,不能查询)。

为了减轻这个情况,下单时,检查库存数量是否足够购买时,不锁数据,等到保存订单数据,真正扣减库存时才加锁。本想着通过减少锁数据的时间,减少商品数据不能查询的情况。但是系统采用Java开发,使用了Spring + Hibernate框架。而Hibernate在事务内使用了一级缓存,即事务内未提交时,查询到的业务数据都放到一级缓存。事务内查询时,会先查询一级缓存,若命中,则不再查询数据库。就导致了检查库存时已获取了商品数据,扣减库存时(从一级缓存获取)不能获取到最新库存(特别是两个客户同时下单同一个商品的情况),最后在并发情况下扣减库存,就出现库存扣少1了的问题。

解决方案很简单,把商品基础数据与库存数据分开两个表存放。库存数据在扣减时,不影响商品浏览。

2 问题2,库存数量,需减少锁定时间。

由于客户浏览商品,或者添加商品到购物车,都需要查询库存数据。如果使用悲观锁,即锁表或锁数据后不能查询,会导致客户不能浏览。参考了以下文章,决定使用乐观锁,即不使用数据库锁。

目前系统规模比较小,且没有涉及分布式,于是决定在扣减库存时直接更新数据的方式。即使用update语句扣减库存时,用where条件判断是否足够扣减,并返回是否扣减成功。

由于使用MySQL,update语句不能返回指定数据(但是,sql server可以使用update...output,PostgreSQL可用update...returning)。加上Hibernate不能同时执行update和select两个语句,最后采用存储过程。参考以下网址:

3 解决方案

总的来说,使用乐观锁(即没有使用数据库的锁),并利用MySQL存储过程实现扣减库存后返回结果。

1)库存表

create table `product_stock` (
    `productId` bigint not null comment '商品ID',
    `instock` int not null default '0' comment '库存数量',
    `createTime` datetime(3) default null comment '创建时间',
    `updateTime` datetime(3) default null comment '更新时间',
    primary key ( productId )
) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_0900_ai_ci comment='商品库存';

2)扣减库存的存储过程

利用存储过程的out参数,返回扣减结果。当outUpdateQty返回的值大于零,扣减成功,否则失败。扣减成功,outStockAfter的值才是正确。

delimiter //
create procedure `product_reduce_instock`(
    in inProductId bigint, /*传入参数:商品ID*/
    in inReduceQty int, /*传入参数:扣减数量*/
    out outUpdateQty int, /*传出参数:实际扣减数量*/
    out outStockAfter int /*传出参数:更新后库存数量*/
)
begin
    -- 初始化返回的值
    set @updateQty=0;
    set @stockAfter=0;
    
    -- 执行扣减库存
    update product_stock 
    set instock = (@stockAfter := instock - (@updateQty := inReduceQty)), updateTime = now() 
    where productId = inProductId and instock >= inReduceQty;
    
    -- 传出参数赋值,即返回扣减结果
    set outUpdateStock=@updateQty;
    set outStockAfter=@updateQty;
end //

锐角云三角主机,入手两年多,一直在家作为私人服务器,稳定跑着各种服务。作为矿难出品,当年以299RMB入手,很多人说贵。但跑了这么久,现在感觉很超值。性价比高,秒杀各种ARM开发板。

先说缺点。当时由于是299,所以没有外壳和M.2接口的128GB SSD。为了不破坏自带的Win10,我另外买了个32GB SSD,装上Debian 10。30 多 RMB,包邮,型号是M.2 2242。CPU性能问题,不能胜任大型计算任务。试过编译minidlna,能跑到2.2Ghz,但仍然有点慢。

装系统时,注意要支持uefi32启动的。所以Debian 10的安装盘,只能用同时支持32位和64位的ISO文件。

另外还有个硬伤,不能上电自动开机。如果家里电路不稳定,可能需要买个12V的UPS补救。另外,也可以参照以下教程,添加上电自动开机的功能:

再来说优点吧。作为家用服务器,首先是省电。然后是8GB大内存(z8350那类电视棒一般是2GB或4GB)!CPU是N3450,x86架构,64位(服务器软件方面几乎没有障碍,例如直接跑Minecraft Bedrock),跑起来风扇不会经常转(就是噪音低)。接口丰富,千兆网卡、3个USB 3.0、HDMI 2.0、WiFi、蓝牙、SSD(M.2 2242接口,SATA协议,最大支持960GB)、耳机接口等。自带64GB存储,默认装Win10,就算不做服务器,作为高清播放器、办公电脑,也是可以。

更多相关资料,可以搜索关键词“锐角云 三角主机”。

本文记录了从jQurey转到原生JavaScript开发的相关处理。

一 历史

二十一世纪初,IE 6还在统治浏览器的时代,出现了一批JavaScript框架。除了提高前端开发效率,还屏蔽了各个浏览器的JavaScript接口差异。那时有3个产品印象比较深刻:

  1. prototype,http://prototypejs.org/
    其特点是在原生JavaScript基础上做扩展,定义通用的方法或接口,屏蔽各个浏览器的差异。很轻量,个人比较喜欢。
  2. Ext JS,https://www.sencha.com/products/extjs/
    数据与界面分离,提供丰富的UI组建,便于页面开发。当时浏览器JavaScript性能不高,用起来不够流畅,不适合简单排版布局的页面。但是对于开发一些管理系统,确实很方便。
  3. jQuery,https://jquery.com/
    最大特别是查找HTML元素很方便(前提是熟悉其搜索语法),有点函数式编程的味道。在那个需要手工修改HTML界面的年代,确实很方便。

二 当前

看看当前的浏览器,已经是Webkit内核的天下,加上IE已亡,ECMAScript 6普及……各个浏览器的JavaScript兼容性大大提高。所以,我们可以直接采用浏览器原生JavaScript,替代jQuery这类用于遍历或搜索DOM的框架。当然,复杂的界面,主要是响应式前端框架(AngularJS、React、VUE)的世界。

三 实现方法

主要参考这个文章,从jQuery转到原生JavaScript。

另外,对于页面上的异步请求(ajax),该文章没有提出timeout的处理。以下整理一个示例:

// 请求错误的类,用于传递错误信息
let RespError = class {
  constructor(code, msg, respJson) {
    this.code = code;
    this.msg = msg;
    this.respJson = respJson;
  }
};

// POST提交Json数据。调用ajaxJson方法前加上async就是同步调用,直接调用就是异步调用
// 默认超时10秒
let ajaxJson = async (url, formParam={}, onSuccess=(respJson)=>{}, 
    onFailed=(respError)=>{}, timeoutSec=10) => {
  let controller = new AbortController();
  let timeoutId = setTimeout(() => {
    // 超时后停止请求
    controller.abort();
    // 抛出超时的错误
    onFailed(new RespError(-1, 'TIMEOUT', null));
  }, timeoutSec * 1000);
  try {
    // 发起请求
    let response = await fetch(url, {
        signal: controller.signal, // 用于接收中断请求信号
        method: 'POST',
        cache: 'no-cache',
        headers: {
          // 声明请求的参数是JSON
          'Content-Type': 'application/json; charset=UTF-8'
        },
        body: JSON.stringify(formParam)
    });
    // 注意,响应的数据只能获取一次,包括response.json()和response.text()
    let respJson = await response.json();
    if(!response.ok) {
      // 请求失败,抛出自定义的错误对象
      throw new RespError(response.status, response.statusText, respJson);
    }
    onSuccess(respJson);
  } catch(err) {
    onFailed(err instanceof RespError ? err : new RespError(-1, err.message, null));
  } finally {
    // 请求结束,停止执行定时函数。避免相应成功后,抛出超时的错误。
    clearTimeout(timeoutId);
  }
};

关于Fetch的使用,参考:

  1. Fetch API 教程
    https://www.ruanyifeng.com/blog/2020/12/fetch-tutorial.html
  2. mdn web docs - 使用 Fetch
    https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch

四 后记

推荐搭配 petite-vue ,实现数据与页面元素的绑定。