Hello, 我是开发者小橙。
+ + +diff --git a/404.html b/404.html new file mode 100644 index 0000000..b4fd0c6 --- /dev/null +++ b/404.html @@ -0,0 +1,339 @@ + + +
+ +Hello, 我是开发者小橙。
+ + +使用docker
运行的wordpress
,有报错The uploaded file exceeds the upload_max_filesize directive in php.ini
。
按照一般的方式去修改文件php.ini
:
+
|
+
+
|
容器中有两个php.ini
文件:
都修改之后却并不生效。
+在文件夹conf.d
中添加文件uploads.ini
:
+
|
+
+
|
文件夹conf.d
位置需要看具体环境:
+
|
+
+
|
然后在其目录下找到文件夹conf.d
。
也可以通过在运行容器时候直接将该文件映射进入。
+ +在使用Docker
时,其默认时区并非使用者所在时区,需要进行修改。对于单个容器,当前修改有几种常见方式,比如直接映射宿主机时区到容器内,而本文介绍的为使用Dockerfile
来直接修改镜像时区。此处仅以常见几个基础容器为例来介绍。
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
tzdata
,所以跟Ubuntu
有所不通过
+
|
+
+
|
+
|
+
+
|
此处不再列举太多,主要解决方式为安装tzdata
,然后修改时区。
在使用vue cli plugin electron builder开发项目,用到涉及node相关的功能,比如fs
、path
,出现报错__dirname not defined
。
该问题在于需要开启electron对于node操作的支持。
+
+
|
+
+
|
修改electron的配置文件,此处我的配置文件为src/background.ts
+
|
+
+
|
Restful作为目前流行的api设计规范,在flask上也有较好的实践,即为flask_restful。我们在使用flask_restful的时候,当代码量达到一定程度,需要将视图函数模块化。然而在Flask之前一直使用Blueprint模块化,那么flask_restful如何模块化呢?底下就来瞅瞅!
+
+
|
+
+
|
__init__.py
:将该文件夹标示为一个模块commands.py
:额外命令,如初始化数据库config.py
:配置文件views.py
:视图函数此为一个简单的flask项目架构,我们可以将flask中的各个功能拆分出来,分给不同文件,最后在__init__.py
导入。
+
|
+
+
|
现在将视图函数改为一个文件夹views
,然后在该文件夹下放入拆分后的文件。这里以登录和获取用户信息接口为例,架构就变成了下面这样。
+
|
+
+
|
此处的__init__.py
文件功能如上,是将文件夹views
标示为模块,但是此处的__init__py
是个空白文件,但是server
文件夹中的__init__.py
的引入需要改变。
+
|
+
+
|
此时还不够,例如登录和获取用户信息接口是User
模块下的,而获取文章标题接口是Article
模块的下的,因此我们继续分。
+
|
+
+
|
server
文件夹中的__init__.py
的引入继续改变。
+
|
+
+
|
至此,关于flask_restful
的视图函数模块化结束,若有后续改进会继续分享,若大家有更好的方式请务必分享一下。
当时为了解决这个问题查了很久,但是要么是介绍blueprint的,要么就是flask_restful插件入门,简直刺激…
+ +Flask_RESTful
是一个Flask 扩展,它添加了快速构建 REST APIs 的支持。其请求解析接口是模仿 argparse
接口。它设计成提供简单并且统一的访问 Flask 中 flask.request
对象里的任何变量的入口。
+
|
+
+
|
+
|
+
+
|
使用参数required
。
+
|
+
+
|
+
|
+
+
|
使用参数action = 'append'
+
|
+
+
|
+
|
+
+
|
使用参数action = 'append'
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
当前产品遇到一个报错,就是接口收到请求没有限制请求字段长度,导致字段长度超过数据库对应字段长度,直接报了500。因此也对此有些新的需求,需要在后端限制请求字段最大长度。
+
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
使用flask_sqlalchemy
,服务端出现500错误,日志显示报错如下:
+
|
+
+
|
添加配置:
+
+
|
+
+
|
该配置作用是设置多少秒后回收连接,如果不提供值,默认为 2 小时。此处将其设置为280秒。
+该配置原文解释:
+++ +Number of seconds after which a connection is automatically recycled. This is required for MySQL, which removes connections after 8 hours idle by default. Note that Flask-SQLAlchemy automatically sets this to 2 hours if MySQL is used.
+
+
|
+
+
|
若要在新增之后获取数据库中新增数据的信息,如id
。
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
代码运行过程中,意外情况会导致500
错误,对于使用者来说体验很不好,对于开发者来说也无法及时获取错误,需要去查看日志。
并且有的插件在某些报错情况下会返回一些敏感信息,非常危险。因此需要去捕获全局错误,通知开发者,自定义错误消息等。
+
+
|
+
+
|
gin
的中间件的使用场景非常广泛,此处主要介绍如何使用其来完成常见场景下的鉴权。
官方文档列出了如下几种使用方式:
+对于api key
的方式需要设置白名单,对白名单外的请求进行token
检测。此中间件在处理请求被处理之前对请求进行拦截,验证token,因此可在此处利用gin.Context
来设置上下文,如请求所属用户的用户信息等。
+
|
+
+
|
对于请求的处理,需要去验证是否对其请求的路径拥有访问权限。
+首先看一下gin
的路由设置:
+
|
+
+
|
其参数为...HandlerFunc
,其解释为:
+
|
+
+
|
所以此处可以通过定制中间件的方式实现一个路由权限处理。
+当然此处的权限处理比较简单,使用角色直接去判断权限。如分为两个角色,管理员admin
和普通用户user
。
不过此处实现有个前提条件,就是如何拿到用户的角色呢?此处需要在上一步(api key
)的实现中加上利用gin.Context
设置角色:
+
|
+
+
|
然后在中间件中拿到角色并进行判断。
+
+
|
+
+
|
在设置路由时候,添加该中间件,并设置白名单。
+
+
|
+
+
|
Go 标准库专门提供了 net/http/httptest
包专门用于进行 http Web 开发测试。
此处基于gin来实现http的单元测试。
+此处使用http单元测试对/ping
请求测试,其正常会返回字符串pong
,响应码为200
。
+
|
+
+
|
+
|
+
+
|
post、put、delete请求处理相似,都是处理其request body,因此此处只以post为例。
+此处对/todo
进行测试,其正常返回为json
,其中code
为20000
,响应码为200
。
+
|
+
+
|
+
|
+
+
|
由于http单元测试代码中存在较多重复,因此此处封装重复代码。
+
+
|
+
+
|
此处以post请求为例。
+
+
|
+
+
|
在Go中POST
请求时报错
+
|
+
+
|
即无法检验证书。
+
+
|
+
+
|
跳过校验即可。此处引入"crypto/tls"
。
+
|
+
+
|
在数据量较小的情况下,可以使用Redis
来实现消息的发布与订阅,来代替Kafka
。Kafka
对于数据量大的场景下性能卓越,但是对于如此小场景时候,不仅运维成本提升,还用不上多少性能。
不过使用Redis
的另一个弊端是消息不能堆积,一旦消费者节点没有消费消息,消息将会丢失。因此需要评估当下场景来选择适合的架构。
此处使用go-redis来实现Redis
的发布与订阅。
官方文档有较为完整的例子:
+
+
|
+
+
|
分步讲解下具体实现代码。
+
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
此处分为一个发布节点和一个订阅节点来实现了简单的发布与订阅。
+
+
|
+
+
|
+
|
+
+
|
项目开发中遇到该问题,网上的文章太乱,为了节省下次踩坑时间,特此记录。
+该函数在加解密中都需要用到。
+
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
对于Golang
操作Redis
,此处使用github.com/go-redis/redis
。
+
|
+
+
|
###存储
+
+
|
+
+
|
+
|
+
+
|
###读取
+
+
|
+
+
|
+
|
+
+
|
使用JSON
格式存储与读取其实就是对目标数据在存储前和读取后进行格式转换。
关于测试工程师,有一个笑话,是这样的:
+
+
|
+
+
|
这个笑话估计也只有开发才明白其中的笑点与心酸吧。
+对于一些刚入门的开发来说,这简直就是噩梦。当初刚入门的时候我的代码也是很多毛病,经不起这样的测试,后来渐渐地经验多了后,代码的健壮性逐渐提升,也明白其中比较重要的就是参数的校验。
+参数的使用有通过协议的接口调用(如http、rpc……)、函数调用、库调用等等方式。
+其实对于http api的请求来说,现在很多web框架都已经自带了参数校验的功能,基本用起来都挺爽的,也无需多讲。
+而对于函数调用这样的常见方式,很多是要靠开发自己去校验参数的。如果仅仅是靠注释,在团队开发过程中,难免会有问题产生。起码我觉得,永远不要相信传过来的参数!
+OK,那么来讲讲这次的题目,就是Golang中的参数校验库——validator。
+用过Gin的小伙伴儿应该知道其binding
参数验证器非常好用,其就是调用了validator。此处呢我们来介绍下validator的基础用法,和在一般场景下的应用案例。
validator
包源码在github.com/go-playground/validator。其基于标记实现结构和单个字段的值验证,包含如下关键功能:
+
|
+
+
|
+
|
+
+
|
此处从官方列举的各个类别中挑选部分举例说明。
+此处的字段大部分可以理解为上面的比较的tag跟field
拼接而成,而中间有cs
的tag为跨struct比较。
+
|
+
+
|
自定义验证可以自己创建一个校验的函数:
+
+
|
+
+
|
然后将其注册到validate上即可:
+
+
|
+
+
|
自定义校验可以满足开发过程中的特殊场景,通过制定规范的校验标准,可以推进团队的协作和开发效率。
+至此便是对于validator
的介绍了。本文篇幅较短,管中窥豹而已,基本可以满足简单场景的使用。以及本文的案例也是基于官方的案例改的,让其稍微接地气点。若有兴趣的小伙伴还是建议去完整看一下官方的文档和案例,多样的用法可以满足多样的场景,在满足代码的健壮性的同时也能确保代码的优美。
因为项目需求,需要去检测用户的农历生日。虽然后来找到了合适的库,但是首先先解释下农历
的定义,也是去了解才知道,原来农历不是阴历。
农历属于阴阳合历,其年份分为平年和闰年。平年为十二个月,闰年为十三个月。月份分为大月和小月,大月三十天,小月二十九天,其平均历月等于一个朔望月。
+
+
|
+
+
|
该库支持1900~2049年。所以项目要跑到2049年后的童鞋就要注意……
+当然,该库还支持阳历转农历、节假日计算等,有兴趣大家可以自行去了解下。
+该库不支持闰年判断,所以需要自己去实现闰年的判断,其参数类型为Boolean
。
+
|
+
+
|
需要转换的阳历日期格式是固定的,是2006-01-02
。此处以农历2021-07-17
为例。
+
|
+
+
|
输出为:
+
+
|
+
+
|
+
|
+
+
|
在Golang
中json
解析时报错:
+
|
+
+
|
代码如下:
+
+
|
+
+
|
首先将result
打印出来,发现并无异常,其标点符号也没有问题。
然后查看网上现有解决方案的帖子基本试了下,起码对于我来说并不适用,概括下方案:
+最后对比代码中获取到的字符产长度和手动复制所见的字符串的长度,发现确实代码中字符长度不同,其长度是80,而手动复制的字符串的长度是72。
+
+
|
+
+
|
就挺简单的……
+ +想要写出好的 Go 程序,单元测试是很重要的一部分。 testing
包为提供了编写单元测试所需的工具,写好单元测试后,可以通过 go test
命令运行测试。
testing
为 Go 语言 package 提供自动化测试的支持。通过 go test
命令,能够自动执行如下形式的任何函数:
+
|
+
+
|
要编写一个新的测试套件,需要创建一个名称以 _test.go 结尾的文件,该文件包含 TestXxx
函数,如上所述。 将该文件放在与被测试文件相同的包中。该文件将被排除在正常的程序包之外,但在运行 go test
命令时将被包含。
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
单元测试可以重复,所以会经常使用 表驱动 风格编写单元测试, 表中列出了输入数据,预期输出,使用循环,遍历并执行测试逻辑。
+
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
具体日志采集方案在Grafana+Loki+Promtail日志收集方案
文章中已经介绍过,此处不再重复介绍。不太了解的小伙伴儿赶紧去复习!
此处主要是记录下Docker Driver Client
方式的部署。
安装loki
插件。
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
容器运行时需要修改log-driver
:
+
|
+
+
|
举个例子:
+
+
|
+
+
|
由此查看日志,其labels
只有container_name
。
Grafana
项目由TorkelÖdegaard于2014年发起,最近几年成为GitHub上最受欢迎的开源项目之一。 它使您可以查询,可视化指标并记录警报,无论它们存储在何处。
Loki
是受Prometheus启发的水平可扩展,高度可用的多租户日志聚合系统。 它被设计为非常经济高效且易于操作。 它不索引日志的内容,而是为每个日志流设置一组标签。
Promtail
是将本地日志内容发送到私有Loki实例或Grafana Cloud的代理。 通常将其部署到需要监视应用程序的每台机器上。
为什么选择Grafana
+ Loki
+ Promtail
的日志采集方案呢?
我尝试过如下几种方案:
+Elasticsearch
+Kibana
+Filebeat
: 运维成本低,侵入性低,但是对于高并发情况下效果不太好,消耗资源也稍高,需要考虑日志存储成本Elasticsearch
+Kibana
+Logstash
+Kafka
+Filebeat
: 可以有效处理高并发情况,且在elk
节点挂掉情况下不会丢失日志。但是,运维成本高,需要考虑日志存储成本,整套消耗资源比较高!Grafana
+Loki
+Docker Driver Client
:使用Docker Driver的方式来直接获取容器的日志,配置较简单,但是需要物理机上安装docker plugin,和运行容器时设置log-driver
,侵入性较高相比之下,当前选择的方案,对于我们当前业务场景下是较为合适的,轻量且侵入性低,由于是检测日志文件,无需担心存储成本。
+首先介绍下通用日志收集版本的部署。只需要使用默认配置即可收集普通日志,可在http://<your-ip>:3000
上查看日志详情。
+
|
+
+
|
部署成功之后,打开http://<your-ip>:3000
访问Grafana
,在左侧菜单栏选择Configuration
,默认进去Data Sources
页面。
点击Add data sources
按钮,选择Loki
。
填入URL
即可,此处为http://loki:3100
,具体要看实际部署。
然后点击Sace & Test
添加。
在左侧菜单栏选择Explore
进入页面,点击左上角的Log brwser
按钮,可以查看该数据源的labels
,如此处为日志文件。
在页面顶部的输入框中输入官方的LogQL
可以筛选日志。此处日志就不展示了,大家知道有就行了。
此处详细介绍下关于docker容器日志收集。
+
+
|
+
+
|
+
|
+
+
|
至此其实就已经可以在Grafana
上看到当前容器的日志了,操作如上通用日志采集
,但是其展现形式只是filename
,也就是类似于107728869f40afa5510879a0e372c77bb513d6154591193d375bfcd421357ed4.log
的以container_id展现的日志文件,难以辨认具体是哪个容器。(要是有功夫去登录服务器看下容器id,不如直接看下日志了…)
所以,为了能够使用容器名去查看日志,此处需要在容器启动时设置参数:
+
+
|
+
+
|
举个例子:
+
+
|
+
+
|
对于使用container name,还有另一种方案,就是每次生成container_name和container_id的映射表,个人认为比较麻烦,有兴趣的小伙伴儿可以尝试下。
+要实现h5检测手机摇一摇动作可以直接调用h5原生api。但是在我的实践中发现在ios
中限制条件比较多,体验还是有些区别的。
调用Window: devicemotion event
即可实现监听。devicemotion
事件以固定的时间间隔触发,并指示设备当时在接收的加速物理力量。 它还提供有关旋转速率的信息(如果有)。
+
|
+
+
|
安卓机上直接按照如上即可实现。
+
+
|
+
+
|
在ios
上限制有两条:
https
协议的devicemotion
+
|
+
+
|
直接调用该函数请求授权会导致报错:
+
+
|
+
+
|
需要用户主动去请求授权,因此此处需要将调用放到比如一个按钮上,让用户去点击请求授权。
+
+
|
+
+
|
+
|
+
+
|
开始是开发electron
时遇到的问题,使用Interval
计时器,在窗口最小化隐藏再打开,计时器在隐藏期间并没有工作。
后来网上查询相关问题,发现更多是在浏览器tab页隐藏/切换情况下,计时器就会停止。
+++在后台选项卡上运行的计时器方法可能会耗尽资源。在后台选项卡中以非常短的时间间隔运行回调的应用程序可能会消耗大量内存,以至于当前活动选项卡的工作可能会受到影响。在最坏的情况下,浏览器可能会崩溃,或者设备的电池会很快耗尽。
+
此限制是浏览器限制的。
+无法突破限制,但是可以使用折中的方式,当然我也觉得此方式相较于一直计时会更优,即监听visibilitychange
事件。
visibilitychange
事件可以监听tab页面的激活与失活事件,因此可以:
+
|
+
+
|
IOS监听手势使用的方法为UISwipeGestureRecognizer
。
+
|
+
+
|
+
|
+
+
|
把上面的整合起来,基本可以按照这个模板来写。
+
+
|
+
+
|
Linux对于开发者来说极其友好,但是由于国内主流办公产品相关的生态较为匮乏,因此如何使用Linux去分享文件是一件十分头疼的问题。
+对于这个问题,可以直接使用静态文件服务器解决部分需求,如下介绍几个常见方法。
+对于Python
来说,可以直接使用内置的库来实现。
python2
+
+
|
+
+
|
Python3
+
+
|
+
+
|
node
生态内有一个项目http-server
,直接V8
引擎带你飞。
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
例如:
+
+
|
+
+
|
https://github.com/http-party/http-server
+Nginx
和Apache
本身可用于静态文件服务器,这就需要用户直接在本地安装。
当然,nginx
需要注意配置一下,打开索引:
+
|
+
+
|
使用Docker
其实也是使用如Nginx
来实现静态文件服务器,但是容器化在该场景存在几大优势:
相对于直接安装Nginx
或者Apache
,更推荐使用Docker
。
+
|
+
+
|
Nginx
配置加上如下配置
+
|
+
+
|
使用electron
开发的windows桌面应用程序,在调用目标文件夹底下的exe执行文件时,开发机子上没有问题,但是其他机子使用时一直调用失败,也抓取不到日志。
+
|
+
+
|
路径存在空格。
+也是经过各种原因排查,然后一次偶然的成功才注意到了路径问题,排查之后发现确实是这问题……
+spawn
按照如上我的代码一定条件下可以运行,其有一个参数cwd
,用来表明运行目录。spawn
第一个参数必须是命令的名字,不能是路径。
所以如上代码改成这样:
+
+
|
+
+
|
在上一篇文章——前端文件花式直传OSS!后端:那我走?中聊了下文件上传的几种方案,这里我们再来聊一下文件下载的花式姿势。
+最常见的方式,莫过于后端存储文件在服务器上,然后通过后端接口传给前端,如下图所示。
+ +该方案对于小规模、成本较低的项目非常适用,开发也较为便捷。
+而对于有性能要求的项目,可以通过砸钱加机器、分片下载等方案提升项目性能。如果可以的话,请砸钱加机器吧!
+ +相较于上一个方案,可以砸丢丢钱整个OSS,将文件存储在OSS上,毕竟OSS上行流量不收费,如下图所示。
+ +那么问题来了,OSS的下行流量不是收费的吗?!
+OK,偷偷告诉各位一个省钱小妙招/狗头,OSS内网的下行流量是不收费的!因此,可以通过后端请求OSS,获取到文件/字符串后,将其以文件流/base64数据的方式返回给前端。这样就避免了下行流量的费用。如下图所示。
+ +不过问题又来了,这样就还是占用了后端服务器的资源,依然会是性能的一个瓶颈。
+ +基于上一个方案,可以再升级。砸丢丢钱,拉上CDN这老哥,利用CDN流量代替OSS的下行流量,既能让前端直接请求OSS资源,不占用服务器资源,也降低了成本。如图所示。
+ +在优先性能的情况下,该方案是较优的。
+要说缺点的话,就是CDN配置吧,需要买域名和SSL证书等,不过一次购买,后续使用体验会非常棒。CDN除了可以代替OSS的下行流量外,其优点不要太多,比如说CDN可以文件缓存、可以调度至加速节点等。
+涉及到OSS的私有Bucket的话,只需要使用CDN的访问控制即可。其也只需要通过后端实现加密,生成文件访问URL给前端直接访问。
+或许你会存在疑问,看上去挺麻烦的啊!但是看看CDN的价格,你肯定会有不一样的想法的。
+ +在使用axios的拦截器时候,需要在request中调用一个promise函数,因此需要等待其执行完成才能去进行下一步。
+
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
部署SFTP服务器,数次遇到几个报错,特此记录
+路径
+
+
|
+
+
|
用户为tom
+
|
+
+
|
+
|
+
+
|
以上两个报错,此处为统一解决。
+
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
为什么要用sqlx
而不是gorm
呢?是因为orm
学习成本比较高,当使用python
时候需要使用sqlalchemy
,遇到go
就要换成gorm
,换成别的语言就又有其他orm。而直接使用原生sql
可以减少学习成本,适用于所有开发语言。其次,gorm
本身支持软删除,但是其对软删除的支持上存在缺陷,在单条查询可以过滤软删除数据,但是在多条查询时无法有效过滤,就造成了有时候要手动过滤又有时候不要手动过滤,使用体验非常差。
因此此处考虑去使用sqlx
来直接调用原生sql
。
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
此处以增删为例。封装常用方法是为了复用,封装时候使用害羞的代码。不需要为了封装而封装。
+
+
|
+
+
|
此处以新增接口为例。
+
+
|
+
+
|
+
|
+
+
|
这篇文章是我写的第一篇Swift UI相关的笔记吧。
+这真的难搞哦,毕竟新出来的。国内文档真的是少之又少,国外的文档也真不多,基本搜出来的都是UIKit的。官方文档,也是一言难尽,基本就处于,我要实现一个功能,然后去搜一下各种帖子,筛选掉无用的帖子,找到有用的点,当然也是时常根本找不到有用的帖子,然后就要去油管上看各种教程,然后发现:哦!原来还有这个方法!接着去官方文档搜一下,看一下属性,自己调用调用……
+光是这个core data我就折腾了近一周,最后发现,原来还是官方好呀…..
+吐槽一下自学swift ui,现在进入正题了。
+core data呢,是苹果官方的本地数据库,但是其存储的文件其实是sqlite
文件。其可以通过icloud实现备份和同步,当然icloud我会额外写一篇文档来详细讲述的(又是一把辛酸泪…)。
如下是我当前的环境:
+在创建项目的时候,可以直接选择Use Core Data
选项,xcode会直接在ContentView.swift
中生成一个相关demo。
创建之后,查看文件目录,相对于不选择Use Core Data
,会多出如下几个文件:
点击<Your-Project-Name>.xcdatamodeId
文件,进入页面。
可以看到页面内有默认的CONFIGURATIONS
为Default
,默认的ENITITIES
为Item
,可以理解为分别对应sql中的库和表。
然后点击Item
,可以看到其字段,有默认的timestamp
字段。
若要新增ENTITIES
(表),点击底部的Add Entity
按钮即可。
若要新增Attributes
(字段),点击右侧Attributes
中的+
即可,注意字段要选择类型。
比如,此处以默认的Item
为例,新增username
和age
两个字段。
+
|
+
+
|
+
|
+
+
|
如上是xcode自动生成的新增函数,若是要自定义添加字段可以这样改动下:
+
+
|
+
+
|
+
|
+
+
|
在默认生成代码中,有一个toolbar
,其在macos
中可以生效,但是在ios
中只有EditionButton()
可以使用,为了方便演示,此处新增一个Button
来添加数据。
其次有点要声明下,在xcode中写代码时,右侧的canvas
会实时渲染,列表中出现的数据并不是core data
中的数据,而是默认生成的Persistence.swift
中生成的演示数据,只能看看,不能当真。只有在模拟器/实体机编译运行时才能操作core data
。
如下为修改过后的ContentView.swift
文件:
+
|
+
+
|
然后点击左侧顶部的运行按钮,编译运行。
+一开始是空白一片,点击Add Item
按钮之后,便会开始添加数据。
对于记录右滑即可删除,其为List
的onDelete
方法。
该文章是面向新手的,也是记录下我踩过的坑,因为目前文档匮乏,身边也没swift的开发小伙伴儿,只能靠自己摸索,若有大佬有更好的方法,真的还请不吝赐教。
+后面持续记录踩坑中……
+ +使用Alert
时,将其用在list
的循环视图元素中,弹出Alert
时,一定时长不选择就会在点击后弹出第二次。
这里提一下就是之前在网上看到一个帖子说他将Alert
放在NavigationView
上也会出现该问题。
+
|
+
+
|
将Alert
放到循环之前的元素上,比如VStack
、List
。
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
在使用Swift UI
和Realiy
开发AR项目时,发现摄像头一直是居中的,无法全屏。
LaunchScreen.storyboard
文件在左侧文件列表中新建文件,名为LaunchScreen.storyboard
。
Launch Screen File
点击左侧文件列表中你的项目文件(最顶级文件),进入文件[your-project].xcodeproj
文件。
在General
中,找到App Icons and Launch Images
,在其模块中有Launch Screen File
选项,点击选择为LaunchScreen.storyboard
。
总结下就是:[yourTarget] -> General -> App Icons and Launch Images
。
swiftui中的点击可以有两种情况:
+根据不同情况可以去不同地使用。
+此处单纯的按钮即为有按钮样式和点击事件。
+
+
|
+
+
|
即为没有按钮样式的按钮,方便直接展示Image
。
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
这是个比较坑的问题,我一开始开发的是macos项目,到网上搜的方案基本都是使用UIPasteboard
方法,但是偏偏用不了。
后来开发ios项目,用macos的就不行,发现UIPasteboard
的可行,所以这里需要清楚的是,ios和macos的复制方法是不同的……
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
此处过滤条件为判断元素是否包含搜索的文本。
+
+
|
+
+
|
+
|
+
+
|
打开文件info.plist
,在空白处右击,选择Add Row
,输入选择Privacy - Face ID Usage Description
,然后在value
中写入我们需要验证您的身份以保护数据
。
打开ContentView.swift
文件,开始如下操作。
+
|
+
+
|
+
|
+
+
|
isUnlocked
为是否解锁,true
表示验证完成,已解锁,false
表示验证失败,未解锁。
+
|
+
+
|
+
|
+
+
|
最近提交了ios和macos两个产品到app store,我设置的强制使用生物识别才能进入应用。但是出现的问题是macos的审核过了,而ios的审核没有过,其反馈的问题即为开始的生物识别没有过,审核人员使用的模拟器,根本不存在生物识别。
+所以跟可能会踩这个坑的小伙伴儿提个醒,目前我还没有好的解决方案,正在等待新的审核中……
+ +A date or time specified in terms of units (such as year, month, day, hour, and minute) to be evaluated in a calendar system and time zone.
+以要在日历系统和时区中计算的单位(例如年、月、日、小时和分钟)指定的日期或时间。
+
+
|
+
+
|
+
|
+
+
|
++Rapidly build modern websites without ever leaving your HTML.
+
Tailwind CSS可以快速建立现代网站,而无需离开HTML。其特性是原子化,很像的BootStrap
的css。
通俗点解释就是,其封装了很多独立的css样式,只需要在html中添加class
即可调用,而不需要去从头写css样式。
+
|
+
+
|
可能会遇到如下报错:
+
+
|
+
+
|
那就需要降低PostCSS
的版本。如下,先卸载,再去安装。
+
|
+
+
|
添加tailwindcss
和autoprefixer
到PostCSS
配置。大部分情况下作为postcss.config.js
文件放在项目的顶级路径下。其也能作为.postcssrc
文件,或者使用postcss
键放在package.json
文件中。
+
|
+
+
|
如果想自定义安装,当使用npm
安装tailwindcss
时候需要使用tailwind命令行去生成一个配置文件。
+
|
+
+
|
这将会创建一个最小化的tailwind.config.js
文件,其位于项目的顶级路径下。
+
|
+
+
|
创建styles.css
文件。
+
|
+
+
|
引入该文件。
+
+
|
+
+
|
为生产而构建时,确保配置清除选项以删除任何最小文件大小的未使用类。
+
+
|
+
+
|
由于其样式属性巨多,此处只举几例作简要说明,讲解基础用法。在开始不熟悉的情况下,要开着其手册查询。
+Class | +解释 | +
---|---|
w-0 | +width: 0px; | +
w-1 | +width: 0.25rem; | +
w-1/2 | +width: 50%; | +
w-full | +width: 100%; | +
… | +… | +
+
|
+
+
|
Class | +解释 | +
---|---|
p-0 | +padding: 0px; | +
p-5 | +padding: 1.25rem; | +
pl-1 | +padding-left: 0.25rem; | +
… | +… | +
+
|
+
+
|
Class | +解释 | +
---|---|
static | +position: static; | +
fixed | +position: fixed; | +
absolute | +position: absolute; | +
… | +… | +
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
最近在用uniapp开发小程序,需要用到canvas画海报然后再保存本地。
+之前写过同样功能的文章,不过场景不同,之前是在web上生成海报,该场景可以使用之前文章的方法——html转canvas来实现。
+但是uniapp则不同,该框架是去DOM化的,因此只能使用uniapp的官方canvas来实现。
+网上找到的文章,有几篇写得挺好的,展现了完整的功能。这里我把用到的几个功能拆解出来,而不用先通读整篇代码。
+
+
|
+
+
|
+
|
+
+
|
图片直接在项目中,可以直接加载。
+
+
|
+
+
|
此处url返回的为文件流,在uniapp中无法直接加载,需要转换成本地信息才可以使用。
+有两种方式可以使用,小程序都需要添加download合法域名:
+原生使用方法是这样的:
+
+
|
+
+
|
由于js的异步问题,如果图片较大或者多个图片的情况下,会有这边还没加载完,canvas就已经绘制完了的情况,所以这里将其优化下。
+
+
|
+
+
|
如果你的图片数据是base64的,那恭喜你,依然加载不了。当然这存在的情况是,微信开发工具是没有问题的,但是上了真机之后直接无法加载了,这波是小程序的锅。
+这里呢需要将图片存储然后用本地地址绘制。
+原生方法:
+
+
|
+
+
|
优化一波:
+
+
|
+
+
|
+
|
+
+
|
想要绘制一个圆角的矩形,啊……这波就复杂了,原理就不细讲了,直接上代码,调用即可。
+
+
|
+
+
|
基本原理是,正常加载图片,canvas画个圆给它裁剪掉,上代码!
+
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
使用 v-charts
做数据可视化,需要给图表添加标题。
v-charts本身并没有提供显示标题的配置,顾需要引入 echarts
的 title
。
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
首先,介绍下vite
和Electron
。
当开始想用vue去开发一个桌面应用时,首先去搜索下,了解到当前如下两种现成方案:
+vue-cli
中使用,使用vue add electron-builder
后可直接上手,免去了基础配置的步骤。但是其只能在vue-cli
下使用,无法配合vite
来使用。因此,若要使用vite
和electron
,还需要自己来配置。
源码:https://github.com/Kuari/Blog/tree/master/Examples/vite_electron/vite_electron_1
+
+
|
+
+
|
创建命令如下:
+
+
|
+
+
|
此处创建一个项目,名为kuari。
+
+
|
+
+
|
进入项目,在运行前需要先安装下依赖。
+
+
|
+
+
|
在运行命令敲下的一瞬间,几乎是已经在运行了,不愧是vite。此时按照输出,打开地址预览,即可看到初始化页面。
+ +至此一个基础的vite项目创建完成。
+在Electron官网的快速入门文档中,有官方给出的利用html、javascript、css来创建一个electron应用的案例,vite+electron的方案也借鉴其中。
+首先安装electron至vite应用。目前electron的版本为^15.1.2,
。
+
|
+
+
|
####config.js
+
+
|
+
+
|
####js
+创建一个新的文件main.js
,需要注意的是,该内容中index.html
的加载路径跟electron官网给的配置不同。
+
|
+
+
|
####js
+创建一个新的文件preload.js
。
+
|
+
+
|
####json
+为了确保能够运行相关electron的命令,需要修改package.json
文件。
首先需要去设置main
属性,electron默认会去在开始时寻找项目根目录下的index.js
文件,此处我们使用的是main.js
,所以需要去定义下。
+
|
+
+
|
最后我们需要新增electron的运行命令。
+
+
|
+
+
|
直接在终端输入如下命令:
+
+
|
+
+
|
接着我们就可以看到我们桌面应用就出来咯!
+ +之前做项目一直用的Vue CLI Plugin Electron Builder,这次有个项目先用electron开发一下,推一波看看,后期看情况swift重新开发一个mac的桌面应用。也刚好尝尝鲜,一直没有机会试试vite。
+electron这个东东确实很方便,就是打包出来的应用体积太大,真的是硬伤啊。这次目标人群首先是windows用户,所以上electron吧!
+上一篇文章Vite+Electron快速构建一个VUE3桌面应用(二)——动态模块热重载完成了开发时的动态模块热重载功能,现在是时候来看看怎么完成最后一步——打包了。
+源码:https://github.com/Kuari/Blog/tree/master/Examples/vite_electron/vite_electron_3
+系列文章:
+先说结论,重点还是在于mainWindow.loadURL()
。
打包后还是加载http://localhost:3000
是无法运行的,因此,此处需要先用vite打包好,然后使用electron-builder
加载vite打包后的文件进行打包。
为了代码能够根据不同环境在运行时加载http://localhost:3000
,在打包时加载文件,此处需要使用环境变量来切换生产和开发环境。
此处使用环境变量NODE_ENV
来切换生产和开发环境,生产环境为NODE_ENV=production
,开发环境为NODE_ENV=development
,若有其它如release
等环境可在此基础上拓展。
在项目根目录下创建文件夹electron
,将main.js
和preload.js
文件移动进来。其结构如下所示:
+
|
+
+
|
若还是不太明白可以看看源码中文件结构。
+该文件主要是需要根据环境变量切换electron加载的内容,修改内容如下:
+
+
|
+
+
|
修改后的完整内容如下:
+
+
|
+
+
|
首先修改main
属性,将main: main.js
改为main: electron/main.js
。
+
|
+
+
|
接着,编辑build
属性:
+
|
+
+
|
然后,更新scripts
属性。
此处需要先安装两个库:
+cross-env
: 该库让开发者只需要注重环境变量的设置,而无需担心平台设置electron-builder
: electron打包库
+
|
+
+
|
更新后的scripts
如下:
+
|
+
+
|
最后,更新后的package.json
完整内容如下:
+
|
+
+
|
直接执行打包命令即可开始打包。
+
+
|
+
+
|
打包完成之后,会多出两个文件夹dist
和dist_electron
,其文件结构如下:
+
|
+
+
|
至此,便完成了打包。
+后面再来写写关于electron的优化,减少electron打包后应用的体积。(这玩意儿确实打包下来有点大呢/狗头)
+ +在上一篇文章Vite+Electron快速构建一个VUE3桌面应用中,我们了解了如何使用Vite
和Electron
来快速构建一个Vue3桌面应用。但是,之前构建的应用仅仅是一个简单的版本。在开发过程中,为了更好的开发体验,在开发electron的时候,肯定也希望能有动态模块热重载(HMR),更别说vite那迅雷不及掩耳盗铃儿响叮当之势的加载速度。
因此,接着上一篇文章所完成的项目代码,我们来完成Vite
和Electron
开发时的动态模块热重载功能。
源码:https://github.com/Kuari/Blog/tree/master/Examples/vite_electron/vite_electron_2
+系列文章:
+先说结论,可利用electron中的mainWindow.loadURL(<your-url>)
来实现。
对于动态模块热重载功能来说,无论是webpack还是vite,其都是将构建内容存入内存,因此我们无法使用mainWindow.loadFile('dist/index.html')
这样加载文件的方式。
但是,单纯地改变该配置也是不行的,需要使用vite将开发服务器运行起来,可以正常运行动态模块热重载,而electron直接加载其开发服务器可访问的url,即http://localhost:3000
。
将mainWindow.loadFile('dist/index.html')
更新为mainWindow.loadURL("http://localhost:3000")
,更新后的文件如下所示:
+
|
+
+
|
修改文件vite.config.js
的base
,修改后的文件如下所示:
+
|
+
+
|
为了使vite和electron正常运行,需要先运行vite,使得其开发服务器的url可以正常访问,然后再开启electron去加载url。
+此处需要安装两个库:
+-k
参数用来清除其它已经存在或者挂掉的进程首先来安装。
+
+
|
+
+
|
接着更新文件package.json
,scripts
新增两条命令:
+
|
+
+
|
更新后完整内容如下:
+
+
|
+
+
|
现已添加两条命令:
+yarn electron
为等待tcp协议3000端口可访问,然后执行electronyarn electron:serve
为阻塞执行开发服务器运行和yarn electron
命令运行项目只要执行命令yarn electron:serve
即可,当修改项目文件时,桌面应用也将自动更新。
为了前后端传输数据的安全性,需要对数据进行加密。因此选定使用非对称加密,此处为RSA
。
在传输数据前,后端生成公钥和私钥,将公钥给前端,前端加密之后,将密文传给后端,后端使用私钥解密即可得到原始的数据。
+jsencrypt
加密Crypto
生成密钥和解密
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
base64
先解密一遍是必要的,否则报错ValueError: Ciphertext with incorrect length.
+
|
+
+
|
+
|
+
+
|
RSA
加密信息最长为128
位,过长则会报错,因此,对于过长的信息需要分段加密,后端也要分段解密后拼装。
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
关于小程序内置浏览器的图片下载,需要一个用来生成图片的块,还需要一个img
,先将其隐藏。实现步骤就是首先生成二维码,然后再将html生成图片,最后在html2canvas回调中替换img
的src
,并将生成图片的块隐藏,将img
显示。
当然关于这个实现方式,我看到的技术分享文章中,还有两种不同的解决方式:
+这里我只记录一下我使用的,后期会再去研究这两种实现方式。
+
+
|
+
+
|
由此即可实现需要的功能了。
+关于后续的优化,需要解决的图片清晰度问题、跨域图片问题等,可以参考这篇文章,这位大佬写得很详细。
+ +最近空下来,正好找个项目尝鲜,把vue3+ts+setup哐哐全堆上,试试最新的前端技术。
+从最先体会到的变化,就是关于响应式APIs了。遇到不好问题,怪我没有理解文档/狗头。比如说:
+所以这里基于遇到的几个问题,来写个笔记。
+官方案例如下:
+
+
|
+
+
|
我们可以先看看,如果是vue2,怎么做呢?
+
+
|
+
+
|
可以很明显地看到此处的count
跟上面的官方文档不同,使用了ref
方法。这就是setup中的响应式APIs,需要预先声明响应式变量。
如果,不声明呢?那就是直接写成如下:
+
+
|
+
+
|
运行一下,首先你会发现,没有任何报错,代码正常运行。但是当你在浏览器上查看,开始改变count
的值时,就会发现,怎么页面没有变化?
所以,需要手动命名响应,才会在值变化时触发视图渲染。
+现在来详细讲讲ref
,该方法常用于单个变量,比如:
+
|
+
+
|
这里需要说明一个问题,那就是ref("hello") !== "hello"
。这就是我说的,为什么同样的值,类型就不同了。那是因为ref返回的是一个Proxy
,而非原来的值。在视图中可直接使用,但是在js/ts中操作,需要使用.value
来操作,如下所示:
+
|
+
+
|
reactive
不同于ref
的点在于,其是“深层”的——它影响所有嵌套 property。也就是说,其可用在对象或者数组上。
+
|
+
+
|
其返回类型也是Proxy
,不同点在于,可以直接修改某一个元素的,如下所示:
+
|
+
+
|
但是如果你想整个替换就会报错了。
+
+
|
+
+
|
当使用的是数组时,如果想整个替换,可以将其写成对象,代码如下所示:
+
+
|
+
+
|
关于reactive
跟ref
一起使用,reactive
将解包所有深层的ref
,同时维持 ref 的响应性。
+
|
+
+
|
setup
总体来说,用起来真的会更加简洁,而响应式虽然好像比之前麻烦些了,但是一定层面上让开发对对于程序有了更深入的操控。墙裂推荐一波!后面再来详细讲讲对于新特性的体验。
在使用vue3+typescript+electron开发时,遇到一个报错为:
+
+
|
+
+
|
点进去是module.exports = require("events")
,并不是自己的代码中的require
,因此无法改变写法只能让项目去支持它。
在electron的配置文件中,新增或者修改如下配置:
+
+
|
+
+
|
基于架构的调整,前端开始转为微前端。经过调研,决定使用qiankun微服务框架来使用,本文将介绍VUE3+TS+qiankun的实践经过。微服务架构的优势之一在于可以结合不同技术栈的节点,基于技术栈的考虑,此处用的都是vue3。
+源码:https://github.com/Kuari/Blog/tree/master/Examples/microFrontend
+如上图所示,微服务架构将会由多个节点构成,首先由一个主节点site_base
连接所有子节点,子节点可以不断拓展。
主节点源码可见于https://github.com/Kuari/Blog/tree/master/Examples/microFrontend/site_base
+创建主节点,选择vue3+ts
+
+
|
+
+
|
安装qiankun
+
|
+
+
|
在src/App.vue
中添加路由和渲染节点
+
|
+
+
|
在src/main.ts
中引入子节点配置
+
|
+
+
|
子节点源码可见于https://github.com/Kuari/Blog/tree/master/Examples/microFrontend/site_1
+此处以site1
为例,site2
同理。
创建子节点,选择vue3+ts
+
+
|
+
+
|
编辑src/App.vue
+
|
+
+
|
编辑src/views/Home.vue
,修改其内容,写一点标识性的文本
+
|
+
+
|
创建文件src/pulic-path.ts
,第一行的注视一定要加,避免eslint对于变量的报错
+
|
+
+
|
编辑src/router/index.ts
,此处直接返回routes
,而不是router
,并且修改
+
|
+
+
|
编辑src/main.ts
+
|
+
+
|
创建文件vue.config.js
+
|
+
+
|
主节点和子节点分别独立运行,但是子节点的地址需要跟主节点配置中子节点对应的地址相同。
+在主节点上点击子节点的路由,即可在主节点上访问子节点的页面了!
+ +主节点除了如上配置,可以进行两项优化:
+优化后主节点源码可见于https://github.com/Kuari/Blog/tree/master/Examples/microFrontend/site_base_optimize
+创建文件夹src/childNodes
,然后创建文件src/childNodes/apps.ts
+
|
+
+
|
创建文件src/childNodes/index.ts
+
|
+
+
|
编辑src/main.ts
+
|
+
+
|
此处的过渡效果采用NProgress
库,先来安装一波
+
|
+
+
|
编辑src/childNodes/index.ts
+
|
+
+
|
qiankun
框架确实挺不错的,配置也并不是复杂,但是唯一想吐槽的一点是对于ts的支持感觉不太好/狗头,或许是我写得不够好吧,后面会持续优化使用。
前端导入excel表格,直接前端解析文件,将数据传给后端。
+
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
原本的文件上传样式可能会跟页面整体风格不搭,所以需要修改其样式。不过此处并不是直接修改其样式而是通过写一个div
来覆盖原有的上传按钮。此处样式与element UI
中的primary
按钮样式相同。
实现该样式的关键在于.upload_file
的opacity
和position
。
+
|
+
+
|
前端的日益强大导致很多功能都可以在前端去直接实现,并且可以减少服务器压力。
+当然单纯地去实现这样的数据传输,尤其对于重要数据,是很不安全的,因此在前后端数据传输的时候,可以加上加密校验,这个后期会来写的。
+为了实现该功能参考了如下大佬的文章:
+ + +按照ElementUI官方文档按需引入却报错,首先报错缺少babel-preset-es2015
。安装该组件之后编译却报错。
+
|
+
+
|
该问题为babel
版本冲突。
+
|
+
+
|
.babelrc
+
|
+
+
|
功能开发过程中写遮罩时,遇到遮罩下页面还可以滚动的问题。
+直接给遮罩下的元素套上一个样式,使其不可滚动。
+
+
|
+
+
|
在vue2中不允许子组件直接修改props
,为单项数据流,所有若要修改只能通过额外的值,并监听props
以改变额外的值。
+
|
+
+
|
在data
中创建一个localDialog
,其值为this.dialog
。
+
|
+
+
|
保持同步的关键在于需要在子组件内监听props
,即此处的dialog
。
+
|
+
+
|
子组件使用this.$emit()
即可向父组件传递变化的值。
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
i18n
是“国际化”的简称。在资讯领域,国际化(i18n)指让产品(出版物,软件,硬件等)无需做大的改变就能够适应不同的语言和地区的需要。对程序来说,在不修改内部代码的情况下,能根据不同语言及地区显示相应的界面。 在全球化的时代,国际化尤为重要,因为产品的潜在用户可能来自世界的各个角落。
Node.js本身有一个i18n
的包,但是为了更好地结合vue,此处我们使用的是vue-i18n
。
源码:https://github.com/Kuari/Blog/tree/master/Examples/vue_i18n_demo
+使用vue cli安装,会自动生成文件且引入。
+
+
|
+
+
|
执行过程中将需要填写如下问题:
+
+
|
+
+
|
执行完成之后,将会自动处理如下文件:
+
+
|
+
+
|
想要多语言则需要配置多个对应语言的json文件,其字段必须相同,否则当用户切换时,会出现找不到该字段对应文字的问题。
+为了便于切换和处理,此处不再使用en
之类的简写,而是和系统语言名称对应起来,此处将使用zh_CN
和en_GB
来做配置。
此处生成两个文件src/locales/zh_CN.json
和src/locales/en_GB.json
,内容分别如下:
+
|
+
+
|
+
|
+
+
|
将相关文件的en
都改成en_GB
。
+
|
+
+
|
+
|
+
+
|
此处重写src/views/Home.vue
文件,先按照正常的内容去写,内容如下:
+
|
+
+
|
需要配置多语言的地方就是展示的文字,所以将该文字替换掉即可。
+
+
|
+
+
|
此处引入t
方法,而其参数message
为上面配置的每一个json文件中的message
字段,其将用json对应字段的value来展示。
可能有小伙伴儿要问,那如果不是html中的展示文字怎么办呢?
+这也一样使用t
方法的,示例如下:
+
|
+
+
|
+
|
+
+
|
此处我们的json结构都是单层的,如果是嵌套的结构的话,可以使用t('home.message')
这样的方式来引用。
此处需要引入locale
,主要通过更新locale.value来切换语言。此处的切换是全局的,所以只需要写一个切换组件,其它组件都将会被切换语言。
写一个下拉框来实现语言的切换,完整内容如下:
+
+
|
+
+
|
通过下拉框切换选项可以切换语言。
+虽然i18n
设置了默认的语言,但是友好的交互应当是根据用户的环境语言加载。这里需要使用navigator.language
来拿到用户的环境语言,通过判断环境语言切换初始的语言配置。
+
|
+
+
|
完整代码如下:
+
+
|
+
+
|
运行之后,默认语言不再是英语,而是跟环境语言一致的。
+WebAssembly 是一种新的编码方式,可以在现代的网络浏览器中运行 - 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如 C / C ++等语言提供一个编译目标,以便它们可以在 Web 上运行。它也被设计为可以与 JavaScript 共存,允许两者一起工作。
+
+
|
+
+
|
+
|
+
+
|
该文件为go的wasm的js支持文件
+
+
|
+
+
|
+
|
+
+
|
浏览器加载html文件,f12打开控制台,可以看到wasm的打印消息。
+
+
|
+
+
|
如上为官方给出的go与js的类型映射表。
+比如在go中调用js函数,参数为array
,那么就可以直接将go的[]interface{}
类型的变量作为参数使用。
syscall/js
提供了两个函数:
func CopyBytesToGo(dst []byte, src Value) int
func CopyBytesToJS(dst Value, src []byte) int
两者对于go而言,类型都是[]byte
,但是对于js而言,需要Uint8Array
或者Uint8ClampedArray
类型,否则就会报错。
那么,如何在go中生成一个Uint8Array
或者Uint8ClampedArray
类型的变量呢?官方的类型映射表也没有啊…那么就看下一步。
对于非官方类型映射表内的类型,和官方提供的两个数据类型转换之外的类型,可以通过一种通用的方式来生成,以上一步的Uint8Array
为例:
+
|
+
+
|
实际使用案例:
+
+
|
+
+
|
那么,比如js中的Date
类型:
+
|
+
+
|
好吧,还有最后一个方案,如果遇到极端情况,上述方案都无法解决,那么请转换成字符串吧!让go和js用各自的方法分别处理一波,得到自己想要的结果或者给出各自想给的数据。
+++此处需要在go中引入syscall/js,以实现js相关的操作。
+
将go的函数注册为js的函数,由js来进行调用。
+
+
|
+
+
|
js.Func()
接受一个函数类型作为其参数,该函数的定义是固定的:
+
|
+
+
|
js.ValueOf返回作为js的值:
+
+
|
+
+
|
在js中使用也非常简单,引入wasm文件之后,直接调用函数即可。
+
+
|
+
+
|
如果在js中本身已经定义了函数,那么在go中也可以直接调用该函数,进行运算,将得出的结果在go中继续使用。
+
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
在前端调试台可以看到输出:
+
+
|
+
+
|
第一个结果就是js.Value的值,第二个结果则是转换成go的值,并按照逻辑进行了+1
处理。
++The Go function fn is called with the value of JavaScript’s “this” keyword and the arguments of the invocation. The return value of the invocation is the result of the Go function mapped back to JavaScript according to ValueOf.
+Invoking the wrapped Go function from JavaScript will pause the event loop and spawn a new goroutine. Other wrapped functions which are triggered during a call from Go to JavaScript get executed on the same goroutine.
+As a consequence, if one wrapped function blocks, JavaScript’s event loop is blocked until that function returns. Hence, calling any async JavaScript API, which requires the event loop, like fetch (http.Client), will cause an immediate deadlock. Therefore a blocking function should explicitly start a new goroutine.
+
syscall/js
官方文档表明,如果go包装函数阻塞,那么js的事件循环也将被阻塞,直到函数返回,调用任何需要事件循环(如fetch)的异步js api都导致立即死锁。因此,一个阻塞函数应该显式地启动一个新的协程。
此处,可以在go中注册一个回调函数,加上协程实现异步,不会产生堵塞。
+
+
|
+
+
|
+
|
+
+
|
在浏览器调试台,可以看到:
+
+
|
+
+
|
上一步的回调函数,解决了函数阻塞问题,此处,结合回调函数实现promise,来丰富异步场景。
+在js中,promise是这样的:
+
+
|
+
+
|
使用async
和await
调用,拿到结果:
+
|
+
+
|
在go中又如何构建promise呢?这里可以用到上述go与js的类型转换,创建一个promise:
+
+
|
+
+
|
go的完整实现如下:
+
+
|
+
+
|
+
|
+
+
|
在浏览器调试台,可以看到:
+
+
|
+
+
|
定义一个全局的document
+
+
|
+
+
|
获取一个id
为container
的div
,设置background-color: red
、widht: 600
、height: 400
+
|
+
+
|
创建一个id
为image
的image
,设置width:300
、height:200
+
|
+
+
|
将image
添加为id
为container
的div
的子元素
+
|
+
+
|
给image
添加右击事件,右击image
则阻止右键菜单
+
|
+
+
|
这里需要注意的是,当不再调用响应事件函数时,必须调用Func.Release以释放资源:
+
+
|
+
+
|
这里放一波canvas的案例,包含了一些常用方法,可以参考完成更多操作。
+
+
|
+
+
|
渲染结果:
+ +
+
|
+
+
|
在vue3
项目的main.ts
中引入
+
|
+
+
|
需要注意的是,animate css在4.0之后使用animate__
前缀
+
|
+
+
|
官方给出的动画延迟是animate__delay-2s
、animate__delay-3s
……
直接在class中添加即可
+
+
|
+
+
|
特殊场景需要使用不同于官方的延迟时间,因此可以自定义延迟时间,直接声明延迟的类,然后在class上加入即可
+
+
|
+
+
|
使用
+
+
|
+
+
|
+
|
+
+
|
在vue3
项目的main.ts
中引入,内容如下:
+
|
+
+
|
使用wow
直接替代animate__animated
即可
+
|
+
+
|
由于写一个页面需要使用到wow,好多年没用过了,查了一下文档超多版本教程,使用起来各种不成功,难受…暂时也没找到可替代的方案…
+前端小伙伴儿们是不是经常遇到ui组件全局引入导致体积太大,按需引入导致不断手写会很麻烦。所以,当当!今天我们来让代码自己按需引入,解放前端小伙伴儿们的生产力,早日实现下班自由!(甲方:我要再改十个需求!)
+我们这里介绍的是unplugin-vue-components
。该组件是由vue核心开发成员antfu开发的,尤大也是推荐的,且是该项目的金牌赞助商。
该组件主要是为了实现vue项目的组件自动引入。
+官方文档:antfu/unplugin-vue-components
+此处我们以vite为例,来主要看一下其对于自定义组件和UI库组件的自动按需引入。
+源码:https://github.com/Kuari/Blog/tree/master/Examples/unplugin_auto_import
+
+
|
+
+
|
然后进入文件夹安装依赖。
+
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
我们默认模板创建的项目中,默认在App.vue
中引入了./components/HelloWorld.vue
。此处就可以来尝试下如何自动引入了。
在配置了unplugin-vue-components
之后,现在只需要删除引入行(其实这时候打开vscode就会发现改行已经灰掉了),被删除行如下所示:
+
|
+
+
|
删除以后完整App.vue
如下所示:
+
|
+
+
|
现在在命令行中运行yarn dev
,再打开浏览器查看http://localhost:3000
页面,是不是发现,哎?!居然引入了!(心中狂喜,要早日实现下班自由了)
这里以element plus为例。
+首先是安装element plus
。官方文档也没说要装,我还以为内置呢,一直报错,有点懵逼,哈哈。
+
|
+
+
|
引入element plus的resolver,此处编辑vite.config.js文件,修改后如下所示:
+
+
|
+
+
|
那么现在神奇的事情来了,就可以直接使用UI库的组件了!
+我们在App.vue
中使用一个el-button
组件试试。我们在App.vue
中加入如下行:
+
|
+
+
|
添加后App.vue
上下代码如下:
+
|
+
+
|
现在,运行yarn dev
,打开浏览器,可以看到,就直接可以使用UI库的组件了。
按需引入的功能并不是仅仅在开发时候的,在打包时,该组件也是Tree-shakable的,只会将你用了的组件打包。
+按照当前教程所写的项目,当全局引入的时候,打包的dist文件夹为1.1MB,而使用该组件之后打包,其dist文件夹为202KB。
+手动按需导入是不可能手动按需导入的,这辈子都不可能了/狗头。
+毕竟是大佬开发的强力工具,希望前端小伙伴儿们早日实现下班自由。
+ +WebAssembly 是一种新的编码方式,可以在现代的网络浏览器中运行 - 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如 C / C ++等语言提供一个编译目标,以便它们可以在 Web 上运行。它也被设计为可以与 JavaScript 共存,允许两者一起工作。
+之前写过一篇文章,是关于如何使用golang来开发WebAssembly的——WebAssembly:未来前端开发的必备技能。
+Rust和Go都可以用来开发WebAssembly,但它们有各自的优势和劣势。
+Rust的优点:
+Go的优点:
+综合来说,如果你更注重性能和内存安全性,那么Rust可能是更好的选择。而如果你更注重开发效率和易用性,那么Go可能更适合你。当然,实际情况还需要根据具体的项目需求和团队情况来选择。
+因为一些工作需求,最近整了些rust的花活儿,这里系统地记录一下。当你遇到更注重性能和内存安全性的场景,希望这能有帮助。
+此处默认已经安装Rust,需要安装的小伙伴儿可以参考官网。
+使用Cargo创建一个名为hello-wasm
的项目:
+
|
+
+
|
进入项目,打开文件Cargo.toml
,添加依赖:
+
|
+
+
|
默认创建项目中,存在一个名为lib.rs
的文件,将内容全部替换成:
+
|
+
+
|
至此,我们创建了一个最简单的功能——一个返回两个整数相加结果的函数。
+然后我们可以进行编译了,编译之前需要安装一个工具wasm-pack
:
+
|
+
+
|
然后进行编译:
+
+
|
+
+
|
编译完成之后,将会多出来一个pkg
文件夹,内容如下:
+
|
+
+
|
虽然文件很多,但是首先我们看到了我们所需要的wasm文件,并且,根据go的wasm引入方式,这里我们或许会需要用到js文件。
+为了方便最快校验,直接在hello-wasm
项目中创建index.html
文件,来进行前端引入。
那么,首先,创建index.html
文件:
+
|
+
+
|
是的,没错!这是一场标准的开局!😼
+其实不同于go语言的wasm引入方式,Rust更希望直接引入js文件,而不是让开发者手动引入wasm文件。
+这里使用js引入:
+
+
|
+
+
|
完整的html代码如下:
+
+
|
+
+
|
这里可以快速起一个http server,这里我选择使用http-server
,也可以使用python3 -m http.server
这样的方式,看怎么各自的使用习惯。
那么,启动http server:
+
+
|
+
+
|
打开浏览器,访问http://localhost:8080
,打开调试器,即可看到输出the result from rust is: 3
,这就意味着迈出了整花活儿的第一步!
详细报错如下:
+
+
|
+
+
|
当引入WebAssembly生成的js文件时,可能会遇到这个报错。报错乍一看是http server的响应问题,或者搜索时候,也会有帖子说这是一个response问题。
+实际上,当按照这个文章一步步操作时是不会有这个问题的,是因为本文的编译参数是直接解决了这个问题的。当我自己摸索的时候,解决这个问题真的是看到人都麻了……
+关键在于编译命令的参数:--target
。
当没有设置这个参数时,默认的参数其实是--target bundler
,其是编译成给webpack之类的脚手架使用的。因此这里使用—target web
,则是使其编译成可直接在web中使用。
相关参数如下:
+若此时尝试直接引入wasm文件,而不是使用本文所述的方式,那么你会发现,也是可行的!
+
+
|
+
+
|
是的,没错,当前是可行的,但是当引入了一些别的比如dom之类的,就坏起来了……
+上一问题中,且不说是否可以直接引入wasm文件,这里仅说一下,instantiateStreaming
这个方法。这是一个更新的方法,无需转成arrayBuffer
,这也是摸索Rust整活儿时候发现的。如果在别的语言引入wasm,请使用这个更新的方法吧。
在上一篇文章《使用Rust和WebAssembly整花活儿(二)——DOM和类型转换》中,描述了使用Rust操作DOM,并实现Rust与JS类型转换的多种方法。
+在开发 Web 应用程序时,使用 Rust 编写的 Wasm 模块可以提供更高的性能和更好的安全性。但是,为了与现有的 JavaScript 代码集成,必须实现 Rust 与 JS 之间的交互。Rust 与 JS 交互的主要目的是将两种语言的优势结合起来,以实现更好的 Web 应用程序。
+基于上一篇文章中,Rust与JS的类型转换的多种方法,本篇文章继续深入Rust与JS的交互。
+首先,Rust与JS的类型转换,可以实现变量的传递,那么变量是要用在哪里呢?那必然是函数了!
+所以,本篇文章来讲述一下Rust与JS的函数相互调用,基于此,可以实现大量日常功能开发。
+并且,还将会讲述一下,如何导出Rust的struct给JS调用。
+是的,没错,在JS调用Rust的struct!一开始看到这个功能的时候,我的脑子是有点炸裂的……😳
+本篇文章中,将基于上一篇文章中创建的项目来继续开发。
+源码:github.com/Kuari/hello-wasm
+其实,在本系列文章的第一篇中,就是使用的JS调用Rust函数作为案例来演示的,这里依然以此为例,主要讲一下要点。
+首先,声明一个Rust函数:
+
+
|
+
+
|
此处需要注意的要点如下:
+wasm_bindgen
pub
声明#[wasm_bindgen]
宏来将Rust函数导出为WebAssembly模块的函数接着,编译成wasm文件:
+
+
|
+
+
|
然后,在JS中调用该函数:
+
+
|
+
+
|
最后,启动http server,在浏览器的控制台中可以看到the result from rust is: 3
,表明调用成功!
###1 指定JS对象
+在Rust中调用JS函数,需要进行指定JS对象,也就是说,得明确告诉Rust,这个JS函数是从JS哪儿拿来的用的。
+主要在于下面两个方式:
+js_namespace
,则所有的导出函数将被放置在全局命名空间下。js_name
,则导出函数的名称将与Rust中的函数名称相同。###2 JS原生函数
+对于一些JS原生函数,在Rust中,需要去寻找替代方案,比如我们上一篇文章中讲的console.log()
函数,是不是觉得好麻烦啊!
那么,你想直接在Rust中调用JS原生函数吗?!
+此处,就以console.log()
函数为例,直接在Rust中引入并调用,免去替代方案的烦恼。
首先,给出Rust代码:
+
+
|
+
+
|
如上代码中,call_js_func
函数,顾名思义,此处是调用了js函数,并传入参数hello, javascript!
。
那么,call_js_func
函数上方的代码,我们来一步步解析一下:
#[wasm_bindgen]
是Rust的属性,它告诉编译器将函数导出为WebAssembly模块extern "C"
是C语言调用约定,它告诉Rust编译器将函数导出为C语言函数#[wasm_bindgen(js_namespace = console)]
告诉编译器将函数绑定到JavaScript中的console对象fn log(message: &str)
是一个Rust函数,它接受一个字符串参数,并将其打印到JavaScript中的console对象中此处与JS交互的关键是js_namespace
。在Rust中,js_namespace
是用于指定JavaScript命名空间的属性。在WebAssembly中,我们可以通过它将函数绑定到JavaScript中的对象上。
在上述代码中,#[wasm_bindgen(js_namespace = console)]
告诉编译器将函数绑定到JavaScript中的console
对象。这意味着在JS中使用console.log()
函数来调用Rust中的log()
函数。
因此,类似的原生函数,都可以使用该方法来实现调用。
+最后,我们在JS中调用下:
+
+
|
+
+
|
可以在浏览器的控制台中看到hello, javascript!
。妙啊!
其实对于console.log()
而言,还有另一种调用方式,那就是使用js_namespace
和js_name
同时指定。
或许,你会问,这有什么不同吗?是的,这有些不同。
+不知道你是否发现,当前这个案例中,指定了js_namespace
为console
,但是真实执行的函数是log()
,那么这个log
函数的指定,其实是体现在Rust中同样的函数名log
。也就是说,该案例的log()
就是console.log()
中的log()
。
我们来换个名字看看,将原来的log()
换成log2()
:
+
|
+
+
|
然后编译后去控制台看看,就会看到报错:
+
+
|
+
+
|
因此,当我们使用js_namespace
和js_name
结合的方式,在此处是可以进行自定义函数名的。
看一下Rust代码:
+
+
|
+
+
|
此处,重新定义了一个函数log_str
,但是其指定了js_namespace = console
和js_name = log
,那么此处,就可以使用自定义的函数名。
直接编译后,在控制台看一下,可以直接看到正常输出:hello, javascript!
。
总结一下,如果没有指定js_name
,则 Rust 函数名称将用作 JS 函数名称。
###3 自定义JS函数
+在一定场景下,需要使用Rust调用JS函数,比如对于一些对于JS而言更有优势的场景——用JS操作DOM,用Rust计算。
+首先,创建一个文件index.js
,写入一个函数:
+
|
+
+
|
当前的文件结构关系如下:
+
+
|
+
+
|
其中,index.js
和lib.rs
,以及hello_wasm_bg.wasm
都是不在同一级别的,index.js
都在其它两个文件的上一级。记住这个机构关系!
然后,在lib.rs
中,指定函数:
+
|
+
+
|
其中,raw_module = "../index.js"
的意思是,指定对应的index.js
文件,大家应该清楚,此处指定的是刚刚创建的index.js
。raw_module
的作用就是用来指定js文件的。
这段代码在前端,可以等同于:
+
+
|
+
+
|
这样在前端都不用引入了,直接在Rust中引入了,感觉还有点奇妙的。
+接着,在Rust调用该函数:
+
+
|
+
+
|
最后,在前端调用,编译后,在浏览器的控制台中可以看到输出结果了!
+总结一下,这里有几个注意点:
+raw_module
只能用来指定相对路径,并且,大家可以在浏览器的控制台中注意到,此处的../
的相对路径,其实是以wasm文件而言的相对路径,这里一定要注意呀!现在,来点炸裂的,JS调用Rust的struct?!
+JS中连struct都没有,这玩意儿导出来会是什么样,得怎么在JS中调用呢?!
+首先,定义一个struct,并且声明几个方法:
+
+
|
+
+
|
此处,声明了一个struct名为User
,包含name
和age
两个字段,并声明了new
、print_user
和set_age
方法。
其中还有一个未见过的#[wasm_bindgen(constructor)]
,constructor
用于指示被绑定的函数实际上应该转换为调用 JavaScript 中的 new 运算符。或许你还不太清晰,继续看下去,你就会明白了。
接着,在JS中调用这个struct,和其方法:
+
+
|
+
+
|
可以看到,这里的用法就很熟悉了!
+大概想一下,在Rust中要如何调用?也就是直接new一个——User::new('kuari', 20)
。
此处在JS中,也是如此,先new一个!
+然后很自然地调用struct的方法。
+编译后,打开浏览器,可以在控制台看到输出:name is : kuari, age is : 21
。
其实,或许大家会很好奇,起码我是非常好奇的,Rust的struct在JS中到底是一个怎样的存在呢?
+这里直接添加一个console.log(user)
,就可以在输出看到。那么到底在JS中是一个怎样的存在呢?请各位动手打印一下看看吧!:P
本篇文章中,主要讲述了Rust与JS的交互,体现在Rust与JS的相互调用,这是建立在上一篇文章中类型转换的基础上的。
+Rust与JS的函数相互调用的学习成本还是较大的,而且对比Go写wasm,Rust的颗粒度是非常细的,几乎可以说是随心所欲了。
+比较炸裂的就是Rust的struct导出给JS用,这对于Rust与JS的交互而言,还是非常棒的体验。
+ +在上一篇文章《使用Rust和WebAssembly整花活儿(一)——快速开始》中,描述了如何创建项目和快速生成wasm并在前端中使用,迈出了整花活儿的第一步。
+在开发 Web 应用程序时,使用 Rust 编写的 Wasm 模块可以提供更高的性能和更好的安全性。但是,为了与现有的 JavaScript 代码集成,必须实现 Rust 与 JS 之间的交互。Rust 与 JS 交互的主要目的是将两种语言的优势结合起来,以实现更好的 Web 应用程序。
+本篇文章中,将基于上一篇文章中创建的项目来继续开发。
+源码:github.com/Kuari/hello-wasm
+要操作DOM,需要引入新的依赖web-sys
,因此,可以配置Cargo.toml
中依赖如下:
+
|
+
+
|
你或许会好奇,这个features
是什么,讲真,我一开始很好奇,又没看到什么特别的说明,试错才发现,原来是要手动引入功能依赖…比如说,当你需要在Rust中使用JS的console
,那么你需要在features
中加入console
。
在Rust中使用Document
,我们需要按照上一步的说明,添加features
。那么这里有一个依赖关系,首先在Rust中获取window
,然后再获取document
。
因此,添加features
后如下:
+
|
+
+
|
然后在lib.rs
中创建一个函数,用来调用document
:
+
|
+
+
|
那么,现在就是在Rust中解锁了document
,就可以在前端为所欲为了!
那么开始操作一波,首先得获取到Element
……
是的,你没有想错,继续来添加features
吧,此处要添加一个Element
:
+
|
+
+
|
ok,那么继续。此处设定函数传入两个参数selector
和message
,然后通过selector
获取element,更新值为message
参数的值。完整函数如下:
+
|
+
+
|
将写完的Rust项目编译成wasm:
+
+
|
+
+
|
基于上一篇文章的项目中的html,此处添加一个div
,id为message
,添加调用wasm的update_message
函数,代码如下:
+
|
+
+
|
启动一个http server,然后在浏览器查看,可以看到在页面上出现一个h1
标签的Hello, Rust!
。
按照文章来写的过程中,你应该会发现一个问题——怎么这些方法没有补全?!
+是的,没错的,(至少我发现)当前web-sys
并没有补全,所以只能结合开发者优秀的前端技能和丰富的官方文档来开发了。
对于wasm而言,性能固然是提升的,但是类型转换一直是个问题。当大量数据需要在wasm/js中进行类型转换时,这对性能来说,真的是个灾难。之前在使用go开发wasm时,就遇到过这样的问题,需要用官方的方法来进行手动类型转换,然而wasm处理的是一个很大的数据量……
+不过好在Rust的类型支持真的挺丰富的!
+基础类型挺简单的,而且Rust的范性也很好地支持了很多类型。如下是基础类型映射表:
+Rust类型 | +JavaScript类型 | +
---|---|
i8 | +number | +
i16 | +number | +
i32 | +number | +
i64 | +BigInt | +
u8 | +number | +
u16 | +number | +
u32 | +number | +
u64 | +BigInt | +
f32 | +number | +
f64 | +number | +
bool | +boolean | +
char | +string | +
&str | +string | +
String | +string | +
&[T] 例如:&[u8] | +[T] 例如:Uint8Array | +
Vec |
+Array | +
在lib.rs
文件中,创建一个函数,挑选几个类型作为参数传入,然后将其读取并打印:
+
|
+
+
|
可以看到该函数传入了JS的number
、boolean
、Uint8Array
和Array
四个类型的参数。
然后编译:
+
+
|
+
+
|
接着,在前端中引入函数并调用:
+
+
|
+
+
|
最后,启动http server并打开浏览器,在控制台可以看到…看不到?!
+是的,没错,Rust的println!
只会将打印的内容发送到Rust的标准输出流,而不是前端的控制台。如果想在控制台中打印,那么需要调用JS的console
了。
使用新的功能,第一步就是添加features
,Cargo.toml
中添加console
如下:
+
|
+
+
|
在Rust中调用console.log()
如下:
+
|
+
+
|
此处将其封装成一个函数:
+
+
|
+
+
|
然后,将示例函数的println
改成console_log()
和format!
,函数代码如下:
+
|
+
+
|
最后,编译之后,打开浏览器,就可以在控制台看到输出:
+
+
|
+
+
|
Rust中提供了一个通用的类型——JsValue,可以作为任何JS类型。
+这里给一个简单的案例,设置一个函数,使用JsValue
作为参数传入,并打印。
创建函数:
+
+
|
+
+
|
然后编译成wasm文件。
+在html中调用:
+
+
|
+
+
|
在html中,传入了不同类型的参数,但是在浏览器的控制台中可以看到,将所有不同类型的参数都打印出来了:
+
+
|
+
+
|
Result
在Rust中是一个很重要的存在,经常写Rust的话,也不想在写WebAssembly时改变开发习惯。
其实对于JS而言,Result
可以直接在catch
中捕获到,只是说,这里我们需要定义好参数类型。
####1 使用Result返回报错
+首先来一个只返回报错的场景:
+
+
|
+
+
|
这里返回类型是Result
,但是仅仅返回了一个错误。值得注意的是,这里的报错使用的类型是JsError
,当然,这里也可以使用JsValue
。
然后在html调用:
+
+
|
+
+
|
这里调用了两次,第一次应当是错误的,第二次应该是正确的,并且都使用了catch
来捕获错误。
那么,在浏览器的控制台可以看到输出:
+
+
|
+
+
|
####2 使用Result返回正常值和错误
+那么,如果想既返回正常值,也想返回错误呢?Rust返回一个Result
是没有问题,那么JS怎么解析呢?
直接上Rust代码:
+
+
|
+
+
|
该函数,获取到参数后,如果满足条件,加10后返回,否则报错。
+那么看看html中如何调用:
+
+
|
+
+
|
是的,没错,正常获取就行了……/捂脸哭
+这里的调用,依然是,第一个是错误的,第二个是正确返回值的,并且都使用了catch
来捕获错误。
最后,就是在浏览器的控制台中看到:
+
+
|
+
+
|
如果你想更直接一点,那么可以直接引入JS类型!这里主要是利用js-sys
这个依赖,可以在官方文档上看到很多JS的类型和函数,直接引入即可使用。当然,一定场景下,直接引入的类型,是需要手动转换类型的。
####1 配置依赖
+在Cargo.toml
中添加js-sys
依赖:
+
|
+
+
|
####2 Uint8Array
+首先以Uint8Array
举例,在lib.rs
头部引入类型:
+
|
+
+
|
然后创建一个函数,参数和返回都是Uint8Array
类型:
+
|
+
+
|
++忽略该函数中无意义的逻辑和
+arr
变量的警告,只是为了演示用法。
可以在代码中看到,直接引入的Uint8Array
有自己的方法,一定场景下,需要转换类型,但是最好避免进行类型转换,而直接使用其自带的方法。
这里可以简要总结下,就是最好一定场景内全部使用直接引入的JS类型,或者直接全部使用Rust类型来代替JS类型,两者都存在场景下,手动转换类型是件很糟糕的事。
+####3 Date
+Date
类型,在上面的篇章中都没有提及,这里可以直接引入JS的Date
类型来使用。
首先是引入类型:
+
+
|
+
+
|
然后,创建一个函数,返回时间戳:
+
+
|
+
+
|
接着,在html中调用:
+
+
|
+
+
|
最后,在浏览器的控制台中,可以看到:
+
+
|
+
+
|
本文中,主要讲述了如何使用Rust来实现DOM操作,读者可以根据方法自己去找到合适的方法,来实现自己的场景。其次,还讲述了Rust与JS的类型转换,从基础的各自类型的映射,到Rust独有的Result
,到直接引入JS类型。当然这里需要注意的是,直接引入JS类型和Rust的基础类型映射JS类型这两种方法尽量不要混用,混用会导致需要手动类型转换,造成性能损耗。
至此,又向Rust和WebAssembly整花活儿迈进了一步~😼
+ +在上一篇文章《使用Rust和WebAssembly整花活儿(三)——Rust与JS交互》中,讲述了Rust与JS的交互,包括Rust与JS的函数相互调用,比较炸裂的功能就是使用JS调用Rust的struct,JS本身连struct都没有,居然可以调用Rust的struct,这对于Rust开发者的开发体验而言,是真的很棒!
+基于前面的系列文章,已经足以使用Rust开发一个完整的功能了。
+但是,在前端引入wasm文件时,还是可能存在一些问题,比如wasm文件较大,导致网页访问时间较长,用户体验较差。本篇文章将会通过多种途径来减少Rust编译wasm文件的体积,以减少前端加载wasm文件的时间。
+曾经一段时间,我一直用Go开发WebAssembly,其编译后的wasm文件体积还是较大的,为了减少wasm文件的体积真是煞费苦心,1xMB大小的wasm文件真的是太痛了……但是体积优化往往都会指向一条“充满魅惑的不归路”——换成Rust开发😆!如果你已经在用Rust开发WebAssembly了,那么恭喜你,对Go而言,在wasm体积上,你已经赢在起跑线上了。
+在真正去减少体积前,我们需要来先看一下,当前情况下体积是多少,方便后续对比前后体积。
+查看体积的方式有多种,这里推荐几个,(Linux和MacOS)使用其一即可。
+可以使用ls -l
或者ll
:
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
以wc
为例,当前该wasm文件体积为23347b。
++Link-Time Optimization (LTO) 是指在程序链接时进行的一种过程间优化(interprocedural optimization)。它允许编译器在链接阶段对多个编译单元进行优化,从而提高程序的性能、可靠性和安全性。
+
从代码层面优化,主要是利用LTO(Link-Time Optimization)。
+在Cargo.toml
中开启LTO:
+
|
+
+
|
开启LTO虽然能够减少编译后的体积,但是也会增加编译时间。
+LTO开启后,默认是在减少一定程度的编译体积的情况下,要确保编译的时间。如果你的需求就是更小的体积,而不是较短的时间,那么,可以通过手动指定编译等级来让LTO作出改变。
+在代码内可以使用如下等级:
+s
:默认的 LTO 等级。它会进行最基本的 LTO 优化,包括内联函数、函数重写、数据重排等z
:最高级的 LTO 等级。它会进行更复杂的 LTO 优化,包括死代码消除、内存分配优化、安全性优化等那么可以在Cargo.toml
中这么配置:
+
|
+
+
|
原始的文件体积是23347b,现在编译后看一下体积:
+
+
|
+
+
|
很明显是减少体积!但是,使用z
等级并不代表一定每次体积都会比s
小的,有时候s
也会比z
小,这需要视代码情况而定。
在代码外,可以使用wasm-opt来进行优化,其可以对 WebAssembly 模块进行多方面的优化,当然本篇文章中重点在体积方面(挖个坑,后面再详聊/狗头)。并且wasm-opt可以对所有符合WebAssembly规范的wasm文件进行优化,所以,就算你不是Rust写的,那也可以用其进行优化。(想想我曾经Go写的wasm,也是有多一个法子可以优化一把了……)
+首先,来看一下wasm-opt的基本优化参数:
+-Os
参数-O3
相同,但会启用更为激进的优化-Os
相同,但会启用更为激进的优化基于本篇文章主题,此处将使用-Os
和-Oz
两种参数,其于上述"代码内"的等级是对应的。
此处以原始wasm文件,以-Oz
参数来执行一下,看一下对比效果:
+
|
+
+
|
再以上述开启“代码内“LTO编译后的wasm文件,以wasm-opt执行一下,看一下对比效果:
+
+
|
+
+
|
总体而言,wasm文件的体积越来越小。只是当前我这里的案例,是沿用系列文章内容的代码,没有什么实际性复杂代码,再者本身体积已经很小了,所以不会特别有效果。
+网络层面的话,就是众所周知的在网络传输时,客户端和服务端约定相同的压缩算法,然后服务端给出时进行压缩,客户端接收时进行解压。网络层面可以对传输报文进行压缩,但不丢失信息。
+比如大家都很熟悉的gzip压缩算法,不过,压缩算法有好几种:
+其中gzip也是压缩率最高的了,此处就以gzip为例。
+在网络层面,将wasm文件以gzip压缩,减少其在传输时的体积。虽然减少了传输时的体积,但是浏览器在拿到压缩后的数据,需要消耗一定性能来解密。
+开启GZIP其实简单,只要前后端约定好都用gzip就行了。
+首先,前端请求wasm文件时,需要在request header中放入浏览器支持的压缩模式:
+
+
|
+
+
|
接着,服务端收到这个请求后就可以给出服务端也支持的压缩模式,并告诉浏览器服务端将会用什么压缩模式。
+跟浏览器通信的方式就是将信息塞到respone header里面:
+
+
|
+
+
|
这样就开启GZIP了。
+然后,就是浏览器接收到response的body和header,知道后端使用gzip压缩的,那么浏览器就会自动用gzip来解压,拿到完整的数据了。
+或许你会想问,浏览器能自动解密,那服务端怎么自动加密呢?要后端写代码让文件加密吗?
+那当然不是了,直接让http server来完成这个操作。此处以耳熟能详的Nginx为例。
+最简单的就是一行配置开启gzip了:
+
+
|
+
+
|
也可以指定gzip的一些参数,比如可以加密的类型、最小加密长度等等:
+
+
|
+
+
|
更多的http server配置,可以去各自官方文档查阅。
+你可能会惊奇,什么物理层面?!
+没错,真就是物理层面——直接对wasm文件进行gzip物理压缩!哈哈,这个方法也真是绝了,我之前在Go开发wasm时,寻找减少体积的时候发现的,如果你的wasm已经优化得穷途末路了,不妨大胆试试这个方案。😆
+还记得上面章节“网络层面“中,有个问题就是是否需要手动压缩,那么这里就是全程手动压缩和解压缩了,哈哈。
+首先,是对wasm文件进行物理层面的gzip压缩,此处先使用原始的wasm(23347b):
+
+
|
+
+
|
然后,看一下其体积:
+
+
|
+
+
|
效果卓群,从23347b减少到了10403b!
+然后来把上述“代码层面”的优化来一遍,看一下最后的体积:
+
+
|
+
+
|
效果更加卓群了,从19871b减少到了9237b了!
+所以,此处就是对wasm文件进行物理压缩并存储,然后浏览器请求时,直接请求到.gz
文件。
浏览器拿到.gz
文件后,需要物理解压。
这里推荐使用pako
这个前端库,对.gz
文件进行解压:
+
|
+
+
|
之后就可以直接使用了。
+此处直接将上述所有方法都用起来,直接叠加buff,来看看当前(本系列文章积累的)这个案例能减少多少体积。在“物理层面”章节中,已经累加除了“网络层面”的buff了,所以可以直接使用其结果。而“网络层面”章节中,以gzip来压缩,将gzip的压缩率以40%来估算。
+那么最终该案例的wasm体积将在5542b,压缩率大约在77%!
+当然,还要算上一个初始的语言buff——Rust,使用Rust本身就已经导致wasm文件体积很小了。
+本片文章中,从代码层面、网络层面、物理层面共三个层面介绍了对wasm文件的体积优化方案,其中共有四个方案。
+最后,当前(本系列文章积累的)该案例叠加了所有buff之后,能够减少77%的体积,真的感觉挺棒的了,哈哈。
+希望能够对各位有所帮助。
+ +前端还在传文件给后端吗?你们的服务器扛得住吗?什么……老板砸钱加机器?!告辞!/狗头
+前后端文件传输涉及数据较大,往往会成为很多项目的性能瓶颈。常见的传输方式也有不少,相对来说,OSS直传能够减轻很大压力。
+本文我们来列举下常见的和oss直传的几种传输方式,并列举其优劣。
+表单上传文件是最常见的方式,前后端开发小伙伴都很轻松,前端哐哐传,后端哐哐收就成了。其过程如下图所示。
+ +优势:
+Base64方式上传文件,多常见于小文件,如小图片等,前后端都可直接使用String
类型发送和接收。不过在前端,需要将文件转成base64数据,不仅会增加些性能消耗,还会增加传输数据的体积。而对于后端,如果并不是想直接存储base64数据,也还需要将其转成文件再存储,也会增加后端的性能消耗。
该上传方式可适用于Resetful Api
,也可适用于文件的加密、回调接口携带文件等等。其过程如下图所示。
优势:
+Resetful Api
,可用于加密、回调等场景,较为灵活劣势:
+此处的OSS直传方案都是使用的阿里云的OSS产品,以下将介绍三个方案,可适用于不同的场景。
+该方案可在前端直接通过browser.js
上传文件到OSS,可分成三步:
其流程如下图所示。
+ +Browser.js
的方式需要前端安装阿里云的库ali-oss
,然后在前端调用。直传还需要OSS账户的Key和Secret,因此为了安全考虑,需要建立RAM账户,然后前端向后端先请求一个STS
临时访问凭证来完成直传,其流程如下图所示。
Javascript客户端签名直传,需要先从后端获取临时签名,其流程与上一步的browser.js
方案大致相同,不同点在于:
其流程如下图所示。
+ +不过该方案做下来,我感觉最大的问题是权限配置有点麻烦……/泪眼
+该方案其实是上面方案——Javascript客户端签名直传的升级版本,其加上了后端的上传回调功能。不错呦~
+前端需要改动的很少,只需要在请求参数中加上callback
参数即可,该参数为后端加密,在签名请求的响应中一起返回回来,内加密了后端回调接口。在前端直传完成后,后端回调接口将会接收到相关文件参数,包括文件路径、大小、类型等。最后OSS会将回调接口response转发给前端,响应直传OSS的请求。
其流程如下图所示。
+ +传统方式相比直传OSS,相对来说有三个缺点:
+当然,对于规模较小、成本较低的项目来说,常见的上传方式还是适合的,毕竟没有最好的,只有最适合的。
+在日常的前端开发中,经常需要将一些数据从网页上复制到剪切板中。而实现复制功能,第一时间想到的就是引入第三方库。
+曾经过多不少第三方的剪切板的库,是真的很繁琐,又是创建对象,又是绑定DOM,头都要炸了,就个简单的复制功能,第三方库换来换去地测试……
+后来看到了vueuse可以直接用,突然觉得,哇!真棒!
+直到有一天,搜到了Clipboard api……
+官方文档:https://developer.mozilla.org/en-US/docs/Web/API/Clipboard
+不管是读,还是写,统统搞定!而且都还是异步方法。
+比如复制文本到剪切板:
+
+
|
+
+
|
复制canvas到剪切板:
+
+
|
+
+
|
读取剪切板的文本:
+
+
|
+
+
|
在特定敏感数据的场景需要加密,一开始采用rsa
加密,但是rsa
加密对性能要求较高,在解密时候对于数据量限制较大,导致加密传输的数据量上限较低。而采用Base64
虽然简单明了但是解密过于简单。因此采用折中的对称加密aes
。
而aes
加密需要前后端加密类型相同,因此此处采用CTR
,其对加密文本没有长度限制。
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
最近很多小伙伴儿咨询我关于学习技术方面的问题,我都一一回复了。这里也专门出一个总结,关于我个人这些年入门新的编程语言和框架的经验和技巧,来跟大家分享一下,希望能够所有帮助。
+本次分享分为两个时期:
+当我有了一定开发经验后,去学习一个新的编程语言或者框架,我会注意如下两点:
+所有的编程语言或者框架,都不是凭空产生的。当前比较流行的语言,大都是类C语言,比如我们常见的Java、Python、JavaScript、Golang等等。
+所以,我们学习的编程语言,除了特有的特性,大都是相似的,也就是它们的共性。我们只要理解和掌握这些类C语言的共性,就可以很快地掌握一门新的编程语言。
+那么,哪些是共性呢?比如说变量、常量、运算符、判断、循环、函数、面向对象等等。
+我们在学习一门新的编程语言的时候,是不是必然要学习如上这些共性的知识点?
+那么,当你已经有一个编程语言的基础后,当你在学习和写代码的时候,这些共性可以直接使用你已学会的编程语言的知识去代入。
+举个例子,比如我已经会Go了(不过,当你先学Go的话,可能不太知道面向对象是什么/狗头),要去学一下Python。
+我需要做的就是代入:
+Go语言声明变量是a := 1
,考虑到Python没有强类型,那么就直接a = 1
就完事了。
Go语言声明函数是func aFunction(username String) {}
,那么Python不过是换了个关键词,去掉了强类型,变成def aFunctino(username):
,当然Python如果要申明类型也是可以的,写成def login(username: str):
。
其中,比如运算符大都是一样的,判断也是类似。
+但是突然你发现,怎么Python的循环不一样,然后发现Python的循环好方便,但是其实原理还是和Go一样的。甚至,当你遇到不知道怎么处理的时候,直接来一波for (i := 0; i < count; i++)
。当然,Python没法这么写。
除了我举的这些例子,还有比如正则、数据类型、数据结构等等,都可以作为共性来代入。
+其实,框架也是如此。此处以web框架为例,比如Python的Flask、Django、FastApi,Go的Gin,Node的express,Java的SpringBoot等,都是比较熟知的框架。
+这些Web框架,都是依托于网络的,大都是http(s)请求,甚至其他TCP请求。我们可以只在网络层面来看,它们共性就是request和response。所以在网络层面就可以理解为,后端web框架就是一个接收request和response的东西。
+那么,看具体框架的功能上,可以抽象出路由、中间件。
+所以,对于一个web框架,其共性为request、response、路由、中间件。
+因此,我们不管是使用Flask、Gin还是SpringBootd等,可以使用我们已有的框架知识去代入:
+那么,就可以完成一个新的web框架的入门了。
+除了编程语言和框架,其实还有很多别的都是拥有共性,可以快速入门的,比如Nginx和Apache,比如Linux发行版本,比如Kong和ApiSix,比如不同的数据库等等。甚至最近我发现,wasm并不是前端特有的,EBPF也在用wasm。
+编程语言的情况还比较少,更多是学习框架时,一定要看官方文档。
+网上虽然有很多教程,但是毕竟是二次加工的,可能存在信息遗漏、版本更新等等问题。
+官方文档可能比较生涩,但是它是最全最新的一手文档,会非常有助于少走弯路。
+再次强调,一定要看官方文档!
+当我还是新手的时候,我其实也常常苦恼于入门——教程看了好多遍,跟着教程敲了一遍又一遍,感觉还是只会基础,自己想写点什么却不会。
+后来,当我入门一段时间后,我返回去想一想,才想明白一些技巧。
+所以,以我的经历,总结如下步骤:
+一开始的时候,我也是不停地啃教程,不管是文档还是视频,但是发现我还是只会基础理论知识,那些高级特性案例都敲了,却并不明白什么意思。
+后来,我就发现,对于我而言,并不需要学太多,只要学会基础即可,甚至基础也不用太深入。
+跟着教程敲代码,效果甚微。
+要自己构思一个项目,比如写一个TODO,写一个命令行聊天工具,写一个博客等等简单的程序。最好是自己感兴趣的领域。
+但是,需要注意的是,一定要自己去构思如何写:
+然后,直接开始写代码!哪怕只是创建了几个文件。
+只有自己思考的,才能深刻理解和记忆。
+当然,因为仅仅学了基础,你会发现很多功能并不会,甚至比如如何读取一个文件都不会。
+如果这时候,遇到不会的,就当场去学,哪怕是高级特性,学到把不会的这个点能做出来为止。
+如果一个功能不懂什么意思,搞不明白,那么直接复制过来,调试到能满足功能为止,然后将这段代码和这个场景记住。
+我一直觉得写代码有一个门槛,在迈过门槛前,学习会非常困难,但是,一旦迈过这个门槛,你就会发现,学起来异常轻松,你所学的技术可以(相对)融会贯通。
+那么迈过这个门槛的方法就是:重复2和3两个步骤,多写代码,多思考。
+是的,不断重复,然后可能某一天,当你对你学的技术恍然大悟的时候,那么其实就是迈过了那个门槛。
+然后,之后便是更深入地学习了。
+一路走来,我觉得最重要的是“多写代码,多思考”。不少代码写到最后甚至都是肌肉记忆了,但是代码还是会变的,自己思考的方式和过程,自己沉淀的知识才是最宝贵的,足以支撑我能够不断学习新的技术,适应新的模式。
+ +前端从后端获取到sts,然后直接minio,极大减少服务端的压力。
+当然,肯定会这种疑问,为什么不在后端生成临时签名url,给前端上传/下载呢?问就是业务需要 /狗头……
+为什么标题上要强调“浏览器”呢?
+这是因为官方的minio.js,感觉当前版本并没有考虑浏览器的使用场景,无法直接在浏览器上直传。
+所以这里就是推荐一个折中的方案,可以在前端直传。
+当然了,如果业务允许,当前版本请直接在后端生成临时签名url吧!
+
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
官方提供的putObject
方法,必填这三个字段,前两个很好理解,主要是第三个stream
,需要详细看一下。
stream
的类型是:string | internal.Readable | Buffer
string
很好搞定,直接putObject(bucketName, 'hello.txt', 'hello,world!')
这样子上传文本文件。
但是另外两个类型都是nodejs的啊…啊这…(也许是我错了,有大佬能够解决的请务必直接告诉我…)
+那么折中的方案就是由前端生成临时签名url,再由前端进行上传 /哭。
+
+
|
+
+
|
然后利用http请求url进行上传即可。虽然比较曲折,但是目前相对比较好的前端解决方案。
+此处介绍下http请求上传的相关操作。
+请求上传可以有三种方案。
+
+
|
+
+
|
+
|
+
+
|
+
|
+
+
|
此处可以封装一下请求,叠加promise
buff,此处以XMLHttpRequest
为例:
+
|
+
+
|
要完成上传,怎么能没有响应事件呢。
+此处的事件包括:
+请求代码如下:
+
+
|
+
+
|
+
|
+
+
|
设想一个场景:
+你作为一个程序员,听着攒劲的小曲,写着优雅的代码,加班加点终于写完了迭代中分配的模块需求。然后你跟别的同事一联调,却发现各种问题。
+你跟同事A说:“你得在这种情况下给我数据A。“
+同事A一脸懵逼:“这种情况需求也没写啊,在这种情况下我也没有数据A啊!“
+然后你和同事A一起顺着业务流程,找到了负责上层模块的同事B。
+同事B一脸懵逼:“这个迭代中我没有这个模块的开发需求啊,这种情况下我也没有数据A啊!”
+然后你和同事A、同事B一起顺着业务流程,找到了负责上层模块的……
+最后,你和同事ABC…一起找到了产品,产品一脸懵逼:“那没办法,就改需求吧……”
+你眼看着所剩无几的迭代时间,生无可恋地掏出手机,发个微信取消了这周末的相亲…
+不知道程序员xdm有没有经历过这样的场景,如果各位程序员xd没有经历过这样的场景,那真的是恭喜你了,且行且珍惜!从毕业后参加工作开始,我是真的经历太多了,太痛了!
+我之后不断学习各种方法,尝试去破解这样的困局。虽然说,工作中的问题很多,但是能解决一个是一个,后面再慢慢跟各位分享其他问题中我的解决方案。
+所以,本篇文章将分享一波关于这类问题我的解决方案。谨代表个人主观观点。
+需求是用户的痛点,但是在这个场景下,是程序员的痛点…
+那么,仔细分析一下,这个场景下的痛点,到底是什么呢?我认为是如下两点:
+当然,可能有程序员xd会疑问:这种问题不是靠严谨的开发流程就可以避免吗?
+理论上来说是这样的,但是以我个人经历而言,不管是大团队还是小团队,总有很多不稳定的因素,比如说迭代周期、协作流程的规范性等等。
+特别是在创业团队,或者有些项目特别着急的时候,经常是没有足够的时间给到团队的各个角色去充分准备的,甚至为了赶时间,会精简一些环节甚至直接去掉。这种时候出现这种问题导致改需求,往往是非常可怕的,后果也往往是开发团队加班。
+我目前发现且实践后效果较好的方案,就是“曳光弹式开发”。
+经常关注军事的小伙伴儿,应该了解,曳光弹是一种运用于军事场景的特殊子弹。
+我们都知道武器发射时需要瞄准,比如枪就通过照门和准星来瞄准,而坦克和装甲车以及飞机都有专用的瞄准具,非常复杂。对于轻武器来说,一般攻击距离都比较近,使用自带的机械瞄准具或者外加的光学瞄准镜都足够,但是在距离稍远的时候瞄准镜显然不够。而对于飞机和战斗机来讲,在空中飞行时飞行姿态变化多样,有时候进攻的时间很短,需要快速射击并且调整弹道,这时候就需要可以发光的曳光弹。
+曳光弹正如其名,可以发光而且指示弹道。曳光弹的结构比一般子弹更加复杂。子弹的弹壳部分和一般子弹一样,前半部分是钢心或者铅心的弹头,但是在后部有一个空腔,一般称作曳光管,里面填充着曳光剂。曳光剂的成分还比较复杂,一般来说主要成分是镁粉和铝镁合金粉,用来燃烧,除此之外还有硝酸锶。这样一来,燃烧的时候硝酸锶就会发出红光。大家平时看到的很多曳光弹还有黄光和绿光,加入钠盐就会发出黄光,而加入铜盐就会发出绿光。除此之外还在表面加入一层过氧化钡,以保证曳光剂被点燃。
+对于机枪手来说,如果射击距离较远,自然不能选择过于精确的设计方式,也就是说不能靠瞄准具来射击。而一般的机枪主要起压制和面杀伤作用,加入曳光弹就是机枪手更加快速方便的控制弹道,随时变化攻击的方向。如果没有曳光弹的话,射手根本无法发现自己的子弹弹道,也就很难去调整弹道。毕竟枪械射击温度升高之后,弹道会有所变化,不同的弹药不同的枪管也都会改变子弹的弹道,如果仅仅依靠枪械自身的瞄准具去调整反而会适得其反,而曳光弹就很好的解决了这个问题。
+理解曳光弹本身在军事中的作用后,我们再来看曳光弹式开发如何在开发中起作用。
+开发团队在接手到产品给到的业务需求后,特别是构建一些以前从未做过的东西时,对这个产品/功能的最终成效是模糊的。
+程序员就像坦克上的机枪手一样,在尝试在黑暗中击中目标。但是如果像文章一开始的时候,大家先埋头写自己的模块,然后再联调,最终发现问题,感觉就像一上战场,大家都朝着各自理解的方向拼命清空弹夹,击中目标的寥寥无几,最后受伤的还是自己。
+所以,此时,就需要先使用几颗曳光弹,去击中目标,在黑暗中划出轨迹,开发团队再调整方向,对着目标集中火力。
+非常简单!步骤如下:
+这样,射出一颗曳光弹,穿透客户端、后端、数据库、运维、测试等不同层面。一旦击中目标——即符合用户需求,后续的任务便大都是搬砖的活儿,去丰富这个骨架。
+曳光弹并不是总能击中目标的,中途若是发现未能击中目标,便可在前期以极小成本去调整曳光弹的方向,继续发射曳光弹。
+如果你要问,什么是“最基础的完整功能”,那么可以这么说,那么举个例子:前端不要任何样式,直接能够满足比如表单功能即可,后端不用任何校验,能够处理和传递数据即可。
+为了解决文章开头的问题,需要使用曳光弹式开发,先开发一个骨架,确认满足需求后,即可继续丰富骨架,完成产品。
+作为团队的一员,团队的每一个角色都很重要,程序员跟产品也是需要紧密协作,互帮互助,才能使得产品更好,也使得业绩更好。
+ +裸辞后,我花了一个多月开发我的第一个独立开发产品RedisMate。其MVP版本上线五天,产品从没有排名,到冲到了国区mac app store开发工具类排名榜的前十,并持续有了几笔收入。
+那么,我是怎么做的呢?
+我是一个正在尝试独立开发的普通程序员,在此跟各位分享我的过程,不仅是我对自己的复盘,还希望能够给一些正在或准备尝试独立开发的兄弟一点参考。所有的过程都是按照我主观的想法去推进执行的,如果有更好的建议,欢迎交流!
+本次分享分为上下两篇文章,分别为产品设计和产品推广两个主题。
+首先,我并不是因为我有一个很棒的点子或产品,而选择去裸辞,去独立开发。
+而是我因为别的原因选择了裸辞,然后开始把独立开发作为未来的一条路,并尝试去走。
+所以,也可以说,一开始我是为了独立开发而去独立开发。
+既然我没有一个很棒的点子去完成一个产品,那么我就开始去找。
+首先,为了能够独立开发,我要审视自身,我会什么?
+罗列下我的开发技能:
+检查下其它独立开发必需技能:
+罗列下我熟悉的可以寻找需求的领域:
+我能在独立开发过程中投入多少资源:
+那么,基于开发技能,得出我可以做的产品的形态:
+但是考虑到希望以较小的成本来尝试独立开发,和希望能够短期得到收入来验证产品,那么就剩下了
+并且还都是离线应用,不需要服务器的支撑,没有太多服务器的额外成本,服务器仅仅需要最小配置用来备案即可。
+考虑到付费方式的成本、上架的成本,再排除掉安卓app、Windows/Linux应用程序。
+那么就只剩下
+确定了产品形态,我就要考虑,到底要做什么产品呢?产品需求是什么?
+如果我从当下很火但我完全不熟悉的领域去找需求,那我肯定没法挖掘真正的需求。
+如果我找一个熟悉某个领域的人咨询,他并没有挖掘需求的能力,无法识别出真正的需求,这一点在我多年的工作中已经见识过太多太多次了,最后完完全全就是一个定制化项目,在市场上根本没有竞争力。
+这就要从上述罗列的,我熟悉的领域去找。
+所以我把目标放在了“开发”上。“开发”是我最熟悉的领域,在其中这么多年了,需求来源于我自己,我就是用户,我就可以直接验证这个需求是不是伪需求,这个功能是不是好功能。
+并且,鉴于我三年前开发过一款开源的Redis GUI Client——RedisFish,积累了一些经验,并且后来因为工作搁置了,导致一直没有完成也心有遗憾。
+所以最终决定开发一款Redis GUI for Mac。
+在决定产品之后,我先去问了一圈我的朋友和前同事,他们正在用的Redis GUI是什么。
+得到的回复是如下产品:
+其中RedisDesktopManager和AnotherRedisDesktopManager是最多的,毕竟是老牌的强劲产品!
+然后,我又去翻了些论坛和帖子,发现有些人在发帖问mac上有哪些推荐的Redis GUI,但是哪怕是老外,也是如下选项居多:
+接着,我也去了解了下近期新出的,比如Tiny RDM,听劝作品,真的很棒的,哈哈。
+然后还有几个出现在比如gitee等平台的。
+所以目前mac原生的也只有Medis2。
+最后,我就要说那个经典台词了:没有能够满足我需求的!(不是…
+到这个事件前,我已经开发完了既定的MVP版本,为v 1.1,此时的RedisMate是一个完成了基础Redis功能的App。
+而我也已经怀着激动而又紧张的心情,在想如何推广MVP版本了。
+但是,在我准备尝试推广的那个早上,在上厕所的时候,当时正在看《人性的弱点》。刚好看到书上讲了一个推销员的案例,推销员疯狂地讲述自己的产品怎么怎么好,让作者要赶紧买。
+因此作者就在书上写道:
+++人们其实什么东西都不需要。如果我们想买点什么,早就出门去买回来了。人们真正需要的,是解决问题的方式。人类永远都面临着种种问题,永远都需要这些问题的解决方案——如果销售人员能够证明其服务或产品可以帮助人们解决问题,不用推销,我们就会主动掏钱。对消费者而言,“主动买”比“被推销”的感觉好得多。
+
所以,我就在想,我现在做的这是什么啊?!我能给其他人解决什么问题呢?凭什么要选择我的,而不是别的成熟产品?
+我就开始重新构思MVP版本,希望能够加上独特的、创新的功能,能够真实解决用户问题的功能。
+如何发现问题并解决问题?可以从经济学的四种不同类型的效用来入手:
+在此处,我的产品RedisMate上,最简单也是最快的的创新方式就是“让事情变得更快”。
+我先明确Redis GUI使用频率最高的功能,然后从那个功能入手,让用户能够立竿见影感受到RedisMate的便捷。
+那么,经过自己的总结,和跟朋友的沟通,我总结出,Redis GUI工具,使用频率从高到低的功能分别是:查(甚至是搜索)、删、改、增。
+因此,我就从搜索功能入手。
+我把RedisMate搜索功能的所有步骤罗列出来,然后思考,有哪些步骤可以减去?如何减少步骤?
+最终就得到了现在“快速搜索”功能!
+当Redis GUI窗口常驻时,一般最少需要约7步操作,那么在RedisMate上最少只要3步。
+当Redis GUI仅打开没有连接任何服务器时,一般最少需要约9步操作,但是在RedisMate上最少只要3步。
+当Redis GUI反复搜索固定key时,一般最少需要约4步,但是在RedisMate最少仅仅需要1步。
+所以,我在RedisMate上针对搜索场景,进行了优化,并确实在推广时,得到了不少积极反馈。
+并且,基于上述过程,优化了RedisMate的布局和一些细微的功能,让用户能够更直观更快去操作。
+等我后面回过神的时候,才发现我一直有这个需求。
+经常性,我要前后端一起开发,由于是微服务,多个节点并发开发和调试,每次都要开超多窗口。
+我经常要切换窗口时,要愣一会儿思考一下,我要切什么窗口来着…
+所以我就一直在想,能不能省略掉这些窗口,窗口自己呼出然后切回去!
+现在RedisMate就可以了,这就解决了我曾经的问题。
+然后,我就又想到曾经的一个抱怨,有时候在调试时,代码有点问题,或者业务链路较长,需要联调时间有点长,或者MQ驱动的业务节点产生key有点慢等等,key在我查的时候没有出现。我就要反复调出Redis GUI然后点点点后搜索再点点点,发现不对!调一下代码,再来一遍!
+我当时就在想,要是GUI能帮我盯着就好了。
+所以,我设计了"Search Until Found"功能,在得到朋友的肯定后,将其加入到了MVP版本中 。
+首先,我很庆幸自己这些年学习和玩了不少技术,让我能够在想去独立开发的时候,不仅能够独立完成开发,还能有多种产品形态可以选择。并且我也有意识地去读技术之外的书,让我在其它领域有那么点点基础,只可惜没有学得更多。
+其次,我觉得对我而言,苹果生态开发是当前成本最小的方式。
+然后,我从我熟悉的领域入手,寻找自己的痛点,验证自己的需求,得到解决方案后,向身边的目标用户寻求验证。
+最后,花一个多月开发完MVP版本,推出来验证自己的产品。
+感谢你看完这篇文章,希望我的经过能够给你一些参考,帮助到你。
+本次分享的下一篇文章——产品推广方面,我正在努力写,敬请期待并关注我以获取更新。
+如果你对我的产品RedisMate感兴趣,非常欢迎点击此处了解和下载,或者在mac app store中搜索“RedisMate“。
+最后,要感谢各位道上兄弟的关照,给了很多支持和反馈。
+还要特别感谢那些购买高级版的兄弟,非常感谢你们的支持!
+ +