蒙奇在路上

迷之操作|Django版静态Note生成器

Powered by Django

正如这句话而言,目前这个网站是用Django来搭建的,至于当初为什么要选择Django,可能是因为它作为最流行的Web框架之一,开发迅速,以及自带的admin管理,可以迅速成型一个小型站点,YYDS。

Why change

最初这个网站是部署在HK的一个VPS上的,配置也是最低配置,解析是套了一层CF,所以造成国内访问非常慢。不过也可以忍受,就没在这上面折腾。按理说这种网站是用来写的,但我好像不务正业的折腾起来了页面,每换一种风格,用不了多久就觉得看着不舒服,于是又重新改了一套页面,于是又重复于此。

前段时间正好赶上各大云厂商双11做活动,于是在结束的最后一天在良心云上薅了一台国内的VPS,配置要比之前的好一点,而且在国内速度跟之前比也是起飞一般的速度。

在迁移项目的时候突然想到,现在的数据都是在当前主机上的,万一哪一天服务器忘记续费的话,等到期的时候数据岂不是就全丢了,要是把数据全上传到Git,这样不就不用担心了,而且切换服务器的时候,直接把项目拉下来直接就能跑,就像Page服务一样,那岂不是妙哉。

Ready to go

切换数据库从MySQL到SqlLite

因为SqlLite数据库是一个本地文件数据库,对于这种小型站点来说,十分便携方便,而且还可以直接上传到Git,所以就把数据全导入到这个数据库里边了。

全站静态化

首先统计网站的页面都有哪些,每个页面的路由都是什么,通过这些可以组织静态网页存放的目录结构,而且这些在之后的Nginx配置中也十分重要。

比如我的文章路由为https://itsso.cool/blog/django-staticize.html,那么文章 django-staticize.html就应该放在静态文件根目录的 blog目录下。

由于平时写笔记喜欢在Typora上写完之后,在把文章源码复制到在线的编辑器内,再保存上传发布,同时笔记文件也会同时上传到Github以做备份。但是这样就会存在一个问题,假入想要修改其中某一篇内容的话,重复上面的步骤,实在繁琐,要是把笔记文件连同静态文件在项目部署的时候一起打包发布就好了。

所以就开始改造模型,修改文章内容字段为FileField,将笔记文件的相对路径保存到本地数据库内,每次写完就直接把文件上传。

在构建静态文件的时候,数据来源从本地保存的文件来读取,这样一来既可以将笔记备份,同时在修改内容的时候也可以极大的缩减上传步骤。

给Typora配置专属图片上传服务器

在写笔记的时候难免会有一些图片,在上传这些图片的时候就会比较麻烦,因为Typora里边文件存放的只是一个当前主机的绝对路径,直接将笔记发布到线上的时候,图片会因为路径的问题加载不出来。此时就需要将图片保存到一个公网可以访问的一个图床内。

好多人的做法是将自己的图片上传到微博、知乎、csdn等一些知名网站上,然后再将这些图片链接插入到自己的笔记内。这样做虽然是很方便快速,但是也会存在一些问题,就比如微博在某一天,突然给自己的链接加上了防盗链,导致了所有的图片在自己以外的网站不能继续使用。

另外的像GitHub/Gitee Page服务,也可以将自己的图片上传到他们的服务器,但是据说会存在不稳定的情况,想了想还是算了。

自己的东西掌握在自己手里才踏实,于是就自己搞了一个专属于自己的图床服务,这次倒是没有选择Django,而且选择了更合适轻量化的Flask。

#!/usr/bin/python
# -*- coding: UTF-8 -*-
import os
import random
import string
from datetime import datetime

from flask import Flask, request
from markupsafe import escape
from werkzeug.utils import secure_filename

from dotenv import load_dotenv

env_path = '.env'
load_dotenv(dotenv_path=env_path)

STATIC_FOLDER = "images"
STATIC_URL = "/img/"

app = Flask(__name__, static_folder=STATIC_FOLDER, static_url_path=STATIC_URL)

app.config['UPLOAD_FOLDER'] = STATIC_FOLDER
basedir = os.path.abspath(os.path.dirname(__file__))
ALLOWED_EXTENSIONS = {'txt', 'png', 'jpg', 'xls', 'JPG', 'PNG', 'xlsx', 'gif', 'GIF'}

HOSTNAME = os.getenv('HOSTNAME')


# 用于判断文件后缀
def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS


# 上传文件
@app.route('/api/upload', methods=['POST'], strict_slashes=False)
def api_upload():
    access_token = request.form.get("access_token") or " "
    if access_token != os.getenv("ACCESS_TOKEN"):
        return "error", 403

    time_path = datetime.now().strftime("%Y/%m/")
    file_dir = os.path.join(basedir, app.config['UPLOAD_FOLDER'], time_path)
    if not os.path.exists(file_dir):
        os.makedirs(file_dir)
    f = request.files['file']  # 从表单的file字段获取文件,myfile为该表单的name值
    if f and allowed_file(f.filename):  # 判断是否是允许上传的文件类型
        filename = secure_filename(f.filename)
        ext = filename.rsplit('.', 1)[-1]  # 获取文件后缀
        ran_str = ''.join(random.sample(string.ascii_letters + string.digits, 8))
        new_filename = ran_str + '.' + ext  # 修改了上传的文件名
        f.save(os.path.join(file_dir, new_filename))  # 保存文件到upload目录
        return HOSTNAME + STATIC_URL + time_path + escape(new_filename)
    else:
        return "不支持的文件格式!"


if __name__ == '__main__':
    app.run(debug=True, port=8080)

仅需这些代码就可以搭建一个轻量的图片上传服务器,Flask真香~

接下来就是配置Typora,在图片复制进来的时候自动上传到服务器

在Typora→Preferences→Image→Image Uploader选择Custom Command,同时在When Insert选择Upload image

kwhpUo4f

部署到服务器,接下来我们选择使用脚本来上传,那么就需要一个脚本来对接我们的图片服务器

#!/bin/bash

# 各类配置信息
base_url="https://img.itsso.cool/api/upload/"
access_token="xxxxxxxxxx"

# 上传图片
for i in "$@"; do
    curl -POST $base_url -H "Content-Type:multipart/form-data" -F "file=@$i" -F "access_token=$access_token"
    echo ""
done

点击Test Uploader就可以测试一下我们脚本是否可以正常上传

swQeFOjS

现在来看一下效果如何,复制一张图片到Typora。

LAbunMRk

配置WebHook让远程服务器自动拉取更新代码

所谓webhook,就是用户可以自定义一种回调函数,通过这种回调函数来改变web应用的行为,这些回调函数可以是web应用的开发者,也可以是第三方用户,并且与原始的web应用没有关联。

这里采用的是Flask搭建一个webhook服务,并通过该服务触发脚本来完成整个流程操作

首先在GitHub上开通webhook,并添加secret,选择settings→webhooks

使用Flask搭建一个简易的webhook服务,其中要注意对请求来源做验证,GitHub官方已经提供了验证方法

Note: For backward-compatibility, we also include the X-Hub-Signature header that is generated using the SHA-1 hash function. If possible, we recommend that you use the X-Hub-Signature-256 header for improved security. The example below demonstrates using the X-Hub-Signature-256 header.

接下来就是完整的代码:webhook.py

import hmac
from flask import Flask, request, jsonify
import subprocess

app = Flask(__name__)
# github中webhooks的secret
github_secret = 'xxxxxxxx'

def encryption(data):
    key = github_secret.encode('utf-8')
    obj = hmac.new(key, msg=data, digestmod='sha256')
    return obj.hexdigest()

@app.route('/hook', methods=['POST'])
def post_data():
    """
    github加密是将post提交的data和WebHooks的secret通过hmac的sha256加密,放到HTTP headers的
    X-Hub-Signature256参数中
    """
    post_data = request.data
    token = encryption(post_data)
    # 认证签名是否有效
    signature = request.headers.get('X-Hub-Signature-256', '').split('=')[-1]
    if signature != token:
        return "token认证无效", 401
    # 运行shell脚本,更新代码
    subprocess.run(["bash", "auto_deploy.sh"])
    return jsonify({"status": 200})

if __name__ == '__main__':
    app.run(port=9000)

要触发的脚本文件:auto_deploy.sh

cd "$(dirname "$0")"
echo '--------Git fetch------------'
git fetch
echo '--------Git merge------------'
git merge
echo '-----Already up-to-date------'
echo '----- reload nginx-----'
nginx -s reload

接下来将webhook服务部署好,在项目文件夹下push代码的时候,就会触发该hook,在远程服务器自动将更新的代码拉取下来。

到此为止,用了奇奇怪怪的方法实现了自己奇奇怪怪的想法。

其实现在已经有很多很成熟的静态博客生成器,比如Hugo,Jekyll以及Hexo等等,以前搞过一次,后来网站没了,再加上觉得上传方式并不是那么Geek,也有可能是我没找到一些方便的方法,就直接放弃了。

后来重新搭建网站的时候,就打算干脆直接搭建一个动态的网站,至少很多东西要比静态的灵活方便。

至于现在为什么又变成这样了,那可能是

脑子抽风了吧。

Django批量创建时出现bulk_create内存异常

背景

因为需要往项目数据库上传大量数据,数据是以文件的方式存储,所以采用django的bulk_create批量读取并上传,但是在上传过程中发现,上传程序占用的内存一直在上升,甚至到最后直接把内存占满了。

排查问题

刚开始首先怀疑的就是程序代码有问题导致内存没有释放,自己看没发现什么问题,请教同事帮忙看也没有发现什么问题,所以干脆就硬着头皮去试代码。在每次循环之后都加入gc.collect(),尝试主动释放内存,发现问题仍然存在。

于是开始尝试内存排查工具tracemalloc来排查什么地方一直在增加内存

def batch_insert(filepath):
    tracemalloc.start()
    start_snapshot=tracemalloc.take_snapshot()  # 建立快照
    path = Path(filepath)
    for i, p in enumerate(sorted(path.glob('**/*.json')), start=1):
        with p.open(encoding='utf-8') as f:
            data = json.load(f)
    			... # 内容整理
        with transaction.atomic():
            try:
              	# 批量插入数据库
                Regulation.objects.bulk_create(rules)
            except Exception as e:
                print(e)

        gc.collect()	# 主动释放内存,
       	end_snapshot = tracemalloc.take_snapshot()
        top_stats = snapshot.compare_to(start_snapshot, 'lineno')
        for index, stat in enumerate(top_stats[:50], 1):
            frame = stat.traceback[0]
            print("#%s: %s:%s: %.1f KiB"
                  % (index, frame.filename, frame.lineno, stat.size / 1024))

RFK6GbLQ

ewSJd0CP

发现内存主要的增长是在第一行,而且后面也主要和MySQLdb有关,那么就去看看到底是怎么回事,点开源码在发现django/utils/encoding.py:62

def force_str(s, encoding='utf-8', strings_only=False, errors='strict'):
    """
    Similar to smart_str(), except that lazy instances are resolved to
    strings, rather than kept as lazy objects.

    If strings_only is True, don't convert (some) non-string-like objects.
    """
    # Handle the common case first for performance reasons.
    if issubclass(type(s), str):
        return s
    if strings_only and is_protected_type(s):
        return s
    try:
        if isinstance(s, bytes):
            s = str(s, encoding, errors)
        else:
            s = str(s)
    except UnicodeDecodeError as e:
        raise DjangoUnicodeDecodeError(s, *e.args)
    return s

虽然有注解,但还是不明所以,那最起码看看是谁在调用这个方法总也行吧

grep -nr 'force_str('

发现还不少调用,那就找到关键的信息继续往上追溯

上面那么多虽然都有调用,但是与我们实际的使用情况不符,因为使用的mysql,所以发现红线部分挺符合预期,继续查看代码django/db/backends/mysql/operations.py:171

def last_executed_query(self, cursor, sql, params):
    # With MySQLdb, cursor objects have an (undocumented) "_executed"
    # attribute where the exact query sent to the database is saved.
    # See MySQLdb/cursors.py in the source distribution.
    # MySQLdb returns string, PyMySQL bytes.
    return force_str(getattr(cursor, '_executed', None), errors='replace')

继续查找调用改方法的地方

grep -nr 'last_executed_query('

找到源码django./db/backends/utils.py:113

@contextmanager
def debug_sql(self, sql=None, params=None, use_last_executed_query=False, many=False):
    start = time.monotonic()
  try:
    yield
  finally:
    stop = time.monotonic()
    duration = stop - start
    if use_last_executed_query:
      sql = self.db.ops.last_executed_query(self.cursor, sql, params)
      try:
        times = len(params) if many else ''
        except TypeError:
          # params could be an iterator.
          times = '?'
          self.db.queries_log.append({
            'sql': '%s times: %s' % (times, sql) if many else sql,
            'time': '%.3f' % duration,
          })
          logger.debug(
            '(%.3f) %s; args=%s',
            duration,
            sql,
            params,
            extra={'duration': duration, 'sql': sql, 'params': params},
          )

研究一下代码发现,sql = self.db.ops.last_executed_query(self.cursor, sql, params),在批量上传的时候,会将所有要上传的内容变成一条sql语句,到此为止目前还没发现有什么异常。

但是看到下面这一段,Django将生成的sql语句保存起来,那这个对象会清空之前保存的sql吗?

self.db.queries_log.append({
                'sql': '%s times: %s' % (times, sql) if many else sql,
                'time': '%.3f' % duration,
            })
            logger.debug(
                '(%.3f) %s; args=%s',
                duration,
                sql,
                params,
                extra={'duration': duration, 'sql': sql, 'params': params},
            )

经过Debug发现,是不会的,每生成一条sql就添加到self.db.queries_log里,只要程序没有停止,那么这里边的sql就永远不会消失,所以真相大白,原来就是这个家伙导致的内存一直飙升。

继续向上看,寻找是否存在参数可以选择是否保存这些sql,在django/db/backends/utils.py:97发现这段代码

def execute(self, sql, params=None):
    with self.debug_sql(sql, params, use_last_executed_query=True):
            return super().execute(sql, params)

发现确实存在一个参数use_last_executed_query,但是这个参数已经写死在代码里。。。

尝试修改源码将use_last_executed_query设置为False,运行代码发现问题解决。

后来有尝试将sql语句放入self.db.queries_log这段代码注释掉,运行代码仍然可以解决。

至此已经发现问题的真正根源出在什么地方了,但是通过修改源码的方式总归是不合适的,继续尝试有没有Django自带的方案。经过查找发现django/db/__init.py:26

# Register an event to reset saved queries when a Django request is started.
def reset_queries(**kwargs):
    for conn in connections.all():
        conn.queries_log.clear()

于是尝试在代码中引入该方法

from django.db import reset_queries

...
# 插入数据库之前首先清空
reset_queries()
# 批量插入数据库
Regulation.objects.bulk_create(rules)

发现问题完美的得到解决,完美!

后来在官方文档中发现,已经有这个方法的相关文档

dmTAMrGI

文档中还说只有在DEBUG=True时,这些sql语句才会保存起来以便有需要的时候查看sql。

这也解答了我心中的一个疑问,系统运行时产生的sql为什么要保存起来呢,现在看来是我知识浅薄了。

所以最终这个问题的解决方案有两个:

一、将DEBUG设置为False

二、手动清除,引入django.db.reset_queries

就第一种方案而言,只有在生产环境下,DEBUG选项才会为False,所以在自己的电脑上或者测试机上运行,还是第二种方案比较好。

Elasticsearch相关汇总

在使用Elasticsearch的过程中,难免会要去官网翻看文档,由于目前还没有读完文档,以至于在找一些没用过的API时还挺费劲,有时候甚至还可能找不到。因此就把目前已经用到过的地方在这里汇总记录一下,方便以后碰到的话可以直接去查看。

Index

Aliases

索引的一个别名,在某些情况下非常有用,比如在无缝切换索引的时候。

Mappings

索引的mapping定义十分重要,他决定了我们的数据是如何保存在索引内,以及保存的数据都有什么字段,各个字段的数据类型又是什么。

Setting

Query

Full text queries

全文检索相关,主要包含match querymatch_bool_prefix querymatch_phrase querymatch_phrase_prefix querymulti_match queryquery_string querymatch_bool_prefix querymatch_phrase querymatch_phrase_prefix querymulti_match queryquery_string query

Compound queries

混合索引,包含bool queryboosting queryconstant_score querydis_max queryfunction_score query

Function score query

用户可以通过自定义一个或多个查询语句来提高某些文档的比分权重,

还可以通过script_score使用脚本给每个文档重新打分

Highlight

Prefix query

使用前缀查询可以返回前缀为指定前缀的文档,多用于即时搜索一类的提示。

Match phrase prefix query

当需要对一个短语或词组进行前缀查询时,就需要用到来进行搜索了

Named query

通过使用_name参数可以在多字段查询时知道是哪个子查询语句命中了该文档,并将结果返回在每个响应文档的matched_queries字段内。

Nested query

Exists query

在某些情况下,并不是所有的字段都存在确切的值,可以通过Exists来或者筛选包含某些字段的文档,同时配合must_not可以来筛选所有存在该字段的文档。

Scripts

ES的脚本语言是painless,语法与Java类似,可直接按照Java的语法来编写检索脚本,具体可见地址:Shard API

这里只记录一下自己使用到的,以便以后再遇到可直接CV。

删除数组内满足条件的元素

使用removeIf来完成,例如删除ID为10的元素

{
  "script": {
    "source": "ctx._source.members.removeIf(list_item -> list_item.id == params.member_id)",
    "lang": "painless",
    "params": {
      "member_id": 10
    }
  }
}

判断数组内是否包含某一个对象

使用contains来完成,返回包含name张三的文档

{
  "script": {
    "source": "ctx._source.members.contains(params.name)",
    "lang": "painless",
    "params": {
      "name": "张三"
    }
  }
}

根据时间提高某些文档的权重

使用时间格式化方法toInstanttoEpochMilli来完成,将时间转换成毫秒级权重因子

{
    "script": {
        "lang": "painless", 
        "source": "double dateScore; try {dateScore = Math.abs(doc['enforcementDate'].value.toInstant().toEpochMilli()/1e12);} catch (Exception e) {dateScore=0;} return dateScore;"
    }
}

我不报歉|人间失格

《人间失格》作为著名的“丧”文化代表,其实在读高中的时候就早已耳闻,只不过那时候学校赶的紧,再加上自己也不喜欢看书,就不了了之了。后来随着年纪的增长,虽说已经有大把的时间可以读完这本书,但这时自己又抗拒又害怕那种“丧”,所以也就一直没有去看过。

至于为什么又突然看起来这本书了,可能是最近心情低落,而我又偏喜欢在心情不好的时候找些比较致郁的东西来听或者看,这样反而会觉得有那么一丝愉悦......

断断续续用了将近两天的时间看完,书的内容确实很阴郁,整篇采用第一人称的方式,就好像一个坐在你面前的人,在絮絮叨叨他的一生。

可能是没有什么文学素养,再或者是觉得他人的悲惨与自己无关,总之看的过程很平静,就好像是在观看一场一个人的悲惨演出而已。唯一一次动容的时刻,是在直治自杀之后写给姐姐的遗书里有这一段话。

我早就该死了。只是,唯一让我牵挂不下的便是慈爱的妈妈,想到这个我才没有去死。人,就像享有自由生存的权利一样,也享有决定自己什么时候死的权利,可是我觉得,只要母亲在世,这个死的权利是不得行使的,否则就等于是亲手将自己的母亲杀死。

大概是因为在漫长的阴郁里,突然出现了一抹温柔吧。

其实看到叶藏费尽心思去迎合别人的时候,也好像看到了以前自己的影子。

学着抽烟,因为大家都抽烟

学着打架,因为兄弟们打架不能少了你

学着寒暄扯皮,因为你要嘴皮子玩的溜

不懂拒绝,因为拒绝可能会伤了对方的心

年轻人看到什么书就以为自己是什么样的人,越仔细回想,就越有代入感,幸好以前并没有看这本书。

倘若叶藏可以摒弃自己的懦弱和骄傲,包容自己的不足,他是不是就不会变成那样,包括直治也是。

把他们当作是一部反面教材,鞭策自己,包容自己,活出自我。这样是否也算是这本灰暗阴郁的书的正面意义呢。

最后附上书中的一句话:

一切都会过去的

平庸者的崩坏|绝叫

最近看了一本日本的悬疑推理小说,讲的是一名出生在昭和年代的女人挣扎的前半生,读的过程一度绝望,甚至一度觉得它就是《被嫌弃的阳子的一生》。泡沫经济,失业,大地震,家庭暴力,校园暴力,自杀率攀升等种种因素结合在一起的日本社会,在这样的社会背景下,平庸的普通人只能一步一步的陷入黑暗。

整本书是由三个线路分别同时叙事,同时也保证了结构安排合理,伏笔也埋的不深,所以整体读下来还是比较流畅的。

首先一条线就是以第二人称为叙述方式,仿佛自己就是站在局外人或者是高高在上的审判者的角度一样,评论叙述着铃木阳子的一生,刚开始读的时候还很不习惯,直到最后才觉得这种叙述方式的巧妙之处。

另外一条线是以女警官凌乃的角度来写的,作为这场刑事案件的负责人,对案件进行深入调查之后,发现腐烂在房间的死尸就是铃木阳子。同时在调查的过程中,两个人甚至还有了交集。

最后一条线则是以对一个嫌疑犯八木审讯来记录的,刚开始看的时候会觉得莫名其妙,甚至一度因为他的审讯记录就觉得猜到了结局,其实则不然,这条线感觉更像是一个画外音。

铃木阳子出生在一个重男轻女的家庭,自小都不受家庭重视,甚至连名字都是随便起的。

自从弟弟纯子出生以后,阳子就备受母亲的忽视和否定。当纯子遭受校园暴力自杀之后,可笑的母亲甚至还相信纯子在学校里和其他人相处的很好,对阳子的态度始终如一。

之后日本经济大崩盘,无数人失业自杀,阳子的父亲因为轻信股票可以带来高收益,导致负债上千万,又遇裁员致使失踪。因为阳子父亲的负债导致房子抵押,只好搬出去住,此时的母亲却独自一个人跑去舅舅家住,抛下阳子一个人不管。至此这个家已经完全分崩离析。

之后阳子在当客服之后,遇到了当初暗恋的山崎,俩人成婚之后因为山崎的外遇和失业,又被迫离婚,期间只有两年多的时间。

离婚之后的阳子又当上了保险推销员,在这期间经历了职场PUA,但是缺爱的他却以为上司是对他好,爱上了这个上司。甚至为了他去出卖的自己身体,通过性交易的手段来拉拢客户,去满足上司的要求。

最后阳子虽然发现上司是一个渣男之后,但是他已经无法从这种泥沼中摆脱自我,辞去了保险的工作之后就去做了应召女。在做应召女的过程中,由于身心俱疲,无法辨识牛郎的陷阱,对牛郎的慰问呵护无法自拔。谁能想这个牛郎却也是个烂人,对阳子家暴,无付出一味的索取。

突然有一天阳子下班的时候,被神代和其同伙拖上了车,抢走身上所有的钱财,并且强奸了阳子,阳子一度窒息而亡。此刻的阳子也开始觉醒。

阳子和神代开始计划杀人骗保,第一个对象就是这个牛郎。之后因为这样来钱快,又寻找新的对象开始杀人骗保。阳子已完全堕入深渊。此刻的阳子已经开始反抗,合伙刺杀神代,独自杀害亲母,诱杀朋友,偷梁换柱,整个过程冰冷的像一个机器人一样。

阳子本就是一个平庸的不能再平庸的人了,可奈何时代的灰尘落到普通人的身上竟会像一座山一样,压的人痛苦不堪,只得挣扎绝叫。

自我绑架

晚上做了一场似乎荒诞又真实的梦。

在一栋正在修建的写字楼里,电梯已经布置好了并可以正常使用。我和一群人在电梯里缓缓上行,突然电梯门开了,应该是中途有人乘坐。然而电梯门打开之后,映入眼帘的却是一位老太太因为脚卡到破碎的一块墙体里边,不停的往外拔,旁边的老伴也一直在帮他用力,只是因为年纪大了,似乎并没有什么作用。

我看到这一幕,立即就往电梯外走,同时跟电梯里的人说:“咱们去帮帮她吧,那个墙估计很沉,我们一起帮帮她...…”,同时又用手按着点击的闸门,防止它自动关上。

可是任凭我怎么说,那些人好像听不到我说话一样,面无表情。僵持了好长一会,终于有一个人动了,本以为那人是要和我一起,哪知道那人就往前走了一步,推开了我的手,紧接着他按了一下关门键,电梯门缓缓关上,只剩我一个人在电梯外边。

由于受到一些新闻影响,心里不自觉的有一些警惕性,觉得我应该拿手机拍下来整个过程,这样以防万一也好有个证据。可是就我一个人,一只手拿着手机的话,另外一只手就没办法抬起来那水泥墙。

纠结无奈之下,觉得就把手机递给那老大爷,让他拿着手机拍,递过去之后心里也突然愧疚自己怎么能这么看人家,世界上哪有那么多坏人。

由于是实打实的水泥墙,抬的过程是真费力且焦急。以至于没有心思保持自己的警惕。快要抬过去的时候,那大爷突然跟我说:“手机锁屏了,密码多少,要解开才能继续拍”。我这时正一心思抬东西,完全没有想太多,就把密码告诉他了。后来想想自己真蠢。

过了一会,我终于把那老太太的腿拔出来,气喘吁吁的问那大爷要手机的时候,那大爷却说:“你问我要什么手机,我没见你的手机。

”你手机拿的就是我的啊,刚才我让你帮我拍着视频“

”这是我的啊,怎么会是你的,是你的你能解开吗“

说完那大爷就把手机递给我,让我看看能不能解锁,我输入我的密码,发现已经不能解锁,人脸识别也不能用了,这时我才意识到,我刚告诉那大爷手机密码之后,手机密码已经被那大爷给改了。

于此同时,那老太太也在拿手机拍着我,说到:

”不行吧,不行就赶紧还给我们,你不还我就要报警了,我这可是录着像呢“

脑子里血气上涌,感觉要被气死了,心里骂着:”操他妈的,这是被坑了啊“,可是我还是想着先好好聊,就语气平稳但还是略带一点急躁的问他们要,让他们还给我。

突然,闹钟响了,突然把我从梦境中扯回到现实,可是我心里还是觉得很不爽,就赶紧把闹钟关了,试图再次进入这个梦里,我势必要把我的手机夺回来,我咽不下这口气!

可惜,续梦失败!

躺在床上的我回想着这场的梦,长舒一口气,感叹这还好只是场梦。

假如这不是梦,我又会不会后悔自己一个人走出电梯。

以前在微博上看到一个博主的视频,他们会组织演员去街头扮演一些弱势群体,看看路人的反应。比如腿脚不利索的大爷过马路,拐卖儿童的贩子等等,视频中总是会有热情的人愿意仗义相助。

我看的时候我就在想,假如我真的遇到这种情况,我是否会像他们一样,仗义相助呢?

从小接受的教育告诉我们,应该是要这么做的。

7.20暴雨日记

2021年7月20日,地处中原的郑州,刚经历了一场百年以来难得一遇的特大暴雨。

雨刚开始下的时候,还以为只是普通的一场大雨,我还跟朋友说不想去上班,但是拥有超高打工人觉悟的我还是去上了班。

哈哈哈,其实是因为这个月的假期已经用完,去上班只是为了不扣工资。

结果就是,直到下班的时候雨还在下,此时外边路面上的水已经快到膝盖了,地铁也已经停运,附近酒店也被订满,只剩下一些“天价房”。 还好可以留宿在朋友家,不然就只能住公司了。

21号一大早醒来,没水没电没信号,彷佛突然与外界隔绝了一样,郑州好像变成了一座没有互联网的城市。 可以联系上外界的时候,也是快到晚上了。雨中午已经停了,到晚上的时候小区路面已经很少水了。

在小区门口找到了一点微弱的信号,立马就给家里和朋友报了平安,免得他们太过担心,但是也只停留在可以打电话发短信的程度。

22号路面基本上已经没水了,我们几个失联群众步行去公司上班。 手机也在快到公司附近时来了信号,一大堆的信息和未接电话。

好像就是只有外边的人才知道郑州里边有多严重,我们在郑州里边好像没事人一样。

23号依旧没水没电,所以就早早来了公司蹭水蹭电。由于交通瘫痪,只有公司附近的人才能过来上班,所以公司人并不多。 由于是周五,再加上已经几天没回家了,就想着回去看看。 下班的时候地铁还没通,公交也要将近三个小时才能回去。 不过还好可以搭乘朋友的车,就跟他一起回家了。挺好的是,家里边已经开始有电有网了,水可能还要晚点才能来。

暴雨过后,这座城市变得一片狼藉,也有人不幸罹难,彷佛人类在大自然面前就像渺小的蚂蚁一样。 但是,一切正在努力恢复,它也正在慢慢变好。

暴雨冲刷了房屋街道,同时也冲刷了人们内心的浮躁。

珍惜眼前的小确幸!

半日偷闲,絮絮叨叨

马上就要六月了,代表着今年已经是过完小半年了。虽说夏天还没有正式的到来,可是外面的温度已经高达38℃了,这让人很难想象后面的天气会有多热。躲在空调冷风下的我,像往常周末一样,无聊的刷着短视频,手指不间断的向上滑动。可能是刷视频刷累了吧,突然想通过文字叨叨这半年。回头看这小半年,生活过得像往常一样琐碎忙碌,而且自己也没有干出来什么值得骄傲的成绩。好像也没什么可写的,那既然这样,干脆就写点琐碎吧,毕竟生活都是由琐碎的生活片段拼凑而成的。

最近正在看科幻小说《三体》,目前只看到大概一半吧,觉得网上对三体的评价果然不是吹的,就是刚开始的部分会让人觉得好像这不是一本科幻小说,如果继续看下去的话真的会觉得这本书真是宝藏。由于还没看完,也并不能对此作出过多的评价,就目前来说,他改变了我对科幻固有的认知,心中会不由得惊叹:“原来还可以这样!”,完全想不到下一页的剧情会是什么。

最近也有一件好玩的事,公司的一名翻译王同学,正在学习跟他的专业八竿子打不着的东西:数学和编程。问她为什么想要学习这些东西,她回答说:“我现在掌握的东西都太偏文科了,想学习一下理科的东西”。于是乎我就成了她的一个“老师”,她有时候有不会的问题也会向我请教,但是每次问的问题都很奇怪,有时候都不知道怎么回答,还有每次回答完他还会象征性的给我出五毛的劳务费。。。 真怕他问到我也不会的东西,回答不出来那岂不是很尴尬。

这半年下来我们IT部也离职了两个人,目前就剩下两个后端一个前端了,老板最近的打算是不招新人,这我也能理解,因为最近的工作的确不多,三个人足以应付的过来。在第一个跟我同时期来的人离职后,老板也专门找过我问我的情况,我也向他表态说了不会离职,同事也趁此机会向老板提了提加薪的事,最后也是如愿以偿。其实在这个公司两年左右的时间,工作的也蛮舒服的,虽说规模不大,但是却并没有乱七八糟的规章制度,这也是我选择继续留在这里的一部分原因。另外关于技术方面,最近半年除了维护一些旧项目以外,也在开发一个新的检索系统,技术上也不会是重复造轮子。

前段时间也看了一部超级好看的连续剧《觉醒年代》,是一部纪念建党一百周年的优秀电视剧展播。电影般的镜头光感、优秀的人物演绎。作为一个理科生,曾经在高一下学期就已经逃离历史的魔爪,所以我可以说是一个历史白痴。但是通过看这部剧,我好像更深入得了解了那段历史,那段我国优秀历史人物的光辉岁月。

今年三月份也搬了新家,因为之前一起住的两个朋友今年去别的地方发展了,再加上跟房东也不太合得来,就决定换一个地方住了,最后走的时候押金都没退。不得不说,郑州的租房市场似乎已经全被中介和托管占领了,想找一个房东直租的都要费好大劲,或者也可能是我们找的方式不对吧。另外,搬家真的很累很累!搬家之前是收拾东西,看着房间的东西觉得并没有多少,哪知道越收拾越多,三个人的东西客厅都占满了。到后来把东西搬到新家的时候,大包小包从一楼五楼,最后走路腿都是抖的,,,累坏我了。

现在记忆力也不太好,有些事情都已经忘记了,絮絮叨叨的写了一些琐碎的事情,记录这庸碌琐碎的小半年。在接下来的时间里,若是能摆脱一点这种庸碌的状态当然更好,若不能的话,那就这样吧,庸碌但能追求真实其实也挺好。

突然想到我的这个网站,已经挺久时间没有更新过文章了,本来给自己定的是一周更新一篇,现在看来也算是食言了。其实对于我个人而言,似乎很少通过文字来记录自己,可能是性格使然,对于生活中的心事经常苦闷心中然后慢慢消化,而对于有趣好玩的事情我也是一个人乐呵完之后就没有然后,手机上的日记同样也只是断断续续的更新;也可能是自认为文笔不行,写出来的东西连自己都觉得丢脸,又何必写出来让人取笑呢。好像文字对于我而言,似乎只是颜如玉与黄金屋吧。

爱上听鲸歌

不知道怎么回事,最近喜欢偷空听鲸鱼的叫声,声音空灵、纯静,可以让人紧绷的神经放松下来。很多次因为工作感到烦躁,亦或是晚上躺床上失眠的时候,都会打开手机听听鲸鱼的叫声,心境瞬间就平和下来了。

其实最开始听到鲸鱼的叫声是某短视频平台听到的,是一只座头鲸的叫声,刚听到的时候真的是让人惊艳且陶醉。于是就在网上寻找更多这样的声音,在搜索的过程中发现也有好多爱听鲸歌的,当然也有一些害怕听见鲸鸣的。

喜欢的人觉得这是一种心灵的洗礼,置身事外而又与世无争。害怕的人则大都是有深海恐惧症,深邃浩瀚仿佛置人于汪洋深海而又孤身一人。

一首最近经常听的歌:Whale sounds 2 hours,两个小时的鲸鱼叫声,欢快、希望、孤独尽在其中。

Run Elasticsearch by docker

1. Download the docker image of Elasticsearch, taking version 7.6.0 as an example

    docker pull elasticsearch:7.6.0

2. Create a container and run it.

if your command is:

   docker run -d --name es -p 9200:9200 -p 9300:9300 elasticsearch:7.6.0

It may exit shortly after starting. To find out the reason, view logs by log command:

   docker logs es

Some error message like max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144] may appear. Go the following command:

   # change the variable
   sysctl -w vm.max_map_count=262144
   # check the variable value
   sysctl -n vm.max_map_count

If the error message is the default discovery settings are unsuitable for production use; at least one of [discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configured, which means the container has wrong configuration and can be corrected by setting those prompted environment variables or setting to standalone development mode by discovery.type=single-node.

In short, the correct command is:

   docker run -d --name es -p 9200:9200 -p 9300:9300 -e discovery.type=single-node elasticsearch:7.6.0

Using IK Chinese segmentation plugin.

1. Download the plugin.

The plugin version must equal the Elasticsearch version. Version 7.6.0 download link is https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.0/elasticsearch-analysis-ik-7.6.0.zip

2. Unzip to an empty directory, which is referred to as *$IK*.

3. Copy the $IK into the directory of the container's plugin.

   docker cp $IK es:/usr/share/elasticsearch/plugins/ik

This plugin provides analyzer and tokenizer named ik_smart and ik_max_word where ik_smart splits by the coarsest granularity, while ik_max_word will exhaust all kinds of split combinations.

Find more info at IK.

Confession of a salted fish

Today, I listened to the sound of the drum all day that didn't stop till evening. So I was thinking whose child was training so hard. By contrast, I played all day, which made me feel guilty.

I wonder if I've done something like that. I wonder if I study in my spare time like that child. It seems very seldom.

Some time ago, I developed this website and wanted to use it to record my coding notes. However, after it was established, I have tossed it many times, such as changing the theme of the website, adding some new features, but have not focused on the content of the website and sticked to writing.

So I was wondering why I did this, why I developed this website at the beginning. It's just for me to take notes in my coding time. I found that I had violated my original intention.

Since I started working,taking notes and diary happened only occasionally. When I want to do that, I just do it in the beginning days, hardly to keep the impetus of that. Not only blogging, but aslo learning English. I have wanted to learn English many times, but I never sticked to it, so I don't learn English well, nor can I read computer documentation smoothly.

In the end, actually my main purpose of writing this blog is to practice my English, and also to reflect and spur myself,although it's all nonsense.

Fulltext retrieval with whoosh and Jieba

Environment dependencies

pip install django-haystack
pip install jieba
pip install whoosh

Environment configuration

Add this configuration in settings.py

INSTALLED_APPS = (
    'haystack',	#register fulltext searching framework
    )

#the configuration of fulltext searching
HAYSTACK_CONNECTIONS = {
    'default': {
    	# use the whoosh search engine
    	'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
    	# Specifies the default path of the index files generated by the index data corresponding to the keyword. When using the custom index file, write the custom file path here.
    	'PATH': os.path.join(BASE_DIR,'whoosh_index'), #  the file path of the index files.
    	}
}

# Auto generate indexes when add, change and delete data in database tables.
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'

Create search_indexs.py in applications which need support searching.

from haystack import indexes
from apps.blog.models import Article


class ArticleIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True, use_template=True)

    def get_model(self):
        return Article

    def index_queryset(self, using=None):
        return self.get_model().objects.filter(status='p')

In the templates folder of the project, create folder structure like search/indexes/article/article_text.txt, where article is the lowercased model name.

# Specifies which fields in the table to index
{{ object.title }}	# index Title field
{{ object.body }}	# index Body field

Add search route

path('search', include('haystack.urls'), name='search'),

Add search form in the template.

<form action="/search" method="get">
            {% csrf_token %}
                <span id="searchAria" tabindex="0" onclick="searching()" onblur="offsearch()">
                    <input type="text" id="searchInput" style="display: none; border: none;" name="q">
                    <input type="submit" value="Search" style="border: none; display: none;" id="submitInput">
                    <a href="javascript:void(0);"  class="navPlugs"><i class="fa fa-search" aria-hidden="true"></i></a>
                </span>
</form>

tips: there must be an input tag whose attribute named name equals q in the form.

The following is the page of search results.

    <div class="jupe main-body">
        <ul class="post-list">
            {% if query and page.object_list %}
                {% for result in page.object_list %}
                    <li class="post-item">
                        <a class="post-title"
                           href="{{ result.object.get_absolute_url }}"
                           title="{{ result.object.title }}">{{ result.object.title | truncatesmart:34 }}</a>
                        <span class="post-time">{{ result.object.create_time | date:"Y.m.d" }}</span>
                    </li>
                {% empty %}
                    <p>Not found</p>
                {% endfor %}

                {% if is_paginated %}{% load_pages %}{% endif %}
            {% else %}
                <h3>Found nothing. Try to search by another keyword</h3>
            {% endif %}

        </ul>
    </div>

Build index

python manage.py rebuild_index

Configure Jieba Chinese Search

Because the default engine of whoosh doesn't support Chinese, u need to improve it.

Copy the default engine file \site-packages\haystack\backends\whoosh_backend.py to the project folder and rename it to whoosh_cn_backend.

Open it and import Jieba Chinese analyzer from jieba.analyse import ChineseAnalyzer.

Replace StemmingAnalyzer in the file with ChineseAnalyzer

Change the file path of search engine to custom path in settings.py

'ENGINE': 'apps.search.whoosh_cn_backend.WhooshEngine'

Rebuild index python manage.py rebuild_index

It supports Chinese search now.