leiem.cn Open in urlscan Pro
163.181.145.171  Public Scan

URL: https://leiem.cn/
Submission: On January 05 via api from US — Scanned from US

Form analysis 1 forms found in the DOM

GET //google.com/search

<form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="搜索"><button type="submit" class="search-form-submit"></button><input type="hidden"
    name="sitesearch" value="https://leiem.cn"></form>

Text Content

首页 文章


2023-12-03
前端


EVENTSOURCE入门

EventSource 接口是 web 内容与服务器发送事件通信的接口。一个 EventSource 实例会对 HTTP 服务器开启一个持久化的连接,以
text/event-stream 格式发送事件。

与 WebSocket
不同的是,服务器发送事件是单向的。数据消息只能从服务端到发送到客户端(如用户的浏览器)。这使其成为不需要从客户端往服务器发送消息的情况下的最佳选择。

前端示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19


let source = new EventSource('/api/hello')
// 连接建立完成
source.onopen = function () {
  console.log('Connection was opened.');
}
// 收到消息,event:message
source.onmessage = function (event) {
  console.log('on message: ', event.data);
}
// 连接失败/关闭
source.onerror = function (event) {
  console.log('on error: ', event)
  // 可以手动 close(),禁止默认的自动重连机制
  source.close()
}
// 指定 event 的处理,event: ping
source.addEventListener('ping', function (event) {
  console.log('ping: ', event.data);
})


Python代码,Django 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18


from django.http import StreamingHttpResponse
import json

def hello(request):
    # 生成器函数
    def handle_data():
      for i in range(int(last_id or -1) + 1, 100):
          # 触发前端的 onmessage 回调函数,默认 event:message
          yield f'data:data is {i}\n\n'
          # 返回复杂数据(转JSON字符串)
          data = {'a': 1, 'b': 2}
          yield f'data:{json.dumps(data)}\n\n'
          # 触发前端的 addEventListener('ping'),event:ping
          yield f'event:ping\ndata: ping data...\n\n'

    response = StreamingHttpResponse(handle_data(), content_type='text/event-stream')
    response['Cache-Control'] = 'no-cache'
    return response


分享
 * javascript

2023-08-20
后端


PYTHON LOGGING入门

Python 内置的日志记录工具

配置

 * Level 日志级别
   * DEBUG
   * INFO
   * WARNING
   * ERROR
   * CRITICAL

基本使用

1
2
3
4
5
6


import logging

# 日志记录到./demo.log文件中,format指定日志格式
logging.basicConfig(filename='./demo.log', format='%(asctime)s %(message)s')

logging.warning('hello')


格式化

1
2
3
4
5


# 基础使用
logging.basicConfig(filename='./demo.log', format='%(asctime)s %(message)s')
# 记录器使用,配合Handler
formatter = logging.Formatter('%(asctime)s %(message)s')
handler.setFormatter(formaater)


格式 描述 %(asctime)s 表示人类易读的 LogRecord 生成时间。 默认形式为 ‘2003-07-08 16:49:45,896’
(逗号之后的数字为时间的毫秒部分)。 %(created)f LogRecord 被创建的时间(即 time.time() 的返回值)。
%(filename)s pathname 的文件名部分。 %(funcName)s 函数名包括调用日志记录. %(levelname)s
消息文本记录级别('DEBUG','INFO','WARNING','ERROR','CRITICAL')。 %(levelno)s 消息数字的记录级别
(DEBUG, INFO, WARNING, ERROR, CRITICAL). %(lineno)d 发出日志记录调用所在的源行号(如果可用)。
%(message)s 记入日志的消息,即 msg % args 的结果。 这是在发起调用 Formatter.format() 时设置的。
%(module)s 模块 (filename 的名称部分)。 %(msecs)d LogRecord 被创建的时间的毫秒部分。 %(name)s
用于记录调用的日志记录器名称。 %(pathname)s 发出日志记录调用的源文件的完整路径名(如果可用)。 %(process)d 进程ID(如果可用)
%(processName)s 进程名(如果可用) %(relativeCreated)d 以毫秒数表示的 LogRecord 被创建的时间,即相对于
logging 模块被加载时间的差值。 %(thread)d 线程ID(如果可用) %(threadName)s 线程名(如果可用)

文件日志

1
2
3
4
5
6
7
8
9
10
11
12
13


import logging

# 创建记录器对象
logger = logging.getLogger('log1')
# 创建文件处理器,指定日志文件
handler = logging.FileHandler('./demo.log')
# 格式器
formatter = logging.Formatter('%(asctime)s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

logger.setLevel('INFO')
logger.error('hello error')


轮换日志(基于文件大小)

1
2
3
4
5
6
7
8
9
10


from logging.handlers import RotatingFileHandler
import logging

logger = logging.getLogger('log1')
# maxBytes 设置单个文件的最大值(单位:字节)
# backupCount 设置最多保留多少个备份文件
# maxBytes 和 backupCount 都必须同时设置才生效
# 以下配置会最多生成 demo.log demo.log.1 demo.log2 demo.log3  4个文件
handler = RotatingFileHandler('./demo.log', maxBytes=1024, backupCount=3)
logger.addHandler(handler)


轮换日志(基于日期)

1
2
3
4
5
6
7
8
9


from logging.handlers import TimedRotatingFileHandler
import logging

logger = logging.getLogger('log1')
# when 设置轮换间隔时间(midnight:午夜0点)
# backupCount 设置最多保留多少个备份文件
# 以下配置会每天生成新文件最多保留7天历史文件
handler = TimedRotatingFileHandler('./demo.log', when='midnight', backupCount=7)
logger.addHandler(handler)


分享
 * Python

2023-05-07
python


DJANGO ORM入门

表结构如下

1
2
3
4
5
6
7
8
9
10
11


from django.db import models


class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()
    remarks = models.CharField(max_length=255, null=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'persons'


基础操作

filter 与 exclude用法一致

 * 插入
   
   1
   2
   3
   4
   5
   6
   7
   
   
   # 方式一
   Person.objects.create(name='张三', age=22)
   # 方式二
   person = Person(name='李四', age=23)
   person.save()
   # 方式三,查询匹配到更新,否则插入
   Person.objects.update_or_create(name='张三三', defaults={'age': 100})
   

 * 更新
   
   1
   
   
   Person.objects.filter(name='张三三').update(age=F('age') + 1, remarks='我是备注')
   

 * 删除
   
   1
   2
   3
   4
   5
   
   
   # 匹配删除
   Person.objects.filter(age=23).delete()
   # 单个删除
   person = Person.objects.get(id=1)
   person.delete()
   

 * 简单查询
   
   1
   2
   3
   4
   5
   6
   7
   8
   
   
   # 简单查询, 如果未匹配到或匹配到多条则报错
   Person.objects.get(id=1)
   # 查询第一条数据
   Person.objects.first()
   # 查询最后一条数据
   Person.objects.last()
   # 查询所有数据
   Person.objects.all()
   

 * 条件查询
   
   1
   2
   3
   4
   5
   
   
   # 根据指定字段匹配查询
   Person.objects.filter(name='10号').first()
   # 多条件匹配
   Person.objects.filter(age=30, name__contains='张')  # 都满足条件
   Person.objects.filter(Q(age=22) | Q(name__contains='张'))  # 满足任意一个
   

 * 大小比较
   
   1
   2
   3
   4
   5
   6
   7
   8
   
   
   # 大于 
   Person.objects.filter(age__gt=40)
   # 大于等于
   Person.objects.filter(age__gte=40)
   # 小于
   Person.objects.filter(age__lt=40)
   # 小于等于
   Person.objects.filter(age__lte=40)
   

 * 包含
   
   1
   2
   3
   4
   5
   6
   7
   8
   9
   10
   
   
   # 年龄是23, 24, 25的
   Person.objects.filter(age__in=(23, 24, 25))
   # 名字包含字符串‘张’的
   Person.objects.filter(name__contains='张')
   # 名字以‘张’开头
   Person.objects.filter(name__startswith='张')
   # 名字以‘张’结尾
   Person.objects.filter(name__endswith='张')
   # 匹配时忽略大小写,icontains/istartswith/iendswith
   Person.objects.filter(name__istartswith='a')
   

 * 日期/时间
   
   1
   2
   3
   4
   5
   6
   7
   8
   9
   10
   
   
   # 匹配年
   Person.objects.filter(created_at__year=2022)
   # 年/月/日/周
   __month
   __day
   __week_day
   __week
   __hour
   __minute
   __second
   

 * 其他
   
   1
   2
   3
   4
   
   
   # 过滤字段是否为NULL
   Person.objects.filter(remarks__isnull=True)
   # 正则匹配,iregex 忽略大小写
   Person.objects.filter(name__regex=r'[0-9]+')
   

关联查询

表结构信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22


from django.db import models


class Class(models.Model):
    name = models.CharField(max_length=30)
    remarks = models.CharField(max_length=255, null=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'classes'



class Person(models.Model):
    cls = models.ForeignKey(Class, on_delete=models.PROTECT, null=True)
    name = models.CharField(max_length=30)
    age = models.IntegerField()
    remarks = models.CharField(max_length=255, null=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'persons'


 * 关联查询
   
   1
   2
   3
   4
   5
   6
   7
   
   
   # 通过人查询所属班级信息
   person = Person.objects.first()
   print(person.cls.name)
   # 查询班级下的学生
   cls = Class.objects.filter(name='一一班').first()
   cls.person_set.all()   # 班级里的所有人
   cls.person_set.filter(age__in=(22, 23))   # 对属于版本的学生再匹配过滤
   

 * 关联匹配
   
   1
   2
   3
   4
   
   
   # 匹配属于一一班的学生
   Person.objects.filter(cls__name='一一班')
   # 匹配一年级的学生(班级名称一开头)
   Person.objects.filter(cls__name__startswith='一')
   

 * 查询优化
   
   1
   2
   3
   4
   
   
   # 关联查询避免访问学生的班级信息时再次查询班级表
   Person.objects.select_related('cls')   # sql层面
   Person.objects.prefetch_related('cls') 
   Person.objects.annotate(cls_name=F('cls__name'))  # 仅关联指定字段查询
   

 * 其他
   
   * 参考另外一篇文章 https://www.leiem.cn/2022/02/02/27221/
   * 参考官网文档 https://docs.djangoproject.com/en/3.2/ref/models/relations/

分享
 * ORM
 * django

2023-04-02
后端


HTTP请求参数解析器实现


HTTP请求参数

 * GET / DELETE 查询参数/URL参数
   * https://www.baidu.com/?key=123
   * request.GET = {“key”: “123”}
 * POST / PATCH / PUT
   * 请求体(body)application/json www-xxxx form-data
   * request.POST {“key”: “123”}


解析器实现目标

 * 能解析GET/POST参数
 * 参数校验
   * 参数类型,例如必需是int
   * 必填判断
   * 自定义校验,例如该参数只是能(“a”, “b”, “c”)中的一个


代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81


import json

body = {
    "id": "12",
    "url": "https://gitee.com/yooke/User.git",
    "type": "c",
}

# request.GET
# request.POST
# application/json   json.loads(request.body)


body = json.dumps(body)


class Argument:
    def __init__(self, key, required=True, filter=None, type=None, help=None):
        self.key = key
        self.required = required
        self.filter = filter
        self.type = type
        self.help = help


class Parser:
    def __init__(self, *arguments):
        self.arguments = arguments

    def parse(self, data):
        form, error = {}, None
        for arg in self.arguments:
            value = data.get(arg.key)
            if arg.required and value is None:  # 判断required属性是否必填
                error = arg.help
                break
            if arg.type:  # 判断type属性类型是否正确
                if not isinstance(value, arg.type):
                    try:
                        value = arg.type(value)  # 尝试转换类型
                    except ValueError:
                        error = arg.help
                        break
            if arg.filter and not arg.filter(value):  # 判断是否符合filter过滤条件
                error = arg.help
                break
            form[arg.key] = value
        return form, error


class JSONParser(Parser):  # 扩展解析JSON消息体
    def parse(self, data):
        data = json.loads(data)
        return super().parse(data)


class XMLParser(Parser):   # 扩展解析XML消息体
    def xmltodict(self, data):
        # TODO: xml to dict
        return data

    def parse(self, data):
        data = self.xmltodict(data)
        return super().parse(data)


def main():
    form, error = JSONParser(
        Argument('id', type=int, help='ID必须是整数'),
        Argument('name', required=False, help='name参数必填'),
        Argument('url', help='url参数必填'),
        Argument('type', filter=lambda x: x in ('a', 'b', 'c'), help='type参数必须是a,b,c中的一个'),
    ).parse(body)
    if error is None:
        print('参数校验通过: ', form)
    else:
        print('参数校验失败: ', error)


main()



分享
 * Python

2023-03-13
随记


不同路径写法对RSYNC的影响

Rsync 同步时需要指定源路径与目标路径,那么路径末尾的 / 会影响同步的结果吗?做了以下测试


同步目录

以源路径 /Downloads/User 目录,远端目录 /data 为例

 * rsync Dowloads/User root@ip:/data
   * /data 存在,/data/User 与源目录一致
   * /data不存在,/data/User与源目录一致
 * rsync Downloads/User root@ip:/data/
   * /data存在,/data/User与源目录一致
   * /data不存在,/data/User与源目录一致
 * rsync Downloads/User/ root@ip:/data
   * /data存在,/data 与源目录一致
   * /data不存在,/data与源目录一致
 * rsync Dowloads/User/ root@ip:/data/
   * /data存在,/data与源目录一致
   * /data 不存在, /data与源目录一致


同步文件

以源路径 /Downloads/User/a.txt文件,远端路径 /data 为例

 * rsync Dowloads/User/a.txt root@ip:/data
   * /data 存在,/data 覆盖内容与a.txt一致
   * /data不存在,/data 创建文件,内容与a.txt一致
 * rsync Downloads/User/a.txt root@ip:/data/
   * /data存在,/data/a.txt 与a.txt一致
   * /data不存在,/data/a.txt 与a.txt一致
 * rsync Downloads/a.txt/ root@ip:/data
   * /data存在,报错:”Downloads/User/a.txt/.” failed: Not a directory (20)
   * /data不存在,报错:”Downloads/User/a.txt/.” failed: Not a directory (20)
 * rsync Dowloads/User/a.txt/ root@ip:/data/
   * /data存在,报错:”Downloads/User/a.txt/.” failed: Not a directory (20)
   * /data 不存在,报错:”Downloads/User/a.txt/.” failed: Not a directory (20)


总结

 * 同步的源路径为目录时
   * 源路径以/结尾时,同步源路径下边的所有文件至目标路径内
   * 源路径非/结尾时,同步源路径自身至目标路径内(源目录会作为目标路径的子目录)
   * 与目标路径是否以/结尾无关
 * 同步的源路径为文件时
   * 源路径以/结尾时报错,无法执行同步
   * 目标路径以/结尾时,同步文件至目标路径下,新建或覆盖目标路径下的同名文件
   * 目标路径非/结尾时,目标路径即为同步之后的文件路径(可理解为把源文件重命名为目标文件)

分享
 * rsync

2023-03-05
随记


XTERMJS使用入门

Xterm是一个实现web终端的js库。


使用方法

 1. 安装依赖
    
    1
    2
    
    
    npm install xterm
    yarn add xterm
    

 2. 引入xterm
    
    1
    
    
    import { Terminal } from 'xterm'
    

 3. 相关的html代码
    
    1
    
    
    <div id="terminal"/>
    

 4. 相关的js代码
    
    1
    2
    3
    
    
    let term = new Terminal()
    term.open(document.getElementById('terminal'));
    term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
    


常用配置

 * 字体
   * term.options.fontFamily = 'monospace'
 * 字号
   * term.options.fontSize = 12
 * 行号
   * term.options.lineHeight = 1.2
 * 主题配色
   * term.options.theme = {background: '#2b2b2b', foreground: '#A9B7C6', cursor:
     '#2b2b2b'}


常用插件

 * xterm-addon-fit
   
   提供terminal内容自适应
   
   1
   2
   3
   4
   5
   
   
   import { FitAddon } from 'xterm-addon-fit'
   
   const fitAddon = new FitAddon();
   term.loadAddon(fitAddon);
   fitAddon.fit()
   

分享
2023-02-19
Python


PYTHON使用OPENPYXL读写EXCEL

 * 读取excel的内容
   
   1
   2
   3
   4
   5
   6
   7
   8
   9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   
   
   wb = load_workbook('/Users/aka/Downloads/w_pyxl.xlsx')
   ws = wb['Sheet1']
   
   for row in ws.rows:  # 遍历所有行
       # 按索引取出每行的指定位置的值
       print(row[0].value, row[1].value, row[2].value, row[3].value)
       
   
   # 每行组合成字典返回
   def parse_ws(sheet):
       keys = []
       for index, row in enumerate(sheet.rows):
           if index == 0:
               for item in row:
                   keys.append(item.value)
               continue
   
           values = [x.value for x in row]
           yield dict(zip(keys, values))
   
   
   wb = load_workbook('/Users/aka/Downloads/w_pyxl.xlsx')
   ws = wb['Sheet1']
   for line in parse_ws(ws):
       print('姓名:', line['姓名'])   # 通过表头来取值
   

 * 生成excel文件
   
   1
   2
   3
   4
   5
   6
   7
   8
   9
   10
   11
   12
   13
   14
   15
   16
   
   
   from openpyxl import Workbook
   
   wb = Workbook()
   ws = wb.active
   
   data = [
       ('张三', '一三班', '100'),
       ('李四', '一三班', '65'),
       ('王武', '一三班', '95')
   ]
   
   ws.append(('姓名', '班级', '分数'))
   for item in data:
       ws.append(item)
   
   wb.save('/Users/aka/Downloads/test.xlsx')
   

分享
 * excel
 * openpyxl

2022-07-11
docker


使用BUILDX创建多架构镜像

> 参考文档1:https://docs.docker.com/buildx/working-with-buildx/#build-multi-platform-images
> 
> 参考文档2:https://docs.docker.com/desktop/multi-arch/


需求

 * 内核版本 >= 4.8
 * 通过 https://docs.docker.com/engine/install/ 安装docker,或参考
   https://docs.docker.com/build/buildx/install/ 安装 buildx 插件


步骤

1. 启用 BINFMT_MISC

1


docker run --privileged --rm tonistiigi/binfmt --install all


2. 创建并切换构建器

1
2
3


docker buildx create --name mybuilder
docker buildx use mybuilder
docker buildx inspect --bootstrap


3. 构建镜像

1


docker buildx build --platform linux/amd64,linux/arm64 -t openspug/spug-service --push .


分享
 * docker

2022-05-11
前端


FLEX布局元素被挤压


问题

先看个常见的需求,元素A 设置了固定宽度 100px,但当元素B内容过多时会挤压A的宽度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16


<style>
  .container {
    display: flex;
    flex-direction: row;
    width: 200px;
    border: 1px solid #999;
  }
  .a {
    width: 100px;
  }
</style>

<div class="container">
  <div class="a">leiem.cn</div>
  <div>先看个常见的需求,元素A 设置了固定宽度 `100px`,但当元素B内容过多时会挤压A的宽度。</div>
</div>


image-20220511122609088

可以看到总宽度 200px 元素A 设置的宽度 100px 应该占据一半的空间,但展示的效果明显被挤压了。


解决方法

处理办法也很简单就是 flex-shrink
属性,该属性定义了当父元素主轴空间不足时子元素的缩小比例。具体怎么缩小还受其他属性的影响,我们这里就不展开详述了,因为大部分情况下也不会遇到那么复杂场景。

针对上述例子只需要在元素A上添加 flex-shrink: 0 即可解决。

1
2
3
4


.a {
  width: 100px;
  flex-shrink: 0;
}


添加后的效果,完美解决 😄

image-20220511123334535

分享
 * css

2022-04-18
python


DJANGO ORM小记

对一些常用的小技巧记录。


SELECT_FOR_UPDATE

select_for_update
可以做小并发的控制,其只能在事务中使用。符合条件的查询结果会被加锁,其他查询包含加锁的数据时会阻塞,直到事务完成,如果其他查询结果不包含加锁的记录则不受影响。

1
2
3
4
5
6
7


from django.db import transaction

with transaction.atomic():
  # 会给所有type = '1' 的记录加锁
  tickets = Ticket.objects.select_for_update().filter(type='1')
  # 单条记录加锁
  ticket = Ticket.objects.select_for_update().get(id=12)



SELECT_RELATED

select_related 在查询时通过外键关系将关联的对象一起查询出来,从而避免查询查询以优化查询性能,适合一对一/一对多关系或关联表数据量比较小的情况。

1


Person.objects.select_related('cls')


对应执行的sql语句

1


SELECT `persons`.`id`, `persons`.`name`, `classes`.`id`, `classes`.`name` ... FROM `persons` LEFT OUTER JOIN `classes` ON (`persons`.`cls_id` = `classes`.`id`)



PREFETCH_RELATED

prefetch_related
一样可以用于优化查询性能,但实现方式与select_related不同,其会产生两条sql语句,查询结果会被缓存到内存中,然后进行合并,适合对多关系或关联表数据量比较大的情况。

1


Person.objects.prefetch_related('cls')


对应执行的两条sql语句

1
2


SELECT `persons`.`id`, `persons`.`name` ... FROM `persons`;
SELECT `classes`.`id`, `classes`.`name` ... FROM `classes` WHERE `classes`.`id` IN (2, 3)';


分享
 * ORM
 * django

12下一页 »


分类

 * Python
 * docker
 * python
 * 前端
 * 后端
 * 小程序
 * 随记


标签

 * ORM
 * Python
 * antd
 * css
 * django
 * docker
 * excel
 * javascript
 * openpyxl
 * react
 * rsync


标签云

ORM Python antd css django docker excel javascript openpyxl react rsync


归档

 * 十二月 2023
 * 八月 2023
 * 五月 2023
 * 四月 2023
 * 三月 2023
 * 二月 2023
 * 七月 2022
 * 五月 2022
 * 四月 2022
 * 三月 2022
 * 二月 2022
 * 一月 2022


最新文章

 * EventSource入门
 * Python logging入门
 * Django ORM入门
 * HTTP请求参数解析器实现
 * 不同路径写法对Rsync的影响
 * Xtermjs使用入门
 * Python使用openpyxl读写excel
 * 使用buildx创建多架构镜像
 * flex布局元素被挤压
 * Django ORM小记

豫ICP备16000449号-2 © 2023 leiem.cn

首页 文章