xulihang's blog 2023-10-29T12:45:17+00:00 xulihanghai@163.com 够用就好 2023-10-29T12:25:50+00:00 xulihang blog.xulihang.me/overpreparing 我们在学习软件开发的时候,会感受到学习技术是永无止境的,不管是深度还是广度,还是紧跟潮流,都需要耗费相当的精力。但实际开发,我们只需要运用技术解决特定的问题,这个问题是明确的,只要我们花一点精力,就能比较好地去解决这个问题。所以我们不必过度忧虑技术学得不到位的问题。

再以个人开发者为例。一个软件公司,可能需要产品、开发、市场、销售等诸多部门协作完成任务,使用各种用户管理系统、营销工具、自己开发销售系统等等。但对于个人来说,可以简单一点。比如用户量不大,每天只卖一两笔订单时,可以手动给用户回邮件,生成订单,用本地的FileMaker或者Excel管理数据。等到用户规模真的上来了,再考虑开发更复杂的系统。

同样的还有论文写作。我记得我本科的时候想了一个很宏大的翻译教学系统的设计的选题,论文一改再改,不知道啥时候满足要求。结果提交后,指导老师并没有指出很多问题,并说,论文修改是没有止境的,目前这样就不错了。后来我的论文还获得了2017年江苏省优秀毕业论文二等奖。我现在想来,本科阶段对毕业论文的要求不高,所以那时我写的已经够用了。

我这里想说,很多时候,我们不必活在未来,整天担心所学不够用的问题,一般来说够用就好。但并不是说一点不为未发生的事情做准备,比如学习一些急救知识,万一真的发生危险的事情,当场学是来不及的。

注:因为难以直译,本文的英文标题为overpreparing,意为过度准备。

]]>
个人开发者的跋涉 2023-09-10T12:14:50+00:00 xulihang blog.xulihang.me/the-struggle-of-an-indie-developer 做个人软件开发是件很有意思的事情,但也会有很多艰辛。本文我想对我的个人开发经历做个回顾。

开始个人软件开发

我从2018年起就一直专注于计算机辅助翻译软件的开发,从开源免费的BasicCAT到收费的图片翻译软件ImageTrans。

这些软件都是为了方便我工作而开发的,我是软件的第一个用户,所以一开始的开发工作是比较明确和愉快的,就是为了满足我自己的需求。而完成一个个编程挑战,也是很有意思的一个过程。我可以写一天的程序。遇到想不明白的问题,就去公园散步,可能就豁然开朗了。

收费问题

因为我对开源的信仰,我的第一个软件BasicCAT选择使用GPL协议开源。而同类的商业计算机辅助翻译软件,比如Trados,售价都在数千。我觉得这些商业软件又贵又不好用,我想用开源软件来挑战一下它们。

但对于个人开发者来说,开源存在若干问题:

  1. 用户提需求,开发者无偿进行劳动,开发动力不足。
  2. 冷门领域,很难吸引到其它开发者共同维护代码。
  3. 作为包罗万象的桌面软件,而不是类库,代码很难被其它项目使用。

于是我第二个主要软件ImageTrans,选择了收费模式。

软件销售主要有订阅制和买断制这两种。我选择了买断制,有下面这些原因:

  • ImageTrans是桌面软件,我不提供什么云服务
  • 图片翻译这个场景,用户使用频率一般不高
  • 买断制比较简单,维护后台也比较方便

作为个人开发者,不成立公司,收款也是个问题。我选择了第四方的收款平台FastSpring和面包多,一个用于国际用户,一个用于国内用户。

这两个平台都有服务费,FastSpring是成交金额*5.9%+0.95美元,而面包多是成交金额的5.7%。

推广问题

软件开发完成后,还要进行推广。我做了下面这些工作:

  • 建网站
  • 写博客
  • 录视频
  • 到潜在用户活跃的论坛发帖
  • 找翻译行业的老师同学帮忙宣传

刚开始的确取得了很好的宣传效果,吸引了很多用户联系。但后续的增长完全依赖自然流量,增长相对乏力。如果有用户帮忙宣传,销量会有较大提高,但波动较大。

与用户共同成长

我通过邮件、GitHub与用户沟通,用户会给我提各种需求和bug,在这个过程中,软件能得到不断的完善。毕竟我自己从做翻译变成了开发翻译工具,用户对需求的理解比我更加深刻。

而且我吸引了全世界不同国家的用户,他们使用着不同的语言,比如阿拉伯语是从右往左阅读的双向语言,我花了不少功夫让软件支持阿拉伯语。

为了吸收早期用户,我一开始提供了试用版。在软件成熟之后,因为维护用户的成本比较大,我便取消了试用版。

什么都要做的挑战

因为只有我一个人,什么工作都需要我自己完成,从需求整理、界面设计、软件开发、文档编写、网站建设到售前售后的客服工作,有时候会觉得分身乏术,啥都做不好。不像在公司,我只需要做好一份工作。

赚钱主要是一个利他的行为,还是需要受点苦的。

对于被动收入的忧虑

因为是买断制,收入依赖于新客户的转化,收入波动较大。所以个人开发只能作为副业进行。

而每当收入下滑,我便会思考怎么吸引更多用户,是不是用户都转去竞品了,该怎么加强软件。

其实这种忧虑是正常的,它能促使我去改进产品。但也不必太焦虑,毕竟只是副业,而且产品本身有着独一无二的特性,应该接受和竞品共存的局面。

即使没有新的用户了,它能用于满足我自己的工作需求,那也是有经济价值的。

]]>
电视剧 2023-08-20T12:52:50+00:00 xulihang blog.xulihang.me/TV-series 搬家后买了台电视,既然买了,想想平时多打开来看看。我会看一些地方台,有很多当地相关的节目,比如浙江经视。一天,发现浙江经视正好在放赵丽颖的《风吹半夏》的第一集,之前在微博上看到过相关视频,觉得她演得挺好的,于是跟着浙江经视把这部剧看完了。

有个电视剧追,似乎生活也更有规律了,每天看三集,放好了再看看经视新闻。现在有回放,看到不想看的部分还可以快进。

电视剧一集有40分钟,需要花较长的时间在里面,其间只需要沉浸在剧情中,不用想别的事情,是件挺放松的事情。电视的节目都是安排好的,也不用我自己去劳心劳神寻找内容。

其实电视剧对我个人的成长还是有很大的影响的。小时候看金庸的电视剧,至今听吴启华唱的《风起云涌》都能感受到一股侠义之情。在我学语文的时候,电视剧给我提供了大量的素材。比如傅彪在《恩情》里说了毫毛这个词,我小时候不懂,听他用这个词我才学会。

《风吹半夏》里,可能和我这个阶段最像的是野猫。野猫对许半夏说她为什么喜欢童骁骑,是因为他对许半夏很好,如果自己做了童骁骑女朋友,那他得好成什么样(笑)。

]]>
意外骨折 2023-08-13T12:52:50+00:00 xulihang blog.xulihang.me/accidentally-broke-my-ankle 上周回家,周六下午盘腿坐着写代码时,突然觉得左脚踝疼痛。我以为是坐麻了,不以为是。但后来发现脚一着地就很疼,不能正常走路了。

我就纳闷了,我也没有扭伤,怎么有一种扭伤的感觉。我以为是跟腱和足底筋膜太紧张的问题,找了些运动康复的视频来照着练,但并没有缓解多少。我想先休息两天看看,可能自己就好了。网上搜突然疼的原因,说有可能是痛风和风湿,但症状和我不是很符合,比如痛风一般是脚趾关节先疼。第二天忍着疼乘地铁回杭州,晚上买了云南白药喷。

周一醒来,发现我不动,脚也会疼,于是决定去医院看一下。拍了X光片后,医生能看到腓骨远端,也就是左外踝有小骨片(小片状骨性密度影),看上去是骨折了。但并不算严重,医生让我自己买支具固定,配了止疼的洛索洛芬钠贴膏、活血化瘀的龙血竭和健骨的金天格。

然后我就开始了四天的远程上班,能感觉到每天都有点好转。因为脚能着地,每天自己出去倒垃圾,一天走大约2000步路。到今天,休养了一星期,我戴着安德玛的护踝,穿着安踏创2.0的鞋子出去走,走得慢基本就不疼了。我这里特别提到创2.0,是这双鞋子对脚踝的包裹性比较好,能给予一定的支撑。之前穿都没发现这个优点,这次受伤后能感受到在护踝这方面的确穿着比别的鞋子舒服。

我回想下上周六,有一次跳起来摸店面的招牌的行为,单脚落地后可能造成了这次骨折。以后做类似的动作得注意好落地了。医生推荐做核磁共振,能看到韧带的损伤。现在好得差不多了,就不去做了。

]]>
爱的艺术 2023-08-07T13:54:50+00:00 xulihang blog.xulihang.me/the-art-of-loving 《爱的艺术》是由弗洛姆编写的一本关于爱的书籍。他认为爱是一门艺术,就像音乐、设计一样,需要我们通过学习去学会欣赏和演绎的方式。全书主要讨论了理论,并略微讨论了下如何实践。

本文呢,我不具体讲这本书,我想基于自己的经历做一些讨论。

爱的前提

好的爱情需要一定的智慧。它体现在对自己的了解、对他人的了解。很多人在感情中不断试探,也是因为不够了解。不够了解自己,也不够了解对方。要面子、害怕敞开心扉或者不知道如何深入交流,喜欢自己脑补、希望一切按自己的预想发展。我们需要掌握合理的方式,有计划地去了解彼此。

进入爱情时,两个人应该都处在一个比较好的状态。什么样的状态算是好的状态,我觉得应该是一种找到自己想做的事情、有一些每天坚持的好的习惯的状态。比如写作的习惯。写作一方面有助于自己和别人了解自己,另一方面能锻炼自己表达的能力。交流的两个难点,一个是听别人讲,另一个是正确表达自己。

相对来说,有同等能量、共同经历的人更容易和愿意了解彼此。所以在爱之前,就像鹿小草说的,先好好学习,积累能量和经历。

这里举一个关于了解的例子:我在厦门了解了陈嘉庚建筑风格,屋顶采用中式的设计,比如飞檐。回到海宁后,我才发现看了几十年的海皇大厦也采用了这种设计。

爱的缘起

爱情是怎么产生的?一开始可能是被对方的一些好的特质所吸引,比如笑得好看、声音好听、眼神灵动、有责任心。之后可能会因为对方的问题而上心,比如他看起来不开心、他看起来比较忧郁。然后在一些互动事件,比如一个对视、一次长谈、一次散步中,感情就不断升温了。“看见彼此”在互动中比较重要,比如工作中,一个同事看出来另一个同事喜欢帮助他人,会时不时找他帮忙。

喜欢一个人常常是因为对方的缺点,像创造101时的杨超越,给人一种真实可爱的感觉。当然这里要排除不好的缺点,比如不认真、走在前面吸烟等。然后我们也不应该忽视那些强大一点,真正喜欢自己也更合适的人。

爱的经营

爱情是个复杂的问题,需要两个人共同去努力。

刚喜欢上一个人,似乎充满了新鲜感和激情。但足够了解之后,会打破自己之前的一些幻想,展现在自己面前的是一个更真实的人,好像就没有新鲜感和激情了。

一个好的状态是,真的喜欢一个人,会喜欢去探索他,不只想看到他的优点,也想看到他的缺点。不只想看到今天的他,也期待看到明天的他。想了解他的过往、家人、朋友、家乡。想一起面对生活的平淡和挑战。然后我们会反复爱上同一个人。

但现实往往是,我们想改造对方,对对方给与了很高的期望,但对方没有做到,积攒了很多的失望。

凡事还是顺其自然,尽力而为,如果真的经营不下去,也不必强求。

这里举一个ImageTrans的例子:ImageTrans是我维护的一款软件,初始版本的功能都是自己设计的。在发售后,用户不断提意见,才有了现在的成熟度。开始往往并不是完美的,它是需要一个过程的。

爱的苦恼

喜欢一个人,如果没有经验,会有很多苦恼,造成精神内耗,我这里罗列一二:

  • 纠结。多见于爱而不得、想放弃但又想坚持的关系。
  • 占有欲、嫉妒、多想。看到对方和某个异性在一起,脑补出一个电视连续剧。
  • 没有安全感。喜欢对方把各种事情都分享给自己,但对方可能并没有这个习惯或者没有一个身份。
  • 不配得感。觉得对方太优秀了,自己配不上人家。
  • 较劲。你不理我,那我也不理你。你冷淡,我要比你更冷淡。
  • ……

具体的问题

上面讲的比较抽象,其实爱情的复杂在于它充满了具体的问题,没有人告诉我们正确的答案,以致于我们常常举棋不定、瞻前顾后。比如以下这些问题:

  • 还要主动吗
  • 他为什么不主动呢
  • 过节要不要问候一下
  • 他为什么忽冷忽热的
  • ……

这些问题的确需要一定的思考和实践。实践过程中犯错是常事,但如果两个人是合适的,会有很大的包容性,所以不必害怕犯错,重要的是去尝试。有时候还是要遵循自己的感觉。可能自己当下的感觉是错的,但的确是当下自己想做的。

相关链接

爱的话题并不是我擅长的话题,我可能说得比较幼稚,也没有把所有想法说清楚。网上大家其实说得都比我好,下面是相关的链接。

  • 江金霏思想舍,不单纯讲爱情,但涉及到的内容可以帮助我们处理爱情
  • 鹿小草,从不下场的恋爱军师

结尾

下面是吉他曲《爱的罗曼史》的一段视频。听每个音符慢慢地被弹响,心也能安静下来。

视频是一段游戏录屏。我在周末时突然想起这首曲子的旋律,在网易云音乐上找到了它。发现评论里都在说小陆总,说小陆总的某张卡片剧情给了这首曲子新的意义。我搜了下,原来是一个游戏的主角。这个游戏画面的确和这首曲子挺匹配的。

]]>
厦门行 2023-07-29T08:02:50+00:00 xulihang blog.xulihang.me/travel-in-xiamen 上一篇长沙行的博客写了旅行的意义,这次随公司前往厦门,我打算写一些旅行的细节。

厦门是一个美丽的海滨城市,有着一眼可见的美,但也有很多细节需要自己去挖掘和感受。在这个过程中可能会有很多意外之喜,就像生活的常态是平淡的,开心和不开心常常都是意外。

第一天

乘G1653次高铁,经停杭州东-南平-福州-莆田,就到了厦门北站。我们一行58人分别乘坐两辆大巴前往集美学村。华侨对厦门的建设有很大的贡献,集美学村便是由南洋企业家陈嘉庚创办,里面有各类学校。其中的建筑,主体采用西式风格,而屋顶是中式的,很有特色。

我们在导游的带领下,参观了陈嘉庚的故居。

故居南面归来园的门口有一颗大榕树,可以看到一缕缕榕条从枝干垂到树根。我之前好像没见过榕树,在福建应该有广泛的种植。有个小说网站叫榕树下,厦门北边泉州下辖的晋江有晋江文学网,厦门本土有小蓝和他的朋友这样的公司,感觉福建的文创环境还是很好的。

参观完两层陈嘉庚故居后,我们沿着边上的坡路尚南路走回大巴。路边有一些小店和果摊,我看到一家店在卖背包,便上前询问,毕竟我老家海宁也是箱包生产基地。老板娘操着带闽南话口音的普通话跟我说这些包是联合台湾设计生产的,很多都是用来外贸出口的。也不知道是真是假。

往南走,是龙舟池,龙舟池东面是南熏楼,北面是道南楼,都是陈嘉庚建筑的风格。

之后我们便乘大巴,经江泽民主席题字的集美大桥,来到厦门本岛。先经环岛路前往西边的酒店吃饭,再入住东边的日航酒店。

晚上和Evan出去走路。往西行遇到一个弄口,写着云梯街,我们便走了进去。发现里面是一个叫前埔村的城中村,房屋不规则地排列着,沿街有很多的店铺,有肉食蔬菜店、水果店、便利店、理发店、饭店等等。虽然环境比较脏乱差,但有一种生活气息。前埔村是“鹭漂”聚居地,村里有很多河南人,所以有很多河南特色食品店。

穿过前埔村,回到马路上,我们往东返回酒店。沿途看到一家店的老板坐在门口板凳上吹口琴,让我觉得厦门的人可能都多才多艺。有时候喜欢一座城市,除了当地的自然环境,还因为有一群可爱的人啊。

第二天

第二天,我们前往列入世界遗产名录的鼓浪屿。从厦鼓码头乘游船,经过20多分钟到达鼓浪屿。

鼓浪屿的面积是1.8平方公里,上面有错落有致的历史建筑,主要都是居民楼、别墅,也有幼儿园、小学、中学、医院、教堂、商业街等建筑。里面有很多有钱人修建的离宫别馆,后来都改为公用。

我们从三丘田码头下来,前往偏西南方向的日光岩。从海拔近100米的顶峰下来,参观菽庄花园、钢琴博物馆,之后去黄家花园酒店吃饭。饭后,我们跟着导游继续游览了毓园,之后自由活动。

走在鼓浪屿的街上,感觉每一个建筑都是一个景点。也有大海和沙滩,隔着鹭江能看到厦门本岛。

晚上部门活动,去酒店3公里外的大嶝岛海鲜大排档吃饭,就结束了一天的行程。这一天和大名同学的互动比较多,看到了很多同学活泼的一面。

第三天

第三天,我们前往南普陀寺和厦门植物园。

我们大致参观了南普陀寺的各大殿,拜了拜观音。我发现每个殿都有工作人员,让游客不要踩门槛,从正门进后门出。南普陀的素饼很有名,我尝了下类似苏式月饼,不过体积比较小。时间有限,我们又启程前往植物园。

植物园其实就是一个大公园,里面有座万石山,山上有不同的植物区,有雨林植物、藤本植物、多肉植物等等。

我和市场部的同学慢悠悠地从雨林区走到藤本区再走到多肉区,看看离返程时间比较近了,就打算回去。我想也不想就按原路返回了,但其实这样是绕路了。就这样旅程有了一丝意外,不过好在最后顺利从西门出来了。

晚上小组活动,去酒店周边的小店尝了沙茶面。这次的沙茶面不像酒店里那样辣,里面放了蛏子、豆腐等各种料作,配上Chloe要的王老吉,还挺好吃的。

之后和Evan前往厦门市中心,顺便体验下厦门的公交和地铁。我们从酒店走到软件园二期的地铁站,打算乘地铁到莲坂。3公里的路走了快40分钟。后来发现还是乘公交方便,毕竟厦门的地铁建设还不久,地铁网络不是很密集。

厦门的地铁有两个地方比较特别,一个是厦门方言的报站,一个是电子屏幕里滚动推出的厦门树洞。

厦门树洞会有关于工作、情感等各种话题的内容,有些可能是抄的网络段子,但总的来说还是挺有想法的一个设计。

metro line screen

第四天

行程的最后一天,我们去了曾厝垵。我先跟着导游上了环岛健康步道,眺望了金门的大担岛,回来逛了下海滩后再前往曾厝垵。曾厝垵本来是个渔村,有很多历史遗存,目前发展成了一个商业街,里面有很多的店面。类似于海宁的南关厢,但规模大得多。

曾厝垵有一条文创街,里面一家店里的店员是一个设计师,设计了很多文创作品的形象,比如他设计了几百个闽南地区的风狮爷的形象。我才知道我在鼓浪屿买的小狮子叫风狮爷。

四天的行程很快就结束了,我们重新回到厦门北,乘D2424次动车回杭州。

]]>
给Jekyll站点添加全文检索功能 2023-06-04T02:45:50+00:00 xulihang blog.xulihang.me/how-to-add-full-text-search-to-jekyll-blog 这两天给本站添加了全文检索功能,在此把过程做一个记录。

全文检索,顾名思义就是用关键词匹配全文,得到符合条件的内容。如果所需检索的内容不多,可以直接进行搜索。但如果内容比较多,像本博客算上这篇有400篇了,搜索的性能会比较糟糕。这个时候需要先建立索引,再进行搜索。

如果有后端的话,通常是由后端提供全文检索服务。但本站是个基于Jekyll的纯静态站点,只能选择Google CSE、Algolia这样的第三方服务或者直接在前端进行检索。

之前一篇博客介绍了如何用lunr.js在前端实现全文检索。它的一个缺点是对中文支持不佳,需要用nodejs在桌面去给中文建立索引。

时代在发展,这次我们选择用FlexSearch作为全文检索引擎。它有更好的性能和灵活性,可以自由配置以支持中英文检索。

使用FlexSearch检索中英文

为了让FlexSearch支持中英文检索,我们需要修改它的encode方法,把中文按字拆分,英文用单词拆分。比如“FlexSearch全文检索”,需要被拆分为“FlexSearch”、“全”、“文”、“检”、“索”。

我们可以用JavaScript的正则替换功能,给单个中文的两边加上空格,之后再根据空格split来做到这点。

下面是具体的代码:

let index = new FlexSearch.Index({
  tokenize: "forward",
  encode: str => str.replace(/[:"“”:]/g, " ").replace(/\n/g, " ").replace(/([\u4e00-\u9fa5])/g, " $1 ").split(" ")
});

这里还替换了冒号、换行、引号等内容,避免英文单词和它们相连时会检索不到的问题。

使用Liquid模板生成文章内容的JSON文件

我们需要把所有文章的内容保存成一个JSON文件,供前端使用。

我们可以用Liquid来实现。

  1. 在站点根目录建一个叫posts.json的文件。

  2. 添加一段yaml头信息,这样构建站点时会把它视为一个liquid模板文件。

    ---
    # Content for Full-Text Search
    layout: null
    ---
    
  3. 使用Jekyll的Liquid保存站点内容为JSON。这里需要使用一系列filter来使内容符合JSON规范。保存的内容包含博客标题、正文、发布时间和URL。

    {
        "posts":[
            {% for post in site.posts %}{
                    "id": {% increment index %},
                    "title": {{ post.title | jsonify }},
                    "date": {{ post.date | date:"%Y-%m-%d" | jsonify }},
                    "text": {{ post.content | strip_html | normalize_whitespace | jsonify }},
                    "url": {{ post.url | relative_url | jsonify }}
                }{%- unless forloop.last -%},{%- endunless -%}
            {%- endfor -%}
        ]
    }
    

最终在站点构建时会生成1MB多的一个JSON文件。gZip压缩后,实际传输的大小500KB不到。

gzipped json

注意这里缓存时间只有10分钟,我们可以把这个JSON文件存到indexedDB里做持久存储,然后根据last-modified信息判断是不是要更新。

构建一个搜索页面

  1. 新建一个search目录,在里面放一个index.html文件。

    ---
    title: 搜索
    layout: default
    enableSearch: true
    ---
           
    <div>
      <input type="text" class="keywords"/>
      <label><input type="checkbox" class="matchAll" checked/>完全匹配</label>
      <button class="search-button">搜索</button>
      <span class="status"></span>
    </div>
    <div class="search-results"></div>
    <script src="/media/js/search.js"></script>
    
    
  2. 修改默认的layout文件,如果页面启用了检索,从CDN引入FlexSearch。

    <head>
    {% if page.enableSearch %}
        <script src="https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.7.31/dist/flexsearch.bundle.js"></script>
        {% endif %}
    </head>
    
  3. 建立一个search.js文件,用于存放对应的JS代码。

  4. 下载posts.json并建立索引。需要建立索引的内容包含标题和正文。

    let index = new FlexSearch.Index({
      tokenize: "forward",
      encode: str => str.replace(/[:"“”:]/g, " ").replace(/\n/g, " ").replace(/([\u4e00-\u9fa5])/g, " $1 ").split(" ")
    });
    
    async function indexDocument(){
      updateStatus("索引中……");
      const jsonString = await downloadPosts();
      const result = JSON.parse(jsonString);
      posts = result.posts;
      for (let i = 0; i < posts.length; i++) {
        const post = posts[i];
        index.add(post.id,getContentToIndex(post));
      }
      updateStatus("");
    }
    
    function downloadPosts(){
      return new Promise(function(resolve){
        updateStatus("下载博客内容中……");
        function reqListener() {
          updateStatus("");
          resolve(this.responseText);
        }
        const req = new XMLHttpRequest();
        req.addEventListener("load", reqListener);
        req.open("GET", "/posts.json");
        req.send();
      });
    }
       
    function getContentToIndex(post){
      return post.title + " " + post.text;
    }
    
  5. 如果URL参数有keywords这项或者用户点击了搜索按钮,进行检索。检索时,用History API更新浏览器历史,URL中的检索词参数会有变化,执行前进和后退也能对应到之前的检索记录。使用History API的一个好处是页面不用刷新,不用重新建立索引,虽然建立索引基本上不花时间。

    document.getElementsByClassName("search-button")[0].addEventListener("click",function(){
      search();
    });
       
    window.addEventListener("popstate", (event) => {
      checkURLParamAndSearch();
    });
       
    function checkURLParamAndSearch(){
      if (getURLParameter("keywords")){
        document.getElementsByClassName("keywords")[0].value = getURLParameter("keywords");
        search();
      }
    }
       
    function search(){
      updateStatus("搜索中……");
      const keywords = document.getElementsByClassName("keywords")[0].value;
      const results = index.search(keywords);
      console.log(results);
      listSearchResults(results);
      const newURL = window.location.origin + window.location.pathname + "?keywords=" + encodeURIComponent(keywords);
      if (newURL != window.location.href) {
        history.pushState(null, null, newURL);
      }
      updateStatus("");
    }
    
  6. 在文章内容中查询关键词,截取它前后50个字符的内容用于在搜索结果中显示,并高亮关键词。

    function getHighlights(content){
      const keywords = document.getElementsByClassName("keywords")[0].value;
      let context = getContext(content, keywords);
      const regexForContent = new RegExp(keywords, 'gi');
      // Replace content where regex matches
      context = context.replace(regexForContent, "<span class='hightlighted'>$&</span>");
      return context;
    }
       
    function getContext(content,keywords){
      const startIndex = Math.max(0,content.indexOf(keywords) - 50);
      const endIndex = Math.min(content.indexOf(keywords) + 50 + keywords.length, content.length);
      return content.substring(startIndex, endIndex)+"...";
    }
    

下面是最终结果的截图:

Full-text search screenshot

全文检索还有很多可以优化的地方,比如大小写匹配、词型还原、模糊匹配、条件检索等等。用于本站的话,目前的功能也足够了。

]]>
如何做好软件的市场工作 2023-05-20T06:45:50+00:00 xulihang blog.xulihang.me/software-marketing 我在一家2B的软件公司的市场部做了两年多的工作,自己也运营着一款同时面向个人用户和企业的软件3年多,这里想把过往的经验做个总结。

市场工作,对应的英文是marketing,在台湾译为行销,而在日语则不做翻译,直接用片假名。工作内容主要是明确目标用户并做营销和推广,让用户发现并选择自己的产品。

定义目标用户

针对什么样的用户,做什么产品,好的开始是成功的一半。

比如我要做辅助漫画翻译软件,目标用户就是有能力和意愿自己翻译出高质量漫画的人群或者公司,而不是希望点几下就能自动翻译漫画的用户。后者对软件的精细度要求低,竞争对手也会很多。辅助翻译软件对效率有很大的提升,付费需求也会更高。

对于个人开发者来说,很多时候,可以先做一款自己会使用的软件。即使没有别人用,能帮助到自己也是有用的。

用户转化的流程

这里用一些专业术语,来说明用户转化的流程:

  • 访客(visitor):访问了软件的相关内容
  • 线索(lead):访问后主动联系了,提供了邮箱等联系方式
  • 客户(customer):完成购买,成为了客户
  • 推广者(promoter):用户完成购买后,还接着帮忙推广软件

推广方式

主要有建设网站、社交媒体营销、发贴等方式。

网站

网站可以包含各种内容,有产品描述页面、博客、视频、电子书、信息图表等等,帮助用户发现和了解产品。

做内容时要适当考虑搜索引擎优化(SEO),方便搜索引擎检索到自己的内容。要思考下用户会用什么关键词去搜索到自己的产品。如果有钱,也可以买一些广告,做付费推广。

社交媒体营销

经营自己的媒体账号,积累粉丝。也可以联系网红KOL,让他们帮助营销。

发贴

前往潜在用户聚集的论坛等网络平台发贴。

内容创作的一点思路

  • 结合某个应用案例
  • 结合某个热门技术
  • 结合热点事件

需要一直做市场推广吗

我个人而言,前期投入精力把需要的网站、文档、博客、视频建设好后,可以暂时不做内容更新。目标客户能发现自己的产品就行。后面随着软件更新和市场的变化再看看是不是要更新已有内容或者做一些新的市场工作。

]]>
相信 2023-05-16T13:33:50+00:00 xulihang blog.xulihang.me/xiang-xin 相信在现代汉语词典里的意思有认为某事正确、信赖某人。而在吴语方言中,它也有喜欢的意思,比如相信十字绣,是说有十字绣这个爱好,不过这一含义可以视为相信这个词的基本含义的引申义。因为相信具有多重含义,本文的英文标题采用的相信的拼音,而不是belief、hobby等词。

相信这件事情为我们不同的人生阶段提供了动力,比如相信高考考上好的大学,能出人头地,比如相信学好本专业的知识,一定能找到好的工作。因为我们相信,所以能比较纯粹地去做一件事情。

但现实却常常让我们发现,之前相信的事情并不完全是正确的。历史的车轮不断向前,一切都在发生着变化。各种条件在变,人自身的认识也在变。

其实很多事情,不必放在一个很宏大的背景里面。只要有一点意义,就可以去相信,去做一些事情。

我维护计算机辅助翻译软件快5年了,我知道它们是能切实提高工作效率的,我对此十分相信,所以我能一直坚持做下去。

但很多事情并不像软件那样容易掌控,比如相信爱情、相信医生、相信健身教练这样依赖他人的事情。这时候还是需要自己学习一定的知识,做出正确的判断。

]]>
买房 2023-04-23T13:27:50+00:00 xulihang blog.xulihang.me/bought-an-apartment-in-China 在杭州租房两年多,在翠苑买了一套二手房。60多平,总价200多万。

为什么不继续租房,因为之前租的城中村的房间虽然月租金只要1200元,但面积小,想换套大的,家庭手头现金也足够,干脆买房了。贷款30年,月供5800,其实和整租一套好点的房子价格差不多,但房子是自己的了,有产权、想怎么处理房子就怎么处理、房产还可能升值、加强归属感。

看房子不是一件容易的事情,我们可以先列出一些条件来做筛选:

  • 价格
  • 面积
  • 户型
  • 设计
  • 楼层
  • 交通
  • 房龄
  • 周边环境
  • ……

最终通过我爱我家中介,选了一套98年一梯两户南北通透的房子,原房东是浙大教授,房子装修得不错,空了一年,没有出租就是因为装修好,想直接转手给有缘人。

确定后就是走流程:

  • 签合同
  • 办理贷款,选择30年等额本息的商业贷款
  • 等卖家还清贷款
  • 过户
  • 银行放款
  • 交房

买房之后,还要考虑装修和买家具。和租房不一样,以前遇到事情找房东,现在都要自己操办了。

在这个过程中可能会遇到不少问题,但方法总比问题多。既然选择了这套房子,就要好好地使用它。

]]>