-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 744 KB
/
content.json
1
{"meta":{"title":"Gray-Ice","subtitle":"","description":"ArvinWu's Blog Site","author":"John Doe","url":"http://example.com","root":"/"},"pages":[{"title":"blog","date":"un44fin44","updated":"un11fin11","comments":true,"path":"blog/index.html","permalink":"http://example.com/blog/index.html","excerpt":"","text":""},{"title":"tags","date":"un44fin44","updated":"un11fin11","comments":true,"path":"tags/index.html","permalink":"http://example.com/tags/index.html","excerpt":"","text":""},{"title":"categories","date":"un66fin66","updated":"un11fin11","comments":true,"path":"categories/index.html","permalink":"http://example.com/categories/index.html","excerpt":"","text":""},{"title":"resume","date":"un22fin22","updated":"un11fin11","comments":true,"path":"resume/index.html","permalink":"http://example.com/resume/index.html","excerpt":"","text":""}],"posts":[{"title":"Windows复制文件到剪切板","slug":"Windows复制文件到剪切板","date":"un33fin33","updated":"un44fin44","comments":true,"path":"2023/05/10/Windows复制文件到剪切板/","link":"","permalink":"http://example.com/2023/05/10/Windows%E5%A4%8D%E5%88%B6%E6%96%87%E4%BB%B6%E5%88%B0%E5%89%AA%E5%88%87%E6%9D%BF/","excerpt":"","text":"微软论坛链接。 那么先说一下预期实现的效果是什么: 将文件(非文件内容)复制到剪切板,实现可以通过按下Ctrl + V在文件浏览器或其他软件内粘贴文件的效果。因为目的并不是简单的从磁盘复制到磁盘,所以不能直接使用文件操作。 以下是PowerShell命令: 1Set-Clipboard -Path file_name 同时可参阅Set-Clipboard的文档。你可能注意到了这不是微软的文档,但是不要惊讶,微软的文档根本没有写-Path相关的内容!真材实料还得看我第三方。 这个命令还可以复制多个文件到剪切板: 12$flist="a.txt","b.txt" # 定义一个文件名列表Set-Clipboard -Path $flist # 复制列表中的文件到剪切板中 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"RPA","slug":"blog/RPA","permalink":"http://example.com/categories/blog/RPA/"}],"tags":[]},{"title":"Python运行时增加实例方法","slug":"Python动态增加实例方法","date":"un11fin11","updated":"un44fin44","comments":true,"path":"2023/04/10/Python动态增加实例方法/","link":"","permalink":"http://example.com/2023/04/10/Python%E5%8A%A8%E6%80%81%E5%A2%9E%E5%8A%A0%E5%AE%9E%E4%BE%8B%E6%96%B9%E6%B3%95/","excerpt":"","text":"参考博文: Python语言的动态性:运行时动态绑定,删除属性和方法–CSDN。 主要是要通过types..MethodType(function_obj, inited_class_obj)来实现。 代码如下: 1234567891011import typesasync def _new_page(self) -> SafePage: print("*" * 20) page = await self.new_page() return SafePage(page)class Example: def __init__(self): self.context = Context() self.context.new_safe_page = types.MethodType(_new_page, self.context)","author":"Arvin","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"解决PyQt5跨PC移植源码报错pyqt5 This application failed to start because no Qt platform plugin could be initialized","slug":"解决PyQt5程序移植报错","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2023/02/24/解决PyQt5程序移植报错/","link":"","permalink":"http://example.com/2023/02/24/%E8%A7%A3%E5%86%B3PyQt5%E7%A8%8B%E5%BA%8F%E7%A7%BB%E6%A4%8D%E6%8A%A5%E9%94%99/","excerpt":"","text":"参考文章: CSDN-pyqt5在pycharm遇到的问题与解决,CSDN-PyQT:This application…。 在将写好的PyQt5源码 + Python环境移植到同事机器上时报错: “pyqt5 This application failed to start because no Qt platform plugin could be initialized。 解决方法是在程序入口处执行以下代码(一定要放在Qt相关代码之前执行): 1234567main_path = sys.path[0]python_path = main_path[:main_path.rfind("\\\\")]qt_path = PyQt5.__file__[:PyQt5.__file__.rfind("\\\\")]sys.path.append(qt_path + "\\\\plugins")sys.path.append(qt_path + "\\\\bin")sys.path.append(qt_path + "\\\\plugins\\\\platforms") 出现这个报错的原因是Qt找不到相关文件,上面这段代码的目的就是将PyQt5相关目录(bin, plugins, plugins/platforms)添加到Path里,实测这样就不会出错了。 本篇完。","author":"ArvinWu","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"PyQt5","slug":"blog/PyQt5","permalink":"http://example.com/categories/blog/PyQt5/"}],"tags":[]},{"title":"Python标准输出重定向","slug":"Python标准输出重定向","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2023/02/21/Python标准输出重定向/","link":"","permalink":"http://example.com/2023/02/21/Python%E6%A0%87%E5%87%86%E8%BE%93%E5%87%BA%E9%87%8D%E5%AE%9A%E5%90%91/","excerpt":"","text":"虽然没有什么难的,用谷歌搜也可以搜到,我还是记一下吧。 print的时候实际上调用的是sys.stdout.write方法,所以要先把stdout替换成一个具有write方法的对象,再print就会调用那个对象的write方法。请注意: 该方法仅测试过print,其他与stdout的相关的内置函数没有测试过。 示例代码如下: 1234567891011121314151617181920# .\\test.pyimport ioimport sys# 先保存下stdout,避免待会儿无法通过print查看输出结果stdout = sys.stdoutmemstring = io.StringIO() # 内存文件# 替换stdoutsys.stdout = memstring# 输出内容。该内容会被重定向到memstringprint(1234)# 重新设置stdout。该步骤用于查看上面的print是否如预期写入1234到memstringsys.stdout = stdout# 打印memstring中的内容print(memstring.getvalue()) 输出如下: 123> python .\\test.py1234","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"Windows在文件夹中递归查找字符串","slug":"Windows在文件夹中递归查找字符串","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2023/02/20/Windows在文件夹中递归查找字符串/","link":"","permalink":"http://example.com/2023/02/20/Windows%E5%9C%A8%E6%96%87%E4%BB%B6%E5%A4%B9%E4%B8%AD%E9%80%92%E5%BD%92%E6%9F%A5%E6%89%BE%E5%AD%97%E7%AC%A6%E4%B8%B2/","excerpt":"使用findstr命令来实现搜索字符串所在文件的需求,详情可查看官方文档。 该命令在powershell和cmd下可用。","text":"使用findstr命令来实现搜索字符串所在文件的需求,详情可查看官方文档。 该命令在powershell和cmd下可用。 12345# 在当前目录下包括子目录所有.py后缀名的文件中查找popup_error,不忽略大小写findstr /s import *.py# 在当前目录下包括子目录所有.py后缀名的文件中查找popup_error,忽略大小写findstr /s /i import *.py 以上命令均会列出所有的含有import字符串的py文件及其相对于当前目录的路径。 输出如下: 1234567891011121314151617181920212223242526272829303132333435# powershellPS C:\\Users\\Arvin>findstr /s /i import *.pycore\\globalManager.py:from typing import TypedDict, Union, Anycore\\globalManager.py:from PyQt5 import Qt as Qcore\\globalManager.py:from utils import SingletonClasscore\\textcore.py:from utils import SingletonClasscore\\textcore.py:import win32clipboardcore\\__init__.py:from .textcore import TextCoremain.py:import sysmain.py:from typing import Listmain.py:from widget.menubar import MenuButtonTypemain.py:from PyQt5 import Qt as Qmain.py:from widget import MenuBar, TextViewermain.py:from utils.buttons import buttons as custom_buttonsutils\\buttons.py:from widget.menubar import MenuButtonTypeutils\\buttons.py:from typing import Listutils\\clipboard.py:import win32clipboardutils\\clipboard.py:from typing import Unionutils\\messagebox.py:from PyQt5 import Qt as _Qutils\\__init__.py:import messageboxwidget\\menubar.py:from PyQt5 import Qt as Qwidget\\menubar.py:from typing import TypedDict, List, Callablewidget\\menubar.py:from core import TextCorewidget\\menubar.py:import corewidget\\menubar.py:from functools import partialwidget\\menubar.py:from utils.clipboard import get_clipboardwidget\\menubar.py:from text import MenuBar as MText, TextViewerwidget\\textviewer.py:from PyQt5 import Qt as Qwidget\\textviewer.py:from core import TextCorewidget\\textviewer.py:import win32clipboardwidget\\textviewer.py:from typing import Unionwidget\\textviewer.py:from utils.clipboard import get_clipboardwidget\\textviewer.py:from text import TextViewer as Textwidget\\__init__.py:from .menubar import MenuBarwidget\\__init__.py:from .textviewer import TextViewer 更多的其他用途请查看官方文档。","author":"ArvinWu","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"乱七八糟","slug":"blog/乱七八糟","permalink":"http://example.com/categories/blog/%E4%B9%B1%E4%B8%83%E5%85%AB%E7%B3%9F/"}],"tags":[]},{"title":"python拥有固定参数的匿名函数","slug":"python固定参数的匿名函数","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2023/02/15/python固定参数的匿名函数/","link":"","permalink":"http://example.com/2023/02/15/python%E5%9B%BA%E5%AE%9A%E5%8F%82%E6%95%B0%E7%9A%84%E5%8C%BF%E5%90%8D%E5%87%BD%E6%95%B0/","excerpt":"","text":"注: 本文所介绍的技术原理是通过类函数对函数进行状态保存的包装,对包装后的函数进行的调用或访问,除了__call__之外所得到的结果可能都不是预期的结果! 写PyQt5对按钮点击事件进行函数绑定时有时候会需要类似C++那样带有固定参数的匿名函数,这样就省去了声明多个函数的麻烦步骤。 比如要在一个按钮点击后更改Label,然后设置一个全局变量: 12345def on_button_clicked(self, name: str, behavior: Callable): """name表示label的名称,behavior表示要设置的函数""" self.current_button_label.setText(name) TextCore.set_text_function(behavior) 上面这个函数如果要绑定到多个按钮上,并且每个按钮的name和behavior都不一样,使用lambda就会容易出现问题,这时可以使用partial类来进行包装: 123456789101112from functools import partial # 引入partial# 不要在意缩进以及self,未定义变量等问题。 for button in buttons: name = button['name'] behavior = button['behavior'] qbtn = Q.QPushButton(self) qbtn.setText(name) # 使用functools.partial()来达到C++中匿名函数的效果 qbtn.clicked.connect(partial(self.on_button_clicked, name, behavior)) self.h_box.addWidget(qbtn) 最后来看一下partial的代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566# Purely functional, no descriptor behaviourclass partial: """New function with partial application of the given arguments and keywords. """ __slots__ = "func", "args", "keywords", "__dict__", "__weakref__" def __new__(cls, func, /, *args, **keywords): if not callable(func): raise TypeError("the first argument must be callable") if hasattr(func, "func"): args = func.args + args keywords = {**func.keywords, **keywords} func = func.func self = super(partial, cls).__new__(cls) self.func = func self.args = args self.keywords = keywords return self def __call__(self, /, *args, **keywords): keywords = {**self.keywords, **keywords} return self.func(*self.args, *args, **keywords) @recursive_repr() def __repr__(self): qualname = type(self).__qualname__ args = [repr(self.func)] args.extend(repr(x) for x in self.args) args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items()) if type(self).__module__ == "functools": return f"functools.{qualname}({', '.join(args)})" return f"{qualname}({', '.join(args)})" def __reduce__(self): return type(self), (self.func,), (self.func, self.args, self.keywords or None, self.__dict__ or None) def __setstate__(self, state): if not isinstance(state, tuple): raise TypeError("argument to __setstate__ must be a tuple") if len(state) != 4: raise TypeError(f"expected 4 items in state, got {len(state)}") func, args, kwds, namespace = state if (not callable(func) or not isinstance(args, tuple) or (kwds is not None and not isinstance(kwds, dict)) or (namespace is not None and not isinstance(namespace, dict))): raise TypeError("invalid partial state") args = tuple(args) # just in case it's a subclass if kwds is None: kwds = {} elif type(kwds) is not dict: # XXX does it need to be *exactly* dict? kwds = dict(kwds) if namespace is None: namespace = {} self.__dict__ = namespace self.func = func self.args = args self.keywords = kwds 通过partial的源码可以看到,该类会将函数的参数保存起来,然后在类被调用的时候调用函数并传递保存的参数。","author":"ArvinWu","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"Pycharm不补全sys路径","slug":"pycharm不补全sys路径","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2023/02/10/pycharm不补全sys路径/","link":"","permalink":"http://example.com/2023/02/10/pycharm%E4%B8%8D%E8%A1%A5%E5%85%A8sys%E8%B7%AF%E5%BE%84/","excerpt":"","text":"参考自YouTrack-Pycharm not recognizing custom…。 在Pycharm中对想要添加补全的路径右键 -> Mark Directory As -> Sources Root。 然后重启Pycharm就好了。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"乱七八糟","slug":"blog/乱七八糟","permalink":"http://example.com/categories/blog/%E4%B9%B1%E4%B8%83%E5%85%AB%E7%B3%9F/"}],"tags":[]},{"title":"flutter doctor报错","slug":"flutter doctor报错","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2023/02/07/flutter doctor报错/","link":"","permalink":"http://example.com/2023/02/07/flutter%20doctor%E6%8A%A5%E9%94%99/","excerpt":"","text":"搭建Flutter开发环境在使用flutter doctor –android-licenses时会抛出java的错误,解决方案参考: Unable to find bundled Java version #118502。 将Android Studio下的jbr文件夹拷贝到Android Studio下的jre文件夹,重新运行命令即可。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"flutter","slug":"blog/flutter","permalink":"http://example.com/categories/blog/flutter/"}],"tags":[]},{"title":"JavaScript模拟输入事件","slug":"JavaScript模拟输入事件","date":"un44fin44","updated":"un44fin44","comments":true,"path":"2022/12/08/JavaScript模拟输入事件/","link":"","permalink":"http://example.com/2022/12/08/JavaScript%E6%A8%A1%E6%8B%9F%E8%BE%93%E5%85%A5%E4%BA%8B%E4%BB%B6/","excerpt":"","text":"本篇博文旨在记录做RPA流程时遇到更改输入框的value后前端的后台数据没变的情况时的解决方案。 主要思路是先创建几个事件,然后使用DOMElement.dispatch()方法触发事件。代码如下: 1234567891011var input = document.getElementById("compose_preload").contentDocument.querySelector("#ccContainer > div > div.ItemContainer.writeTable-txt.clearfix > div.addrText > input")input.value = cc;var event = document.getElementById("compose_preload").contentDocument.createEvent('HTMLEvents');event.initEvent("input", true, true);input.dispatchEvent(event);var event = document.getElementById("compose_preload").contentDocument.createEvent('HTMLEvents');event.initEvent("blur", true, true);input.dispatchEvent(event);var event = document.getElementById("compose_preload").contentDocument.createEvent('HTMLEvents');event.initEvent("change", true, true);input.dispatchEvent(event); 其中”document.getElementById(“compose_preload”).contentDocument”这段代码是在有iframe的情况下才这么写的,即要将Event创建在input对应的文档下。如果目标元素外面没有套iframe标签,可以直接document.createEvent创建事件。","author":"ArvinWu","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"JavaScript","slug":"blog/JavaScript","permalink":"http://example.com/categories/blog/JavaScript/"}],"tags":[]},{"title":"打包较小体积Docker镜像","slug":"打包较小体积Docker镜像","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2022/04/17/打包较小体积Docker镜像/","link":"","permalink":"http://example.com/2022/04/17/%E6%89%93%E5%8C%85%E8%BE%83%E5%B0%8F%E4%BD%93%E7%A7%AFDocker%E9%95%9C%E5%83%8F/","excerpt":"","text":"本篇博文用于记录博主打包Go语言编写出的程序至镜像的经历。最终打包出了82MB的镜像。该镜像功能简单,仅为Gin的示例代码。 踩坑进行时该标题下的内容只是详细记录一下博主的经历,想直接看最终做法的请直接翻到下一个标题。 我从Gin的README之中拷贝了示例代码,确认了它在本机可以运行,于是我就直接build了项目,之后将go build生成的二进制文件一同打包至镜像内,然后运行的时候提示我缺库。我试了各种编译参数,始终无法做出不依赖动态链接库的二进制文件。我感觉直接从我的机器上把库拷贝到镜像里有点蠢,于是就将GO环境(直接从官网下载的tar.gz包),项目所需的模块(GOPATH/pkg)和项目本身拷贝进golang镜像,然后编译,之后我收获了1.25GB的镜像.. lol 然后我在编译后将环境删掉,将镜像缩减到了900多MB,仍然太大了,于是我换用ubuntu镜像,往里放GO SDK,依赖和代码,编译完后删掉环境,做出了250多MB的镜像。但我心里还是觉得膈应,于是选择了另一种方法(见下一个标题内容)。 最终做法新建一个容器,该容器基于一个基础的系统镜像,然后在构建的时候将go的环境和代码拷贝到镜像中,之后在镜像中编译程序,最后再将编译好的程序拷贝出来,再把这个编译好的程序打包进一个空系统(该系统需要与之前编译时用的系统相同),然后用这个空系统运行编译好的程序。 编译环境Dockerfile如下(最后一步rm其实没必要): 123456789101112131415FROM ubuntuENV GOPATH /go/goENV GO111MODULE onCOPY ./pkg /go/pkgCOPY ./website /websiteCOPY ./go1.18.1.linux-amd64.tar.gz /go/go.tar.gzRUN cd /go \\ && tar -xzvf ./go.tar.gz \\ && cp -r ./pkg/* /go/go/pkg \\ && export PATH=$PATH:$GOPATH/bin \\ && cd /website \\ && go build \\ && rm -rf /goCMD bash 构建完成之后运行该镜像,将里面编译好的二进制文件拷贝出来: 1docker cp bd8a52a3c05b:/website/server ./ 之后将该二进制文件放进空系统,Dockerfile如下: 123FROM ubuntuCOPY ./server /serverCMD ["/server"] 然后就大功告成辣,这个镜像除了基础文件外就只剩这个二进制文件了,几乎做到了最小(有老哥做出了不依赖任何库的文件,放在alpine上,大小只有9M!!!但是我做不到 —— 或者说 我懒得做 ——我只要在alpine下搭一个编译环境就好了,但是太折腾了)。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Go","slug":"blog/Go","permalink":"http://example.com/categories/blog/Go/"}],"tags":[]},{"title":"Ubunutu下安装最新版Golang","slug":"Ubunutu下安装最新版Golang","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2022/02/28/Ubunutu下安装最新版Golang/","link":"","permalink":"http://example.com/2022/02/28/Ubunutu%E4%B8%8B%E5%AE%89%E8%A3%85%E6%9C%80%E6%96%B0%E7%89%88Golang/","excerpt":"","text":"最近在家办公了,记录一下安装Go的过程。 我是参照的官方文档安装的。 首先下载安装包: 下载地址。 接下来开始安装,首先进入到安装包所在的文件夹,然后执行以下命令(如果之前没有安装过go就不用执行了,目的是删除之前安装的go。需要加sudo): 1rm -rf /usr/local/go 接下来执行这条命令(命令的含义: 将go1.17.7.linux-amd64.tar.gz解压到/usr/bin目录下。请自行将go1.17.7.linux-amd64.tar.gz替换成你下载的安装包的文件名): 1tar -C /usr/local -xzf go1.17.7.linux-amd64.tar.gz 最后需要将路径添加PATH环境中(建议将下面这条命令添加到shell的配置文件中,例如bashrc, zshrc。如果不添加,那么每新建一个终端都得执行一遍这个命令才可以直接使用go命令): 1export PATH=$PATH:/usr/local/go/bin 当然,你也可以这样使用go( 1$ /usr/local/go/bin/go run main.go 你可以通过使用以下命令来检测是否安装成功: 1go version 如果输出了关于go的版本信息就算成功了。 好的,那么我们总结一下,go的安装总共有三步: 下载安装包,解压,设置路径。其中路径是可变的,其实不是必须解压到/usr/local,哪怕解压到/都没问题,只要你知道它在哪就好。 那么,本篇完。","author":"Arvin","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Go","slug":"blog/Go","permalink":"http://example.com/categories/blog/Go/"}],"tags":[]},{"title":"循环查找前缀树","slug":"循环查找前缀树","date":"un66fin66","updated":"un22fin22","comments":true,"path":"2022/01/29/循环查找前缀树/","link":"","permalink":"http://example.com/2022/01/29/%E5%BE%AA%E7%8E%AF%E6%9F%A5%E6%89%BE%E5%89%8D%E7%BC%80%E6%A0%91/","excerpt":"","text":"在学习AVL树的时候我就有用栈代替递归遍历树的想法了,当时只是觉得可行,奈何被左旋右旋单旋双旋转昏了头,拿递归实现都是硬着头皮做的,今天我实现了这个想法,感觉整个人都升华了。 PS 本篇博文只是博主实现心中突然冒出来的设想的产物,在写之前并没有查看过别的文章,而且写出来后代码也没有进行大量的测试,所以很有可能有大量的bug,不建议观看。 本篇博文的示例使用了一个路由前缀树,具有查找/插入这两个功能。实现可能会有bug,看看思路就行。 那么我讲一讲逻辑,如果看过CSAPP就会知道递归其实就是不断的压栈存数据和弹栈取数据,所以我们完全可以用栈+循环来代替递归。在下面这颗路由前缀树中,查找的逻辑是: 判断当前路由层级是否为最后一层,如果是的话就返回对应的Handler;如果不是则搜寻子节点,将全部匹配的子节点压栈。然后弹栈,开始下一次循环。这个逻辑如果用在二叉树中既可以实现先序遍历也可以实现后序遍历,只要改变一下压栈的顺序就可以了。 其实我觉得没有什么好解释的,蛮简单的,不过理解前是真的不明白,理解后就只觉得: 就这( 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130package route_trieimport ( "strings" "github.com/golang-collections/collections/stack")type HandlerFunc *stringtype Node struct { ISWild bool Path string Children []*Node Handler HandlerFunc}// 该结构体用于路由前缀树查询type pairNode struct { Node *Node Index int}// 拆分路径func splitPath(path string) []string { return strings.Split(path, "/")}func newNode(path string) *Node { // 判断path[0]是否为通配符,如果是,则将ISWild字段设置为true if len(path) != 0 { if path[0] == '*' || path[0] == ':' { return &Node{ISWild: true, Path: path, Children: make([]*Node, 0)} } else { return &Node{ISWild: false, Path: path, Children: make([]*Node, 0)} } } else { return &Node{ISWild: false, Path: path, Children: make([]*Node, 0)} }}// 找到单个节点,不使用通配符。该函数用于插入。func (n *Node) searchChild(path string) *Node { if n.Children == nil { return nil } for _, child := range n.Children { if child == nil { continue } if child.Path == path { return child } } return nil}// 找到所有匹配的子节点,使用通配符。该函数用于查询。func (n *Node) searchChildren(path string) []*Node { children := make([]*Node, 0) for _, child := range n.Children { // 查找合适的节点,添加进children if child.Path == path || child.ISWild { children = append(children, child) } } // 返回所有匹配的结果 return children}// 路由树的插入操作func (n *Node) Insert(path string, handler HandlerFunc) { // 分割路由 paths := splitPath(path) p_len := len(paths) // 初始化root节点 root := n for i := 0; i < p_len; i++ { child := root.searchChild(paths[i]) // 没有找到匹配的子节点,所以新建一个子节点 if child == nil { child = newNode(paths[i]) root.Children = append(root.Children, child) } // 将root赋值为子节点,用于下一层路由的插入 root = child } root.Handler = handler}// 查找路由对应的handlerfunc (n *Node) Search(path string) HandlerFunc { // 初始化必要变量 paths := splitPath(path) p_len := len(paths) nstack := stack.New() index := 0 // pairNode.Index记录当前层级,Node记录子节点 root := &pairNode{Node: n, Index: index} for { // 栈已经空了,查找不到结果,返回nil if root == nil { return nil } index = root.Index // 判断是否是最后一层 if index == p_len { return root.Node.Handler } // 查找匹配的子节点,将子节点全部压栈 children := root.Node.searchChildren(paths[index]) for _, child := range children { nstack.Push(&pairNode{Index: index + 1, Node: child}) } // 判断栈顶是否为空 if nstack.Peek() != nil { root = nstack.Pop().(*pairNode) } else { root = nil } }} 测试代码: 1234567891011121314151617package mainimport ( "fmt" route_tire "route_trie")func main() { var root route_tire.Node root.Children = make([]*route_tire.Node, 0) s := "hey!" root.Insert("/*hello/hey", &s) fmt.Println(root.Children) fmt.Println("This is handler: ") fmt.Println(*root.Search("/hello/hey"))} 结果: 1234 go run main.go[0xc0000ae040]This is handler: hey!","author":"Arvin","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Go","slug":"blog/Go","permalink":"http://example.com/categories/blog/Go/"}],"tags":[]},{"title":"Go语言高性能编程总结","slug":"Go语言高性能编程总结","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2022/01/21/Go语言高性能编程总结/","link":"","permalink":"http://example.com/2022/01/21/Go%E8%AF%AD%E8%A8%80%E9%AB%98%E6%80%A7%E8%83%BD%E7%BC%96%E7%A8%8B%E6%80%BB%E7%BB%93/","excerpt":"","text":"本篇内容基于: Go 语言高性能编程。 注意: 本文内容不全,不建议观看! 注意: 本文仅指出结果,原理和测试过程请在原文(第一段的超链接)中检索。本文中的副标题仅对应原文中的标题,不含任何其他意义。 字符串拼接性能及原理1使用+和fmt.Sprintf的效率是最低的。strings.Builder, bytes.Buffer和[]byte的性能差距不大,消耗的内存也十分接近。综合易用性和性能,一般推荐使用 strings.Builder 来拼接字符串。 切片性能及陷阱123456长度不同的两个数组是不可以相互赋值的,因为这 2 个数组属于不同的类型。在 C 语言中,数组变量是指向第一个元素的指针,但是 Go 语言中并不是。Go 语言中,数组变量属于值类型(value type),因此当一个数组变量被赋值或者传递时,实际上会复制整个数组。为了避免复制数组,一般会传递指向数组的指针。切片操作并不复制切片指向的元素,创建一个新的切片会复用原来切片的底层数组,因此切片操作是非常高效的。在已有切片的基础上进行切片,不会创建新的底层数组。因为原来的底层数组没有发生变化,内存会一直占用,直到没有变量引用该数组。因此很可能出现这么一种情况,原切片由大量的元素构成,但是我们在原切片的基础上切片,虽然只使用了很小一段,但底层数组在内存中仍然占据了大量空间,得不到释放。比较推荐的做法,使用 copy 替代 re-slice。 for 和 range 的性能比较1range 在迭代过程中返回的是迭代值的拷贝,如果每次迭代的元素的内存占用很低,那么 for 和 range 的性能几乎是一样,例如 []int。但是如果迭代的元素内存占用较高,例如一个包含很多属性的 struct 结构体,那么 for 的性能将显著地高于 range,有时候甚至会有上千倍的性能差异。对于这种场景,建议使用 for,如果使用 range,建议只迭代下标,通过下标访问迭代值,这种使用方式和 for 就没有区别了。如果想使用 range 同时迭代下标和值,则需要将切片/数组的元素改为指针,才能不影响性能。 Go 空结构体 struct{} 的使用12345空结构体 struct{} 实例不占据任何的内存空间。因为空结构体不占据内存空间,因此被广泛作为各种场景下的占位符使用。一是节省资源,二是空结构体本身就具备很强的语义,即这里不需要任何值,仅作为占位符。因此呢,将 map 作为集合(Set)使用时,可以将值类型定义为空结构体,仅作为占位符使用即可。有时候使用 channel 不需要发送任何的数据,只用来通知子协程(goroutine)执行任务,或只用来控制协程并发度。这种情况下,使用空结构体作为占位符就非常合适了。无论是 int 还是 bool 都会浪费额外的内存,因此呢,这种情况下,声明为空结构体是最合适的。","author":"Arvin","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Go","slug":"blog/Go","permalink":"http://example.com/categories/blog/Go/"}],"tags":[]},{"title":"lumberjack滚动日志","slug":"lumberjack滚动日志","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2022/01/19/lumberjack滚动日志/","link":"","permalink":"http://example.com/2022/01/19/lumberjack%E6%BB%9A%E5%8A%A8%E6%97%A5%E5%BF%97/","excerpt":"","text":"先上Github地址: lumberjack。 本篇博文中的示例为logrus + lumberjack。 因为没有找到很好的中文示例,我只能读文档了。所以本篇文章中的内容不会很高级,但会将其github上README文档中的内容简略写出。有时你可能会发现我完全是在翻译文档,请不要感到意外。 注意 这个包是v2.0版本的lumberjack,因此应该用 gopkg.in导入。[博主注: 不是很明白这里有什么因果关系] 1import "gopkg.in/natefinch/lumberjack.v2" Lumberjack故意把自己设计成日志基础的一部分,它不是一个集全面于一体的解决方案,相反,它是在日志底层可插拔的组件,它可以简单的控制日志写入的文件。 [博主: 有机翻那味儿了] Lumberjack在任何使用io.Writer作为写入的日志库中都表现良好,包括标准库中的log包。 Lumberjack假设只有一个进程在写入到输出文件,在同一台机器上多进程使用相同的lumberjack配置将会造成lumberjack出现不当的行为。[博主注: 因为不加锁的多进程/线程对同一文件进行读写是不安全的] 示例 标准库中的log包只需要在应用启动时调用SetOutput函数就可以使用lumberjack。 代码: 1234567log.SetOutput(&lumberjack.Logger{ Filename: "/var/log/myapp/foo.log", MaxSize: 500, // megabytes MaxBackups: 3, MaxAge: 28, //days Compress: true, // disabled by default}) logger类型123456789101112131415161718192021222324252627282930313233343536373839type Logger struct { // Filename is the file to write logs to. Backup log files will be retained // in the same directory. It uses <processname>-lumberjack.log in // os.TempDir() if empty. // Filename是日志将要写入的文件的名字。备份的日志文件将会保留在同级目录中。(后面的我没太懂什么意思,就不翻译了) Filename string `json:"filename" yaml:"filename"` // MaxSize is the maximum size in megabytes of the log file before it gets // rotated. It defaults to 100 megabytes. // MaxSize是日志文件轮转前的最大尺寸,单位是MB。它默认是100MB。 MaxSize int `json:"maxsize" yaml:"maxsize"` // MaxAge is the maximum number of days to retain old log files based on the // timestamp encoded in their filename. Note that a day is defined as 24 // hours and may not exactly correspond to calendar days due to daylight // savings, leap seconds, etc. The default is not to remove old log files // based on age. // MaxAge是保留旧文件的最大日期,它基于编码在文件名中的时间戳来实现。注意,一天的定义是24小时,并且由于夏令时,闰秒等原因,可能与日历不对应默认是不移除旧的日志文件。 MaxAge int `json:"maxage" yaml:"maxage"` // MaxBackups is the maximum number of old log files to retain. The default // is to retain all old log files (though MaxAge may still cause them to get // deleted.) // MaxBackups是最大保存的日志文件数量。默认是保存所有的旧日志。(但MaxAge仍然会使旧文件被删除) MaxBackups int `json:"maxbackups" yaml:"maxbackups"` // LocalTime determines if the time used for formatting the timestamps in // backup files is the computer's local time. The default is to use UTC // time. // LocalTime决定了备份日志时的时间戳的格式是否是本机的本地时间。默认是UTC时间。 LocalTime bool `json:"localtime" yaml:"localtime"` // Compress determines if the rotated log files should be compressed // using gzip. The default is not to perform compression. // Compress决定了旧日志是否需要用gzip压缩。默认是不执行压缩。 Compress bool `json:"compress" yaml:"compress"` // contains filtered or unexported fields // 过滤过的或未导出的字段} Logger是一个写入指定文件名的io.WriteCloser类型。 Logger会打开日志文件,如果是第一次打开(即文件此时不存在),则会创建文件。如果文件已经存在并且文件大小小于MaxSize规定的MB,lumberjack将会打开文件并且向这个文件中添加内容。如果文件已经存在了并且文件大小>=MaxSize MB,这个文件会立即被重命名,lumberjack会添加当前时间的时间戳在文件名的扩展名之前(如果没有扩展名 时间戳将会添加在文件名之后),然后一个新的文件会以原来文件的文件名被创建。[博主注: 就是传入Logger的Filename] 当一次写入会造成当前文件的大小超过MaxSize MB时,当前的文件将会被关闭,重命名,然后一个新的日志文件将会被以原来的文件名被创建。因此,传入Logger的文件名将总是”当前”文件名。 麻了,README中关于文件名的解释的太多了,我眼睛看酸了,不翻译了。剩下的内容看原文吧: 1Backups use the log file name given to Logger, in the form name-timestamp.ext where name is the filename without the extension, timestamp is the time at which the log was rotated formatted with the time.Time format of 2006-01-02T15-04-05.000 and the extension is the original extension. For example, if your Logger.Filename is /var/log/foo/server.log, a backup created at 6:30pm on Nov 11 2016 would use the filename /var/log/foo/server-2016-11-04T18-30-00.000.log 清除旧文件当一个新的日志文件被创建时,旧的日志文件就有可能被删除。根据时间戳,最近创建的文件将会被保留,保留的文件的数量等于MaxBackups,如果MaxBackups为0,则全部都会被保留。根据时间戳,所有比MaxAge规定的天数旧的文件都会被删除,不管是否达到了MaxBackups规定的最大保留数。注意,时间戳中编码的时间是轮转时间,可能与文件最后一次写入的时间有所不同。 如果MaxAge和MaxBackups都为0,那么旧文件将不会被删除。 func (*Logger) Close1func (l *Logger) Close() error Close实现了io.Closer,它会关掉当前的日志文件。 func (*Logger) Rotate1func (l *Logger) Rotate() error Rotate会使Logger关闭当前存在的日志文件并且立即创建一个新的日志文件。这个函数在想要在轮转规则之外发起日志轮转时是相当有用的,例如响应SIGHUP。在轮转之后,它会根据正常的轮转规则清除旧文件。 示例 这个示例演示了如何在响应SIGHUP时轮转。 1234567891011l := &lumberjack.Logger{}log.SetOutput(l)c := make(chan os.Signal, 1)signal.Notify(c, syscall.SIGHUP)go func() { for { <-c l.Rotate() }}() func (*Logger) Write1func (l *Logger) Write(p []byte) (n int, err error) Write实现了io.Writer。如果一次写入将会造成日志文件大于MaxSize,那么这个文件将会被关闭,并以文件名中包含当前时间时间戳的方式重命名,之后将会以原来的文件名创建一个新的文件。如果一次写入的字节大于MaxSize,那么这个函数将会返回错误。 logrus+lumberjack示例我翻译了这么长的文档,就为了等这一刻。我给logrus的logger添加了一个钩子,这个钩子会在使用Info级别输出时使用SetOutput函数将lumberjack的logger传入logrus的logger,之后logrus的logger就会调用这个lumberjack的logger的Write方法。 示例: 12345678910111213141516171819202122232425// main.gopackage mainimport ( "github.com/sirupsen/logrus" "gray-ice.com/test/hooks")func main() { // 新建logger logger := logrus.New() // 设置logger为JSON格式 logger.SetFormatter(&logrus.JSONFormatter{}) // 添加钩子 logger.AddHook(&hooks.InfoHook{}) // 循环打印 for i := 0; i < 100*10000; i++ { logger.Info("Info output!") logger.Warn("Warn output!") }} “gray-ice.com/test/hooks”的定义: 123456789101112131415161718192021222324252627282930// gray-ice.com/test/hooks/hooks.gopackage hooksimport ( "github.com/sirupsen/logrus" lj "gopkg.in/natefinch/lumberjack.v2")var info_logger = &lj.Logger{ Filename: "./log/info.log", MaxSize: 100, MaxAge: 1, Compress: false,}// 定义Info钩子type InfoHook struct{}// 实现Levels方法func (h *InfoHook) Levels() []logrus.Level { return []logrus.Level{logrus.InfoLevel}}// 实现Fire方法func (h *InfoHook) Fire(e *logrus.Entry) error { // 设置输出为Logger e.Logger.SetOutput(info_logger) return nil} 运行程序后,log文件夹中多出了两个文件: 1234567891011121314~/codeSet/goCode/test » cd log~/codeSet/goCode/test/log » ls info-2022-01-19T07-26-54.717.log info.log~/codeSet/goCode/test/log » stat info-2022-01-19T07-26-54.717.log File: info-2022-01-19T07-26-54.717.log Size: 104857558 Blocks: 204800 IO Block: 4096 regular fileDevice: 810h/2064d Inode: 204243 Links: 1Access: (0644/-rw-r--r--) Uid: ( 1000/ zero) Gid: ( 1000/ zero)Access: 2022-01-19 15:26:42.556896700 +0800Modify: 2022-01-19 15:26:54.706896700 +0800Change: 2022-01-19 15:26:54.706896700 +0800 Birth: -","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Go","slug":"blog/Go","permalink":"http://example.com/categories/blog/Go/"}],"tags":[]},{"title":"GO语言logrus库使用","slug":"GO语言logrus库使用","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2022/01/18/GO语言logrus库使用/","link":"","permalink":"http://example.com/2022/01/18/GO%E8%AF%AD%E8%A8%80logrus%E5%BA%93%E4%BD%BF%E7%94%A8/","excerpt":"","text":"Github地址。 与logrus相关的比较全的中文文章:Go 每日一库之 logrus –darjun(强烈推荐看这篇,因为我写的不太全,而且内容也比较简单)。 日志级别Logrus有七个日志级别: Trace, Debug, Info, Warning, Error, Fatal和Panic。 123456789log.Trace("Something very low level.")log.Debug("Useful debugging information.")log.Info("Something noteworthy happened!")log.Warn("You should probably take a look at this.")log.Error("Something failed but I'm not quitting.")// 在输出日志之后调用os.Exit(1)log.Fatal("Bye.")// 在输出日志之后调用panic()log.Panic("I'm bailing.") 可以通过设置日志级别来控制打印哪个级别的日志。 示例: 1234567891011121314package mainimport ( "github.com/sirupsen/logrus")func main() { logrus.SetLevel(logrus.InfoLevel) // 设置日志级别为Info logrus.Debug("Level: Debug.") logrus.Info("Level: Info.") logrus.Warn("Level: Warn.") logrus.Error("Level: Error.")} 输出: 1234~/codeSet/goCode/test » go run main.go INFO[0000] Level: Info. WARN[0000] Level: Warn. ERRO[0000] Level: Error. 这里因为设置了日志级别为Info,所以日志级别在Info之下的Debug没有被打印出。 日志级别从下至上排序如下: 1234567TraceDebugInfoWarningErrorFatalPanic。 自定义输出格式logrus可以自定义输出格式。 TextFormatter示例如下: 1234567891011121314151617181920package mainimport ( "github.com/sirupsen/logrus")func main() { logrus.SetLevel(logrus.InfoLevel) // 设置日志等级为INFO //设置格式 logrus.SetFormatter(&logrus.TextFormatter{ DisableColors: true, FullTimestamp: true, }) logrus.Info("Level: Info.") logrus.Warn("Level: Warn.") logrus.Error("Level: Error.")} 输出: 1234~/codeSet/goCode/test » go run main.go time="2022-01-18T14:32:54+08:00" level=info msg="Level: Info."time="2022-01-18T14:32:54+08:00" level=warning msg="Level: Warn."time="2022-01-18T14:32:54+08:00" level=error msg="Level: Error." 上面的代码设置了Text格式,该格式还有其他选项,头文件中是这样定义的: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374// TextFormatter formats logs into texttype TextFormatter struct { // Set to true to bypass checking for a TTY before outputting colors. ForceColors bool // Force disabling colors. DisableColors bool // Force quoting of all values ForceQuote bool // DisableQuote disables quoting for all values. // DisableQuote will have a lower priority than ForceQuote. // If both of them are set to true, quote will be forced on all values. DisableQuote bool // Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/ EnvironmentOverrideColors bool // Disable timestamp logging. useful when output is redirected to logging // system that already adds timestamps. DisableTimestamp bool // Enable logging the full timestamp when a TTY is attached instead of just // the time passed since beginning of execution. FullTimestamp bool // TimestampFormat to use for display when a full timestamp is printed. // The format to use is the same than for time.Format or time.Parse from the standard // library. // The standard Library already provides a set of predefined format. TimestampFormat string // The fields are sorted by default for a consistent output. For applications // that log extremely frequently and don't use the JSON formatter this may not // be desired. DisableSorting bool // The keys sorting function, when uninitialized it uses sort.Strings. SortingFunc func([]string) // Disables the truncation of the level text to 4 characters. DisableLevelTruncation bool // PadLevelText Adds padding the level text so that all the levels output at the same length // PadLevelText is a superset of the DisableLevelTruncation option PadLevelText bool // QuoteEmptyFields will wrap empty fields in quotes if true QuoteEmptyFields bool // Whether the logger's out is to a terminal isTerminal bool // FieldMap allows users to customize the names of keys for default fields. // As an example: // formatter := &TextFormatter{ // FieldMap: FieldMap{ // FieldKeyTime: "@timestamp", // FieldKeyLevel: "@level", // FieldKeyMsg: "@message"}} FieldMap FieldMap // CallerPrettyfier can be set by the user to modify the content // of the function and file keys in the data when ReportCaller is // activated. If any of the returned value is the empty string the // corresponding key will be removed from fields. CallerPrettyfier func(*runtime.Frame) (function string, file string) terminalInitOnce sync.Once // The max length of the level text, generated dynamically on init levelTextMaxLength int} JSONFormatter将格式设置为JSONFormatter会让日志以JSON形式输出。示例如下: 12345678910111213141516171819package mainimport ( "github.com/sirupsen/logrus")func main() { logrus.SetLevel(logrus.InfoLevel) // 设置日志等级为INFO //设置格式 logrus.SetFormatter(&logrus.JSONFormatter{ DisableTimestamp: false, }) logrus.Info("Level: Info.") logrus.Warn("Level: Warn.") logrus.Error("Level: Error.")} 输出如下: 1234~/codeSet/goCode/test » go run main.go {"level":"info","msg":"Level: Info.","time":"2022-01-18T14:39:20+08:00"}{"level":"warning","msg":"Level: Warn.","time":"2022-01-18T14:39:20+08:00"}{"level":"error","msg":"Level: Error.","time":"2022-01-18T14:39:20+08:00"} 打印调用者我忘了是在哪看的了,说如果启用打印调用者文件名会导致性能变差,不过我这里还是记录一下。 123456789101112131415161718192021package mainimport ( "github.com/sirupsen/logrus")func main() { logrus.SetLevel(logrus.InfoLevel) // 设置日志等级为INFO // 设置打印调用者 logrus.SetReportCaller(true) //设置格式 logrus.SetFormatter(&logrus.JSONFormatter{ DisableTimestamp: false, }) logrus.Info("Level: Info.") logrus.Warn("Level: Warn.") logrus.Error("Level: Error.")} 输出如下: 1234~/codeSet/goCode/test » go run main.go {"file":"/home/zero/codeSet/goCode/test/main.go:17","func":"main.main","level":"info","msg":"Level: Info.","time":"2022-01-18T14:44:57+08:00"}{"file":"/home/zero/codeSet/goCode/test/main.go:18","func":"main.main","level":"warning","msg":"Level: Warn.","time":"2022-01-18T14:44:57+08:00"}{"file":"/home/zero/codeSet/goCode/test/main.go:19","func":"main.main","level":"error","msg":"Level: Error.","time":"2022-01-18T14:44:57+08:00"} 打印时添加字段我觉得叫它字段可能不太好,但我也不知道叫什么,所以还是叫它字段吧。关于”字段”是什么,看了代码你就会明白。 添加单个字段示例: 12345678910111213141516171819package mainimport ( "github.com/sirupsen/logrus")func main() { logrus.SetLevel(logrus.InfoLevel) // 设置日志等级为INFO //设置格式 logrus.SetFormatter(&logrus.JSONFormatter{ DisableTimestamp: false, }) // 添加字段 e := logrus.WithField("K-v pair", 1) // 以Info级别打印日志 e.Info("Info!!")} 输出如下: 12~/codeSet/goCode/test » go run main.go {"K-v pair":1,"level":"info","msg":"Info!!","time":"2022-01-18T15:14:48+08:00"} 可以看到,输出内容里有一对键值对: “‘k-v pair’: 1”,这就是代码里用WithField函数添加的。 那么我们看一下WithField的函数定义: 123func WithField(key string, value interface{}) *Entry { return std.WithField(key, value)} 可以看到,在WithField函数中key必须是string类型,而value是interface{} (即任何类型)。该函数会返回一个Entry类型的指针,我们就是通过这个指针调用的Info()。 添加多个字段示例(其实定义字段,添加字段,打印日志完全可以连到一行写,我这里为了更好的可读性拆开了): 12345678910111213141516171819202122232425262728package mainimport ( "github.com/sirupsen/logrus")func main() { logrus.SetLevel(logrus.InfoLevel) // 设置日志等级为INFO //设置格式 logrus.SetFormatter(&logrus.JSONFormatter{ DisableTimestamp: false, }) // 定义字段 fields := logrus.Fields{ "name": "Arvin", "dep": "???", "age": 18, } // 添加多个字段 e := logrus.WithFields(fields) // 以Info级别打印日志 e.Info("Info!!") // 以Warn级别打印日志 e.Warn("Warn!!!")} 输出如下: 123~/codeSet/goCode/test » go run main.go{"age":18,"dep":"???","level":"info","msg":"Info!!","name":"Arvin","time":"2022-01-18T15:27:03+08:00"}{"age":18,"dep":"???","level":"warning","msg":"Warn!!!","name":"Arvin","time":"2022-01-18T15:27:03+08:00"} 我们看一下Fields类型的定义: 12// Fields type, used to pass to `WithFields`.type Fields map[string]interface{} 从添加多个字段到以Info级别打印为止,在源代码中流程大概为: 用定义的Fields初始化一个新的Entry结构,将Fields中的内容添加到Entry结构的Data字段中 -> 巴拉巴拉 -> 从Entry.Data字段中读取内容并拼接字符串 -> 巴拉巴拉 -> 生成bytes.Buffer类型的buffer -> 输出。 我感觉还是蛮麻烦的,但是因为logrus支持的功能多一些,所以麻烦一些也是很正常。 那么上面有提到,源代码里是从Entry.Data字段中读取数据的,那么我们为什么不直接改Entry.Data呢?欢迎观看”新建Entry”小节。 新建logger示例: 12345678910111213141516171819202122package mainimport ( "github.com/sirupsen/logrus")func main() { // 新建logger log := logrus.New() // 设置格式 log.SetFormatter(&logrus.TextFormatter{ DisableColors: true, }) // 设置日志等级 log.SetLevel(logrus.InfoLevel) // 以Info级别打印 log.Info("Hey!")} 输出如下: 12~/codeSet/goCode/test » go run main.gotime="2022-01-18T17:05:36+08:00" level=info msg="Hey!" 这个我觉得没啥说的,新建一个logger,并且可以对这个Logger进行各种操作,我想大家都可以很容易理解。 新建Entry示例: 1234567891011121314151617181920package mainimport ( "github.com/sirupsen/logrus")func main() { // 新建logger logger := logrus.New() // 新建Entry entry := logrus.NewEntry(logger) // 向entry.Data中添加字段 entry.Data["Are you Arvin?"] = true // 打印 entry.Info("Hey!")} 输出如下: 12~/codeSet/goCode/test » go run main.go INFO[0000] Hey! Are you Arvin?=true 可以看到,我们在Entry.Data中添加的内容也被打印出来了。 也许TextFormatter下不太直观,那么换成JSON看一看: 1234567891011121314151617181920212223package mainimport ( "github.com/sirupsen/logrus")func main() { // 新建logger logger := logrus.New() // 设置logger为JSON格式 logger.SetFormatter(&logrus.JSONFormatter{}) // 新建Entry entry := logrus.NewEntry(logger) // 向entry.Data中添加字段 entry.Data["Are you Arvin?"] = true // 打印 entry.Info("Hey!")} 输出如下: 12~/codeSet/goCode/test » go run main.go {"Are you Arvin?":true,"level":"info","msg":"Hey!","time":"2022-01-18T17:18:56+08:00"} Hooklogrus提供了钩子方法,可以自定义不同日志等级不同行为。使用AddHook函数可以向logger添加钩子。 那么先看源码中的AddHook函数: 123456// AddHook adds a hook to the logger hooks.func (logger *Logger) AddHook(hook Hook) { logger.mu.Lock() defer logger.mu.Unlock() logger.Hooks.Add(hook)} 可以看到,想要使用AddHook函数需要向其中传入一个Hook类型的参数,Hook类型是一个接口,看一下它的定义: 123456789// A hook to be fired when logging on the logging levels returned from// `Levels()` on your implementation of the interface. Note that this is not// fired in a goroutine or a channel with workers, you should handle such// functionality yourself if your call is non-blocking and you don't wish for// the logging calls for levels returned from `Levels()` to block.type Hook interface { Levels() []Level Fire(*Entry) error} 该了解的都了解了,那么接下来我们看示例: 123456789101112131415161718192021222324252627282930313233343536373839404142package mainimport ( "github.com/sirupsen/logrus")// 定义Hook结构体,该结构体只需要实现logrus.Hook接口就可以了,当然你也可以往这个结构体里塞几个字段type Hook struct{}// 实现Levels方法。该方法返回一个logrus.Level类型的切片。func (h *Hook) Levels() []logrus.Level { // 该函数返回一个logrus.Level类型的切片,这个切片中包含了logrus.InfoLevel和logrus.WarnLevel // 意思就是这个钩子只对info和warn起作用 return []logrus.Level{logrus.InfoLevel, logrus.WarnLevel}}// 实现Fire方法。该方法接收一个*logrus.Entry类型的参数和返回一个error类型的返回值func (h *Hook) Fire(e *logrus.Entry) error { // 向Entry.Data中添加字段,因为在调用打印的时候会先将Entry.Data中的内容拼接成字符串, // 所以我们将会看到在这里添加的字段待会儿会被打印出来 e.Data["new_field"] = "Hook!!!" return nil}func main() { // 新建logger logger := logrus.New() // 设置logger为JSON格式 logger.SetFormatter(&logrus.JSONFormatter{}) // 添加钩子 logger.AddHook(&Hook{}) // 打印 logger.Info("Info output!") logger.Warn("Warn output!") // 因为Error没有在Hook结构体的Level()方法返回的切片里,所以它不会打印Hook.Fire()中添加的字段。 logger.Error("Error output!")} 输出如下: 1234~/codeSet/goCode/test » go run main.go {"level":"info","msg":"Info output!","new_field":"Hook!!!","time":"2022-01-19T09:13:32+08:00"}{"level":"warning","msg":"Warn output!","new_field":"Hook!!!","time":"2022-01-19T09:13:32+08:00"}{"level":"error","msg":"Error output!","time":"2022-01-19T09:13:32+08:00"}","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Go","slug":"blog/Go","permalink":"http://example.com/categories/blog/Go/"}],"tags":[]},{"title":"元旦假日随便写写","slug":"life8","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2022/01/02/life8/","link":"","permalink":"http://example.com/2022/01/02/life8/","excerpt":"","text":" 今天是元旦的第二天。 我博客最近都没有更新,不是因为我忙的没空写,而是因为一件比较尴尬的事: 我没有桌子放电脑。我房间里只有一张桌子,这个桌子平时都放锅碗瓢盆,属实是没有地方其他东西了。另外就是我最近开发的东西没有什么技术含量,所以我认为不值得记录。而且我晚上做完饭时间也很晚了,写一写日记就要睡觉了。感觉这么说下去,下一句就是:“我宣布,本博客今天停更,完结撒花!“了,但是我并不想博客完结,我还想继续写下去。等我啥时候换工作了应该就会频繁写博客了。 不过说实话,我觉得我换工作还不太好换,因为我现在净干些没什么技术含量的自动化,导致我WEB技术下滑十分严重,到时候找工作都不好找。虽说如此,我还是在学习,希望有一天能找个有双休的,不996的WEB方向的工作。要不然我这么多年都白学了。 我办公室的开发就只有我一个人,所以我平时也没法交流技术,只能靠着水群来学习( 而且我的工作用的电脑内存只有8G,开个Pycharm,开个Windows,就啥也不剩了。这体验属实糟糕。不过我没打算把我自己的电脑拿到办公室,怕哪天丢了。好吧,其实不只是怕丢,还怕哪天我走的时候要我清空电脑上的所有数据。 出来前没感觉,出来后感觉在外面好孤单,不过一个人也挺好的,不用管别人,十分舒服,但偶尔还是想着找个女朋友,租个大房子每天晚上学技术( 额,这个技术是正儿八经的计算机技术。 有很多想写的,不知道写什么,还是不写了,好了,本篇完。","categories":[{"name":"life","slug":"life","permalink":"http://example.com/categories/life/"}],"tags":[]},{"title":"解决multiprocessing_pyinstaller打包弹窗问题","slug":"解决multiprocessing_pyinstaller打包弹窗问题","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2022/01/02/解决multiprocessing_pyinstaller打包弹窗问题/","link":"","permalink":"http://example.com/2022/01/02/%E8%A7%A3%E5%86%B3multiprocessing_pyinstaller%E6%89%93%E5%8C%85%E5%BC%B9%E7%AA%97%E9%97%AE%E9%A2%98/","excerpt":"","text":"具体行为: 程序开始运行后不断弹窗。解决方法: StackOverflow。 懒人版解决方法: 在进入程序入口之前执行: multiprocessing.freeze_supoort(),最好把程序入口放在__main__里(真的有人不这么干吗?),然后在pyinstaller打包的时候不要打包成单个文件,而是要打包成一个文件夹。看StackOverflow上答题老哥的意思是在非Windows平台下似乎有打包成单个文件的方法,不过我也没那心思去找了,对我来说别的平台 == Linux,反正在Linux下直接安装依赖运行就对了,用啥Pyinstaller。 本篇完。","author":"Arvin","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"谷歌搜索屏蔽CSDN","slug":"谷歌搜索屏蔽CSDN","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2021/12/02/谷歌搜索屏蔽CSDN/","link":"","permalink":"http://example.com/2021/12/02/%E8%B0%B7%E6%AD%8C%E6%90%9C%E7%B4%A2%E5%B1%8F%E8%94%BDCSDN/","excerpt":"","text":"在搜索时加上 -csdn即可。如果要屏蔽知乎,就是 -知乎。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"乱七八糟","slug":"blog/乱七八糟","permalink":"http://example.com/categories/blog/%E4%B9%B1%E4%B8%83%E5%85%AB%E7%B3%9F/"}],"tags":[]},{"title":"Go语言等待所有goroutine结束","slug":"go语言等待所有子线程结束","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2021/11/25/go语言等待所有子线程结束/","link":"","permalink":"http://example.com/2021/11/25/go%E8%AF%AD%E8%A8%80%E7%AD%89%E5%BE%85%E6%89%80%E6%9C%89%E5%AD%90%E7%BA%BF%E7%A8%8B%E7%BB%93%E6%9D%9F/","excerpt":"","text":"逻辑是先给一个变量加上将要运行的goroutine的数量,然后goroutine在运行结束时把数量减一。主线程会一直等待直到这个变量的值为0。那么代码如下: 12345678910111213141516171819202122package mainimport ( "fmt" "sync")var wg sync.WaitGroupfunc fun(wg *sync.WaitGroup, num int) { // 在程序运行完毕后执行wg.Done() defer wg.Done() fmt.Println(num)}func main() { wg.Add(2) go fun(&wg, 1) go fun(&wg, 2) wg.Wait()} 输出(这个好像没有什么意义): 123» go run main.go zero@DESKTOP-IECQH7221","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Go","slug":"blog/Go","permalink":"http://example.com/categories/blog/Go/"}],"tags":[]},{"title":"Gcc只执行语法检测","slug":"Gcc只执行语法检测","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/11/15/Gcc只执行语法检测/","link":"","permalink":"http://example.com/2021/11/15/Gcc%E5%8F%AA%E6%89%A7%E8%A1%8C%E8%AF%AD%E6%B3%95%E6%A3%80%E6%B5%8B/","excerpt":"","text":"摘抄自: 100个Gcc小技巧。 使用该命令即可: 1gcc -fsyntax-only main.c # main.c是C文件 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"C","slug":"blog/C","permalink":"http://example.com/categories/blog/C/"}],"tags":[]},{"title":"Qv2ray对局域网开放","slug":"Qv2ray对局域网开放","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/10/04/Qv2ray对局域网开放/","link":"","permalink":"http://example.com/2021/10/04/Qv2ray%E5%AF%B9%E5%B1%80%E5%9F%9F%E7%BD%91%E5%BC%80%E6%94%BE/","excerpt":"","text":"今天好像一口气更新了很多篇博文,嗯,是年轻的味道。 那么本篇博文介绍一下Qv2ray如何允许局域网连接,想看Github Issues的请点击这里。 首先打开首选项,之后打开入站设置,之后把监听地址从127.0.0.1改成0.0.0.0就可以啦。 博文贵精不贵多,所以本篇完突然想起自己开头还说了两句废话。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"乱七八糟","slug":"blog/乱七八糟","permalink":"http://example.com/categories/blog/%E4%B9%B1%E4%B8%83%E5%85%AB%E7%B3%9F/"}],"tags":[]},{"title":"WSL2使用Windows上的代理软件","slug":"WSL2使用Windows上的代理软件","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/10/04/WSL2使用Windows上的代理软件/","link":"","permalink":"http://example.com/2021/10/04/WSL2%E4%BD%BF%E7%94%A8Windows%E4%B8%8A%E7%9A%84%E4%BB%A3%E7%90%86%E8%BD%AF%E4%BB%B6/","excerpt":"","text":"参考wsl2配置使用windows网络代理。也不知道这是不是原作者。 首先通过这条命令获取Windows IP: 1cat /etc/resolv.conf|grep nameserver|awk '{print $2}' 我获取到的是: 1172.20.0.1 这里我没有文中提到的防火墙问题,所以就不提及防火墙了。 然后打开代理软件,我用的是Qv2ray,http代理的端口是8889,并且设置对局域网开放(Qv2ray设置对局域网开放: 首选项->入站设置->监听地址->0.0.0.0),然后当然是必要的连接到代理服务器啦。 之后打开WSL,然后设置代理: 1export https_proxy=172.20.0.1:8889 再之后就可以让WSL使用代理了。 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"乱七八糟","slug":"blog/乱七八糟","permalink":"http://example.com/categories/blog/%E4%B9%B1%E4%B8%83%E5%85%AB%E7%B3%9F/"}],"tags":[]},{"title":"Windows下键位替换","slug":"Windows下键位替换","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/10/04/Windows下键位替换/","link":"","permalink":"http://example.com/2021/10/04/Windows%E4%B8%8B%E9%94%AE%E4%BD%8D%E6%9B%BF%E6%8D%A2/","excerpt":"","text":"首先下载微软推出的软件: PowerToys。安装好之后选择键盘管理器,左键单击”打开设置”,点击重映射键,左侧是将被更改的键,右侧是将替换成对应功能的键。左键单击”类型“(英文下是Type),然后按下键即可。最后别忘了点击确定。反正有图形界面,我就不演示怎么操作了。 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"乱七八糟","slug":"blog/乱七八糟","permalink":"http://example.com/categories/blog/%E4%B9%B1%E4%B8%83%E5%85%AB%E7%B3%9F/"}],"tags":[]},{"title":"十九岁随便写写","slug":"life7","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/10/04/life7/","link":"","permalink":"http://example.com/2021/10/04/life7/","excerpt":"","text":"额,自打19岁以来,一直忙着没写,也没啥想写的,现在终于有可写的内容了,也有写的心思了,于是就来写写。 在我19岁生日的那几天,每天都在给家里帮忙,累倒是不累,就是比较耗时间。那会儿我就在想: 该出去找工作了,要不然一直在家帮忙干活,岂不得把我辛辛苦苦累积的知识全部都给忘光了。于是我就投简历,找工作,嘿,还真找到了。现我已经入职上海微创,外派到深圳这边的IBM做项目。 那接下来说说我来深圳这几天的经历吧。 我是29号早上从郑州坐飞机去的深圳,凌晨去的机场。嘿,凌晨可真冷。于是我就穿了个外套。我只在5点的时候吃了十个煎饺,飞机预计是7点20起飞的,6点多的时候机场好多店铺都没开门,于是我就准备到了深圳再吃饭。上了飞机后我就开始睡觉。睡着睡着觉得不对劲,这飞机怎么这么平稳?睁眼一看时间,7点50,根据脚下地板颤动的幅度仔细思考一下,我发现这飞机竟然晚点了半个小时。不过让人感到开心的事情出现了,乘务员居然一人给发了一份牛肉面,我可是清楚的记得买票的时候上面写的没有餐饮的,想来是晚点儿的补偿。于是我就开开心心的吃了碗面。边吃面我就边打量我的机友们,我发现了一件惊悚到让人汗毛倒竖的事情:他们都穿的半截袖。飞机到站后,深圳的半截袖人群出现了一个手持外套肩背包的伟岸身影,他平静的目光中仿佛有日月在流转,旁人看一眼灵魂就会被吸入他那棕色的眼眸,继而深陷在他那瞳仁中无法自拔。许久,他嘴角勾起弧度,冷笑了一声,放弃思考了。 29号的夜晚我是在锐思特酒店度过的,还不错。30号我去了趟公司,这中间出现了让人无语的事情:29号傍晚微创的人事跟我说要我明天8点到IBM,我说我怎么记得IBM的技术面试官对我说的是9点,然后微创的人事跟我说是9点10分。过了一会儿又跟我说是9点30。但是,我把9点10分存进了我的寄存器,然后没有mov 9点30, 时间寄存器 于是就导致了我提前在楼下等了很长时间。 到公司后Angeline和KC给我介绍了一下同事,我只记住了Sam兄(绝对不是因为好记),其他几位都忘了叫啥了。中午让Sam兄带着一起吃了午饭。 晚上我又回了锐思特酒店。 1号是国庆节,我出门找房子,先去了一家之前联系好的房东那里,因为我的工资不多,所以我打算合租。房东因为比较忙,请了一名租客带我看房子,这位租客是位女性,长得是沉鱼落雁闭月羞花,有倾国倾城之姿。我为何如此夸赞她?主要是因为她后面帮了我亿点点忙。然后这位大美女带我看了房,我相中了一间房子,问阿姨价格,阿姨要1850,我说我工资少,能不能便宜点,阿姨说只能减50。然后我也懒得和她讨价还价了,我是真的不擅长,就跟她说我最高出1600,阿姨沉默了一会儿,说前三个月1700,后面都是1800。我根据大美女的指导打起了感情牌,把价格拉到了前三个月1600,后面都是1700。后来我觉得价格还是太高了,而且看阿姨的样子也是不愿意再降价了,我就签了间别人的离公司大概二十分钟脚程的房子。在我找到后,我跟阿姨说我找到了,阿姨在晚上回我:这么快?你说的那间可以1600给你。 我当时只感觉自己还是太年轻。时间回到在我签了房子之后,我跟某位大美女说了我找到房子了,大美女说要来帮我,我就答应了。然后这位美丽女子就带着我购买床上用品和一些清洁卫生用的东西,还帮我擦了擦家具,简直比我还出力,我要不说,别人肯定以为这房子是她住的。之后她还送了我一张垫子。我也没啥形容词了,这是遇上了女菩萨。不过,她帮我的原因让人很是难过:她说因为看我年纪小,而且有点傻,再加上还是同乡,就帮我了。 其实,在她说同乡的时候,我很想说一句:你没听说过老乡见老乡,背后beng一枪吗。 之后就是2号了,2号我干了什么呢?啊呀,忘了,那二号就不提了。哦哦哦,想起来了,我晚上喝着啤酒吃了外卖还看了电影《星际穿越》,心满意足。 再之后是3号,3号我办了张银行卡和电话卡。 今天是4号。今天我把我的博客从Arch迁移到了WSL Ubuntu上。然后想起了29号微创的人事催着我去办社保卡,我跑了三个银行都没有办成的事情,于是我就去了趟中国银行办社保卡,中国银行的人说:你交社保了吗?我:?中国银行的人打开我的手机查了查,说:你没交。我:√。中国银行的人:公司交了你再办吧。我:???敢情这人事自己都不清楚怎么回事,就让我来回跑,真是离谱。 好了,不说了,我要看代码辽。本篇完。","categories":[{"name":"life","slug":"life","permalink":"http://example.com/categories/life/"}],"tags":[]},{"title":"RabbitMQ用Python来一个Hello World","slug":"RabbitMQ用Python来一个Hello-World","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2021/09/23/RabbitMQ用Python来一个Hello-World/","link":"","permalink":"http://example.com/2021/09/23/RabbitMQ%E7%94%A8Python%E6%9D%A5%E4%B8%80%E4%B8%AAHello-World/","excerpt":"","text":"参考文档: 官方文档 hello world - python。本来我是想将整个文档都翻译下来的,后来考虑到这样做会耽误我学习,就只能粗略的记录一下使用方法了。本文中用到的图片是直接复制了RabbitMQ教程上的链接,并不是保存到了本地再加载,所以如果你发现图片加载不出来而且确认自己的网络没问题,请联系博主更新图片。本篇博文中的”message”统一翻译成消息。 博主用的是docker容器,直接执行该命令会自动安装好并运行: 1docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.9-management 这里我加了rm选项,容器一旦停止就会自动删除。 开始前的准备 你应该已经安装了RabbitMQ并且它正运行在localhost:5672上。 Hello World!(使用Pika Python客户端)在这部分教程里我们将会用Python写两个小程序: 一个发送一条信息的生产者(发送者),和一个接受消息并打印出消息的消费者(接受者)。这是一个消息传递的”Hello World”。 在下图中,”P”是我们的生产者,”C”是我们的消费者。这个中间的盒子是一个队列 - 一个RabbitMQ代表消费者保留的消息缓冲区。 总体设计看起来像下图一样: 生产者发送消息到”hello”队列。消费者从这个队列接收消息。 RabbitMQ库 RabbitMQ使用多种协议,这个教程使用了AMQP 0-9-1,这是一种消息传递的开放式通用协议。这里有一些RabbitMQ的不同语言的客户端。在这一系列教程里我们将会使用RabbitMQ团队推荐的Python客户端Pika 1.0.0。你可以使用pip包管理工具安装它: 1python -m pip install pika --upgrade 现在我们已经安装好了Pika,可以开始写代码了。 发送 我们的第一个程序send.py将会发送一个信息到队列。我们要做的第一件事情是与RabbitMQ服务建立一个连接。 12345#!/usr/bin/env pythonimport pikaconnection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))channel = connection.channel() 我们连接到本地代理上——所以是localhost。如果我们想要连接到其他机器上,我们应该在这里指定它的名字或者IP地址。[博主注: “这里”指的是’localhost’字符串这里,我不想翻译的画蛇添足,于是便没有在译文中说明] 接下来,在发送消息之前我们需要确保接受队列是存在的。如果我们发送一条消息到不存在的位置,RabbitMQ只会丢弃掉那条消息。让我们创建一个hello队列,消息将会发送到这个队列。 1channel.queue_declare(queue='hello') 此时我们已经准备好发送消息了。我们的第一个消息将会只包含一个字符串”Hello World!”并且我们会将它发送到我们的hello队列。 在RabbitMQ中一条消息永远不会被直接发送给队列,它总是需要交换[博主注: 我也不知道需要交换什么,原文这里写的是 it always needs to go through an exchange]。但我们不要在意这些细节 - 你可以从第三部分教程获取更多关于交换的内容。现在我们需要知道的是如何使用由空字符串标识的默认交换[博主注: 原文: All we need to know now is how to use a default exchange identified by an empty string]、这个交换是特别的 - 它允许我们准确的指定消息应该去的队列。队列名需要在routing_key参数中被指定: 1234channel.basic_publish(exchange='', routing_key='hello', body='Hello World!')print(" [x] Sent 'Hello World!'") 在退出程序之前我们需要确认网络缓冲区已经刷新过了并且我们的消息已经真正的发到了RabbitMQ。我们通过关闭连接来做到它。 1connection.close() 发送无效! 如果这是你第一次使用RabbitMQ并且你没有看到”已发送消息”你可能会感到摸不着头脑并想知道什么地方出了问题。也许代理启动时没有足够的可用磁盘空间(默认至少需要200MB空闲空间)并且因此拒绝消息。检查代理日志文件确认或在必要情况下减少限制。配置文件文档将会告诉你如何设置*磁盘空闲空间限制(disk_free_limit)*。 接收 我们的第二个程序receive.py将会从队列接收并打印消息到屏幕。 再次的,我们需要连接到RabbitMQ服务器。负责连接Rabbit的代码和之前一样。 第二步,就像之前一样,是确认队列是否存在。使用queue_declared创建队列是幂等的 - 我们可以运行这个命令许多次,但只有一次会创建。 1channel.queue_declare(queue='hello') 你也许会问为什么我们要再次声明这个队列 - 我们已经在我们的上一个代码里声明过了。如果我们确认队列已经存在,我们就可以避免这种情况。比如send.py已经运行过了,但我们不确定哪个程序先运行。在这种情况下,最好在两个程序中重复声明队列。 查看队列 你也许想看到RabbitMQ有哪些队列并且它们之中有哪些消息,你可以通过在特权用户下使用rabbitmqctl工具做到这一点: 1sudo rabbitmqctl list_queues 在Windows下,忽略sudo: 1rabbitmqctl.bat list_queues 从队列接收消息是更复杂的。它通过将回调函数订阅到队列来工作。当我们接收到一条消息时,回调函数将会通过Pika库被调用。在我们的例子中,这个函数将在屏幕上打印消息的内容。 12def callback(ch, method, properties, body): print(" [x] Received %r" % body) 接下来,我们需要告诉 RabbitMQ 这个特定的回调函数应该从我们的 hello 队列接收消息: 123channel.basic_consume(queue='hello', auto_ack=True, on_message_callback=callback) 为了使命令成功运行,我们必须确保我们要订阅的队列存在。幸运的是,我们对此充满信心——我们已经在上面使用 queue_declare创建了一个队列。 auto_ack参数将会在后面描述。 最终,我们进入了一个等待数据的死循环并且在需要的时候运行回调函数。并在程序关闭期间捕获 KeyboardInterrupt异常。 12print(' [*] Waiting for messages. To exit press CTRL+C')channel.start_consuming() 123456789if __name__ == '__main__': try: main() except KeyboardInterrupt: print('Interrupted') try: sys.exit(0) except SystemExit: os._exit(0) 代码全部放在这里: send.py(源文件) 123456789101112#!/usr/bin/env pythonimport pikaconnection = pika.BlockingConnection( pika.ConnectionParameters(host='localhost'))channel = connection.channel()channel.queue_declare(queue='hello')channel.basic_publish(exchange='', routing_key='hello', body='Hello World!')print(" [x] Sent 'Hello World!'")connection.close() receive.py (源文件) 1234567891011121314151617181920212223242526#!/usr/bin/env pythonimport pika, sys, osdef main(): connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) channel = connection.channel() channel.queue_declare(queue='hello') def callback(ch, method, properties, body): print(" [x] Received %r" % body) channel.basic_consume(queue='hello', on_message_callback=callback, auto_ack=True) print(' [*] Waiting for messages. To exit press CTRL+C') channel.start_consuming()if __name__ == '__main__': try: main() except KeyboardInterrupt: print('Interrupted') try: sys.exit(0) except SystemExit: os._exit(0) 现在我们可以在终端尝试我们的程序了。第一步,让我们启动一个消费者,它将不断的等待交付(等待生产者发送消息): 123python receive.py# => [*] Waiting for messages. To exit press CTRL+C# => [x] Received 'Hello World!' 接下来启动生产者,生产者程序每一次运行后都将停止: 12python send.py# => [x] Sent 'Hello World!' 欢呼吧!我们已经能够通过RabbitMQ发送第一条消息了。你可能已经注意到了,receive.py 程序不会退出。 它将随时准备接收更多消息,并且它可能会被 Ctrl-C 中断[博主注: 这是文档翻译的翻译: 意思是可以通过Ctrl + C中断这个程序]。 尝试在新的终端中运行send.py。[博主注: 不一定要在新终端,只要你别关闭消费者的那个终端就行。其实关闭了问题也不大,如果消息少的话,打开消费者还是能照样一字不差接收到消息] 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"RabbitMQ","slug":"blog/RabbitMQ","permalink":"http://example.com/categories/blog/RabbitMQ/"}],"tags":[]},{"title":"Linux下电子书文件格式转换","slug":"Linux下电子书文件格式转换","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/09/20/Linux下电子书文件格式转换/","link":"","permalink":"http://example.com/2021/09/20/Linux%E4%B8%8B%E7%94%B5%E5%AD%90%E4%B9%A6%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F%E8%BD%AC%E6%8D%A2/","excerpt":"","text":"今天本篇博文要介绍的工具据书伴所说,默认支持的输入文件格式包括:azw4, chm, comic, djvu, docx, epub, fb2, htlz, html, lit, lrf, mobi, odt, pdb, pdf, pml, rb, rtf, recipe, snb, tcr, txt;默认支持的输出文件格式包括:azw3, docx, epub, fb2, html, htmlz, lit, lrf, mobi, oeb, pdb, pdf, pml, rb, rtf, snb, tcr, txt, txtz。 我的目的是将mobi文件转换成pdf文件。 那么首先是安装包了,输入以下命令来安装calibre: 1pacman -S calibre 安装好了之后就可以开始文件转换了,格式如下(input_file是输入文件,output_file是输出文件,options是可选选项,电子书格式转换几乎用不到选项): 1ebook-convert input_file output_file [options] 例如,我想将名为python_book.mobi的mobi转换为名为pybook.pdf的pdf文件,就可以这么写: 1ebook-convert python_book.mobi pybook.pdf 然后静等程序运行完毕,你就会看到在同级目录下出现了一个叫pybook.pdf的文件,这个文件就是python_book.mobi的pdf格式。 想了解更多内容请点击本篇博文开头的超链接(博主只是记录下了博主自己想用的内容,还有一些内容没记录)。 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"乱七八糟","slug":"blog/乱七八糟","permalink":"http://example.com/categories/blog/%E4%B9%B1%E4%B8%83%E5%85%AB%E7%B3%9F/"}],"tags":[]},{"title":"JS导出和导入模块","slug":"JS导出和导入模块","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/09/17/JS导出和导入模块/","link":"","permalink":"http://example.com/2021/09/17/JS%E5%AF%BC%E5%87%BA%E5%92%8C%E5%AF%BC%E5%85%A5%E6%A8%A1%E5%9D%97/","excerpt":"","text":"本文所写的内容皆来自红宝书,并且是ES6的知识。 本篇博文阅读方法: 命名导出对应命名导入,默认导出对应默认导入。 模块导出export关键字用于声明一个值为命名导出。导出语句必须在模块顶级,不能嵌套在某个块中: 12345678// 允许export ...// 不允许if (condition){ export ...} 命名导出123456789101112131415// example1export const foo = 'foo';// example2const foo = 'foo';export { foo };// example3const foo = 'foo';const foo1 = 'foo1';export { foo, foo1 };// example4const foo = 'foo';export { foo as myFoo }; 默认导出123456789101112131415161718// example1const foo = 'foo';export default foo;// example2cosnt foo = 'foo';export { foo as default };//example3const foo = 'foo';const foo1 = 'foo1';export { foo1 };export default foo;// example4const foo = 'foo';const foo1 = 'foo1';export { foo as default, foo1 }; 模块导入与export类似,import必须出现在模块的顶级。 命名导入1234567// example1import { foo } from './fooModule.js';console.log(foo);// example2import {foo1, * as Foo} as Foo from './foo.js';console.log(Foo.foo); 默认导入123456// example1 这种方法可导入* as name 或 默认导出的值。*中不含有默认导出的值。import foo from './foo.js';console.log(foo);// example2import { default as foo } from './foo.js';console.log(foo); 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"JavaScript","slug":"blog/JavaScript","permalink":"http://example.com/categories/blog/JavaScript/"}],"tags":[]},{"title":"请求Django无报错无日志","slug":"请求Django无报错无日志","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2021/09/16/请求Django无报错无日志/","link":"","permalink":"http://example.com/2021/09/16/%E8%AF%B7%E6%B1%82Django%E6%97%A0%E6%8A%A5%E9%94%99%E6%97%A0%E6%97%A5%E5%BF%97/","excerpt":"","text":"本篇博文记载的是我今天踩的一个坑,虽然我之前已经踩过这个坑很多次了,但都因为那时的我经常用Django,从而轻松修好此坑,但今日我Django复健的时候,又遇到它了,脑子里只记得我踩过它,修好过它,但忘了怎么修的了。于是今天我就记录一下。 先来描述一下问题吧: 后台为Django,当前端请求Django时,前端如果用的是XHR请求的后台,那么会报一个跨域的错,如果用的fetch请求的后台,且加了参数({“mode”: “cors”}),那么也会出一个跨域的报错,用浏览器抓包,会看到一个红色的条目。而此时,再看Django的情况: Django没有打印404,没有打印URL访问记录,它什么都没有打印,就仿佛前端的这个请求没有到达后台一样。在看解决方案之前先来看一眼我Django的URL配置, 这个URL是在/user下的: 1path("add_user/", views.add_user, name='add_user') 那么接下来是解决方式: 这是我前端请求的URL: “http://localhost:8000/user/add_user“,我们只要在这个URL最后加一个斜杠,如: “http://localhost:8000/user/add_user/“,这个问题就会消失。没错,解决方式就是这样。这解决方案已经超出了我的知识储备,我在网上查到的关于加不加/的内容只有”目录与文件的区别”或者”不加/会请求两次”。我想如果没有关于路径格式标准的规定的话,这种错也许是Django独有的。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"踩坑","slug":"踩坑","permalink":"http://example.com/tags/%E8%B8%A9%E5%9D%91/"}]},{"title":"CSAPP系统级I/O笔记","slug":"CSAPP系统级IO笔记","date":"un66fin66","updated":"un22fin22","comments":true,"path":"2021/09/11/CSAPP系统级IO笔记/","link":"","permalink":"http://example.com/2021/09/11/CSAPP%E7%B3%BB%E7%BB%9F%E7%BA%A7IO%E7%AC%94%E8%AE%B0/","excerpt":"","text":"本篇博文仅列出本章中提到的标准函数,不会记录RIO包等手写函数。 打开和关闭文件open()12#include <fcntl.h> // 头文件int open(char *filename, int flags, mode_t mode); // 返回: 若成功则为新文件描述符,若出错为-1。 open函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。flags参数指明了进程打算如何访问这个文件。 O_RDONLY: 只读。 O_WRONLY: 只写。 O_RDWR: 可读可写。 flags参数也可以是一个或者更多位掩码的或,为写提供给一些额外的指示: O_CREAT: 如果文件不存在,就创建它的一个截断的(truncated)(空)文件。 O_TRUNC: 如果文件已经存在,就截断它。 O_APPEND: 在每次写操作前,设置文件位置到文件的结尾处。 123456789101112在sys/stat.h中定义的访问权限位S_IRUSR: 使用者能够读这个文件S_IWUSR: 使用者能够写这个文件S_IXUSR: 使用者能够执行这个文件S_IRGRP: 拥有者所在组的成员能够读这个文件S_IWGRP: 拥有者所在组的成员能够写这个文件S_IXGRP: 拥有者所在组的成员能够执行这个文件S_IROTH: 其他人(任何人)能够读这个文件S_IWOTH: 其他人(任何人)能够写这个文件S_IXOTH: 其他人(任何人)能够执行这个文件 close()12#include <unistd.h>int close(int fd); // 返回: 若成功则为0,若出错则为-1。 读和写文件(read和write)123#include <unistd.h>ssize_t read(int fd, void *buf, size_t n); // 返回: 若成功则为读的字节数,若EOF则为0,若出错为-1。ssize_t write(int fd, const void *buf, size_t n); // 返回: 若成功则为写的字节数,若出错则为-1。 读取文件元数据应用程序能够通过调用stat和fstat函数,检索到关于文件的信息(有时也称为文件的元数据(metadata))。 1234#include <sys/stat.h>int stat(const char *filename, struct stat *buf);int fstat(int fd, struct stat *buf);// 这两个函数的返回: 若成功则为0,若出错则为-1 stat函数以一个文件名作为输入,并填写一个stat数据结构中的各个成员。fstat是相似的,只不过是以文件描述符而不是文件名作为输入。 12345678910111213141516/* Metadata returned by the stat and fstat functions */struct stat { dev_t st_dev; // Device ino_t st_ino; // inode mode_t st_mode; // Protection and file type nlink_t st_nlink; // Number of hard links uid_t st_uid; // User ID of owner gid_t st_gid; // Group ID of owner dev_t st_rdev; // Device type (if inode device) off_t st_size; // Total size, in bytes unsigned long st_blksize; // Block size for filesystem I/O unsigned long st_blocks; // NUmber of blocks allocated time_t st_atime; // Time of last access time_t st_mtime; // Time of last modification time_t st_ctime; // Time of last change} st_size成员包含了文件的字节数大小,st_mode成员则编码了文件访问许可位和文件类型。Linux在sys/stat.h中定义了宏谓词来确定st_mode成员的文件类型: S_ISREG(m)。这是一个普通文件吗? S_ISDIR(m)。这是一个目录文件吗? S_ISSOCK(m)。这是一个网络套接字吗? 读取目录内容123#include <sys/types.h>#include <dirent.h>DIR *opendir(const char *name); // 返回: 若成功,则为处理的指针;若出错,则为NULL。 函数opendir以路径名为参数,返回指向*目录流(directory stream)*的指针。流是对条目有序列表的抽象,在这里是指目录项的列表。 12#include <dirent.h>struct dirent *readdir(DIR *dirp); // 返回: 若成功,则为指向下一个目录项的指针;若没有更多的目录项或出错,则为NULL。如果出错会设置errno。判断readdir究竟是出错还是结束的唯一方法就是判断errno是否改变。 每次对readdir的调用返回的都是指向流dirp中下一个目录项的指针,或者,如果没有更多目录项则返回NULL。每个目录项都是一个结构,其形式如下: 1234struct dirent { ino_t ino; // inode number char d_name[256]; // Filename} I/O重定向12#include <unistd.h>int dup2(int oldfd, int newfd); // 返回: 若成功则为非负的描述符,若出错则为-1。 dup2函数复制描述符表表项oldfd到描述符表表项newfd,覆盖描述符表表项newfd以前的内容。如果newfd已经打开了,dup2会在复制oldfd之前关闭newfd。 共享文件内核用三个相关的数据结构来表示打开的文件: ***描述符表(descriptor table)。每个进程都有它独立的描述符表,它的表项是由进程打开的文件描述符来索引的。每个打开的描述符表项指向文件表*中的一个表项。 ***文件表(file table)**。打开文件的集合是由一张文件表来表示的,所有的进程共享这张表。每个文件表的表项组成(针对我们的目的)包括当前的文件位置,引用计数(reference count)*(即当前指向该表项的描述符表项数),以及一个指向v-node表中对应表项的指针。关闭一个描述符会减少相应的文件表表项中的引用计数。内核不会删除这个文件表表项,直到它的引用计数为零。 ***v-node表(v-node table)***。同文件表一样,所有的进程共享这张v-node表,每个表项包含stat结构中的大多数信息,包括st_mode和st_size成员。 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"CSAPP","slug":"blog/CSAPP","permalink":"http://example.com/categories/blog/CSAPP/"}],"tags":[]},{"title":"CSAPP并发编程笔记","slug":"CSAPP并发编程笔记","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2021/09/09/CSAPP并发编程笔记/","link":"","permalink":"http://example.com/2021/09/09/CSAPP%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E7%AC%94%E8%AE%B0/","excerpt":"","text":"本篇博文仅会列出本章节中所使用的函数。 select()123456#include <sys/select.h> // 头文件int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict timeout); // 返回已准备好的描述符的非零的个数,若出错则为-1。FD_ZERO(fd_set *set); // 清空fd_setFD_CLR(int fd, fd_set *fdset); // 清空fdset中fd对应的位[博主注: 具体操作应该是置零]。FD_SET(int fd, fd_set *fdset); // 在fdset中设置fd的位。[博主注: 可理解成将fd添加进fdset]FD_ISSET(int fd, fd_set *fdset); // 查看fd是否在fdset中。如果fd目前在fdset中,则返回非零值,如果fd不再fdset中,就返回0。 当select触发后,对应的fd_set中的内容会被改变。 线程创建线程123#include <pthread.h> // 头文件typedef void *(func)(void *);int pthread_create(pthread_t *tid, pthread_attr_t *attr, func *f, void *arg); // 若成功则返回0,若出错则为非零。 pthread_create函数创建一个新的线程,并带着一个输入变量arg,在新线程的上下文中运行线程例程f。能用attr参数来改变新创建线程的默认属性。 获取线程ID12#include <pthread.h> // 头文件pthread_t pthread_self(void); // 返回调用者线程的ID。 终止线程一个线程是通过下列方式之一来终止的: 当顶层的线程例程返回时,线程会隐式地终止。 通过调用pthread_exit函数,线程会显式地终止。如果主线程调用pthread_exit,它会等待所有其他对等线程终止,然后再终止主线程和整个进程。 12#include <pthread.h> // 头文件void pthread_exit(void *thread_return); // 返回值为thread_return。 某个对等线程调用Linux的eixt函数,该函数终止进程以及所有与该进程相关的线程。 另一个对等线程通过以当前线程ID作为参数调用pthread_cancel函数来终止当前线程。12#include <pthread.h>int pthread_cancel(pthread_t tid); // 若成功则返回0,若出错则为非零。 回收已终止线程的资源线程通过调用pthread_join函数等待其他线程终止。 12#include <pthread.h>int pthread_join(pthread_t tid, void **thread_return); // 若成功则返回0,若出错则为非零。 pthread_join函数会阻塞,直到线程tid终止,将线程例程返回的通用(void*)指针赋值为thread_return指向的位置,然后回收已终止线程占用的所有内存资源。 分离线程在任何一个时间点上,线程是**可结合的(joinable)或者是分离的(detached)**。一个可结合的线程能够被其他线程收回和杀死。在被其他线程回收之前,它的内存资源(例如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的。它的内存资源在它终止时由系统自动释放。 默认情况下,线程被创建成可结合的。为了避免内存泄漏,每个可结合线程都应该要么被其他线程显式地收回,要么通过调用pthread_detach函数被分离。 12#include <pthread.h> // 头文件int pthread_detach(pthread_t tid); // 若成功则返回0,若出错则为非零。 pthtread_detach函数分离可结合线程tid。线程能够通过以pthread_self()为参数的pthread_detach调用来分离自己。[博主注: 其实意思就是你可以在线程中使用pthread_detach(pthread_self())来分离调用这段代码的线程] 初始化线程123#include <pthread.h> // 头文件pthread_once_t once_control = PTHREAD_ONCE_INIT;int pthread_once(pthread_once_t *once_control, void(*init_routine)(void)); // 总是返回0 once_control变量是一个全局或者静态变量,总是被初始化为PTHREAD_ONCE_INIT。当你第一次用参数once_control调用pthread_once时,它调用init_routine,这是一个没有输入参数,也不返回什么的函数。接下来的以once_control为参数的pthread_once调用不做任何事情。[博主注: 其实它的功能就是调用一个无返回值且无参数的函数一次,以后再执行这个函数,如果还是同一个once_control参数,便不再调用了] 信号量12345678910111213141516171819202122#include <semaphore.h> // 头文件int sem_init(sem_t *sem, int pshared, unsigned int value); // 初始化信号量/*sem_init的英文描述(来自man7.org):sem_init() initializes the unnamed semaphore at the address pointed to by sem. The value argument specifies the initial value for the semaphore.The pshared argument indicates whether this semaphore is to be shared between the threads of a process, or between processes.If pshared has the value 0, then the semaphore is shared between the threads of a process, and should be located at some address that is visible to all threads (e.g., a global variable, or a variable allocated dynamically on the heap).If pshared is nonzero, then the semaphore is shared between processes, and should be located in a region of shared memory (see shm_open(3), mmap(2), and shmget(2)). (Since a child created by fork(2) inherits its parent's memory mappings, it can also access the semaphore.) Any process that can access the shared memory region can operate on the semaphore using sem_post(3), sem_wait(3), and so on.Initializing a semaphore that has already been initialized results in undefined behavior.[博主的翻译(博主英语水平有限,有些地方翻译的比较生硬,若是有能力还请看英文版): sem_init()初始化sem指向的地址。value参数指定信号量的初始值。pshared参数决定这个信号量是用于同一个进程下的多个线程 还是 用于多个进程。如果pshared参数的值是0,那么信号量将会在一个进程下的多个线程之间共享。它的地址应该能够被所有线程所访问(如将其设置为全局变量或者动态分配的在堆上的内存)。如果pshared的参数是非零值,那么信号量将会在多个进程之间共享。并且这个信号量应该位于共享内存中(请查看sem_open(),mmap(),和shmget())。当一个子进程被创建后,它会继承它父进程的内存映射,也能访问信号量。任何进程都可以通过使用sem_post, sem_wait操作共享内存区。初始化一个已经初始化了的信号量是未定义行为。]*/int sem_wait(sem_t *s); // 如果s是非零的,那么该函数将s减一,并且立即返回。如果s为零,那么就挂起这个线程,直到s变为非零。而一个sem_post操作会重启这个线程。在重启之后,sem_wait操作将s减一,并将控制返回给调用者。int sem_post(sem_t *s); // sem_post操作将s加一。如果有任何线程阻塞在sem_wait操作等待s变成非零,那么sem_post操作会重启这些线程中的一个,然后该线程将s减一,完成它的sem_wait操作。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"CSAPP","slug":"blog/CSAPP","permalink":"http://example.com/categories/blog/CSAPP/"}],"tags":[]},{"title":"CSAPP网络编程笔记","slug":"CSAPP网络编程笔记","date":"un66fin66","updated":"un22fin22","comments":true,"path":"2021/08/14/CSAPP网络编程笔记/","link":"","permalink":"http://example.com/2021/08/14/CSAPP%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E7%AC%94%E8%AE%B0/","excerpt":"","text":"本篇只记录CSAPP网络编程章节中的函数所属的头文件以及参数,不会有任何额外的知识,因为经过了之前写”信号”笔记的毒打,我深刻的意识到了整本书都是需要记的知识点这个惨痛的事实。另外,本篇的内容与我的博文: Unix网络编程篇有所重复,但我依然会记载Unix网络编程篇出现过并且在CSAPP也出现过的函数与结构,所以无需担心我因偷懒而导致内容不全。 struct in_addr1234#include <arpa/inet.h> // 头文件struct in_addr{ uint32_t s_addr; // Address in network byte order (big-endian) 博主解释: 这个是用来存储IP地址的,采用大端法。而且这个结构体只有这一个成员。} 网络/主机 字节顺序转换12345678#include <arpa/inet.h> // 函数所属头文件uint32_t htonl(uint32_t hostlong);uint16_t htons(uint16_t hostport);// 返回: 按照网络字节顺序的值。uint32_t ntohl(uint32_t netlong);uint16_t ntohs(uint16_t netshort);// 返回: 按照主机字节顺序的值。 IP地址和点分十进制串转换1234#include <arpa/inet.h> // 头文件int inet_pton(AF_INET, const char *src, void *dst); // 返回: 若成功则为1, 若src为非法点分十进制则为0,若出错则为-1。const char* inet_ntop(AF_INET, const void *src, char *dst, socklen_t size); // 返回: 若成功则指向点分十进制字符串的指针,若出错则为NULL。 struct sockaddr_in123456789101112#include <arpa/inet.h> // 头文件struct sockaddr_in{ uint16_t sin_family; // Protocol family. uint16_t sin_port; // Port number in network byte order. struct in_addr sin_addr; // IP address in network byte order. unsigned char sin_zero[8]; // Pad to sizeof(struct sockaddr).}struct sockaddr{ uint16_t sa_family; // Protocol family. char sa_data[14]; // Address data.} 注意,_in后缀是互联网络(internet)的缩写,而不是输入(input)的缩写。 另外,结构体sockaddr_in中的成员sin_zero是用来填充结构体sockaddr_in来使其与结构体sockaddr保持相同大小的。sockaddr_in的大小为2 bytes + 2 bytes + 4 bytes + 8 bytes = 16 bytes,sockaddr的大小为2 bytes + 14 bytes = 16 bytes。 socket()客户端和服务器使用socket函数来创建一个套接字描述符(socket descriptor)。 12#include <sys/socket.h> // 头文件int socket(int domain, int type, int protocol); // 返回: 若成功则为非负描述符,若出错则为-1。 connect()客户端通过调用connect函数来建立和服务器的连接。 12#include <sys/socket.h> // 头文件int connect(int clientfd, const struct sockaddr *addr, socklen_t addrlen); // 返回: 若成功则为0,若出错则为-1。 bind()bind函数告诉内核将addr中的服务器套接字地址和套接字描述符sockfd联系起来。参数addrlen就是sizeof(sockaddr_in)。 12#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // 返回: 若成功则为0,若出错则为-1。 listen()服务器调用listen函数告诉内核,描述符是被服务器而不是客户端使用的。 12#include <sys/socket.h>int listen(int sockfd, int backlog); // 返回: 若成功则为0,若出错则为-1。 listen函数将sockfd从一个主动套接字转化为一个监听套接字(listening socket),该套接字可以接受来自客户端的连接请求,backlog参数暗示了内核在开始拒绝连接请求之前,队列中要排队的未完成的连接请求的数量。 accept()服务器通过调用accept函数来等待来自客户端的连接请求。 12#include <sys/socket.h>int accept(int listenfd, struct sockaddr *addr, socklen_t *addrlen); // 返回: 若成功则为非负连接描述符,若出错则为-1。 主机和服务的转换getaddrinfo()Linux提供了一些强大的函数(称为getaddrinfo和getnameinfo)实现二进制套接字地址结构和主机名,主机地址,服务名和端口号的字符串表示之间的相互转化。 1234567#include <netdb.h> // 头文件int getaddrinfo(const char *host, const char *service, const struct addrinfo *hints, struct addrinfo **result); // 返回: 如果成功则为0,如果错误则为非零的错误代码。void freeaddrinfo(struct addrinfo *result); // 用来释放链表,result填getaddrinfo中使用的result。const char *gai_strerror(int errcode); // 返回: 错误消息。这个错误消息是根据errcode来变化的。 给定host和service(套接字地址的两个组成部分),getaddrinfo返回result,result一个指向addrinfo结构的链表,其中每个结构指向一个对应于host和service的套接字地址结构。 getaddrinfo的host参数可以是域名,也可以是数字地址(如点分十进制IP地址)。service参数可以是服务名(如http),也可以是十进制端口号。如果不想把主机名转换成地址,可以把host参数设置为NULL.对service来说也是一样。但是必须指定两者中至少一个。 可选的参数hints是一个getaddrinfo结构,它提供对getaddrinfo返回的套接字地址列表的更好的控制。如果要传递hints参数,只能设置下列字段: ai_family,ai_socktype,ai_protocol和ai_flags字段。其他字段必须设置为0(或NULL)。 getaddrinfo默认可以返回IPv4和IPv6套接字地址。ai_family设置为AF_INET会将列表限制为IPv4地址;设置为AF_INET6则限制为IPv6地址。 对于host关联的每个地址,getaddrinfo函数默认最多返回三个addrinfo结构,每个的ai_socktype字段不同: 一个是连接,一个是数据报,一个是原始套接字。ai_socktype设置为SOCK_STREAM将列表限制为对每个地址最多一个addrinfo结构,该结构的套接字地址可以作为连接的一个端点。 ai_flags字段是一个位掩码,可以进一步修改默认行为。可以把各种值用OR组合起来得到该掩码。下面是一些有用的值: AI_ADDRCONFIG。如果在使用连接,就推荐这个标志。它要求只有当本地主机被配置为IPv4时,getaddrinfo返回IPv4地址。对IPv6也是类似。 AI_CANONNAME。ai_canonname字段默认为NULL。如果设置了该标志,就是告诉getaddrinfo将列表中第一个addrinfo结构的ai_canonname字段指向host的权威(官方)名字。 AI_NUMERICSERV。参数service默认可以是服务名或端口号。这个标志强制参数service为端口号。 AI_PASSIVE。getaddrinfo默认返回套接字地址,客户端可以在调用connect时用作主动套接字。这个标志告诉该函数,返回的套接字地址可能被服务器用作监听套接字。在这种情况下,参数host应该为NULL。得到的套接字地址字段会是通配符地址(wildcard address),告诉内核这个服务器会接受发送到该主机所有IP地址的请求。1234567891011// getaddrinfo使用的addrinfo结构struct addrinfo{ int ai_flags; // Hints argument flags int ai_family; // First arg to socket function int ai_socktype; // Second arg to socket function int ai_protocol; // Third arg to socket function char *ai_canonname; // Canonical hostname size_t ai_addrlen; // Size of ai_addr struct struct sockaddr *ai_addr; // Ptr to socket address structure struct addrinfo *ai_next; // Ptr to next item in linked list} 当getaddrinfo创建输出列表中的addrinfo结构时,会填写每个字段,除了ai_flags。ai_addr字段指向一个套接字地址结构,ai_addrlen字段给出这个套接字地址结构的大小,而ai_next字段指向列表中下一个addrinfo结构。其他字段描述这个套接字地址的各种属性。 getaddrinfo一个很好的方面是addrinfo结构中的字段是不透明的,即它们可以直接传递给套接字接口中的函数,应用程序代码无需再做任何处理。这个强大的属性使得我们编写的客户端和服务器能够独立于某个特殊版本的IP协议。 getnameinfo()getnameinfo函数和getaddrinfo是相反的,将一个套接字地址结构转换成相应的主机和服务名字符串。它是已弃用的gethostbyaddr和getservbyport函数的新的替代品,和以前的那些函数不同,它是可重入和与协议无关的。 12#include <netdb.h> // 头文件int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *service, size_t servlen, int flags); // 返回: 如果成功则为0,如果错误则为非零的错误代码。 参数sa指向大小为salen字节的套接字地址结构,host指向大小为hostlen字节的缓冲区,service指向大小为servlen字节的缓冲区。getnameinfo函数将套接字地址结构sa转换成对应的主机和服务名字符串,并将它们复制到host和service缓冲区。如果getnameinfo返回非零的错误代码,应用程序可以调用gai_strerror把它转化成字符串。 如果不想要主机名,可以把host设置为NULL,hostlen设置为0,对服务字段来说也是一样。不过,两者必须设置其中之一。 参数flags是一个位掩码,能够修改默认的行为。可以把各种值用OR组合起来得到该掩码。下面是两个有用的值。 NI_NUMERICHOST。getnameinfo默认试图返回host中的域名。设置该标志会使该函数返回一个数字地址字符串。 NI_NUMERICSERV。getnameinfo默认会检查/etc/services,如果可能,会返回服务名而不是端口号。设置该标志会使该函数跳过查找,简单地返回端口号。 getaddrinfo()和getnameinfo()示例12345678910111213141516171819202122232425262728293031323334353637383940414243#include <netdb.h>#include <stdio.h>#include <string.h>int main(void){ char host[30]; // host用来存储点分十进制地址 struct addrinfo hints, *rst, *p; // hints用来告诉getaddrinfo我们所选择的参数,rst将会在调用getaddrinfo成功后指向一个struct addrinfo的链表, p将用于迭代rst指向的链表 int gstatus; // gstatus用来存储getaddrinfo的结果 // 用0填充hints memset((void*)&hints, 0, sizeof(struct addrinfo)); // 配置socktype为SOCK_STREAM hints.ai_socktype = SOCK_STREAM; // 如果调用成功,输出点分十进制地址,否则输出错误原因 if((gstatus = getaddrinfo("www.gray-ice.com", NULL, &hints, &rst)) == 0) { // 迭代addrinfo链表 for(p = rst; p != NULL; p = p->ai_next) { // 将struct sockaddr *类型的ai_addr指向的内容转换成字符串形式的点分十进制地址 if(getnameinfo(p->ai_addr, p->ai_addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST) != 0) { printf("getnameinfo failed.\\n"); continue; } // 输出点分十进制地址 printf("%s\\n", host); } // 释放addrinfo链表 freeaddrinfo(rst); } else{ // 输出错误原因 printf("%s\\n", gai_strerror(gstatus)); } return 0;}","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"CSAPP","slug":"blog/CSAPP","permalink":"http://example.com/categories/blog/CSAPP/"}],"tags":[]},{"title":"期约外完成期约","slug":"期约外完成期约","date":"un66fin66","updated":"un22fin22","comments":true,"path":"2021/07/24/期约外完成期约/","link":"","permalink":"http://example.com/2021/07/24/%E6%9C%9F%E7%BA%A6%E5%A4%96%E5%AE%8C%E6%88%90%E6%9C%9F%E7%BA%A6/","excerpt":"","text":"逻辑是这样的: 先在期约的作用域外定义一个变量,然后在期约中让这个变量等于期约的resolve方法,然后在期约外调用这个定义的函数。那么看代码吧: 12345678910let syncfunction;let p = new Promise((resolve)=>{ syncfunction = resolve;})p.then(()=>{ console.log("p run trap.");})syncfunction();console.log("the last run"); 我这里浏览器控制台输入如下: 12the last runp run trap. then中的箭头函数因为期约被完成而调用。本篇完。js是真的离谱。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"JavaScript","slug":"blog/JavaScript","permalink":"http://example.com/categories/blog/JavaScript/"}],"tags":[]},{"title":"信号","slug":"信号","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/07/23/信号/","link":"","permalink":"http://example.com/2021/07/23/%E4%BF%A1%E5%8F%B7/","excerpt":"","text":"本篇所有内容均参考CSAPP,部分内容会加上博主理解(为了防止博主的理解有问题而误导读者,博主会在所有自己理解的地方标注是博主的理解,被标注的内容请谨慎阅读)。[博主吐槽: 本来想摘抄一些知识点的,没想到整个章节全部都是知识点]Linux信号允许进程和内核中断其他进程。 信号术语 发送信号。内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。发送信号可以有如下原因: 1)内核检测到一个系统事件,比如除零错误或者子进程终止。2) 一个进程调用了kill函数,显式的要求内核发送一个信号给目的进程。一个进程可以发送信号给它自己。 接收信号。 当目的进程被内核强迫以某种方式对信号的发送做出反应时,它就接收了信号。进程可以忽略这个信号,终止或者通过执行一个称为*信号处理程序(signal handler)*的用户层函数捕获这个信号。[博主理解: 基本流程为: 进程正在运行中->进程接收到信号->控制传递到信号处理程序->信号处理程序运行->信号处理程序返回到下一条指令。 简单的来说就是当进程接收到一个信号时,会先执行这个信号对应的处理程序,执行完毕之后再返回到进程中下一条指令接着执行] 一个发出而没有被接收的信号叫做待处理信号(pending signal)。在任何时刻,一种类型至多只会有一个待处理信号。如果一个进程有一个类型为k的待处理信号,那么任何接下来发送到这个进程的类型为k的信号都不会排队等待,它们只是被简单的丢弃。一个进程可以有选择性地阻塞接收某种信号。当一种信号被阻塞时,它仍可以被发送,但是产生的待处理信号不会被接收,直到进程取消对这种信号的阻塞。 一个待处理信号最多只能被接收一次。内核为每个进程在pending位向量中维护着待处理信号的集合,而在blocked位向量中维护着被阻塞的信号集合。只要传送了一个类型为k的信号,内核就会设置pending中的第k位,而只要接收了一个类型为k的信号,内核就会清除pending中的第k位。 发送信号Unix系统提供了大量向进程发送信号的机制。所有这些机制都是基于*进程组(process group)*这个概念的。 进程组每个进程都只属于一个进程组,进程组是由一个正整数进程组ID来标识的。 123456789// getpgrp函数: 返回当前进程的进程组ID。函数原型: pid_t getpgrp(void);所属头文件: <unistd.h>返回值: 调用进程的进程组iD// setpgid函数: 改变自己或者其他进程的进程组。它将进程pid的进程组改为pgid。如果pid是0,那么就使用当前进程的pid,如果pgid是0,那么就用pid指定的进程的PID作为进程组的ID。函数原型: int setpgid(pid_t pid, pid_t pgid);所属头文件: <unistd.h>返回值: 若成功则为0,若错误则为-1 用/bin/kill程序发送信号kill程序可以向另外的进程发送任意的信号,比如: 1linux> /bin/kill -9 15213 这条命令会发送信号9(SIGKILL)给进程15213。一个为负的PID会导致信号被发送到进程组PID中的每个进程。比如: 1linux> /bin/kill -9 -15213 这条命令发送一个SIGKILL给进程组15213中的每个进程。 有些shell可能有自己内置的kill命令,所以使用kill的时候最好指定路径。 从键盘发送信号Unix Shell使用*作业(job)*这个抽象概念来表示为对一条命令行求值而创建的进程。在任何时刻,至多只有一个前台作业和0个或多个后台作业。 1linux> ls | sort 会创建一个由两个进程组成的前台作业,这两个进程是通过Unix管道连接起来的: 一个进程运行ls程序,另一个运行sort程序。shell为每个作业创建一个独立的进程组。进程ID通常取自作业中父进程中的一个。 在键盘上输入Ctrl + C会导致内核发送一个SIGINT信号到前台进程组中的每个进程。默认情况下,结果是终止前台作业。类似的,输入Ctrl + Z会发送一个SIGTSTP信号到前台进程组中的每个进程。默认情况下,结果是停止(挂起)前台作业。 用kill函数发送信号进程通过调用kill函数发送信号给其他进程(包括它们自己)。 1234// kill函数: 给指定进程发送信号。函数原型: int kill(pid_t pid, int sig);所属头文件: <signal.h>返回值: 若成功则为0,若错误则为-1 如果pid大于零,那么kill函数发送信号号码sig给进程pid.如果pid等于零,那么kill发送信号sig给调用进程所在进程组中的每个进程,包括调用进程自己。如果pid小于0,kill发送信号sig给进程组 |pid|(pid的绝对值)中的每个进程。 用alarm函数发送信号1234// alarm函数: 进程可以通过alarm函数向它自己发送SIGALRM信号。函数原型: unsigned int alarm(unsigned int secs);所属头文件: <unistd.h>返回值: 前一次闹钟剩余的秒数,若以前没有设定闹钟,则为0 alarm函数安排内核在secs秒后发送一个SIGALRM信号给调用进程。如果secs是零,那么不会调度安排新的闹钟(alarm)。在任何情况下,对alarm的调用都将取消任何*待处理(pending)*闹钟,并且返回任何待处理闹钟在被发送前还剩下的秒数(如果这次对alarm的调用没有取消它的话);如果没有任何待处理的闹钟,就返回零。 接收信号当内核把进程p从内核模式切换到用户模式时(例如: 从系统调用返回或是完成了一次上下文切换),它会检查进程p的未被阻塞的待处理信号的集合(pending &~blocked)。如果这个集合为空(通常情况下),那么内核将控制传递到p的逻辑控制流中的下一条指令。然而,如果集合是非空的,那么内核选择集合中的某个信号k(通常是最小的k),并且强制p接收信号k.收到这个信号会触发进程采取某种行为。一旦进程完成了这个行为,那么控制就传递回p的逻辑控制流中的下一条指令。每个信号都有一个预定义的默认行为,是下面的一种: 进程终止。 进程终止并转储内存。 进程停止(挂起)直到被SIGCONT信号重启。 进程忽略该信号。 12345// signal函数: 修改和信号相关联的默认行为,SIGSTOP和SIGKILL的默认行为是不能修改的。typedef void (*sighandler_t)(int); // sighandler_t代表了一个返回类型为void,有一个int类型参数的函数指针。函数原型: sighandler_t signal(int, signum, sighandler_t handler);所属头文件: <signal.h>返回值: 若成功则为指向前次处理程序的指针,若出错则为SIG_ERR(不设置errno) signal函数可以通过下列三种方法之一来改变和信号signum相关联的行为: 如果handler是SIG_IGN,那么忽略类型为signum的信号。 如果handler是SIG_DFL,那么类型为signum的信号行为恢复为默认行为。 否则,handler就是用户定义的函数的地址,这个函数被称为信号处理程序,只要进程接收到一个类型为signum的信号,就会调用这个程序[博主理解: CSAPP上写的是”调用这个程序”,博主认为改为”调用这个函数更恰当”]。通过把处理程序的地址传递到signal函数从而改变默认行为,这叫做设置信号处理程序(installing the handler)。调用信号处理程序被称为捕获信号,执行信号处理程序被称为处理信号。 当一个进程捕获了一个类型为k的信号时,会调用为信号k设置的处理程序,一个整数参数被设置为k.这个参数允许同一个处理函数捕获不同类型的信号。 信号处理程序可以被其他信号处理程序中断[博主注: 在信号处理程序执行完毕后,控制会返回到被中断的信号处理程序继续执行]。 阻塞和解除阻塞信号Linux提供阻塞信号的隐式和显式的机制: 隐式阻塞机制。内核默认阻塞任何当前处理程序正在处理信号类型的待处理的信号。 显式阻塞机制。应用程序可以使用sigprocmask函数和它的辅助函数,明确的阻塞和解除阻塞选定的信号。 123456789#include <signal.h>int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);int sigemptyset(sigset_t *set);int sigfillset(sigset_t *set);int sigaddset(sigset_t *set, int signum);int sigdelset(sigset_t *set, int signum);// 以上函数的返回值: 如果成功则为0,若出错则为-1int sigismember(const sigset_t *set, int signum); // 返回值: 若signum是set的成员则为1,如果不是则为0,若出错则为-1 sigprocmask函数改变当前阻塞的信号集合。具体的行为依赖于how的值: SIG_BLOCK: 把set中的信号添加到blocked中(blocked=blocked | set)。 SIG_UNBLOCK: 从blocked中删除set中的信号(blocked=blocked &~set)。 SIG_SETMASK: block=set。 如果oldset非空,那么blocked位向量值之前的值保存在oldset中。 未完待续。。。 编写信号处理程序信号处理程序有几个属性使得它们很难推理分析: 1)处理程序与主程序并发运行,共享同样的全局变量,因此可能与主程序和其他处理程序相互干扰;2) 如何以及何时接收信号的规则常常有违人的直觉;3) 不同的系统有不同的信号处理语义。 安全的信号处理保守编写处理程序的原则: G0. 处理程序要尽可能简单。避免麻烦的最好方法是保持处理程序尽可能小和简单。例如,处理程序可能只是简单的设置全局标志并立即返回,所有与接收信号相关的处理都由主程序执行,它周期性地检查(并重置)这个标志。 G1. 在处理程序中只调用异步信号安全的函数。所谓异步信号安全的函数(或简称安全的函数)能够被信号处理程序安全地调用,原因有二: 要么它是可重入的(例如只访问局部变量),要么它不能被信号处理程序中断。[博主注: 可以百度搜索”异步信号安全函数”看看哪些是异步信号安全函数,这里博主就不写了,太多了。] G2. 保存和恢复errno。许多Linux异步信号安全的函数都会在出错返回时设置errno。在处理程序中调用这样的函数可能会干扰主程序中其他依赖于errno的部分。解决方法是在进入处理程序时把errno保存在一个局部变量中,在处理程序返回前恢复它。注意,只有在处理程序要返回时才有此必要。如果处理程序调用_exit终止该进程,那么就不需要这样做了。 G3. 阻塞所有的信号,保护对共享全局数据结构的访问。如果处理程序和主程序或其他处理程序共享一个全局数据结构,那么在访问(读或写)该数据结构时,你的处理程序和主程序应该暂时阻塞所有的信号。这条规则的原因是从主程序访问一个数据结构d通常需要一系列的指令,如果指令序列被访问d的处理程序中断,那么处理程序可能会发现d的状态不一致,得到不可预知的结果。在访问d时展示阻塞信号保证了处理程序不会中断该指令序列。 G4. 用volatile声明全局变量。考虑一个处理程序和一个main函数,它们共享一个全局变量g。处理程序更新g,main周期性地读取g。对于一个优化编译器而言,main中g的值看上去从来没有变化过,因此使用缓存在寄存器中g的副本来满足对g的每次引用是很安全的。如果这样,main函数可能永远都无法看到处理程序更新过的值。可以用volatile类型限定符来定义一个变量,告诉编译器不要缓存这个变量。volatile限定符强迫编译器每次在代码中引用g时,都要从内存中读取g的值。一般来说,和其他所有共享数据结构一样,应该暂时阻塞信号,保护每次对全局变量的访问。 G5. 用sig_atomic_t声明标志。在常见的处理程序设计中,处理程序会写全局标志来记录收到了信号。主程序周期性地读这个标志,响应信号,再清除该标志。对于通过这种方式来共享的标志,C提供一种整型数据类型sig_atomic_t,对它的读和写保证会是原子的(不可中断的),因为可以用一条指令来实现它们: “volatile sig_atomic_t flag;”。因为它们是不可中断的,所以可以安全地读和写sig_atomic_t变量,而不需要暂时阻塞信号。注意,这里对原子性的保证只适用于单个的读和写,不适用于像flag++或flag=flag+10这样的更新,它们可能需要多条指令。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"CSAPP","slug":"blog/CSAPP","permalink":"http://example.com/categories/blog/CSAPP/"}],"tags":[]},{"title":"计算机网络自顶向下方法第一章笔记","slug":"计算机网络自顶向下方法第一章笔记","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/07/09/计算机网络自顶向下方法第一章笔记/","link":"","permalink":"http://example.com/2021/07/09/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E8%87%AA%E9%A1%B6%E5%90%91%E4%B8%8B%E6%96%B9%E6%B3%95%E7%AC%AC%E4%B8%80%E7%AB%A0%E7%AC%94%E8%AE%B0/","excerpt":"","text":"与互联网相连的某些设备,如手机,平板电脑,电视等,称为主机**(host)** 或端系统**(end system)**。 端系统通过通信链路(communication link) 和分组交换机(packet switch) 连接到一起。 链路的传输速率(transmission rate) 以比特/秒(bit/s, 或bps) 度量。 当一台端系统要向另一台端系统发送数据时,发送端系统将数据分段,并为每段加上首部字节。由此形成的信息包用计算机网络的术语来说称为**分组(packet)**。 两种最著名的分组交换机的类型是路由器(router) 和**链路层交换机(link-layer switch)**。 从发送端系统到接收端系统,一个分组所经历的一系列通信链路和分组交换机称为通过该网络的**路径(route或path)**。 未完待续。(因为这破书老是想自己翻页,导致我写笔记效率下降严重。待我的读书板到货之日,便是更新之时)","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"计算机网络","slug":"blog/计算机网络","permalink":"http://example.com/categories/blog/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"}],"tags":[]},{"title":"CSS绘制桃心","slug":"CSS绘制桃心","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2021/07/06/CSS绘制桃心/","link":"","permalink":"http://example.com/2021/07/06/CSS%E7%BB%98%E5%88%B6%E6%A1%83%E5%BF%83/","excerpt":"","text":"先看图吧。那么先说一下具体逻辑,就是先弄一个正方形,然后弄两个直径等于这个正方形边长的圆形,之后一个圆的圆心放在与正方形左下角垂直的地方,一个圆的圆心放在与正方形右上角垂直的地方。那么来看代码吧。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> *{ margin: 0; padding: 0; } body{ display: flex; justify-content: center; align-items: center; height: 100vh; } main{ background-color: red; width: 200px; height: 200px; display: flex; justify-content: center; align-items: center; position: relative; transform: rotate(45deg); } main>div{ position: absolute; border-radius: 50%; width: 100%; height: 100%; background-color: red; } main>div:nth-of-type(1){ transform: translateX(-50%); } main>div:nth-of-type(2) { transform: translateY(-50%); } </style></head><body> <main> <div></div> <div></div> </main></body></html> 因为圆的直径等于正方形的任意一边的边长,所以移动圆心至与正方形某个角垂直的位置只需要移动圆的半径的内容,也就是百分之五十的正方形的任意一边的边长。移动完两个圆后再旋转正方形就OK了。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"前端","slug":"blog/前端","permalink":"http://example.com/categories/blog/%E5%89%8D%E7%AB%AF/"}],"tags":[]},{"title":"CSS正方体嵌套正方体","slug":"CSS正方体嵌套正方体","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/07/02/CSS正方体嵌套正方体/","link":"","permalink":"http://example.com/2021/07/02/CSS%E6%AD%A3%E6%96%B9%E4%BD%93%E5%B5%8C%E5%A5%97%E6%AD%A3%E6%96%B9%E4%BD%93/","excerpt":"","text":"先看效果图。 额,,,虽然这个效果图看着不咋地,但这是因为我的配色的原因,实际上配好色还是很好看的。。。 那么先说都用到了什么操作吧,首先是CSS3,然后。。额,好像没有然后了,我这里只用了CSS3(憋说还有HTML)。 那么先讲一下用到了CSS3中的哪些操作吧,首先呢,用到了透视,然后用到了3d,用到的3d中包括了x, y, z轴的旋转。 接下来讲一下具体的逻辑,因为我会一次性放上所有代码,所以我会一次性讲完所有逻辑。首先我们要弄出外边的正方体,一个正方体有6个面,我们要先做出来这6个面,其中通过旋转可以做出来4个面,分别是上下左右这4个面;通过Z轴操作可以做出来1个面,这个面是后面;剩下的一面是正面,有宽高就行。 之后我们要弄出里面的正方体,说是正方体,实际上这个正方体只有三个面分别是正面,右面和上面。实现这个正方体还是很简单的,只要外部正方体正面,右面和上面各弄出一个正方形,然后通过Z轴移动,拼在一起就行。 那么下面看代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> *{ margin: 0; padding: 0; } body{ height: 100vh; display: flex; justify-content: center; align-items: center; } main{ /* 通过旋转父盒子来看到旋转后的子盒子的样式 */ transform: perspective(900px) rotateX(-30deg) rotateY(-30deg); /* 设置样式为3d */ transform-style: preserve-3d; width: 400px; height: 400px; /* 使子盒子居中 */ display: flex; justify-content: center; align-items: center; position: relative; } main>div{ /* 通过设置绝对定位 使多个盒子在同一位置,以此来保证旋转后的位置 */ position: absolute; width: 200px; height: 200px; /* 子盒子居中 */ display: flex; justify-content: center; align-items: center; } /* 右面 */ main>div:nth-of-type(1) { border: 2px solid silver; /* 设置旋转基点 */ transform-origin: top; /* X轴旋转90度 */ transform: rotateX(-90deg); /* 设置样式为3d,这个功能主要是为了作用到子盒子上 */ transform-style: preserve-3d; background: rgba(255, 165, 79, .4); } main>div:nth-of-type(1) div{ width: 100px; height: 100px; background: skyblue; /* 子盒子Z偏移50像素 */ transform: translateZ(50px); } main>div:nth-of-type(2) { border: 2px solid silver; transform-origin: right; transform: rotateY(-90deg); transform-style: preserve-3d; background: rgba(139, 58, 58, .4); } main>div:nth-of-type(2) div{ background: pink; width: 100px; height: 100px; transform: translateZ(50px); } main>div:nth-of-type(3) { border: 2px solid silver; transform-style: preserve-3d; background: rgba(255, 255, 0, .4); } main>div:nth-of-type(3) div{ height: 100px; width: 100px; background: teal; transform: translateZ(-50px); } main>div:nth-of-type(4) { border: 2px solid silver; transform-origin: left; transform: rotateY(90deg); background: rgba(127, 255, 212, .5); } main>div:nth-of-type(5) { border: 2px solid silver; transform-origin: bottom; transform: rotateX(90deg); background: rgba(253, 245, 230, .8); } main>div:nth-of-type(6) { border: 2px solid silver; transform: translateZ(-200px); background: rgba(187, 255, 255, .5); } </style></head><body> <main> <div><div></div></div> <div><div></div></div> <div><div></div></div> <div></div> <div></div> <div></div> </main> </body></html> 可以看到有些盒子的Z轴偏移量是正数,有些是负数,这很正常,因为某些面旋转之后那个面相当于是反过来的,所以Z轴自然也就反过来了。 然后说一下为什么Z轴偏移量要选50px,这是因为外层盒子的宽高都是200px,200除以4等于50。那么为什么要除以4呢?这是因为如果把一个盒子分成4份,中间两份存放内容,最左和最右两份空白,可以实现居中效果。同理,这里Z轴偏移量设置为50px可以实现居中效果。 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"前端","slug":"blog/前端","permalink":"http://example.com/categories/blog/%E5%89%8D%E7%AB%AF/"}],"tags":[]},{"title":"CSS毛玻璃特效","slug":"CSS毛玻璃特效","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2021/06/29/CSS毛玻璃特效/","link":"","permalink":"http://example.com/2021/06/29/CSS%E6%AF%9B%E7%8E%BB%E7%92%83%E7%89%B9%E6%95%88/","excerpt":"","text":"参考链接: CSS3:毛玻璃效果 –LXEP。 写下这篇博客的时候博主是相当懵比的,因为想不通为什么可以这样实现。但是这不妨碍把代码记下来。 那么我们先看效果图: 下面是实现的代码(图片请自备): 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> body{ background: url("./3_Jewel_8k.jpg") 0/cover fixed; } #main{ margin: auto; width: 700px; height: 700px; } #content{ color: blue;; position: relative; background: rgba(255, 255, 255, .3); width: 100%; height: 100%; font-size: 30px; } #content::after{ content: ''; position: absolute; top: 0; bottom: 0; left: 0; right: 0; background: url("./3_Jewel_8k.jpg") 0/cover fixed; filter: blur(10px); z-index: -1; } </style></head><body> <div id="main"> <div id="content"> This is Content <p>Here are some meaningless words.Although I say they are meaningless, they are meaningful,because they make you know this beautiful box could run beautifully.</p> </div> </div></body></html> 以下所有内容都不建议观看,因为博主的代码虽然写出来了,但是对其的理解不透彻,所以分析中大概率会出现错误,从而误导读者。 也许看完这段代码的你有很多问号,比如为什么不把”::after”设置为rgba格式的背景,然后再加模糊,以及为什么不把”content” ID设置为rbga格式的背景再加模糊等。很遗憾的是,上面提到的两种方法都不行,把”::after”设置为rbga格式再加模糊,模糊的只是rbga格式的背景,透过背景看到的图片是不模糊的。而给”content”设置为rgba格式再加模糊,”content”里的内容也会一并模糊。我代码里写的这个方法是我目前发现的最简单的方法。那么下面开始讲实现的大致原理。 假设我们想让ID为”content”的元素实现模糊效果,首先给content加上”::after”伪元素,然后把这个伪元素”垫在” content下面(实际上就是伪元素在z轴的位置小于content),这个伪元素的背景是一个图片,但是这个图片实现了与”body”元素的背景图的无缝衔接(就是这个实现无缝衔接的方式让我感到迷惑),丝毫看不出这里有一张图片,之后模糊这张图片,就实现了毛玻璃效果。 那么接下来开始讲解代码中的内容,”content”设置了relative定位,目的是防止”:;after”伪元素的absolute定位脱标后占据整个body的空间。”z-index”设置了z轴的位置。”filter: blur(10px);”设置了模糊度,括号中的值越大越模糊。”background: url(“./3_Jewel_8k.jpg”) 0/cover fixed;”中”0/cover/fixed”的涵义是这样的: “background-position-x: 0; background-size: cover; background-attachment: fixed;” 关于”background-size”属性,MDN中是这样描述的: “设置背景图片大小。图片可以保有其原有的尺寸,或者拉伸到新的尺寸,或者在保持其原有比例的同时缩放到元素的可用空间的尺寸。”。 而”background-attachment”属性,在MDN中的描述如下: “决定背景图像的位置是在视口内固定,或者随着包含它的区块滚动。”。 天知道为什么这两个属性凑一起会让元素的背景图无缝衔接body的背景图。 伴随着博主的懵比本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"前端","slug":"blog/前端","permalink":"http://example.com/categories/blog/%E5%89%8D%E7%AB%AF/"}],"tags":[]},{"title":"GRID布局","slug":"GRID布局","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/06/28/GRID布局/","link":"","permalink":"http://example.com/2021/06/28/GRID%E5%B8%83%E5%B1%80/","excerpt":"","text":"本篇博客简略记录一下Grid布局的使用。博主本人为前端菜鸡,不建议想通过本篇博文习得高深知识的朋友观看。 GRID首先介绍一下Grid是什么,因博主本人没文化,只能说这东西是个网格,其具体特性是网格中的每个单元格都可以控制大小,比如在一个3x3的网格中,你可以让某个单元格为2x2,也可以让某个单元格1x2,等。 只说这些难免让人感到懵比,所以下面讲一些使用方法。 创建网格创建一个3*3的网格,代码如下: 12345678910111213141516171819202122232425262728293031323334<html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> article{ width: 300px; height: 300px; display: grid; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 1fr 1fr 1fr; } article div{ border: 1px solid black; } </style></head><body> <article> <div>1</div> <div>2</div> <div>3</div> <div>4</div> <div>5</div> <div>6</div> <div>7</div> <div>8</div> <div>9</div> </article></body></html> 效果如下: 请忽略这透明的效果,这是因为我的compton,和这代码并没有关系。 那么接下来开始代码讲解。 最重要的一点在于”display: grid”这行代码,其作用为将元素设置为网格。 然后说一下”grid-template-columns”的意思,这个是网格的列数,”1fr 1fr 1fr”代表3列,你可以把1fr认为是一个基本大小,就如1px一样,只不过px是用来衡量像素,fr是用来衡量单元格,因此,当”grid-template-columns: 1fr 2fr 1fr”时,第二个单元格的列宽为第一个单元格或第三个单元格的列宽的两倍。 “grid-template-rows”也是同样的计量方法,不过含义从列变成了行。 通过”grid-template-columns: 1fr 1fr 1fr”和”grid-template-rows: 1fr 1fr 1fr”,创建了一个3x3的网格,该网格共有9个单元格,每个单元格的行高和列宽都是1fr。 可以看到,在article元素中,有9个div,不要以为是这9个div撑起了grid,而是9个单元格作为容器,各容纳了一个div。也就是说,div和这个网格的布局没有任何关系,它是在单元格之中,不管单元格中有没有这个div,这个单元格仍然会存在,所以无论有多少个div,只要div的个数不大于9,就不会对布局产生影响。比如article元素中一共有8个div,那么最后一个单元格中不会有任何东西,但是它仍然占据了一个单元格的位置。 操作单元格的大小我们可以通过”grid-column-start”和”grid-column-end”来操作某个单元格所占据的列的个数,也可以通过”grid-row-start”和”grid-row-end”来操作某个单元格所占据的行的个数。 在操作之前,我们需要先知道一件事,在上面的代码中,我们创建了一个3x3的网格,而这个网格,从左到右一共有4条线。这四条线分别是: 第一个单元格的左边框,第一个单元格的右边框与第二个单元格的左边框,第二个单元格的右边框与第三个单元格的左边框,第三个单元格的右边框与第四个单元格的左边框,第四个单元格的右边框。而且这个单元格从上到下一共也是4条线,逻辑与从左到右相同。 不理解这个概念也没关系,看了下面代码就理解了。 这里我要让第一行的第二个单元格成为2x2的单元格,即占据两行两列的单元格。代码如下(该实现方法为方法一): 123456789101112131415161718192021222324252627282930313233343536373839<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> article{ margin: auto; width: 300px; height: 300px; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat (3, 1fr); } article div{ border: 1px solid black; } article div:nth-child(2){ grid-row-start: 1; grid-row-end: 3; grid-column-start: 2; grid-column-end: 4; } </style></head><body> <article> <div>1</div> <div>2</div> <div>3</div> <div>4</div> <div>5</div> <div>6</div> </article></body></html> 效果图如下: 这里我使用了”article div:nth-child(2)”选中了第二个div,然后通过”grid-row-start: 1; grid-row-end: 3;”设置了行高,通过”grid-column-start: 2; grid-column-end: 4;”设置了列宽。其中的”1 3 2 4”这几个参数对应了我上面描述的”4条线”。 理解完了这个之后,我们再来看一下上面我所写的stle标签中的”article”元素选择器中的”repeat”,repeat主要用于手懒,比如你要创建一个n x n的表格,那你就要写n个fr,勤奋的程序员肯定忍受不了这个情况,所以repeat可以避免这种情况的出现。repeat的用法为: repeat(n, u) 其中n代表重复的个数,u代表单位,u可以是1fr也可以是2fr也可以是1px也可以是2px等。在repeat之后还可以跟其他单位,如: “grid-template-rows: repeat(2, 1fr) 1fr”。 接下来我将介绍其他几种实现上面布局的方法,但由于我这篇博客已经写了一个多小时了,所以接下来就不贴图片了,反正样子都一样。 方法二(只改动了”article div:nth-child(2)”中的内容,其他代码与方法一相同): 只设置要改变的单元格方向的对应的end属性,span 2代表该单元格为该方向上两个单元格的大小(如column方向使用这个,代表两个列宽,row使用这个,代表两个行高)。 1234article div:nth-child(2){ grid-row-end: span 2; grid-column-end: span 2;} 方法三(改动了article元素选择器与article div:nth-child(2)):为每一条边设置名字,使用的时候使用边名。 123456789101112131415article{ margin: auto; width: 300px; height: 300px; display: grid; grid-template-columns: [c1-start] 1fr [c1-end c2-start] 1fr [c2-end c3-start] 1fr [c3-end]; grid-template-rows: [r1-start] 1fr [r1-end r2-start] 1fr [r2-end r3-start] 1fr [r3-end];}article div:nth-child(2){ grid-row-start: r1-start; grid-row-end: r2-end; grid-column-start: c2-start; grid-column-end: c3-end;} 方法四: 方法三的操作属实反人类,所以这里推荐方法四,直接指定从第几个单元格开始到第几个单元格结束就完了。 123456789101112131415article{ margin: auto; width: 300px; height: 300px; display: grid; grid-template-columns: repeat(3, [c-start] 1fr [c-end]); grid-template-rows: repeat(3, [r-start] 1fr [r-end]);}article div:nth-child(2){ grid-row-start: r-start 1; grid-row-end: r-end 2; grid-column-start: c-start 2; grid-column-end: c-end 3;} 简写每次都写start和end实在是麻烦,所以可以用简写来代替。 比如: 1234article div:nth-child(2){ grid-row: 1/3; grid-column: 2/4;} “/“号左边代表开始位置,右边代表结束位置。当然,左右也不一定非写数字不可,”c-row 1 / c-row / 2”等也是可以的。 间隙直接看代码吧,博主困的要死了,不上图了: 123456789101112131415161718192021222324252627282930313233343536373839<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> article{ margin: auto; width: 300px; height: 300px; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr); row-gap: 10px; column-gap: 10px; } article div{ border: 1px solid black; } article div:nth-child(2){ grid-row: 1/3; grid-column: 2/4; } </style></head><body> <article> <div>1</div> <div>2</div> <div>3</div> <div>4</div> <div>5</div> <div>6</div> </article></body></html> 这里使用了”row-gap”来指定行于行之间的间隙,使用”column-gap”指定列与列之间的间隙。也可以直接使用”gap: 1px 2px”类似的方式来指定间隙,第一个参数是行的间隙,第二个参数是列的间隙。 好了,本篇完(刷牙洗脸睡觉辽)。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"前端","slug":"blog/前端","permalink":"http://example.com/categories/blog/%E5%89%8D%E7%AB%AF/"}],"tags":[]},{"title":"XHR跨域","slug":"XHR跨域","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/06/18/XHR跨域/","link":"","permalink":"http://example.com/2021/06/18/XHR%E8%B7%A8%E5%9F%9F/","excerpt":"","text":"之前虽然知道”跨域”这个词,也知道怎么给服务端加上”跨域”这一功能,但是一直不明白跨域是怎么回事,今天看了红宝书终于整明白了。 先说说请求的大概流程吧: 前端发送请求->后端收到请求->后端发送响应->前端收到响应。如果不跨域的情况下,前三步都没有问题,但是第四步会出现问题,导致第四步出现问题的原因是服务器的响应头中没有”Access-Control-Allow-Origin”或者”Access-Control-Allow-Origin”的信息与浏览器当前页面的Origin对不上。先说说Origin是什么,Origin是一个请求头,其中包含了发送请求的页面的源(如:协议,网址,端口),比如我在浏览器打开”http://localhost:5000/page"页面时发送一个请求,那么这个请求中的Origin值为"http://localhost:5000"。知道了这一点后,我们再把目光转移到前端接收到后端响应这一过程,这时后端的响应已经发出去了,浏览器会根据响应头中的”Access-Control-Allow-Origin”中的值是否等于当前页面的Origin做出判断,如果”Access-Control-Allow-Origin”等于当前页面的Origin,那么会成功响应,这时就可以根据xhr对象的responseText方法来获取响应体了;如果”Access-Control-Allow-Origin”不等于当前页面的Origin,那么浏览器就会报错。 假设当前页面为”http://localhost:5500/try.html",需要请求的后端接口为:"http://localhost:5000/test",那么XHR请求的代码应该如下: 1234567891011121314function sendXHR(){ let xhr = new XMLHttpRequest(); // 接收完响应后在控制台打印响应体 xhr.onreadystatechange = function() { if(xhr.readyState === 4) { console.log(xhr.responseText); } } xhr.open("get", "http://localhost:5000/test", true); xhr.send(null);} 这里我用的python框架flask做的后端,后端的代码为: [email protected]('/test')def test(): with open("./log/share.log", "r") as f: data = f.read() res = make_response(data) // 设置响应头 res.headers["Access-Control-Allow-Origin"] = "http://localhost:5500" return res 这样前端就能成功接收到响应了。本篇完。真没想到原来是浏览器的锅。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"JavaScript","slug":"blog/JavaScript","permalink":"http://example.com/categories/blog/JavaScript/"}],"tags":[]},{"title":"elementui表格重新渲染","slug":"elementui表格重新渲染","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/06/11/elementui表格重新渲染/","link":"","permalink":"http://example.com/2021/06/11/elementui%E8%A1%A8%E6%A0%BC%E9%87%8D%E6%96%B0%E6%B8%B2%E6%9F%93/","excerpt":"","text":"参考自表格渲染异常。element UI中的表格在data更改之后其中的内容不会自动重新渲染,所以我们需要手动让它渲染。 vue中el-table代码如下: 123456789101112<el-table :data="pro_data" v-if="showTable"><el-table-column prop="" label="操作"> <template slot-scope="scope"> <el-button type="primary" plain v-show="scope.act === 0">运行</el-button> <el-button type="danger" plain v-show="scope.act === 1">暂停</el-button> </template></el-table-column></el-table> 刷新表格的代码: 123456refreshTable:function(){ let that = this; that.showTable = false; that.$nextTick(()=>that.showTable = true);} 只要调用refreshTable函数,就可以重新渲染表格了。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[]},{"title":"vue注册全局axios","slug":"vue注册全局axios","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2021/06/09/vue注册全局axios/","link":"","permalink":"http://example.com/2021/06/09/vue%E6%B3%A8%E5%86%8C%E5%85%A8%E5%B1%80axios/","excerpt":"","text":"详情请看axios中文网。首先安装这俩模块: 12npm install axios --savenpm install vue-axios --save 然后在main里加入如下代码: 12345import Vue from 'vue'import axios from 'axios'import VueAxios from 'vue-axios'Vue.use(VueAxios, axios) 注意import的顺序。之后就可以在.vue文件中引用axios了: 1234this.axios({ url: '127.0.0.1:8000', method: 'get'}) 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[]},{"title":"解决vue有间隙","slug":"解决vue有间隙","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2021/06/09/解决vue有间隙/","link":"","permalink":"http://example.com/2021/06/09/%E8%A7%A3%E5%86%B3vue%E6%9C%89%E9%97%B4%E9%9A%99/","excerpt":"","text":"当你新建一个vue-cli项目时,你可能会发现id为app的盒子左侧和上侧有间隙。解决这个问题的方法如下:打开vue/public/index.html,在head里面加入这样一块代码: 1234567<style> body{ margin: 0; padding: 0; }</style> 然后就可以去掉app盒子与body之间的间隙了。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[]},{"title":"Python3 日志","slug":"Python3日志","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/06/07/Python3日志/","link":"","permalink":"http://example.com/2021/06/07/Python3%E6%97%A5%E5%BF%97/","excerpt":"","text":"本篇博文主要是为了方便博主再次使用python3 logging模块时能够快速上手,故此讲解的不会很详细。若是第一次接触这个模块,建议看这些内容: 日志 HOWTO, python3 logging模块使用说明, 第一个链接是文档,可以快速入门,第二个链接是某位博主写的博客,质量不错。 那么接下来就开始讲解logging模块的使用。 日志等级分为4个等级,CRITICAL, ERROR, WARNING, INFO, DEBUG。简单的使用方法如下: 1234567import logginglogging.info("Info")logging.warning("warning")logging.error("error")logging.critical("critical") 输出如下: 12345/home/fire/PyVenv/web_env/bin/python3.9 /home/fire/work_project/test.pyWARNING:root:warningERROR:root:errorCRITICAL:root:critical 可以看到,info的内容并没有输出出来,这是因为被称为”日志级别”的东西限制了info的输出。日志级别是有等级的,默认日志级别是warning,因为info的级别小于warning,所以就没有输出出来。日志级别等级: CRITICAL > ERROR > WARNING > INFO > DEBUG。 好,基础知识已经讲完了,那么下面我们来实现一个根据文件大小自动截断日志的功能: 12345678910111213141516171819202122232425import loggingfrom logging import handlers# ulog: 用于记录用户的日志# getLogger返回一个Logger对象,关于日志的操作通过它来执行ulog = logging.getLogger("root.user")# 设置日志级别ulog.setLevel(logging.INFO)# 配置ulog# 可以把handler看成一个工具,它可以改变Logger对象的行为# handler有很多种,这里使用的当文件大小达到限制后就自动截断日志并生成新的文件的handler# 文件名: user.log, 打开方式: a, 最大字节数: 1024 * 1024 Bytes, 日志根据时间依次存放在user.log.1, user.log.2, user.log.3。uhandler = handlers.RotatingFileHandler(filename="user.log", mode='a', maxBytes=1024 * 1024, backupCount=3)# 设置日志的格式,详细内容建议参考文档uformat = logging.Formatter(fmt="%(asctime)s %(levelname)s %(filename)s %(funcName)s:line %(lineno)d %(message)s", datefmt='%Y-%m-%d %H:%M:%S')# 设置handler的格式uhandler.setFormatter(uformat)# 添加handlerulog.addHandler(uhandler)ulog.info("Hey!")ulog.info("Hello, %s.", "Jack") 执行上面的程序,结果如下: 1232021-06-07 20:12:15 INFO test.py <module>:line 45 Hey!2021-06-07 20:12:15 INFO test.py <module>:line 46 Hello, Jack. 关于backupCount这个参数,也许你会感到一脸懵,所以我贴心的复制了一份RotatingFileHandler的注释: 12345678910111213141516171819202122232425262728class RotatingFileHandler(BaseRotatingHandler): """ Handler for logging to a set of files, which switches from one file to the next when the current file reaches a certain size. """ def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False, errors=None): """ Open the specified file and use it as the stream for logging. By default, the file grows indefinitely. You can specify particular values of maxBytes and backupCount to allow the file to rollover at a predetermined size. Rollover occurs whenever the current log file is nearly maxBytes in length. If backupCount is >= 1, the system will successively create new files with the same pathname as the base file, but with extensions ".1", ".2" etc. appended to it. For example, with a backupCount of 5 and a base file name of "app.log", you would get "app.log", "app.log.1", "app.log.2", ... through to "app.log.5". The file being written to is always "app.log" - when it gets filled up, it is closed and renamed to "app.log.1", and if files "app.log.1", "app.log.2" etc. exist, then they are renamed to "app.log.2", "app.log.3" etc. respectively. If maxBytes is zero, rollover never occurs. """ 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"pymysql查询字符串日期","slug":"pymysql查询字符串日期","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2021/05/30/pymysql查询字符串日期/","link":"","permalink":"http://example.com/2021/05/30/pymysql%E6%9F%A5%E8%AF%A2%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%97%A5%E6%9C%9F/","excerpt":"","text":"pymysql查询出的日期默认是datetime.datetime类型,有时候十分的不方便,所以这篇博客介绍直接查询出字符串类型日期的方法。直接修改sql语句就行。例如: 1QUERY_FILE = "select id, vs, dname, fname, fpath, uid, CAST(ct as char) as createdate, CAST(ut as char) as updatetime from files where vs=%s&&dname=%s" 这里ct和up都是日期类型的字段。OK,这篇博文的内容虽少,但是实用。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"禁用鼠标的脚本","slug":"禁用鼠标的脚本","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/05/24/禁用鼠标的脚本/","link":"","permalink":"http://example.com/2021/05/24/%E7%A6%81%E7%94%A8%E9%BC%A0%E6%A0%87%E7%9A%84%E8%84%9A%E6%9C%AC/","excerpt":"","text":"本脚本背景交代: 有些时候,想要在桌前看实体书,嫌屏幕开着碍事,用xset关闭屏幕后,书一翻页就有可能触发鼠标移动,导致屏幕自动点亮。为了开开心心的看书而不被鼠标移动自动点亮显示器影响,故此有了该脚本。这次主要用到了python3和shell。说来惭愧,本来应该全部用shell写的,但是我太菜了,不会用awk,故此改用python3的re模块代替awk来进行解析。 在开始用禁用鼠标脚本前,必须要先使用xinput –list看看自己想要禁用哪个设备: 123456789101112131415161718192021> xinput --list⎡ Virtual core pointer id=2 [master pointer (3)]⎜ ↳ Virtual core XTEST pointer id=4 [slave pointer (2)]⎜ ↳ SONiX USB DEVICE Keyboard id=13 [slave pointer (2)]⎜ ↳ HOLTEK USB Gaming Mouse Keyboard id=15 [slave pointer (2)]⎜ ↳ AlpsPS/2 ALPS GlidePoint id=17 [slave pointer (2)]⎜ ↳ HOLTEK USB Gaming Mouse id=14 [slave pointer (2)]⎣ Virtual core keyboard id=3 [master keyboard (2)] ↳ Virtual core XTEST keyboard id=5 [slave keyboard (3)] ↳ Power Button id=6 [slave keyboard (3)] ↳ Video Bus id=7 [slave keyboard (3)] ↳ Video Bus id=8 [slave keyboard (3)] ↳ Power Button id=9 [slave keyboard (3)] ↳ Sleep Button id=10 [slave keyboard (3)] ↳ USB HD WEBCAM: USB HD WEBCAM id=11 [slave keyboard (3)] ↳ SONiX USB DEVICE id=12 [slave keyboard (3)] ↳ AT Translated Set 2 keyboard id=16 [slave keyboard (3)] ↳ SONiX USB DEVICE Keyboard id=18 [slave keyboard (3)] ↳ HOLTEK USB Gaming Mouse Keyboard id=19 [slave keyboard (3)] 这个id号就是设备的id号,我们后面要通过这个id来确定要对哪个设备进行操作。在这里,我的鼠标的id号是14,即”HOLTEK USB Gaming Mouse”,把它记下来,待会儿写脚本的时候会用到。关于为什么不记id而是记设备名的原因: 因为把设备换插USB口会导致id号变化(有时候插入新设备也会导致已插入设备的id号变化),所以我们要记设备名字。 那么既然我们已经知道了设备名字,那么接下来要通过名字匹配到对应的id号来禁用设备: 12345678910111213141516171819# lock.pyimport reimport sysimport os# 从标准输入读取信息for line in sys.stdin: mouse_id = None # 这里要把正则表达式换成匹配你要禁用的设备的id的正则表达式 rst = re.search("HOLTEK USB Gaming Mouse\\s+id=(\\d+)", line, re.S) # 如果匹配到设备的id了,则禁用该设备 if rst: mouse_id = rst.groups()[0] # 禁用设备的命令 exec_str = "xinput -set-prop " + mouse_id + ' "Device Enabled" 0' # 禁用设备 os.system(exec_str) break 那么接下来我们可以通过sh脚本来把xinput的信息传入到py脚本里: 123# lock_mouse.sh# 路径是python文件的路径xinput --list | python3 /home/fire/toolSet/mySh/mouseSh/lock.py 然后当我们想禁用鼠标的时候,直接执行这个脚本就好了。当然,有禁用鼠标的脚本当然还得有解禁用的脚本,不然就麻烦了,代码如下: 1234567891011121314151617181920# unlock.pyimport reimport sysimport os# 从标准输入读取信息for line in sys.stdin: mouse_id = None # 这里要把正则表达式换成匹配你要禁用的设备的id的正则表达式 rst = re.search("HOLTEK USB Gaming Mouse\\s+id=(\\d+)", line, re.S) # 如果匹配到设备的id了,则禁用该设备 if rst: mouse_id = rst.groups()[0] # 禁用设备的命令 exec_str = "xinput -set-prop " + mouse_id + ' "Device Enabled" 1' # 禁用设备 os.system(exec_str) break sh脚本代码如下: 12# unlock_mouse.shxinput --list | python3 /home/fire/toolSet/mySh/mouseSh/unlock.py 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[]},{"title":"使元素垂直水平居中的三种方法","slug":"使元素垂直水平居中的三种方法","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/05/21/使元素垂直水平居中的三种方法/","link":"","permalink":"http://example.com/2021/05/21/%E4%BD%BF%E5%85%83%E7%B4%A0%E5%9E%82%E7%9B%B4%E6%B0%B4%E5%B9%B3%E5%B1%85%E4%B8%AD%E7%9A%84%E4%B8%89%E7%A7%8D%E6%96%B9%E6%B3%95/","excerpt":"","text":"之前一直只知道如果要使div居中,需要添加宽和高并且margin 0 auto,今天看大佬的css3教程,发现了比较妙的方法。 利用Flex来使元素垂直水平居中这个方法仅适用于父元素里只有一个子元素的情况(如果子元素是绝对定位的当我没说)。代码如下:html: 12345678910111213141516<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="./test.css"></head><body> <main> <div> </div> </main></body></html> css: 1234567891011121314151617181920main{ width: 500px; height: 500px; display: flex; /* 水平居中 */ justify-content: center; /* 垂直居中 */ align-items: center;}div{ height: 100px; width: 100px;}div:nth-child(1){ background-color: #fab1a0;} 利用transform属性来使元素垂直水平居中这里父元素和子元素都使用了绝对定位。但是其实父元素不用绝对定位也可以。代码如下: 12345678910111213141516<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="./test.css"></head><body> <main> <div> </div> </main></body></html> css: 12345678910111213141516171819202122232425main{ position: absolute; top: 50%; left: 50%; width: 500px; height: 500px; // 第一个参数是相对于x轴位移的数据,第二个参数是相对于y轴位移的数据 transform: translate(-50%, -50%); border: 1px solid black;}div{ height: 100px; width: 100px; position: absolute; top: 50%; left: 50%; // 第一个参数是相对于x轴位移的数据,第二个参数是相对于y轴位移的数据。位移的百分比是相对于该元素自身的。 transform: translate(-50%, -50%);}div:nth-child(1){ background-color: #fab1a0;} 手动计算偏移量这个方法有点不太方便,因为你要手动计算出偏移量,所以不是很推荐。那么代码如下: 12345678910111213141516<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="./test.css"></head><body> <main> <div> </div> </main></body></html> css: 1234567891011121314151617181920212223242526main{ position: absolute; top: 50%; left: 50%; width: 500px; height: 500px; margin-left: -250px; margin-top: -250px; border: 1px solid black;}div{ height: 100px; width: 100px; position: absolute; top: 50%; left: 50%; margin-left: -50px; margin-top: -50px;}div:nth-child(1){ background-color: #fab1a0;} 那么本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"前端","slug":"blog/前端","permalink":"http://example.com/categories/blog/%E5%89%8D%E7%AB%AF/"}],"tags":[]},{"title":"xpath获取多个class属性","slug":"xpath获取多个class属性","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2021/05/18/xpath获取多个class属性/","link":"","permalink":"http://example.com/2021/05/18/xpath%E8%8E%B7%E5%8F%96%E5%A4%9A%E4%B8%AAclass%E5%B1%9E%E6%80%A7/","excerpt":"","text":"参考自Xpath里如何定义包含一个或多个class属性。如果某个元素具有多个class属性,那么使用以下代码无法获取到该元素: 1234# 元素: <div class="a b">111</a>tree.xpath("//div[@class='a b']")# 这样也不行tree.xpath("//div[@class='a']") 应该使用contains来获取: 1tree.xpath('//div[@contains(@class,"a")]') 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"XHR","slug":"XHR","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/05/17/XHR/","link":"","permalink":"http://example.com/2021/05/17/XHR/","excerpt":"","text":"提示: 本篇博文内容全部取自红宝书,博主只是记录一下使用方法,以下博文中的内容几乎全部都可以在红宝书中找到。所有现代浏览器都通过XMLHttpRequest构造函数原生支持XHR对象: 1let xhr = new XMLHttpRequest(); 使用XHR对象首先要调用open()方法,这个方法接收三个参数: 请求类型(“get”, “post”等),请求URL,以及表示请求是否异步的布尔值(值为true则异步,为false则同步)。下面是一个例子: 1xhr.open("get", "127.0.0.1:8000/index", false); 这行代码就可以向”127.0.0.1:8000/index”发送一个同步的GET请求。但是不要急,仅仅调用open()并不会立即发起请求,它只是为发送请求做准备。只能访问同源URL,也就是域名相同,端口相同,协议相同。如果请求的URL与发送请求的页面在任何方面有所不同,则会抛出安全错误。 要发送定义好的请求,必须像下面这样调用send()方法。 12xhr.open("get", "http://127.0.0.1:8000/api/test", false);xhr.send(null); send()方法接收一个参数,是作为请求体发送的数据。如果不需要发送请求体,则必须传null,因为这个参数在某些浏览器中是必需的。调用send()之后,请求就会发送到服务器。 因为这个请求是同步的,所以JavaScript代码会等待服务器响应之后再继续执行。收到响应后,XHR对象的以下属性会被填充上数据: 1234responseText: 作为响应体返回的文本responseXML: 如果响应的内容类型是"text/xml"或"application/xml",那就是包含响应数据的XML DOM文档。status: 响应的HTTP状态。statusText: 响应的HTTP状态描述 一般来说,status为2xx表示成功,此时responseText或responseXML(如果内容类型正确)属性中会有内容。statusText在跨浏览器的情况下不可靠,一般不建议使用。responseXML对于非XML数据是null。XHR对象有一个readyState属性,表示当前处在请求/响应过程的哪个阶段。这个属性有如下可能的值: 123450: 未初始化(Uninitialized)。尚未调用open()方法。1: 已打开(Open)。已调用open()方法,尚未调用send()方法。2: 已发送(Sent)。已调用send()方法,尚未收到响应。3: 接收中(Receiving)。已经收到部分响应。4: 完成(Complete)。已经收到所有响应,可以使用了。 每次readyState从一个值变成另一个值,都会触发readystatechange事件,可以借此机会检查readyState的值。为保证跨浏览器兼容,onreadystatechange事件处理程序应该在调用open()之前赋值。 在收到响应之前如果想取消异步请求,可以调用abort()方法: 1xhr.abort(); 调用这个方法后,XHR对象会停止触发事件,并阻止访问这个对象上任何与响应相关的属性。中断请求后,应该取消对XHR对象的引用。由于内存问题,不推荐重用XHR对象。 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"JavaScript","slug":"blog/JavaScript","permalink":"http://example.com/categories/blog/JavaScript/"}],"tags":[]},{"title":"Canvas绘制2D图形","slug":"canvas2d","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2021/05/12/canvas2d/","link":"","permalink":"http://example.com/2021/05/12/canvas2d/","excerpt":"","text":"我先把整体需要明白的内容都放在最上面这一行。创建canvas元素至少要设置宽和高。2D绘图上下文提供了绘制方法,包括矩形,弧形和路径。2D上下文的坐标原点(0, 0)在canvas元素的左上角。所有坐标值都相对于该点计算。因为x坐标向右增长,y坐标向下增长。默认情况下,width和height表示两个方向上像素的最大值。fill和stroke是两种绘图的方式,可以简单理解为fill为填充绘图,stroke为描边绘图。那么下面是一些常用的方法。canvas.getContext(contextId, options); 这个方法十分的重要,其作用是获取上下文。在2d绘图中参数一般只填写”2d”,如:canvas.getContext(“2d”);canvas.toDataURL(MIMEType); 这个方法用于导出canvas元素上的图像。这个方法接受一个参数: 要生成图像的MIME类型(与用来创建图形的上下文无关)。context.fillStyle = “Style”; context为canvas.getContext(“2d”)得到的返回值。这个属性用于设置fill的样式。样式可以是CSS支持的任意格式: 名称,十六进制代码,rgb,rgba,hsl或hsla。如:canvas.fillStyle = “#ffffff”或canvas.fillStyle = “rgb(255, 255, 255)”context.strokeStyle = “Style”; 这个属性用于设置stroke的样式。其与fillStyle一样,也支持CSS的任意格式。 绘制矩形context.fillRect(x, y, width, height); 这个方法用于绘制fill矩形。从(x, y)坐标点开始,绘制一个宽width,高height的矩形。context.strokeRect(x, y, width, height); 这个方法用于绘制stroke矩形。从(x, y)坐标点开始,绘制一个宽width,高height的矩形。context.clearRect(x, y, width, height); 这个方法用于擦除一片矩形区域,从(x, y)坐标点开始, 擦除一片宽width, 高height的矩形空间。context.lineWidth = width; 这个属性用于控制描边宽度,它可以是任意整数值。 绘制路径context.beginPath(); 这个方法表示要开始绘制新路径,在开始绘制路径之前必须要调用这个方法。context.fill(); 用fill的形式绘制路径。context.stroke(); 用stroke的形式绘制路径。context.arc(x, y, radius, startAngle, endAngle, counterclockwise); 这个方法用于绘制弧线。它以(x, y)为圆心,以radius为半径,以startAngle为起始角度,以endAngle为结束角度绘制一条弧线。couterclockwise接受一个布尔值,表示是顺时针绘制还是逆时针绘制。默认为顺时针。context.arcTo(x1, y1, x2, y2, radius); 以给定半径radius,经由(x1, y1)绘制一条从上到(x2, y2)的弧线。context.bezierCurveTo(c1x, c1y, c2x, c2y, x, y); 以(c1x, c1y)和(c2x, c2y)为控制点,绘制一条从上一点到(x, y)的弧线(三次贝赛尔曲线)。context.lineTo(x, y); 绘制一条从上一点到(x, y)的直线。context.moveTo(x, y); 不绘制线条,只把绘制光标移动到(x, y)。context.quadraticCurveTo(cx, cy, x, y); 以(cx, cy)为控制点,绘制一条从上一点到(x, y)的弧线(二次贝赛尔曲线)。context.rect(x, y, width, height); 以给定宽度和高度在坐标点(x, y)绘制一个矩形。这个方法与strokeRect()和fillRect()的区别在于,它创建的是一条路径,而不是独立的图形。context.closePath(); 绘制一条返回起点的线。context.isPointInPath(x, y); 这个方法用于确定指定的点是否在路径上,可以在关闭路径前随时调用。context.clip() 基于已有路径创建一个新剪切区域。 绘制文本context.fillText(String, x, y, maxWidth); 绘制fill样式的文本,将其绘制在坐标点(x, y)。maxWidth为最大宽度。context.strokeText(String, x, y, maxWidth); 绘制stroke样式的文本,将其绘制在坐标点(x, y)。maxWidth为最大宽度。context.font = “Style”; 以CSS语法指定的字体样式,大小,字体族等,比如”10px Arial”。context.textAlign = “start | end | left | right | center”; 指定文本的对其方式。如果该属性的值是”start”,那么x坐标在从左到右书写的语言中表示文本的左侧坐标,而end会让x坐标在从左到右书写的语言中表示文本的右侧坐标。context.textBaseline = “top | hanging | middle | alphabetic | ideographic | bottom”; 指定文本的基线。设置”top”意味着y轴表示文本顶部,”bottom”表示文本底部。context.measureText(String); 该方法使用font, textAlign和textBaseline属性当前的值计算绘制指定文本后的大小。 变换context.rotate(angle); 围绕原点把图像旋转angle弧度。context.scale(scaleX, scaleY); 通过在x轴乘以scaleX,在y轴乘以scaleY来缩放图像。scaleX和scaleY的默认值都是1.0。context.translate(x, y); 把原点移动到(x, y)。执行这个操作后,坐标(0, 0)就会变成(x, y)。context.transform(m1_1, m1_2, m2_1, m2_2, dx, dy); 通过矩阵乘法直接修改矩阵。context.save(); 保存当前属性和变换状态到暂存栈。context.restore(); 从暂存栈中取出并恢复之前保存的设置。 绘制图像img参数可以是HTML的img元素,也可以是另一个canvas元素。另外有时候会出现img为HTML的img元素时,没有绘制出来图片的情况。出现这个问题的原因是这样的: 图片还没有加载到img元素,canvas就把这个元素给画上去了,所以画了个寂寞。。。这种情况下只需要等img加载完成后再绘制图片就好了,比如使用img.onload等方式来等待其加载完成。context.drawImage(img, x, y); 将img绘制到(x, y)。context.drawImage(img, x, y, width, height); 将img绘制到(x, y),其宽度为width,高度为height。context.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight, targetX, targetY, targetWidth, targetHeight); 从img的(sourceX, sourceY)开始,截取宽sourceWidth,高sourceHeight大小的矩形,将其绘制到(targetX, targetY),其宽度为targetWidth,高度为targetHeight。 阴影context.shadowColor = “color”; CSS颜色值,表示要绘制的阴影颜色,默认为黑色。context.shadowOffsetX = value; 阴影相对于形状或路径的x坐标的偏移量,默认为0。context.shadowOffsetY = value; 阴影相对于形状或路径的Y坐标的偏移量,默认为0。context.shadowBlur = value; 表示阴影的模糊量。默认值为0, 表示不模糊。 渐变分为线性渐变和径向渐变。 线性渐变context.createLinearGradient(startX, startY, endX, endY); 该方法会返回一个CanvasGradient实例,我们如果要对这个渐变做出改变,应该操作返回的这个CanvasGradient。这个方法接收4个参数,起点x, 起点y, 终点x, 终点y。该方法会根据这些参数创建一个画布,绘制的图像若是超出了该画布,则不会绘制出渐变效果。CanvasGradient.addColorStop(pos, String); 为渐变指定色标。这个方法接收两个参数,第一个参数是色标的位置,其范围为0~1。第二个参数是CSS颜色字符串。所以在使用的时候,一般都要调用这个方法两次,用于指定渐变的色标。 使用方法如下: 12345let linearGradient = context.createLinearGradient(0, 0, 30, 30);linearGradient.addColorStop(0, "white");linearGradient.addColorStop(1, "black");context.fillStyle = linearGradient;context.fillRect(0, 0, 20, 20); 为了让渐变覆盖整个矩形,而不只是其中一部分,两者的坐标必须搭配合适。如果矩形没有绘制到渐变的范围内,则只会显示部分渐变。 径向渐变context.createRadialGradient(r1x, r1y, r1r, r2x, r2y, r2r); 该方法接收6个参数,分别对应两个圆形圆心的坐标和半径。前三个参数指定起点圆形中心的x, y坐标和半径,后三个参数指定终点圆形中心的x,y坐标和半径。径向渐变使用方法与线性渐变一样。JS示例: 12345let gradient = context.createRadialGradient(100, 100, 30, 100, 100, 70);gradient.addColorStop(0, "white");gradient.addColorStop(1, "black");context.fillStyle = gradient;context.fillRect(30, 30, 140, 140); 图像数据主要是对原始图像进行操作。context.getImageData(x, y, width, height); 该方法可以获取原始图像数据。这个方法接受四个参数: 要取得数据中第一个像素的左上角坐标(x, y)和要取得的像素的宽度width和高度height。该方法返回一个ImageData实例。ImageData: 每个ImageData都包含3个属性: width. height和data。其中data属性是包含图像的原始像素信息的数组。每个像素在data数组中都由4个值表示,分别代表红,绿,蓝和透明值。也就是说,第一个像素在数组中的位置为data[0], data[1], data[2], data[3],而这四个值分别对应了RGBA中的Red,Green,Blue和Alpha。这个数组中的每个值都在0~255范围内(包括0和255)。context.putImageData(ImageData, x, y); 该方法将ImageData对象绘制到画布上。下面代码是红宝书上实现的一个灰阶过滤器。 1234567891011121314151617let imgData = context.getImageData(0, 0, 300, 300);let data = imgData.data;let len = data.length;let red, blue, green, average, alpha;for(let i = 0; i < len; i+=4){ red = data[i]; green = data[i + 1]; blue = data[i + 2]; alpha = data[i + 3]; average = Math.floor((red + green + blue) / 3); data[i] = average; data[i + 1] = average; data[i + 2] = average;}imgData.data = data;context.putImageData(imgData, 0, 0); 合成2D上下文中绘制的所有内容都会应用两个属性: globalAlpha和globalCompositionOperation。其中globalAlpha属性是一个范围在0~1的值(包括0和1),用于指定所有绘制内容的透明度,默认值为0。如果所有后来的绘制都需要使用同样的透明度,那么可以将globalApha设置为适当的值,执行绘制,然后再把globalAlpha设置为0。globalCompositionOperattion属性表示新绘制的形状如何与上下文中已有的形状融合。这个属性是一个字符串,可以取以下值。source-over: 默认值,新图形绘制在原有图形上面。source-in: 新图形只绘制出与原有图形重叠的部分,画布上其余部分全部透明。source-out: 新图形只绘制出不与原有图形重叠的部分,画布上其余部分全部透明。source-atop: 新图形只绘制出与原有图形重叠的部分,原有图形不受影响。destination-over: 新图形绘制在原有图形下面,重叠部分只有原图形透明像素下的部分可见。destination-in: 新图形绘制在原有图形下面,画布上只剩下二者重叠的部分,其余部分完全透明。destination-out: 新图形与原有图形重叠的部分完全透明,原图形其余部分不受影响。destination-atop: 新图形绘制在原有图形的下面,原有图形与新图形不重叠的部分完全透明。lighter: 新图形与原有图形重叠部分的像素值相加,使该部分变亮。copy: 新图形将擦除并完全取代原有图形。xor: 新图形与原有图形重叠部分的像素执行”异或”计算。不同浏览器在实现这些选项时可能存在差异。 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"JavaScript","slug":"blog/JavaScript","permalink":"http://example.com/categories/blog/JavaScript/"}],"tags":[]},{"title":"JS添加右键菜单","slug":"JS添加右键菜单","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/05/10/JS添加右键菜单/","link":"","permalink":"http://example.com/2021/05/10/JS%E6%B7%BB%E5%8A%A0%E5%8F%B3%E9%94%AE%E8%8F%9C%E5%8D%95/","excerpt":"","text":"先看效果图(我没有做美化,简陋了亿点点)。然后我先讲解一下大概的逻辑: 在用户右键时,获取其右键的位置,然后在用户右键的位置展示出右键菜单。在用户左键时,关闭右键菜单。在用户右键的位置展示出右键菜单这一步很简单,只需要添加一个绝对定位的盒子,然后将这个盒子移动到右键的位置即可。那么请看代码: 123456789101112<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> <script src="./test.js"></script></body></html> 这是js代码: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465// test.js// 创建右键菜单盒子let rightMenuDiv = document.createElement("div");// 计数,用于关闭右键菜单let rightMenuCount = 0;// 设置右键菜单样式rightMenuDiv.style.backgroundColor = "red";rightMenuDiv.style.position = "absolute";// 防止点击右键菜单中的内容的时候冒泡,导致关闭右键菜单rightMenuDiv.addEventListener("mousedown", (event)=>{event.stopPropagation();})// 给右键菜单添加内容(子节点)let rightMenuChildLink = document.createElement("a");rightMenuChildLink.appendChild(document.createTextNode("www.gray-ice.com"));rightMenuChildLink.setAttribute("href", "https://www.gray-ice.com");rightMenuDiv.appendChild(rightMenuChildLink);// 展示右键菜单function showRightMenu(x, y){ // 因为右键菜单有postion: absolute属性,已经脱标了,只要父元素没有定位,添加到哪都行,我这里是随便添加到底部了。 document.body.appendChild(rightMenuDiv); // 调整右键菜单的位置,将其至于当前鼠标所在的位置上。 rightMenuDiv.style.left = String(x) + "px"; rightMenuDiv.style.top = String(y) + "px";}// 这里利用了事件冒泡,无论在哪个节点上点击的右键,最终都会冒泡到document上document.addEventListener("contextmenu", function(event){ console.log(event); // 避免触发浏览器的右键菜单 event.preventDefault(); // 显示自定义的右键菜单 showRightMenu(event.pageX, event.pageY); // 将当前右键菜单的状态设置为 打开 rightMenuCount = 1;})// 关闭右键菜单document.addEventListener("mousedown", function(event){ // 如果右键菜单当前是打开状态 if(rightMenuCount) { // 如果按下的是主键 if(event.button === 0) { // 从body上移除右键菜单 document.body.removeChild(rightMenuDiv); // 将右键菜单的状态设置为关闭 rightMenuCount = 0; } }})// 创建100个p标签,用来测试页面高度变化是否会影响右键菜单的行为function create100P(){ let str = ""; for(let i = 0; i < 100; i++) { str += `<p>${i}</p>\\n`; } let ps = document.createElement("div"); document.body.appendChild(ps); ps.innerHTML = str;}create100P(); 就是这样。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"前端","slug":"blog/前端","permalink":"http://example.com/categories/blog/%E5%89%8D%E7%AB%AF/"},{"name":"JavaScript","slug":"blog/JavaScript","permalink":"http://example.com/categories/blog/JavaScript/"}],"tags":[]},{"title":"HTML事件处理","slug":"HTML事件处理","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/05/07/HTML事件处理/","link":"","permalink":"http://example.com/2021/05/07/HTML%E4%BA%8B%E4%BB%B6%E5%A4%84%E7%90%86/","excerpt":"这里只讲如何获取触发的是什么事件以及获取触发该事件的节点。","text":"这里只讲如何获取触发的是什么事件以及获取触发该事件的节点。 获取触发的事件类型: 1<button onmouseover="console.log(event.type)" name="Hey">Test</button> 该按钮在鼠标移至其上时会在控制台打印: “mouseover”,此时的event类型为:MouseEvent。 然后就是获取该节点了。代码如下: 1<button onmouseover="console.log(this)" name="Hey">Test</button> 当鼠标移动到该按钮时,会在控制台打印如下内容: 1<button onmouseover="console.log(this)" name="Hey">Test</button> 这里的this代表了该节点本身,其nodeType == Node.ELMENT_NODE,即其为一个元素节点。如果想要在函数里使用event,可以直接调用。如果想要在函数里用this,可以将this做为参数传递给函数(要不然函数里直接用this得到的结果是window): 1234567891011<button onmouseover="even_test(this)" name="Hey">Test</button><script> function even_test(ev) { // 使用event console.log(event.type); // 使用该节点 console.log(ev); console.log(ev.nodeType === Node.ELEMENT_NODE); }</script> 有一种方法可以在js里给节点添加事件处理: 1234567<button name="Hey">Test</button><script> let ts_b = document.getElementsByName("Hey")[0]; // 这里的this引用了元素本身 ts_b.onclick = function(){console.log(this.name);console.log(event)};</script> 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"JavaScript","slug":"blog/JavaScript","permalink":"http://example.com/categories/blog/JavaScript/"}],"tags":[]},{"title":"JS滚动到指定元素","slug":"JS滚动到指定元素","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2021/05/06/JS滚动到指定元素/","link":"","permalink":"http://example.com/2021/05/06/JS%E6%BB%9A%E5%8A%A8%E5%88%B0%E6%8C%87%E5%AE%9A%E5%85%83%E7%B4%A0/","excerpt":"","text":"这里用到了HTML5标准的scrollIntoView()方法。那么先看代码案例: 12345678910111213141516171819202122232425262728293031323334353637383940<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Scroll Test</title></head><body> <button onclick="goto_fb()">Go</button> <div class="f_box"> This is text for test. </div></body><script> let f_b = document.getElementsByClassName("f_box")[0]; // 滚动到f_b的位置(f_b是类名为f_box的一个元素节点) function goto_fb() { f_b.scrollIntoView({behavior:"smooth"}); } // 该函数的作用是生成100个p标签元素,方便展示滚动效果。 function create_ps() { let str = "" for(let i = 0; i < 100; i++) { str += "<p>"+String(i)+"</p>" } let ps = document.createElement("div"); document.body.insertBefore(ps, document.getElementsByClassName("f_box")[0]); ps.innerHTML = str; } // 生成100个p标签元素 create_ps();</script></html> 以上的代码展现的效果中,点击”Go”按钮即可触发滚动效果。因为我将behavior设置为了”smooth”,所以会有一段页面滚动的效果,如果将behavior设置成为”auto”,则会直接跳转到目的元素的位置。那么这里是一些用法:alignToTop: 这是一个布尔值。当其为true时,窗口滚动后元素的顶部与视口顶部对齐。当其为false时,窗口滚动后元素的底部与视口底部对齐。scrollIntoViewOptions是一个选项对象。behavior: 定义过渡动画,可取的值为”smooth”和”auto”,默认为”auto”。block: 定义垂直方向的对齐,可取的值为”start”, “center”, “end”和”nearest”,默认为”start”。inline: 定义水平方向的对齐, 可取的值为”start”, “center”, “end”和”nearest”,默认为”nearest”。alignToTop和scrollIntoViewOptions一起用: 1f_b.scrollIntoView(false, {behavior:"auto"}); 那么本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"JavaScript","slug":"blog/JavaScript","permalink":"http://example.com/categories/blog/JavaScript/"}],"tags":[]},{"title":"JavaScriptDOM节点操作","slug":"JavaScriptDOM操作","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2021/05/06/JavaScriptDOM操作/","link":"","permalink":"http://example.com/2021/05/06/JavaScriptDOM%E6%93%8D%E4%BD%9C/","excerpt":"","text":"创造节点document.createElement(); 创建一个元素节点,返回该元素节点。类似的还有createTextNode等,这里就不写了。 插入节点Node.appendChild(Node); 插入一个子节点,该子节点成为该节点的lastChild。Node.insertBefore(newNode, someNode); 参数newNode为要插入的节点,someNode为参照节点。该函数会把newNode插入在someNode之前。当someNode为空时,newNode会成为Node的最后一个子节点。当someNode为Node.firstChild时,newNode会成为Node.firstChild(这是理所当然的,插入在第一个元素前方,那么其就会成为第一个元素)。Node.replace(newNode, targetNode); replace会使newNode替换掉targetNode的位置,而targetNode会被从文档中完全移除。targetNode会被作为返回值返回。 移除节点Node.replace(newNode, targetNode); replace会使newNode替换掉targetNode的位置,而targetNode会被从文档中完全移除。targetNode会被作为返回值返回。Node.removeChild(targetNode); targetNode为要被移除的节点,该节点会被移除,并作为返回值返回。 根据关系获取节点Node.firstChild; 获取该节点的第一个子节点Node.lastChild; 获取该节点最后一个子节点Node.nextSibling; 获取该节点的下一个同胞节点Node.previousSibling; 获取该节点的上一个同胞节点Node.childNodes; 获取该节点的子节点列表,类型为NodeList。 克隆节点Node.cloneNode(bool); 该方法会返回与调用它的节点一模一样的节点。cloneNode()方法接受一个布尔值参数,表示是否深复制。在传入true参数的时候,会进行深复制,即复制节点及其整个子DOM树。如果传入false,则只会复制调用该方法的节点。复制返回的节点属于文档所有,但尚未指定父节点,所以可以称为孤儿节点。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"JavaScript","slug":"blog/JavaScript","permalink":"http://example.com/categories/blog/JavaScript/"}],"tags":[]},{"title":"看不见错误的gcc","slug":"看不见错误的gcc","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2021/05/05/看不见错误的gcc/","link":"","permalink":"http://example.com/2021/05/05/%E7%9C%8B%E4%B8%8D%E8%A7%81%E9%94%99%E8%AF%AF%E7%9A%84gcc/","excerpt":"","text":"提示:本篇文章只是娱乐,请不要嘲笑博主的nt操作。你有没有过这样的经历,用gcc编译时,出现一大堆的警告,搞得你心态炸裂,再也不想看到红红的报错。那么解决方法来啦!博主刚做了个脚本,可以让你看不见这些烦人的警告。 123456val=$(gcc -Wall -Werror -Wextra -pedantic "test.c" 2> /dev/null)if [ $? -ne 0 ]then echo "Error: False."fi 这个脚本在gcc发现test.c文件中的错误时会提示你出现了错误,但它并不会提示你哪里出错了,也不会提示你有多少错误。在你编译成功时则什么也不提示。Oh Yeah,看不见有多少条报错了,这就是我想要的gcc(大雾)!哈哈,其实之所以写这篇文章,是因为最近一直在玩js,又因为我的js水平太次,没什么好写的,但是不写些东西又心里难受,所以就拿了个之前的智障脚本写了篇博文,望博君一笑。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"C","slug":"blog/C","permalink":"http://example.com/categories/blog/C/"}],"tags":[]},{"title":"js间歇调用和超时调用","slug":"js间歇和超时调用","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/04/30/js间歇和超时调用/","link":"","permalink":"http://example.com/2021/04/30/js%E9%97%B4%E6%AD%87%E5%92%8C%E8%B6%85%E6%97%B6%E8%B0%83%E7%94%A8/","excerpt":"","text":"我现在想单独创建一个js分类了。。。咳咳,我们先来说间歇调用和超时调用的区别。间歇调用就是设置之后会会持续不断的触发超时,每次超时调用一次; 而超时调用是设置之后只会在第一次超时的时候调用。 超时调用以下代码是超时调用: 123456// 定义超时后要调用的函数function sayTimeOut(){ console.log("Time Out!!");}// 设置超时调用。第一个参数是要调用的函数,第二个参数是间隔的时间setTimeout(sayTimeOut, 1000); 运行这段代码,我的Console(控制台)输出了以下内容: 1Time Out!! 取消超时调用如果想要在超时之前取消掉这个调用,可以使用clearTimeout函数,下面是示例代码: 1234567function sayTimeOut(){ console.log("Time Out!!");}// setTimeout会返回一个id,这个id代表了该超时调用var timerId = setTimeout(sayTimeOut, 1000);// 取消超时调用window.clearTimeout(timerId); 运行这段代码,我的Console(控制台)什么也没有输出,这很正常,因为我的机器还没有弱到一秒种只能执行一条js语句。只有在这个超时调用被调用前可以使用clearTimeout()函数,道理也十分简单: 都调用完了再取消调用也没用了。 间歇调用先看代码: 123456// 定义间歇调用要调用的函数function sayTimeOut(){ console.log("Time Out!!");}// 启动间歇调用setInterval(sayTimeOut, 1000); 这段代码会导致Console(控制台)每隔1秒输出一次”Time Out!!”。 取消间歇调用取消间歇调用的方法和关掉超时调用的方法类似: 1234567function sayTimeOut(){ console.log("Time Out!!");}// timerId为该间歇调用的idvar timerId = setInterval(sayTimeOut, 1000);// 取消间歇调用window.clearTimeout(timerId); 红宝书上说,建议使用超时调用来代替间歇调用,因为超时调用会在前一个超时调用执行完之后执行,但是间歇调用不会,所以使用间歇调用可能会出现前一个间歇调用还在执行中,后一个间歇调用已经开始执行了的情况。替代方法: 12345678910111213141516171819function TimeOutTest(){};TimeOutTest.prototype.num = 0;TimeOutTest.prototype.maxNum = 10;TimeOutTest.prototype.startCount = function(){TimeOutTest.prototype.num++; if(that.num < that.maxNum) { console.log(that.num); // 把当前执行环境传入函数。要不然超时调用的函数的执行环境会是window setTimeout(this.startCount.bind(this), 1000); } else { console.log("Done"); }}var tot = new TimeOutTest();tot.startCount(); Console(控制台)输出如下: 12345678910123456789Done 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"JavaScript","slug":"blog/JavaScript","permalink":"http://example.com/categories/blog/JavaScript/"}],"tags":[]},{"title":"防止子元素浮动导致父元素变化","slug":"清除浮动","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2021/04/29/清除浮动/","link":"","permalink":"http://example.com/2021/04/29/%E6%B8%85%E9%99%A4%E6%B5%AE%E5%8A%A8/","excerpt":"","text":"因为浮动会导致元素脱离标准流,导致其父元素出现一些看上去比较异常的现象。所以这里是几种防止浮动导致父元素表现异常的方法。 额外标签法其实就是在容纳浮动的容器的最后添加一个具有clear属性的块级元素,如: 123456789<div id="main_d"> <div id="hd_box"> <div class="ud_b hd_b">Hi</div> <div class="ud_b hd_b">Hi</div> <div class="ud_b hd_b">Hi</div> <div class="ud_b hd_b">Hi</div> <div style="clear:both;"></div> </div></div> 其中ud_b是加了左浮动的盒子。”hd_box”中的最后一个元素指定了”clear”属性,其作用是指定一个元素是否必须移动(清除浮动后)到在它之前的浮动元素下面。 给父元素添加overflow属性给父元素添加overflow属性,就拿上面的代码来说,在id “main_d”中添加一条”overflow:hidden”就好。不过这个方法有个缺点,就是无法显示溢出部分。 添加after伪类元素先定义一个类: 1234567891011.clearfix{ /* IE6, 7 only */ *zoom: 1;}.clearfix::after{ content: ""; display: block; height: 0; clear: both; visibility: hidden;} 然后把”clearfix”这个类添加给父元素。因为after是在使用该类的元素后面加一个内容,我们又把添加的这个内容设置成了block,即块元素,并对其添加了clear属性。所以这个方法其实相当于”额外标签法”。 双伪元素清除123456789101112.clearfix{ /* IE6, 7 only */ *zoom: 1;}.clearfix::before,.clearfix::after{ content: ""; display: table;}.clearfix::after{ clear: both;} 这个方法和上面那个方法类似,但是你若问我为啥display是table而不是block,我只能回答你我也不知道为啥,但是block也是能用的。然而我看的教程里写的就是table,也没有解释为什么,所以我就只能当作”block在某些情况下也许有bug”处理。 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"前端","slug":"blog/前端","permalink":"http://example.com/categories/blog/%E5%89%8D%E7%AB%AF/"}],"tags":[]},{"title":"郑州找工作经历","slug":"life6","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2021/04/29/life6/","link":"","permalink":"http://example.com/2021/04/29/life6/","excerpt":"前几天去郑州找工作去了,大概在郑州待了有4天,我就回家了。","text":"前几天去郑州找工作去了,大概在郑州待了有4天,我就回家了。一个是郑州那边的岗位比较少,我的目的是找python,但是郑州那边大部分python岗位都是少儿编程。另一个就是那边不要python应届生,或者就是要学过人工智能的,或者就是要经验的。当我把目光从python上移开,想找一下其他岗位时,我发现最低的学历要求也是大专。然而比较烦恼的是,我现在虽然是大专,但是只是在读,还是个函授的。很明显没有人会接受也确实没有人接受。有一次我去面试,面试官也觉得很满意,但是当我说出我大专还没有毕业时,他当即表示他们只要毕业的。我无法判断他是否在委婉的拒绝我,毕竟聊的还算可以。反正就挺郁闷的。之后我想找点其他工作,先去了一个公司,他们说要把我外派到宇通(就是提供郑州公交车的那个公司),要我准备一下假简历,方便到宇通面试。但是我想了想,若是要造假的话,那我可去的地方可就太多了,也没必要去宇通。于是我就拒绝了。之后找了一个标注着”有师傅带”且面向中专的工作,结果到了地方一瞧,是个卖超市系统和pos机的…很不合我心意,于是我就又拒绝了。搞来搞去,最后还是没有找到工作。我是否仍要贯彻不说谎到底呢?这点答案似乎已经很明显了,但是我还是不想骗人,呵呵,有点顽固了。目前来看就只有上全日制大专或者找工作这两条路了。不知道我是要去上全日制大专还是要去找工作,找工作也不一定能找到。啧,难以选择。另外我还发现自己搞错了一些事情,之前无论学什么都想要学精通,但是找工作似乎只要求“会就行”。有趣,那我也来一下博而不精吧。目前来看继续钻研后端下去是个不明智的选择,所以我现在打算仔细学一下前端了。如果不上全日制大专,那么我最近应该就要开始闭关了。","categories":[{"name":"life","slug":"life","permalink":"http://example.com/categories/life/"}],"tags":[]},{"title":"Arch Linux安装并使用elasticsearch","slug":"安装并使用elastaticsearch","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/04/19/安装并使用elastaticsearch/","link":"","permalink":"http://example.com/2021/04/19/%E5%AE%89%E8%A3%85%E5%B9%B6%E4%BD%BF%E7%94%A8elastaticsearch/","excerpt":"","text":"很遗憾,这次我们并不能使用万能的pacman了,因为无论是elasticsearch还是kibana,使用pacman安装后都无法运行。安装参考自安装并运行Elasticsearch。如果想要详细的安装过程,请看Installation。虽然这是篇英文文档,不过通篇易懂,对于java和elasticsearch一脸懵的朋友可以看看这个。首先要有一个较新版本的java,这里我用的是extra仓库的jdk-openjdk和jre-openjdk。另外似乎想要运行elasticsearch似乎需要配置JAVA_HOME,但是到底需要不需要我并不清楚,因为我已经配好了…我之前的博文里有写过如何配置JAVA_HOME,如果读者使用elasticsearch的时候需要配置JAVA_HOME,那么可以参考我之前写的博客。那么开始安装。 获取最新版本的Elasticsearch第一步: 下载最新版Elasticsearch。第二步,解压: 1tar -zxvf elasticsearch.tar.gz 执行完上面那条命令,应该会解压出一个elasticsearch带着版本号的文件夹。然后执行以下命令就可以运行elasticsearch了: 12cd elasticsearch文件夹/bin./elasticsearch 如果跑起来了,那么大概是成功了。如果跑着跑着突然停了,那么可能发生了一些意外事故。。。然而博主由于不是Java崽,无法对那些错误进行解答,所以还请自行Google。执行下面这条命令检验是否成功: 1curl 'http://localhost:9200/?pretty' 只要响应的内容不是Error就算成功。 Sense官方文档里写了可以安装Sense,但是这里我们不安装Sense,若要问我为什么,因为elasticsearch的官方文档似乎年久失修了。不过我们有替代方案。 安装kibanakibana依然不能从pacman安装,因为它和elasticsearch一样会报错。kibana的官方文档:Kibana(英文)。首先下载kibana的软件包:Kibana。然后执行以下命令解压: 1tar -zxvf kibana-版本号.tar.gz 然后执行以下命令运行kibana: 12cd kibana文件夹/bin./kibana 服务能跑起来不挂就算成功。 Sense的替代方案官方文档里说的这条安装Sense的命令已经行不通了: 1./bin/kibana plugin --install elastic/sense 输入这条命令会提示你已经没有plugin这条命令啦!好耶!也许你会瞄一眼bin文件夹,发现里面有一个叫做kibana-plugin的可执行文件,但是我尝试用这个可执行文件安装Sense依然没有成功。这时候我看到了Stackoverlow上的一篇帖子:“No valid url specified” when trying to install Kibana’s Sense plugin。按照里面MKaz老哥的说法,Sense插件不是必要的,可以通过kibana的dev tools来代替。运行kibana服务后,访问”http://localhost:5601/app/dev\\_tools#/console",这里和Sense的功能也差不多。 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"},{"name":"elasticsearch","slug":"blog/elasticsearch","permalink":"http://example.com/categories/blog/elasticsearch/"}],"tags":[]},{"title":"解决elasticsearch{error:Content-Type header [application/x-www-form-urlencoded] is not supported","slug":"elasticsearch_jsonheader","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/04/19/elasticsearch_jsonheader/","link":"","permalink":"http://example.com/2021/04/19/elasticsearch_jsonheader/","excerpt":"","text":"今天在跟着elasticsearch官方文档学到”交互”时,出现了个错误,curl命令如下: 1234567curl -XGET 'http://localhost:9200/_count?pretty' -d '{ "query": { "match_all": {} }}' 结果我这里返回了个这: 1234{ "error" : "Content-Type header [application/x-www-form-urlencoded] is not supported", "status" : 406} 那么如何解决呢?方法很简单,指定Content-Type为json就可以了,那么命令应该写: 1234567curl -H"Content-Type:application/json" -XGET 'http://localhost:9200/_count?pretty' -d ' { "query": { "match_all": {} }}' elasticsearch成功给出反馈: 123456789{ "count" : 38, "_shards" : { "total" : 6, "successful" : 6, "skipped" : 0, "failed" : 0 }} 不得不吐槽一下,官方文档都能写错(有可能是之前的版本不识别Content-Type,后来软件更新了文档没有更新吧)。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"elasticsearch","slug":"blog/elasticsearch","permalink":"http://example.com/categories/blog/elasticsearch/"}],"tags":[]},{"title":"解决adb no permissions","slug":"adb_no_permissions","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2021/04/15/adb_no_permissions/","link":"","permalink":"http://example.com/2021/04/15/adb_no_permissions/","excerpt":"","text":"用adb连接过一次手机后,重启再用adb devices连接,提示: no permissions;解决这个问题的方法很简单: 12adb kill-serversudo adb start-server (这个sudo我也不知道是否有必要加,我用的时候是加sudo的,不知道不加sudo能否成功执行该命令)然后再使用adb devices就可以又连接上手机了。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"乱七八糟","slug":"blog/乱七八糟","permalink":"http://example.com/categories/blog/%E4%B9%B1%E4%B8%83%E5%85%AB%E7%B3%9F/"}],"tags":[]},{"title":"arch配置JAVA HOME","slug":"arch配置JAVA_HOME","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2021/04/14/arch配置JAVA_HOME/","link":"","permalink":"http://example.com/2021/04/14/arch%E9%85%8D%E7%BD%AEJAVA_HOME/","excerpt":"","text":"博主从未学过Java,对java一窍不通,并且不知道JAVA_HOME下应该包含什么,只知道配置好了之后appium不再报缺少JAVA_HOME的错。所以本文注定不会讲的详细,甚至可能会有错误,慎读。首先安装jdk-openjdk: 1pacman -S jdk-openjdk 然后安装两个公共包: 1pacman -S java-runtime-common java-environment-common 然后配置JAVA_HOME环境变量。编辑/etc/profile文件,在里面加入这么一段文本: 1export JAVA_HOME=/usr/lib/jvm/java-15-openjdk 这是jdk-openjdk安装的目录。至此就配置好了,运行source /etc/profile就可以使用JAVA_HOME了。找的教程大都不写怎么配置JAVA_HOME,害得我瞪大眼盯了快一个小时浏览器,真烦人。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[]},{"title":"使用QUdpSocket","slug":"QtQUdpSocket","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2021/04/13/QtQUdpSocket/","link":"","permalink":"http://example.com/2021/04/13/QtQUdpSocket/","excerpt":"","text":"先看效果图:使用QUdpSocket需要在项目的.pro文件里加上: 1QT += network QUdpSocket通信的步骤如下: 绑定端口,收消息/发消息。那么看代码: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667#include "serverwidget.h"#include "ui_serverwidget.h"ServerWidget::ServerWidget(QWidget *parent) : QWidget(parent) , ui(new Ui::ServerWidget) , socket(nullptr){ ui->setupUi(this); // socket是一个QUdpSocket类型的指针 socket = new QUdpSocket(this); // 设置绑定9997端口 socket->bind((quint16)8887); setWindowTitle("port 8887"); // 当有数据可读取时,触发readyRead信号,然后转到自定义的dealmsg槽函数 connect(socket, &QUdpSocket::readyRead, this, &ServerWidget::dealmsg); // 当发送信息按钮的clicked事件触发时,转到自定义的sendmsg槽函数 connect(ui->sendButton, &QPushButton::clicked, this, &ServerWidget::sendmsg);}ServerWidget::~ServerWidget(){ delete ui;}void ServerWidget::dealmsg(){ // 定义buffer用来读取数据 char buffer[1024]; // 用0填充buffer memset(buffer, 0, 1024); // QHostAddress类型的变量ip和quint16类型的变量port分别用来接收ip地址和端口号。 QHostAddress ip; quint16 port; // readDatagram如果成功返回一个大小的值,如果失败返回-1 auto mlen = socket->readDatagram(buffer, 1024, &ip, &port); // 判断是否读取成功 if(mlen > 0) { // 格式化字符串 QString qs = QString("[%1:%2]:%3").arg(ip.toString()).arg(port).arg(buffer); // 展示字符串 ui->textEdit->append(qs); } else ui->textEdit->append("Read a shit.");}void ServerWidget::sendmsg(){ // 获取用户输入的ip(这几行变量名都起的比较随意,所以请仔细看注释) QString qs = ui->ipLine->text(); // 用QString类型的ip初始化QHostAddress对象 QHostAddress qh(qs); // 获取用户输入的端口号 quint16 port = ui->portLine->text().toUInt(); // 获取用户输入的内容 QString msg = ui->textEdit_2->toPlainText(); // 向指定ip发送内容。第一个参数是转化成QByteArray的QString // 返回发送成功的字节。如果发送失败,返回-1 auto result = socket->writeDatagram(msg.toUtf8(), qh, port); if(result < 0) ui->textEdit->append("Send Error."); else ui->textEdit->append("Send Success.");} 然后这是头文件: 123456789101112131415161718192021222324252627#ifndef SERVERWIDGET_H#define SERVERWIDGET_H#include <QWidget>#include <QHostAddress>#include <QUdpSocket>QT_BEGIN_NAMESPACEnamespace Ui { class ServerWidget; }QT_END_NAMESPACEclass ServerWidget : public QWidget{ Q_OBJECTpublic: ServerWidget(QWidget *parent = nullptr); ~ServerWidget(); void dealmsg(); void sendmsg();private: Ui::ServerWidget *ui; QUdpSocket* socket; // udp socket};#endif // SERVERWIDGET_H 写完之后如何做到效果图中的效果呢?先运行一个程序,该程序的端口号为8887,然后修改代码,把端口号改成8888,就能实现效果图中的效果了。然后是关于组播与广播了。想要广播的话用户直接输入255.255.255.255再指定ip即可。组播的话有点麻烦,需要改一下绑定: 1234// 任意ip地址。本地端口为8887socket->bind(QHostAddress::AnyIPv4, (quint16)8887);// 加入组播组244.0.0.1socket->joinMulticastGroup(QHostAddress("244.0.0.1")); 然后用户把ip指定为244.0.0.1,端口指定为要接收这个信息的端口即可。假如ip指定了244.0.0.1,端口指定为8888,那么所有绑定了8888端口且加入了244.0.0.1组的UDP套接字都会收到消息。若是想要离开组的话,使用这段代码: 1socket->leaveMulticastGroup(QHostAddress("244.0.0.1")); 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"Cpp定义类型转换运算符","slug":"cpp定义类型转换运算符","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/04/12/cpp定义类型转换运算符/","link":"","permalink":"http://example.com/2021/04/12/cpp%E5%AE%9A%E4%B9%89%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E8%BF%90%E7%AE%97%E7%AC%A6/","excerpt":"","text":"以下内容引自C++ Primer。类型转换运算符是类的一种特殊成员函数,它负责将一个类类型的值转换成其他类型。类型转换函数的一般形式如下: 1operator type() const; 其中type表示某种类型。类型转换运算符可以面向任意类型(除了void之外)进行定义,只要该类型能够作为函数的返回类型。因为,我们不允许转换成数组或者函数类型,但允许转换成指针(包括数组指针及函数指针)或者引用类型。类型转换运算符既没有显示的返回类型,也没有形参,而且必须定义成类的成员函数。类型转换运算符通常不应该改变待转换对象的内容,因此,类型转换运算符一般被定义成const成员。 (以下内容为博主自己做的例子)那么来看一个例子: 123456789101112131415161718192021222324#include <iostream>using namespace std;class Tt{ public: int a = 3; int b = 4; Tt() = default; // 定义类型转换函数 operator int() const noexcept { return b; }};int main(void){ Tt t1; int a = t1; cout << a << endl; return 0;} 这里我们将Tt类型的变量t1转换成了int类型的值并赋值给int类型的变量a,然后输出a,这是输出结果: 12~/codeSet/CPPCode » ./a.out fire@butterfly4 可以看到,类型转换按照我们预先定义的方式规则工作了。为了防止隐式转换,我们可以在声明的时候加上explicit: 1explicit operator int()const {return b;}; 然而该显示转换在用作条件的时候依然会被隐式转换。不得不说,C++真是花里胡哨啊。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"}],"tags":[]},{"title":"Qt客户端和服务端Tcp通信","slug":"Qt客户端和服务端通信","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/04/12/Qt客户端和服务端通信/","link":"","permalink":"http://example.com/2021/04/12/Qt%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%92%8C%E6%9C%8D%E5%8A%A1%E7%AB%AF%E9%80%9A%E4%BF%A1/","excerpt":"","text":"这里使用到了QTcpSocket,QTcpServer和QHostAddress这三个头文件。你应该先在项目的.pro文件里添加QT+=network这段文本。先说服务端。服务端的逻辑很简单,bind和listen一步到位,直接使用QTcpServer的对象的listen函数就行: 1server->listen(QHostAddress::Any, quint16(8888)); 然后服务就打开啦!QHostAddress::Any的意思是任意ip段,quint16(8888)的意思是8888号端口。也就是说我们打开了一个服务,它能够接受任意ip段的地址,而且它在监听8888端口。然后我们来看服务端完整代码,代码里我做了关闭socket通信,发送消息,接受消息并将消息显示在textEdit控件上的操作: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778#include "serverwidget.h"#include "ui_serverwidget.h"ServerWidget::ServerWidget(QWidget *parent) : QWidget(parent) , ui(new Ui::ServerWidget), // server 和socket分别为QTcpServer和QTcpSocket类型的指针 server(nullptr), socket(nullptr), sock_count(0) // 这个参数用来防止多次connect{ ui->setupUi(this); // 实例化QTcpServer对象并设置其监听端口,然后开始监听 server = new QTcpServer(this); server->listen(QHostAddress::Any, quint16(8888)); // 当有一个QTcpSocket连接过来时,会触发newConnection信号 connect(server, &QTcpServer::newConnection, [=]() { std::cout << "Connected" << std::endl; // nextPendingConnection返回的是一个QTcpSocket对象的指针,我们之后要进行的操作都是对这个返回的QTcpSocket对象进行操作 socket = server->nextPendingConnection(); // 将连接过来的Socket的信息显示在textEdit控件上 ui->textEdit->append(QString("ip: %1, port: %2 connected").arg(socket->peerAddress().toString()).arg(socket->peerPort())); // 避免多次connect。因为一开始socket还是个nullptr,所以如果连接放在外面的话会出现奇妙的Bug。 if(sock_count == 0) { sock_count++; // 当接收到对方发送的信息后,会触发readyRead信号。这时候可以用readAll来读取对方发送的全部内容 connect(socket, &QTcpSocket::readyRead, [=](){ // readAll返回的是一个QByteArray对象 QString msg = socket->readAll(); // QByteArray对象隐式转换为QString,在控件上显示其内容 ui->textEdit->append(msg); }); // 当socket连接关闭时,会触发disconnected信号(注意是disconnected不是disconnect哦) connect(socket, &QTcpSocket::disconnected, [=](){ // 在控件上提示连接已断开 ui->textEdit->append("Disconnected."); }); } } );}ServerWidget::~ServerWidget(){ delete ui;}// 这是发送信息的按钮的槽函数void ServerWidget::on_pushButton_clicked(){ // 这一步十分必要,因为在构造函数里socket被初始化为nullptr,如果不加判断的话有可能引发段错误 if(socket != nullptr) { // 获取textEdit控件上的内容 QString msg = ui->textEdit_2->toPlainText(); // 发送内容。write接收一个const char*的参数,所以要这么转换。 socket->write(msg.toUtf8().data()); }}// 这是关闭socket连接的按钮的槽函数void ServerWidget::on_pushButton_2_clicked(){ // 判断socket指针是否为空 if(socket != nullptr) { // 关闭连接 socket->disconnected(); socket->close(); // 在控件上提示关闭连接 ui->textEdit->append("Activily disconnected"); }} 其实最主要的就是readyRead, newConnection, disconnected这几个信号和readAll,write,listen等成员函数,其他的也没啥。然后来看客户端的代码。客户端我做了发送消息,接收消息,发起连接和关闭连接。代码如下: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182#include "clientwidget.h"#include "ui_clientwidget.h"#include <iostream>ClientWidget::ClientWidget(QWidget *parent) : QWidget(parent) , ui(new Ui::ClientWidget) , socket(nullptr) // 初始化socket为nullptr{ ui->setupUi(this); setWindowTitle("Client"); // socket指向一个QTcpSocket对象 socket = new QTcpSocket(this); // connected是一个信号,它的触发代表着连接成功 connect(socket, &QTcpSocket::connected, [=](){ ui->textEdit->append("Connect successful."); }); // readyRead是一个信号。它的触发代表着有消息过来了 connect(socket, &QTcpSocket::readyRead, [=](){ QString msg; // 将QbyteArray转换成QString msg.prepend(socket->readAll()); // 在控件上展示消息 ui->textEdit->append(msg); }); // 当连接关闭的时候会触发disconnected信号 connect(socket, &QTcpSocket::disconnected, [=](){ ui->textEdit->append("Disconnected."); std::cout << "disconnected" << std::endl; // 关闭连接。其实这一步应该是不用的。但是我们为了保险起见,还是关闭一下。 socket->disconnectFromHost(); socket->close(); // 重置socket为nullptr socket = nullptr; });}ClientWidget::~ClientWidget(){ delete ui;}// 这是发起连接按钮的槽函数void ClientWidget::on_connButton_clicked(){ std::cout << "connecting" << std::endl; // 获取用户输入的ip QString ip = ui->lineEdit->text(); // 获取用户输入的端口号 QString port = ui->lineEdit_2->text(); // 把用户输入的端口号转化为无符号int,再转化为quint16类型 quint16 p = port.toUInt(); // 向服务端发起连接 socket->connectToHost(ip, p);}// 这是关闭连接按钮的槽函数void ClientWidget::on_closeButton_clicked(){ // 依然是判断是否为nullptr防止访问未分配的内存 if(socket != nullptr) { // 主动断开Tcp连接 socket->disconnectFromHost(); socket->close(); }}// 这是发送消息的槽函数void ClientWidget::on_sendButton_clicked(){ // 防止访问为分配的内存 if(socket != nullptr) { // 从控件获取要发送的消息 QString msg = ui->textEdit_2->toPlainText(); // 把要发送的消息转换成const char*类型作为write函数的参数 socket->write(msg.toUtf8().data()); }} 相比于服务端,客户端要做的内容要简单一些,主要就是主动发起连接,收发信息以及关闭socket连接。各段代码的内容我的注释写的很明白了,若是还不懂欢迎在我博客下方的评论区发起评论,我会在收到消息后第一时间与发起者进行技术讨论。关键内容就是上面那些,那么下面来看看我的头文件:server的头文件: 12345678910111213141516171819202122232425262728293031323334#ifndef SERVERWIDGET_H#define SERVERWIDGET_H#include <QWidget>#include <iostream>#include <QTcpServer>#include <QTcpSocket>#include <QHostAddress>QT_BEGIN_NAMESPACEnamespace Ui { class ServerWidget; }QT_END_NAMESPACEclass ServerWidget : public QWidget{ Q_OBJECTpublic: ServerWidget(QWidget *parent = nullptr); ~ServerWidget();private slots: void on_pushButton_clicked(); void on_pushButton_2_clicked();private: Ui::ServerWidget *ui; QTcpServer* server; QTcpSocket* socket; char sock_count;};#endif // SERVERWIDGET_H client的头文件: 1234567891011121314151617181920212223242526272829303132#ifndef CLIENTWIDGET_H#define CLIENTWIDGET_H#include <QWidget>#include <QHostAddress>#include <QTcpSocket>QT_BEGIN_NAMESPACEnamespace Ui { class ClientWidget; }QT_END_NAMESPACEclass ClientWidget : public QWidget{ Q_OBJECTpublic: ClientWidget(QWidget *parent = nullptr); ~ClientWidget();private slots: void on_connButton_clicked(); void on_closeButton_clicked(); void on_sendButton_clicked();private: Ui::ClientWidget *ui; QTcpSocket* socket;};#endif // CLIENTWIDGET_H 最后再说一次,别忘记在项目的.pro文件里加上: 1QT += network 好了,本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"linuxQt依赖自动拷贝脚本","slug":"linuxQt依赖自动拷贝脚本","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2021/04/11/linuxQt依赖自动拷贝脚本/","link":"","permalink":"http://example.com/2021/04/11/linuxQt%E4%BE%9D%E8%B5%96%E8%87%AA%E5%8A%A8%E6%8B%B7%E8%B4%9D%E8%84%9A%E6%9C%AC/","excerpt":"","text":"手动拷贝Qt的依赖实非明智之举,于是我就做了个半自动拷贝依赖的脚本(之所以是半自动,是因为其中有一部分需要手动操作)。那么脚本代码如下: 1234567891011121314151617181920212223242526272829303132333435363738394041import osimport shutil# create dir QDependLibrarydef createdir(): if not os.path.exists("./QDependLibrary"): os.mkdir("./QDependLibrary")def copy_lib(path): # copy lib shutil.copy(path, "./QDependLibrary/" + path[path.rfind("/"):])def main(): with open("./dep.txt", "r") as f: if not f: f.close() exit(0) createdir() while True: deps = f.readline() if deps: lpos = deps.find("=> ") rpos = deps.rfind(" (") if lpos < 0 or rpos < 0: continue else: target = deps[deps.find("=> ")+3: deps.rfind(" (")] copy_lib(target) # file end else: break f.close()if __name__ == '__main__': main() 那么应该如何使用这个脚本呢?先使用这条命令获取你的Qt程序的依赖并将其保存为”dep.txt”: 1ldd Qtprogram >> dep.txt ldd会列出该程序的依赖,后面的>> dep.txt会将其输出重定向到文件。然后我们假设上面的python代码名字是dp.py,那么现在将dp.py放在dep.txt的同级目录下,然后运行: 1python3 dp.py 该脚本会自动逐行解析dep.txt的内容并将其中的库文件拷贝到同级目录下的QDependLibrary文件夹里,如果该文件夹不存在就新建一个。 拷贝过来的权限等与之前一致。那么关于这个脚本的介绍就结束了。 现在是闲聊时间。 为什么用字符串内置方法解析库文件路径?一开始我做库文件路径解析的时候用的是re库,结果可劲儿报错,然后我发现引起报错的原因竟然是我的文本的问题???Excuse me?于是我心想拉倒吧,干脆自己拿str切片解析得了。 为什么用python来做自动拷贝脚本?本来我想用C语言的,因为C性能高嘛(虽然性能高在这里也没啥卵用)。但是想了想写出的代码别人拷贝下来还要编译属实不人道,于是就用python啦!用python可以直接执行命令来运行脚本,不像C还得编译一下,少了一道工序哦(老懒狗了)。那么本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"C语言多进程","slug":"C语言进程","date":"un66fin66","updated":"un22fin22","comments":true,"path":"2021/04/10/C语言进程/","link":"","permalink":"http://example.com/2021/04/10/C%E8%AF%AD%E8%A8%80%E8%BF%9B%E7%A8%8B/","excerpt":"","text":"本篇博文参考自csapp。因为详细内容书上已经讲的很清楚,所以这里我就只记一下各个函数所需要的参数以及返回值。 函数getpid()返回调用进程的PID。头文件: <unistd.h>函数原型: pid_t getpid(void);参数: 无返回值: 调用进程的PID。 getppid()返回它的父进程的PID。头文件: <unistd.h>函数原型: pid_t getppid(void);参数: 无返回值: 返回它的父进程的PID。 fork()返回两个值: 在主进程中返回子进程的id,在子进程中返回0。头文件: <unistd.h>函数原型: pid_t fork(void);参数: 无返回值: 如果当前进程是主进程,返回fork出的子进程的进程id;如果当前进程是子进程,返回0。 waitpid()根据不同的参数表现出不同的行为返回不同的值。头文件: <wait.h>函数原型: pid_t waitpid(pid_t pid, int *statusp, int options);参数: pid: 如果pid > 0,那么等待集合就是一个单独的子进程,它的进程ID等于pid。如果pid = -1,那么等待集合就是由父进程的所有子进程组成的。 statusp: 该参数反映了已回收子进程的状态。 options: 该参数设置了函数的行为。由于该函数稍微复杂,非三言两语可以讲明白,若欲详细了解相关内容及其参数的作用请移步百度百科: waitpid。 sleep()sleep函数将一个进程挂起一段时间。头文件: <unistd.h>函数原型: unsigned int sleep(unsigned int secs);参数: 秒速返回值: 如果请求的时间量已经到了,secs返回0; 否则返回还剩下要休眠的秒数。 变量errno头文件: <errno.h>如果waitpid的调用进程没有子进程,那么waitpid会把errno设置为ECHILD。如果waitpid函数被一个信号中断,那么它会设置errno为EINTR。未完待续。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"CSAPP","slug":"blog/CSAPP","permalink":"http://example.com/categories/blog/CSAPP/"}],"tags":[]},{"title":"vim注释插件","slug":"vim注释插件","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/04/09/vim注释插件/","link":"","permalink":"http://example.com/2021/04/09/vim%E6%B3%A8%E9%87%8A%E6%8F%92%E4%BB%B6/","excerpt":"","text":"本篇介绍的插件是nerdcommenter,github地址nerdcommenter。本博客参考自: Vim插件推荐–Vim高效率注释插件nerdcommenter的安装和使用方法。本博客中的改变<leader>键的方法参考自:VIM学习笔记 前缀键(leader)。那么先安装这个插件,因为我是用的Vim-Plug,所以需要在vim的配置文件里加入这段话: 1Plug 'preservim/nerdcommenter' 然后打开vim,运行:PlugInstall来安装插件。安装好之后,可以通过[count]<leader>cc来注释count行,也可以通过[count]<leader>cu来解除注释count行。比如默认leader键是\\,我们要注释3行,就可以输入3然后按住\\键输入cc,这样就会向下注释三行。如果要解除3行注释,就输入3然后按住\\键输入cu,这样就会向下解除三行注释。count是可选项,你可以直接输入\\cc来注释光标当前所在行,亦可以直接输入\\cu来解除光标当前所在行的注释。当<leader>键为\\的时候做这样的操作有点难受,所以可以用这个方法把空格改成<leader>:在vim配置文件中加入这段文本: 1let mapleader = "\\<space>" 然后你的<leader>键就变成空格啦,就可以按住空格再按两下cc或cu来注释或解注释了。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vim","slug":"blog/vim","permalink":"http://example.com/categories/blog/vim/"}],"tags":[{"name":"vim","slug":"vim","permalink":"http://example.com/tags/vim/"}]},{"title":"QtQBuffer","slug":"QtQBuffer","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2021/04/08/QtQBuffer/","link":"","permalink":"http://example.com/2021/04/08/QtQBuffer/","excerpt":"","text":"QBuffer可以在内存中开辟一块空间用来存储临时数据,QBuffer继承自QIODevice。这个比较好理解,直接看代码吧(记得包含QBuffer头文件): 12345678910111213141516// 创建QBuffer对象QBuffer membuf;// 设置打开模式为只写if(membuf.open(QIODevice::WriteOnly)){ // 写入文本 membuf.write("Hello"); membuf.write(" This is QBuffer"); // 关闭只写打开模式 membuf.close(); // 以只读方式打开membuf membuf.open(QIODevice::ReadOnly); // 读取内容并输出。reaAll()返回一个QByteArry cout << membuf.readAll().toStdString() << endl; membuf.close();} 输出内容如下: 12311:35:49: Starting /home/fire/codeSet/QtSet/build-untitled9-unknown-Debug/untitled9 ...Hello This is QBuffer11:35:52: /home/fire/codeSet/QtSet/build-untitled9-unknown-Debug/untitled9 exited with code 0 你也可以这么做: 1234567891011121314151617181920// 所有的操作都是对QByteArray类型的arr变量的操作QByteArray arr;QBuffer membuf(&arr);QDataStream stream;if(membuf.open(QIODevice::WriteOnly)){ QString msg("Now!Buf!"); stream.setDevice(&membuf); // 写入QString类型的数据和int类型的数据 stream << msg << 777; membuf.close(); QString resq; int resi; membuf.open(QIODevice::ReadOnly); // 依次读取QString类型和int类型的数据 stream >> resq >> resi; std::cout << resq.toUtf8().data() << resi << endl;} 输出如下: 12311:41:50: Starting /home/fire/codeSet/QtSet/build-untitled9-unknown-Debug/untitled9 ...Now!Buf!77711:41:54: /home/fire/codeSet/QtSet/build-untitled9-unknown-Debug/untitled9 exited with code 0 这篇文章就介绍到这里,本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"QtQTextStream","slug":"QtQTextStream","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2021/04/08/QtQTextStream/","link":"","permalink":"http://example.com/2021/04/08/QtQTextStream/","excerpt":"","text":"QTextStream是一个文本流,用法如下: 1234567891011// 指定文件名QFile qf("../new.txt");// 设置以只写方式打开文件,如果打开成功则执行if中的内容if(qf.open(QFile::WriteOnly)){ QTextStream stream; stream.setDevice(&qf); stream << QString("Hello Shark") << 250; qf.close();} 写入文件的内容直接就是文本: 12# ../new.txtHello Shark250 本篇博客就写到这儿了。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"IEEE浮点数","slug":"IEEE浮点数","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2021/04/07/IEEE浮点数/","link":"","permalink":"http://example.com/2021/04/07/IEEE%E6%B5%AE%E7%82%B9%E6%95%B0/","excerpt":"","text":"本篇博文的内容参考自CSAPP第三版。 IEEE浮点数大致概念IEEE浮点标准用V = (-1)s * M * 2E 的形式来表示一个数。符号s决定这数是负数(s=1)还是正数(s=0),而对于数值0的符号位解释作为特殊情况处理。尾数M是一个二进制小数。阶码E的作用是对浮点数加权,这个权重是2的E次幂(可能是负数)。浮点数的位表示划分为3个字段,分别对这些值进行编码:一个单独的符号位s直接编码符号s。k位的阶码字段编码E。n位的小数字段编码M。这些内容建议直接看CSAPP第三版78页,我写这篇博客的目的其实是为了记录下来如何表示E和M。 表示方法将一个位序列划分成三个部分,从高位到低位,第一部分表示s,共有1位;第二部分表示E,共有k位;第三部分表示M,共有n位。这里我们假设有一个8位的位序列,第一部分用来表示符号s,所以占据一位;第二部分用来表示E,它占3位,因为我们用k表示第二部分的位数,所以k=3;第三部分用来表示M,它占4位,因为我们用n表示第三部分的位数,所以n=4。这样一个8位的位序列就已经分配完了,接下来我们来看看它是如何表示浮点数的。s的值如果为1,那么该浮点数为负数;如果s的值为零,那么该浮点数为正数。第二部分的位字段如果全部为1或者全部为0,属于特殊情况。我们先讲正常情况的。 规格化的值这里我们讲的是正常情况:第二部分的位字段不全部为1或者0。因为第二部分共3位,而且其位字段不全部为1或者为0,所以其最大值为0b110,即十进制的6。在这时,阶码字段E为:E = e - bias。e为第二部分所表示的无符号值,这里我们假设第二部分表示的为其最大值,即0b110,十进制的6。bias为2k-1-1,所以E = 6 - (23 - 1 - 1) = 3。M = 1+f。f为第三部分表示的小数值,即(1或0) * 2-1 + (1或0) * 2-2 +…(1或0) * 2-4。设第三部分的位表示为:0b1000,则M = 1 + f = 1 + 1/2 = 3/2。此时我们将第一部分的值设为1,那么V = -1 * 23 * 3/2 = -12。 非规格化的值当第二部分全部为0时,E = 1 - bias,M = f。所以当第二部分和第三部分都为0时,值为(+或-)0。 特殊值当第二部分全部为1,当第三部分全部为0,得到的值表示无穷。s = 0则为正无穷,s = -1则为负无穷。当第二部分全部为1但是第三部分不全部为0,结果值被称为NaN,即”Not a Number”。 如果发现讲的哪里有问题欢迎提出或补充。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"C","slug":"blog/C","permalink":"http://example.com/categories/blog/C/"}],"tags":[]},{"title":"VSCode中使用QQ","slug":"VSCode中使用QQ","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2021/04/07/VSCode中使用QQ/","link":"","permalink":"http://example.com/2021/04/07/VSCode%E4%B8%AD%E4%BD%BF%E7%94%A8QQ/","excerpt":"","text":"今天我刚回家,在内蒙待了一个星期,所以这么长时间都没有更新博客了。前几天看到群友在VSCode中玩起来了QQ,虽然功能简陋,但是对于Linux玩家来说这玩意儿仍然胜过qq-linux,所以刚回家我就来写一篇关于VSCode中使用QQ的博客。先看我的示例:据我群友所说,这个QQ插件可以直接在扩展市场里搜关键字”QQ”,但是我搜了搜并没有搜到,于是这里采用离线安装的方式安装QQ插件。那么这是这个插件的网址: QQ。从这个网址下载好插件后,在其同级目录输入: 1code --install-extension takayama.vscode-qq-1.0.0.vsix 把takayama.vscode-qq-1.0.0.vsix替换成你自己的插件名。 然后下面是使用。打开VSCode,你会发现右下角多了QQ俩字,然后点击QQ,点击”我在线上”,输入帐号密码,就可以登录上帐号尽情的聊天啦。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[]},{"title":"QtQDataStream读取变量","slug":"QtDataStream","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/03/29/QtDataStream/","link":"","permalink":"http://example.com/2021/03/29/QtDataStream/","excerpt":"","text":"写这篇博文的前一分钟,我是处在自闭之中的。那么先说说QDataStream是什么玩意儿吧,看名字也知道,这是一个流,那么其作用是什么呢?其作用就像fstream那样,是对文件进行操作的。只不过它不是以文本的形式把数据写入文件,而是以二进制的形式把数据写入文件的。就像C语言能够直接把结构体写入文件,读出来的还是个结构体一样,这个是能够把任意数据类型写入文件,然后只要按照对应的类型读取,都能够读出来。那么先看把数据写入文件的代码: 1234567891011121314151617// 创建QFile变量,将文件的路径传入QFile fll("../new.txt");// 以只写格式打开文件。如果打开失败则执行elseif(fll.open(QFile::WriteOnly)){ // 让QDataStream对象明白自己要对哪个文件进行操作 QDataStream ds(&fll); // 创建QString变量和int变量 QString qs("Hello"); int i = 1; // 写入文件 ds << qs << i; // 关闭QFile fll.close();}// 如果文件打开失败则输出以下内容else std::cout << "Open Error" << std::endl; 下面是读的代码: 12345678910111213141516171819202122// 传入QFile对象文件的路径QFile fl("../new.txt");// 以只读方式打开文件,如果失败执行elseif(fl.open(QIODevice::ReadOnly)){ // 让QDataStream对象明白自己要对哪个文件进行操作 QDataStream ds(&fl); // 创建QString类型的变量和int类型的变量,用于存储读取的数据 QString qs; int i; // 读取数据。先读取到qs,再读取到i ds >> qs >> i; // 打印数据 std::cout << qs.toUtf8().data() << std::endl; std::cout << i << std::endl; fl.close();}else{ // 打开文件失败,输出提示内容 std::cout << "Read Error." << std::endl;} 然后我们先执行写的代码,再紧接着执行读的代码,输出如下: 123415:53:51: Starting /home/fire/codeSet/QtSet/build-untitled12-unknown-Debug/untitled12 ...Hello115:53:53: /home/fire/codeSet/QtSet/build-untitled12-unknown-Debug/untitled12 exited with code 0 可以看到,我们以什么格式写进去的,以什么格式读出来就会原样读取。但是如果写的格式与读的格式不一样呢?请看示例代码: 123456789101112131415161718192021222324252627282930// 写 QFile fll("../new.txt"); if(fll.open(QFile::WriteOnly)) { QDataStream ds(&fll);// QString qs("Hello"); int i = 1;// 更改了这段代码!!!!!!!! ds << "Hello" << i; fll.close(); } else std::cout << "Open Error" << std::endl;//读取 QFile fl("../new.txt"); if(fl.open(QIODevice::ReadOnly)) { QDataStream ds(&fl); QString qs; int i; ds >> qs >> i; std::cout << qs.toUtf8().data() << std::endl; std::cout << i << std::endl; fl.close(); } else { std::cout << "Read Error." << std::endl; } 这里我只更改了写的代码,读的代码还是以之前一样。这是输出结果: 123415:56:08: Starting /home/fire/codeSet/QtSet/build-untitled12-unknown-Debug/untitled12 ...䡥汬漀115:56:10: /home/fire/codeSet/QtSet/build-untitled12-unknown-Debug/untitled12 exited with code 0 因为QString和字符串字面量(其实这里的字符串字面量的真实面目并不是std::string,而是const char*类型的数组)的类型不同,所以读出来的也不同。关于为什么int类型能够正常读出来我也不知道。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"QtQFileInfo获取文件信息","slug":"QtQFileInfo","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/03/29/QtQFileInfo/","link":"","permalink":"http://example.com/2021/03/29/QtQFileInfo/","excerpt":"","text":"这里只列举几个常用的方法: 获取文件所在文件夹的绝对路径,获取文件所在的目录,获取文件名,获取文件创建事件,获取文件大小。那么直接看代码: 123456789101112131415161718// 输出文件的绝对路径QString path = QFileDialog::getOpenFileName(this, "open a file", "../", "All(*.*)");// 创建QFileInfo对象,将文件的绝对路径作为参数传入QFileInfo fi(path);// 因为我的环境下qDebug莫名其妙的坏了,所以只能使用cout了using namespace std;// 输出文件所在的文件夹的名字,absoluteDir()返回一个QDir,使用dirName()获取其QString值。后面的.toUtf8().data()是因为我当前环境的原因要把QString转换成string,但是正常情况下不需要这样,直接qDebug() << QString;即可。cout << fi.absoluteDir().dirName().toUtf8().data() << endl;// 输出文件所在文件夹的绝对路径cout << fi.absoluteDir().absolutePath().toUtf8().data() << endl;// 输出文件名cout << fi.fileName().toUtf8().data() << endl;// 输出文件的创建时间QDateTime dt = fi.birthTime();cout << dt.toString("yyyy-MM-dd HH:mm").toLocal8Bit().data() << endl;// 输出文件大小cout << fi.size() << endl; 那么这篇就这些。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"QtQFile读写文件","slug":"QtQFile读写文件","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2021/03/28/QtQFile读写文件/","link":"","permalink":"http://example.com/2021/03/28/QtQFile%E8%AF%BB%E5%86%99%E6%96%87%E4%BB%B6/","excerpt":"","text":"直接看代码吧,没啥好说的。 读: 123456789101112131415161718// 获取文件路径QString path = QFileDialog::getOpenFileName(this, "open a file", "../", "All(*.*)");// 创建一个QFile对象QFile qf;// 设置文件名(因为你得知道文件名才能对文件进行操作),也可以用QFile(QString filename)的方式来设置文件名。qf.setFileName(path);// 设置打开文件的模式qf.open(QIODevice::ReadOnly);// 读取文件一行,最多读取300字符。并将其转换为QString。该函数为重载函数,当传入qint64的值或不传入值的时候,返回一个QByteArray对象,故此需要转换成QString。如果不转换,有些时候在用到QString的地方编译器会隐式转换QString lines(qf.readLine(300));// 再读取一行// 想要一次性读完可以使用qf.readAll()。这里我就不写了。lines += qf.readLine(300);// 使用读取的数据ui->textBrowser->setText(lines);// 关闭文件qf.close(); 写文件: 12345678910111213// 获取将要创建的文件的路径QString path = QFileDialog::getSaveFileName(this, "Create a file", "../", "All(*.*)");// 设置文件路径QFile qf(path);// 如果打开成功返回true,失败返回falsebool open_ok = qf.open(QIODevice::WriteOnly);if(open_ok){ // 写文件 qf.write("hello"); // 关闭文件 qf.close();} 就是这样。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"Qt设置无边框和窗口透明","slug":"Qt设置程序边框和窗口透明","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2021/03/28/Qt设置程序边框和窗口透明/","link":"","permalink":"http://example.com/2021/03/28/Qt%E8%AE%BE%E7%BD%AE%E7%A8%8B%E5%BA%8F%E8%BE%B9%E6%A1%86%E5%92%8C%E7%AA%97%E5%8F%A3%E9%80%8F%E6%98%8E/","excerpt":"","text":"setWindowFlag(Qt::FramelessWindowHint);可以设置无边框。然后看看设置无边框的效果。setAttribute(Qt::WA_TranslucentBackground);可以设置窗口背景透明,效果图如下:我看帮助文档上说如果想要在Windows上使用这个选项,你得先把无边框设置了才行。 然后看看这两者一起使用的效果: 十分漂亮。(然而并没有什么用)本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"Qt事件过滤器","slug":"Qt事件过滤器","date":"un66fin66","updated":"un22fin22","comments":true,"path":"2021/03/27/Qt事件过滤器/","link":"","permalink":"http://example.com/2021/03/27/Qt%E4%BA%8B%E4%BB%B6%E8%BF%87%E6%BB%A4%E5%99%A8/","excerpt":"","text":"今天我的截图工具Peek坏了,似乎是ffmpeg的锅。所以今天没有GIF演示了,我会把GIF可以表达的内容全部换成终端表达。 先看代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445// widget.hclass Widget : public QWidget{ Q_OBJECTpublic: Widget(QWidget *parent = nullptr); ~Widget();private: Ui::Widget *ui;protected: bool eventFilter(QObject*, QEvent*);};// widget.cppWidget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget){ ui->setupUi(this); ui->pushButton->installEventFilter(this); // 设置过滤器}Widget::~Widget(){ delete ui;}bool Widget::eventFilter(QObject *ob, QEvent *ev){ // 拦截pushButton对象的鼠标点击事件 if(ui->pushButton == ob) { if(ev->type() == QEvent::MouseButtonPress) { std::cout << "Filter has an effect." << std::endl; // 如果触发,则输出内容 return true; } } return QWidget::eventFilter(ob, ev); // 不相干的事件继续执行。如果没有这段代码的话,可能会因为事件没有被执行导致各种BUG} eventFilter返回一个bool类型,如果为true,代表该事件已经处理。在这次代码中,我拦截了一个我自定义的button类的鼠标点击事件,而我这个自定义的button类设置了一个鼠标点击事件和一个进入事件: 12345678910void MyButton::mousePressEvent(QMouseEvent *ev){ std::cout << "Press" << std::endl; // 如果出发鼠标点击事件,输出Press}void MyButton::enterEvent(QEvent *ev){ std::cout << "Enter!" << std::endl; // 如果触发进入事件,输出Enter} 然后我编译并运行程序,单击一下按钮,输出如下: 123417:47:06: Starting /home/fire/codeSet/QtSet/build-untitled9-unknown-Debug/untitled9 ...Enter!Filter has an effect.17:47:13: /home/fire/codeSet/QtSet/build-untitled9-unknown-Debug/untitled9 exited with code 0 这里触发”Enter”没有什么问题,一是因为我们没有拦截,二是因为想要点击按钮,鼠标必需要进入按钮的区域才行。可以看到,与鼠标点击事件不相关的进入事件执行了,过滤器中的鼠标点击事件生效了,而我们定义的mosuePressEvent事件并没有生效,因为其在触发事件之前就已经被过滤器拦截了。接下来我们修改过滤器,使得过滤器中的代码和我们定义的mousePressEvent事件都能触发: 123456789101112bool Widget::eventFilter(QObject *ob, QEvent *ev){ if(ui->pushButton == ob) { if(ev->type() == QEvent::MouseButtonPress) { std::cout << "Filter has an effect." << std::endl; } } return QWidget::eventFilter(ob, ev);} 然后编译运行,并点击一次按钮,输出如下: 1234517:51:35: Starting /home/fire/codeSet/QtSet/build-untitled9-unknown-Debug/untitled9 ...Enter!Filter has an effect.Press17:51:41: /home/fire/codeSet/QtSet/build-untitled9-unknown-Debug/untitled9 exited with code 0 可以看到,过滤器里的输出和mousePressEvent里的输出都输出出来了。而且过滤器里的输出顺序在Press之上,这也说明了过滤器是在事件触发之前就被触发的。那么本篇博文到这里就结束了,没有Peek截GIF真难受。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"Qt绘图","slug":"Qt绘图","date":"un66fin66","updated":"un22fin22","comments":true,"path":"2021/03/27/Qt绘图/","link":"","permalink":"http://example.com/2021/03/27/Qt%E7%BB%98%E5%9B%BE/","excerpt":"","text":"先来看看效果:这是我在主窗口上绘制的一个图形,其中顶部和左边的灰线可以随着主窗口的重绘而跟着变化。先来看看代码吧: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859// widget.hclass Widget : public QWidget{ Q_OBJECTpublic: Widget(QWidget *parent = nullptr); ~Widget();private: Ui::Widget *ui;protected: // 绘图事件 void paintEvent(QPaintEvent *event);};// widget.cpp#include "widget.h"#include "ui_widget.h"Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget){ ui->setupUi(this);}Widget::~Widget(){ delete ui;}void Widget::paintEvent(QPaintEvent *event){ // 创建画家(画画全靠它) QPainter p; // 创建画笔(其实就是拥有各种属性的工具,如颜色,刷子,样式等) QPen pen; // 开始绘画。参数是绘画设备。这里由于我们要直接在窗口上绘制,所以是this。看不懂没关系,待会儿看一下我保存图片的代码就明白这个参数是什么了。begin()会开始绘画,如果开始成功返回true,其他情况会返回false p.begin(this); // 设置画笔的粗细 pen.setWidth(10); // 设置画笔的颜色,这里设置的是一个RGB颜色。当然你也可以使用RGBA。详情得看QColor的帮助文档 pen.setColor(QColor(200, 200, 200)); // 设置画笔的样式。这个样式会让画出来的线隔一定长度有一个断裂的效果 pen.setStyle(Qt::DashLine); // 给画家选中画笔。因为画笔我们设置了各种各样的属性,所以当画家使用该画笔时,画出的东西就会具有画笔上的属性 p.setPen(pen); // 画两条线。第一个是参数是x轴,第二个是y轴。第三个也是x轴,第四个是y轴。因为两点构成一条直线,所以这是两个点的坐标。width()返回当前绘图设备的宽度。 p.drawLine(10, 10, width(), 10); p.drawLine(10, 10, 10, height()); // height返回当前屏幕的高度 p.drawRect(100, 100, 300, 60); // 画个矩形 p.setBrush(Qt::Dense1Pattern); // 设置刷子。不同的刷子有不同的特效。 // 画一个圆。第一个参数是其圆心的坐标。第二个和第三个参数分别是圆的x方向和y方向距离圆心的长度。 p.drawEllipse(QPoint(150, 130), 30, 30); // 结束绘画 p.end();} 这里我主要用到了paintEvent事件。它在控件被重绘时触发。千万不要在这里面调用重绘函数,因为这将导致其一直重绘。这次我的注释很全,所以直接看注释即可。然后就是绘图设备是什么东西了,在我眼里,绘图设备就是绘图的目标,因为解释起来太麻烦了,所以直接看代码吧,看完就明白了。 1234567891011121314151617181920212223242526Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget){ ui->setupUi(this); // 设置画布大小 QPixmap xmap(200, 200); // 用白色填充画布 xmap.fill(Qt::white); // 创建画家 QPainter p; // 开始绘画。绘图设备为QPixmap类型的变量xmap p.begin(&xmap); // 创建画笔 QPen pen; // 自定义画笔 pen.setWidth(10); // 使用画笔 p.setPen(pen); // 画线 p.drawLine(10, 20, 30, 40); // 结束绘画 p.end(); // 保存文件 xmap.save("./new.jpg");} 我在构造函数里添加了一段代码,其功能是绘制一个图像并保存在统计目录下的名为new.jpg的文件中。这是画出来的图像:根据以上代码我们可以得知,”绘图设备”就是”画在哪里”的意思。 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"Qt事件的接收与忽略","slug":"Qt事件的接收与忽略","date":"un66fin66","updated":"un22fin22","comments":true,"path":"2021/03/27/Qt事件的接收与忽略/","link":"","permalink":"http://example.com/2021/03/27/Qt%E4%BA%8B%E4%BB%B6%E7%9A%84%E6%8E%A5%E6%94%B6%E4%B8%8E%E5%BF%BD%E7%95%A5/","excerpt":"","text":"Qt事件的接收与忽略事件对象有个接收标志,其为bool类型的accepted。这个标志(flag)决定了当前事件是否是需要的事件,如果不是需要的事件话,那么这个事件就会被父组件接收(注意,是父组件,不是父类)。当accepted为true的时候,其会被认为是需要的事件,如果是false,其会被认为是不需要的事件。使用QMouseEvent.accept()函数可以将其设置为true,使用QMouseEvent.ignore()可以将其设置为false。 而重写事件,会导致该事件对应的信号失效,因为信号就是在事件里发送的。解决方法就是在事件里手动发送信号。那么我们先看重写事件导致的信号失效。 重写事件导致对应的信号失效那么下面是代码例子: 12345678910111213141516171819202122// MyButton函数头文件class MyButton:public QPushButton{ Q_OBJECTpublic: explicit MyButton(QWidget* parent);protected: void mousePressEvent(QMouseEvent*);};// MyButton实现#include "mybutton.h"MyButton::MyButton(QWidget* parent):QPushButton(parent){ connect(this, &MyButton::clicked, [](){std::cout << "This button was clicked." << std::endl;});}void MyButton::mousePressEvent(QMouseEvent *ev){ std::cout << "Press!!" << std::endl; ev->accept();} 可以看到,我重写了mousePressEvent事件,在mousePressEvent事件里输出了”Press!!”,这样每当mousePressEvent被触发都会输出一次”Press!!”。ev->accept()的作用是将accepted标志设置为true,这样就不会触发父组件的对应事件了。而在构造函数里我连接了这个类的click信号和一个lambda定义的槽,每次点击的时候会输出:”This button was clicked.”。那么我运行一下程序,并单击一次这个BUtton: 12313:35:02: Starting /home/fire/codeSet/QtSet/build-untitled9-unknown-Debug/untitled9 ...Press!!13:35:06: /home/fire/codeSet/QtSet/build-untitled9-unknown-Debug/untitled9 exited with code 0 这是这个程序的输出。可以看到,mousePressEvent被触发了,但是clicked信号对应的槽函数并没有输出,所以clicked信号并没有被触发。原因我上面说过了,是因为我们重写了mousePressEvent,导致了对应的信号失效了。那么我们如何让这个信号正常工作呢?很简单,直接在事件函数里重新调用这个事件就好了: 123456void MyButton::mousePressEvent(QMouseEvent *ev){ std::cout << "Press!!" << std::endl; clicked(); // 发送信号 ev->accept();} 这是我运行程序并单击一次button的输出结果: 123413:39:20: Starting /home/fire/codeSet/QtSet/build-untitled9-unknown-Debug/untitled9 ...Press!!This button was clicked.13:39:23: /home/fire/codeSet/QtSet/build-untitled9-unknown-Debug/untitled9 exited with code 0 可以看到,这次clicked信号对应的槽函数也被触发了。 事件的接收与忽略接下来我们来讲事件的接收与忽略,这是我的button的mousePressEvent: 12345void MyButton::mousePressEvent(QMouseEvent *ev){ std::cout << "Press!!" << std::endl; ev->accept();} 这是这个button父组件的mousePressEvent: 12345void Widget::mousePressEvent(QMouseEvent *event){ std::cout << "Father widget was pressed." << std::endl;} 然后我运行程序并单击一下button: 12313:44:44: Starting /home/fire/codeSet/QtSet/build-untitled9-unknown-Debug/untitled9 ...Press!!13:44:48: /home/fire/codeSet/QtSet/build-untitled9-unknown-Debug/untitled9 exited with code 0 这是程序的输出结果。接下来我修改button的mousePressEvent函数,把accept()改成ignore(): 12345void MyButton::mousePressEvent(QMouseEvent *ev){ std::cout << "Press!!" << std::endl; ev->ignore();} 然后再次运行程序并单击一次button: 123413:47:40: Starting /home/fire/codeSet/QtSet/build-untitled9-unknown-Debug/untitled9 ...Press!!Father widget was pressed.13:47:43: /home/fire/codeSet/QtSet/build-untitled9-unknown-Debug/untitled9 exited with code 0 这是程序的输出结果。可以看到,不只是button的mousePressEvent事件,其父组件的mousePressEvent也被触发了。代码已经很清楚了。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"Qt计时器事件","slug":"Qt计时器事件","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/03/26/Qt计时器事件/","link":"","permalink":"http://example.com/2021/03/26/Qt%E8%AE%A1%E6%97%B6%E5%99%A8%E4%BA%8B%E4%BB%B6/","excerpt":"","text":"不多说,直入主题。想要启用计时器事件,自然需要先在头文件里定义了: 12protected: void timerEvent(QTimerEvent *e); 可以通过在构造函数里调用startTimer来根据设置的触发时间来触发计时器事件: 1startTimer(1000); // 单位毫秒 比如这里我设置的就是每隔一秒触发一次。不过,一个类可以设置多个计时器,如: 12startTimer(1000); // 一秒一次startTimer(500); // 500毫秒一次 那么如何区分是谁触发的计时器事件呢?我们来看一看这个函数的原型: 123456int QObject::startTimer(int interval, Qt::TimerType timerType = Qt::CoarseTimer) // 函数原型// 说明Starts a timer and returns a timer identifier, or returns zero if it could not start a timer.A timer event will occur every interval milliseconds until killTimer() is called. If interval is 0, then the timer event occurs once every time there are no more window system events to process.The virtual timerEvent() function is called with the QTimerEvent event parameter class when a timer event occurs. Reimplement this function to get timer events.If multiple timers are running, the QTimerEvent::timerId() can be used to find out which timer was activated. 可以看到,如果startTimer正常启动的话,会返回一个timer识别码,我们直接叫它timerID好了。我们可以根据这个TimerID来判断是谁触发了计时器事件,下面请看代码: 123456789101112131415161718192021222324252627282930// 头文件private: int timerID1; int timerID2;// 构造函数timerID1 = startTimer(1000);timerID2 = startTimer(500);// timerEvent事件void MyWidget::timerEvent(QTimerEvent *e){ static int i1 = 0; static int i2 = 0; if(e->timerId() == timerID1) { QString qs = QString("<center>i1: %1</center>").arg(++i1); ui->label->setText(qs); if(i1 == 3) { killTimer(timerID1); // 关闭计时器 } } else if(e->timerId() == timerID2) { QString qs = QString("<center>i1: %1</center>").arg(++i2); ui->label_2->setText(qs); }} 代码中定义了两个计时器,一个每一秒触发一次,一个每500毫秒触发一次。使用QTimerEvent.timerId()来获取当前触发事件的计时器的ID。当每秒触发一次的计时器触发3次时,使用killTimer()关闭该计时器。下面是代码运行起来的演示图片:那么相信看到这里,就已经明白这个事件该怎么玩了。本篇完。(今天居然又水了三篇博客)","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"QtQwidget触发键盘事件","slug":"QtQWidget触发键盘事件","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/03/26/QtQWidget触发键盘事件/","link":"","permalink":"http://example.com/2021/03/26/QtQWidget%E8%A7%A6%E5%8F%91%E9%94%AE%E7%9B%98%E4%BA%8B%E4%BB%B6/","excerpt":"","text":"今天跟着Qt教程走的时候发现教程里可以触发键盘事件(如keyPressEvent),但是我不可以,于是一番谷歌后找到了这个博客:检测按键输入及解决无法响应方向键问题。当然你也可以直接看我博客。这个问题似乎是所有QWidget的派生类都有的,那么如何解决它呢?可以在类的构造函数里加一条: 1setFocusPolicy(Qt::ClickFocus); 该函数可以设置focus策略。然后Qt::ClickFocus是点击程序窗口后聚焦焦点。Qt::ClickFocus是枚举类型,相应的还有:Qt::TabFocus,Qt::StrongFocus,Qt::WheelFocus,Qt::NoFocus。功能分别是TabFocus(按照名字以及英文的解释来看应该是使用Tab聚焦焦点,但是我点击了一下程序窗口依然聚焦了。),StrongFocs(同时具有ClickFocus和TabFocus的特性),WheelFocus(相当于是StrongFocus的升级版,鼠标滚轮也会导致聚焦焦点),NoFocus(看名字就知道啥意思了,不聚焦焦点)。那么本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"Qt控件设置默认追踪鼠标","slug":"Qt控件设置默认追踪鼠标","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/03/26/Qt控件设置默认追踪鼠标/","link":"","permalink":"http://example.com/2021/03/26/Qt%E6%8E%A7%E4%BB%B6%E8%AE%BE%E7%BD%AE%E9%BB%98%E8%AE%A4%E8%BF%BD%E8%B8%AA%E9%BC%A0%E6%A0%87/","excerpt":"","text":"先来看一下只使用mouseMoveEvent()而不设置默认追踪鼠标的效果。我的mouseMoveEvent是这么写的: 1234567void MyLabel::mouseMoveEvent(QMouseEvent *ev){ int x = ev->x(); int y = ev->y(); QString qs = QString("<center><h1>this is x: %1, and this is y: %2</h1></center>").arg(x).arg(y); this->setText(qs);} 这是效果图:我设置了如果鼠标点击就会使QLabel里的字体变色的效果。在图中,我一开始滑动鼠标,QLabel并没有任何变化,但是在我拖拽鼠标之后发生了变化。然后再来看看如何设置默认追踪鼠标的: 1234MyLabel::MyLabel(QWidget *parent) : QLabel(parent){ this->setMouseTracking(true);} 我在构造函数里加了setMouseTracking(true)。这是效果图:可以看到,我的鼠标一滑进应用程序框mouseMoveEnvent就立即触发。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"QtQtimer的使用","slug":"QtQTimer","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2021/03/24/QtQTimer/","link":"","permalink":"http://example.com/2021/03/24/QtQTimer/","excerpt":"","text":"建议直接看这篇文章:QTimer - 计时器类。今天做秒表的时候用到了QTimer,这是一个可以计时的类。那么直接说使用吧:想要用这个类需要导入QTimer库,下面是代码: 123456#include <QTimer>QTimer* qtmer_p = new QTimer(this); // 创建一个QTimer类qtmer_p -> setInterval(1000); // 设置发送信号的频率connect(qtmer_p, &QTimer::timeout, this, &MainWindow::time_out); // 连接信号与槽qtmer_p->start(); // 将QTimer类设置为活动状态qtmer_p->stop(); // 将QTimer类设置为非活动状态 QTimer类在start之后,每隔setInterval设置的时间(以毫秒为单位),就会发送一个timeout信号。调用stop可以停止让其发送。有种只发一次信号的操作,不过这不在本文的讨论范围,我在文章顶部加的那条超链接里有写这种操作。 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"linux下录制gif软件","slug":"linux下录gif软件","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2021/03/24/linux下录gif软件/","link":"","permalink":"http://example.com/2021/03/24/linux%E4%B8%8B%E5%BD%95gif%E8%BD%AF%E4%BB%B6/","excerpt":"","text":"这是一个叫做peek的软件,其功能不仅仅限于录制GIF。安装: 1pamcan -S peek 使用: 在命令行输入peek并回车,即可打开peek。peek是一个窗口,其中心是透明的,且鼠标可以穿透其中心(这么看来与其说是透明不如说是什么都没有比较恰当)。其中心中的所有内容都会在你按下录制按钮后被录制。录制完成后点击停止即可。如果嫌默认的窗口太小,可以用鼠标把窗口拉的大一些。 这样我写Qt的博客就可以录制GIF看效果了。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[]},{"title":"Qt创建工具栏","slug":"Qt创建工具栏","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2021/03/23/Qt创建工具栏/","link":"","permalink":"http://example.com/2021/03/23/Qt%E5%88%9B%E5%BB%BA%E5%B7%A5%E5%85%B7%E6%A0%8F/","excerpt":"","text":"书接前文(不知道前文是什么的可以在我博客的搜索框里搜索”Qt创建菜单栏”),这次我们来创建工具栏,代码还用上文的代码。在本文中我只改变mainwindow.cpp这一个文件。那么先看代码吧: 1234567891011121314151617181920212223242526#include "mainwindow.h"#include <QMenuBar>#include <QMenu>#include <QAction>#include <QToolBar>MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){ // set window size resize(500, 500); // create menu QMenuBar* qmb_p = menuBar(); // add menu QMenu* file_p = qmb_p -> addMenu("File"); //add action QAction* new_p = file_p -> addAction("New"); // add tool bar QToolBar* qtb_p = addToolBar("tool bar");}MainWindow::~MainWindow(){} addToolBar()返回了一个QToolBar类型的指针。这是其原型(这个函数是个重载函数,详情我就不写了,只写用到的这个): 1QToolBar *QMainWindow::addToolBar(const QString &title) 然后这是运行结果:可以看到,子菜单”File”下有个栏,这就是工具栏。接下来往工具栏里添加些东西: 1234567891011121314151617181920212223242526272829#include "mainwindow.h"#include <QMenuBar>#include <QMenu>#include <QAction>#include <QToolBar>MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){ // set window size resize(500, 500); // create menu QMenuBar* qmb_p = menuBar(); // add menu QMenu* file_p = qmb_p -> addMenu("File"); //add action QAction* new_p = file_p -> addAction("New"); // add tool bar QToolBar* qtb_p = addToolBar("tool bar"); // add actions QAction* hi_p = qtb_p -> addAction("Hi"); QAction* hl_p = qtb_p -> addAction("Hello");}MainWindow::~MainWindow(){} 这里添加了俩选项。直接看效果图吧:信号和槽帮助文档里都写了,我就不Copy了,22:30了,该睡觉了。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"Qt创建状态栏","slug":"Qt创建状态栏","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2021/03/23/Qt创建状态栏/","link":"","permalink":"http://example.com/2021/03/23/Qt%E5%88%9B%E5%BB%BA%E7%8A%B6%E6%80%81%E6%A0%8F/","excerpt":"","text":"书接前文(不知道前文是什么的可以在我博客的搜索框里搜索”Qt创建菜单栏”),这次我们来创建状态栏,代码还用上文的代码。在本文中我只改变mainwindow.cpp这一个文件。那么先看代码吧: 1234567891011121314151617181920212223242526#include "mainwindow.h"#include <QMenuBar>#include <QMenu>#include <QAction>#include <QToolBar>MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){ // set window size resize(500, 500); // create menu QMenuBar* qmb_p = menuBar(); // add menu QMenu* file_p = qmb_p -> addMenu("File"); //add action QAction* new_p = file_p -> addAction("New"); // add tool bar QToolBar* qtb_p = addToolBar("tool bar");}MainWindow::~MainWindow(){} addToolBar()返回了一个QToolBar类型的指针。这是其原型(这个函数是个重载函数,详情我就不写了,只写用到的这个): 1QToolBar *QMainWindow::addToolBar(const QString &title) 然后这是运行结果:可以看到,子菜单”File”下有个栏,这就是状态栏。接下来往状态栏里添加些东西: 1234567891011121314151617181920212223242526272829#include "mainwindow.h"#include <QMenuBar>#include <QMenu>#include <QAction>#include <QToolBar>MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){ // set window size resize(500, 500); // create menu QMenuBar* qmb_p = menuBar(); // add menu QMenu* file_p = qmb_p -> addMenu("File"); //add action QAction* new_p = file_p -> addAction("New"); // add tool bar QToolBar* qtb_p = addToolBar("tool bar"); // add actions QAction* hi_p = qtb_p -> addAction("Hi"); QAction* hl_p = qtb_p -> addAction("Hello");}MainWindow::~MainWindow(){} 这里添加了俩选项。直接看效果图吧:信号和槽帮助文档里都写了,我就不Copy了,22:30了,该睡觉了。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"Qt创建菜单栏","slug":"Qt创建菜单栏","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2021/03/23/Qt创建菜单栏/","link":"","permalink":"http://example.com/2021/03/23/Qt%E5%88%9B%E5%BB%BA%E8%8F%9C%E5%8D%95%E6%A0%8F/","excerpt":"","text":"Qt创建菜单栏本篇博文讲菜单栏的创建以及添加子菜单和选项(我也不知道这个翻译成什么好,请原谅野生自学者的学识浅薄)。先说下我的开发环境,我的操作系统是Arch Linux,使用的桌面是KDE,Qt版本是5.9.9。那么首先是创建菜单,这里我们需要一个继承了QMainWindow类的类,这里我是用Qt Creator创建的QMainWindow项目。那么这是我用Qt Creator创建的项目的源码(此时我仅仅在代码里加了些注释以便读者区分是哪些文件),我在创建的时候并没有选择生成form(即UI文件)。这是生成的项目文件: 123456789101112131415161718192021222324252627282930# untitled4.proQT += core guigreaterThan(QT_MAJOR_VERSION, 4): QT += widgetsCONFIG += c++11# The following define makes your compiler emit warnings if you use# any Qt feature that has been marked deprecated (the exact warnings# depend on your compiler). Please consult the documentation of the# deprecated API in order to know how to port your code away from it.DEFINES += QT_DEPRECATED_WARNINGS# You can also make your code fail to compile if it uses deprecated APIs.# In order to do so, uncomment the following line.# You can also select to disable deprecated APIs only up to a certain version of Qt.#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0SOURCES += \\ main.cpp \\ mainwindow.cppHEADERS += \\ mainwindow.h# Default rules for deployment.qnx: target.path = /tmp/$${TARGET}/binelse: unix:!android: target.path = /opt/$${TARGET}/bin!isEmpty(target.path): INSTALLS += target 这是头文件: 12345678910111213141516// mainwindow.cpp#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>class MainWindow : public QMainWindow{ Q_OBJECTpublic: MainWindow(QWidget *parent = nullptr); ~MainWindow();};#endif // MAINWINDOW_H 这是生成的cpp文件: 123456789101112// mainwindow.cpp#include "mainwindow.h"MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){}MainWindow::~MainWindow(){} 这是生成的main.cpp文件: 123456789101112#include "mainwindow.h"#include <QApplication>int main(int argc, char *argv[]){ QApplication a(argc, argv); MainWindow w; w.show(); return a.exec();} 创建菜单栏好了,在开始之前我们先运行代码,看看QMainWindow裸奔起来是什么样子:周围蓝色的是我的窗口装饰,与咱运行的QMainWindow无关。那么接下来创建菜单。先声明一下,由于我的输入法唤起快捷键与Qt Creator的某快捷键冲突,所以注释我就用英文写了。修改mainwindow.cpp文件: 12345678910111213141516171819#include "mainwindow.h"#include <QMenuBar>#include <QMenu>MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){ // set window size resize(500, 500); // create menu QMenuBar* qmb_p = menuBar(); // add menu QMenu* file_p = qmb_p -> addMenu("File");}MainWindow::~MainWindow(){} 可以看到,我包含了QMenuBar和QMenu这两个库。这里说一下menuBar(),它返回一个QMenuBar类型的指针。想要了解更多的细节请看Qt的帮助文档。然后就是qmb_p -> addMenu(“File”)了,它添加了一个叫做File的子菜单,其中addMenu()返回了一个QMenu类型的指针。这里是运行效果:。 添加选项然后我们可以给子菜单里添加选项(即Action),依然修改mainwindow.cpp: 12345678910111213141516171819202122#include "mainwindow.h"#include <QMenuBar>#include <QMenu>#include <QAction>MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){ // set window size resize(500, 500); // create menu QMenuBar* qmb_p = menuBar(); // add menu QMenu* file_p = qmb_p -> addMenu("File"); //add action QAction* new_p = file_p -> addAction("New");}MainWindow::~MainWindow(){} 这里我导入了QAction库,调用了file_p的addAction()成员函数并将其赋值给了new_p。addAction返回一个QAction类型的指针。addAction添加了一个Action(我个人称其为选项)。这是运行结果(由于我不想做GIF,所以这是单击子菜单”File”后的结果):可以看到,”File”子菜单下有了一个Action(选项)。可以添加多个Menu和多个Action,本来想要一一展示的,但是由于现在已经22:11了,待会儿还要写状态栏,于是我就不写了,读者知道可以添加多个就行。 那么本篇完,我要开始写下一篇了这一篇我居然写了一个多小时,果然我语言组织能力不行。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"QT自动内存回收","slug":"QT自动内存回收","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2021/03/23/QT自动内存回收/","link":"","permalink":"http://example.com/2021/03/23/QT%E8%87%AA%E5%8A%A8%E5%86%85%E5%AD%98%E5%9B%9E%E6%94%B6/","excerpt":"","text":"本博文并不会讲的很全,因为博主也是刚知道还有这么回事。在Qt中,QObject是顶层对象,当Qt程序关闭的时候,QOject对象会被自动释放掉。而因为QObject是顶层对象,所以其所有派生类在Qt程序关闭的时候都会被自动释放掉。 –这段话是我从别人的博客里抄来的。 实际的应用中,在一个动态分配的类指定了父对象后,其会被自动释放掉。但是如果没有指定父对象,则其不会被释放。 举个例子,我定义一个类,其继承自QPushButton,我在它的析构函数中加一个cout。这是其头文件: 1234567891011121314151617181920// mybutton.h#ifndef MYBUTTON_H#define MYBUTTON_H#include <QPushButton>#include <QWidget>class MyButton : public QPushButton{ Q_OBJECTpublic: explicit MyButton(QWidget *parent = nullptr); ~MyButton();signals:};#endif // MYBUTTON_H 这是其cpp文件: 12345678910111213141516// mybutton.cpp#include "mybutton.h"#include <QPushButton>#include <iostream>MyButton::MyButton(QWidget *parent) : QPushButton(parent){ setText("Hello, this is a button"); resize(300, 300);}MyButton::~MyButton(){ std::cout << "This function is going to be deleted." << std::endl;} 然后随便找个控件,在随便找的这个控件的构造函数里new一个我定义的MyButton类: 12345678910MyWidget::MyWidget(QWidget *parent) : QWidget(parent) , ui(new Ui::MyWidget){ resize(100, 100); move(100, 100); QPushButton* qp = new MyButton(this); qp->move(30, 30); ui->setupUi(this);} 可以看到,我并没有delete qp。但是在我编译运行程序并关闭程序后,我的控制台输出了我写在析构函数里的cout(Linux下的Typora有Bug,导致了我添加图片困难,所以我把输出复制了过来): 12316:42:03: Starting /home/fire/codeSet/QtSet/build-untitled1-unknown-Debug/untitled1 ...This function is going to be deleted.16:42:08: /home/fire/codeSet/QtSet/build-untitled1-unknown-Debug/untitled1 exited with code 0 然后举一个没有被内存回收的例子: 在上面的构造函数中(即第三个代码块),删掉这段代码中的this: 1234// 删除前QPushButton* qp = new MyButton(this);// 删除后QPushButton* qp = new MyButton(); 其他部分不变,编译运行程序并关闭程序,输出如下: 1217:24:57: Starting /home/fire/codeSet/QtSet/build-untitled1-unknown-Debug/untitled1 ...17:24:59: /home/fire/codeSet/QtSet/build-untitled1-unknown-Debug/untitled1 exited with code 0 可以看到,cout语句并没有执行。好,那么看到这里,相信机智的你已经明白了。那么,本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"}],"tags":[]},{"title":"vim交换CAPSLOCK和ESC","slug":"vim交换CAPSLOCK和ESC","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2021/03/21/vim交换CAPSLOCK和ESC/","link":"","permalink":"http://example.com/2021/03/21/vim%E4%BA%A4%E6%8D%A2CAPSLOCK%E5%92%8CESC/","excerpt":"","text":"本文只讲Linux下的方法。关于把CAPSLOCK映射成ESC键,网上有各种各样的教程。总结起来无非就是三种。 1.在终端启动时执行把这段代码写进shell的配置文件: 1xmodmap -e 'clear Lock' -e 'keycode 0x42 = Escape' 然后在打开终端的时候就会自动删除CAPSLOCK并将其换成Esc。这么做需要有xorg-xmodmap这个包才能行。这种方法有一个缺点: 打开终端时会卡几秒钟(推测是因为要执行xmodmap命令)。 2.在vim启动时执行把这段代码写入.vimrc中: 12au VimEnter * !xmodmap -e 'clear Lock' -e 'keycode 0x42 = Escape'au VimLeave * !xmodmap -e 'clear Lock' -e 'keycode 0x42 = Caps_Lock' 这样在启动vim时会把CAPSLOCK换成ESC,在离开vim后会换回来。不过这样做缺点也很明显: 打开vim时和离开vim时都会卡个几秒钟。同上一样需要xorg-xmodmap。 3.配置Xorg映射表关于这个映射表是什么,Arch Wiki上有详细的解释: Xmodmap。此时让我们再次高呼Arch Wiki永远滴神。那么这一步应该干什么?首先复制以下代码: 12clear Lockkeycode 0x42 = Escape 然后按照Arch Wiki上推荐的方法,在Home目录下创建一个.Xmodmap文件(为了防止有读者不明白Home目录是啥,先解释以下,Home即是$HOME。如果还不懂,Home即是~。即家目录。而不是/home或者~/home)。然后把上面的代码复制进~/.Xmodmap里。然后编辑~/.xinitrc,在里面加入这样一段代码: 123if [ -f $HOME/.Xmodmap ]; then/usr/bin/xmodmap $HOME/.Xmodmapfi 我来翻译一下: 如果Home目录下存在这个叫做.Xmodmap的文件,那么执行/usr/bin/xmodmap $HOME/.Xmodmap。/usr/bin/xmodmap是xmodmap可执行文件的路径,$HOME/.Xmodmap是参数。所以其实我们也可以在.xinitrc中直接这么写: 1xmodmap -e 'clear Lock' -e 'keycode 0x42 = Escape' 那么为什么不这样写呢?因为这样写没有逼格。。。不过同上面的几点一样,想要让这段代码正常执行,你同样需要安装xorg-xmodmap包(毕竟这条命令本身就是依靠xmodmap执行的)。然后重启xorg,因为.xinitrc是在xorg启动后执行的,所以一定要重启xorg。那么怎么重启xorg呢?直接关机重启就行。。。重启之后可以按一下CAPSLOCK键,键盘指示灯没有亮起就算成功。 4.使用KDE自带的键位改写此时,让我们怀着虔诚的心,大声颂扬BugDEKDE的名字。KDE的设置里,有能够更改CAPSLOCK键功能的功能。其中一条就是CAPSLOCK与ESC键互换。怎么个互换法呢?就是字面上的意思,即ESC变为CAPSLOCK,CAPSLOCK变为ESC。那么怎么打开这个设置呢?打开KDE的系统设置,找到”硬件”下的”输入设备”,单击”输入设备”,单击”键盘”,单击”高级”,勾选”配置键盘选项”,找到”大写锁定行为”,打开”大写锁定行为”的下拉列表,找到”交换ESC和大写锁定”,勾选上,单击右下角的”应用”。然后你的ESC就和CAPS互换了。不过我也不知道这个方法需不需要xorg-xmodmap这个包。 那么本篇博客的内容到这里就结束了,别看这次的篇幅这么短,写下来可是花费了一番功夫。也许有的朋友会疑惑我为什么会在HOME那里写的那么详细,这是我怕以后我推荐小白看我的博客时看的稀里糊涂的(绝不是因为我当年就把家目录当成/home和~/home从而勾起了回忆)。那么,本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vim","slug":"blog/vim","permalink":"http://example.com/categories/blog/vim/"}],"tags":[{"name":"vim","slug":"vim","permalink":"http://example.com/tags/vim/"}]},{"title":"g++报错will be initialized after","slug":"cpp_will_be_init","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2021/03/21/cpp_will_be_init/","link":"","permalink":"http://example.com/2021/03/21/cpp_will_be_init/","excerpt":"","text":"报错如下: 12345678910111213╭─fire@butterfly ~/codeSet/CPPCode ╰─➤ g++ -Werror -Wextra -Wall -pedantic -Wconversion test.cpptest.cpp: 在复制构造函数‘HasPtr::HasPtr(const HasPtr&)’:test.cpp:11:18: 错误:‘HasPtr::ps’ will be initialized after [-Werror=reorder] 11 | std::string* ps; | ^~test.cpp:10:9: 错误: ‘int HasPtr::i’ [-Werror=reorder] 10 | int i; | ^test.cpp:13:5: 错误:在此处初始化后被初始化 [-Werror=reorder] 13 | HasPtr(const HasPtr& hp):ps(new (nothrow) string(*(hp.ps))), i(hp.i){}; | ^~~~~~cc1plus:所有的警告都被当作是错误 造成错误的代码: 123456789101112131415161718192021222324class HasPtr{ public: int i; std::string* ps; HasPtr():ps(new string()), i(0){}; HasPtr(const HasPtr& hp):ps(new (nothrow) string(*(hp.ps))), i(hp.i){}; HasPtr(const string &s):ps(new string(s)){}; HasPtr& operator=(const HasPtr& hp) { delete ps; ps = new string(*(hp.ps)); i = hp.i; return *this; }; ~HasPtr() { delete ps; }; void tell() { cout << *ps << endl; }}; “真是奇怪,我代码看着也没啥问题啊?”这么想着的我Google了一下,在StackOverFlow上找到了回答:gcc warning” ‘will be initialized after’。原来是这么回事:因为我先定义了int i,再定义了string* ps,但是我构造函数里先初始化的ps,再初始化的i。因为初始化的顺序与定义的顺序不对,所以出现了错误。虽然StackOverFlow里有说明原因, 但是我真的很想吐槽一下。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"}],"tags":[]},{"title":"vue组件导入js文件中的函数","slug":"vue组件导入js文件函数","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2021/03/21/vue组件导入js文件函数/","link":"","permalink":"http://example.com/2021/03/21/vue%E7%BB%84%E4%BB%B6%E5%AF%BC%E5%85%A5js%E6%96%87%E4%BB%B6%E5%87%BD%E6%95%B0/","excerpt":"","text":"可能这个问题对于前端的朋友们来说很简单,但是对于我这个半吊子前端来说着实有点难度,所以我就记录下来了。我有一个名为dev_helper.js的文件,里面定义了一个函数: 123456789101112131415161718192021222324252627282930import axios from 'axios'export function CheckToken(){ var token = localStorage.getItem('token') if(token === '' || !token) { return -1; } else { var form = new FormData(); form.append('token', token); axios({ method: "POST", url: 'http://localhost:7788/check_token', data: form }).then(res=>{ if(res.data.code === 200) { localStorage.setItem("token", res.data.token); return 1; } else { return 0; } }) }} 这是一个用LocalStorage进行token校验的函数,不过我们的重点是vue组件导入该函数,所以我们只需要记住在函数前加个export就行了。然后这是vue组件中的导入代码: 1import {CheckToken} from "@/dev_helper" 因为我的js文件与vueProject/src/components同级,所以路径用的是@/dev_help。这样就可以直接在.vue文件中使用CheckToken函数了。 还有一种方法,其实和上面的那一个方法差不多,不过这个有点类似C++中的namespace了。这是js文件中的代码: 12345678910111213141516171819202122232425262728293031323334import axios from 'axios'function CheckToken(){ var token = localStorage.getItem('token') if(token === '' || !token) { return -1; } else { var form = new FormData(); form.append('token', token); axios({ method: "POST", url: 'http://localhost:7788/check_token', data: form }).then(res=>{ if(res.data.code === 200) { localStorage.setItem("token", res.data.token); return 1; } else { return 0; } }) }}export const Henv = { CheckToken } 其中重点在最后4行,export const Henv相当于创建了一个叫做Henv的命名空间(不知道这个比喻恰不恰当),导入的时候只需要导入这个命名空间(即Henv),然后调用的时候直接Henv.functionName()即可。需要注意的是,export后面的const是必须要加的。这是vue组件中的代码: 12345// 导入import {Henv} from "@/dev_helper"// 调用 Henv.CheckToken(); 就是这样。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[]},{"title":"requests报错SSLError","slug":"requests请求ssl报错","date":"un66fin66","updated":"un22fin22","comments":true,"path":"2021/03/20/requests请求ssl报错/","link":"","permalink":"http://example.com/2021/03/20/requests%E8%AF%B7%E6%B1%82ssl%E6%8A%A5%E9%94%99/","excerpt":"","text":"参考python request 请求https 挂代理报错。这是我在挂了代理后用requests请求Youtube时发生的错误,报错: 1requests.exceptions.SSLError: HTTPSConnectionPool(host='www.youtube.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:1123)'))) 看到是SSLError后我果断在requests.get()的参数中加入了verify=False,结果还是不行。然后Google一番后,发现是proxies参数的问题(注: 如果不加proxies参数,只给终端挂代理,运行py文件后会报:requests.exceptions.InvalidURL: Proxy URL had no scheme, should start with http:// or https://): 1proxies = {"http": "http://localhost:8889", "https": "socks5://localhost:1089"} 让https走socks代理,就解决问题了。毛病可真多。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"原生js增删class","slug":"原生js增删class","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/03/19/原生js增删class/","link":"","permalink":"http://example.com/2021/03/19/%E5%8E%9F%E7%94%9Fjs%E5%A2%9E%E5%88%A0class/","excerpt":"","text":"今天是沉迷前端无法自拔的一天…那么本篇文章要说的是js增删class。参考自原生js增加或者删除class。并在MDN上查到了文档:Element.classList。Html5增加了classList,MDN上是这么写的:Element.classList 是一个只读属性,返回一个元素的类属性的实时 DOMTokenList 集合。语法: 1const elementClasses = elementNodeReference.classList; elementClasses 是一个 DOMTokenList 表示 elementNodeReference 的类属性 。如果类属性未设置或为空,那么 elementClasses.length 返回 0。虽然 element.classList 本身是只读的,但是你可以使用 add() 和 remove() 方法修改它。add()是添加类名,如: 1button_dom.classList.add("using_btn"); 这段代码会给button_dom添加一个using_btn的类。remove是删除类名,如: 1button_dom.classList.remove('using_btn'); 这段代码会删除掉button_dom的using_btn类。(2021/05/06更新) 还有几个方法也很有用: 12classList.contains(value); 返回布尔值,表示给定的value是否已经存在。toggle(value); 如果类名中已经存在指定的value,则删除;如果不存在,则添加。 那么本篇完。前端真香.jpg。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"JavaScript","slug":"blog/JavaScript","permalink":"http://example.com/categories/blog/JavaScript/"}],"tags":[]},{"title":"vue-cli删除eslint","slug":"vue_cli关闭eslint","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/03/19/vue_cli关闭eslint/","link":"","permalink":"http://example.com/2021/03/19/vue_cli%E5%85%B3%E9%97%ADeslint/","excerpt":"","text":"多亏这个了vue.js - 如何在vue cli 3生成的项目中完成删除eslint的操作?。Eslint有的时候会让人感到十分烦人,比如你在index.html导入了js,到组件上用的时候Eslint偏偏说你没定义,所以这篇博客记录怎么删除eslint。在package.json中找到所有和eslint有关的东西,删除掉,然后运行npm i。然后重启项目,就没有eslint来打扰你了。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[]},{"title":"vue-cli使用layui","slug":"vue_cli使用layui","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/03/19/vue_cli使用layui/","link":"","permalink":"http://example.com/2021/03/19/vue_cli%E4%BD%BF%E7%94%A8layui/","excerpt":"","text":"先clone下来layui,github地址:layui。然后把layui/src下的全部文件复制到vue项目的public文件夹下,之后在index.html中引入css和js文件: 12<link rel="stylesheet" href="./css/layui.css"><script type="text/javascript" src="./layui.js"></script> 然后就是使用了,随便在一个vue文件里的script下输入: 12345layui.use(['layer'], function (){ var layer = layui.layer; layer.msg("hello");}); 打开该页面,如果自动弹出”hello”,就说明成功了。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[]},{"title":"Flask限制访问次数","slug":"flask限制访问次数","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2021/03/17/flask限制访问次数/","link":"","permalink":"http://example.com/2021/03/17/flask%E9%99%90%E5%88%B6%E8%AE%BF%E9%97%AE%E6%AC%A1%E6%95%B0/","excerpt":"","text":"今天访问网站的时候发现访问的过于频繁被人限制访问次数了,于是我决定自己也玩玩限制次数。其实直接拿Redis实现就好,不过这里我就不用redis了,我用的是flask-limiter。文档在这里: FlaskLimiter。如果不想读英语的话,可以看一下这位博主写的Flask-Limit使用详细说明,很不错。 我这里就只写简单的应用了。 1234567891011121314151617181920from flask import Flask, make_response, request, url_forfrom flask_limiter import Limiterfrom flask_limiter.util import get_remote_addressapp = Flask(__name__)# 实例化limiter# 一天可以请求300次,一个小时可以请求5次limiter = Limiter(key_func=get_remote_address, default_limits=["300 per day", "5 per hour"])limiter.init_app(app=app)@app.route("/slow", methods=["GET"])# 一天可以请求5次,一个小时可以请求5次(这两者并不冲突)@limiter.limit(["5 per day", "5 per hour"])def slow(): return ":("# 当请求次数到达阀值后(超速后),会触发429状态码,所以我们可以在这里通知用户超速了(如果不定义的话,limiter会自己返回一个提示)@app.errorhandler(429)def over_per(code): return "Your Request too fast." key_func是获取ip地址的函数,这是get_remote_address()的源码: 1234567def get_remote_address(): """ :return: the ip address for the current request (or 127.0.0.1 if none found) """ return request.remote_addr or '127.0.0.1' 如果用Nginx的话就得自定义方法了。这次我又写注释了,所以就不解释了。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"},{"name":"flask","slug":"blog/Python/flask","permalink":"http://example.com/categories/blog/Python/flask/"}],"tags":[]},{"title":"cowsay --萌萌的终端玩具","slug":"cowsay","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/03/15/cowsay/","link":"","permalink":"http://example.com/2021/03/15/cowsay/","excerpt":"","text":"cowsay是我在昨天和群友唠嗑时发现的好玩的东西。那么cowsay是什么?cowsay是一个能够输出数行文本的工具(玩具?)。先看看它的一个输出: 1234567891011121314151617181920╭─fire@butterfly ~/volantis_try/source/_posts ‹master*› ╰─➤ cowsay -f stegosaurus hello _______ < hello > ------- \\ . . \\ / `. .' " \\ .---. < > < > .---. \\ | \\ \\ - ~ ~ - / / | _____ ..-~ ~-..-~ | | \\~~~\\.' `./~~~/ --------- \\__/ \\__/ .' O \\ / / \\ " (_____, `._.' | } \\/~~~/ `----. / } | / \\__/ `-. | / | / `. ,~~| ~-.__| /_ - ~ ^| /- _ `..-' | / | / ~-. `-. _ _ _ |_____| |_____| ~ - . _ _ _ _ _> 看到这个输出你是不是明白了什么呢?(明白了确实是玩具)-f 选项能够选择输出不同的图像,而hello是输出的内容。想要看都有哪些图像,可以输入cowsay -l来查看。下面是cowsay支持的图像列表 12345678910╭─fire@butterfly ~/volantis_try/source/_posts ‹master*› ╰─➤ cowsay -l Cow files in /usr/share/cows:beavis.zen blowfish bong bud-frogs bunny cheese cower daemon default dragondragon-and-cow elephant elephant-in-snake eyes flaming-sheep ghostbustershead-in hellokitty kiss kitty koala kosh luke-koala meow milk moofasa moosemutilated ren satanic sheep skeleton small sodomized stegosaurus stimpysupermilker surgery telebears three-eyes turkey turtle tux udder vadervader-koala www 我们来试一试kiss: 123456789101112131415161718192021222324252627╭─fire@butterfly ~/volantis_try/source/_posts ‹master*› ╰─➤ cowsay -f kiss "kiss~" _______ < kiss~ > ------- \\ \\ ,;;;;;;;, ;;;;;;;;;;;, ;;;;;'_____;' ;;;(/))))|((\\ _;;((((((|)))) / |_\\\\\\\\\\\\\\\\\\\\\\\\ .--~( \\ ~)))))))))))) / \\ `\\-(((((((((((\\\\ | | `\\ ) |\\ /|) | | `. _/ \\_____/ | | , `\\~ / | \\ \\ / | `. `\\| / | ~- `\\ / \\____~._/~ -_, (\\ |-----|\\ \\ ';; | | :;;;' \\ | / | | | | | 看,这是kiss的图像。博主感觉自己受到了暴击。咳咳,那么这个玩意儿主要用途(玩法)是什么呢?博主目前发现的最有用的玩法就是把命令写在.zshrc里(bash则是写在.bashrc),然后每一次打开终端都受到一次暴击。不过说起来,这玩意儿搭配lolcat还挺好玩的…本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"},{"name":"乱七八糟","slug":"blog/乱七八糟","permalink":"http://example.com/categories/blog/%E4%B9%B1%E4%B8%83%E5%85%AB%E7%B3%9F/"}],"tags":[]},{"title":"Flask屏幕截图","slug":"flask屏幕截图","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/03/15/flask屏幕截图/","link":"","permalink":"http://example.com/2021/03/15/flask%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE/","excerpt":"","text":"没啥好说的,直接看代码吧。这次我写了详细的注释。 123456789101112131415161718192021222324from flask import Flask, make_responsefrom PIL import ImageGrabimport ioapp = Flask(__name__)@app.route("/")def response(): # 截图 im = ImageGrab.grab() # 创建BytesIO对象 bio = io.BytesIO() # 把截的图存储进内存 im.save(bio, format="png") # 构建响应体 res = make_response(bio.getvalue()) # 设置响应头, content-type声明了数据的类型,以便浏览器识别 res.headers["content-type"] = "image/png" return resif __name__ == '__main__': app.run("localhost", 7788) 不过关于res.headers[“content-type”] = “image/png”这段代码我要说一下,如果不加上这个键值对的话,浏览器说不定会把发送过来的二进制内容当作文本处理,从而让你的图片以文本的形式呈现出来(会显示出一堆乱码)。为什么我会知道这个知识点呢?这是一个因为没有深入了解浏览器工作机制而导致的悲伤的故事。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"},{"name":"flask","slug":"blog/Python/flask","permalink":"http://example.com/categories/blog/Python/flask/"}],"tags":[]},{"title":"KDE下拉式终端","slug":"KDE下拉式终端","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2021/03/03/KDE下拉式终端/","link":"","permalink":"http://example.com/2021/03/03/KDE%E4%B8%8B%E6%8B%89%E5%BC%8F%E7%BB%88%E7%AB%AF/","excerpt":"","text":"这是一个十分舒服的下拉式终端,Arch Wiki上有关于它的详情: Yakuake。由于我安装KDE的时候直接安装了kde-applications这个包,所以刚安装好就有这个终端。想要启用它可以在终端输入: 1yakuake 来打开它,也可以使用KDE的Alt+Space开启搜索,在里面输入Yakuake来打开它。打开它之后可以设置快捷键来快速呼出它。非常好用,配上透明效果极佳。 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[]},{"title":"vim窗口操作","slug":"vim窗口操作","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2021/03/02/vim窗口操作/","link":"","permalink":"http://example.com/2021/03/02/vim%E7%AA%97%E5%8F%A3%E6%93%8D%E4%BD%9C/","excerpt":"","text":"vim窗口操作参考(其实已经差不多可以说是转载了)自vim 多窗口编辑。建议直接看这篇博客,写的非常好,而且有图片实例。 启动多窗口编辑vim -o file1 file2 显示为水平分割成两半的窗口。vim -O file1 file2 显示为垂直分割成两半的窗口。vim -o5 file1 file2 将分配5个相同的窗口,其中3个是闲置的。 vim运行中的多窗口编辑:split 新建一个窗口,水平分割当前窗口。:vsplit 新建一个窗口,垂直分割当前窗口。如果这两个命令后面加了一个文件路径,那么新打开的窗口里显示的会是那个文件。 分割窗口的选项:[n] split(vsplit) [++opt] [+cmd] [file] 命令中:n 为vim指定在新窗口中显示的行数,且新窗口的大小刚好容纳该行数,新窗口位于画面顶端opt 传递vim选项信息给新的窗口会话(请注意,它的前面必须加上两个加号)cmd 传入欲在新窗口中执行的命令(请注意,它的前面必须加上一个加号)file 指定在新窗口中编辑的文件 :sview filename 以只读的方式水平分割打开一个新窗口:sfind [++opt] [+cmd] [file] 和split的运作方式相似,但在path中寻找filename,如果vim未找到文件则不显示 在窗口间移动光标按住Ctrl + w, 然后按h,j,k,l这几个键来切换窗口(也可以先按Ctrl+w, 松开后再按h, j, k, l。h, j, k, l对应左,下,上,右移动)。Ctrl + w + w 这个命令会在所有窗口中循环移动。Ctrl + w + t 移动到最左上角的窗口。Ctrl + w + b 移动到最右下角的窗口。Ctrl + w + p 移动到前一个访问的窗口。 移动窗口vim中有两种移动窗口方式,一种只是简单地在屏幕上切换窗口,尺寸维持不变;另一种则是改变窗口的实际布局,还会调整尺寸,以填充它移动的位置。 移动窗口本身(轮换或交换) Ctrl + w + r:向右或向下方交换窗口,而Ctrl + w + R则和它方向相反。 光标会随着窗口而移动 Ctrl + w + x:交换同列或同行的窗口的位置。vim默认交换当前窗口的与它下一个窗口的位置,如果下方没有窗口,则试着与上一个窗口交换位置。亦可在此命令前加上数量,与制定的窗口交换位置。 移动窗口并改变其布局注:下面的第三个字母都是大写,按玩Ctrl + w之后,按shif + 相应的字母。后面用^代替CtrlCtrl + w + K 移动当前窗口至屏幕顶端,并占用全部宽度^WJ 移动窗口至屏幕底端,并占用全部宽度^WH 移动窗口至屏幕左端,并占用全部高度^WL 移动窗口至屏幕右端,并占用全部高度 ^WT 移动窗口至屏新的现有分页 调整窗口尺寸改变当前窗口的尺寸,同时当然也会影响到其他窗口。在gvim和vim中,可以用鼠标点击窗口的顶部白色条并窗口直接调整尺寸。 也可以直接用命令,调整尺寸命令也是以Ctrl + W开头:Ctrl + W + = 让所有窗口调整至相同尺寸(平均划分)Ctrl + W + - 将当前窗口的高度减少一行,也可在ex命令中,:resize -4明确指定减少的尺寸Ctrl + W + + 将当前窗口的高度增加一行。同样在ex命令中,:resize +n 明确指定增加尺寸 Ctrl + W + < 将当前窗口的宽度减少Ctrl + W + > 将当前窗口的宽度增加 Ctrl + W + | 将当前窗口的宽度调到最大,也可他哦你通过ex命令:vertical resize n明确指定改变宽度 分页编辑除了使多窗口编辑外,vim还允许创建多个分页(tab),每个分页各有独自的行为,类似于浏览器firefox或chrome的分页浏览功能。 我们可以在vim或gvim中使用分页,但在gvim会更好用。最终要的命令:tabnew filename 打开新分页并编辑新文件(可选的)。如果未指定文件,则只打开新分页,并附上空的缓冲区。:tabclose 关闭当前分页:tabonly 关闭其他所有的分页。如果其他分页中有修改过的文件,则不会移除该分页,除非设置了autowrite选项。此时,所有修改过的文件都在分页关闭前写入磁盘。 游走分页可以直接点鼠标来移动到另一个分页,也可以用命令Ctrl + PageDown:移动到下一个分页Ctrl + PageUp:移动到上一个分页 关闭与离开窗口有4种关闭窗口的方式,分别是:离开(quit)、关闭(close)、隐藏(hide)、关闭其他窗口 ^代表Ctrl键^Wq,离开当前窗口^Wc,关闭当前的窗口^Wo,关闭当前窗口以外的所有窗口 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vim","slug":"blog/vim","permalink":"http://example.com/categories/blog/vim/"}],"tags":[{"name":"vim","slug":"vim","permalink":"http://example.com/tags/vim/"}]},{"title":"python闭包","slug":"python闭包","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/03/01/python闭包/","link":"","permalink":"http://example.com/2021/03/01/python%E9%97%AD%E5%8C%85/","excerpt":"","text":"关于闭包的概述:”在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中实现词法绑定的一种技术。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。闭包跟函数最大的不同在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。捕捉时对于值的处理可以是值拷贝,也可以是名称引用,这通常由语言设计者决定,也可能由用户自行指定(如C++)。” –wikipedia 那么下面是代码实现: 1234567def func(): def func1(): print("Hello") return func1func()() 程序输出: 1Hello 就我个人而言,在python开发中很少用到闭包。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"python猴子补丁","slug":"python猴子补丁","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/03/01/python猴子补丁/","link":"","permalink":"http://example.com/2021/03/01/python%E7%8C%B4%E5%AD%90%E8%A1%A5%E4%B8%81/","excerpt":"","text":"什么是猴子补丁?”猴补丁(英语:Monkey patch)是一种很脏的编程技巧,用拼凑代码的方法修改程序逻辑。这种技巧也叫鸭子双关。[1]猴补丁意思是用类似双关的技巧拼凑出和常规程序相左的程序逻辑,这种技巧只会在运行时刻生效。猴补丁的出现说明程序本身设计有缺陷,它用在网页和数据库上就是SQL注入攻击,Unix Shell的flag使用不当也会产生类似的安全问题,比如将文件命名为“-x”形式,命令行就可能将文件名认作一个传递的参数而造成运行异常。” –wikipedia 其实就是在程序运行时修改类或模块。那么代码如下: 123456789101112131415class Test(object): def func(self): print("I'll tell you ", end='')def monkey(self): print("The truth.")test1 = Test()test1.func() # 此时程序输出的是I'll tell youTest.func = monkeytest1.func() # 此时程序输出的是The truth. 其实就我个人的认知来说,如果用猴子补丁会导致程序逻辑的混乱,虽然作者本人也许不会受影响,但是其他人在维护代码的时候一定会骂他。。。写这篇博客只是因为觉得猴子补丁好玩,从C和C++回来后觉得python太有趣了,哈哈哈。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"JS修改CSS变量","slug":"JS修改CSS变量","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2021/02/28/JS修改CSS变量/","link":"","permalink":"http://example.com/2021/02/28/JS%E4%BF%AE%E6%94%B9CSS%E5%8F%98%E9%87%8F/","excerpt":"","text":"这个问题折磨我快俩小时了,其中因为各种因素导致内在原因一直没有被我发现,现在我也只是略懂了一些,不过代码好歹是能用了。 声明CSS变量声明一个CSS变量需要在变量名前加俩-号,如: 1234.title{ --vara: red;} 使用CSS变量直接看代码(请忽略变量名和实际用处的牛头不对马嘴): 123456789.title{ --h1-size: 10px; --h1-color: red; --title-align: right; text-align: var(--title-align); font-size: var(--h1-size); color: var(--h1-color);} 不过直接这么定义的话,会导致js无法修改变量的值(也可能是Chromium的Bug)。而且网上的其他教程一般都是类似这么定义的: 12345678910111213:root{ --h1-size: 10px; --h1-color: red; --title-align: right;}.title{ text-align: var(--title-align); font-size: var(--h1-size); color: var(--h1-color);} 很多教程里都说:root是全局作用域,不过不知道为何,我这里的.title无法访问:root下的变量(若无法访问变量的话,就会取默认值)。那我也没有办法,只能在认为:root不是全局作用域的情况下动手了。而且因为上面提到过在同级作用域声明的变量无法被js修改,所以我就索性不写变量,直接用JS设置变量了。那么JS如何设置变量以及我代码最后写成了什么样子请看下一标题。 JS修改CSS变量代码如下: 123document.documentElement.style.setProperty("--h1-size", "10px");document.documentElement.style.setProperty("--h1-color", "blue");document.documentElement.style.setProperty("--title-align", "right"); 这段代码就能修改CSS的变量了。不过设置的貌似是全局变量,会被局部变量覆盖。那么我的代码如下(无关部分我就缩写了): 12345678910111213141516171819202122232425<html> <h1 class="title">{{ title }}</h1></html><style>:root{ --h1-size: 10px; --h1-color: red; --title-align: right;}.title{ text-align: var(--title-align); font-size: var(--h1-size); color: var(--h1-color);}</style><script> document.documentElement.style.setProperty("--h1-size", "10px"); document.documentElement.style.setProperty("--h1-color", "blue"); document.documentElement.style.setProperty("--title-align", "right");</script> 虽然:root下的变量无法作用到.title里,但是这里还是写一下代表有这个变量吧。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"JavaScript","slug":"blog/JavaScript","permalink":"http://example.com/categories/blog/JavaScript/"}],"tags":[]},{"title":"Vue安装路由","slug":"vue安装路由","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2021/02/25/vue安装路由/","link":"","permalink":"http://example.com/2021/02/25/vue%E5%AE%89%E8%A3%85%E8%B7%AF%E7%94%B1/","excerpt":"","text":"由于我不想到时候再去官网看(其实是怕忘了官网网址), 于是乎先记在博客上。官网网址: Vue Router 中文文档。 NPM安装1npm install vue-router 如果在一个模块化工程中使用它,必须要通过 Vue.use() 明确地安装路由功能: 1234import Vue from 'vue'import VueRouter from 'vue-router'Vue.use(VueRouter) 如果使用全局的 script 标签,则无须如此 (手动安装)。 Vue CLI如果你有一个正在使用 Vue CLI 的项目,你可以以项目插件的形式添加 Vue Router。CLI 可以生成上述代码及两个示例路由。它也会覆盖你的 App.vue,因此请确保在项目中运行以下命令之前备份这个文件: 1vue add router 本篇完。这篇只是我用来偷懒才写的,还是建议看官网。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[]},{"title":"ssh连接Termux","slug":"ssh连接Termux","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/02/22/ssh连接Termux/","link":"","permalink":"http://example.com/2021/02/22/ssh%E8%BF%9E%E6%8E%A5Termux/","excerpt":"","text":"参考博客电脑通过ssh登录到Termux – Moneys。 先安装openssh: 12apt updateapt install openssh 然后启动sshd服务: 1sshd 然后配置登录密钥。如果电脑生成过密钥,可以跳过这一步: 1ssh-keygen 默认会生成在.ssh下,然后把生成的id_rsa.pub拷贝到手机里。注意,这一步的目的是要把文件拷贝进Termux。所以建议拷贝到手机的dcim, downloads, movies, music, pictures, shared这几个文件夹任意一个里。因为这样可以用termux直接访问。这里我假设拷贝进了dcim里。然后termux申请读写权限: 1termux-setup-storage 获得读写权限后, 执行以下命令: 123cd ~/.sshcp ~/storage/dcim/id_rsa.pub ./cat id_rsa.pub >> authorized_key 然后手机查看当前用户名。 1whoami 查看当前ip: 1ifconfig -a 然后电脑使用ssh连接到termux: 1ssh username@ip -p 8022 username就是whoami输出的内容,ip就是手机的ip地址。这样就可以了。本篇完。好困,午休time。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Termux","slug":"blog/Termux","permalink":"http://example.com/categories/blog/Termux/"}],"tags":[]},{"title":"Termux获得文件访问权限","slug":"termux获得文件访问权限","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/02/22/termux获得文件访问权限/","link":"","permalink":"http://example.com/2021/02/22/termux%E8%8E%B7%E5%BE%97%E6%96%87%E4%BB%B6%E8%AE%BF%E9%97%AE%E6%9D%83%E9%99%90/","excerpt":"","text":"仔细一看发现我居然没有写过关于Termux的文章。。。那么先简介一下,Termux是一个Android下的终端模拟器, 无需root即可使用。可以在Google Play商店里找到。 好,那么简介完毕,接下来说这篇博文的标题: 获得文件访问权限。只需要在Termux里执行这条命令即可: 1termux-setup-storage 然后手机大概会弹窗提示你Termux在申请存储权限,允许即可。之后Termux中会创建一个新目录~/storage,其中的文件夹有:dcim, downloads, movies, music, pictures, shared。这几个文件夹是不是有点熟悉?没错,就是你想的那样。不过只能访问这几个文件夹(我不知道root后能否访问根目录下全部文件夹), 但是能够访问这些文件夹也足够在手机与Termux之间传递文件了。 好,那么,本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Termux","slug":"blog/Termux","permalink":"http://example.com/categories/blog/Termux/"}],"tags":[]},{"title":"Arch安装netstat和ifconfig","slug":"arch使用netstat","date":"un66fin66","updated":"un22fin22","comments":true,"path":"2021/02/20/arch使用netstat/","link":"","permalink":"http://example.com/2021/02/20/arch%E4%BD%BF%E7%94%A8netstat/","excerpt":"","text":"关于netstat的原文:Arch Forums。关于ifconfig的原文: Arch Forums。 博主精简内容之后: 输入pacman -S net-tools即可。里面有老哥不推荐使用net-tools,不过这篇博文只讲安装netstat和ifconfig。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[]},{"title":"C语言获取时间","slug":"C语言获取时间","date":"un66fin66","updated":"un22fin22","comments":true,"path":"2021/02/20/C语言获取时间/","link":"","permalink":"http://example.com/2021/02/20/C%E8%AF%AD%E8%A8%80%E8%8E%B7%E5%8F%96%E6%97%B6%E9%97%B4/","excerpt":"本篇博文中获取的时间格式是这样的: Sat Feb 20 17:41:09 2021 。 那么代码如下:","text":"本篇博文中获取的时间格式是这样的: Sat Feb 20 17:41:09 2021 。 那么代码如下: 12345678910#include <stdio.h>#include <time.h>int main(void){ time_t tm; tm = time(NULL); printf("%s\\n", ctime(&tm)); return 0;} 当前时间和日期是由库函数time返回的,它实际上返回的是自Unix纪元即1970年1月1日0点0分0秒(国际标准时间)以来的秒数。下一个库函数ctime把该整数值转换成直观可读的时间格式。(摘抄自Unix网络编程卷一) 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"C","slug":"blog/C","permalink":"http://example.com/categories/blog/C/"}],"tags":[]},{"title":"好玩的ncurses库","slug":"好玩的ncurses库","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2021/02/17/好玩的ncurses库/","link":"","permalink":"http://example.com/2021/02/17/%E5%A5%BD%E7%8E%A9%E7%9A%84ncurses%E5%BA%93/","excerpt":"","text":"在我苦苦寻找类似pacman更新包时输出#号的方法时,一位大佬告诉我使用ncurses库。然后我就发现这玩意儿真好玩。我参考了这位博主的博客: ncurses库常见用法。这里是文档NCURSES Programming HOWTO,不过是英文的,虽然我也想看中文文档,但是找不到就很气。然后写了一个在屏幕上实时显示输入的字符的玩具。 12345678910111213141516171819#include <ncurses.h>int main(void){ initscr(); // 初始化屏幕 raw(); noecho(); char input; while((input = getchar())!= 'q' && input != EOF) { clear(); // 防止输入Ctrl+Z时显示^Z导致字符一直留在屏幕上 refresh(); // 刷新 mvprintw(0, 0, "%c", input); // 在指定位置输出字符 refresh(); } clear(); endwin(); // 结束屏幕绘画 return 0;} 不过我测试的时候getchar()输入Ctrl + D居然捕获不到EOF了,有点奇妙。而且我还另写了一个程序,证实了我的系统下EOF确实是Ctrl + D, 这可真是太怪了。 在编译上面的程序的时候,记得加上-lncurses,比如: 1gcc test.c -lncurses 好了,那么本篇完(话说我为什么在学Socket的时候突然学起了ncurses?)","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"C","slug":"blog/C","permalink":"http://example.com/categories/blog/C/"}],"tags":[]},{"title":"Unix网络编程卷一使用的函数及库","slug":"Unix网络编程卷1第一章","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2021/02/17/Unix网络编程卷1第一章/","link":"","permalink":"http://example.com/2021/02/17/Unix%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E5%8D%B71%E7%AC%AC%E4%B8%80%E7%AB%A0/","excerpt":"","text":"以下部分内容摘抄自C语言中文网与百度百科,因为我不知道比这两个更好的渠道了,如果有其他更好的渠道欢迎补充。在我博客的socket分类下,超链接文本”更多内容”通常是跳转到以上提到的这几个网站。以下内容皆与Windows无关。写此篇的目的是方便博主自己边看书边方便的查看相关内容。(2021/02/20) 在每个函数中都加入了返回值条目。参考文献: 百度百科, C语言中文网。如果这两个网站上查到的函数中如果有一个是返回宏的,一个是返回数值的,我会将返回数值的记录在返回值条目里,而返回宏的不会被记录。 第一章1.2章 一个简单的时间获取客户程序书中提到的206.168.112.96已经ping不通了。 宏AF_INET: Ipv4网络协议.SOCK_STREAM: 提供面向连接的稳定数据传输,即TCP协议.SA: 书中有提到,unp.h中使用#define把SA定义为struct sockaddr, 也就是通用套接字地址结构。 socket()函数头文件:#include <sys/types.h> #include <sys/socket.h>函数原型: int socket(int domain, int type, int protocol);返回值: 成功则返回socket 处理代码, 失败返回-1. 更多内容。这里写了参数等内容。 sockaddr_in结构体头文件: netinet/in.h在windows/linux下有下面结构: 12345678910111213struct sockaddr_in { short sin_family;/*Address family一般来说AF_INET(地址族)PF_INET(协议族)*/ unsigned short sin_port;/*Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/ struct in_addr sin_addr;/*IP address in network byte order(Internet address)*/ unsigned char sin_zero[8];/*Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐*/ }; 更多内容。里面有参数等内容。 bezero()函数该函数效果和memset()差不多,故此不再多写。 htonl()函数将主机数转换成无符号长整型的网络字节顺序。本函数将一个32位数从主机字节顺序转换成网络字节顺序。 头文件: <arpa/inet.h> 有些系统包含的头文件是 <netinet/in.h> 而不是 <arpa/inet.h>.函数原型: uint32_t htonl(uint32_t hostlong);返回值: 返回一个网络字节顺序的值。参数: hostlong:主机字节顺序表达的32位数。 更多内容。 inet_pton()函数inet_pton是一个IP地址转换函数,可以在将点分文本的IP地址转换为二进制网络字节序”的IP地址,而且inet_pton和inet_ntop这2个函数能够处理ipv4和ipv6。 头文件: <sys/socket.h> <netinet/in.h> <arpa/inet.h>函数原型: int inet_pton(int af, const char *src, void *dst);返回值: 如果函数出错将返回一个负值,并将errno设置为EAFNOSUPPORT,如果参数af指定的地址族和src格式不对,函数将返回0。 更多内容。 connect()函数connect()用于建立与指定socket的连接。 头文件: <sys/socket.h>函数原型: int connect(SOCKET s, const struct sockaddr * name, int namelen);返回值: 若无错误发生,则connect()返回0。参数:s:标识一个未连接socketname:指向要连接套接字的sockaddr结构体的指针namelen:sockaddr结构体的字节长度 更多内容。 read()函数read()会把参数fd 所指的文件传送count 个字节到buf 指针所指的内存中. 若参数count 为0, 则read()不会有作用并返回0. 返回值为实际读取到的字节数, 如果返回0, 表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。 头文件: <unistd.h>函数原型: ssize_t read(int fd, void * buf, size_t count);返回值: 成功返回读取的字节数,出错返回-1并设置errno,如果在调read之前已到达文件末尾,则这次read返回0。 更多内容。 1.5章一个简单的时间获取服务器程序已经在前面提到过的函数不会再归类在1.5章里。作者使用的包裹函数,如Write(), Listen()等,只会给出其中调用的函数,如write(), listen()。本段并不会记录属于time.h里面内容。 宏INADDR_ANY: 任何地址LISTENQ: #define LISTENQ 1024 (这是在我的unp.h中找到的) bind()函数将一本地地址与一套接口捆绑。本函数适用于未连接的数据报或流类套接口,在connect()或listen()调用前使用。当用socket()创建套接口后,它便存在于一个名字空间(地址族)中,但并未赋名。bind()函数通过给一个未命名套接口分配一个本地名字来为套接口建立本地捆绑(主机地址/端口号)。 头文件: <sys/types.h> <sys/socket.h>函数原型: int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen);返回值: 如无错误发生,则bind()返回0。否则的话,将返回-1,应用程序可通过WSAGetLastError()获取相应错误代码。 更多内容: 百度百科中的bind(), C语言中文网中的bind()。 listen()函数创建一个套接口并监听申请的连接. 头文件: <sys/socket.h>函数原型: int listen( int sockfd, int backlog); 返回值: 如无错误发生,listen()返回0。否则的话,返回-1,应用程序可通过WSAGetLastError()获取相应错误代码。 更多内容。 accept()函数accept()是在一个套接口接受的一个连接。accept()是c语言中网络编程的重要的函数,本函数从s的等待连接队列中抽取第一个连接,创建一个与s同类的新的套接口并返回句柄。 头文件: <sys/socket.h>函数原型: int accept(int s, struct sockaddr * addr, socklen_t * restrict addrlen); (其实socklen_t就是int类型,而C语言中文网上也写着是int类型,但是无论怎么说,它形参上确实写着socklen_t。Linus说这似乎是POSIX为了掩饰自己的错误创造出来的类型 –博主注)返回值: 返回值:成功则返回新的socket 处理代码, 失败返回-1, 错误原因存于errno 中。 更多内容: 百度百科中的accept(), C语言中文网中的accept()。 write()函数函数说明:write()会把参数buf 所指的内存写入count 个字节到参数fd 所指的文件内. 当然, 文件读写位置也会随之移动。 头文件: <unistd.h>函数声明: ssize_t write (int fd, const void * buf, size_t count);返回值: 如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1,错误代码存入errno中。 更多内容。 close()函数函数说明:当使用完文件后若已不再需要则可使用 close()关闭该文件, 二close()会让数据写回磁盘, 并释放该文件所占用的资源. 参数fd 为先前由open()或creat()所返回的文件描述词. 头文件: <unistd.h>函数原型: int close(int fd);返回值:若文件顺利关闭则返回0, 发生错误时返回-1. 更多内容。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Socket","slug":"blog/Socket","permalink":"http://example.com/categories/blog/Socket/"}],"tags":[{"name":"Socket","slug":"Socket","permalink":"http://example.com/tags/Socket/"}]},{"title":"解决kde桌面特效消失","slug":"解决kde桌面特效消失","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2021/02/14/解决kde桌面特效消失/","link":"","permalink":"http://example.com/2021/02/14/%E8%A7%A3%E5%86%B3kde%E6%A1%8C%E9%9D%A2%E7%89%B9%E6%95%88%E6%B6%88%E5%A4%B1/","excerpt":"","text":"前天用OBS Studio的时候发现录制的画面一直闪烁,就把渲染后端换成了Xrender。然后录制完视频后第二天才发现,我的桌面特效不见辽。不见的特效有:破碎,飘落,魔灯等。那么该怎么办呢?很简单,把渲染后端切换成OpenGL就行了,无论是OpenGL2.0和3.1都没有问题。(也许你会以为我在水博客,但是我曾经因为这个问题找不到问题所在重装了一次系统)本篇完。祝大家过年快乐。虽然今天已经大年初三了。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[]},{"title":"安装Unix网络编程卷一的unp.h库","slug":"安装Unix网络编程卷1的unp库","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2021/02/14/安装Unix网络编程卷1的unp库/","link":"","permalink":"http://example.com/2021/02/14/%E5%AE%89%E8%A3%85Unix%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E5%8D%B71%E7%9A%84unp%E5%BA%93/","excerpt":"","text":"也许各位朋友在读Unix网络编程卷一的时候发现没有unp.h库,不知道该咋解决了,那么可以先访问官网:http://www.unpbook.com。在页面左边有一个叫”Source Code”的超链接,里面会告诉你接下来要怎么做。那么不愿意看英文的朋友也不用着急,接下来我会直接给出安装的方法。第一步,先clone这个项目:unpbook/unpv13e。你可以尝试看一看README文件,里面记载了使用方法,如果不愿意看的话跟着第二步走。第二步只是我翻译了一部分文档。 第二步:首先cd进入目录,然后在cli输入:./configure然后cd进入lib,输入make然后cd进入../libfree, 输入make如果你是在Linux系统上,则不需要输入以下两行: 12cd ../libroute # only if your system supports 4.4BSD style routing socketsmake # only if your system supports 4.4BSD style routing sockets 看样子如果你是BSD系统才需要输入以上两行的内容。接下来输入:cd ../intro,之后再输入make daytimetcpcli,然后输入./daytimetcpcli 127.0.0.1。这一步是做一个测试,可执行文件成功工作即可。其实似乎是让./daytimetcpcli 206.168.112.96的,但是这个地址已经ping不通了。。。 然后就是这个unp.h到底在哪呢?它在unpv13e/lib下。更多内容还需要看REAME文档。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Socket","slug":"blog/Socket","permalink":"http://example.com/categories/blog/Socket/"}],"tags":[]},{"title":"Cpp中定义析构函数","slug":"cpp定义析构函数","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2021/02/11/cpp定义析构函数/","link":"","permalink":"http://example.com/2021/02/11/cpp%E5%AE%9A%E4%B9%89%E6%9E%90%E6%9E%84%E5%87%BD%E6%95%B0/","excerpt":"这只是一篇备忘博文(虽然我大多数文章的目的也都是备忘)。定义析构函数时只需要在类名前加上~即可说明这是析构函数,如:","text":"这只是一篇备忘博文(虽然我大多数文章的目的也都是备忘)。定义析构函数时只需要在类名前加上~即可说明这是析构函数,如: 123456789101112#include <iostream>using namespace std;class Ts{ int num1 = 0; int num2 = 0; public: ~Ts() { cout << "This class was deleted." << endl; }}; 需要注意的是,析构函数不接受任何参数。那么来演示一下上面定义的类的运行结果,代码: 1234567891011121314151617181920#include <iostream>using namespace std;class Ts{ int num1 = 0; int num2 = 0; public: Ts() = default; ~Ts() { cout << "Ts is going to be delete." << endl; }};int main(void){ Ts t1, t2; return 0;} 运行结果: 1234╭─fire@butterfly ~/codeSet/CPPCode ╰─➤ ./a.out Ts is going to be delete.Ts is going to be delete. 由于内存回收,所以~Ts()会被调用。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"}],"tags":[]},{"title":"Linux安装mysql++","slug":"cpp安装mysql++","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2021/02/07/cpp安装mysql++/","link":"","permalink":"http://example.com/2021/02/07/cpp%E5%AE%89%E8%A3%85mysql++/","excerpt":"昨天晚上就想拿C++连接Mysql了,今天一早上就起来尝试,终于弄明白了怎么安装(顺便还给自己补了一波cpp的知识)。下面是安装方法:","text":"昨天晚上就想拿C++连接Mysql了,今天一早上就起来尝试,终于弄明白了怎么安装(顺便还给自己补了一波cpp的知识)。下面是安装方法: 安装先到官网下载最新版本(其实是不是最新应该没有关系),如果您实在是不原因看英文,那么点击这个链接mysql++-3.2.5.tar.gz即可下载。下载完成后,首先要解压: 1tar -zxvf mysql++-3.2.5.tar.gz 如果您是萌新,请把上面的”mysql++-3.2.5.tar.gz”换成您下载的压缩包的名字。之后进入解压后的目录: 1cd mysql++-3.2.5.tar.gz 接下来是安装: 123456# 配置./configure# 编译make#安装(如果权限不够记得加sudo)make install 默认会安装到/usr/local/lib和/usr/local/include下。 卸载先进入mysql++-3.2.5文件夹(即您解压出的文件夹),然后输入: 1make uninstall 如果提示您权限不够记得加sudo。 使用中遇到的问题如果您按照以上方法安装完成后,使用:#include “mysql++.h”的时候提示您未找到文件,将其换成#include “mysql++/mysql++.h”。(我开头说到的补了Cpp的知识就是因为这个。如果您使用此方法依旧无效后,请将其填写成mysql++.h的绝对路径)。另外,如果按照我上面所说的修改include语句后,提示缺少”mysql.h”文件,不要急,这是因为mysql++.h包含了connection.h,而connection.h包含了common.h,但是common.h中有这么一段话: 123456789// Now that we've defined all the stuff above, we can pull in the full// MySQL header. Basically, the above largely replaces MySQL's my_global.h// while actually working with C++. This is why we disobey the MySQL// developer docs, which recommend including my_global.h before mysql.h.#if defined(MYSQLPP_MYSQL_HEADERS_BURIED)# include <mysql/mysql.h>#else# include <mysql.h>#endif 恕我学识浅薄,只能看得似懂非懂,不过显而易见问题就出在了这里。虽然知道作者是基于一定的考量才写下了: 1#include <mysql.h> 这段代码。不过既然出错了,我们就需要手动更正,比如将其换为: 1#include <mysql/mysql.h> 即可。 编译时如果提示类似undefined reference to `mysqlpp::Connection::Connection(bool)’之类的话,则将/usr/local/lib下的关于mysql++的东西全部拷贝到/usr/lib,在我写这篇博客的时候,需要拷贝的有:libmysqlpp.so libmysqlpp.so.3 libmysqlpp.so.3.2.5 。这个问题困扰了我近一个小时,无论是改ld的配置文件还是指定目录都会出现各种各样的bug,最后脑子一抽就想出了这么个方法。 不行了,太累了,有机会我再出博客介绍mysql++的使用吧,本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"}],"tags":[]},{"title":"vim实用指令","slug":"vim实用指令","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/02/05/vim实用指令/","link":"","permalink":"http://example.com/2021/02/05/vim%E5%AE%9E%E7%94%A8%E6%8C%87%E4%BB%A4/","excerpt":"","text":"发现了挺实用的几条vim指令,来分享一下。以后再有发现好用的指令会在这里面更新。这里面不会有hjkl之类的基础操作。以下内容均经过博主亲自测试并成功后才分享。 删除删除括号以及引号里面的内容使用方法: 将光标停留在括号上或者引号上,或者停留在括号或者引号中。主要命令是:di。d还是原意,此时的i似乎代表了”inside”。 删除双引号里面的内容di”: 删除双引号”里面的内容,如: 1234moxi"moxi"q# 光标停留在从左往右第一个"上或第一个"与第二个"之间,执行di"# 结果:moxi""q 因为每条都这样说明很麻烦,所以以后就不带第二行和第三行的注释了,读者只要知道该二级标题下的内容都是这个意思就好。 删除小括号里面的内容di(。 123hello(YoXI)aaahello()aaa 删除大括号里的内容di{。 123h{hadasdas}hh{}h 复制复制括号以及引号里面的内容使用方法: 将光标停留在括号上或者引号上,或者停留在括号或者引号中。主要命令是yi。y仍是原意,此时的i似乎代表了”inside”。然后需要注意的是,这些命令是复制括号或者引号内的内容,而不是复制从当前光标到下一个括号或引号内的内容。 复制小括号内的内容yi(。 1234gc(gcc)a# 此时光标停留在(上或者括号内容中, 在Normal模式下输入:yi(# 之后按下p, 出现内容如下gcc 双引号,单引号,大括号也是同理,其分别对应: yi”, yi’, yi{。 简写变量比如我想输入ad,让其变成adc,那么可以使用这条指令: 1:iabbr ad adc 然后输入ad(后面加上;或空格等符号时)会自动变成adc。比如我输入ade = 3,ad并不会变成adc,但是如果我输入ad空格= 3或qwe = ad;时,ad就会自动变成adc。 (本篇未完结,时不时会更新一下)","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vim","slug":"blog/vim","permalink":"http://example.com/categories/blog/vim/"}],"tags":[{"name":"vim","slug":"vim","permalink":"http://example.com/tags/vim/"}]},{"title":"解决Cpp中智能指针与自动回收的冲突","slug":"cpp智能指针定义删除器","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2021/02/03/cpp智能指针定义删除器/","link":"","permalink":"http://example.com/2021/02/03/cpp%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88%E5%AE%9A%E4%B9%89%E5%88%A0%E9%99%A4%E5%99%A8/","excerpt":"这个问题是当智能指针指向一个变量的时候产生的,因为智能指针会自动释放,变量也会被内存回收,所以这两者冲突了。虽然最容易的解决方法是直接使用普通指针,但是因为丰富的求知欲,我们就来看一看如何解决这个问题。","text":"这个问题是当智能指针指向一个变量的时候产生的,因为智能指针会自动释放,变量也会被内存回收,所以这两者冲突了。虽然最容易的解决方法是直接使用普通指针,但是因为丰富的求知欲,我们就来看一看如何解决这个问题。 代码如下: 1234567891011121314151617#include <iostream>#include <memory>using namespace std;void release_ptr(int *ptr){}int main(void){ int a = 30; shared_ptr<int> p1(&a, release_ptr); cout << *p1 << endl; return 0;} 这个原理其实就是定义了一个删除器来代替掉智能指针默认的自动释放,当智能指针不再自动释放的时候,问题也就解决了,int变量a的内存将由自动回收清理掉。 本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"}],"tags":[]},{"title":"让Cpp中new不抛出异常","slug":"cpp中new不抛出异常","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2021/02/02/cpp中new不抛出异常/","link":"","permalink":"http://example.com/2021/02/02/cpp%E4%B8%ADnew%E4%B8%8D%E6%8A%9B%E5%87%BA%E5%BC%82%E5%B8%B8/","excerpt":"为了防止日后找不到解决方法,所以就先写上。","text":"为了防止日后找不到解决方法,所以就先写上。 12345678910111213#include <iostream>#include <new>using namespace std;int main(void){ int * p = new (nothrow) int(1024); // 如果没有分配成功,则返回空指针。 if(p != NULL) cout << *p << endl; delete p; return 0;}","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"}],"tags":[]},{"title":"做一个QQ机器人","slug":"做一个QQ机器人","date":"un66fin66","updated":"un22fin22","comments":true,"path":"2021/01/30/做一个QQ机器人/","link":"","permalink":"http://example.com/2021/01/30/%E5%81%9A%E4%B8%80%E4%B8%AAQQ%E6%9C%BA%E5%99%A8%E4%BA%BA/","excerpt":"","text":"本篇博客中使用的框架为OPQBOT,这是它在Github上的仓库: OPQBOT/OPQ。 本篇博客中与接口交互使用的语言是Python。 总而言之,OPQBOT和Python几乎就是本篇中的所有知识点。而我运行服务的平台是阿里云的学生版服务器,操作系统是Ubuntu,架构是amd64,不过OPQBOT是跨平台的,作者提供了Window, Linux, 和FreeBSD的多个版本。 那么事不宜迟,下面就先讲怎么安装这个框架。 安装框架因为我的平台的原因,我下载了linux_amd64版本的Release:https://files.gitter.im/5f27939ed73408ce4feb3112/E7rD/OPQBot_6.0.6_linux_amd64.tar.gz。如果大家是在别的平台的话也不必担心,这个超链接将会指引你到用户的聊天室中: OPQBOT,在里面翻一翻聊天记录,就会找到作者发的各个版本的包。 在这之后,你需要到Gitter上获取一个Token:Gitter。复制这个Token并粘贴到CoreConf.conf中的”Token”中,如: Token = “123456”。然后CoreConf.conf中”Port”项能够改变服务运行的端口。如果你要将其运行在服务器上,那么前期你应该将它运行在0.0.0.0上,等到一切都配置完毕,再运行在本地即127.0.0.1上,以防止有人恶意访问。然后执行OPQBot: 1./OPQBot 等到控制台输出:Everything is ok!这样服务就跑起来了。 登陆QQ帐号强烈建议使用小号,因为你登录的QQ号会和你的Github绑在一起(可能是作者出于某种考虑这么做的,但是即使这样也建议使用小号登录,以免造成不必要的损失。)。那么如何登录QQ号呢?只有通过扫码的方式。使用浏览器访问http://IP:PORT/v1/Login/GetQRcode来获取二维码,然后扫码。虽然大多数人都明白,但我还是说一下,IP是运行服务的机器的IP,如果是内网的机器则把IP换成该机器的内网IP,如果是公网的机器则换成公网IP(如果机器同时在内网和公网中当我没说)。PORT则是服务运行的端口。注意,如果你的服务运行在127.0.0.1上,其他机器是无法访问的,只能让机器自己访问自己。 收发消息其实这里看这位兄台的博客就好使用Python制作IOTQQ插件。然后这里是手册:Home。然后有一点需要注意的是登录QQ的时候如果使用的小号的话会有几天不能在群聊里发送消息,我的小号的限制期为3天,其他人也许会不一样吧。 那么接下来是我写的代码,只有一个功能: 监听群聊消息,如果目标QQ号发送消息,那么就回复一句Hello。不过有个BUG没有解决: 如果目标发送的是中文(我想除了键盘上这些字符以外的所有字符受到的待遇也会与中文一样)则会导致该条信息无法识别。其实是可以识别的,但是不知道为什么就触发不了自动回复。不过我也没打算用中文进行交互,所以就没有修。如果你打算直接照搬代码的话请注意这一点。 安装依赖: 123pip install python-socketiopip install websocket-clientpip install requests 代码: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758import socketioimport jsonimport requestssio = socketio.Client()robotqq = "123456789" # 这里填写你的QQ号webapi = "http://127.0.0.1:8888"@sio.eventdef connect(): print('成功连接服务器') sio.emit('GetWebConn',robotqq)#取得当前已经登录的QQ链接# 该函数的作用是发送消息。ToQQ是要发送的QQ号或群号,Content是内容,sendToType是要发送的类型,即好友,群聊,私聊这三个类型,1为好友,2为群聊,3为私聊,这其中有些需要注意的地方,这里我就不再多说了,请看我上面发出的文档的链接里的内容def send(ToQQ,Content,sendToType,atuser=0,sendMsgType='TextMsg',groupid=0): tmp={} # 注意,关于这些键值对代表什么以及它们的值应该是什么,请看文档。上面我已经发出文档的链接。 tmp['sendToType'] = sendToType tmp['toUser']=ToQQ tmp['sendMsgType']=sendMsgType tmp['content']=Content tmp['groupid']=groupid tmp['atUser']=atuser print("This is Send") print("this is sendToType::", sendToType) print("This is ToQQ: ", ToQQ) print("This is atuser: ", atuser) print("This is groupid: ", groupid) print("This is Content: ", Content) tmp1 = json.dumps(tmp) result = requests.post(webapi+'/v1/LuaApiCaller?funcname=SendMsg&qq='+robotqq,data=tmp1) print(result.text)@sio.on("OnGroupMsgs")def OnGroupMsgs(message): """监听群消息""" print(message) if(message['CurrentPacket']['Data']['FromUserId'] == 11223344): # 11223344为目标QQ号,其应该为整型。这一步是判断是否为目标QQ号 send(message['CurrentPacket']['Data']['FromGroupId'], message['CurrentPacket']['Data']['Content'], 2)@sio.on('OnFriendMsgs')def OnFriendMsgs(message): ''' 监听好友消息 ''' print(message)@sio.on('OnEvents')def OnEvents(message): ''' 监听相关事件''' print(message)def main(): try: sio.connect("http://127.0.0.1:8888",transports=['websocket']) sio.wait() #阻塞进程 except BaseException as e: print (e)if __name__ == '__main__': main() 就是这些,其中监听好友消息和监听事件没有用到。 那么,本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"乱七八糟","slug":"blog/乱七八糟","permalink":"http://example.com/categories/blog/%E4%B9%B1%E4%B8%83%E5%85%AB%E7%B3%9F/"}],"tags":[]},{"title":"Cpp中逐个读取ifstream中的字符","slug":"cpp中逐个读取ifstream字符","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/01/25/cpp中逐个读取ifstream字符/","link":"","permalink":"http://example.com/2021/01/25/cpp%E4%B8%AD%E9%80%90%E4%B8%AA%E8%AF%BB%E5%8F%96ifstream%E5%AD%97%E7%AC%A6/","excerpt":"因为这个问题太容易了,所以直接上代码吧。","text":"因为这个问题太容易了,所以直接上代码吧。 12345678910111213#include <iostream>#include <fstream>using namespace std;int main(void){ ifstream ifs("./c1.txt"); char ch; ifs >> ch; cout << ch << endl; return 0;} 输出结果就不展示了,反正就是这么一回事。这么容易的事我却没想到。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"}],"tags":[]},{"title":"Cpp中查找字符串的子字符串","slug":"cpp中查找子字符串","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/01/25/cpp中查找子字符串/","link":"","permalink":"http://example.com/2021/01/25/cpp%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2/","excerpt":"直接使用string对象的find()方法,如果find()找到了目标返回目标的下标,如果没有找到返回string对象.npos。 下面是代码:","text":"直接使用string对象的find()方法,如果find()找到了目标返回目标的下标,如果没有找到返回string对象.npos。 下面是代码: 123456789101112131415#include <iostream>#include <string>using namespace std;int main(void){ string text(" Fire f"); auto pos = text.find("Fire"); cout << pos << endl; pos = text.find("Water"); if(pos == text.npos) cout << "Not Found" << endl; return 0;} 下面是输出: 1234╭─fire@butterfly ~/codeSet/CPPCode ╰─➤ ./a.out 1Not Found 本来是不想写基础操作的博客的,但是最近老是忘,于是就写了,到时候再忘了也方便自己查找。本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"}],"tags":[]},{"title":"Linux使用QQ音乐","slug":"使用qq音乐","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2021/01/18/使用qq音乐/","link":"","permalink":"http://example.com/2021/01/18/%E4%BD%BF%E7%94%A8qq%E9%9F%B3%E4%B9%90/","excerpt":"现在QQ音乐也出了Linux版(总感觉自己之前好像已经写过了), 目前更新下来还不错,已经有了歌词功能了,声音也没之前那么折磨耳朵了(可能之前是因为我没有QQ音乐会员的原因),总之,现在单纯听听音乐还是可以的,而且也已经提供了歌词功能,不过使用KDE平铺脚本的要小心歌词功能,它会打乱你目前的布局。那么我就来写一下Arch Linux下如何安装它吧:","text":"现在QQ音乐也出了Linux版(总感觉自己之前好像已经写过了), 目前更新下来还不错,已经有了歌词功能了,声音也没之前那么折磨耳朵了(可能之前是因为我没有QQ音乐会员的原因),总之,现在单纯听听音乐还是可以的,而且也已经提供了歌词功能,不过使用KDE平铺脚本的要小心歌词功能,它会打乱你目前的布局。那么我就来写一下Arch Linux下如何安装它吧: 1yay -s qqmusic-bin 使用yay安装即可。若是没有yay,则用pacman安装yay。那么其他操作系统的朋友肯定在等我给网址吧,不好意思,,我没找到网址。。。这点就很奇妙,Google了半个小时愣是没找到在哪下载。不过确实是有途径可以下载的,我之前用的折磨耳朵版本就是就下载的Appimage,现在是忘了在哪下的了。。。不过没关系,我在这里贴上QQ音乐 for Linux的QQ群群号: 1046496784。里面管理很活跃,找他们要链接就好。那么本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"Golang使用Jsoniter","slug":"Golang使用Jsoniter","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2021/01/17/Golang使用Jsoniter/","link":"","permalink":"http://example.com/2021/01/17/Golang%E4%BD%BF%E7%94%A8Jsoniter/","excerpt":"","text":"据说jsoniter的速度比golang标准库的速度快,我就来用了。 jsoniter提供的图表(它自己提供的哈,可不是我说的。图片网址是githubusercontent.com,所以国内用户可能看不到这个图): 项目地址。 项目中文Wiki(竟然有中文!)。 虽说有了中文Wiki我再写这篇markdown显得有点憨,但我还是写一下吧。 序列化结构体切片转换成JSON字符串/字节切片 (单个结构体也可以用同样的方法): 注意结构体的字段要大写,不然读取不到。 12345678910111213141516171819202122232425262728293031323334353637383940414243package mainimport ( "fmt" jsoniter "github.com/json-iterator/go")type Menu struct { Id int Title string}func main() { var menus []Menu // 生成测试数据 for i := 0; i < 10; i++ { menus = append(menus, Menu{Id: i, Title: "title " + fmt.Sprintf("%d", i)}) } // 打印测试数据 fmt.Println("Struct array:\\n", menus) // 生成json字节数组 res, err := jsoniter.Marshal(&menus) if err != nil { panic(err) } // 以字节形式打印字节数组 fmt.Println("\\nJson bytes: \\n", res) // 直接转换成字符串 string_res, err := jsoniter.MarshalToString(&menus) if err != nil { panic(err) } // 以字符串格式打印字节数组 fmt.Println("\\nJson string: ", string_res)} 终端输出: 12345678~/codeSet/goCode/test » go run main.go Struct array: [{0 title 0} {1 title 1} {2 title 2} {3 title 3} {4 title 4} {5 title 5} {6 title 6} {7 title 7} {8 title 8} {9 title 9}]Json bytes: [91 123 34 73 100 34 58 48 44 34 84 105 116 108 101 34 58 34 116 105 116 108 101 32 48 34 125 44 123 34 73 100 34 58 49 44 34 84 105 116 108 101 34 58 34 116 105 116 108 101 32 49 34 125 44 123 34 73 100 34 58 50 44 34 84 105 116 108 101 34 58 34 116 105 116 108 101 32 50 34 125 44 123 34 73 100 34 58 51 44 34 84 105 116 108 101 34 58 34 116 105 116 108 101 32 51 34 125 44 123 34 73 100 34 58 52 44 34 84 105 116 108 101 34 58 34 116 105 116 108 101 32 52 34 125 44 123 34 73 100 34 58 53 44 34 84 105 116 108 101 34 58 34 116 105 116 108 101 32 53 34 125 44 123 34 73 100 34 58 54 44 34 84 105 116 108 101 34 58 34 116 105 116 108 101 32 54 34 125 44 123 34 73 100 34 58 55 44 34 84 105 116 108 101 34 58 34 116 105 116 108 101 32 55 34 125 44 123 34 73 100 34 58 56 44 34 84 105 116 108 101 34 58 34 116 105 116 108 101 32 56 34 125 44 123 34 73 100 34 58 57 44 34 84 105 116 108 101 34 58 34 116 105 116 108 101 32 57 34 125 93]Json string: [{"Id":0,"Title":"title 0"},{"Id":1,"Title":"title 1"},{"Id":2,"Title":"title 2"},{"Id":3,"Title":"title 3"},{"Id":4,"Title":"title 4"},{"Id":5,"Title":"title 5"},{"Id":6,"Title":"title 6"},{"Id":7,"Title":"title 7"},{"Id":8,"Title":"title 8"},{"Id":9,"Title":"title 9"}] 反序列化12345678910111213141516171819202122232425262728293031323334353637383940414243package mainimport ( "fmt" jsoniter "github.com/json-iterator/go")type Menu struct { Id int Title string}func main() { var menus []Menu // 生成测试数据 for i := 0; i < 10; i++ { menus = append(menus, Menu{Id: i, Title: "title " + fmt.Sprintf("%d", i)}) } // 打印测试数据 fmt.Println("Struct array:\\n", menus) // 直接转换成字符串 string_res, err := jsoniter.MarshalToString(&menus) if err != nil { panic(err) } // 以字符串格式打印字节数组 fmt.Println("\\nJson string: ", string_res) var ms []Menu // 该变量用于存储反序列化后的结构 // 从字符串形式反序列化(也有从字节数组形式反序列化的) err = jsoniter.UnmarshalFromString(string_res, &ms) if err != nil { panic(err) } // 打印反序列化后的数据 fmt.Println("\\nDeserialization: \\n", ms)} 输出: 1234567Struct array: [{0 title 0} {1 title 1} {2 title 2} {3 title 3} {4 title 4} {5 title 5} {6 title 6} {7 title 7} {8 title 8} {9 title 9}]Json string: [{"Id":0,"Title":"title 0"},{"Id":1,"Title":"title 1"},{"Id":2,"Title":"title 2"},{"Id":3,"Title":"title 3"},{"Id":4,"Title":"title 4"},{"Id":5,"Title":"title 5"},{"Id":6,"Title":"title 6"},{"Id":7,"Title":"title 7"},{"Id":8,"Title":"title 8"},{"Id":9,"Title":"title 9"}]Deserialization: [{0 title 0} {1 title 1} {2 title 2} {3 title 3} {4 title 4} {5 title 5} {6 title 6} {7 title 7} {8 title 8} {9 title 9}] 还有一些配置JSON的内容我就不写了,因为我暂时用不到: ( 建议看github上的Wiki。 本篇完。","author":"Arvin","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Go","slug":"blog/Go","permalink":"http://example.com/categories/blog/Go/"}],"tags":[]},{"title":"ElementPlus自动导入","slug":"ElementPlus自动导入","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2021/01/12/ElementPlus自动导入/","link":"","permalink":"http://example.com/2021/01/12/ElementPlus%E8%87%AA%E5%8A%A8%E5%AF%BC%E5%85%A5/","excerpt":"","text":"Element Plus官方指南。 本文借鉴(抄)自[实现自动引入+按需引入element-plus原来如此简单] - 河豚学前端。 本篇博客仅记载vue-cli的安装方法,其他方法还请参考上面的官方指南。 安装npm包:1npm i unplugin-vue-components unplugin-auto-import -D 配置(vue-cli)在项目根目录下创建vue.config.js文件,并在其中写入: 12345678910111213141516const AutoImport = require('unplugin-auto-import/webpack')const Components = require('unplugin-vue-components/webpack')const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')module.exports = { configureWebpack: { plugins: [ AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }), ], }} 之后使用这条命令启动项目: 1npm run serve 然后直接在template里使用Element Plus的组件就行。","author":"Arvin","categories":[],"tags":[]},{"title":"使用uos微信","slug":"使用uos微信","date":"un66fin66","updated":"un22fin22","comments":true,"path":"2021/01/02/使用uos微信/","link":"","permalink":"http://example.com/2021/01/02/%E4%BD%BF%E7%94%A8uos%E5%BE%AE%E4%BF%A1/","excerpt":"","text":"其实我安装uos微信本来是想要激活网页版微信的,结果发现并不好使。。。但是uos微信也是能用的。 那么直接安装吧: 1yay -S wechat-uos 安装完成之后终端输入wechat-uos启动。由于我的KDE Connect出了点问题,就不发截图了。那么,本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"元旦了随便说点","slug":"life5","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/01/01/life5/","link":"","permalink":"http://example.com/2021/01/01/life5/","excerpt":"祝各位元旦快乐。","text":"祝各位元旦快乐。 var binft = function (r) { function t() { return b[Math.floor(Math.random() * b.length)] } function e() { return String.fromCharCode(94 * Math.random() + 33) } function n(r) { for (var n = document.createDocumentFragment(), i = 0; r > i; i++) { var l = document.createElement(\"span\"); l.textContent = e(), l.style.color = t(), n.appendChild(l) } return n } function i() { var t = o[c.skillI]; c.step ? c.step-- : (c.step = g, c.prefixP < l.length ? (c.prefixP >= 0 && (c.text += l[c.prefixP]), c.prefixP++) : \"forward\" === c.direction ? c.skillP < t.length ? (c.text += t[c.skillP], c.skillP++) : c.delay ? c.delay-- : (c.direction = \"backward\", c.delay = a) : c.skillP > 0 ? (c.text = c.text.slice(0, -1), c.skillP--) : (c.skillI = (c.skillI + 1) % o.length, c.direction = \"forward\")), r.textContent = c.text, r.appendChild(n(c.prefixP < l.length ? Math.min(s, s + c.prefixP) : Math.min(s, t.length - c.skillP))), setTimeout(i, d) } var l = \"\", o = [\"祝大家2021快乐\", \"愿2021没有加班\"].map(function (r) { return r + \"\" }), a = 2, g = 1, s = 5, d = 75, b = [\"rgb(110,64,170)\", \"rgb(150,61,179)\", \"rgb(191,60,175)\", \"rgb(228,65,157)\", \"rgb(254,75,131)\", \"rgb(255,94,99)\", \"rgb(255,120,71)\", \"rgb(251,150,51)\", \"rgb(226,183,47)\", \"rgb(198,214,60)\", \"rgb(175,240,91)\", \"rgb(127,246,88)\", \"rgb(82,246,103)\", \"rgb(48,239,130)\", \"rgb(29,223,163)\", \"rgb(26,199,194)\", \"rgb(35,171,216)\", \"rgb(54,140,225)\", \"rgb(76,110,219)\", \"rgb(96,84,200)\"], c = { text: \"\", prefixP: -s, skillI: 0, skillP: 0, direction: \"forward\", delay: a, step: g }; i() }; binft(document.getElementById('binft')); 从今天起就是2021年了,时间过的还蛮快的,回首望去,好的坏的都已成回忆,而前方还有未知的未来在等着自己。 本来有好些话想写的,但是不知道写什么了,算了,祝你我新的一年都能攀上技术高峰。 祝我新的一年里能找的到女朋友…","categories":[{"name":"life","slug":"life","permalink":"http://example.com/categories/life/"}],"tags":[]},{"title":"nvim使用系统剪切板","slug":"nvim使用剪切板","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2021/01/01/nvim使用剪切板/","link":"","permalink":"http://example.com/2021/01/01/nvim%E4%BD%BF%E7%94%A8%E5%89%AA%E5%88%87%E6%9D%BF/","excerpt":"","text":"今天是元旦了,节日快乐。 由于我重装系统后发现nvim的剪切板失效了,无论怎样”+y就是不好使,于是我在Arch群问了问群友,得到了答案: 因为我没装xclip包。那么就装上xclip包: 1pacman -S xclip 之后就可以正常使用nvim的复制粘贴功能了。那么,本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vim","slug":"blog/vim","permalink":"http://example.com/categories/blog/vim/"}],"tags":[{"name":"vim","slug":"vim","permalink":"http://example.com/tags/vim/"}]},{"title":"解决pacman提示数据库锁定","slug":"解决pacman锁定问题","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2020/12/20/解决pacman锁定问题/","link":"","permalink":"http://example.com/2020/12/20/%E8%A7%A3%E5%86%B3pacman%E9%94%81%E5%AE%9A%E9%97%AE%E9%A2%98/","excerpt":"","text":"前几天滚的时候因为网速太慢就停了,过了一天发现pacman -Syu无法正常工作了,提示我数据库被锁定,但是pacman -S还能正常运作。然后解决方法如下: 1rm /var/lib/pacman/db.lck 记得加root权限。执行完这条命令后再pacman -Syu就可以了。这条命令看上去是删掉pacman的数据库锁文件。那么,本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"解决make时提示缺少“/lib/modules/5.9.14-arch1-1/build”","slug":"解决make时缺少build目录","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2020/12/20/解决make时缺少build目录/","link":"","permalink":"http://example.com/2020/12/20/%E8%A7%A3%E5%86%B3make%E6%97%B6%E7%BC%BA%E5%B0%91build%E7%9B%AE%E5%BD%95/","excerpt":"","text":"昨天使用make安装无线网卡驱动的时候提示not found file or directory /lib/modules/5.9.14-arch1-1/build。于是我上Arch Linux CN TG群问了问,得知出现这个问题的原因是少了一个包,arch linux使用以下命令安装: 1pacman -S linux-headers 安装完这个包再make就不会提示缺少build目录了。那么本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"vim以指定编码打开文件","slug":"vim以指定编码打开文件","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2020/12/18/vim以指定编码打开文件/","link":"","permalink":"http://example.com/2020/12/18/vim%E4%BB%A5%E6%8C%87%E5%AE%9A%E7%BC%96%E7%A0%81%E6%89%93%E5%BC%80%E6%96%87%E4%BB%B6/","excerpt":"","text":"今天用vim打开文件时遇到了些问题,我从淘宝上买的网卡对应的电子文档用vim打开乱码了,我看到文件名是.txt,盲猜该文件是用windows的记事本编辑的,那么既然乱码的话,其编码应该是GBK。于是百度一番,找到了vim用指定编码打开文件的命令,这里先放上我借鉴的博客的链接: VIM如何以指定编码打开或加载文件。 那么下面是两种设置编码的方式(其实用到的命令都是同一条)。在用vim打开文件后再指定编码: 1:e ++enc=gbk 其中gbk是你要指定的编码,你需要将其换成你想要的编码,比如utf-8之类的。 下面这一条命令是打开文件的时候设置编码(其实我觉得应该是打开文件之后再设置的),在命令行中输入: 1vim file_name -c "e ++enc=gbk" 其中file_name是你用vim打开的文件的名字,gbk是你想要指定的编码。 好,那么这篇博客结束。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vim","slug":"blog/vim","permalink":"http://example.com/categories/blog/vim/"}],"tags":[{"name":"vim","slug":"vim","permalink":"http://example.com/tags/vim/"}]},{"title":"coc_nvim安装c++代码补全","slug":"nvim安装c++代码补全","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2020/12/11/nvim安装c++代码补全/","link":"","permalink":"http://example.com/2020/12/11/nvim%E5%AE%89%E8%A3%85c++%E4%BB%A3%E7%A0%81%E8%A1%A5%E5%85%A8/","excerpt":"","text":"之前安装的C语言代码补全自从我开始学CPP后就不太灵光的样子,而且我忘记COC插件列表的网址是什么了,所以这里我又百度到了一篇安装C++的博客。 以下内容借鉴自VIM 插件之 coc.nvim,感谢博主分享知识。 那么首先要确保你安装了clang,下面是Arch Linux下安装clang的命令: 1pacman -S clang 博主原文中说如果没有安装clang直接装COC的CPP扩展会报错,由于我已经安装过clang了,所以就不再探这个坑的虚实了。然后打开nvim: 1nvim 输入以下指令: 1:CocInstall coc-cmake 然后静等安装完毕。为了验证效果,可以nvim一个cpp文件试一试,如果输入using有提示的话,那就是成功啦! 好了,那么本篇完,今天居然更新了两篇呢。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vim","slug":"blog/vim","permalink":"http://example.com/categories/blog/vim/"}],"tags":[{"name":"vim","slug":"vim","permalink":"http://example.com/tags/vim/"}]},{"title":"解决卡sddm登陆界面","slug":"解决卡sddm登陆界面","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2020/12/11/解决卡sddm登陆界面/","link":"","permalink":"http://example.com/2020/12/11/%E8%A7%A3%E5%86%B3%E5%8D%A1sddm%E7%99%BB%E9%99%86%E7%95%8C%E9%9D%A2/","excerpt":"","text":"记忆中我是发过这篇博客的,但是当交流群中的群友遇到这个问题的时候我在博客里找居然没找到。。。也许是我管理博客的时候一不小心漏了几篇文章吧。既然没有,那我就重新写一遍了。 为了精准对接问题,我再来描述一下问题吧,Arch Linux在安装sddm后,可以进入sddm界面,但是输入完帐号密码后(这两者都输对了)按回车,并没有出现预想中的进入桌面而是又跳回了sddm。 这件事与你的用户有关。因为sddm是不支持登陆root的,所以我又换了lightdm,以root的身份成功进入桌面后,我又手动切回了lightdm尝试以普通用户登陆,结果居然失败了。这一切的原因,都是因为。。。不好意思,因为距离我亲身经历这件事的时间有些久了,忘了是什么原因了,不过不要急,解决方法我还是记着的(因为我偷偷存在手机的记事本上了)。 那么解决方法如下:第一步: 使用我下面给的方法创建用户: 1useradd -m -g users -s /bin/bash 用户名 例如: 1useradd -m -g users -s /bin/bash fire 第二步: 设置密码。这个大家都会,我就不上代码了。 第三步: 使用第一步创建的用户和第二步设置的密码登录sddm,这样就可以进入桌面了。由代码我们可以推断,出现这个问题似乎是因为当前用户不在用户组里才导致的sddm登陆失败。 那么就是这些了,又废话了不少呢。(本篇完)","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"踩坑","slug":"踩坑","permalink":"http://example.com/tags/%E8%B8%A9%E5%9D%91/"}]},{"title":"纪念拿到驾照","slug":"life4","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2020/11/16/life4/","link":"","permalink":"http://example.com/2020/11/16/life4/","excerpt":"终于拿到驾照啦,发个博客纪念一下。不过我不是今天才拿到的,我是在11月9号拿到的。一直想发篇博客纪念一下,结果一直忘,今天终于想起来了,就发了。总之非常开心,相信以后的我回头看时看到这篇博客也会感慨颇多吧。","text":"终于拿到驾照啦,发个博客纪念一下。不过我不是今天才拿到的,我是在11月9号拿到的。一直想发篇博客纪念一下,结果一直忘,今天终于想起来了,就发了。总之非常开心,相信以后的我回头看时看到这篇博客也会感慨颇多吧。 好,那么感慨结束了,我感觉最近发生活文章的次数比发技术文章的次数还要多了。。。依然同以前一样,最近没有研究什么技术,除了配置了一份conky之外,啥也没弄,而且因为conky也没啥好发的,安装Arch Wiki上就有,配置也是百度一搜抄一份稍作修改就行。 最近几个月一直没有感到技术前进带来的踏实感,反而越来越感到无趣,但是技术确实是变强了(不过Python也快忘干净了)。不得不说K&R是一本好书,它让我不止一次意识到自己是fw,我觉得这上面的部分练习题太难了,所以就转而看C Primer Plus。这本书又让我觉得自己是大神。总之因为这两本书侧重的方向不一样,导致了我夹在中间很难受。 C语言快要学完了,不,C Primer Plus快要看完了,打算下个月入坑C++,因为我短期目的是要当QT工程师,不过我觉得拿py写QT应该不太好,所以就学C++了。打算学QT不只是为了给别人打工拿工资,还有一个原因是想要为Linux社区贡献一份自己的力量,毕竟如果写一个功能比较少的程序,图形化一定比命令行更容易操作吧。顺便一提,我的最终目的是要做操作系统,不过不知道三年内能不能做出来就是了。 最近不敢找爬虫的工作,看到那些搞爬虫进去的案例就感到害怕,这也是我打算学习QT的一个原因,还有一个原因是我现在依然不喜欢Web。我打算去找一下工作了,虽然C还没有学完并且py都快忘光了,我依然打算试一下,因为在家里待着实在是太无聊了,而且看到同学换新手机了,我也打算更新一下自己的设备了,也许每个热爱高科技的人都希望自己的设备走在时代前沿吧,我也不例外。Oh,不过说实话我认为这次找工作很可以以我的失败而收尾,不过我并不在意这些,就当是给每天努力学习的自己一个放松时间吧,好久没坐火车和地铁了,还挺怀念的。 从今天开始要认真了,虽然会有部分放松的时间,但是自己大部分时间一定都在学习中度过因为意识到自己缺钱了。","categories":[{"name":"life","slug":"life","permalink":"http://example.com/categories/life/"}],"tags":[]},{"title":"python3将png转换为jpg文件","slug":"python3将png转换为jpg","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2020/10/26/python3将png转换为jpg/","link":"","permalink":"http://example.com/2020/10/26/python3%E5%B0%86png%E8%BD%AC%E6%8D%A2%E4%B8%BAjpg/","excerpt":"","text":"好久没更新博客了,其实并非是我的热情消失了,而是最近没有什么好写的,因为我在学用C语言实现一些东西,且水平低的令人羞于启齿,所以就没写了。但是今天不一样了!额,今天有了新的需求,要我把几张图片从png格式转换成jpg格式。因为图片少,我也就没想用代码实现。但是手动拿PS转换的时候发现,速度真的是太低啦!emmm,也许是最近我又变懒了。。However,在手动转换完图片后,我又学习了一下用代码实现的方法,我一共参考了两篇博客,其中一篇的url切换系统的时候弄丢了,所以只能贴出另一篇没有丢掉的博客链接了:OSError: cannot write mode RGBA as JPEG。顺便提一句,手机版CSDN分享链接时自动加的那一段话真的很烦人。 那么下面是代码: 12345678910111213141516171819202122232425262728293031323334353637import osfrom PIL import Image# 获取当前同级目录的文件列表# get file list in this directoryimg_list = os.listdir("./")# 如果备份文件夹在这个文件夹下就跳过,如果没有备份文件夹就创建# if the backup directory not in this directory, create it.if (not (os.path.exists("./JPGSet"))): os.mkdir("./JPGSet")for name in img_list: # 如果文件以png结尾 # if the file is end with png if name.endswith(".png"): try: # 读取文件 # read the file img = Image.open(name) # 获取没有后缀名的文件名 # Get the file name without end name. first_name = name[:name.rfind(".")] # 将RGBA转换成RGB。A是Alpha,即透明度,JPG不支持透明度,所以如果不转换的话会报错 # change RGBA to RGB.The meaning of "A" is Alpha, if you don't change it to RGB, PIL will rise an Error. img = img.convert('RGB') # 保存图片到备份文件夹"JPGSet"下,其后缀名将会变成jpg # save the pic to back up directory "JPGSet", it will end with jpg img.save("./JPGSet/" + first_name + '.jpg', 'jpeg') print(first_name + '.jpg', 'was saved at ./JPGSet.') # 捕获无法读取的异常(因为代码我测试过了,可行,所以报IOError应该就是无法读取了) # get the IOError.I tested the code, at my computer, it could be run.So if there is some reason to make IOError raise, maybe the file is not a picture or you don't have enough permission. except IOError: print("This file made IOError:", name) else: continue 这次的注释非常全,因为我打算发到github上…所以我就不解释了。着急睡觉了。那么,本篇博客完~","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"PIL","slug":"PIL","permalink":"http://example.com/tags/PIL/"}]},{"title":"Linux下使用scrcpy控制你的手机","slug":"Linux下使用scrcpy","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2020/10/12/Linux下使用scrcpy/","link":"","permalink":"http://example.com/2020/10/12/Linux%E4%B8%8B%E4%BD%BF%E7%94%A8scrcpy/","excerpt":"","text":"博主现在正沉浸在舒爽之中~~~ 下面是使用的截图: 那么先放出参考博客的链接:使用scrcpy控制你的手机–DarkSun。感谢博主分享经验,也感谢scrcpy的开发者开发如此好用的软件。 参考的博客中一共需要安装两个包:android-tools和scrcpy,ArchLinux直接pacman即可,其他发行版我也不知道咋装,我在这里奉上scrcpy的项目地址:Scrcpy。 那么安装好这两个包之后,需要用数据线将手机连接到电脑上,然后打开USB调试模式。我的手机是OnePlus 6,打开调试模式的方法是在手机上点击设置中的关于手机中的版本号(手机版本号不是Android版本号),然后在”系统”选项中找到开发者选项,找到USB调试并开启。然后不要断掉数据线,这里有两条连接方式可以选,一条是无线连接一条是有线连接,这两条连接方式我都试了,都可行。安装的过程中如果提醒你计算机密钥什么请同意。 有线连接此时你的数据线应该仍然连接着手机与电脑。将手机的USB用途设置为文件传输(MTP),然后运行scrcpy即可。运行的命令是scrcpy。 无线连接首先确保手机与电脑在同一WIFI下,这个是通过内网ip连接的。这时你的数据线应该仍然连接着手机与电脑。在终端输入并运行adb tcpip 服务端口。这个服务端口你随便起,但是不要和其他端口冲突了。然后拔出手机,别忽略这一步。然后在电脑终端上输入并运行adb connect 手机IP:服务端口。服务端口就是刚才你起的那个端口。手机ip是手机在内网中的ip,别搞错了,我的手机依然是OnePlus 6,查看手机ip方法是在设置中打开关于手机,选择状态信息,其中就有ip地址。然后运行scrcpy。 需要注意的是,无线会比有线卡顿,但是这并不代表有线就不卡顿了。有线的卡顿只有一点点,而无线的卡顿是时不时来一下大卡顿,不过这丝毫不影响它是一款很好的软件。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[]},{"title":"C语言位操作","slug":"C语言位操作","date":"un66fin66","updated":"un22fin22","comments":true,"path":"2020/10/10/C语言位操作/","link":"","permalink":"http://example.com/2020/10/10/C%E8%AF%AD%E8%A8%80%E4%BD%8D%E6%93%8D%E4%BD%9C/","excerpt":"前几天和朋友上了几天网,前进的脚步有点缓了,今日再次开始充实自我。因为K&R上讲的过于简洁,所以我转而参考C Primer Plus。因为C Primer Plus上讲的很详细,所以我这里只写几个实例。","text":"前几天和朋友上了几天网,前进的脚步有点缓了,今日再次开始充实自我。因为K&R上讲的过于简洁,所以我转而参考C Primer Plus。因为C Primer Plus上讲的很详细,所以我这里只写几个实例。 按位逻辑运算符 4个按位逻辑运算符都用于整型数据,包括char。之所以叫做按位(bitwise)运算,是因为这些操作都是针对每一个位进行,不影响它左右两边的位。不要把这些运算符与常规的逻辑运算符(&&,||和!)混淆,常规的逻辑运算符操作的是整个值。 二进制反码或按位取反: ~ 直接上代码: 123456789101112#include <stdio.h> int main(void) { int val; val = 0xFF; printf("%X\\n", ~val); printf("%X\\n", val); return 0; } 输出结果为: 123CCode$ ./a.out FFFFFF00FF 因为在我的系统下int是32位的,所以变量val实际上是0x000000FF,使用取反后就成了0xFFFFFF00。第二条输出是为了表示val不会改变val的值,就像3 * val不会改变val的值一样。 按位与: & 二元运算符&通过逐位比较两个运算对象,生成一个新值。对于每个位,只有两个运算对象中相应的位都为1时,结果才为1(从真/假方面看,只有当两个位都为真时,结果才为真)。那么接下来上代码: 123456789include <stdio.h> int main(void) { printf("%X\\n", (0xF0F0) & (0x0F0F)); printf("%X\\n", (0x1010) & (0x0010)); return 0; } 接下来是输出: 123CCode$ ./a.out 010 按位或: | 二元运算符|,通过逐位比较两个运算对象,生成一个新值,对于每个位,如果两个运算对象中相应的位为1,结果就为1(从真/假两个方面看,如果两个运算对象中相应的一个位为真或两个位都为真,那么结果为真)。下面上代码: 1234567#include <stdio.h>int main(void){ printf("%X\\n", (0x1010) | (0x0100)); return 0;} 输出结果(这期间我换了个主题,从$那里看就好): 123┌─[fire@butterfly] - [~/codeSet/CCode] - [1804]└─[$] ./a.out [20:26:41]1110 如果上面的按位与: & 理解了那么这个也就好理解了。 按位异或: ^ 二元运算符^逐位比较两个运算对象。对于每个位,如果两个运算对象中相应的位一个为1(但不是两个为1),结果为1(从真/假方面看,如果两个运算对象中相应的一个位为真且不是两个位同为1,那么结果为真)。下面上代码: 1234567#include <stdio.h>int main(void){ printf("%X\\n", (0x0101) ^ (0x1100)); return 0;} 下面是输出结果: 1234┌─[fire@butterfly] - [~/codeSet/CCode] - [1815]└─[$] ./a.out [20:35:47]1001 C有一个按位异或和赋值结合的运算符: ^=,下面两条语句产生的最终作用相同: 12val ^= 0377;val = val ^ 0377; 下面我写了一个实例: 123456789#include <stdio.h>int main(void){ int val = 0X1010; val ^= 0X0011; printf("%X\\n", val); return 0;} 下面是输出结果: 123fire@butterfly ~/codeSet/CCode $ ./a.out1001","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"C","slug":"blog/C","permalink":"http://example.com/categories/blog/C/"}],"tags":[]},{"title":"KDE更改默认终端为deepin_terminal","slug":"kde更改默认终端","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2020/10/07/kde更改默认终端/","link":"","permalink":"http://example.com/2020/10/07/kde%E6%9B%B4%E6%94%B9%E9%BB%98%E8%AE%A4%E7%BB%88%E7%AB%AF/","excerpt":"","text":"最近怎么看Konsole怎么觉得难看,再看看群里时不时晒桌面的群友们,基本都是人手一个Deepin-Terminal,漂亮的看的我都眼红了。那么今天,咱就来装一个Deepin-Terminal。 这个包官方仓库里就有,所以这里我们直接: 1sudo pacman -S deepin-terminal-old 也许大家注意到了这里有个old的后缀,这是为什么呢?其实仓库里有两个deepin-terminal,一个叫做deepin-terminal,一个叫做deepin-terminal-old。那么这两个有什么区别呢?安装deepin-terminal需要的依赖比deepin-terminal-old多,所以这里我们就选择deepin-terminal-old。另外需要注意的是,这两个包冲突,如果你安装了deepin-terminal-old再想安装deepin-terminal,那么你首先就要删掉deepin-terminal-old。先安装deepin-terminal也是同理。 安装完毕之后,我们就可以设置默认终端了。当然如果想要先预览一下的话,可以使用这条命令预览:deepin-terminal。那么接下来我们来设置默认终端,说是设置默认终端,其实就是更改唤起终端的快捷键。KDE下唤起终端的快捷键默认是Ctrl+Alt+T,唤起的是Konsole。那么这里我们就修改一下,首先,把Konsole的快捷键给删掉:在KDE的设置菜单里找到”快捷键”并点击它,然后你可以看到有三个选项,全局快捷键,标准快捷键和自定义快捷键。我们点击全局快捷键,输入konsole,然后点击Ctrl+Alt+T旁边的删除标志。删除完毕点击应用。然后再打开自定义快捷键,选择编辑-新建-全局快捷键-命令/URL,然后就会出现一个叫做新建动作的东西,双击它可以改变它的名称。点击触发器,点击快捷键旁边的那个标识,输入快捷键:Ctrl+Alt+T。然后点击动作,在命令那一行里输入:deepin-terminal,点击应用。然后按下Ctrl+Alt+T,就可以呼出Deepin-Terminal啦。 那么,本篇完~","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[]},{"title":"C语言实现删除字符串中指定字符","slug":"C语言实现删除字符串中指定字符","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2020/10/05/C语言实现删除字符串中指定字符/","link":"","permalink":"http://example.com/2020/10/05/C%E8%AF%AD%E8%A8%80%E5%AE%9E%E7%8E%B0%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E6%8C%87%E5%AE%9A%E5%AD%97%E7%AC%A6/","excerpt":"今天跟着K&R继续学习C语言,其中有这么一段代码让我感觉很妙: 1234567891011#include <stdio.h>void chars_process(char fire[], char c){ int g, f; for (g = f = 0; fire[g] != '\\0'; g++) if (fire[g] != c) fire[f++] = fire[g]; fire[f] = '\\0';} 这是一段删除字符串中指定字符的代码。如果您觉得这段代码没什么,那么这篇博客下面的内容就不需要看了。如果您对它感兴趣,请继续看我分析。","text":"今天跟着K&R继续学习C语言,其中有这么一段代码让我感觉很妙: 1234567891011#include <stdio.h>void chars_process(char fire[], char c){ int g, f; for (g = f = 0; fire[g] != '\\0'; g++) if (fire[g] != c) fire[f++] = fire[g]; fire[f] = '\\0';} 这是一段删除字符串中指定字符的代码。如果您觉得这段代码没什么,那么这篇博客下面的内容就不需要看了。如果您对它感兴趣,请继续看我分析。 相信您也看得出来,这段代码是个函数,其有两个形参,分别是字符数组类型的fire和字符类型的c,因为我们要删除字符串中的指定字符嘛,所以这两个参数必不可少。 下面是主要思路: 让字符串从开始到结尾过一遍,如果当前字符不是指定的那个字符,那么就对它重新赋值;如果当前字符是指定的那个字符,那么就跳过它。 那么接下来我们来讲一下上面的代码,f++是先使用f的值,再自增1,所以当fire[g] != c时,执行的语句就相当于对fire[g]重新赋值。但是当fire[g] == c时,除了判断和g自增后没有执行的语句。当fire[g]再次不等于c时,fire[f++] = fire[g]就会执行。因为中间fire[g] == c的时候没有赋值,所以这次fire[f++] = fire[g]就相当于跳过了字符c: 此时的f != g,此时的f就是原来字符c的位置,此时的g就是c字符之后的位置。 此程序设计之精巧真是让人赞叹,虽然每一次for的赋值感觉有点浪费,但是在这个思路下似乎只能如此。如果您有更好的想法欢迎使用GitTalk留言。 那么本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"C","slug":"blog/C","permalink":"http://example.com/categories/blog/C/"}],"tags":[]},{"title":"18岁生日,随便写写","slug":"life3","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2020/09/21/life3/","link":"","permalink":"http://example.com/2020/09/21/life3/","excerpt":"emmm,今天18岁了,来随便写写。 这几天我更新博客的速度显著下降,完全没有了一个月前一天一篇的劲头。其实不是我不想更新,而是我最近在学C语言,我自认为学的效果还算不错,但是没有什么我认为是需要记下来的,所以这几天就停了更新。 最近练车,教练天天数落我,说我理解能力差,后来上升到了人身攻击,说我智商有问题,哈哈,他不知道我根本就没用心学,我的热情都给了计算机,对驾驶汽车这一技术根本谈不上喜欢,因为我认为驾驶汽车上路是很危险的一件事,可能遇到各种各样的突发事件,况且不学汽车也没什么问题。 现在我也当上了现充的人(这词的意思是现实生活充实而不是现场充钱),其实我也不想这样的,实在是因为破事太多 生活所迫。","text":"emmm,今天18岁了,来随便写写。 这几天我更新博客的速度显著下降,完全没有了一个月前一天一篇的劲头。其实不是我不想更新,而是我最近在学C语言,我自认为学的效果还算不错,但是没有什么我认为是需要记下来的,所以这几天就停了更新。 最近练车,教练天天数落我,说我理解能力差,后来上升到了人身攻击,说我智商有问题,哈哈,他不知道我根本就没用心学,我的热情都给了计算机,对驾驶汽车这一技术根本谈不上喜欢,因为我认为驾驶汽车上路是很危险的一件事,可能遇到各种各样的突发事件,况且不学汽车也没什么问题。 现在我也当上了现充的人(这词的意思是现实生活充实而不是现场充钱),其实我也不想这样的,实在是因为破事太多 生活所迫。 最近一个星期,我每天都是早上五点多起床,6:11挨教练数落 练车,7:30恰碗扁粉菜,8:00到家开电脑学C语言,15:00看电影,5:20看英语,21:00睡觉这样的生活 玩游戏的空也没有,我英雄联盟这个赛季还没有上黄金啊啊啊。 这样的生活还是蛮不错的,虽然早上我想多睡会儿… 回想当初大概是15岁的时候,去了北京某伪装成大学的培训机构,从此就开始了我的人生新篇章。上网得借身份证,吃饭要排队,东西贼贵,购物拿个塑料袋还要钱……一开始这些似乎并不算什么,直到我被塞到了江苏。 一开始还好,在人家卫校里面住,饭的味道还不错,空调也很好 ,卫校的妹子也多,除了时不时需要与老师和出卖了自己的身体与灵魂加入学生会的人斗智斗勇之外都挺好的。但是后来的日子就惨了,卫校是租的人家的地方,最后还是要回到该去的地方去。那个地方不但人多,饭难吃,而且空调还要交电费,上了一段时间后感觉这地方不行了,太次了,机构也很会从学生身上抽钱,干脆走得了。正所谓理论引导实践 有了这种想法,我的行为也跟上了思想。然后终于如愿以偿的离开了,虽然还挺舍不得一起待了那么长时间的同学的,不过如果要我继续忍受下去我可受不了。 后来在家待了大概有四个月,家里人觉得我还是应该去上学,于是我爸就和我出发去北京找学校。为什么要去北京呢?我感到很疑惑,问了问家里人,得到的答案是”北京的肯定比郑州的要好”。到底好不好我也不知道,我也没去过郑州的。当时似乎是去了两家机构,一个叫做北大青鸟,一个叫做某云教育。其实我是想去北大青鸟的,因为我之前在某培训机构的时候就有同学去了北大青鸟。但是后来我还是去了某云。当时为什么去某云呢?不知道是不是我去的那个北大青鸟的校区的问题,见到了很多发色奇葩的学生,有种进了小混混占领的街道的感觉,而且那个校区整体来看很没有秩序。我爸一看,这不行哇,这太差劲了,坚决不能来这儿;后来去了某云(我忘了先去的哪了),看到了一帮人穿着西装和皮鞋,我想起了以前在某培训机构被西装皮鞋支配的恐惧,当场表示坚决不来某云(后来没招了,还是去了某云)。其实去某云的一个主要原因是它在吉利大学内,而某云的第一个字的发音也是ji,我和我妈就认为它或多或少都和吉利大学有点关系,然而实际上并没有什么关系,还有个次要原因就是有个招生老师,承诺会发一个”中级软件工程师证”。 后来到了某云,发现学校的生活环境差的离谱。这也是个培训机构,并且随着我对所谓”中级软件工程师证”的疑惑不断加深,我就查了一下,呵呵,并没有这个证。不过在这里还是有些收获的,碰到了同样热爱技术的同学刘某,也碰到了负责的老师和技术大佬(比如v3u.cn),当然也碰到了一些比较水的老师,连vue入门都没搞懂的三牛哥。。。在这里的日子还是很值得怀念的,虽然在这一年多里我连有些室友的名字都没记住,不过当回忆起一边和王女士打电话一边和刘某通宵打游戏的时光还是挺美好的。当然,美好的回忆不只是和hxd一起玩游戏,我还记得和葛同志还有史同志一起学习 探讨如何交上女朋友这个重要话题时的快乐,也记得和室友们道别时心里惆怅的感觉,还记得室友们走后我住上单间的舒服。 距离我脱离正规学校已经三年了,三年间不知经历了多少难过,也不知经历了多少快乐,很多事再回忆都感觉淡了,但也有些事回忆起来仿佛就发生在上一秒。 不知不觉就18岁了。再也不能想着”和他们比我还年轻”这样的话了。希望今后无论何时,我的心都会青春永存,并且在钻研技术的同时也不会忽略掉身边的一切。人不应当只有在回忆的时候才会感觉到美好,希望以后的日子能继续的,不断的发现美。","categories":[{"name":"life","slug":"life","permalink":"http://example.com/categories/life/"}],"tags":[]},{"title":"jwt加密","slug":"jwt加密","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2020/09/13/jwt加密/","link":"","permalink":"http://example.com/2020/09/13/jwt%E5%8A%A0%E5%AF%86/","excerpt":"1234567891011121314151617import datetimeimport jwtpayload = { # 过期时间 'exp': int((datetime.datetime.now() + datetime.timedelta(seconds=60)).timestamp()), 'data': {'uid': 2}}encode_jwt = jwt.encode(payload, 'qwe123', algorithm='HS256')encode_str = str(encode_jwt, 'utf-8')decode_jwt = jwt.decode(encode_str, 'qwe123', algorithms=['HS256'])print(encode_str)print(encode_jwt)print(decode_jwt) 在这之中,paylod中的exp是可选的,data也是可以直接写成:","text":"1234567891011121314151617import datetimeimport jwtpayload = { # 过期时间 'exp': int((datetime.datetime.now() + datetime.timedelta(seconds=60)).timestamp()), 'data': {'uid': 2}}encode_jwt = jwt.encode(payload, 'qwe123', algorithm='HS256')encode_str = str(encode_jwt, 'utf-8')decode_jwt = jwt.decode(encode_str, 'qwe123', algorithms=['HS256'])print(encode_str)print(encode_jwt)print(decode_jwt) 在这之中,paylod中的exp是可选的,data也是可以直接写成: 1'uid': 2 加上exp是为了设置一个过期时间,过了时间后,解码就解不出来了,会直接报错,没错,就是这么直接! 编码的时候,第一个参数是字典类型,第二个参数是密钥,第三个参数是加密方式。 encode之后会返回一条Byte类型的数据,然后将其转码,弄出一个utf-8编码的字符串。 接下来是解码,参数第一个填转码后的字符串,第二个填密钥,第三个要注意了,参数是列表形式,键名也不是编码时的algorithm,而是algorithms。加了个s,这里要看清楚,以免遭遇不测。解码之后就可以看到内容了。 再说一下Django+Vue时使用jwt的设计思路: 在用户成功登陆后就把jwt放在客户端,设置jwt会过期,在过期后使用户退出登陆,再次登陆获取新的jwt。 (2021/03/19)2.0.1版本的PyJWT与之前不同了,encode返回的直接就是字符串,所以上面的代码应该改为: 1234567891011121314import datetimeimport jwtpayload = { # 过期时间 'exp': int((datetime.datetime.now() + datetime.timedelta(seconds=60)).timestamp()), 'data': {'uid': 2}}encode_jwt = jwt.encode(payload, 'qwe123', algorithm='HS256')decode_jwt = jwt.decode(encode_jwt, 'qwe123', algorithms=['HS256'])print(encode_jwt)print(decode_jwt) (这边文章因为写的时候没有设置时间,一修改或者复制就会因为日期重置出现”浮出水面”的现象,所以在2021/03/19将其日期设置为2020/09/13,其实我觉得这篇文章应该是我在2020年6月份写的。。。)本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"Django","slug":"Django","permalink":"http://example.com/tags/Django/"},{"name":"jwt","slug":"jwt","permalink":"http://example.com/tags/jwt/"},{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"}]},{"title":"初次使用zsh","slug":"初次使用zsh","date":"un66fin66","updated":"un22fin22","comments":true,"path":"2020/09/12/初次使用zsh/","link":"","permalink":"http://example.com/2020/09/12/%E5%88%9D%E6%AC%A1%E4%BD%BF%E7%94%A8zsh/","excerpt":"本篇文章参考自:Ubuntu | 安装oh-my-zsh,oh-my-zsh插件推荐。感谢这些博主的分享。 zsh是一个相当骚气的shell,但是由于博主水平过低加上英文辣鸡,所以导致一直想像别人的zsh一样骚气但是一直没有那个效果。今天在和群友吹水的时候突然提到了zsh,正好今天下午学过了C语言,晚上又不想打游戏,那就来折腾一把zsh吧。其实说是折腾,其实一点难度也没有,主要还是因为看了别人的博客和群友提供了帮助。。。 那么废话少说,我们这就开始吧。首先,安装zsh:","text":"本篇文章参考自:Ubuntu | 安装oh-my-zsh,oh-my-zsh插件推荐。感谢这些博主的分享。 zsh是一个相当骚气的shell,但是由于博主水平过低加上英文辣鸡,所以导致一直想像别人的zsh一样骚气但是一直没有那个效果。今天在和群友吹水的时候突然提到了zsh,正好今天下午学过了C语言,晚上又不想打游戏,那就来折腾一把zsh吧。其实说是折腾,其实一点难度也没有,主要还是因为看了别人的博客和群友提供了帮助。。。 那么废话少说,我们这就开始吧。首先,安装zsh: 1pacman -S zsh 然后,将zsh设置为默认的shell: 1chsh -s /bin/zsh 设置完成后需要自己手动注销重新登录才会切换到zsh。注意如果这里加上sudo的话就会把root用户的shell换成zsh,如果不加就是当前用户的。如果没用过zsh可以先输入zsh体验一把: 1zsh 重新登录之后,输入以下命令来查看当前shell: 1echo $SHELL 我的输出是: 1/bin/zsh 安装oh-my-zsh: 1sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" 这样就安装好了。你应该可以看到~/.zshrc有大约一百来行内容,这都是因为oh-my-zsh。这里给出oh-my-zsh的github链接。有一点需要提到的是,上面那条命令中的网站不挂代理应该是无法访问的,似乎是被墙了,哎。。。终端使用https代理的方法: 1export https_proxy=host:port 如: 1export https_proxy=127.0.0.1:1111 已经成功安装好oh-my-zsh了。那么接下来就是照抄参考其他博主的博客了。下面这些内容都是抄自借鉴自最上面我发的那两条链接中下面的那一条链接。一共是三个插件 autojump、zsh-autosuggestion 和 zsh-syntax-highlighting。先配置.zshrc文件,更改plugins的内容: 12345plugins=(git zsh-syntax-highlighting zsh-autosuggestions autojump ) autojump实现目录间快速跳转,想去哪个目录直接 j + 目录名,不用再频繁的 cd 了。 (博主注:不过使用的时候需要注意,比如有个source文件夹,还是要输入cd source,不然就会执行source命令)。安装: 123git clone git://github.com/joelthelion/autojump.gitcd ./autojump./install.py 之后会弹出一段话,让你把一段代码复制到.zshrc中。建议添加到尾部。我抄的那个博主复制的内容是这样的: 1[[ -s ~/.autojump/etc/profile.d/autojump.sh ]] && . ~/.autojump/etc/profile.d/autojump.sh 我自己复制的内容是这样的: 1234[[ -s /home/fire/.autojump/etc/profile.d/autojump.sh ]] && source /home/fire/.autojump/etc/profile.d/autojump.shautoload -U compinit && compinit -u 我这么来暂时也没有出现什么问题,就不改了。 zsh-autosuggestion输入命令时可提示命令,输入右箭头可自动补全,依据是你安装过这个插件后输入的命令。github地址:zsh-autosuggestion。安装: 1git clone git://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions 没错,就这么一条命令。 zsh-syntax-highlighting日常用的命令会高亮显示,命令错误显示红色。github地址:zsh-syntax-highlighting。安装: 1git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting 重启终端或者source ~/.zshrc即可体验安装这些插件后的zsh!","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[{"name":"zsh","slug":"zsh","permalink":"http://example.com/tags/zsh/"}]},{"title":"我的vim配置","slug":"我的vim插件","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2020/09/09/我的vim插件/","link":"","permalink":"http://example.com/2020/09/09/%E6%88%91%E7%9A%84vim%E6%8F%92%E4%BB%B6/","excerpt":"","text":"本篇的目的是为了 记录我现在使用的vim插件。为什么要记录呢?我怕之后系统挂了需要重装的时候忘了都配置了什么了。(ps.博主是vim新手,如果想要一份强大的vim配置文件不要看此文章) 插件管理器: vim-plug Github地址:https://github.com/junegunn/vim-plug 代码补全插件: coc.nvim 实测这个插件在vim上也能使用,而且该插件功能十分强大。不过安装某些代码补全功能时需要编写配置文件,而有些代码补全只要一条命令就可以安装。比如:CocInstall coc-clangd就可以安装c语言的代码补全。如果出现补全的时候闪烁,可以卸载YouCompleteMe试一下能不能变好。Github地址: https://github.com/neoclide/coc.nvim。使用vim-plug安装:Plug ‘neoclide/coc.nvim’, {‘branch’: ‘release’} 括号补全插件: delimitMate。不但有补全的功能,还能够在删除左括号的同时删除掉右括号,我超爱它的。Github地址:https://github.com/Raimondi/delimitMate。使用vim-plug安装: Plug ‘Raimondi/delimitMate’ 同时附上一份我的vim启动时加载的配置:(注意自己去掉#号) 123456set ts=4 # 设置tab为4个空格set expandtab # 我也不知道这是什么,抄别人tab缩进4个空格的配置时写的set autoindent # 这个看名字应该是设置自动缩进吧,同样是抄缩进4个空格写的:set nu # 显示行号。关闭为:set nu!:set pastetoggle=<F2> # 设置是否自动缩进。你明白的,有的时候vim的自动缩进很烦人,抄...借鉴python代码的时候就更是如此。在Normal(普通)模式下按下F2可以关闭自动缩进,再次按F2可以打开缩进 这是我上面提到的插件的配置,使用的插件管理器是vim-plug: 123456call plug#begin('~/.vim/plugged') # 表示插件配置开始Plug 'neoclide/coc.nvim', {'branch': 'release'}Plug 'Raimondi/delimitMate'call plug#end() # 表示插件配置结束","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vim","slug":"blog/vim","permalink":"http://example.com/categories/blog/vim/"}],"tags":[{"name":"vim","slug":"vim","permalink":"http://example.com/tags/vim/"}]},{"title":"scp自动输入密码","slug":"scp自动输入密码","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2020/09/04/scp自动输入密码/","link":"","permalink":"http://example.com/2020/09/04/scp%E8%87%AA%E5%8A%A8%E8%BE%93%E5%85%A5%E5%AF%86%E7%A0%81/","excerpt":"","text":"今天刚刚考完科一,心情十分的舒畅。就在我快乐的学习CSS3的时候,一个朋友问我如何在Linux服务器上和Windows之间传递数据。我一开始想到的是开一个端口,但是很显然,这个方法有点愚蠢,容易把自己暴露在外,何况朋友也没有那个权限。那么怎么做呢?那么就使用scp命令传输文件吧!不过使用scp命令的时候需要输入密码,这就有点不灵活了。于是google了一番,找到了一个不用手动输入密码的方法。 以下代码相关内容参考自linux脚本实现scp命令自动输入密码和yes/no等确认信息,感谢作者分享技术。 那么首先需要安装一个叫做expect的包。在Arch Linux下使用如下命令: 1sudo pacman -S expect 如果你没有sudo命令请先使用su获取root权限,再执行pacman语句。如果找不到这个包是因为你的镜像源配置有问题或者你没有开启extra仓库。 下面是代码, 具体作用请看注释: 1234567891011121314151617#!/bin/bashexpect -c "spawn scp -r /home/fire/newfile [email protected]:/ # 执行scp命令。这个spawn是什么意思我也不知道。expect { \\"*assword\\" # 匹配与assword有关的内容。其实就是为了匹配password啦 { set timeout 500; # timeout,懂得都懂,就不说了 send \\"a123das1\\r\\"; # 输入密码。在\\"到\\r之间输入密码 } \\"yes/no\\" # 这段不用管了,是匹配yes和no的 { send \\"yes\\r\\"; exp_continue;} }expect eof" 执行: 1sh ./auto_scp.sh # 你的脚本名,后缀名命名成什么都没有问题,只要内容对了就行 在特定情况下,此脚本与死循环配合效果极佳。 那么本博客完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"服务器","slug":"blog/服务器","permalink":"http://example.com/categories/blog/%E6%9C%8D%E5%8A%A1%E5%99%A8/"}],"tags":[{"name":"服务器","slug":"服务器","permalink":"http://example.com/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/"}]},{"title":"关于C语言的字符型","slug":"关于C语言中的字符型","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2020/08/30/关于C语言中的字符型/","link":"","permalink":"http://example.com/2020/08/30/%E5%85%B3%E4%BA%8EC%E8%AF%AD%E8%A8%80%E4%B8%AD%E7%9A%84%E5%AD%97%E7%AC%A6%E5%9E%8B/","excerpt":"","text":"依然是直接抄书,,偶尔会自己写一段代码来演示,顺便对书的内容做些删减。参考书为:C Primer Plus。 Char类型char类型用于存储字符(如,字母或标点符号),但是从技术层面看,char是整数类型,因为char类型实际上存储的是整数而不是字符。计算机使用数字编码来处理字符,即用特定的整数表示特定的字符。美国最常用的是ASCII码,在ASCII码中,整数65代表大写字母A。因此,存储字母A实际上存储的是整数65(许多IBM的大型主机使用另一种编码–EBDIC,其原理相同)。标准ASCII码的范围是0~127,只需7位二进制数即可表示。通常,char类型被定义为8位的存储单元,因此容纳标准ASCII码绰绰有余。一般而言,C语言会保证char类型足够大,以存储系统(实现C语言的系统)基本字符。 许多字符集都超过了127, 甚至多于255。例如,日本汉字(kanji)字符集。商用的统一码(Unicode)创建了一个能表示世界范围内多种字符集的系统,目前包含的字符已超过110000个。国际标准化组织(ISO)和国际电工技术委员会(IEC)为字符集开发了ISO/IEC 10646标准。统一码标准也与ISO/IEC 10646标准兼容。 C语言把1字节定义为char类型占用的位(bit)数,因此无论是16位还是32位系统,都可以使用char类型。 声明char变量char变量的声明与其他类型变量的声明方式相同。下面是一些例子: 12345678910#include <stdio.h>int main(){ char a; char b, c; return 0;} 字符常量和初始化如果要把一个字符常量初始化为字母A, 不必背下ASCII码,用计算机语言很容易做到。通过以下初始化把字母A赋给grade即可: 1char grade = 'A'; 在C语言中,用单引号括起来的单个字符被称为字符常量(character constant)。编译器一发现’A’,就会将其转换成相应的代码值。单引号必不可少。下面还有一些其他的例子: 1234char broiled; // 声明一个char类型的变量broiled = 'T'; // 为其赋值,正确broiled = T; // 错误!此时T是一个变量broiled = "T"; // 错误!此时"T"是一个字符串 如上所示,如果省略单引号,编辑器认为T是一个变量名;如果把T用双括号括起来,编辑器则认为”T”是一个字符串。 实际上,字符是以数值形式储存的,所以也可使用数字代码值来赋值: 1char grade = 65; // 对于ASCII,这样做没问题,但是这是一种不好的编程风格 在本例中,虽然65是int类型,但是它在char类型能表示的范围内,所以将其赋值给grade没问题。由于65是字母A对应的ASCII码,因此本例是把A赋给grade。注意,能这样做的前提是系统使用ASCII码。其实,用’A’代替65才是较为妥当的做法,这样在任何系统中都不会出问题。因此最好使用字符常量,而不是数字代码。 奇怪的是,C语言将字符常量视为int类型而非char类型。例如,在int为32位,char为8位的ASCII系统中,有下面代码: 1char grade = 'B'; 本来B对应的数值存储在32位的存储单元中,现在却可以存储在8位的存储单元中(grade)。利用字符常量的这种特性,可以定义一个字符常量’FATE’,即把4个独立的8位ASCII码存储在一个32位的存储单元中。如果把这样的字符常量赋给char类型变量grade, 只有最后八位有效。因此,grade的值是’E’。 非打印字符下面就不提了。因为这篇博客是纯手打,加上非打印字符的内容我就要累死了。。。 打印字符printf()函数用%c指明待打印的字符。前面介绍过,一个字符变量实际上被存储为1字节的整数值。因此,如果用%d转换说明打印char类型变量的值,打印的是一个整数。而%c转换说明告诉printf()打印该整数值对应的字符。例如: 1234567891011#include <stdio.h>int main(){ char fire; fire = 'A'; printf("%c, %d", fire, fire); return 0;} 编译并执行: 123gcc test.c ./a.out A, 65 由此可知,printf()函数的转换说明决定了数据的显示方式,而不是数据的存储方式。 有符号还是无符号有些C编译器把char实现为有符号类型,这意味着char可表示的范围是-128127。而有些编译器把char实现为无符号类型,那么char可表示的范围是0255。根据C90标准,C语言允许在关键字char前面使用signed 或 unsigned。这样,无论编译器默认char是什么类型,signed char 表示有符号类型,而unsigned char表示无符号类型。这在用char类型处理小整数时很有用。如果只用char处理字符,那么char前面无需使用任何修饰符。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"C","slug":"blog/C","permalink":"http://example.com/categories/blog/C/"}],"tags":[]},{"title":"解决\"modprobe_FATAL Module fuse not found in directory /lib/modules/5.8.2-arch1-1\"","slug":"解决modprobe-FATAL-Module-fuse-not-found-in-directory-libmodules5.8.2-arch1-1","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2020/08/24/解决modprobe-FATAL-Module-fuse-not-found-in-directory-libmodules5.8.2-arch1-1/","link":"","permalink":"http://example.com/2020/08/24/%E8%A7%A3%E5%86%B3modprobe-FATAL-Module-fuse-not-found-in-directory-libmodules5.8.2-arch1-1/","excerpt":"","text":"解决标题中提到的问题前几天更新了arch内核,然而今天在挂载磁盘的时候出现了错误,我的命令是这么写的: 1sudo mount /dev/sdb1 /mnt 然而报错了。提示了我windows cache什么什么的,问我windows有没有正常关闭,同时还抛出了以下错误: 1modprobe: FATAL: Module fuse not found in directory /lib/modules/5.8.2-arch1-1 这个windows cache我知道,如果不解决它的话就只能以只读形式访问磁盘。但是这个modprobe我就懵了。所幸有万能的谷歌,在谷歌了大概20分钟后,发现不是自己的ntfs-3g和fuse的问题,挠了挠脑袋后执行了以下命令: 1sudo reboot 是的,你没有看错。就是这条命令,相信你也同我一样,没见过重启还要用管理员权限执行的,其实我现在的Arch重启也是不需要管理员权限的,但是谷歌到的解决方案就是这样。重启之后执行: 1sudo modprobe fuse 然后发现它没有报错!之后再挂载磁盘: 1mount /dev/sdb1 /mnt 说实话这也是我第一次碰见这种事,真是绝了。 解决Windows Cache的问题这个问题就很简单了,谷歌了一下就找到了,执行这条命令: 1ntfsfix /dev/sdb1 把sdb1换成你的设备即可。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"踩坑","slug":"踩坑","permalink":"http://example.com/tags/%E8%B8%A9%E5%9D%91/"}]},{"title":"Arch下Intel+Nvidia双显卡解决方案","slug":"Intel+Nvidia双显卡解决方案","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2020/08/20/Intel+Nvidia双显卡解决方案/","link":"","permalink":"http://example.com/2020/08/20/Intel+Nvidia%E5%8F%8C%E6%98%BE%E5%8D%A1%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/","excerpt":"","text":"参考自:可能是Arch分支最正确的显卡驱动方案。 (2021/02/20)更新两条systemd指令。安装这些包: 12pacman -S nvidia bbswitchyaourt -S optimus-manager-qt # 如果是KDE桌面,可以选择optimus-manager-qt-kde 安装完成后终端输入: 12systemctl enable optimus-managersystemctl start optimus-manager 然后输入: 1sudo optimus-manager-qt 来启动管理器,应该是以一个托盘的形式存在的。这里必须要以管理员权限运行,不然不会出现托盘。启动之后,右击托盘,选择设置,在Optimus选项中将Switching method 选为Bbswitch。然后再右击托盘,就可以选择切换显卡啦。(2021-08-18更新警告)如果出现开机黑屏,先右击optimus-manager-qt的托盘,选择设置,之后点击Nvidia,然后把modeset取消勾选。如果手上没有liveCD,建议直接取消勾选modeset,因为出了问题后你是进不去tty的!!!其实没有liveCD我建议你立即卸载掉optimus-manager,因为如果一出问题基本上就无法抢修了。切换完显卡后需要登出再登入才行,这个程序似乎是提供了自动登出功能的,不过不知道为什么在我的机器上无效,所以手动登出即可。 因为optimus-manager而导致的黑屏问题,只将其设置为开机不启动是没用的,我将其mask了依然开机黑屏。博主建议直接pacman -Rsc optimus-manager卸载。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[]},{"title":"关于C语言中的整数类型","slug":"关于C语言中的整数类型","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2020/08/17/关于C语言中的整数类型/","link":"","permalink":"http://example.com/2020/08/17/%E5%85%B3%E4%BA%8EC%E8%AF%AD%E8%A8%80%E4%B8%AD%E7%9A%84%E6%95%B4%E6%95%B0%E7%B1%BB%E5%9E%8B/","excerpt":"最近学的很杂,PyQt5,数据分析,操作系统……都在学习。可能我就是这样吧,对于任何事物都只有三分钟热情。这样就没有办法了,只能先学一点,等一段时间后再学一点。之前学过C语言,不过学的不深,而且经过了一年多差不多忘的干净了。本篇的主题是关于C语言中的整数类型,参考书为C primer Plus第六版(说是参考,实际上大部分都是抄出我认为有必要记的地方啦)。 那么下面开讲。","text":"最近学的很杂,PyQt5,数据分析,操作系统……都在学习。可能我就是这样吧,对于任何事物都只有三分钟热情。这样就没有办法了,只能先学一点,等一段时间后再学一点。之前学过C语言,不过学的不深,而且经过了一年多差不多忘的干净了。本篇的主题是关于C语言中的整数类型,参考书为C primer Plus第六版(说是参考,实际上大部分都是抄出我认为有必要记的地方啦)。 那么下面开讲。 C语言中的整数类型可以表示不同的取值范围和正负值,一般情况下使用int即可,但是为了满足特定任务和机器的需求,还可以选择其他类型。 int类型int类型是有符号整形,即int类型的值必须是整数,可以是正整数,负整数或零。其取值范围因计算机系统而异。一般而言,储存一个int要占用一个机器字长。因此,早期的16位IBM PC兼容使用16位来储存一个int值。其取值范围(即int值的取值范围)是-3276832767。目前的个人计算机一般是32位,因此用32位储存一个值(其实现在大部分都是64位了,这本书可能有点老了)。ISO C规定int的取值范围最小为-3276832767。一般而言,系统用一个位表示有符号整数的正负号。 声明int类型先写上int,然后写变量名,如: 123int fire;// 也可以一次声明多个变量int gray, ice; 编译器在编译的时候会为变量赋予名称并分配int大小的内存空间。 给变量赋值: 12345678int gray = 4;// 或者一次给多个变量赋值int gray = 4, ice = 5;//也可以选择只赋值给指定的变量int fire, gray = 4;// 也可以通过函数(如scanf())获得值int fire;scanf("%d", &fire); int fire 和int fire = 4的区别是,int fire创建了一个内存空间,而int fire = 4创建了一个内存空间并为其赋值。 C语言把不含小数和指数的数称为整形,因此22和-22都是整形常量,但是22.0和2.2E1则不是。C语言把大多数整形常量视为int类型,但是非常大的整数除外。 打印int值%d指定了在一行中打印整数的位置,%d称为转换说明,它指定了printf()应该用什么格式来显示一个值。格式化字符串中的每个%d都与待打印变量列表中相应的int值匹配。这个值可以是int类型的变量,int类型的常量或其他任何值为int类型的表达式。切记一定要细心,不然可能会出现奇妙的错误。如: 12345678910#include <stdio.h>void meaningless();int main(){ int fire; fire = 10; printf("%d, and %d", fire); return 0;}; 输出结果为: 110, and -1013542408 这个奇怪的负数就令人感到离谱。书上说,因为没有给后面那个%d提供任何值,所以打印出的值是内存中的任意值。 八进制和十六进制在C语言中,用特定的前缀表示使用哪种进制。0x或0X前缀表示十六进制,与此类似,0前缀表示是八进制。不过,使用不同的进制是为了方便,不会影响数被储存的方式,也就是说,无论把数字写成16, 020, 或者0x10,储存该数的方式都相同,因为计算机内部都以二进制进行编码。 下面我写几条C语言中给变量赋值8进制,16进制的语句: 1234int hex, octal; hex = 0X10; // 16进制,以0x或0X为前缀 octal = 010; // 8进制,以0为前缀 printf("%d, %d\\n", hex, octal); 下面是输出结果: 123gcc test.c # 编译C文件./a.out # 执行文件。似乎是因为我使用的Linux系统,使用gcc编译完成后的文件后缀名默认为.out16, 8 # 输出结果。十六进制中的10转换成十进制就是16, 同理八进制中的10转换成十进制就是8 打印时显示八进制和十六进制打印时使用%o显示八进制,使用%x显示十六进制。如果要显示数的前缀,八进制需要使用%#o,十六进制需要使用%#x或%#X。 那么下面有代码演示: 12345int hex, octal;hex = 0X10;octal = 010;printf("%x, %X, %o\\n", hex, hex, octal);printf("%#x, %#X, %#o\\n", hex, hex, octal); 下面是输出结果: 1234gcc test.c # 编译C文件./a.out # 执行文件10, 10, 10 # 输出结果。这里没有加代表进制的前缀0x10, 0X10, 010 # 输出结果。这里加上了代表进制的前缀 通过这一段我们可以明白,在C语言中整数(这里先不提浮点数等,我还没有学到)都是以相同的形式的储存的,printf()只是将其转换成了不同的形式打印出来。想来也是,如果我们储存整数的时候将进制类型也一并储存进去,那么需要增加程序所占用的内存不说,整体看起来也不够优雅,而等到打印的时候再决定数据显示的进制确实为一种机智的方式,我从这里又得到了新的收获。 其他的整数类型C语言提供3个附属关键字修饰基本整数类型: short, long, unsigned。 shot int类型(或者简写为short)占用的储存空间可能比int类型少,常用于较小数值的场合以节省空间。与int类似,short是有符号类型。 long int或long占用的储存空间可能比int多,适用于较大数值的场合。与int类似,long是有符号类型。 long long int 或long long(C99标准加入)占用的储存空间可能比long多,适用于更大数值的场合。该类型至少占64位。与int类似,long long是有符号类型。 unsigned int或unsigned只用于非负值的场合。这种类型与有符号类型表示的范围不同。例如,16位的unsigned int允许的取值范围是065535,而不是-3276832767。用于表示正负号的位现在用于表示另一个二进制位,所以无符号整形可以表示更大的数。 在C90标准中,添加了unsigned long int 或 unsigned long 和unsigned short int 或 unsigned short类型。C99标准又添加了unsigned long long int 或 unsigned long long。 在任何有符号类型前面添加关键字signed,可强调使用符号类型的意图。例如:short, short int, signed short, signed short int都表示同一种类型。 声明其他整数类型其他整数类型的声明方式与int类型相同。 如: 123short fire;unsigned good;signed short goo1; 使用多种整数类型的原因为什么说short类型”可能”比int类型占用的空间少,long类型”可能”比int类型占用的空间多?因为C语言只规定了short占用的储存空间不能多于int,long占用的储存空间不能少于int。这样规定是为了适应不同的机器。例如,过去的一台Windows 3.x的机器上,int类型和short类型都占16位,long类型占32位。后来,Windows和苹果系统都使用16位储存short类型,32位存储int类型和long类型(使用32位可以表示的整数数值超过20亿)。现在,计算机普遍使用64位处理器,为了存储64位的整数,才引入了long long 类型。 现在,个人机上最常见的设置是,long long占64位,long占32位,short占16位,int占16位或32位(依计算机的自然字长而定)。原则上,这四种类型代表四种不同的大小,但在实际使用中,有些类型之间通常有重叠。 如果一个数超出了int类型的取值范围,且在long类型的取值范围内时,使用long类型。然而,对于那些long占用的空间比int大的系统,使用long类型会减慢运算速度。因此,如非必要,不要使用long类型。另外要注意一点,如果在long类型和int类型占用空间相同的机器上编写代码,当确实需要32位的整数时,应使用long类型而不是int类型,以便把程序移植到16位机后仍然可以正常工作。类似的,如果确实需要64位的整数,应使用long long类型。 如果在int设置为32位的系统中要使用16位的值,应使用short类型以节省存储空间。通常,只有当程序使用相对于系统可用内存较大的整形数组时,才需要重点考虑节省空间的问题。使用short类型的另一个原因是,计算机中某些组件使用的硬件寄存器是16位。 long常量和long long常量通常,程序代码中使用的数字(如: 2345)都被储存为int类型。如果使用1000000这样的大数字,超出了int类型所能表示的范围,编译器会将其视为long int类型(假设这种类型可以表示该数字)。如果数字超出了long可表示的最大值,编译器则将其视为unsigned long类型。如果还不够大,编译器则将其视为long long 或unsigned long long类型(前提是编译器能识别这些类型)。 八进制和十六进制常量被视为int类型,如果值太大,编译器会尝试使用unsigned int。如果还不够大,编译器会依次使用long, unsigned long, long long 和 unsigned long long 类型。 整数溢出这里要讲的是整数溢出,那么什么是整数溢出呢?我们先看一段代码: 12345678910#include <stdio.h>int main(){ int i = 2147483647; unsigned int j = 4294967295; printf("%d, %d, %d\\n", i, i+1, i+2); printf("%u, %u, %u", j, j+1, j+2); return 0;} 编译并执行它: 1234gcc test.c./a.out 2147483647, -2147483648, -21474836474294967295, 0, 1 注意一下输出结果,也许你会有疑问,为什么值为2147483647的int类型的变量i加上1就会变成负数呢?同样为什么值为4294967295的unsigned int类型的j加上1就会从零开始呢?这就是整数溢出了。因为int类型是有个取值的区间的,如果大过了这个区间就会从头开始。同理unsigned int也是如此,不过因为unsigned int没有符号,所以是从0开始。接下来我们来看一看变量i转换成二进制后的数,因为printf不支持直接打印出二进制,所以我使用了linux下一个叫做bc的计算器工具。这里变量i转换成二进制后为1111111111111111111111111111111,一共有31位,而变量i实际上是占了32位的,因为还有一个位给了符号。同样,变量j转换成二进制为11111111111111111111111111111111,一共有32位,因为不用腾出一个位给符号了。关于位数的正确与否,不用怀疑我是不是数错了,因为我C语言很差劲,所以拿python算了算,嘿嘿。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"C","slug":"blog/C","permalink":"http://example.com/categories/blog/C/"}],"tags":[]},{"title":"最近的状况","slug":"life2","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2020/08/09/life2/","link":"","permalink":"http://example.com/2020/08/09/life2/","excerpt":"最近的日子很丰富多彩,一个月前坐飞机到宁夏银川附近的一个地方,","text":"最近的日子很丰富多彩,一个月前坐飞机到宁夏银川附近的一个地方,水是黄的,黄到洗衣服衣服也跟着变黄;网是慢的,慢到每天都要重复好几遍pacman -Syu以防因为TimeOut导致没有更新成功;厕所是公共的,在抖音外放声中开门见宝;浴室也是公共的,交了40块钱用凉水洗了一个月。 在那里,我不知道自己要干什么,每天来了一坐,更新,失败,再更新,再失败…终于,更新成功了,喝口热水庆祝一下,然后坐到中午,去食堂二楼恰饭。 运气好了能恰到剩饭,运气不好剩饭都恰不到,只能换个地方,去人堆中挤着打饭。我是很讨厌挤在人堆中的,周围人一多就感到难受,就比如打饭和走路的时候,如果好吃的窗口人多,那么我就会去人少的窗口打饭,尽管饭难吃; 如果大家都走在阴凉地下,那么我会挑人少的地方走,尽管是在太阳暴晒下。 因为天气热,每天身上都黏黏的。打算洗澡的时候,去澡堂看一眼,队又排到门外了,得了,星期四再洗;周末每次洗澡的水都是凉水,刚开始两次没问题,后面就开始感冒了,又咳嗽又流鼻涕,每天都有一卷纸也擦不完的鼻涕。 队友又不行,菜就算了,可以慢慢来,关键是又菜又有小情绪,真的是离谱。而当设计表结构的时候,我是真的服了,不知道是什么需求,不知道自己要干什么,就开始设计;设计的时候不注重大体,倒是能在小地方揪一天。终于把无用的字段添加完了,表关联又不会弄,还得让我一边怀疑的看着表一边修改表的结构。真就云后端,思路都没有就直接设计表,这设计出来能有用吗? 每天都在做基本无意义的事情,感受着时间一分一秒的逝去,过了一天,回头看,时间全部被浪费掉了。我想要努力,我在努力,但是理应帮我更进一步的人,却在牵制我前进的步伐。在这时,同寝室天天嘻嘻哈哈的兄弟都去上海了,我也体验了一把一个人一间屋子的感觉。然好景不长,亦或从来就没有什么好景,住了4天左右,有人要求我换房间。我被子卷好后,去了那个房间,好家伙,我都无法相信那是人住的地方,理应摆上电脑的桌子,放着大约20个饭盒,每个饭盒里都装满了纸;地面相当的脏,还放着一台大音箱,看到它我就知道晚上10点睡觉是不可能的了,脑子中已经自动播放出了七个人喊着”放DJ(电子音乐)”,然后各种蠢到令人想笑的歌都以极高的音量被音箱播放出来。 得了,不待了,跑路。把被子放回我的房间,订好第二天的机票,同时在Boss直聘上找工作。第一个联系的人拒绝了我,但是我依然很感谢他对我的祝福。第二天有人联系我做爬虫,我们聊得很好,而且一看需求我都做得出来,问题也都答的上来,心想就是这家了。上午9点打车去机场,和司机聊了一路,司机看起来有点老了,但是精神面貌很好,还记得他问了我一句,好人会有好报,对吧?当时心中突然莫名的就感动了起来。 提前四个小时到了机场,确实是有点早了,但是这对于习惯做准备的我,根本就不算什么。不过身份证消磁了,有点麻烦。待到将近下午一点的时候,觉得有点饿了,去恰饭,恰了一碗45块钱的牛肉面,心疼极了。坐飞机的时候,不知道这次的飞机是不是有什么问题,耳朵比从郑州飞到银川的时候还难受,感觉耳膜将要不堪重负的裂开来。 终于是落地了,找上联系好的司机,回了家。在家待了几天,慎重考虑之后决定还是先拿了驾照再去找工作,在这之前还有几家公司的HR联系了我,但是我感觉其中有点问题,我问他们技术方面的问题,他们就只给我说是机器技术什么的,完全不跟我说要用什么实现,或者说要干什么,而且很快就要约我去面试,不过最终还是因为考驾照而拒绝了。同时也对给我爬虫岗位的范经理感到抱歉。 最近这几天都是白天学习,晚上和好兄弟打英雄联盟,虽然快乐,但是身体有点吃不消了。作息还是得养好啊。(完)","categories":[{"name":"life","slug":"life","permalink":"http://example.com/categories/life/"}],"tags":[]},{"title":"Linux下如何优雅的截图","slug":"Linux下如何优雅的截图","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2020/08/06/Linux下如何优雅的截图/","link":"","permalink":"http://example.com/2020/08/06/Linux%E4%B8%8B%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E7%9A%84%E6%88%AA%E5%9B%BE/","excerpt":"","text":"之前我一直使用scrot截图,将其设置成快捷键后截图十分的方便,但是最近想要截图放在免费图床上的时候,发现它有一点不好: 截图后的文件体积太大了,尽管有牺牲图片质量来降低文件体积的选项,但是就算质量调成1文件也有1MB多,而如果拉满到100,居然有5MB多。。。我看了看,截出来的图还是蛮清晰的,说实话我认为这点不是缺点,作为一个追求最好的人,图片的质量理所应当也应该是最好;但是如果用在网页上的话,这个追求就有点不适用了,我使用一张5MB的图片意味着网速只有100kb/s的用户需要加载很长时间。作为一个曾很长时间都在偏远地区饱受网速折磨的人,这是不能忍受的,图片有500kb也嫌大。于是就有了这篇博客。 那么这里我使用了GIMP,一个开源的图片编辑器,功能强大到可以说是Linux下的PhotoShop。我得知这软件还能截图还是因为Arch Wiki上提了一嘴。。。 那么现在就开始截图:打开GIMP–>文件–>新建(快捷键是T那个)–>屏幕截图,然后选择截图区域,就可以截图了。 截完图之后你可以在工作区看到图片。想要将图片保存的话,还是点击”文件”,然后点击export(我挺纳闷这里咋就没有汉化,如果有汉化的话应该是”导出”吧),然后可以选择导出到哪个文件夹,以及导出的文件类型,选择好后点击右下角的”导出”,可以看到一个很多英文选项的界面,我们不要纠结选哪个选项,直接把Compression level拉满,即拉到最右,其值应该为9。这代表了最大强度的压缩。 选择好之后点击导出,就会在你选择的文件夹下生成一个大小大约为180kb的文件(我截了一张小窗口的图和一张整个桌面的图,差不多都是这个大小)。 那么本篇博客完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[]},{"title":"PyQt5创建一个基本的程序","slug":"pyqt5一个基本程序","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2020/08/06/pyqt5一个基本程序/","link":"","permalink":"http://example.com/2020/08/06/pyqt5%E4%B8%80%E4%B8%AA%E5%9F%BA%E6%9C%AC%E7%A8%8B%E5%BA%8F/","excerpt":"最近在Boss直聘上找工作,看到很多的爬虫工作上都有”能够一键爬虫”这一要求,我觉得有这个要求可能是考虑到用户的水平吧,既然如此,我想用户也不一定能看得习惯命令行,还是给用户一个方便的UI界面比较好。虽然因为种种原因没有去实际工作(这里感谢提供给我机会的喵房科技的范毓魁经理),不过要学习GUI这事我却记下了。之前曾有想法,想要直接使用HTML页面来控制程序,这样不但方便,也省下学习GUI花费的时间了。 然而想法是美好的,HTML也可以让用户在本地访问,但是你都打开浏览器了,那网页不弄得美观点有点说不过去吧?但是鉴于本人的前端技术实在太烂,终于还是选择了学习Gui编程。 这里使用的是PyQt5,见网上有人说这个性能高,于是就打算学这个了。那么闲聊到此结束,下面先来一道基本程序。","text":"最近在Boss直聘上找工作,看到很多的爬虫工作上都有”能够一键爬虫”这一要求,我觉得有这个要求可能是考虑到用户的水平吧,既然如此,我想用户也不一定能看得习惯命令行,还是给用户一个方便的UI界面比较好。虽然因为种种原因没有去实际工作(这里感谢提供给我机会的喵房科技的范毓魁经理),不过要学习GUI这事我却记下了。之前曾有想法,想要直接使用HTML页面来控制程序,这样不但方便,也省下学习GUI花费的时间了。 然而想法是美好的,HTML也可以让用户在本地访问,但是你都打开浏览器了,那网页不弄得美观点有点说不过去吧?但是鉴于本人的前端技术实在太烂,终于还是选择了学习Gui编程。 这里使用的是PyQt5,见网上有人说这个性能高,于是就打算学这个了。那么闲聊到此结束,下面先来一道基本程序。 演示效果先看演示效果: 那么下面上代码: 12345678910111213141516171819202122232425from PyQt5.Qt import * # Qt模块包含了常用的包和模块import sys# sys.argv可以获取参数,QApplication()实例化一个应用app = QApplication(sys.argv)window = QWidget()# 设置窗口标题window.setWindowTitle('道')# 改变窗口大小window.resize(500, 500)# 窗口位移window.move(400, 200)label = QLabel(window)# 设置文字label.setText('Hello World')# 文字位移(相对于窗口)label.move(200, 200)# 展示窗口window.show()# 退出sys.exit(app.exec_()) 这里我使用的Linux系统的XFCE4桌面,由于我是仿MacOS美化,所以成了红绿灯,不要在意这些细节,如果你是Windows系统的话它会自动变成最小化,最大化,关闭窗口。 关于sys.argv那么我先说一下sys.argv是什么意思,看代码: 1234import sysprint(sys.argv) 运行这段代码: 12 python3 test2.py['test2.py'] 这次我们在脚本执行的时候带上一些参数: 12python3 2test.py 1 2 3['2test.py', '1', '2', '3'] 那么这下就理解了,sys.argv是用来接收参数的,其会返回一个列表,且第一个元素为该文件的路径。然而这里有些理解不能的是,如果使用Pycharm的右击Run运行,输出的路径是绝对路径,如:[‘/home/fire/projectset/PythonGui/2test.py’]。而如果直接使用终端,那么输出的就是如上的内容了。 关于sys.exit(app.exec_())接着我再来解释一下sys.exit()的意思,sys.exit()是用来退出的,它可以接收一个状态码,用来返回程序退出的状态,如果程序正确执行,那么其值应该为0。然后我们知道,有个叫做:”一般顺序结构”的东西,它规定了程序的执行的顺序。在Python中,执行顺序应该是这样的: 先执行第一行,再执行第二行,再第三行…. 那么我们既然都sys.exit()了,窗口为什么没有退出呢?就是因为有app.exec_()阻止了它。你可以试一下,把app.exec_()删掉,然后运行一下脚本,就会看到一个窗口一闪而过,而后程序结束。而加上它,程序会在不崩溃的情况下持续到你手动退出,如果崩溃了则会返回状态码。需要注意的是,如果你没有加sys.exit(app.exec_())这行代码,那么窗口也会一闪而过。所以这个app.exec_()到底是什么神奇的东西呢?它的意思是让程序进入主循环,不要停止(别人的课件上是这么说的),流程应该是:创建一个应用程序对象(app = QApplication()),执行应用程序对象(app.exec_())。而关于app.exec_我Google到了这么一句话: # Your application won’t reach here until you exit and the event. 所以大概就是这么个意思吧。 关于程序参数先来看一段代码: 123456import sysfrom PyQt5.Qt import *app = QApplication(sys.argv)print(app.arguments()) 执行这段代码: 12 python3 2test.py 1 2 4['2test.py', '1', '2', '4'] 可以看到,给QApplication传入的参数,可以被arguments()方法取出来。而如果是两个不同的py脚本,它们之间应该怎么传递参数呢?PyQt5.QtWidgets里有这么一个变量:qApp,从PyQt5.Qt中也可以导入它。那么它的作用是什么呢?使用Pycharm按住Ctrl键左击它,可以发现有这么一行代码: 1qApp = QApplication() # real value of type <class 'PyQt5.QtWidgets.QApplication'> replaced 你可以把这货当成一个全局的变量。看下面的代码: 1234567import sysfrom PyQt5.Qt import *app = QApplication(sys.argv)print(app.arguments())print(qApp.arguments()) 运行: 1234python3 2test.py 1 2 4['2test.py', '1', '2', '4']['2test.py', '1', '2', '4'] 这样就理解了吧。 关于控件的操作控件的操作是这么个流程:创建控件–>设置控件–>展示控件。如果一个控件没有父级控件,这个控件就会被视为是顶层控件,会自动被加上窗口标题,关闭窗口等模块。如果有父控件的话,那么父控件会被视为容器,其会在父控件内部展示。如果你一个程序设置了两个顶层控件,那么当你展示这两个控件的时候,就会出现两个窗口。代码如下: 123456789101112131415161718192021222324import sysfrom PyQt5.Qt import *# 创建一个应用程序对象app = QApplication(sys.argv)# 创建控件# 当我们创建一个控件后,如果这个控件没有父控件,则把它当作顶层控件(窗口)window = QWidget()label = QLabel()# 设置控件window.resize(400, 400)window.setWindowTitle('Great')label.setText('Good text.')# 展示控件# 创建好一个控件后,如果这个控件没有什么父控件,默认情况下不会被展示,只有手动的调用show()才可以# 如果说这个控件有父控件,那么一般情况下父控件展示之后,子控件会自动展示window.show()label.show()# 执行应用程序对象sys.exit(app.exec_()) 这里因为我现在的操作系统截图不便的缘故(因为我使用的GIMP截图,所以面对两个窗口的情况就有些蛋疼了)就不放图片效果了。代码中有两个show()方法的调用,删掉任意一个,就只会展示出一个窗口。因为它们都是顶层控件,所以在调用show()方法的时候都会被视为独立的窗口。 那么接下来就演示一下父控件: 123456789101112131415161718192021222324import sysfrom PyQt5.Qt import *# 创建一个应用程序对象app = QApplication(sys.argv)# 创建控件# 当我们创建一个控件后,如果这个控件没有父控件,则把它当作顶层控件(窗口)window = QWidget()label = QLabel(window)# 设置控件window.resize(400, 400)window.setWindowTitle('Great')label.setText('Good text.')# 展示控件# 创建好一个控件后,如果这个控件没有什么父控件,默认情况下不会被展示,只有手动的调用show()才可以# 如果说这个控件有父控件,那么一般情况下父控件展示之后,子控件会自动展示window.show()# 执行应用程序对象sys.exit(app.exec_()) 在这段代码中我们将QWidget作为QLabel的父控件,将其作为参数传入QLabel()中,展示的时候我们只需展示父控件即可。下面是效果:","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"},{"name":"PyQt5","slug":"blog/Python/PyQt5","permalink":"http://example.com/categories/blog/Python/PyQt5/"}],"tags":[{"name":"PyQt5","slug":"PyQt5","permalink":"http://example.com/tags/PyQt5/"}]},{"title":"Linux下使用Qv2ray实现HTTP代理Chromium","slug":"Qv2ray加Chromium代理","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2020/08/05/Qv2ray加Chromium代理/","link":"","permalink":"http://example.com/2020/08/05/Qv2ray%E5%8A%A0Chromium%E4%BB%A3%E7%90%86/","excerpt":"","text":"首先,你需要有一个Qv2ray,并且配置好V2ray核心等,这里就不再说了,具体流程请看文档: Qv2ray。 连接上节点后,点击Qv2ray中的”首选项”,点击”入站设置”,看好监听地址和HTTP设置,然后创建启动器,比如我的监听地址是127.0.0.1,HTTP端口是8888,那么创建的启动器中命令应该填: 1chromium --proxy-server="http://127.0.0.1:8888" 这样直接打开启动器就能打开代理版的Chromium了。听说Chromium有图形化的代理设置,但是很不幸它不支持我的系统,所以只能从命令行启动了。创建启动器是个省事的方法,省得每次都输入同一条指令了,如果不想创建启动器,那么直接在终端输入上面的指令也是可以的(这句话简直是废话)。 注意,正常启动的Chromium和通过代理命令启动的Chromium是不一样的,其区别不仅在于一个是没有设置代理的,另一个是设置了代理的,重要点在于这两者是相互独立的。这意味着你可以同时打开两个Chromium,其中一个走代理,而另一个不走代理。 那么本篇博客就到这里了。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[{"name":"代理","slug":"代理","permalink":"http://example.com/tags/%E4%BB%A3%E7%90%86/"}]},{"title":"pymysql查询","slug":"pymysql查询","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2020/07/30/pymysql查询/","link":"","permalink":"http://example.com/2020/07/30/pymysql%E6%9F%A5%E8%AF%A2/","excerpt":"","text":"本来我也想不到自己会写这么一篇博客的,但是因为Django的ORM用多了,导致忘掉了pymysql的用户,故此写上一篇。那么先看代码吧: 12345678910import pymysqlcoon = pymysql.connect(host='localhost', user='root', password='11223344', port=3306, db="djject")cur = coon.cursor()arg = ('goodmorning', 'great')sql = 'select id, username from user where username=%s&&password=%s'cur.execute(sql, arg)data = cur.fetchone()if data: print(data) fetchone()是只查询一条,如果数据库中没有符合要求的数据,那么返回None; 如果有,那么返回一个元组,比如我这条语句查询的结果就是(29, ‘goodmorning’)。 下面是fetchall()的作用: 12345678910import pymysqlcoon = pymysql.connect(host='localhost', user='root', password='856300as', port=3306, db="djject")cur = coon.cursor()arg = ('goodmorning', 'great')sql = 'select id, username from user where username=%s&&password=%s'cur.execute(sql, arg)data = cur.fetchall()if data: print(data) fetchall()是多条,如果数据库中没有该数据,则返回(); 如果有,则返回一个元组,比如我这条语句查询的结果就是((29, ‘goodmorning’),)。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"Flask_Restful使用局部装饰器","slug":"flask_restful使用装饰器","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2020/07/29/flask_restful使用装饰器/","link":"","permalink":"http://example.com/2020/07/29/flask_restful%E4%BD%BF%E7%94%A8%E8%A3%85%E9%A5%B0%E5%99%A8/","excerpt":"本篇是给使用了flask_restful的视图加装饰器,如果您想要看的不是这种内容,建议访问官方文档视图装饰器。 本篇博客参考了Flask-RESTful中装饰器的使用-dnsir的博客,感谢博主分享知识。 这里我使用的是JWT加密,一看就懂,那么我直接上代码:","text":"本篇是给使用了flask_restful的视图加装饰器,如果您想要看的不是这种内容,建议访问官方文档视图装饰器。 本篇博客参考了Flask-RESTful中装饰器的使用-dnsir的博客,感谢博主分享知识。 这里我使用的是JWT加密,一看就懂,那么我直接上代码: 1234567891011121314151617# Jwt权限校验def post_jwt_isvalid(func): @wraps(func) def inner(*args, **kwargs): try: jwt_token = request.form['token'] jwt_decode(jwt_token) return func(*args, **kwargs) except Exception: return { 'code': 401, 'msg': '您的登录已过期,请重新登录' } return inner 就是类似这样的一个格式。装饰器已经定义完了,那么下面的就是使用了,使用也很简单,直接看代码就行: 123456789101112131415161718192021class Register(Resource): method_decorators = {'post': [post_jwt_isvalid]} def post(self): name = request.form['username'] password = request.form['password'] uname_pwd_isvalid(name, password) arg = (name, password) sql = 'insert into user (username, password)values(%s, %s)' try: cur.execute(sql, arg) coon.commit() return { 'code': 200, 'msg': '注册成功' } except pymysql.IntegrityError: return { 'code': 403, 'msg': '注册失败,已有相同用户名' } 这样装饰器就能正常运行了。请忽略我把权限验证加在注册这一点上,我只是为了演示。无需担心会报错,在2020/07/19:49时,python3.8.5,Flask1.1.2,Flask-RESTful0.3.8下,实测可以正常运行。 使用abort方便的返回响应并终止视图在看官方文档的时候,突然看到名为Full Example标题下的内容里有一个abort方法,看着官方文档的演示,我感觉自己发现了好东西。abort可以应用在很多场景,比如在函数中进行密码或用户名的校验,如果用户名或密码错误,需要返回响应,不用返回字符串和状态码到视图,再由视图函数判断状态码是否正确,然后return终结函数 这么麻烦,abort能够直接在函数中终止视图的运行。那么下面是我自己定义的一个相当简陋的用户名密码过滤器: 12345678910111213# 帐号密码判断def uname_pwd_isvalid(name, password): if name == '' or not name: # abort(jsonify({'code': 400, 'msg': '用户名不能为空'})) abort({'code': 403, 'msg': '用户名不能为空'}) if name == 'null': abort({'code': 403, 'msg': '用户名含有敏感词汇'}) if password == 'null': abort({'code': 403, 'msg': '密码含有敏感词汇'}) if password == '' or not password: abort({'code': 403, 'msg': '密码不能为空'}) 请不要在意它有多么的简陋,这里放出它的目的只是演示。然后调用(这里只给出代码片段): 123456class Login(Resource): def post(self): username = request.form.get('name') password = request.form.get('password') uname_pwd_isvalid(username, password) 然后访问这个视图: 12345curl http://127.0.0.1:5000/login -d 'name=null' -d 'password=1111111'{ "code": 403, "msg": "用户名含有敏感词汇"} 可以看到,这里的响应是由abort()传出的。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"},{"name":"flask","slug":"blog/Python/flask","permalink":"http://example.com/categories/blog/Python/flask/"}],"tags":[{"name":"jwt","slug":"jwt","permalink":"http://example.com/tags/jwt/"}]},{"title":"Flask使用Restful","slug":"flask使用restful接口","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2020/07/29/flask使用restful接口/","link":"","permalink":"http://example.com/2020/07/29/flask%E4%BD%BF%E7%94%A8restful%E6%8E%A5%E5%8F%A3/","excerpt":"本篇博客参考自官方文档:Quickstart。 一个demo首先,安装flask_restful包: 1pip3 install flask_restful","text":"本篇博客参考自官方文档:Quickstart。 一个demo首先,安装flask_restful包: 1pip3 install flask_restful 然后请看代码: 1234567891011121314151617181920from flask import Flaskfrom flask_restful import Resource, Apiapp = Flask(__name__)api = Api(app)class HelloWorld(Resource): def get(self): return { 'hello': 'world' }api.add_resource(HelloWorld, '/')if __name__ == '__main__': app.run(debug=True) 然后运行: 1python3 yourfile.py 访问接口: 1curl http://127.0.0.1:5000 然后能得到响应: 1234{ "hello": "world"} 多方法flask_restful能够使用多方法,如:get, post, put, delete。那么下面是代码,一看就明白: 12345678910111213141516171819202122from flask import Flask, requestfrom flask_restful import Resource, Apiapp = Flask(__name__)api = Api(app)todos = {}class TodoSimple(Resource): # Get方法 def get(self, todo_id): return {todo_id: todos[todo_id]} # Post方法 def post(self, todo_id): todos[todo_id] = request.form['data'] return {todo_id: todos[todo_id]}api.add_resource(TodoSimple, '/<string:todo_id>')if __name__ == '__main__': app.run(debug=True) 以下是用不同的方法请求接口返回的响应: 123456789101112131415# POST方法请求接口,-d是携带的参数curl http://localhost:5000/111 -d "data=Remember the fire" -X POST# 返回的响应{ "111": "Remember the fire"}# GET方式请求接口curl http://localhost:5000/111# 返回的响应{ "111": "Remember the fire"} 返回不同的响应码: 12345678910111213141516171819202122232425262728293031323334from flask import Flask, requestfrom flask_restful import Resource, Apiapp = Flask(__name__)api = Api(app)class Todo1(Resource): def get(self): # 默认返回状态码是200 return { 'task': 'Hello World' }class Todo2(Resource): def get(self): # 设置响应状态码为201 return { 'task': 'hello world' }, 201class Todo3(Resource): def get(self): # 设置响应码并返回定制的头部(这么翻译似乎有些不太妥当,但是鉴于博主的水平,只能翻译到这一步了) # 原文是这么写的: Set the response code for 201 and return custom headers return { 'task': 'Hello world' },201,{ 'Etag': 'some-opaque-string' } 多个路由指向一个视图12345678910111213141516171819from flask import Flask, requestfrom flask_restful import Resource, Apiapp = Flask(__name__)api = Api(app)class Hello(Resource): def get(self): return { 'msg': 'good' }# 匹配多个路由api.add_resource(Hello, '/', '/hello')if __name__ == '__main__': app.run(debug=True) 这段代码执行后,无论是访问/,还是访问/hello,得到的响应都会是’msg’: ‘good’。 参数解析如果不明白”参数解析”是什么意思的话,就将其当成验证参数就好。使用flask_restful中的一个方法,可以判断参数的类型,如果参数的类型不对,就返回一个自定义的提醒。那么下前请看代码: 1234567891011121314151617181920212223242526from flask import Flask, requestfrom flask_restful import Resource, Api, reqparseapp = Flask(__name__)api = Api(app)# 定义解析规则parser = reqparse.RequestParser()parser.add_argument('rate', type=int, help='Rate to charge for this resource')class Hello(Resource): def get(self): return { 'msg': 'good' } def post(self): # 解析 args = parser.parse_args() return { 'msg': 'good' }api.add_resource(Hello, '/hello')if __name__ == '__main__': app.run(debug=True) 运行服务,访问接口,这里要注意我携带的参数: 12# 这里我携带了一个字符串curl -d 'rate=foo' http://127.0.0.1:5000/hello 得到的响应,这个响应有点怪,因为官方文档里还带了一个status:400,我的响应如下: 12345678{ "message": { "rate": "Rate to charge for this resource" }}# 但是官方文档里的响应是:{'status': 400, 'message': 'foo cannot be converted to int'} 这个响应就是我之前自定义的那个提醒,因为我设置的type为int类型,而’foo’明显是字符串,不是我所指定的类型,因此返回了提醒。接下来再用int类型的参数请求一次: 12# 参数是int类型curl -d 'rate=1' http://127.0.0.1:5000/hello 成功得到响应: 1234{ "msg": "good"} 数据格式化这一内容的主要目的是返回一个对象。官方文档上只给出了代码,而且还不是全部代码,没有给出运行结果,就连名为”Full Example”标题下的内容里都没有与”Data Formatting”(数据格式化),这让理解能力奇差的我感到十分的难过。不过好在按照官方文档的代码写还是成功了。 最后我再放全部代码,那么接下来是第一步:导入模块。 1from flask_restful import fields, marshal_with 看字面意思就知道,fields是字段的意思,不过这个marshal_with就让人有点难以理解了,但是不急,官方文档上说明了,这是个装饰器,而关于fields的作用,官方文档上是这么说的:”you use the fields module to describe the structure of your response.”,用我的垃圾英语来翻译的话,就是”使用字段模块来描述响应的结构体”。这个其实和Django的ORM差不多,那么我们先看代码。 1234resource_fields = { 'task': fields.String, 'uri': fields.Url('todo_ep')} task是一个字符串类型的字段,而uri是一个路由的endpoint参数,当然这些都不是死的,你也可以把uri改成uriii,这没有什么问题。如果这个endpoint不存在,即fields_Url(‘todo_ep’)中的值不存在,那么在运行Flask的时候会报错。如果你没有给路由设置endpoint,那么该路由的endpoint名称默认为该路由的视图名,也就是类名。你需要将其替换成你的视图名。下面来一段代码讲一下: 123class Todo(Resource): passapi.add_resource(Todo, '/') 其中/的endpoint参数,即指向Todo视图的endpoint参数,其值为’todo’。而如果你这么写的话: 123class Todo(Resource): passapi.add_resource(Todo, '/', endpoint="fire") 那么你上面的fields.Url()的参数应该为’fire’,即fields.Url(‘fire’)。当然,如果你认为麻烦,这个字段不写也是可以的,它其实就是一个路由的地址。那么下面,我们首先定义一个类: 1234567class TodoDao(object): def __init__(self, todo_id, task): self.todo_id = todo_id self.task = task # This field will not be send in the response self.status = 'active' 这个类是用来接受参数的,不过实际响应的数据还是得看上面的定义的那个字典。 当然,一切还是以实践为主,所以下面我们贴上全部代码: 12345678910111213141516171819202122232425262728293031323334353637383940from flask import Flask, requestfrom flask_restful import Api, fields, marshal_with, Resourceapp = Flask(__name__)api = Api(app)resource_fields = { 'todo_id': fields.String, 'task': fields.String, }class TodoDao(object): def __init__(self, todo_id, task): self.todo_id = todo_id self.task = task # This field will not be send in the response self.status = 'active'class Todo(Resource): # 装饰器,传入前面的字典,这个字典规定了返回的内容 @marshal_with(resource_fields) def get(self, **kwargs): return TodoDao(todo_id='my_todo', task='Remeber the milk')class ToBeBetter(Resource): # 装饰器,传入前面的字典,这个字典规定了返回的内容 @marshal_with(resource_fields) def get(self, **kwargs): return TodoDao(todo_id='my_todo', task="Remeber Me.")api.add_resource(Todo, '/')api.add_resource(ToBeBetter, '/better')if __name__ == '__main__': app.run(debug=True) 那么接下来运行项目,然后请求接口: 1234567891011121314# 访问/curl http://localhost:5000{ "todo_id": "my_todo", "task": "Remeber the milk"}# 访问/bettercurl http://localhost:5000/better{ "todo_id": "my_todo", "task": "Remeber Me."} 那么下面我加上uri参数: 12345678910111213141516171819202122232425262728293031323334353637383940from flask import Flask, requestfrom flask_restful import Api, fields, marshal_with, Resourceapp = Flask(__name__)api = Api(app)resource_fields = { 'todo_id': fields.String, 'task': fields.String, # 加上了uri参数 'uri': fields.Url('todo'), }class TodoDao(object): def __init__(self, todo_id, task): self.todo_id = todo_id self.task = task # This field will not be send in the response self.status = 'active'class Todo(Resource): @marshal_with(resource_fields) def get(self, **kwargs): return TodoDao(todo_id='my_todo', task='Remeber the milk')class ToBeBetter(Resource): @marshal_with(resource_fields) def get(self, **kwargs): return TodoDao(todo_id='my_todo', task="Remeber Me.")api.add_resource(Todo, '/')api.add_resource(ToBeBetter, '/better')if __name__ == '__main__': app.run(debug=True) 然后使用curl访问接口: 12345678910111213141516# 访问/curl http://localhost:5000{ "todo_id": "my_todo", "task": "Remeber the milk", "uri": "/"}# 访问/bettercurl http://localhost:5000/better{ "todo_id": "my_todo", "task": "Remeber Me.", "uri": "/"} 相信这么一看大家就都明白了,那就是需要一个类来接受参数,需要一个字典来定义响应的参数,需要一个装饰器来完成根据字典的规范返回一个类。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"},{"name":"flask","slug":"blog/Python/flask","permalink":"http://example.com/categories/blog/Python/flask/"}],"tags":[{"name":"Flask","slug":"Flask","permalink":"http://example.com/tags/Flask/"}]},{"title":"第一篇","slug":"life1","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/07/28/life1/","link":"","permalink":"http://example.com/2020/07/28/life1/","excerpt":"","text":"在本篇博客(日记?)发布时,已经有102篇博客啦。本来打算在100篇博客的时候发的,但是因为最近一直访问不了一个国外的Icon网站,所以直到今天才发布。这篇博客是没有什么意义的,只是为了纪念达成100篇博客这个成就。好,那就这样吧,本篇完结~","categories":[{"name":"life","slug":"life","permalink":"http://example.com/categories/life/"}],"tags":[]},{"title":"初学Flask","slug":"flask_初学","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2020/07/27/flask_初学/","link":"","permalink":"http://example.com/2020/07/27/flask_%E5%88%9D%E5%AD%A6/","excerpt":"之前曾学过flask,但是快要忘光了,而且也没有做笔记,这次重温一遍,就当是初学了。本篇是在W3Cschool上学习的,链接:<a href=”https://www.w3cschool.cn/flask/flask_application.html"W3Cschool>。 flask的安装与应用输入以下命令来安装flask: 1pip3 install flask","text":"之前曾学过flask,但是快要忘光了,而且也没有做笔记,这次重温一遍,就当是初学了。本篇是在W3Cschool上学习的,链接:<a href=”https://www.w3cschool.cn/flask/flask_application.html"W3Cschool>。 flask的安装与应用输入以下命令来安装flask: 1pip3 install flask 然后新建一个py文件: 1vim flask_try.py flask_try.py是文件名,你可以随便起。 然后编辑其中内容,每行代码的具体作用看注释: 1234567891011121314151617# 导入flaskfrom flask import Flask# 实例化app = Flask(__name__)# route是个装饰器,用来指定访问哪个路由时应该调用哪个函数@app.route('/')def hello_world(): # 返回一个helloworld return 'Hello World'if __name__ == '__main__': # 开启debug模式 app.debug = True # 运行 app.run() 其中app.debug是可选的,开启debug用来在更改py文件的时候同步刷新flask服务。app.run中可以填入一些参数,如:app.run(host, port, debug, options)。host指要监听的主机名,默认为127.0.0.1(localhost)。port是端口号,默认为5000,options是要转发到底层的Werkzeug服务器。关于路由,如果你不想要使用装饰器的方式来写,也可以这么写: 1app.add_url_rule('/', 'hello',hello_world) 其中’/‘是路由,’hello’似乎是名字, 具体作用我也不太清楚,但是如果不加上hello(任意字符串)就会报一个typeError的错误,hello_world是函数名。那么想要运行flask,只需要输入: 1python3 flask_try.py 即可运行。 flask变量规则关于变量规则,w3cschool是这么说的:”通过向规则参数添加变量部分,可以动态构建url,此变量部分标记为,它作为关键字参数传递给与规则相关联的函数”。如果听不懂也没有关系,实践一下就明白了。那么下面是直接贴代码: 123456789101112131415from flask import Flaskapp = Flask(__name__)# <fire>即为变量@app.route('/<fire>')def hello_world(fire): return 'Hello %s' % fireif __name__ == '__main__': app.debug = True app.run() 其中fire为变量。编辑完成后我们开启flask服务,访问127.0.0.1:5000/water会看到如下内容:Hello water 除了使用字符串变量部分之外,还可以使用int,float和path。w3cschool中是这么说的: 123int 接受整数float 对于浮点值path 接受用作目录分隔符的斜杠 博主作为一个小白和一个理解能力奇差的人,除了int之外float和path都没看懂。但是这不妨碍我们继续进行下去。先来看int和float的: 123456789101112131415161718192021m flask import Flaskapp = Flask(__name__)# 这是int的传参@app.route('/int/<int:fire>')def hello_world(fire): return 'Hello %d' % fire# 这是float的传参@app.route('/float/<float:fnum>')def releasefloat(fnum): return 'This is a float number %f' % fnumif __name__ == '__main__': app.debug = True app.run() 如此定义好之后,访问127.0.0.1:5000/int/1可以看到:Hello 1。访问127.0.0.1:5000/float/1.1可以看到:This is a float number 1.100000。注意,如果访问127.0.0.1:5000/float/1,则会出现not found,这是因为1并不是浮点数。同理,访问127.0.0.1:5000/int/1.1也会出现not found,相信看到这里大家已经很明白了,int和float是指定接受参数的类型,如果不是符合要求的参数,则不会正确匹配到指定的函数。 那么下面要说的就是path了。先看代码: 1234567891011121314151617from flask import Flaskapp = Flask(__name__)@app.route('/try')def hello_world(): return 'Hello'@app.route('/fire/')def hello_fire(): return 'Hello fire'if __name__ == '__main__': app.debug = True app.run() 可以看到,/try是一个url,/fire/也是一个url。运行flask,访问/try的时候,可以看到Hello,但是访问/try/的时候,则出现了not found的提示。但是访问/fire的时候,url则自动变成了/fire/,并且出现了:Hello fire 。为社么会出现这样的情况呢?用W3Cschool中的话来说,类似于/fire/的URL是一个规范URL,因此,访问/fire或/fire/返回相同的输出。 URL构建下面要讲的是重定向和重定向传参。先看代码: 1234567891011121314151617181920212223242526from flask import Flask, redirect, url_forapp = Flask(__name__)@app.route('/try/')def hello_water(): return 'I am water'@app.route('/fire/<water>')def hello_fire(water): if water == 'water': # 如果接受的参数是'water',则重定向到hello_water(即/try/) return redirect(url_for('hello_water')) else: # 如果接受到的是其他的参数,则重定向到not_water(即/water/<name>),并且传递参数(注意看清括号) return redirect(url_for('not_water', name=water))@app.route('/water/<name>')def not_water(name): return 'I am not water, I am %s' % nameif __name__ == '__main__': app.debug = True app.run() 在这里我们使用redirect来进行页面重定向。在函数hello_fire(即路由/fire//)中进行了判断,如果传递过来的参数是’water’,则重定向到/try/,否则则带参跳转到not_water(即路由/water/)。首先访问url: 127.0.0.1:5000/fire/water,可以看到显示出了:I am water,并且可以看到路由变成了127.0.0.1:5000/try/(如果路由没有变就刷新一下页面)。接着我们访问127.0.0.1:5000/fire/water1,页面显示出:I am not water, I am water1。路由也变成了127.0.0.1:5000/water/water1。相信看到这里,大家都会明白了,那么我们就进入下一小题。 HTTP方法下面的全部内容几乎都来自W3Cschool:Http协议是万维网中数据通信的基础。在该协议中定义了从指定URL检索数据的不同方法。下面总结了不同的http方法: 1.GET 以未加密的形式将数据发送到服务器,最常见的方法。2.HEAD 和GET方法相同,但没有响应体。3.POST 用于将HTML表单数据发送到服务器。POST方法接收的数据不由服务器缓存。4.PUT 用上传的内容替换目标资源的所有当前表示。5.DELETE 删除由URL给出的目标资源的所有当前表示。 默认情况下,Flask路由响应GET请求,但是,可以通过为route()装饰器提供方法参数来更改此首选项。为了演示URL路由中使用POST方法,首先让我们创建一个HTML表单,并使用POST方法将表单数据发送到URL。将以下代码存入 123456789101112<html> <body> <form action = "http://localhost:5000/try" method = "post"> <p>Enter Name:</p> <p><input type = "text" name = "nm" /></p> <p><input type = "submit" value = "submit" /></p> </form> </body></html> 编辑py文件: 123456789101112131415161718192021222324from flask import Flask, redirect, url_for, requestapp = Flask(__name__)@app.route('/try', methods=['POST', 'GET'])def hello_water(): # 如果请求方式为POST,从表单数据中获取参数为nm的值并返回一段字符串 if request.method == 'POST': user = request.form['nm'] return 'You are %s' % user # 如果请求方式为GET,获取参数nm的值 # args是包含表单参数对及其对应值对的列表的字典对象 elif request.method == 'GET': user = request.args.get('nm') return 'You got me, %s' % user # 其他情况(其实在只有GET和POST的情况下这条语句是不会执行的) else: return 'How did you request me?'if __name__ == '__main__': app.debug = True app.run() 接下来用浏览器打开刚才编辑的html文件,输入好内容之后提交,页面会跳转到localhost:5000/try,页面内容为 You are 加上你输入的内容。接下来直接访问localhost:5000/try?nm=1,或者将html文件中表单提交方式改为get。如果是通过访问页面localhost:5000/try?nm=1的URL来访问页面的话,内容为You got me, 1。如果是使用GET方法的表单提交的话,内容为You got me, 你输入的内容。 模板可以以HTML的形式返回绑定到某个URL的函数的输出,看代码: 12345678910111213from flask import Flask, redirect, url_for, requestapp = Flask(__name__)@app.route('/')def hello_water(): return '<html><body><h1>Good morning</h1></body></html>'if __name__ == '__main__': app.debug = True app.run() 访问127.0.0.1:5000,可以看到以h1标签形式呈现的Good Morning字符串。但是,从Python代码生成HTML内容很麻烦,尤其是在需要放置变量数据和Python语言元素(如条件或循环时)。这需要经常从HTML中转义。这是可以利用Flask所基于的Jinja2模板引擎的地方。而不是从函数返回硬编码HTML,可以通过render_template()函数呈现HTML文件。 –W3Cschool 12345678910111213from flask import Flask, render_templateapp = Flask(__name__)@app.route('/')def hello_water(): return render_template('try1.html')if __name__ == '__main__': app.debug = True app.run() 接下来,新建一个叫做templates的文件夹,并且在其中新建一个叫做try1.html的文件(html文件叫做什么都行,只要和py文件中写的名字一样都可以,但是文件夹必须叫做templates)。注意,文件夹要和py文件同级。 123mkdir templatescd templatesvim try1.html 下面是html的代码: 12345678<html> <head> </head> <body> <h1>Hello {{name}}</h1> </body> <html> 运行flask,访问127.0.0.1:5000,就可以看到一个h1标题的Welcome to THis Page的字符串。那么也许你会有问题,为什么要将html文件放进一个叫做templates的文件夹里呢,为什么我不可以将html文件放在py文件的同级目录下?很遗憾我已经试过了,放在同级目录下就算将’try1.html’改为’./try1.html’也是行不通的。上面引用自W3Cschool的那句话已经说明白了,这是引用的Jinja2模板引擎,既然是别人制定好的东西就要跟着别人走,这个问题就像是在问”python为什么不叫phython”一样,得到的答案是这是别人定的。好了,那么言归正传,除了能够直接返回html之外,也可以在html添加一些自己想要添加的内容,比如将html这样写: 1234567<html> <body> <h1>Hello {{ name }}!</h1> </body></html> 然后稍稍更改一下py文件: 12345678910111213from flask import Flask, render_templateapp = Flask(__name__)@app.route('/root/<user>')def hello_water(user): return render_template('./try1.html', name=user)if __name__ == '__main__': app.debug = True app.run() 运行py文件,访问127.0.0.1:5000/root/fire, 会看到以h1标题展现的Hello fire。 12345{{name}}是一个占位符,Jinja2模板引擎用以下分隔符从HTML转义。{% ... %}用于语句{{ ... }}用于表达式打印到模板输出{# ... #}用于未包含在模板输出中的注释 # ... ##用于行语句 关于这块的内容我就不多说了,我主要是想要使用flask+vue运行项目,所以使用模板对我应该用处不大。想要了解更多可以访问W3Cschool,我这篇博客就是参照它上面的内容写出来的。 Request对象可以查看官方文档响应对象来深入了解,这里我打算从W3Cschool上搬一段。 123456789101112来自客户端网页的数据作为全局请求对象发送到服务器。为了处理请求数据,应该从Flask模块导入。Request对象的重要属性如下所列: Form - 它是一个字典对象,包含表单参数及其值的键和值对。 args - 解析查询字符串的内容,它是问号(?)之后的URL的一部分。 Cookies - 保存Cookie名称和值的字典对象。 files - 与上传文件有关的数据。 method - 当前请求方法。 其实我觉得这一段设置的有点莫名其妙了,我认为它应该在HTTP方法那一节才对。不过官方文档上这一节也是在这个地方。 CookiesCookie以文本文件的形式存储在客户端的计算机上,其目的是记住和跟踪与客户端使用相关的数据,以获得更好的访问者体验或网站统计信息。Request对象包含Cookie的属性。它是所有cookie变量及其对应值的字典对象,客户端已传输。除此之外,cookie还存储其网站的到期时间,路径和域名。 –来自W3Cschool其实我很少用到Cookie,所以这一节也就略过,感兴趣的可以在W3Cschool上面查看。 Flask跨域参考自让Flask支持跨域–邻居的尾巴。感谢作者提供的解决方法~首先,安装flask-cors模块: 1pip3 install flask-cors 然后请看代码: 12345678910111213141516171819from flask import Flask, redirect, url_for, requestfrom flask_cors import CORSimport jsonapp = Flask(__name__)@app.route('/', methods=['GET', 'POST'])def hello_world(): return json.dumps({ 'code': 200, 'msg': 'Good' })if __name__ == '__main__': # 跨域 CORS(app, supports_credentials=True) app.debug = True app.run() 跨域就是这样~~","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"},{"name":"flask","slug":"blog/Python/flask","permalink":"http://example.com/categories/blog/Python/flask/"}],"tags":[{"name":"Flask","slug":"Flask","permalink":"http://example.com/tags/Flask/"}]},{"title":"部署nginx+vue后没有css样式","slug":"部署vue后没有css样式","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2020/07/23/部署vue后没有css样式/","link":"","permalink":"http://example.com/2020/07/23/%E9%83%A8%E7%BD%B2vue%E5%90%8E%E6%B2%A1%E6%9C%89css%E6%A0%B7%E5%BC%8F/","excerpt":"昨晚部署了一波vue,在本地使用”npm run build”打包后使用scp命令发送到了服务器的根目录,然后配置了服务器的nginx,因为我的服务器是ubuntu,所以用apt-get安装的nginx目录为/etc/nginx。我配置了nginx.conf文件,具体配置如下:","text":"昨晚部署了一波vue,在本地使用”npm run build”打包后使用scp命令发送到了服务器的根目录,然后配置了服务器的nginx,因为我的服务器是ubuntu,所以用apt-get安装的nginx目录为/etc/nginx。我配置了nginx.conf文件,具体配置如下: 1234567891011121314151617181920212223242526272829user www-data;worker_processes auto;pid /run/nginx.pid;events { worker_connections 768; # multi_accept on;}http {include /etc/nginx/mime.types;default_type application/octet-stream;server { listen 80; # 监听端口 server_name localhost; # 域名可以有多个,用空格隔开 location / { root /; #站点根目录,即网页文件存放的根目录, 默认主页目录在nginx安装目录的html子目录。 index index.html index.htm; #目录内的默认打开文件,如果没有匹配到index.html,则搜索index.htm,依次类推 try_files $uri $uri/ /index.html; }} } 本以为部署好了就完了,没想到访问我的ip的时候,出现了css样式无法加载的情况,我测试了一下,向后端发送请求还能发送,也能得到响应。最离谱的是在本地用serve还能正常运行。只有服务器的css离奇失踪。打开控制台,有一条黄色信息: 1Resource interpreted as Stylesheet but transferred with MIME type text/plain 然后百度了2小时,大部分文章内容都一样,并且没有效果,我有点裂开。 今天起床再战,这次我使用了bing搜索,很快就找到了解决方法,参考自知乎 关于google chrome 的 Resource interpreted as stylesheet but transferred with MIME type text/plain问题?。把运行npm run build后在dist目录下生成的index.html文件中的 1<!DOCTYPE html> 删掉。这下就解决了问题。不过我认为这么做有点不够优雅,待我找到了新的解决方法就会更新。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"}]},{"title":"本地运行vuebuild后的项目","slug":"vue_build后页面空白","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2020/07/22/vue_build后页面空白/","link":"","permalink":"http://example.com/2020/07/22/vue_build%E5%90%8E%E9%A1%B5%E9%9D%A2%E7%A9%BA%E7%99%BD/","excerpt":"","text":"安装一个包: 1npm install -g serve 然后在vue根目录下运行serve -s dist来运行项目,一般端口会开在localhost:5000。访问这个端口,就可以看到你的项目了。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"}]},{"title":"几条vim指令","slug":"vim的使用","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2020/07/20/vim的使用/","link":"","permalink":"http://example.com/2020/07/20/vim%E7%9A%84%E4%BD%BF%E7%94%A8/","excerpt":"","text":"虽然Windows也有vim,但是我还是把它归在了Linux下,因为我不想再建立一个只有一篇内容的分类了。。。(2021/02/05,该文章从Linux分类移入vim分类)那么废话少说,这就开始。 显示行数在命令状态下输入: 1:set nu 或者: 1set number 来显示行数。 将光标移动到指定行在命令状态下输入行数,然后按shift+G即可跳转到指定的行数。如: 命令状态下输入 2,然后按shift+G,你就会发现光标移动到了第二行。 撤销、重做和保存命令状态输入u,撤销上一步操作。类似Windows记事本的ctrl+z。命令状态按Ctrl+r,可以撤销回你撤销的操作。类似Windows记事本的ctrl+y。 工作区:sp 文件名 横向工作区:vsp 文件名 纵向工作区按下ctrl + w后再按方向键以切换工作区。ctrl + w + o 只显示当前工作区 选区使用v进行字符选择,使用V进行行选择,使用ctrl + v进行列选择。 替换选中目标后按下r(replace的意思),然后按下想要替换成的字符。如果选中了多个字符后按下r1,那么这多个字符都会被替换成1。 查找指定字符串输入/words来查找指定字符串。words就是你要查找的字符串。查找不到会提示你没有对应的模式。查找到后按下n来查找下一个。 单词跳转输入w来跳转到下一个单词。输入b来跳转到上一个单词。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vim","slug":"blog/vim","permalink":"http://example.com/categories/blog/vim/"}],"tags":[{"name":"vim","slug":"vim","permalink":"http://example.com/tags/vim/"}]},{"title":"Vue修改全局背景","slug":"vue修改全局背景","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2020/07/19/vue修改全局背景/","link":"","permalink":"http://example.com/2020/07/19/vue%E4%BF%AE%E6%94%B9%E5%85%A8%E5%B1%80%E8%83%8C%E6%99%AF/","excerpt":"","text":"最近想要给vue设置一个全局背景,但是直接在组件里设置图片大小总会跟着div大小,在App.vue中设置又会出现顶部的空隙,一顿百度后也没解决问题,后来灵光一闪,想起来了根目录下的index.html,所有的页面都是依照着那个来的,直接在里面设置body的样式,问题就完美解决了。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"}]},{"title":"matplotlib绘制条形图","slug":"matplotlib绘制条形图","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2020/07/17/matplotlib绘制条形图/","link":"","permalink":"http://example.com/2020/07/17/matplotlib%E7%BB%98%E5%88%B6%E6%9D%A1%E5%BD%A2%E5%9B%BE/","excerpt":"","text":"使用matplotlib绘制条形图的步骤其实和绘制散点图差不多,只是调用了不同的方法而已.那么下面我粘贴上代码: 123456789101112131415161718# coding=utf-8from matplotlib import pyplot as pltfrom matplotlib import font_manager# 设置字体myfont = font_manager.FontProperties(fname="/usr/share/fonts/wenquanyi/wqy-zenhei/wqy-zenhei.ttc")# 设置画布plt.figure(figsize=(20, 7), dpi=60)# 数据列表a = ["战狼2", "速度与激情8:\\n八个压路", "功夫瑜伽", "西游伏魔篇", "变形金刚", "摔跤吧"]b = [56.01, 26.94, 17.53, 16.49, 15.45, 12.96]plt.xticks(range(len(a)), a, fontproperties=myfont)plt.ylabel("热度", fontproperties=myfont, rotation=0)plt.xlabel("电影名", fontproperties=myfont)# 绘制条形图, width设置条形的宽度plt.bar(range(len(a)), b, width=0.5)plt.show() 下面是图片: 还有一种绘制条形图的方法: 123456789101112131415161718m matplotlib import pyplot as pltfrom matplotlib import font_manager# 设置字体myfont = font_manager.FontProperties(fname="/usr/share/fonts/wenquanyi/wqy-zenhei/wqy-zenhei.ttc")# 设置画布plt.figure(figsize=(20, 7), dpi=60)# 数据列表a = ["战狼2", "速度与激情8:\\n八个压路", "功夫瑜伽", "西游伏魔篇", "变形金刚", "摔跤吧"]b = [56.01, 26.94, 17.53, 16.49, 15.45, 12.96]plt.yticks(range(len(a)), a, fontproperties=myfont)plt.ylabel("电影名", fontproperties=myfont, rotation=0)plt.xlabel("热度", fontproperties=myfont, rotation=0)# 设置网格,alpha设置透明度,color设置颜色plt.grid(alpha=0.3, color='red')# 画出条形图,height设置高度, color设置颜色plt.barh(range(len(a)), b, height=0.8, color="red")plt.show() 下面是绘制的图片:","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"},{"name":"数据分析","slug":"blog/Python/数据分析","permalink":"http://example.com/categories/blog/Python/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"matplotlib","slug":"matplotlib","permalink":"http://example.com/tags/matplotlib/"}]},{"title":"DjangoJWt解密装饰器","slug":"DjangoJWt装饰器","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2020/07/16/DjangoJWt装饰器/","link":"","permalink":"http://example.com/2020/07/16/DjangoJWt%E8%A3%85%E9%A5%B0%E5%99%A8/","excerpt":"","text":"首先,安装pyjwt模块: 1pip3 install pyjwt 然后写出装饰器函数: 1234567891011121314151617181920212223# jwt验证def jwt_isvalid(func): def inner(request, *args, **kwargs): # 获取token token = request.GET.get('token', None) # 如果获取到了token if token: try: # 进行解密,如果token过期则返回登录过期 jwt_decode = jwt.decode(token, 'grayice777', algorithms=['HS256']) # 成功解密后将解密的结果加入到request.META中以传递给被装饰函数,被装饰函数只需执行jwt_token = request.META['jwt_token']即可取得解密内容 request.META['jwt_token'] = jwt_decode func(request, *args, **kwargs) # token过期,捕获异常,返回响应 except jwt.exceptions.ExpiredSignatureError: return Response({'code': 403, 'msg': '您的登录已过期'}) # 没有获取到token,返回响应 else: return Response({'code': 403, 'msg': '您的登录已过期'}) return inner 被装饰函数: 123456from django.utils.decorators import method_decoratorclass GETSelfInformation(APIView): @method_decorator(jwt_isvalid) def get(self, request): print(request.META['jwt_token']) return Response({'code': 200, 'msg': 'Fine'})","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"jwt","slug":"jwt","permalink":"http://example.com/tags/jwt/"}]},{"title":"解决连接mysql时\"RuntimeError:cryptographyisrequiredforsha256_passwordorcaching_sha2_password\"","slug":"mysqlRuntime报错","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2020/07/16/mysqlRuntime报错/","link":"","permalink":"http://example.com/2020/07/16/mysqlRuntime%E6%8A%A5%E9%94%99/","excerpt":"","text":"转载自RuntimeError: cryptography is required for sha256_password or caching_sha2_password。 方法亲测有效。 解决方法: 安装cryptography: 1pip3 install cryptography","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"踩坑","slug":"踩坑","permalink":"http://example.com/tags/%E8%B8%A9%E5%9D%91/"}]},{"title":"使用matplotlib绘制散点图","slug":"绘制散点图","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2020/07/16/绘制散点图/","link":"","permalink":"http://example.com/2020/07/16/%E7%BB%98%E5%88%B6%E6%95%A3%E7%82%B9%E5%9B%BE/","excerpt":"","text":"使用matplotlib绘制散点图使用pyplot.scatter方法绘制散点图,其他步骤和绘制折线图基本没有什么不同。 下面是代码: 1234567891011121314151617181920212223242526272829303132333435363738from matplotlib import pyplot as pltfrom matplotlib import font_manager# 设置字体my_font = font_manager.FontProperties(fname="C:\\Windows\\Fonts\\simkai.ttf")y_3 = [11, 17, 16, 11, 12, 11, 12, 6, 7, 8, 9, 12, 15, 14, 17, 18, 21, 16]y_10 = [26, 26, 28, 19, 21, 17, 16, 19, 18, 20, 20, 19, 22, 23, 17, 20, 21, 20]# 设置图形大小plt.figure(figsize=(20, 8), dpi=80)x = range(1, 19)x_10 = range(19, 37)print(len(y_3), len(y_10))# 绘制散点图plt.scatter(x, y_3, label='三月份')print(len(x_10), len(y_10))# 绘制散点图plt.scatter(x_10, y_10, label='十月份')# 调整x轴的刻度_x = list(x) + list(x_10)# x轴刻度_xtick_labels = ["三月{}日".format(i) for i in x]# x轴刻度_xtick_labels += ["十月{}日".format(i-19) for i in x_10]# 设置x轴刻度,设置字体,设置字体旋转plt.xticks(_x[::3], _xtick_labels[::3], fontproperties=my_font, rotation=45)# 设置x轴标签plt.xlabel('时间', fontproperties=my_font)# 设置y轴标签plt.ylabel('温度', fontproperties=my_font, rotation=1)# 设置图例plt.legend(loc='upper right', prop=my_font)# 保存图片plt.savefig('./fire.png') 效果如图所示:","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"},{"name":"数据分析","slug":"blog/Python/数据分析","permalink":"http://example.com/categories/blog/Python/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[]},{"title":"解决django_makemigrations时提示版本过低","slug":"Djangomysql版本低","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2020/07/15/Djangomysql版本低/","link":"","permalink":"http://example.com/2020/07/15/Djangomysql%E7%89%88%E6%9C%AC%E4%BD%8E/","excerpt":"","text":"参考自mysql异常处理。 在__init__.py文件夹里添加一行: 1pymysql.version_info = (8, 0, 20, 'final', 0) 再次执行: 1python3 manage.py makemigrations 即可成功。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"Django","slug":"Django","permalink":"http://example.com/tags/Django/"}]},{"title":"DjangoModelSerializer返回指定字段","slug":"DjangoModelserializer返回指定字段","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2020/07/15/DjangoModelserializer返回指定字段/","link":"","permalink":"http://example.com/2020/07/15/DjangoModelserializer%E8%BF%94%E5%9B%9E%E6%8C%87%E5%AE%9A%E5%AD%97%E6%AE%B5/","excerpt":"","text":"可以直接在类里设定返回指定的字段: 123456class ArchUserSerializer(ModelSerializer): class Meta: model = ArchUser fields = ['id', 'username', 'email', 'phone', 'profession', 'company', 'school', 'address', 'name', 'headimg', 'status'] 可以看到,我给fields了一个列表,这个列表规定了返回的字段。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"Django","slug":"Django","permalink":"http://example.com/tags/Django/"}]},{"title":"友链","slug":"友链","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/07/14/友链/","link":"","permalink":"http://example.com/2020/07/14/%E5%8F%8B%E9%93%BE/","excerpt":"","text":"这是我的一个朋友的博客:Alfred-Alan. 这是我的另一个朋友的博客: Admin. 这是一个大佬的博客: v3u.cn. Web前端开发: www.educodes.cn。 我的Arch群友们: 点墨阁, 朝色。","categories":[{"name":"friendlink","slug":"friendlink","permalink":"http://example.com/categories/friendlink/"}],"tags":[]},{"title":"订阅和博客问题日志","slug":"Wdnnd_log","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/07/14/Wdnnd_log/","link":"","permalink":"http://example.com/2020/07/14/Wdnnd_log/","excerpt":"","text":"订阅。关于转载: 转载请注明原作者地址。日志:1.进行博客迁移的时候出现了问题,导致了很多博客的的创建日期及更改日期变成了07-14(我进行迁移的那一天)。我觉得时间不重要,内容好就行。但是以防以前看我博客的人觉得之前看到的文章再次看到有点奇怪(有水博客的嫌疑),特此写一条日志。 2.(2020/08/28)最近重装了一次arch,因为买了新的固态硬盘并且放弃了legacy引导。然而这次迁移博客的时候使用的cp命令,又忘记加保留时间的参数了,所以这次又是几乎所有的博客(我看了看大概有六页)的日期变成同一天了,这次都变成了2020/08/15。。。因为在迁移博客后我发现以前的博客某些地方有些错误并进行了改动,并且改动的哪些博客忘记了,所以就不再保留日期的覆盖一遍了,因为原本的那个日期在我第一次迁移博客的时候也变成了错的了。。。 2020/08/07 发现之前博客的主题不够花哨,于是将主题换成了Icarus,我之前使用的主题是Stun。 2020/12/31 上个星期系统出问题了,我就重装了一下系统,没想到我用来备份文件的磁盘突然变成了只读盘,写进去的文件再次开机后全部丢失了。于是我的博客日期再一次乱了。不过由于在前几次日期乱了之后我吸取了教训设置了font-matter中的date,所以只有一些上古时代发布的文章日期乱掉。在这次事件后,我把博客主题换成了volantis,并且不想搞花里胡哨的东西了。并且这次换主题后,我的以hexo的图片格式添加在文章中的图片全部失效。 2022/02/15 因为工作的原因,我以后的博客会先在本地屯着,啥时候想上传了再上传。","categories":[{"name":"博客日志","slug":"博客日志","permalink":"http://example.com/categories/%E5%8D%9A%E5%AE%A2%E6%97%A5%E5%BF%97/"}],"tags":[]},{"title":"Arch连接蓝牙","slug":"Arch连接蓝牙","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/07/14/Arch连接蓝牙/","link":"","permalink":"http://example.com/2020/07/14/Arch%E8%BF%9E%E6%8E%A5%E8%93%9D%E7%89%99/","excerpt":"本博客部分内容借鉴于:解决Linux无法连接蓝牙耳机Bose QuietComfort 35(QC35)的问题-漫月者,感谢博主的分享! 首先,安装蓝牙模块,参照Arch Wiki:Bluetooth。安装bluez和bluez-utils: 1sudo pacman -S bluez 加载模块: 1modprobe bluetooth 设置开机启动: 1sudo systemctl enable bluetooth.service 开启服务: 1sudo systemctl start bluetooth.service","text":"本博客部分内容借鉴于:解决Linux无法连接蓝牙耳机Bose QuietComfort 35(QC35)的问题-漫月者,感谢博主的分享! 首先,安装蓝牙模块,参照Arch Wiki:Bluetooth。安装bluez和bluez-utils: 1sudo pacman -S bluez 加载模块: 1modprobe bluetooth 设置开机启动: 1sudo systemctl enable bluetooth.service 开启服务: 1sudo systemctl start bluetooth.service 使用bluetoothctl来打开蓝牙控制台: 1bluetoothctl 输入help来查看帮助。输入power on(我也不知道干什么用的,似乎是开启控制台)。输入scan on来搜索设备。使用devices来查看设备。使用pair来配对设备。使用connect来连接设备。注意,pair和connect后面应填的参数是设备的Mac地址,使用devices命令会出现设备的Mac地址。pair一次设备后再连接就不要用pair了,要用connect。使用trust来信任设备,这点常用在于没有PIN码的设备上。使用untrust来取消信任设备。我只是列出了常用的命令,更多的命令还请输入help后查看,当然,是英文的。 我在连接设备的时候出现了:Failed to pair: org.bluez.Error.AuthenticationFailed,百度了一番在解决Linux无法连接蓝牙耳机Bose QuietComfort 35(QC35)的问题上找到了原因:似乎是因为我的设备不支持匹配低功耗设备。根据这位博主的教程,编辑/etc/bluetooth/main.conf文件: 1sudo vim /etc/bluetooth/main.conf 将 # ControllerMode = dual改为ControllerMode = bredr,然后保存。然后重启bluetooth服务: 1sudo systemctl restart bluetooth 这里我重启的时候系统崩掉了。。。自动重启了系统后好了过来。然后你需要断开之前设备的连接和untrust掉之前trust的设备(如果你没有连接设备和trust设备当我没说),然后再次连接,成功~如果连接时出现Failed to connect: org.bluez.Error.Failed错误,那是你没装pulseaudio-bluetooth包,无法支持A2DP协议,装一个再试就可以了。成功连接后声音输出可能会默认走的是HSP/HFP,音质会极差,在音频设置里切换到A2DP就正常了。更多蓝牙音频设备的设置可以查看Arch Wiki:Bluetooth headset。 安装这些包:pulseaudio-alsa, pulseaudio-bluetooth, bluez, bluez-libs, bluez-utils, bluez-firmware。bluez-firmware是AUR包,无法使用pacman直接安装,需要下载。链接可以查看上面发的那个Arch Wiki链接。安装完这些包之后我又安装了一个音频控制器pavuconctrol,这个通过pacman就可以直接下载。安装完成后终端输入pavucontrol就可以使用了。 补充: spotify似乎无法使用蓝牙,这点十分的蛋疼,于是我使用了Audacious播放器,这是个本地播放器,可以通过pacman直接安装。使用了Audacious就可以用蓝牙耳机播放音乐了。。。太坑了,我一直以为是自己耳机的问题或是自己配置的不到位,没想到是播放器不支持。。。) 2020/07/16 10:41补充: 博主发现不是播放器不支持,是打开方式不对,正确的打开方式应该是:bluetoothctl连接蓝牙->打开播放器->播放音乐。这样无论是什么播放器都会使用耳机播放音乐了。还有一点,如果你连接上蓝牙打开播放器听了一段时间后又断开了蓝牙,那么需要重新启动播放器才能使用蓝牙耳机(流程: 连接蓝牙耳机->关闭播放器->打开播放器->播放音乐)。那么本博客完~","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"bluetooth","slug":"bluetooth","permalink":"http://example.com/tags/bluetooth/"}]},{"title":"我的常用工具","slug":"常用工具","date":"un11fin11","updated":"un22fin22","comments":true,"path":"2020/07/13/常用工具/","link":"","permalink":"http://example.com/2020/07/13/%E5%B8%B8%E7%94%A8%E5%B7%A5%E5%85%B7/","excerpt":"","text":"音量控制器: alsamixer截图:scrot,被我设置成ctrl + alt + P了。按下这一组快捷键会在/home/fire/Desktop下生成截图文件。vscode: codeOSD Lyrics: 歌词性能监测: htop蓝牙:bluetoothctlv2ray: 终端运行v2raya,浏览器访问:https://v2raya.mzz.pub/。或者使用Qv2ray端口代理。锁屏工具: xscreensaver,现在被我调成了Super + L。输入法:fcitx5本地检索工具: Albert我的世界:hmcl磁力链接下载: aria2c 链接 默认下载到当前所在文件夹。网络工具: NetworkManager,命令行界面nmcli,ui界面nmtui调用电脑摄像头: cheese图形化包管理器: pacmac(开启了会自动最小化到托盘)抓包工具: wiresharkarp工具: bettercap图形化网络管理工具: nmtui(备注:这玩意儿似乎还能管理蓝牙)。谷歌地球: 命令:/opt/google/earth/pro/google-earth-pro %f。可以直接创建启动>器来运行。网络监控工具: nethogs,使用的时候需要有root权限。思维导图工具: draw.io,github地址:drawio-desktop命令行浏览器: lynx, w3m画图工具: kolourpaint数据库可视化工具: dbeaver在线听歌软件: Spotify,不可通过pacman安装。MarkDown编辑器: Typora图片查看器: nomacs办公: WPS图片处理: GIMP(说实话这软件我有点不会用)录屏工具: OBS Studio最最重要的工具: KDE Connect.这简直是神器, 相信用过KDE的朋友都知道这个,是手机与电脑之间传输数据的利器。当然不只是传输文件和剪切板,其还能控制媒体播放。而且其在xfce下也可以使用,简直爽爆了。音量增强器: pavucontrol。这个我已经在Arch群和群友吹过好几次了,它的实际用途当然不是音量增强,但是它确实可以做到音量增强(比如把KDE下的最大音量再增强50%), 不过增强后会有点失真。PDF阅读工具: Okular。这个是kde-applications包自带的, 但是用起来效果还不错。下拉式终端: Yakuake。显卡切换工具: Optimus-Manager。可以在Intel和Nvidia之间切换,很舒服,我博客里写过教程。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"简历","slug":"resume-0","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/05/05/resume-0/","link":"","permalink":"http://example.com/2020/05/05/resume-0/","excerpt":"个人信息:姓名: 吴松哲","text":"个人信息:姓名: 吴松哲 年龄: 18岁 学历: 大专 联系方式QQ: 2548519719 邮箱: as2548519719@gmail.com 2548519719@qq.com firestayinmyeyes.com 技能:熟练使用Linux系统,曾使用CentOS,Ubuntu部署项目,并且十分熟练使用或配置Arch Linux 完全适应在Linux下进行开发(事实上我的主系统就是Arch Linux) 熟练使用Python 能够使用Scrapy框架 能够使用matplotlib进行数据分析 熟练使用Django框架 熟练使用Vue.js进行前端开发 熟练使用Flask框架 使用celery异步邮件发送 能够使用redis数据库 能够使用mongodb数据库 写过的无聊小项目使用Selenium爬取登录需要加法验证的某游戏交流网站并使用xpath对目标数据进行提取 使用Django+Vue做过一个模拟的商城,包含了登录注册,个人信息,JWT权限验证,图形验证码,滑动验证,发邮件,使用PIL给图片打水印和制作用户默认头像,上传文件进度条,商品展示,商品轮播图,商品的详情。但是尚未写出购物车和支付。 替换文件同级目录下.txt后缀名和.md后缀名的文件中指定字符串并将被替换的文件备份(这个可真的是无聊的项目) 个人爱好喜欢研究技术,对于一些很多人都在吹的技术有着很强的探索欲望 不管是各方各面的技术知识,都乐意学习和吸收 没事的时候喜欢研究计算机底层 喜欢给电脑装各种系统 Arch Linux粉丝","categories":[{"name":"resume","slug":"resume","permalink":"http://example.com/categories/resume/"}],"tags":[]},{"title":"PIL长图片合成","slug":"PIL长图片合成","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2020/05/01/PIL长图片合成/","link":"","permalink":"http://example.com/2020/05/01/PIL%E9%95%BF%E5%9B%BE%E7%89%87%E5%90%88%E6%88%90/","excerpt":"这次的内容是PIL长图片合成,效果如下:","text":"这次的内容是PIL长图片合成,效果如下: 我爬一个图片网站的时候发现怕取下来的都是小图片,还需要合成,于是在经过一番百度后成功做出可将小图片合成长图片的代码。 不过在讲解代码前先让我讲一下我的目录结构: ./download/ 章节1文件夹, 图片1,图片2….. 章节2文件夹 图片1, 图片2….. 章节…. 就是这么个结构,如果理解不了这里还有文字描述版的: 在download目录下有许多个文件夹,每个文件夹里都是一个章节的图片,这些文件夹已经命名完毕,命名为: 章节1,章节2, 章节3 ……在每个章节文件夹下都有该章节的小图片,图片已经命名完毕,命名为: 1.jpg, 2.jpg, 3.jpg ….每个文件夹下的图片数量都不一定相同。 那么下面就是代码了: 1234567891011121314151617181920212223242526272829303132333435363738394041424344from PIL import Imagefrom os import listdirimport re# 给图片按章节排序new = sorted(listdir('./download'), key=lambda i: int(re.search('(\\d+)', i).group()))print(new)# 设置一个count用来给合成的长图命名count = 1for dir1 in new: height_list = [] width_list = [] # 获取每个文件夹里的图片名 lop = listdir('./download/%s' % dir1) dir_length = len(lop) # 以下代码用来获取该章节下所有图片宽度和图片长度 for i in lop: im = Image.open('./download/%s/' % dir1 + i) height_list.append(im.size[1]) width_list.append(im.size[0]) # 获取最长的图片尺寸和最宽的图片尺寸,以防画布过小导致无法展示全部图片 max_height = max(height_list) max_width = max(width_list) # 生成一张新图片 new_pic = Image.new('RGB', (max_width, max_height * dir_length)) # 拼接目录下所有图片 for row in range(1, dir_length + 1): im = Image.open('./download/%s/' % dir1 + str(row) + '.jpg') # 这段是我抄的代码,是用来改图片尺寸的,我认为不需要改的话去掉没问题,但是我没去掉过,你可以试试 from_image = im.resize( (width_list[row - 1], height_list[row - 1]), Image.ANTIALIAS) # 说明一下这里的元组: 第一个参数是x轴的位置,这段代码即粘贴到x轴的0上,第二个是y轴,道理同x new_pic.paste(from_image, (0, (row - 1) * height_list[row - 1])) count += 1 # 如果报错了就告诉我哪个章节错了,因为我已经分好了章节,所以可以直接用这种方式,同时防止程序停止运行 try: new_pic.save('./pages/%s.jpg' % count) except Exception: print(count) 最后一点的这个try,我觉得是没有必要的,但是有两个章节报错了,理由我也不知道为什么,不过报错之后你不理他,他会生成一张好的图片,一张坏的图片。。。所以我就加上try了,没有必要改进嘛。同时说明一下,坏的图片就是输出的那个count,好的图片名为count + 1,而且最终结果是全部都搞好了,没有遗漏章节,所以我对于这个报错就感到了一脸迷惑,不过说不定是我的目录有问题。 那么这次博客就到这里了,每天进步一点点~~","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"PIL","slug":"PIL","permalink":"http://example.com/tags/PIL/"},{"name":"图片合成","slug":"图片合成","permalink":"http://example.com/tags/%E5%9B%BE%E7%89%87%E5%90%88%E6%88%90/"}]},{"title":"七牛云对象存储","slug":"七牛云云存储","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2020/04/30/七牛云云存储/","link":"","permalink":"http://example.com/2020/04/30/%E4%B8%83%E7%89%9B%E4%BA%91%E4%BA%91%E5%AD%98%E5%82%A8/","excerpt":"想要用七牛云对象储存,首先,你需要有一个七牛云账号。注册好账号之后点击右上角的控制台,选择对象存储,开心的创建应用…..???什么!居然需要实名认证!告辞… 那么实名认证之后,在命令行输入:","text":"想要用七牛云对象储存,首先,你需要有一个七牛云账号。注册好账号之后点击右上角的控制台,选择对象存储,开心的创建应用…..???什么!居然需要实名认证!告辞… 那么实名认证之后,在命令行输入: 1pip install qiniu 导入七牛云,然后创建一个实例 12345678910from qiniu import Authclass Qiniu(APIView): def get(self, request): # 声明认证对象 q = Auth('你的AK', "你的SK") # 获取token token = q.upload_token('你创建的应用名') return Response({'token': token}) 这里需要说明一下。想要查看你的AK和SK的话,需要将鼠标悬停在右上角那个应该是头像的地方,然后网页会自动弹出一个可供选择的列表,选择密钥管理,就可以看到你的AK和SK啦,不过如果想要复制SK的话,需要点击“显示”,不然复制不了。 这个类是用来获取token的,由于token每隔几分钟就会更新一次(好像是以分钟为单位的,不过确定的是它是动态的),所以前端每次发送文件都从后台这里拿token,然后带着token传递文件给七牛云。 12345678910111213141516171819<table> <tr> <td> 用户头像: <Avatar :src="src" wifth='150' fit="fill"></Avatar> </td> <td> <input type="file" @change="submit"> </td> </tr> <tr> <td> 七牛云上传 </td> <td> <input type="file" @change="upload_qiniu"> </td> </tr> </table> 上面的是页面代码,下面的为函数: 1234567891011121314151617181920get_token:function(){ this.axios.get('http://127.0.0.1:8000/qiniu/').then(res=>{ this.token = res.data.token; }) },upload_qiniu:function(e){ var file = e.target.files[0]; let param = new FormData; param.append('file', file, file.name); param.append('token', this.token); const axios_qiniu = this.axios.create({withCredentials: false}); axios_qiniu({ url: 'http://up-z2.qiniu.com', method: 'post', data: param, timeout: 40000 }).then(res=>{ console.log(res.data); }) } timeout是为了防止传输超时,url是要对应你创建的应用的地址的,比如你创建的应用在华南,那么只能用华南的,如果创建的在华北,那么只能用华北的,这个可以在官网查,或者当你传错url的时候会返回给你一个你应当传的url,复制下来用就行。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"对象存储","slug":"对象存储","permalink":"http://example.com/tags/%E5%AF%B9%E8%B1%A1%E5%AD%98%E5%82%A8/"}]},{"title":"Deepin打开窗口特效","slug":"deepin打开窗口特效","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2020/04/30/deepin打开窗口特效/","link":"","permalink":"http://example.com/2020/04/30/deepin%E6%89%93%E5%BC%80%E7%AA%97%E5%8F%A3%E7%89%B9%E6%95%88/","excerpt":"如果在使用deepin系统时出现个性化里的窗口特效打不开的情况,那么可以采取以下方法: 首先,安装模块 1sudo apt install deepin-wm 然后输入以下命令: 1deepin-wm --replace","text":"如果在使用deepin系统时出现个性化里的窗口特效打不开的情况,那么可以采取以下方法: 首先,安装模块 1sudo apt install deepin-wm 然后输入以下命令: 1deepin-wm --replace 该命令会强制开启窗口特效,但是有一点要注意,那就是当终端运行该命令之后会一直保持着状态直到你终端进程(Ctrl+C)或者关闭终端。那么问题这时就来了!当你终止该命令或者关闭终端时,你的任务栏会消失并且无法通过等待等出来,当你打算重新启动系统的时候,按下Ctrl + Alt + T呼出终端,但是这时你会发现你的光标无法闪烁在终端上,此时建议砸电脑…不要急,右键终端,再左键一下输入栏,光标就会闪烁了,不过不排除这样还不会闪烁的情况,那么就再呼出一个终端,再次右键一下终端,左键一下终端,这样就会好。如果还不好,,,那建议你按Ctrl + Alt + F3(F4, F5,….都行),然后输入reboot吧(doge)。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Deepin","slug":"blog/Deepin","permalink":"http://example.com/categories/blog/Deepin/"}],"tags":[{"name":"Deepin","slug":"Deepin","permalink":"http://example.com/tags/Deepin/"},{"name":"踩坑","slug":"踩坑","permalink":"http://example.com/tags/%E8%B8%A9%E5%9D%91/"}]},{"title":"Windows+deepin双系统安装及踩坑","slug":"windows+deepin","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2020/04/29/windows+deepin/","link":"","permalink":"http://example.com/2020/04/29/windows+deepin/","excerpt":"前几天突然想起来了之前心血来潮下载的deepin镜像,但是当时有事没安装,终于有时间了打算来安装一下。","text":"前几天突然想起来了之前心血来潮下载的deepin镜像,但是当时有事没安装,终于有时间了打算来安装一下。(博主是双磁盘安装,并且从未单磁盘安装过双系统) 结果刚开始就碰到了一个问题,之前安装过windows+kali,但是kali一进去电脑就关机了,,,然后当时身为小白的我(现在是萌新)不知道操作了什么把基本磁盘升成动态磁盘了,这下好了,想要转换回基本磁盘得费一番功夫。 在网上看教程说是有什么无损动态磁盘转换器,下载了一个,结果没有用,然后我想了想就把数据备份后将磁盘格式化了,格式化之后就获得了一个基本磁盘。从备份数据到格式化再到将数据粘贴回新分的盘,用了我一天的时间。。。 然而这之后还有一个小坑: 我的mysql文件保存在被格式化的那个盘,虽然我备份了,但是在我格式化的时候mysql服务就已经停止了,所以在将数据还回去后需要重新开启一下mysql服务,并且我之前写的git的自动提交脚本也不好使了,对于这个问题相信读者们都知道该怎么做,我是直接 rm -r –cached . 全部删除后重新上传了一遍。。 那么接下来是制作Deepin启动盘使用Rufus制作Deepin启动盘,在这里我有一点要说,那就是如果你是双磁盘的话,安装deepin的磁盘需要是GPT分区表,这点非常重要!如果磁盘是MBR的可以用DiskGenius来将其转为GPT。那么我先来解释一下为什么是GPT分区:之前我并不知道需要GPT分区,用过各种方法了始终不能启动deepin,甚至BIOS里都没有deepin的引导项,当我换成GPT后事情才好转了起来(虽然依然没有引导项)。并且除此之外你需要关闭快速启动和安全启动。 Rufus制作U盘的时候分区选择GPT, 选择ISO镜像而不是DD(我使用过一次DD,结果安装的时候出错了,当场就停止了我的安装并让我退出安装界面),然后其他你就看自己需要选就行。 安装系统首先你需要分出来一块给deepin留的未分配的空间,建议大于20G,我留了150G。 电脑插上Rufus制作的U盘后重启电脑,按F12(我是F12)进入BIOS,关闭安全启动,将U盘放在启动顺序的第一位,保存并退出。 然后就进入了deepin的界面了,选择第一项(如果对安装文件的完整性心存疑惑的话选择第三项check md5),语言选择简体中文,记得在协议那里打上对勾,不然你无法进行下一步(doge), 然后其他的看你情况自己选,在安装界面那里选择简易模式,选择你留出的那块未分配的空间,确认,然后等待一杯咖啡的时间,不知道是我水凉得快还是怎么回事,我喝了一杯咖啡又等了将近5分钟…咳咳,那么系统安装完了,接下来还得再解决一个坑。 解决没有deepin引导项的问题就是这个坑,折磨了我两天,现在终于解决了!只要在Windows上用EasyBCD就行了,打开EasyBCD,选择添加新条目,选择Linux/BSD,类型选GRUB2, 驱动器选你分给deepin的那块空间(旁边没有Linux标识也不要紧),选好之后点击加号(或者是创建新条目)。这样再次进入Windows的时候会让你选择进Deepin还是Windows,选择deepin就可以了。 这里再分享下我踩的坑: 第一次我分了三个区,一个/boot, 一个/home, 当然还有一个/,结果使用EasyBCD的时候选择这三个中的无论哪一个都进不去Deepin,我简直裂开了,经过多次尝试才发现使用简易安装才是最简单的、最好使的方法(我安装Deepin只是为了看看那个好看的界面,并没有特别需求)。 解决卡Deepin logo界面的问题在我满心欢喜的进入Deepin界面后,果断选择了第一个选项,然后等着我的是长达1分钟的Deepin logo(并不是只有一分钟,而是等了一分钟后我强制关机了),关机后我看了看别人的博客,得到以下一段话: 12进入第一个安装界面时一定要注意:在跳转前,按E进入grub设置界面,移动光标到倒数第二行的”quiet splash”后面,空一格输入“nouveau.modeset=0”注意中间的那个点,如此的目的个人理解应该是禁止安装时加载Intel的开源驱动,modeset=0针对的就是Intel显卡,不这样输入的话就会卡死在deepin几个文字的安装界面 。为了避免每次进系统前输入那条命令,我们可以在deepin系统里打开终端命令,将grub启动设置保存为默认模板: 输入 sudo vi /etc/default/grub 输入密码 进入文本 第三行splash quite空一格输入 nouveau.modeset=0 点击键盘ESC再输入分号:接着输入wq进行文本的保存 输入 sudo update-grub 进行模板的更新,下次就按照这个进入系统了。 我按照这句话做了之后,真的好使了,可以进入Deepin了,不过进入之后系统奇慢,比我之前用Windows + Ubuntu的时候还要慢,也不知道为什么,不过反正是来体验的嘛,快慢无所谓~ 进去之后使用apt-get update来获取更新列表时,居然提醒我权限不够,,然后我使用了: 1sudo apt-get update 来获取更新列表,并且使用了: 1sudo apt-get upgrade 来获取更新。 使用体验: 总的来说还是相当不错的,系统内置了搜狗输入法,Chrome,并且在更新之后还有了WPS,界面也美观,很好的体验~ 那么本博客完,敲到了11点半,也该睡了,大家再见~","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Deepin","slug":"blog/Deepin","permalink":"http://example.com/categories/blog/Deepin/"}],"tags":[{"name":"Deepin","slug":"Deepin","permalink":"http://example.com/tags/Deepin/"},{"name":"踩坑","slug":"踩坑","permalink":"http://example.com/tags/%E8%B8%A9%E5%9D%91/"},{"name":"双系统","slug":"双系统","permalink":"http://example.com/tags/%E5%8F%8C%E7%B3%BB%E7%BB%9F/"}]},{"title":"Vue传递图片到Django","slug":"Vue传递图片到Django","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2020/04/29/Vue传递图片到Django/","link":"","permalink":"http://example.com/2020/04/29/Vue%E4%BC%A0%E9%80%92%E5%9B%BE%E7%89%87%E5%88%B0Django/","excerpt":"html代码: 1<input type="file" @change="submit">","text":"html代码: 1<input type="file" @change="submit"> vue代码: 1234567891011121314151617181920212223<script>import dragVerify from "vue-drag-verify";import myheader from './myheader.vue';export default { data () { return { } }, methods:{ submit:function(e){ // 请求后台接口 var file = e.target.files[0]; let form_data = new FormData(); let config = {headers: {'Content-Type': 'multipart/form-data'}}; form_data.append('file', file); this.axios.post('http://127.0.0.1:8000/upload/', form_data, config).then(res=>{ console.log(res); }) }, }</script> Django代码: 123456789101112131415161718192021def post(self, request): # 接收文件 myfile = request.FILES.get('file', None) # 建立文件流对象 if myfile: filename = str(uuid.uuid4()) + myfile.name[myfile.name.rfind('.'):] sql = 'update user set img="%s" where username="%s"' % (filename, username) try: cursor.execute(sql) coon.commit() except Exception: print(sql) print(sql) f = open(os.path.join(UPLOAD_ROOT, '', filename), 'wb') for chunk in myfile.chunks(): f.write(chunk) f.close() return Response({'filename': myfile.name}) else: return {'msg': '图片接收失败'} 关键代码: 123456myfile = request.FILES.get('file', None)filename = str(uuid.uuid4()) + myfile.name[myfile.name.rfind('.'):]f = open(os.path.join(UPLOAD_ROOT, '', filename), 'wb')for chunk in myfile.chunks(): f.write(chunk)f.close() 这里的UPLOAD_ROOT是我的settings.py里面的配置,是文件夹路径,如果不想从settings.py导入的话直接输入路径就行,如: “/static/“","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"Django","slug":"Django","permalink":"http://example.com/tags/Django/"},{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"}]},{"title":"cv2压缩图片","slug":"2行代码压缩图片","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2020/04/29/2行代码压缩图片/","link":"","permalink":"http://example.com/2020/04/29/2%E8%A1%8C%E4%BB%A3%E7%A0%81%E5%8E%8B%E7%BC%A9%E5%9B%BE%E7%89%87/","excerpt":"需要导入python的cv2库 pip导入: 1pip install opencv-python","text":"需要导入python的cv2库 pip导入: 1pip install opencv-python 下面是代码: 12345678910import cv2# 图像压缩def compressed_picture(pic_path, new_filename): img = cv2.imread(pic_path) # 压缩 0-9等级越高压缩幅度越大,相应的像素损失越大, 0是无压缩 cv2.imwrite(new_filename, img, [cv2.IMWRITE_PNG_COMPRESSION, 5]) # 这个压缩的幅度更大,50为压缩到原本图片的一半数据量,10为压缩到原本图片的10%数据量(然而实际效果似乎并非如此) cv2.imwrite(new_filename, img, [cv2.IMWRITE_JPEG_QUALITY, 50])","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"cv2","slug":"cv2","permalink":"http://example.com/tags/cv2/"},{"name":"压缩图片","slug":"压缩图片","permalink":"http://example.com/tags/%E5%8E%8B%E7%BC%A9%E5%9B%BE%E7%89%87/"}]},{"title":"使用PIL添加中文水印","slug":"PIL添加中文水印","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2020/04/29/PIL添加中文水印/","link":"","permalink":"http://example.com/2020/04/29/PIL%E6%B7%BB%E5%8A%A0%E4%B8%AD%E6%96%87%E6%B0%B4%E5%8D%B0/","excerpt":"原图: 加水印后:","text":"原图: 加水印后: 使用python的PIL库给图片添加水印: 12345678910111213141516171819202122232425262728293031# 绘画库from PIL import ImageDraw# 字体库from PIL import ImageFont# 图片库from PIL import Image# 图片添加水印def make_watermark(file_path, new_name, fonts, size, font_path): ''' :param file_path: the path of your image file, it must be string. :param new_name: The new name of your picture, it must be string. :param fonts: The font that you want to add on your picture,it must be string. :param size: the size of your watermark. :param font_path: The path of your font file. :return: if it could work normally, it will return True, if not,it will return False. ''' try: font = ImageFont.truetype(font_path, size) im = Image.open(file_path) print(im.format, im.size, im.mode) # 生成画笔 draw = ImageDraw.Draw(im) # 绘制 draw.text((0, 0), fonts, fill=(76, 234, 124, 180), font=font) draw = ImageDraw.Draw(im) im.save(new_name) except Exception: return False 注释是我自己写的,但是由于我的英语太渣了,怕大家看不懂(其实是防止自己看不懂)就来解释一下: 这里的file_path参数是图片路径,如”./image.png”,new_name是生成的图片的名字,fonts是要添加的水印内容,size是要添加的文字的大小,单位应该是像素,font_path是字体文件路径。 1draw.text((0, 0), fonts, fill=(76, 234, 124, 180), font=font) 这一行有必要解释一下,那个(0, 0)分别代表了水印的x, y轴,注意不要调的超过图片尺寸,不然水印就飞出图片了。。。fill=(, , , )这个相信大家都清楚,是RGBA。 关于添加中文水印,这里需要说明一下,那就是你的font_path, 即字体文件必须是要能够支持中文的,比如: 华文新魏.ttf, 华文彩云.ttf,千万不要用不支持中文的字体文件,不然return False给你看!切记切记。 那么本篇博客就到这里了, bye~。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"PIL","slug":"PIL","permalink":"http://example.com/tags/PIL/"},{"name":"水印","slug":"水印","permalink":"http://example.com/tags/%E6%B0%B4%E5%8D%B0/"}]},{"title":"使用钉钉、微博第三方登录","slug":"使用钉钉第三方登录","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/28/使用钉钉第三方登录/","link":"","permalink":"http://example.com/2020/04/28/%E4%BD%BF%E7%94%A8%E9%92%89%E9%92%89%E7%AC%AC%E4%B8%89%E6%96%B9%E7%99%BB%E5%BD%95/","excerpt":"钉钉的第三方登录,乍一看开发文档觉得没什么,然而越往下进行越觉得棘手,","text":"钉钉的第三方登录,乍一看开发文档觉得没什么,然而越往下进行越觉得棘手,到了最后我努力的三个小时终于能成功登录了,然而只成功登录了一次,我只是在代码下面加了几条判断,按理说python这种结构的下面的代码是不会影响到上面的代码的,然而再次运行的时候却不行了,告诉我签名校验失败,当时想死的心都有了。。。直到现在我也没弄明白为什么,明明代码第一次跑的好好的,第二次却不行了。但是现在有能够运行的代码了(本文使用的django项目): 12345678910111213141516171819202122232425262728import timeimport hmacimport base64from hashlib import sha256import urllibimport json#构造钉钉回调方法def ding_back(request): #获取code code = request.GET.get("code", None) t = time.time() #时间戳 timestamp = str((int(round(t * 1000)))) appSecret ='ly-AzMKMmCKQP3geaILT_An32kEfKO3HeOtApy5CgKwjytevVZC0WYsT2gxMB160' #构造签名 signature = base64.b64encode(hmac.new(appSecret.encode('utf-8'),timestamp.encode('utf-8'), digestmod=sha256).digest()) #请求接口,换取钉钉用户名 payload = {'tmp_auth_code':code} headers = {'Content-Type': 'application/json'} res = requests.post('https://oapi.dingtalk.com/sns/getuserinfo_bycode?signature='+urllib.parse.quote(signature.decode("utf-8"))+"&timestamp="+timestamp+"&accessKey=dingoaukgkwqknzjvamdqh",data=json.dumps(payload),headers=headers) res_dict = json.loads(res.text) # 到上面已经拿到数据了,下面的只是我拿来确信拿到数据的代码 print(res_dict) return HttpResponse(res.text) 这代码中构造签名是最令人难受的地方,钉钉那里只提供了PHP和Java的构造方法,却没有python的,虽然我们有万能的百度,但是百度到的代码可能在别人那里可以,在你这里就不行了,这不就整个人都裂开了嘛。但是本文中这个方法是可以使的,我试了试没有什么问题,可以成功的拿到数据,而不是提醒你: 时间戳有误、签名校验失败、签名不对之类的(我都要被折磨到崩溃了)。 接下来是微博的第三方登录,微博的这个我个人感觉还是很好搞的: 123456789101112131415161718def Weibo(request): code = request.GET.get('code', None) # url地址 url = "https://api.weibo.com/oauth2/access_token" # 定义参数 re = requests.post(url, data={ "client_id": 你的Key, "client_secret": "你的密钥", "grant_type": 'authorization_code', 'code': code, 'redirect_uri': 'http://127.0.0.1:8000/sina_login' }) # 获取新浪微博用户名称 res = requests.get('https://api.weibo.com/2/users/show.json', params={'access_token': re.json()['access_token'], 'uid': re.json()['uid']}) print(res.json()) # 成功 return HttpResponse(res.json()) 然后是带参重定向: 1return redirect("http://localhost:8080?username=%s&uid=%s" % (sina_id, user_id)) 前端接收参数: 12var username = this.$route.query.username;var uid = this.$route.query.uid; 这样就接收到了。 那么本篇博客就到此为止吧,我实在是累得不行了,但是怕明天一觉醒来再忘掉,所以还是先写下来。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"Django","slug":"Django","permalink":"http://example.com/tags/Django/"},{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"},{"name":"钉钉第三方登录","slug":"钉钉第三方登录","permalink":"http://example.com/tags/%E9%92%89%E9%92%89%E7%AC%AC%E4%B8%89%E6%96%B9%E7%99%BB%E5%BD%95/"},{"name":"微博第三方登录","slug":"微博第三方登录","permalink":"http://example.com/tags/%E5%BE%AE%E5%8D%9A%E7%AC%AC%E4%B8%89%E6%96%B9%E7%99%BB%E5%BD%95/"}]},{"title":"Selenium完成简单的滑块验证与下载验证码","slug":"selenium滑块","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2020/04/26/selenium滑块/","link":"","permalink":"http://example.com/2020/04/26/selenium%E6%BB%91%E5%9D%97/","excerpt":"完成简单的滑块验证这次内容是使用selenium完成简单的滑块验证,并非拼图哦~ 那么简单的滑块到底有多简单呢?它是长这个样子的~","text":"完成简单的滑块验证这次内容是使用selenium完成简单的滑块验证,并非拼图哦~ 那么简单的滑块到底有多简单呢?它是长这个样子的~ 啊这,,是不是感觉自己很久没见过这么easy的滑块了!那么对该滑块感到失望的同学们可以退出我的博客了…… 嗯,言归正传,我们是真的要讲这个滑块的,那么为什么不讲讲拼图的滑块呢? 那个太高级辣!我现在还不会搞。 那么开始了: 我使用的是selenium。 建立浏览器实例并请求目标网址(不要在意这个localhost): 12345# 建立浏览器实例browser = Chrome()# 调整窗口大小browser.set_window_size(1024, 720)browser.get(url='http://localhost:8080/login') 然后,定位滑块元素(就是那个圆圈): 1button = browser.find_element_by_class_name('dv_handler') 声明动作实例并完成滑动: 1234567# 声明动作实例action = ActionChains(browser)# 点击并且按住action.click_and_hold(button).perform()action.reset_actions()# 参数超过滑块的长度不会滑动,并且实际拖动像素和轨迹长度是有出入的action.move_by_offset(271,0).perform() 我感觉这个滑动的动作是比较容易被识别出来是机器的,但是对付这种简单的滑块应该还是可以的吧(小声)。 下载验证码图片直接定位到元素并下载即可 1browser.find_element_by_xpath('//table/tr[4]/td[2]/img').screenshot('1.png') 那么,完~","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"selenium","slug":"selenium","permalink":"http://example.com/tags/selenium/"},{"name":"滑块验证","slug":"滑块验证","permalink":"http://example.com/tags/%E6%BB%91%E5%9D%97%E9%AA%8C%E8%AF%81/"}]},{"title":"调用百度文字识别","slug":"useBaiduOCR","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2020/04/26/useBaiduOCR/","link":"","permalink":"http://example.com/2020/04/26/useBaiduOCR/","excerpt":"要使用百度的文字识别,首先你需要有一个百度账号,然后登入百度智能云,登录后在右上角选择”管理控制台“,页面跳转之后往下划,","text":"要使用百度的文字识别,首先你需要有一个百度账号,然后登入百度智能云,登录后在右上角选择”管理控制台“,页面跳转之后往下划,找到”文字识别“点进去,选择”创建应用“, 内容随便填,创建之后应该会跳转到一个页面,如果没有跳转的话就在”应用列表“找到要调用的应用并复制其API Key 和 Secret Key。 然后还请查看百度云通用文字识别(高精度版)帮助文档,记得参考Access Token获取。 代码: 1234567891011121314151617181920212223242526272829import requestsimport base64import urllib# 记得删掉大括号res = requests.get("https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=【你的API Key】&client_secret=【你的Secret Key】")token = res.json()['access_token']# 定义头部信息headers = { 'Content-Type': 'application/x-www-from/urlencoded'}url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic?access_token=' + token# 读取图片my_img = open('1.png', 'rb')tem_img = my_img.read()my_img.close()# 进行base64编码temp_data = {'image': base64.b64encode(tem_img)}# 对图片地址进行urlencode操作temp_data = urllib.parse.urlencode(temp_data)# 请求视图接口res = requests.post(url=url, data=temp_data, headers=headers)code = res.json()['words_result'][0]['words'].replace(' ', '')# 查看识别结果print(code)","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"百度文字识别","slug":"百度文字识别","permalink":"http://example.com/tags/%E7%99%BE%E5%BA%A6%E6%96%87%E5%AD%97%E8%AF%86%E5%88%AB/"}]},{"title":"Vue滑动验证","slug":"vue滑动验证码","date":"un00fin00","updated":"un22fin22","comments":true,"path":"2020/04/26/vue滑动验证码/","link":"","permalink":"http://example.com/2020/04/26/vue%E6%BB%91%E5%8A%A8%E9%AA%8C%E8%AF%81%E7%A0%81/","excerpt":"首先,你需要有一个叫做“vue-drag-verify”的东西,可以在vue/package.json文件中查看有没有该组件,如果有的话,那么接下来是它的使用:","text":"首先,你需要有一个叫做“vue-drag-verify”的东西,可以在vue/package.json文件中查看有没有该组件,如果有的话,那么接下来是它的使用: 引用组件: 1import dragVerify from "vue-drag-verify"; 然后注册组件: 123components:{'dragVerify': dragVerify} 设置参数 12345678data(){return {// 声明滑块验证相关数据width: 320, // 宽度height: 42, // 高度text: '请将滑块拖动到右边' // 滑块文本}} 调用: 12<drag-verify :width="width" :height="height" :text="text" ref="Verify"> </drag-verify> 显示效果如图: 可以查看其布尔值: 1console.log(this.$refs.Verify.isPassing); 完成~","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"},{"name":"滑块验证","slug":"滑块验证","permalink":"http://example.com/tags/%E6%BB%91%E5%9D%97%E9%AA%8C%E8%AF%81/"}]},{"title":"五次登录失败锁账号","slug":"fivecount","date":"un66fin66","updated":"un22fin22","comments":true,"path":"2020/04/25/fivecount/","link":"","permalink":"http://example.com/2020/04/25/fivecount/","excerpt":"这次用到了redis模块和Redis数据库。那么代码如下: 首先,连接上redis数据库","text":"这次用到了redis模块和Redis数据库。那么代码如下: 首先,连接上redis数据库 12port = 6379r = redis.Redis(host='localhost', port=port) 然后是代码主体: 123456789if not r.get(username): r.setex(username, 500, 1)else: time_lock = int(r.get(username).decode('utf-8')) if time_lock != 5: r.setex(username, 500, time_lock + 1) else: return Response({'code': 200, 'message': '您的登录过于频繁,请稍后再试!'}) 这里来说一下具体的逻辑: 首先是数据库的结构: 键(用户名), (失败次数), 计时。 如果登录的时候用户名或者密码出错,那么就向redis里插入一条以该用户名为名的可过期的键值对(因为我写的网站不允许有同名的用户名,所以将用户名设置为键也不怕锁错号),如果redis里面已经有以该用户名为键的键值对了,那么就把它读出来,将其值转换为整型,加一之后再存进数据库。如果读出来的时候值等于5了,那么就锁号。因为设置了过期时间,时间一到该键值对就会自动消失,也就是说时间一到该账号就又可以登录了。 不过这里还有个漏洞: 那就是登录成功5次依然会锁号··· 所以我们需要在用户登录的时候将redis库中的键值对删掉: 1r.delete('username') 这样就完成啦!总体来说,逻辑还是很清晰了,但是有的人使用的时候会有一些问题: 1r.setex(key, time_in_second, value) 这条语句并不能成功将键值对插入数据库,我也不知道为什么,似乎是redis版本低的原因导致了不能设置过期时间,但是解决方法已经被那人找到了,现在将方法写出来: 1r.expire(key, time_in_second) 使用这条语句搭配上set就可以实现了。另外补充一下: redis.setex设置的过期时间是以秒为单位的,所以设置的时候一定要将时间转换好。那么本次博客就到这里了!","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"redis","slug":"redis","permalink":"http://example.com/tags/redis/"}]},{"title":"Django+Vue验证码的生成与使用","slug":"django验证码","date":"un55fin55","updated":"un22fin22","comments":true,"path":"2020/04/24/django验证码/","link":"","permalink":"http://example.com/2020/04/24/django%E9%AA%8C%E8%AF%81%E7%A0%81/","excerpt":"生成验证码注意,代码中的r是实例化了Redis对象,r.set()是向redis中存入键值对的方法。","text":"生成验证码注意,代码中的r是实例化了Redis对象,r.set()是向redis中存入键值对的方法。 123456789101112131415161718192021222324252627282930313233343536373839404142434445import randomfrom PIL import Imagefrom PIL import ImageDrawfrom django.http import HttpResponseimport ioclass MyCode(View): def get_random_color(self): r = random.randrange(255) g = random.randrange(255) b = random.randrange(255) return (r, g, b) def get(self, request, index): # 画布 img_size = (120, 50) # 定义图片对象 image = Image.new('RGB', img_size, 'white') # 定义画笔 draw = ImageDraw.Draw(image, 'RGB') source = '01234567890abcdefghijklmn' # 接收容器 code_str = '' # 进入循环绘制 for i in range(4): # 获取字母颜色 text_color = self.get_random_color() # 获取随机下标 tmp_num = random.randrange(len(source)) # 随机字符串 random_str = source[tmp_num] # 装入容器 code_str += random_str # 绘制字符串 draw.text((10 + 30 * i, 20), random_str, text_color) # 获取缓存区 buf = io.BytesIO() # 将临时图片保存到缓冲 image.save(buf, 'png') r.set('code', code_str) return HttpResponse(buf.getvalue(), 'image/png') 这里get方法加了个index,是为了方便实现验证码的点击刷新。 前端展示验证码下面是urls.py的代码: 1path('code/<index>', MyCode.as_view(), name='code') 这里的index是为了从前端接收参数,接收了参数之后就会返回一个新的验证码(新的验证码与这里接收的参数无关,这里的参数只是起到了一个更改url的作用,下面vue里会讲到),虽然我觉得不应该这样写,但是这是我目前为止能找到的最好的、最简单的方法了,如果我能找到更完美的方法会更新。 (2020年5月25日14:27:48): 前几天突然发现,可以不用在urls.py后面指定接收的参数,直接写成: 1path('code/', MyCode.as_view(), name='code') 即可。 下面是Vue的代码: 这是template里的代码 12345<tr v-if="for_fresh > 0"> <td>验证码: <input type="text" v-model="img_code"></td> <td><img :src="img_src" alt="验证码加载失败" @click="refresh1()"></td></tr> 这是script标签里的代码 1234567891011data(){return {img_src: 'http://127.0.0.1:8000/code/1'},methods:{refresh1:function(){ var num=Math.ceil(Math.random()*10);//生成一个随机数(防止缓存) this.img_src = "http://127.0.0.1:8000/code?num=" + num; }}} 这里我定义了一个refresh1函数,它的作用就是生成一个参数随机的URL,然后导致img标签的src出现变化,img标签就会重新加载。那么也许你会想为什么不点击一下就向后台发送一条请求呢?这样也不用传参了,岂不是更快捷? 那么很遗憾的告诉你,img并不会随着后台图片发生变化而重新加载。要问我为什么。。。。这都是血的教训。所以目前来看还是建议通过改变img标签的src属性来实现验证码的加载。不过这里的代码是有一点问题的: 那就是后台随机生成的验证码有可能会连着几次随机到同一条字符串,也就是说会生成两张一样的图片或者颜色不一样字符串却一样的图片,这里我还没有想解决这个问题,因为把获得两个同样的验证码看成是惊喜也是可以的吧?虽说会导致刷新验证码的原因一般就是用户看不清验证码。。。。 如果想要实现提交的信息不对就刷新验证码,那就在判断提交后的返回数据的时候加上this.refresh1()吧! 本篇完结(真是氵了不少内容呢) (2020年7月25号2:34 pm更新)今天发现之前的验证码做的有点愚蠢,因为只对一个键进行操作,导致如果有两个或多个用户同时注册或者刷新验证码,那么必然只有一个用户可以输入正确的验证码,而且这还是在其他用户不点击验证码刷新的情况下。这件事必定会导致严重的后果。于是今天想了一个方法,虽然我个人感觉也是有点蠢,但是目前只能想到这么个点子了。那么下面来说我的这个新办法的逻辑。 这个逻辑其实很简单,就是用户请求注册页面的时候向后台发送一条消息,后台生成一条唯一标识返回给用户,用户存下唯一标识,在请求验证码的时候带上唯一标识,后台将唯一标识与验证码的正确值作为键值对存入redis并设置过期时间。当用户提交注册信息时,携带上唯一标识一并提交给后端。后端从redis中提取出键名为该唯一标识的键值对进行比较,如果错误或键过期就返回给用户对应的信息,如果正确就继续向下执行。 这个逻辑很简单,丝毫看不出优雅,这是让我感到无法接受的一点。但是秉承着”能用就行”的想法,我还是暂时使用了这个方法,当然,如果有邮箱注册或手机号注册的话,基本就用不着这样的验证码了,如果非要多加一条验证码,我认为滑动验证码是更好的选择。 那么下面还是写一下实现。后台的代码就不放出来了。下面是vue的代码:html中: 1<img :src="img_src" alt="验证码加载失败" width="30%" @click="refresh_code"> method方法: 1234567891011121314151617181920// 刷新验证码refresh_code(){ var num=Math.ceil(Math.random()*10);//生成一个随机数(防止缓存) this.img_src = "http://127.0.0.1:8000/code?uuid=" + localStorage.getItem('rgt_uuid') + '&num=' + num; },// 获取uuid,用于图形验证码注册。该uuid唯一。 get_uuid(){ axios({ url: 'http://127.0.0.1:8000/register/', method: 'get', }).then(res=>{ if(res.data.code === 200){ localStorage.setItem('rgt_uuid', res.data.uuid); this.img_src = 'http://127.0.0.1:8000/code?uuid=' + localStorage.getItem('rgt_uuid') return 0; } layer.msg('获取信息失败了') // 这里我是使用的是layui,作用相当于alert }) } 直接拼接图片链接的url,将uuid携带进url中,并且设置一个随机数(num)以实现验证码的刷新。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"Django","slug":"Django","permalink":"http://example.com/tags/Django/"},{"name":"PIL","slug":"PIL","permalink":"http://example.com/tags/PIL/"},{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"},{"name":"生成图片验证码","slug":"生成图片验证码","permalink":"http://example.com/tags/%E7%94%9F%E6%88%90%E5%9B%BE%E7%89%87%E9%AA%8C%E8%AF%81%E7%A0%81/"}]},{"title":"Django入门","slug":"createDjango","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2020/04/23/createDjango/","link":"","permalink":"http://example.com/2020/04/23/createDjango/","excerpt":"创建Django项目:cd 到你想要创建Django项目的目录下,然后运行以下命令: 1django-admin startproject mysite 这行代码会在当前目录下创建一个mysite目录。(注意事项:","text":"创建Django项目:cd 到你想要创建Django项目的目录下,然后运行以下命令: 1django-admin startproject mysite 这行代码会在当前目录下创建一个mysite目录。(注意事项: 要避免项目名与python或django的关键字冲突) 以下为startproject创建的内容: 12345678mysite/ manage.py mysite/ __init__.py settings.py urls.py asgi.py wsgi.py 这些目录和文件的用处是(以下内容摘抄自官方文档): 最外层的 mysite/ 根目录只是你项目的容器, 根目录名称对Django没有影响,你可以将它重命名为任何你喜欢的名称。 manage.py: 一个让你用各种方式管理 Django 项目的命令行工具。你可以阅读 django-admin and manage.py 获取所有 manage.py 的细节。 里面一层的 mysite/ 目录包含你的项目,它是一个纯 Python 包。它的名字就是当你引用它内部任何东西时需要用到的 Python 包名。 (比如 mysite.urls). mysite/__init__.py:一个空文件,告诉 Python 这个目录应该被认为是一个 Python 包。如果你是 Python 初学者,阅读官方文档中的 更多关于包的知识。 mysite/settings.py:Django 项目的配置文件。如果你想知道这个文件是如何工作的,请查看 Django settings 了解细节。 mysite/urls.py:Django 项目的 URL 声明,就像你网站的“目录”。阅读 URL dispatcher 文档来获取更多关于 URL 的内容。 mysite/asgi.py:作为你的项目的运行在 ASGI 兼容的Web服务器上的入口。阅读 如何使用 WSGI 进行部署 了解更多细节。 mysite/wsgi.py:作为你的项目的运行在 WSGI 兼容的Web服务器上的入口。阅读 如何使用 WSGI 进行部署 了解更多细节。 运行项目切换到项目的目录下 1cd mysite 运行项目 1python manage.py runserver 应该会看到类似以下的输出: 12345678Performing system checks...System check identified no issues (0 silenced).April 23, 2020 - 11:35:20Django version 2.0.4, using settings 'mysite.settings'Starting development server at http://127.0.0.1:8000/Quit the server with CTRL-BREAK.","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"Django","slug":"Django","permalink":"http://example.com/tags/Django/"}]},{"title":"Python md5加密","slug":"pythonmd5","date":"un44fin44","updated":"un22fin22","comments":true,"path":"2020/04/23/pythonmd5/","link":"","permalink":"http://example.com/2020/04/23/pythonmd5/","excerpt":"使用python来进行md5加密,下面是代码:","text":"使用python来进行md5加密,下面是代码: 123456789101112131415#md5加密方法def make_password(mypass): #生成md5对象 md5 = hashlib.md5() #转码操作 mypass_utf8 = str(mypass).encode(encoding="utf-8") #加密操作 md5.update(mypass_utf8) #返回密文 return md5.hexdigest()","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"git push不上解决方案","slug":"gitpush不上解决方案","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2020/04/22/gitpush不上解决方案/","link":"","permalink":"http://example.com/2020/04/22/gitpush%E4%B8%8D%E4%B8%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/","excerpt":"","text":"此操作似乎遗有副作用,慎用! 由于在git push的时候常报错(之前使用git pull把代码拉下来也不行), 于是百度了一下,终于得到一段秘籍,作用是强行push上去~: 1git push -f origin master 这样就可以push上去了!!!好开心!","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"git","slug":"blog/git","permalink":"http://example.com/categories/blog/git/"}],"tags":[{"name":"git","slug":"git","permalink":"http://example.com/tags/git/"}]},{"title":"Vue各个组件的作用","slug":"vue各个组件的作用","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2020/04/22/vue各个组件的作用/","link":"","permalink":"http://example.com/2020/04/22/vue%E5%90%84%E4%B8%AA%E7%BB%84%E4%BB%B6%E7%9A%84%E4%BD%9C%E7%94%A8/","excerpt":"","text":"123456789101112131415161718192021222324252627282930313233343536373839401├── index.html 入口页面 2 ├── build 构建脚本目录 3 │ ├── build-server.js 运行本地构建服务器,可以访问构建后的页面 4 │ ├── build.js 生产环境构建脚本 5 │ ├── dev-client.js 开发服务器热重载脚本,主要用来实现开发阶段的页面自动刷新 6 │ ├── dev-server.js 运行本地开发服务器 7 │ ├── utils.js 构建相关工具方法 8 │ ├── webpack.base.conf.js wabpack基础配置 9 │ ├── webpack.dev.conf.js wabpack开发环境配置10 │ └── webpack.prod.conf.js wabpack生产环境配置11 ├── config 项目配置12 │ ├── dev.env.js 开发环境变量13 │ ├── index.js 项目配置文件14 │ ├── prod.env.js 生产环境变量15 │ └── test.env.js 测试环境变量16 ├── mock mock数据目录17 │ └── hello.js18 ├── package.json npm包配置文件,里面定义了项目的npm脚本,依赖包等信息19 ├── src 项目源码目录 20 │ ├── main.js 入口js文件21 │ ├── app.vue 根组件22 │ ├── components 公共组件目录23 │ │ └── title.vue24 │ ├── assets 资源目录,这里的资源会被wabpack构建25 │ │ └── images26 │ │ └── logo.png27 │ ├── routes 前端路由28 │ │ └── index.js29 │ ├── store 应用级数据(state)30 │ │ └── index.js31 │ └── views 页面目录32 │ ├── hello.vue33 │ └── notfound.vue34 ├── static 纯静态资源,不会被wabpack构建。35 └── test 测试文件目录(unit&e2e)36 └── unit 单元测试37 ├── index.js 入口脚本38 ├── karma.conf.js karma配置文件39 └── specs 单测case目录40 └── Hello.spec.js 需要重点注意下面的内容: index.html文件入口; src放置组件和入口文件; node_modules为依赖的模块; config中配置了路径端口值等; build中配置了webpack的基本配置、开发环境配置、生产环境配置等。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"}]},{"title":"一些vue知识","slug":"vue知识","date":"un33fin33","updated":"un22fin22","comments":true,"path":"2020/04/22/vue知识/","link":"","permalink":"http://example.com/2020/04/22/vue%E7%9F%A5%E8%AF%86/","excerpt":"复习了一遍vue,为了防止忘记特意将其记录下来。","text":"复习了一遍vue,为了防止忘记特意将其记录下来。 本篇笔记含有: vue的一些命令,如v-if 等;vue 标签下的一些组件的语法。 vue命令:v-if: 123456789<div v-if="a==='a'"> a</div><div v-else-if="a==='b'"> b</div><div v-else> c</div> v-for: 1234567891011121314<div v-for="(i, index) in list1"> <!-- 如果 list1 为数组套字典,那么要调用对应的键的值时应该写成{{i.key}}, index为索引,可以不写。--> {{i}}</div><script>export default{ data(){ return { // 也可在列表中套字典,如 list1 : [{'name':'fire', 'price': 1}, {'name': 'water', 'price': 2}] list1: [1, 2, 3] } }}</script> v-model:数据双向绑定。 v-show: 使用方法与v-if相同,不同的是无论v-show的值是false或true,元素都会存在于html代码中。 v-bind: 用于给DOM绑定元素属性。 v-on: 用于监听DOM事件,语法与v-bind类似,如监听点击事件 1@click="" 语法糖:例如 v-bind: 可以用:来表示,这就叫做语法糖,同时v-on可以用@来表示。 vue文件内script标签下各种组件及作用:12345678910111213141516171819202122232425262728293031<script>export default{ // 定义数据 data(){ return { msg: '这是定义数据的语法' } }, // 定义函数 method:{ test:function(){ var name="这就是定义一个函数的语法"; } }, // 计算属性 computed:{ }, // 钩子函数 beforeCreate(){ }, // 监听属性。方法要和变量同名,不然监听不到它的变化 watch:{ // 如一个变量为conter, nval为新值(变化后的值), oval为老值(变化前的值) conter:function(nval, oval){ console.log('计数器由' + oval + '变换为新的' + nval); } },}</script>","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"}]},{"title":"使用git删除仓库里的文件夹","slug":"使用git删除仓库里的文件夹","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/使用git删除仓库里的文件夹/","link":"","permalink":"http://example.com/2020/04/21/%E4%BD%BF%E7%94%A8git%E5%88%A0%E9%99%A4%E4%BB%93%E5%BA%93%E9%87%8C%E7%9A%84%E6%96%87%E4%BB%B6%E5%A4%B9/","excerpt":"首先,初始化: 1git init","text":"首先,初始化: 1git init 然后,将项目克隆到本地: 1git clone 项目url 然后,输入以下命令: 1234git pull origin mastergit rm -r --cached 你要删除的文件夹git commit -m "删除了文件夹"git push -u origin master 尝试过不克隆项目而输入其他命令,结果在git rm -r –cached的时候报错了。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"git","slug":"blog/git","permalink":"http://example.com/categories/blog/git/"}],"tags":[{"name":"git","slug":"git","permalink":"http://example.com/tags/git/"}]},{"title":"几条Hugo命令","slug":"hugo命令","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/hugo命令/","link":"","permalink":"http://example.com/2020/04/21/hugo%E5%91%BD%E4%BB%A4/","excerpt":"打包项目(在根目录下) 1hugo","text":"打包项目(在根目录下) 1hugo 新建md文件 1hugo new filename.md 启动hugo服务 1hugo server 生成站点 1hugo new site blog","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"hugo","slug":"blog/hugo","permalink":"http://example.com/categories/blog/hugo/"}],"tags":[{"name":"hugo","slug":"hugo","permalink":"http://example.com/tags/hugo/"}]},{"title":"git push时卡住解决方法","slug":"gitpush时卡住解决方法","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/gitpush时卡住解决方法/","link":"","permalink":"http://example.com/2020/04/21/gitpush%E6%97%B6%E5%8D%A1%E4%BD%8F%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/","excerpt":"","text":"今天push的时候发现等了好几分钟也没动,上网查了查解决了。 只需使用以下命令即可: 1git reset","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"git","slug":"blog/git","permalink":"http://example.com/categories/blog/git/"}],"tags":[{"name":"git","slug":"git","permalink":"http://example.com/tags/git/"}]},{"title":"清空GitHub仓库下的所有内容","slug":"清空GitHub仓库下的所有内容","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/清空GitHub仓库下的所有内容/","link":"","permalink":"http://example.com/2020/04/21/%E6%B8%85%E7%A9%BAGitHub%E4%BB%93%E5%BA%93%E4%B8%8B%E7%9A%84%E6%89%80%E6%9C%89%E5%86%85%E5%AE%B9/","excerpt":"","text":"首先把本地仓库所有东西移走,只剩下.git和reademe文件。 然后在该目录下使用Git Bash 执行命令: 123git add *git commit -m "清空"git push origin master","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"git","slug":"blog/git","permalink":"http://example.com/categories/blog/git/"}],"tags":[{"name":"git","slug":"git","permalink":"http://example.com/tags/git/"}]},{"title":"GitHub使用hugo","slug":"GitHub使用hugo","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/GitHub使用hugo/","link":"","permalink":"http://example.com/2020/04/21/GitHub%E4%BD%BF%E7%94%A8hugo/","excerpt":"1.先创建一个仓库 2.在新建的仓库的settings页面下GitHUb Pages里选择choose theme,随意选择一个主题。","text":"1.先创建一个仓库 2.在新建的仓库的settings页面下GitHUb Pages里选择choose theme,随意选择一个主题。 3.选择完毕后将public文件夹里的文件上传到仓库 成功使用hugo~ 需要注意的是,如果你想用你的域名来访问这个博客,那么在新建仓库的时候要把命名为 你的github名字.github.io 。 然后在hugo的config.toml文件中将baseURL的值也改成 你的github名字.github.io 。 如果不更改baseURL,在用你的域名访问时会出现无法加载css的情况。 域名解析使用CNAME类型,记录值填 你的github名字.github.io 。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"hugo","slug":"blog/hugo","permalink":"http://example.com/categories/blog/hugo/"}],"tags":[{"name":"hugo","slug":"hugo","permalink":"http://example.com/tags/hugo/"}]},{"title":"Anaconda创建虚拟环境","slug":"Anaconda创建虚拟环境","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Anaconda创建虚拟环境/","link":"","permalink":"http://example.com/2020/04/21/Anaconda%E5%88%9B%E5%BB%BA%E8%99%9A%E6%8B%9F%E7%8E%AF%E5%A2%83/","excerpt":"使用Anaconda创建虚拟环境: 1conda create -n your_env_name python=X.X(2.7、3.6等) 查看当前安装了哪些虚拟环境: 1conda env list 查看安装了哪些包: 1conda list 使用以下命令激活虚拟环境:","text":"使用Anaconda创建虚拟环境: 1conda create -n your_env_name python=X.X(2.7、3.6等) 查看当前安装了哪些虚拟环境: 1conda env list 查看安装了哪些包: 1conda list 使用以下命令激活虚拟环境: 12Linux: source activate your_env_name(虚拟环境名称) Windows: activate your_env_name(虚拟环境名称) 关闭虚拟环境: 123Linux: source deactivateWindows: deactivate 给环境安装额外的包: 1conda install -n your_env_name [package] 也可以先激活虚拟环境,然后使用pip安装: 1pip install package 删除虚拟环境: 1conda remove -n your_env_name --all 删除环境中的某个包: 1conda remove --name your_env_name package_name 如果使用pycharm想要使用你的虚拟环境,那么依次点击: File –> Settings –> Project: your_project_name –> Project Interpreter , 在Project Interpreter里点Project Interpreter右边的齿轮,选择add, 选择 Conda Environment, 选择Existing environment, 点击Interpreter右边的 … ,找到你的Anaconda路径下的env,在env文件夹中扎到你的虚拟环境名,选择文件夹下的python.exe。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Anaconda","slug":"blog/Anaconda","permalink":"http://example.com/categories/blog/Anaconda/"}],"tags":[{"name":"Anaconda","slug":"Anaconda","permalink":"http://example.com/tags/Anaconda/"}]},{"title":"Arch Linux xfce4主题美化","slug":"Arch Linux主题美化","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Arch Linux主题美化/","link":"","permalink":"http://example.com/2020/04/21/Arch%20Linux%E4%B8%BB%E9%A2%98%E7%BE%8E%E5%8C%96/","excerpt":"","text":"首先,先去下载一个主题,访问xfce4-look.org,下载你喜欢的xfce主题。 下载下来的应该是一个.tar.xz的压缩包(如果是其他格式也没事), 使用以下命令解xz压缩: 1xz -d yourfile.tar.xz 将压缩包文件拷贝到/usr/share/themes文件夹(这里由于前面已经解xz压缩了,所以是.tar),注意,这里可能需要用户权限,若是cp失败,则先获取root权限。: 1sudo cp yourfile.tar /usr/share/themes 然后解压缩: 1tar -xvf yourfile.tar 然后删掉.tar文件: 1rm yourfile.tar 然后点击左上角的一个好像叫Application的选项,选择settings里面的Appearance,选中你刚才拷贝到/usr/share/themes的主题。 然后如果你的主题如果对标题栏有更改的话,还需要打开settings里面的Window Manager,这里面可以选择标题栏主题。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"arch安装audacious音乐播放器","slug":"archInstallMusicPlayer","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/archInstallMusicPlayer/","link":"","permalink":"http://example.com/2020/04/21/archInstallMusicPlayer/","excerpt":"","text":"安装音乐播放器: 1pacman -S audacious 这样就安装好了,右击桌面(我使用的xfce),然后选择Create Lanucher,在第一行输入:audacious,会出现一个提示,点击提示就会自动补充下面几行。然后选择Create就创建好桌面打开方式了。然后可以安装一个歌词插件: 1pacman -S osdlyrics 安装完成之后可以在桌面创建快捷方式,创建方式同audacious。打开osdlyrics可以在屏幕右上角看到它的图标,右键会弹出选单。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"Arch安装使用Redis","slug":"archlinux安装redis","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/archlinux安装redis/","link":"","permalink":"http://example.com/2020/04/21/archlinux%E5%AE%89%E8%A3%85redis/","excerpt":"","text":"首先,使用pacman安装redis: 1sudo pacman -S redis 然后,输入: 1systemctl enable redis.service 说实话我不知道这句话有什么用,从作用上来看应该是开机启动redis服务,但是就算这么设置了,redis服务也得自己手动开启。。。在终端输入: 1redis-server 开启redis server,然后输入redis-cli就可以使用redis啦!没错,就是这么简单!!","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"ArchLinux使用sudo","slug":"ArchLinux设置sudo","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/ArchLinux设置sudo/","link":"","permalink":"http://example.com/2020/04/21/ArchLinux%E8%AE%BE%E7%BD%AEsudo/","excerpt":"","text":"Arch Linux刚安装好的时候是没有sudo命令的,需要自行安装,首先输入su命令以获取root权限,然后安装sudo: 1pacman -S sudo 想要查看具体的操作可以查看官方Wiki: Sudo。这里我只写下给用户添加sudo使用权限。安装了sudo之后,终端输入: 1EDITOR=vim visudo 这句话的意思是使用vim编辑sudo文件。这么做的原因是因为我输入visudo的时候提示我没有编译器。。。这句命令似乎只是暂时的设置EDITOR,想要永久设置就在上面发的Arch Wiki链接里仔细找找,在”配置”标题下。输入完这条命令就会自动使用vim打开/etc/sudoers了,然后在有一行没有注释的地方(root下面)输入(yourusername是你的用户名的意思,不要搞错了真输上去个yourusername): 1yourusername ALL=(ALL) ALL 然后su yourusername,再使用sudo执行一条命令,比如执行pacman -S wechat,如果提示你没有这个包,那就成功啦!那么本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"Arch Linux同步时间","slug":"ArchKeepTime","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/ArchKeepTime/","link":"","permalink":"http://example.com/2020/04/21/ArchKeepTime/","excerpt":"","text":"由于我是Arch Linux + Windows双系统,所以在从arch切换到windows的时候或者从windows切换到arch的时候会造成时间不同步。有的时候明明是白天,时间面板上却显示22:00,虽然我也不太在意但是想看时间就要拿起手机确实不方便。于是我参照了Arch Wiki设置了一个开机自动同步时间。 首先安装ntp软件包。 1pacman -S ntp 然后更改配置文件 /etc/ntp.conf,Arch Wiki上说这里应参照pool.ntp.org来设置对应的时间,这里我设置的是: 1234server 0.cn.pool.ntp.org iburstserver 1.cn.pool.ntp.org iburstserver 2.cn.pool.ntp.org iburstserver 3.asia.pool.ntp.org iburst 推荐使用iburst选项,如果第一次尝试无法建立连接,程序会发送一系列的包。burst 选项则总是发送一系列的包,即使第一次也是这样。如果没有明确的允许的话不要使用 burst 选项,有可能被封禁。 按照官方wiki,在终端中启用应该使用: 1# ntpd -u ntp:ntp 这里我没看懂(可能是我太垃圾了吧),我就输入了一下。然而输入后时间并没有变化。接下来是设置启动时启用ntpd(此时我运行上面的命令没有成功后其实已经不报太大希望了,但是我依然想要试一下),在终端中输入: 1systemctl enable ntpd.service 其实这里我总感觉我使用的方法与Arch Wiki的有误,但是我实在是太菜了,根本不明白Arch Wiki在说什么(虽然人家应该已经说的很简单易懂了)。然后我就关闭了电脑。重启电脑的时候因为我的学习资料都在某粉红色网站,我就开启了Windows,结果突然发现自己的时间正确了!然后我又打开Arch Linux,发现时间也正确了!! (最新更新: 我又一次打开WIndows的时候发现时间正确纯属意外。。。。) 因此才写下此篇博客。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"archlinux下python3安装tkinter","slug":"arch_tkinter","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/arch_tkinter/","link":"","permalink":"http://example.com/2020/04/21/arch_tkinter/","excerpt":"","text":"借鉴自ArchLinux Python3 安装 tkinter。 输入: 1sudo pacman -S tk 即可安装tkinter。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"Arch 安装pip及pip3","slug":"Arch安装pip","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Arch安装pip/","link":"","permalink":"http://example.com/2020/04/21/Arch%E5%AE%89%E8%A3%85pip/","excerpt":"","text":"Arch安装pip以及pip3十分的简单,只需要一条指令: 1pacman -S python-pip 说实话执行这条指令的时候我以为是安装的pip的,但是执行完之后我又执行了一遍pacman -S python-pip3,但是提示我未找到该Package,于是我就尝试着输入了一遍pip3,没想到竟然安装上了。。。。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"ArchLinux安装mongoDB","slug":"arch安装mongoDB","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/arch安装mongoDB/","link":"","permalink":"http://example.com/2020/04/21/arch%E5%AE%89%E8%A3%85mongoDB/","excerpt":"","text":"安装的过程有点麻烦(没法直接pacman了),因为官方软件仓库里没有这个包了(详情还请看Arch Wiki),所以这里我们得使用AUR了。访问ArchLinux安装MongoDB,在”安装”标题下选择你想要安装的软件包,这里我选择的是mongodb-bin软件包,使用的git下载,速度还挺快的。那么当软件包clone到本地后,你可以看到文件夹下有一个PKGBUILD文件,打开终端,输入:```shellmakepkg 123就会自动编译软件包,等待编译完成,会生成一个带有.pkg.tar.xz后缀的文件,然后继续输入:```shellmakepkg --install 安装完后输入mongo试一下。那么MongoDB的安装就已经结束了,如果输入mongo之后打印出一大堆乱七八糟的东西,仔细一看里面还带着”Error”,那么就访问Arch Wiki吧,还是上面那个链接,里面有详细的解决方法,我就是按照那个方法解决成功的。那么,本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"Arch下bash安装powerline","slug":"arch安装powerline","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/arch安装powerline/","link":"","permalink":"http://example.com/2020/04/21/arch%E5%AE%89%E8%A3%85powerline/","excerpt":"本来Arch Wiki上是有教程的,没有写这个的必要,但是那个教程是英文的,为了避免以后再翻译一遍(悲),我就先写个博客吧。为了保险起见,这里贴上Arch Wiki Powerline的链接。 那么下面是安装,首先,输入: 1pacman -S powerline 安装powerline,然后输入:","text":"本来Arch Wiki上是有教程的,没有写这个的必要,但是那个教程是英文的,为了避免以后再翻译一遍(悲),我就先写个博客吧。为了保险起见,这里贴上Arch Wiki Powerline的链接。 那么下面是安装,首先,输入: 1pacman -S powerline 安装powerline,然后输入: 1pacman -S powerline-fonts 安装powerline字体。然后编辑~下的.bashrc文件,将以下内容添加进.bashrc文件: 1234powerline-daemon -qPOWERLINE_BASH_CONTINUATION=1POWERLINE_BASH_SELECT=1. /usr/share/powerline/bindings/bash/powerline.sh 重启你的终端或者输入以下命令应用修改: 1source ~/.bashrc","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"Arch安装VNCViewer","slug":"Arch安装vncviewer","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Arch安装vncviewer/","link":"","permalink":"http://example.com/2020/04/21/Arch%E5%AE%89%E8%A3%85vncviewer/","excerpt":"","text":"首先去VNC的官网下载文件VNC Viewer,这里我选择的是Standalone,下载之后会获得一个名字类似“VNC-Viewer-6.20.529-Linux-x86”的文件,这时我们需要使用: 1chmod +x VNC* 来给一个执行的权限,不然即使用root也无法跑起来程序。然后就可以通过:./VNC-Viewer*来执行程序了。就是这么简单。那么本篇完结。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"使用VNCServer","slug":"arch使用vncserver","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/arch使用vncserver/","link":"","permalink":"http://example.com/2020/04/21/arch%E4%BD%BF%E7%94%A8vncserver/","excerpt":"","text":"本次使用的是REALVNC SERVER,目的在于实现局域网内开启一个只能被观看屏幕不能被控制的服务端。 下载VNCServer访问RealVNC官网:VNC Server,选择Linux,在下面的选项里选择Gerneric script x86或x64,自己决定。下载完成之后解压缩: 1tar -zxvf VNC*.tar.gz 安装VNCServercd进解压后的文件夹,终端输入: 1sudo ./vncinstall 静等一段时间,VNCServer就安装好了。在桌面新建启动器,输入VNC,然后选择自动补全的VNC Server。 登录用户打开VNC Server,需要root权限。然后你需要去整个序列号或者帐号,不然无法使用。如果你没有登录或者没有填写序列号,你会看到一个红色的X。 设置viewonly用户点击右上角的三条横杠,选择options,这时需要root权限。然后点击User&Permissions,找到add,添加一个View only用户,设置密码。之后别人就可以通过输入用户名为viewonly的用户和你设置的密码来访问了。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"Arch安装我的世界hmcl","slug":"Arch安装我的世界","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Arch安装我的世界/","link":"","permalink":"http://example.com/2020/04/21/Arch%E5%AE%89%E8%A3%85%E6%88%91%E7%9A%84%E4%B8%96%E7%95%8C/","excerpt":"","text":"直接yaourt -S hmcl安装包,安装好后输入hmcl即可。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"arch使用deb包","slug":"Arch下使用deb包","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Arch下使用deb包/","link":"","permalink":"http://example.com/2020/04/21/Arch%E4%B8%8B%E4%BD%BF%E7%94%A8deb%E5%8C%85/","excerpt":"","text":"先安装debtap包,然后执行: 1sudo debtap -u 如果你的网不好,执行这条命令会很费劲,而且有很大几率会失败。不过多尝试几次(博主失败了四五次才装上)似乎就会成功。安装好之后在含有.deb文件的目录下输入: 1debtap package 来转换包,转换的时候会有选项,记得选。转换好之后输入: 1sudo pacman -U package 之后就安装好啦。那么如何运行文件呢?你在执行debtap转化包的时候输入的包名就是安装好的包的运行命令。比如我在转换的时候包名输入的mindmaster,那么在使用pacman安装之后执行mindmaster来运行安装好的包。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"使用蓝牙耳机录音","slug":"arch蓝牙录音","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/arch蓝牙录音/","link":"","permalink":"http://example.com/2020/04/21/arch%E8%93%9D%E7%89%99%E5%BD%95%E9%9F%B3/","excerpt":"","text":"使用蓝牙耳机播放音乐请看我之前的博客,标题是:Arch连接蓝牙。这里就不放出链接了。我按照之前使用蓝牙耳机播放音乐的博客配置完成后,下载了个OBS用来录屏,结果发现录音噪音过大,整了半天终于发现了解决方法。首先安装blueberry模块: 1pacman -S blueberry 安装完成后在命令行输入: 1blueberry 以启动blueberry。blueberry其实是个蓝牙管理器,但是这里我们可以利用它来配置我们的蓝牙耳机。先连上蓝牙耳机(我使用的bluetoothctl连的蓝牙,因为不知道为什么,我的blueberry连接不了蓝牙),然后在blueberry中点击你连接上的蓝牙设备->点击声音设置->点击配置->找到你的耳机,把侧写栏的”高保真回放(A2DP 信宿)”切换成”头戴耳机单元”,就可以无噪音的录音了。录完音了记得切换回来,因为使用头戴耳机单元 播放的声音听着会变远。。。就离谱。。。。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"ArchLinux安装锁屏","slug":"arch安装锁屏","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/arch安装锁屏/","link":"","permalink":"http://example.com/2020/04/21/arch%E5%AE%89%E8%A3%85%E9%94%81%E5%B1%8F/","excerpt":"","text":"突然想起来自己的Arch还没有锁屏,于是百度一番加自己实际操作一番后写出了一篇安装xscreensaver锁屏的博客。那么首先安装包: 1sudo pacman -S xscreensaver-arch-logo 这个包可以获得有Arch Linux标志的外观,或者你也可以直接装xscreensaver包,不过我觉得装xscreensaver-arch-logo更好玩。安装好之后可以通过输入xscreensaver-demo来启动程序。勾选上”Lock Screen After”选项应该可以自动锁屏,不过这个我没试过,我一直都是手动锁屏:按下Ctrl+Alt+Delete即可锁屏。那么本篇博客就到这里。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"替换关键词","slug":"change_keyword","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/change_keyword/","link":"","permalink":"http://example.com/2020/04/21/change_keyword/","excerpt":"今天闲着无聊(其实就是不想写web),突然想到之前写博客时分类大小写搞错了找文件的艰辛,就决心写一个能够在当前目录下改变指定文件类型中关键字的脚本。那么先说一下思路吧。 先获取当前目录下的所有文件名称,然后根据文件名来判断是否更改,并且在更改前将将要被更改的文件存进一个文件夹(其实就是备份啦),之后替换关键词即可。 那么下面是代码:","text":"今天闲着无聊(其实就是不想写web),突然想到之前写博客时分类大小写搞错了找文件的艰辛,就决心写一个能够在当前目录下改变指定文件类型中关键字的脚本。那么先说一下思路吧。 先获取当前目录下的所有文件名称,然后根据文件名来判断是否更改,并且在更改前将将要被更改的文件存进一个文件夹(其实就是备份啦),之后替换关键词即可。 那么下面是代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124import osimport arrowclass FileChange(object): """ file_list will return a list, and "change_keyword.py" won't in it. change_file will change data.You should have a good think before use it. do_it will use file_list and change_file to change every file in this directory. """ def __init__(self, old_word, new_word, max_range): """ :param old_word: Old word. :param new_word: New word. :param max_range: the number that you want to instead in every file. """ self.old_word = old_word self.new_word = new_word self.path = os.path.abspath('') + '\\\\' self.max_range = int(max_range) self.success_list = [] def file_list(self): """ This class function will return a list. :return: It will return a list without "change_keyword.py". """ fns = os.listdir() if os.path.basename(__file__) in fns: fns.remove(os.path.basename(__file__)) return fns def back_up(self, filedata, filename): """ To back-up the success file. :param filedata: The data of your file. :param filename: The name of your file. :return: Success status. """ try: os.mkdir(self.path + 'back-up') except FileExistsError: pass with open('./back-up/' + filename, 'w') as f: f.write(filedata) return filename + 'was back up in directory back-up' def change_file(self, filename): """ :param filename: The file which will be changed. :return: Return the result, not matter it was success or defeat. """ if filename.endswith('.txt') or filename.endswith('.md'): with open(self.path + filename, 'r') as f: file_msg = f.read() if self.old_word in file_msg: print(self.back_up(filedata=file_msg, filename=filename)) if self.max_range: file_msg = file_msg.replace(self.old_word, self.new_word, self.max_range) else: file_msg = file_msg.replace(self.old_word, self.new_word) with open(self.path + filename, 'w') as f: f.write(file_msg) self.success_list.append(filename) return 'Success!The file name is :' + filename else: return 'Here is no keyword.' else: return 'This is not the right type.' def make_success_file(self): """ To make a success list in ./backup/success.txt . :return: 0 """ with open('./back-up/success_file_list.txt', 'a') as f: f.write(arrow.now().format('YYYY-MM-DD HH:mm') + '\\n') f.write(str(self.success_list)) f.write('\\n\\n') return 'Made a success list file in ./back-up/success_file_list.txt' def do_it(self): """ In order to change every file in this directory. """ for file in self.file_list(): print(self.change_file(file)) if len(self.success_list): print('This is success list: ', self.success_list) print(self.make_success_file()) else: print('Nothing changed.') return 0def main(): old_word = input('Please enter your old word') new_word = input('Please enter your new word') max_range = input( 'Please enter the number that you want to change in every file.' 'If you enter a string, this program will change every string accord with the old word and the new word \\n') if old_word == '': print("Old keyword can't be null, please have a good think.") print('for disabling the old keyword, input it into "old word", and press Enter at "new word"') input('Press any key and press enter to close this window') return 0 if max_range.isalnum(): file = FileChange(old_word=old_word, new_word=new_word, max_range=max_range) file.do_it() else: file = FileChange(old_word=old_word, new_word=new_word, max_range=0) file.do_it() input('Press enter to close this window.')if __name__ == '__main__': main() 代码可以通过git来获取: 1git clone [email protected]:Gray-Ice/Meaningless-Project..git 你也可以直接访问我的github仓库:Meaningless-Project.来查看,该仓库时不时会更新一些无用的代码。大概是我有需要的时候,或者有新奇的想法的时候就会更新,不过代码的质量不会太高就是,因为我也不是大佬····并且每个函数我会尽量用英文来写描述,虽然我的英文很菜。。。我会尽量写类,以便调用。若是不懂怎么用直接在我博客下方评论即可,我看到后会第一时间解答。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"","slug":"C语言打印指针地址","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/C语言打印指针地址/","link":"","permalink":"http://example.com/2020/04/21/C%E8%AF%AD%E8%A8%80%E6%89%93%E5%8D%B0%E6%8C%87%E9%92%88%E5%9C%B0%E5%9D%80/","excerpt":"title: C语言打印指针地址categories: - blog - C发现GCC加了-Werror后C primer plus上打印指针地址的代码无法通过编译,于是问了群里大佬,得到了答复,下面我把能够通过编译的代码贴上:","text":"title: C语言打印指针地址categories: - blog - C发现GCC加了-Werror后C primer plus上打印指针地址的代码无法通过编译,于是问了群里大佬,得到了答复,下面我把能够通过编译的代码贴上: 123456789101112#include <stdio.h>#define SIZE 4int main(void){ int p[SIZE]; int * pt; pt = p; printf("%p\\n", (void *)pt); return 0;} 下面是执行结果: 12345678910~/codeSet/CCode ⌚ 10:48:51$ gcc -Wall -Werror -Wextra -pedantic -Wconversion test.c~/codeSet/CCode ⌚ 10:49:16$ ./a.out 0x7ffc359a0c90~/codeSet/CCode ⌚ 10:49:17$","categories":[],"tags":[]},{"title":"day01的专门术语","slug":"day01的专门术语","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/day01的专门术语/","link":"","permalink":"http://example.com/2020/04/21/day01%E7%9A%84%E4%B8%93%E9%97%A8%E6%9C%AF%E8%AF%AD/","excerpt":"","text":"TAB=4 有的文本编辑器可以调整TAB键的宽度。请使用这种编译器的人将TAB键的宽度设定成4,这样源程序更容易读。 FAT12格式 (FAT12 Format)用Windows或MS-DOS格式化出来的软盘就是这种格式,我们的helloos也采用了这种格式,其中容纳了我们开发的操作系统。这个格式兼容性好,在Windows上也能用,而且剩余的磁盘空间还可以用来保存自己喜欢的文件。 启动区 (boot sector)软盘第一个的扇区称为启动区。那么什么是扇区呢?计算机读写软盘的时候,并不是一个字节一个字节地读写的,而是以512字节为一个单位进行读写。因此,软盘的512字节就称为一个扇区。一张软盘的空间共有1440KB,也就是1474560字节,除以512得2880,这也就是说一张软盘共有2880个扇区。那为什么第一个扇区称为启动区呢?那是因为计算机首先从最初一个扇区开始读软盘,然后去检查这个扇区最后2个字节的内容。如果这最后2个字节不是0x55 AA,计算机会认为这张软盘上没有所需的启动程序,就会报一个不能启动的错误。(也许有人会问为什么一定是0x55 AA呢?那是当初的设计者随便定的)。如果计算机确认了第一个扇区的最后两个字正好是0x55 AA,那它就认为这个扇区的开头是启动程序,并开始执行这个程序。 IPL initial program loader的缩写。启动程序加载器。启动区只有区区512字节,实际的操作系统不像hello-os这么小,根本装不进去。所以几乎所有的操作系统,都是把加载操作系统的程序放在启动区里。有鉴于此,有时也将启动区成为IPL。但hello-os没有加载程序的功能,所以HELLOIPL这个名字不太顺理成章。可以改成其他的名字。但是必须起一个8字节的名字,如果名字不到8字节的话,需要在最后补上空格。 启动 (boot)boot这个词本是长靴(boots)的单数形式,它与计算机的启动有什么关系呢?一般应该将启动成为start的。实际上,boot这个词是bootstrap的缩写,原指靴子上附带的便于拿取的靴带。但自从有了《吹牛大王历险记》(德国)这个故事后,bootstrap这个词就有了”自力更生完成任务”这种意思。而且,磁盘上明明装有操作系统,还要说读入操作系统的程序(即IPL)也放在磁盘里,这就像打开宝物箱的钥匙就在宝物箱里一样,是一种矛盾的说法。这种矛盾的操作系统自动启动机制,被称为bootstrap方式。boot这个说法就来源于此。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"30days","slug":"blog/30days","permalink":"http://example.com/categories/blog/30days/"}],"tags":[{"name":"30days","slug":"30days","permalink":"http://example.com/tags/30days/"}]},{"title":"Django Models排序","slug":"Django models排序","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Django models排序/","link":"","permalink":"http://example.com/2020/04/21/Django%20models%E6%8E%92%E5%BA%8F/","excerpt":"","text":"对Django的model排序: 1goods = Goods.objects.all().order_by('-id') 该命令的意思是根据id字段进行反向排序。 如果有多个属性,可以使用: 1goods = Goods.objects.all().order_by('-id', 'create_time') 该命令的意思是先根据id字段反向排序,然后根据create_time排序。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"Django","slug":"Django","permalink":"http://example.com/tags/Django/"}]},{"title":"Django中间件","slug":"Django中间件","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Django中间件/","link":"","permalink":"http://example.com/2020/04/21/Django%E4%B8%AD%E9%97%B4%E4%BB%B6/","excerpt":"什么是中间件中间件就是在目标和结果之间进行的额外处理过程,在Django中就是request和response之间进行的处理,相对来说实现起来比较简单,但是要注意它是对全局有效的,可以在全局范围内改变输入和输出结果,因此需要谨慎使用,否则不仅会造成难以定位的错误,而且可能会影响整体性能。","text":"什么是中间件中间件就是在目标和结果之间进行的额外处理过程,在Django中就是request和response之间进行的处理,相对来说实现起来比较简单,但是要注意它是对全局有效的,可以在全局范围内改变输入和输出结果,因此需要谨慎使用,否则不仅会造成难以定位的错误,而且可能会影响整体性能。 中间件有什么用?如果想要修改HttpRequest或者HttpResponse,就可以通过中间件来实现。 登陆认证:在中间件中加入登陆认证,所有请求就自动拥有登陆认证,如果需要放开部分路由,只需要特殊处理就可以了。 流量统计:可以针对一些渲染页面统计访问流量。 恶意请求拦截:统计IP请求次数,可以进行频次限制或者封禁IP。 在Django中自定义中间件:在settings.py中找到MIDDLEWARE项,把添加的中间件配置到这里就行了。 例如我在myapp文件夹下(该文件夹与Django文件夹同级)有一个views.py文件,在views.py中有一个叫做MyMiddleware的中间件,那么配置的时候只要在MIDDLEWARE列表中添加一条: 1'myapp.views.MyMiddleware' 每个中间件可以包含五个方法: 12345process_request(self,request)process_view(self, request, callback, callback_args, callback_kwargs)process_template_response(self,request,response)process_exception(self, request, exception)process_response(self, request, response) 执行流程: 请求到达中间件后先依次执行每个中间件的process_request函数 然后再依次执行每个中间件的process_view函数,找到我们的视图函数 执行视图函数处理请求数据 如果在上面的过程中出现异常,则依次反方向执行每个中间件的process_exception函数 如果请求包含模板渲染,则依次反方向执行每个中间件的process_template_response函数 最后依次反方向执行每个中间件的process_response函数 以上这些执行函数将返回None或者HttpResponse对象,如果返回None,则交给下一个中间件的对应函数处理;如果返回HttpResponse对象,则将其返回给用户。 应用: 12345678910111213class MyMiddleware(MiddlewareMixin): def process_request(self, request): print('过滤中间件') pass def process_view(self, request, view_func, view_args, view_kwargs): pass def process_exception(self, request, exception): pass def process_response(self, request, response): return response 每次请求时,都会打印一行”过滤中间件”。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"Django","slug":"Django","permalink":"http://example.com/tags/Django/"},{"name":"中间件","slug":"中间件","permalink":"http://example.com/tags/%E4%B8%AD%E9%97%B4%E4%BB%B6/"}]},{"title":"Deepin安装Docker","slug":"Deepin安装docker","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Deepin安装docker/","link":"","permalink":"http://example.com/2020/04/21/Deepin%E5%AE%89%E8%A3%85docker/","excerpt":"今天感觉好久没有踩坑了,心里有点不舒服,于是就打算在Deepin系统中装个Docker。一有这想法,我就动手干,果然我想要的坑就来了。 首先,我参考了博客: https://www.cnblogs.com/wh4am1/p/10263272.html的内容,博主讲的很好,不过这里我还是说一下我安装的经过,如果有些地方大家也遇到了希望可以帮助到大家。 这里我先讲一下我干了什么,我图省事一开始就执行了安装命令: 1sudo apt-get install docker-ce 然后突然看到了别人的博客,觉得自己的安装步骤不对(其实是根本没有往下看别人的博客),我就拆卸了docker,这里很多人都是输入的: 1sudo apt-get remove docker.io docker-engine 但是我输入之后告诉我没这个东西,我就将docker.io改成了docker.ce,然后果然成功拆卸了。 然后按着步骤走:","text":"今天感觉好久没有踩坑了,心里有点不舒服,于是就打算在Deepin系统中装个Docker。一有这想法,我就动手干,果然我想要的坑就来了。 首先,我参考了博客: https://www.cnblogs.com/wh4am1/p/10263272.html的内容,博主讲的很好,不过这里我还是说一下我安装的经过,如果有些地方大家也遇到了希望可以帮助到大家。 这里我先讲一下我干了什么,我图省事一开始就执行了安装命令: 1sudo apt-get install docker-ce 然后突然看到了别人的博客,觉得自己的安装步骤不对(其实是根本没有往下看别人的博客),我就拆卸了docker,这里很多人都是输入的: 1sudo apt-get remove docker.io docker-engine 但是我输入之后告诉我没这个东西,我就将docker.io改成了docker.ce,然后果然成功拆卸了。 然后按着步骤走: 1.安装docker-ce与密钥管理与下载相关依赖工具1sudo apt-get install apt-transport-https ca-certificates curl python-software-properties software-properties-common 当输入这条命令后提示了: 12345没有可用的软件包 python-software-properties,但是它被其它的软件包引用了。这可能意味着这个缺失的软件包可能已被废弃,或者只能在其他发布源中找到然而下列软件包会取代它: software-properties-common 然后我就在网上找方法,很多博客都说使用 1apt-get update 可以解决这个问题,但是我输入该命令后问题并没有解决,并且提示的内容依然无变化,这里我猜测可能是我的docker拆卸的不彻底,不过博主也是萌新一个,这里只是猜测,并无实际意义,希望大家不要将此话当真。 然后我就抱着尝试的想法直接开始了第二步: 2.下载并安装密钥终端输入: 1curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add - 我个人认为应该报错才对,不过返回的却是OK。可能是我已经有 software-properties-common 了吧。 3.验证是否安装成功终端输入: 1sudo apt-key fingerprint 0EBFCD88 这里我也成功了~就不上图了,若是不了解成功后是什么样子的,可以查看我在文章开头就发的那条链接里的成功案例。 4.添加软件源终端输入: 1sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian jessie stable" 这里报错了,提示我: 12E: 无法获得锁 /var/cache/apt/archives/lock - open (11: 资源暂时不可用) E: 无法对目录 /var/cache/apt/archives/ 加锁 然后我百度了一下,找到了一个解决方案: 12sudo rm /var/cache/apt/archives/locksudo rm /var/lib/dpkg/lock 相信大家也看到这个 “rm”了,那么我就不多讲了。 然而解决了这个问题后再次输入命令又出了新的错误: 1aptsources.distro.NoDistroTemplateException: Error: could not find a distribution template for Deepin/stable 这是一个博主应对无法更改git仓库时的解决方案,我这里拿来用也合适。这里需要编辑lsb-release文件: 1sudo deepin-editor /etc/lsb-release 把已有内容的每行头加#注释掉,添加Ubuntu相关的内容: 123456789#DISTRIB_ID=Deepin#DISTRIB_RELEASE="15.11"#DISTRIB_DESCRIPTION="Deepin 15.11 "#DISTRIB_CODENAME=stableDISTRIB_ID=UbuntuDISTRIB_RELEASE=18.04DISTRIB_DESCRIPTION="Ubuntu 18.04 LTS"DISTRIB_CODENAME=trusty 然后重新添加源: 1sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian jessie stable" 这次没有任何提示,成功了。 5.安装docker在终端输入: 1sudo apt-get install docker-ce 安装完后可以查看版本 1docker version 可以运行helloworld测试 1docker run hello-world 如果本地没有hello-world,那么docker会下载一个,等它下载就好。 下载完成后docker会自动运行hello-world。 我到了这里运行是无误的,那么应该也就是说docker已经装好了。 所以,本篇完~","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Deepin","slug":"blog/Deepin","permalink":"http://example.com/categories/blog/Deepin/"}],"tags":[{"name":"Deepin","slug":"Deepin","permalink":"http://example.com/tags/Deepin/"},{"name":"Docker","slug":"Docker","permalink":"http://example.com/tags/Docker/"},{"name":"踩坑","slug":"踩坑","permalink":"http://example.com/tags/%E8%B8%A9%E5%9D%91/"}]},{"title":"Django展示图片","slug":"Django访问图片","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Django访问图片/","link":"","permalink":"http://example.com/2020/04/21/Django%E8%AE%BF%E9%97%AE%E5%9B%BE%E7%89%87/","excerpt":"","text":"想要通过访问如同127.0.0.1:8000/static/Tree.png的形式来访问在Django目录下的静态文件,首先需要在Django目录下创建一个static文件夹,该文件夹需与manage.py文件同级。然后编辑settings.py,在末尾加上: 12345STATIC_URL = '/static/'STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static')] 然后就可以通过url访问文件了,建议先添加进static一张图片测试一下。 你也可以在static下新建个文件夹来进行文件的分类,比如我在static下新建一个文件夹img,在img文件夹中添加一张Tree.png的图片,然后想要访问该文件,可通过:http://127.0.0.1:8000/static/img/Tree.png来访问","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"Django","slug":"Django","permalink":"http://example.com/tags/Django/"}]},{"title":"关于Django询问是否已经导入mysql","slug":"Django询问是否导入mysql","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Django询问是否导入mysql/","link":"","permalink":"http://example.com/2020/04/21/Django%E8%AF%A2%E9%97%AE%E6%98%AF%E5%90%A6%E5%AF%BC%E5%85%A5mysql/","excerpt":"","text":"今天新开了个Django项目,创建好数据库模型后输入: 1python manage.py makemigrations appname 结果提示错误: 12django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module.Did you install mysqlclient? 解决方法: 1.首先,安装pymysql(如果安装过了就跳过这一步)。 2.然后,在Django项目的__init__.py下添加这样一段代码: 12import pymysqlpymysql.install_as_MySQLdb() 这样就解决这个问题了! 然后重新输入: 1python manage.py makemigrations appname","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"踩坑","slug":"踩坑","permalink":"http://example.com/tags/%E8%B8%A9%E5%9D%91/"},{"name":"Django","slug":"Django","permalink":"http://example.com/tags/Django/"}]},{"title":"Docker微信","slug":"Docker微信","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Docker微信/","link":"","permalink":"http://example.com/2020/04/21/Docker%E5%BE%AE%E4%BF%A1/","excerpt":"在文章的开头,我先说出我配置完成后遗留的问题,方便大家决定是否参考我的博客。我按照我博客里的流程配置完毕后,微信出现的问题是看不到输入框里的字,无论是输入中文还是输入英文,都看不见。不过这不影响发送出去文字。另外有一点,我不知道这是不是问题,因为wine-wechat也是如此,那就是截图的时候屏幕是黑的。。。除了这些之外,消息和图片消息都可以正常接受,暂时没有发现其他问题。 那么下面是安装了。首先安装Dokcer: 1pacman -S docker","text":"在文章的开头,我先说出我配置完成后遗留的问题,方便大家决定是否参考我的博客。我按照我博客里的流程配置完毕后,微信出现的问题是看不到输入框里的字,无论是输入中文还是输入英文,都看不见。不过这不影响发送出去文字。另外有一点,我不知道这是不是问题,因为wine-wechat也是如此,那就是截图的时候屏幕是黑的。。。除了这些之外,消息和图片消息都可以正常接受,暂时没有发现其他问题。 那么下面是安装了。首先安装Dokcer: 1pacman -S docker 然后更改镜像地址,这里我就不说了。Docker的配置可以根据Arch Wiki Docker上来做。配置好后可以观看bilibili视频Linux上使用Docker运行QQ和微信解决痛点来使用Docker微信。如果你不想看视频,那么可以看我的文字。我已经将用户加入到docker组了,所以使用docker命令的时候不用加sudo或使用su了。输入: 1234567docker pull zixia/wechat````这一步会下载一个名叫zixia/wechat的image,我写这篇博客的时候下载下来的image有3个G。下载完毕后,可以查看<a href="https://github.com/huan/docker-wechat">盒装微信</a>来选择使用一键脚本还是自己编辑配置脚本,这里我选择的自己编辑。编辑一个dockerwechat.sh脚本(当然,放在哪,叫什么都无所谓,我把该文件放在了~下)。然后从上面发的那个网址下的README.md中"For Hackers"标题下的代码块中复制代码,粘贴代码至刚才编辑的脚本。如果粘贴的时候出现缩进问题,可以查看我的博客下vim标签里的“关闭vim自动缩进",这里我不放超链了,以后博客可能会整改,怕放了超链可能找不到文件。那么言归正传,将复制的代码粘贴进sh脚本后,你可以还没有执行它的权限,使用:```bashchmod +x dockerwechat.sh 来增加执行权限。然后使用./dockerwechat.sh来执行脚本。这时等一会儿就会弹出微信的登录页面了。接下来也许会提示你文件位置的问题,选择路径即可。那么,如果你选择路径的时候出现了问题,即选择了路径后还提示你需要选择路径,嘿嘿,恭喜你,遇到了和我一样的问题。我参考了Github项目下的Issues:文件默认保存位置无法使用,将不能正常使用微信,请更改位置。答主说,将docker共享目录owner设置为当前用户就可以正常使用了。 1sudo chown -R username:username $HOME/DoChat 将username换成你的登录用户名。执行这条命令后,我重新登录了微信,问题果然解决了。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Docker","slug":"Docker","permalink":"http://example.com/tags/Docker/"}]},{"title":"Hello World","slug":"hello-world","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/hello-world/","link":"","permalink":"http://example.com/2020/04/21/hello-world/","excerpt":"","text":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new "My New Post" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment","categories":[],"tags":[]},{"title":"WindowsHexo迁移至Linux","slug":"hexo博客迁移","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/hexo博客迁移/","link":"","permalink":"http://example.com/2020/04/21/hexo%E5%8D%9A%E5%AE%A2%E8%BF%81%E7%A7%BB/","excerpt":"","text":"首先我先说一下我迁移过程中出现的问题,看完我的问题后再迁移也不迟。我迁移成功了,但是文件的创建时间混乱了,我很懵,似乎是因为我使用cp命令的问题,到底怎么样你可以看一下的我的归档,很多博客都是显示在6月29日创建的,但是事实并非如此。那么接下里,我讲讲操作吧。首先你的linux上要有hexo,本文并不会写如何安装hexo,建议先按照百度的方法安装上hexo再看本文。如果你安装上了hexo了,那么新建一个你要用作博客的文件夹,cd进文件夹,打开终端,输入: 1hexo init 来初始化一个hexo项目,然后将Windows的hexo根目录下的themes,source,scaffolds文件夹以及_config.yml, package.json文件拷贝进Linux下的你刚初始化的项目下并覆盖掉项目下的文件或文件夹,然后npm install一下,然后输入: 1hexo g -d 看看能否成功。注意,执行这条命令前你必须已经配置好了git。如果成功了那就成功了,如果不成功,那安装hexo-deployer-git试试: 1npm install hexo-deployer-git --save 如果还不行那我也不会啦~","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://example.com/tags/hexo/"}]},{"title":"Arch Linux下使用LinuxQQ","slug":"linuxQQ","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/linuxQQ/","link":"","permalink":"http://example.com/2020/04/21/linuxQQ/","excerpt":"","text":"在Arch Linux下安装 Linux QQ十分的简单,你甚至不需要安装wine(我也是今天才知道),QQ提供了Linux版。那么回归正题,首先你需要安装下面两个包,输入命令: 12pacman -S gtk2pacman -S nss 然后是安装QQ,这里我使用过使用pacman直接安装,但是效果不怎么理想,所以这里我打开了QQ for linux的官网,点击立即下载,选择pacman(注意看你自己的架构),下载之后应该是一个.tar.xz文件,解压: 12xz -d yourqqzip.tar.xztar -xv yourqqzip.tar 然后你会发现解压出来了一个usr文件夹(????怎么是usr文件夹),这里你需要把里面的文件放到对应的文件夹下: 比如qq文件在usr/local/bin下,那么就把它复制到/usr/local/bin下(没错就是这么简单),复制完所有的文件后在桌面创建Lanucher,输入QQ,剩下的让它自动补全即可。然后打开QQ扫码登录即可~如果扫码登录后出现闪退的情况,如果你是按照我这个方法安装的,那么不要慌,编辑文件/usr/share/applications/qq.desktop,在Exec后面,即Exec=/usr/local/bin/qq后面加上这样一段字符串(注意,%U前面有一个空格): 1%U --no-sandbox 然后重启QQ,扫码后就不会闪退了。 更新: (2020-07-10)使用vim编辑的文档似乎没有保留创建时间这一说。。。不过没关系,重要的是文章不是文章的时间,最近qq-linux又出毛病了,加了%U –no-sandbox依然闪退,后来在百度上找到一条能够解决问题的命令: 1rm -r ~/.config/tencent-qq 删除掉qq-linux的配置文件,之后qq-linux就不闪退了,这波操作有点秀,虽然没看懂 但是确实好使。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"Linux下查看文件安装路径","slug":"Linux查看二进制文件的路径","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Linux查看二进制文件的路径/","link":"","permalink":"http://example.com/2020/04/21/Linux%E6%9F%A5%E7%9C%8B%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%96%87%E4%BB%B6%E7%9A%84%E8%B7%AF%E5%BE%84/","excerpt":"","text":"使用whereis binaryfile来查看文件路径。如: 1whereis pacman 输出: 1pacman: /usr/bin/pacman /etc/pacman.d /etc/pacman.conf /usr/share/pacman /usr/share/man/man8/pacman.8.gz /usr/share/man/man6/pacman.6.gz 之前就觉得应该有这个命令,但是一直不知道叫什么,都是自己手动搜索目录,今天实在是忍不了就百度了一下…","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[{"name":"Linux命令","slug":"Linux命令","permalink":"http://example.com/tags/Linux%E5%91%BD%E4%BB%A4/"}]},{"title":"linux磁力下载器","slug":"Linux磁力下载","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Linux磁力下载/","link":"","permalink":"http://example.com/2020/04/21/Linux%E7%A3%81%E5%8A%9B%E4%B8%8B%E8%BD%BD/","excerpt":"","text":"这是一个名叫aria2的磁力下载器,可以直接使用pacman安装: 1pacman -S aria2 使用方法也很简单,直接在终端输入:aria2c 链接即可,默认会下载到当前终端所在的目录(如当前目录是/home/username/download,那么文件就会下载到/home/username/download文件夹下)。那么本篇完。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[]},{"title":"Linux使用v2raya代理","slug":"Linux使用v2raya代理","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Linux使用v2raya代理/","link":"","permalink":"http://example.com/2020/04/21/Linux%E4%BD%BF%E7%94%A8v2raya%E4%BB%A3%E7%90%86/","excerpt":"","text":"由于博主是小白,配置好了Qv2ray之后能够测试延迟但是上不了网,在百般百度无果的情况下,偶然得知还有这么一款开箱即用的v2ray图形化软件。那么话不多说,开始安装。v2rayA是无法通过pacman获取的,所以要从github上下载它,在Releases中下载对应的包,一番解压之后可以得到两个文件夹:etc文件夹和usr文件夹,打开可以看到,etc文件夹中有一个systemd文件夹,usr文件夹中有一个bin文件夹和一个share文件夹,我们使用传统艺能,将etc文件夹里的东西放在/etc文件夹下面,将usr文件夹里的东西放在/usr文件夹下面。如:将etc/systemd/system/v2raya.service文件放在/etc/systemd/system下。这里建议复制,因为想要删除它的时候你可以根据你安装时候的文件目录进行删除。文件复制完之后,输入: 1sudo /usr/bin/v2raya 来运行程序,然后在桌面创建一个v2rayA启动器,或者运行v2raya.desktop文件,再或者在浏览器中打开v2rayA管理页面来操作v2rayA,地址是:https://v2raya.mzz.pub。这样v2rayA的安装就结束了,配置好节点之后,在设置里打开全局透明代理就可以上网了!","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[{"name":"代理","slug":"代理","permalink":"http://example.com/tags/%E4%BB%A3%E7%90%86/"}]},{"title":"","slug":"Linux命令","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Linux命令/","link":"","permalink":"http://example.com/2020/04/21/Linux%E5%91%BD%E4%BB%A4/","excerpt":"转载自 Linux常用命令大全(非常全!!!) 原作者只是给字体加粗了,没有设置标题,我加了标题,方便在博客右方的“文章目录里查找自己需要的内容”。感谢原作者的分享!","text":"转载自 Linux常用命令大全(非常全!!!) 原作者只是给字体加粗了,没有设置标题,我加了标题,方便在博客右方的“文章目录里查找自己需要的内容”。感谢原作者的分享! 系统信息arch 显示机器的处理器架构uname -m 显示机器的处理器架构uname -r 显示正在使用的内核版本dmidecode -q 显示硬件系统部件 - (SMBIOS / DMI)hdparm -i /dev/hda 罗列一个磁盘的架构特性hdparm -tT /dev/sda 在磁盘上执行测试性读取操作cat /proc/cpuinfo 显示CPU info的信息cat /proc/interrupts 显示中断cat /proc/meminfo 校验内存使用cat /proc/swaps 显示哪些swap被使用cat /proc/version 显示内核的版本cat /proc/net/dev 显示网络适配器及统计cat /proc/mounts 显示已加载的文件系统lspci -tv 罗列 PCI 设备lsusb -tv 显示 USB 设备date 显示系统日期cal 2007 显示2007年的日历表date 041217002007.00 设置日期和时间 - 月日时分年.秒clock -w 将时间修改保存到 BIOS 关机 (系统的关机、重启以及登出 )shutdown -h now 关闭系统init 0 关闭系统telinit 0 关闭系统shutdown -h hours:minutes & 按预定时间关闭系统shutdown -c 取消按预定时间关闭系统shutdown -r now 重启reboot 重启logout 注销 文件和目录cd /home 进入 ‘/ home’ 目录’cd .. 返回上一级目录cd ../.. 返回上两级目录cd 进入个人的主目录cd ~user1 进入个人的主目录cd - 返回上次所在的目录pwd 显示工作路径ls 查看目录中的文件ls -F 查看目录中的文件ls -l 显示文件和目录的详细资料ls -a 显示隐藏文件ls [0-9] 显示包含数字的文件名和目录名tree 显示文件和目录由根目录开始的树形结构lstree 显示文件和目录由根目录开始的树形结构mkdir dir1 创建一个叫做 ‘dir1’ 的目录’mkdir dir1 dir2 同时创建两个目录mkdir -p /tmp/dir1/dir2 创建一个目录树rm -f file1 删除一个叫做 ‘file1’ 的文件’rmdir dir1 删除一个叫做 ‘dir1’ 的目录’rm -rf dir1 删除一个叫做 ‘dir1’ 的目录并同时删除其内容rm -rf dir1 dir2 同时删除两个目录及它们的内容mv dir1 new_dir 重命名/移动 一个目录cp file1 file2 复制一个文件cp dir/* . 复制一个目录下的所有文件到当前工作目录cp -a /tmp/dir1 . 复制一个目录到当前工作目录cp -a dir1 dir2 复制一个目录ln -s file1 lnk1 创建一个指向文件或目录的软链接ln file1 lnk1 创建一个指向文件或目录的物理链接touch -t 0712250000 file1 修改一个文件或目录的时间戳 - (YYMMDDhhmm)file file1 outputs the mime type of the file as texticonv -l 列出已知的编码iconv -f fromEncoding -t toEncoding inputFile > outputFile creates a new from the given input file by assuming it is encoded in fromEncoding and converting it to toEncoding.find . -maxdepth 1 -name *.jpg -print -exec convert “{}” -resize 80x60 “thumbs/{}” ; batch resize files in the current directory and send them to a thumbnails directory (requires convert from Imagemagick) 文件搜索find / -name file1 从 ‘/‘ 开始进入根文件系统搜索文件和目录find / -user user1 搜索属于用户 ‘user1’ 的文件和目录find /home/user1 -name *.bin 在目录 ‘/ home/user1’ 中搜索带有’.bin’ 结尾的文件find /usr/bin -type f -atime +100 搜索在过去100天内未被使用过的执行文件find /usr/bin -type f -mtime -10 搜索在10天内被创建或者修改过的文件find / -name *.rpm -exec chmod 755 ‘{}’ ; 搜索以 ‘.rpm’ 结尾的文件并定义其权限find / -xdev -name *.rpm 搜索以 ‘.rpm’ 结尾的文件,忽略光驱、捷盘等可移动设备locate *.ps 寻找以 ‘.ps’ 结尾的文件 - 先运行 ‘updatedb’ 命令whereis halt 显示一个二进制文件、源码或man的位置which halt 显示一个二进制文件或可执行文件的完整路径 挂载一个文件系统mount /dev/hda2 /mnt/hda2 挂载一个叫做hda2的盘 - 确定目录 ‘/ mnt/hda2’ 已经存在umount /dev/hda2 卸载一个叫做hda2的盘 - 先从挂载点 ‘/ mnt/hda2’ 退出fuser -km /mnt/hda2 当设备繁忙时强制卸载umount -n /mnt/hda2 运行卸载操作而不写入 /etc/mtab 文件- 当文件为只读或当磁盘写满时非常有用mount /dev/fd0 /mnt/floppy 挂载一个软盘mount /dev/cdrom /mnt/cdrom 挂载一个cdrom或dvdrommount /dev/hdc /mnt/cdrecorder 挂载一个cdrw或dvdrommount /dev/hdb /mnt/cdrecorder 挂载一个cdrw或dvdrommount -o loop file.iso /mnt/cdrom 挂载一个文件或ISO镜像文件mount -t vfat /dev/hda5 /mnt/hda5 挂载一个Windows FAT32文件系统mount /dev/sda1 /mnt/usbdisk 挂载一个usb 捷盘或闪存设备mount -t smbfs -o username=user,password=pass //WinClient/share /mnt/share 挂载一个windows网络共享 磁盘空间df -h 显示已经挂载的分区列表ls -lSr |more 以尺寸大小排列文件和目录du -sh dir1 估算目录 ‘dir1’ 已经使用的磁盘空间’du -sk * | sort -rn 以容量大小为依据依次显示文件和目录的大小rpm -q -a –qf ‘%10{SIZE}t%{NAME}n’ | sort -k1,1n 以大小为依据依次显示已安装的rpm包所使用的空间 (fedora, redhat类系统)dpkg-query -W -f=’${Installed-Size;10}t${Package}n’ | sort -k1,1n 以大小为依据显示已安装的deb包所使用的空间 (ubuntu, debian类系统) 用户和群组groupadd group_name 创建一个新用户组groupdel group_name 删除一个用户组groupmod -n new_group_name old_group_name 重命名一个用户组useradd -c “Name Surname “ -g admin -d /home/user1 -s /bin/bash user1 创建一个属于 “admin” 用户组的用户useradd user1 创建一个新用户userdel -r user1 删除一个用户 ( ‘-r’ 排除主目录)usermod -c “User FTP” -g system -d /ftp/user1 -s /bin/nologin user1 修改用户属性passwd 修改口令passwd user1 修改一个用户的口令 (只允许root执行)chage -E 2005-12-31 user1 设置用户口令的失效期限pwck 检查 ‘/etc/passwd’ 的文件格式和语法修正以及存在的用户grpck 检查 ‘/etc/passwd’ 的文件格式和语法修正以及存在的群组newgrp group_name 登陆进一个新的群组以改变新创建文件的预设群组 文件的权限 - 使用 “+” 设置权限,使用 “-“ 用于取消ls -lh 显示权限ls /tmp | pr -T5 -W$COLUMNS 将终端划分成5栏显示chmod ugo+rwx directory1 设置目录的所有人(u)、群组(g)以及其他人(o)以读(r )、写(w)和执行(x)的权限chmod go-rwx directory1 删除群组(g)与其他人(o)对目录的读写执行权限chown user1 file1 改变一个文件的所有人属性chown -R user1 directory1 改变一个目录的所有人属性并同时改变改目录下所有文件的属性chgrp group1 file1 改变文件的群组chown user1:group1 file1 改变一个文件的所有人和群组属性find / -perm -u+s 罗列一个系统中所有使用了SUID控制的文件chmod u+s /bin/file1 设置一个二进制文件的 SUID 位 - 运行该文件的用户也被赋予和所有者同样的权限chmod u-s /bin/file1 禁用一个二进制文件的 SUID位chmod g+s /home/public 设置一个目录的SGID 位 - 类似SUID ,不过这是针对目录的chmod g-s /home/public 禁用一个目录的 SGID 位chmod o+t /home/public 设置一个文件的 STIKY 位 - 只允许合法所有人删除文件chmod o-t /home/public 禁用一个目录的 STIKY 位 文件的特殊属性 - 使用 “+” 设置权限,使用 “-“ 用于取消chattr +a file1 只允许以追加方式读写文件chattr +c file1 允许这个文件能被内核自动压缩/解压chattr +d file1 在进行文件系统备份时,dump程序将忽略这个文件chattr +i file1 设置成不可变的文件,不能被删除、修改、重命名或者链接chattr +s file1 允许一个文件被安全地删除chattr +S file1 一旦应用程序对这个文件执行了写操作,使系统立刻把修改的结果写到磁盘chattr +u file1 若文件被删除,系统会允许你在以后恢复这个被删除的文件lsattr 显示特殊的属性 打包和压缩文件bunzip2 file1.bz2 解压一个叫做 ‘file1.bz2’的文件bzip2 file1 压缩一个叫做 ‘file1’ 的文件gunzip file1.gz 解压一个叫做 ‘file1.gz’的文件gzip file1 压缩一个叫做 ‘file1’的文件gzip -9 file1 最大程度压缩rar a file1.rar test_file 创建一个叫做 ‘file1.rar’ 的包rar a file1.rar file1 file2 dir1 同时压缩 ‘file1’, ‘file2’ 以及目录 ‘dir1’unrar x file1.rar 解压rar包tar -cvf archive.tar file1 创建一个非压缩的 tarballtar -cvf archive.tar file1 file2 dir1 创建一个包含了 ‘file1’, ‘file2’ 以及 ‘dir1’的档案文件tar -tf archive.tar 显示一个包中的内容tar -xvf archive.tar 释放一个包tar -xvf archive.tar -C /tmp 将压缩包释放到 /tmp目录下tar -cvfj archive.tar.bz2 dir1 创建一个bzip2格式的压缩包tar -jxvf archive.tar.bz2 解压一个bzip2格式的压缩包tar -cvfz archive.tar.gz dir1 创建一个gzip格式的压缩包tar -zxvf archive.tar.gz 解压一个gzip格式的压缩包zip file1.zip file1 创建一个zip格式的压缩包zip -r file1.zip file1 file2 dir1 将几个文件和目录同时压缩成一个zip格式的压缩包unzip file1.zip 解压一个zip格式压缩包 RPM 包 - (Fedora, Redhat及类似系统)rpm -ivh package.rpm 安装一个rpm包rpm -ivh –nodeeps package.rpm 安装一个rpm包而忽略依赖关系警告rpm -U package.rpm 更新一个rpm包但不改变其配置文件rpm -F package.rpm 更新一个确定已经安装的rpm包rpm -e package_name.rpm 删除一个rpm包rpm -qa 显示系统中所有已经安装的rpm包rpm -qa | grep httpd 显示所有名称中包含 “httpd” 字样的rpm包rpm -qi package_name 获取一个已安装包的特殊信息rpm -qg “System Environment/Daemons” 显示一个组件的rpm包rpm -ql package_name 显示一个已经安装的rpm包提供的文件列表rpm -qc package_name 显示一个已经安装的rpm包提供的配置文件列表rpm -q package_name –whatrequires 显示与一个rpm包存在依赖关系的列表rpm -q package_name –whatprovides 显示一个rpm包所占的体积rpm -q package_name –scripts 显示在安装/删除期间所执行的脚本lrpm -q package_name –changelog 显示一个rpm包的修改历史rpm -qf /etc/httpd/conf/httpd.conf 确认所给的文件由哪个rpm包所提供rpm -qp package.rpm -l 显示由一个尚未安装的rpm包提供的文件列表rpm –import /media/cdrom/RPM-GPG-KEY 导入公钥数字证书rpm –checksig package.rpm 确认一个rpm包的完整性rpm -qa gpg-pubkey 确认已安装的所有rpm包的完整性rpm -V package_name 检查文件尺寸、 许可、类型、所有者、群组、MD5检查以及最后修改时间rpm -Va 检查系统中所有已安装的rpm包- 小心使用rpm -Vp package.rpm 确认一个rpm包还未安装rpm2cpio package.rpm | cpio –extract –make-directories bin 从一个rpm包运行可执行文件rpm -ivh /usr/src/redhat/RPMS/arch/package.rpm 从一个rpm源码安装一个构建好的包rpmbuild –rebuild package_name.src.rpm 从一个rpm源码构建一个 rpm 包 YUM 软件包升级器 - (Fedora, RedHat及类似系统)yum install package_name 下载并安装一个rpm包yum localinstall package_name.rpm 将安装一个rpm包,使用你自己的软件仓库为你解决所有依赖关系yum update package_name.rpm 更新当前系统中所有安装的rpm包yum update package_name 更新一个rpm包yum remove package_name 删除一个rpm包yum list 列出当前系统中安装的所有包yum search package_name 在rpm仓库中搜寻软件包yum clean packages 清理rpm缓存删除下载的包yum clean headers 删除所有头文件yum clean all 删除所有缓存的包和头文件 DEB 包 (Debian, Ubuntu 以及类似系统)dpkg -i package.deb 安装/更新一个 deb 包dpkg -r package_name 从系统删除一个 deb 包dpkg -l 显示系统中所有已经安装的 deb 包dpkg -l | grep httpd 显示所有名称中包含 “httpd” 字样的deb包dpkg -s package_name 获得已经安装在系统中一个特殊包的信息dpkg -L package_name 显示系统中已经安装的一个deb包所提供的文件列表dpkg –contents package.deb 显示尚未安装的一个包所提供的文件列表dpkg -S /bin/ping 确认所给的文件由哪个deb包提供 APT 软件工具 (Debian, Ubuntu 以及类似系统)apt-get install package_name 安装/更新一个 deb 包apt-cdrom install package_name 从光盘安装/更新一个 deb 包apt-get update 升级列表中的软件包apt-get upgrade 升级所有已安装的软件apt-get remove package_name 从系统删除一个deb包apt-get check 确认依赖的软件仓库正确apt-get clean 从下载的软件包中清理缓存apt-cache search searched-package 返回包含所要搜索字符串的软件包名称 查看文件内容cat file1 从第一个字节开始正向查看文件的内容tac file1 从最后一行开始反向查看一个文件的内容more file1 查看一个长文件的内容less file1 类似于 ‘more’ 命令,但是它允许在文件中和正向操作一样的反向操作head -2 file1 查看一个文件的前两行tail -2 file1 查看一个文件的最后两行tail -f /var/log/messages 实时查看被添加到一个文件中的内容 文本处理cat file1 file2 … | command <> file1_in.txt_or_file1_out.txt general syntax for text manipulation using PIPE, STDIN and STDOUTcat file1 | command( sed, grep, awk, grep, etc…) > result.txt 合并一个文件的详细说明文本,并将简介写入一个新文件中cat file1 | command( sed, grep, awk, grep, etc…) >> result.txt 合并一个文件的详细说明文本,并将简介写入一个已有的文件中grep Aug /var/log/messages 在文件 ‘/var/log/messages’中查找关键词”Aug”grep ^Aug /var/log/messages 在文件 ‘/var/log/messages’中查找以”Aug”开始的词汇grep [0-9] /var/log/messages 选择 ‘/var/log/messages’ 文件中所有包含数字的行grep Aug -R /var/log/* 在目录 ‘/var/log’ 及随后的目录中搜索字符串”Aug”sed ‘s/stringa1/stringa2/g’ example.txt 将example.txt文件中的 “string1” 替换成 “string2”sed ‘/^$/d’ example.txt 从example.txt文件中删除所有空白行sed ‘/ *#/d; /^$/d’ example.txt 从example.txt文件中删除所有注释和空白行echo ‘esempio’ | tr ‘[:lower:]’ ‘[:upper:]’ 合并上下单元格内容sed -e ‘1d’ result.txt 从文件example.txt 中排除第一行sed -n ‘/stringa1/p’ 查看只包含词汇 “string1”的行sed -e ‘s/ $//‘ example.txt 删除每一行最后的空白字符sed -e ‘s/stringa1//g’ example.txt 从文档中只删除词汇 “string1” 并保留剩余全部sed -n ‘1,5p;5q’ example.txt 查看从第一行到第5行内容sed -n ‘5p;5q’ example.txt 查看第5行sed -e ‘s/00/0/g’ example.txt 用单个零替换多个零cat -n file1 标示文件的行数cat example.txt | awk ‘NR%2==1’ 删除example.txt文件中的所有偶数行echo a b c | awk ‘{print $1}’ 查看一行第一栏echo a b c | awk ‘{print $1,$3}’ 查看一行的第一和第三栏paste file1 file2 合并两个文件或两栏的内容paste -d ‘+’ file1 file2 合并两个文件或两栏的内容,中间用”+”区分sort file1 file2 排序两个文件的内容sort file1 file2 | uniq 取出两个文件的并集(重复的行只保留一份)sort file1 file2 | uniq -u 删除交集,留下其他的行sort file1 file2 | uniq -d 取出两个文件的交集(只留下同时存在于两个文件中的文件)comm -1 file1 file2 比较两个文件的内容只删除 ‘file1’ 所包含的内容comm -2 file1 file2 比较两个文件的内容只删除 ‘file2’ 所包含的内容comm -3 file1 file2 比较两个文件的内容只删除两个文件共有的部分 字符设置和文件格式转换dos2unix filedos.txt fileunix.txt 将一个文本文件的格式从MSDOS转换成UNIXunix2dos fileunix.txt filedos.txt 将一个文本文件的格式从UNIX转换成MSDOSrecode ..HTML < page.txt > page.html 将一个文本文件转换成htmlrecode -l | more 显示所有允许的转换格式 文件系统分析badblocks -v /dev/hda1 检查磁盘hda1上的坏磁块fsck /dev/hda1 修复/检查hda1磁盘上linux文件系统的完整性fsck.ext2 /dev/hda1 修复/检查hda1磁盘上ext2文件系统的完整性e2fsck /dev/hda1 修复/检查hda1磁盘上ext2文件系统的完整性e2fsck -j /dev/hda1 修复/检查hda1磁盘上ext3文件系统的完整性fsck.ext3 /dev/hda1 修复/检查hda1磁盘上ext3文件系统的完整性fsck.vfat /dev/hda1 修复/检查hda1磁盘上fat文件系统的完整性fsck.msdos /dev/hda1 修复/检查hda1磁盘上dos文件系统的完整性dosfsck /dev/hda1 修复/检查hda1磁盘上dos文件系统的完整性 初始化一个文件系统mkfs /dev/hda1 在hda1分区创建一个文件系统mke2fs /dev/hda1 在hda1分区创建一个linux ext2的文件系统mke2fs -j /dev/hda1 在hda1分区创建一个linux ext3(日志型)的文件系统mkfs -t vfat 32 -F /dev/hda1 创建一个 FAT32 文件系统fdformat -n /dev/fd0 格式化一个软盘mkswap /dev/hda3 创建一个swap文件系统 SWAP文件系统mkswap /dev/hda3 创建一个swap文件系统swapon /dev/hda3 启用一个新的swap文件系统swapon /dev/hda2 /dev/hdb3 启用两个swap分区 备份dump -0aj -f /tmp/home0.bak /home 制作一个 ‘/home’ 目录的完整备份dump -1aj -f /tmp/home0.bak /home 制作一个 ‘/home’ 目录的交互式备份restore -if /tmp/home0.bak 还原一个交互式备份rsync -rogpav –delete /home /tmp 同步两边的目录rsync -rogpav -e ssh –delete /home ip_address:/tmp 通过SSH通道rsyncrsync -az -e ssh –delete ip_addr:/home/public /home/local 通过ssh和压缩将一个远程目录同步到本地目录rsync -az -e ssh –delete /home/local ip_addr:/home/public 通过ssh和压缩将本地目录同步到远程目录dd bs=1M if=/dev/hda | gzip | ssh user@ip_addr ‘dd of=hda.gz’ 通过ssh在远程主机上执行一次备份本地磁盘的操作dd if=/dev/sda of=/tmp/file1 备份磁盘内容到一个文件tar -Puf backup.tar /home/user 执行一次对 ‘/home/user’ 目录的交互式备份操作( cd /tmp/local/ && tar c . ) | ssh -C user@ip_addr ‘cd /home/share/ && tar x -p’ 通过ssh在远程目录中复制一个目录内容( tar c /home ) | ssh -C user@ip_addr ‘cd /home/backup-home && tar x -p’ 通过ssh在远程目录中复制一个本地目录tar cf - . | (cd /tmp/backup ; tar xf - ) 本地将一个目录复制到另一个地方,保留原有权限及链接find /home/user1 -name ‘.txt’ | xargs cp -av –target-directory=/home/backup/ –parents 从一个目录查找并复制所有以 ‘.txt’ 结尾的文件到另一个目录find /var/log -name ‘.log’ | tar cv –files-from=- | bzip2 > log.tar.bz2 查找所有以 ‘.log’ 结尾的文件并做成一个bzip包dd if=/dev/hda of=/dev/fd0 bs=512 count=1 做一个将 MBR (Master Boot Record)内容复制到软盘的动作dd if=/dev/fd0 of=/dev/hda bs=512 count=1 从已经保存到软盘的备份中恢复MBR内容 光盘cdrecord -v gracetime=2 dev=/dev/cdrom -eject blank=fast -force 清空一个可复写的光盘内容mkisofs /dev/cdrom > cd.iso 在磁盘上创建一个光盘的iso镜像文件mkisofs /dev/cdrom | gzip > cd_iso.gz 在磁盘上创建一个压缩了的光盘iso镜像文件mkisofs -J -allow-leading-dots -R -V “Label CD” -iso-level 4 -o ./cd.iso data_cd 创建一个目录的iso镜像文件cdrecord -v dev=/dev/cdrom cd.iso 刻录一个ISO镜像文件gzip -dc cd_iso.gz | cdrecord dev=/dev/cdrom - 刻录一个压缩了的ISO镜像文件mount -o loop cd.iso /mnt/iso 挂载一个ISO镜像文件cd-paranoia -B 从一个CD光盘转录音轨到 wav 文件中cd-paranoia – “-3” 从一个CD光盘转录音轨到 wav 文件中(参数-3)cdrecord –scanbus 扫描总线以识别scsi通道dd if=/dev/hdc | md5sum 校验一个设备的md5sum编码,例如一张 CD 网络 - (以太网和WIFI无线)ifconfig eth0 显示一个以太网卡的配置ifup eth0 启用一个 ‘eth0’ 网络设备ifdown eth0 禁用一个 ‘eth0’ 网络设备ifconfig eth0 192.168.1.1 netmask 255.255.255.0 控制IP地址ifconfig eth0 promisc 设置 ‘eth0’ 成混杂模式以嗅探数据包 (sniffing)dhclient eth0 以dhcp模式启用 ‘eth0’route -n show routing tableroute add -net 0/0 gw IP_Gateway configura default gatewayroute add -net 192.168.0.0 netmask 255.255.0.0 gw 192.168.1.1 configure static route to reach network ‘192.168.0.0/16’route del 0/0 gw IP_gateway remove static routeecho “1” > /proc/sys/net/ipv4/ip_forward activate ip routinghostname show hostname of systemhost www.example.com lookup hostname to resolve name to ip address and viceversanslookup www.example.com lookup hostname to resolve name to ip address and viceversaip link show show link status of all interfacesmii-tool eth0 show link status of ‘eth0’ethtool eth0 show statistics of network card ‘eth0’netstat -tup show all active network connections and their PIDnetstat -tupl show all network services listening on the system and their PIDtcpdump tcp port 80 show all HTTP trafficiwlist scan show wireless networksiwconfig eth1 show configuration of a wireless network cardhostname show hostnamehost www.example.com lookup hostname to resolve name to ip address and viceversanslookup www.example.com lookup hostname to resolve name to ip address and viceversawhois www.example.com lookup on Whois database JPS工具jps(Java Virtual Machine Process Status Tool)是JDK 1.5提供的一个显示当前所有java进程pid的命令,简单实用,非常适合在linux/unix平台上简单察看当前java进程的一些简单情况。 我想很多人都是用过unix系统里的ps命令,这个命令主要是用来显示当前系统的进程情况,有哪些进程,及其 id。 jps 也是一样,它的作用是显示当前系统的java进程情况,及其id号。我们可以通过它来查看我们到底启动了几个java进程(因为每一个java程序都会独占一个java虚拟机实例),和他们的进程号(为下面几个程序做准备),并可通过opt来查看这些进程的详细启动参数。 使用方法:在当前命令行下打 jps(需要JAVA_HOME,没有的话,到改程序的目录下打) 。 jps存放在JAVA_HOME/bin/jps,使用时为了方便请将JAVA_HOME/bin/加入到Path. $> jps23991 Jps23789 BossMain23651 Resin 比较常用的参数: -q 只显示pid,不显示class名称,jar文件名和传递给main 方法的参数$> jps -q286802378923651 -m 输出传递给main 方法的参数,在嵌入式jvm上可能是null $> jps -m28715 Jps -m23789 BossMain23651 Resin -socketwait 32768 -stdout /data/aoxj/resin/log/stdout.log -stderr /data/aoxj/resin/log/stderr.log -l 输出应用程序main class的完整package名 或者 应用程序的jar文件完整路径名 $> jps -l28729 sun.tools.jps.Jps23789 com.asiainfo.aimc.bossbi.BossMain23651 com.caucho.server.resin.Resin -v 输出传递给JVM的参数 $> jps -v23789 BossMain28802 Jps -Denv.class.path=/data/aoxj/bossbi/twsecurity/java/trustwork140.jar:/data/aoxj/bossbi/twsecurity/java/:/data/aoxj/bossbi/twsecurity/java/twcmcc.jar:/data/aoxj/jdk15/lib/rt.jar:/data/aoxj/jd k15/lib/tools.jar -Dapplication.home=/data/aoxj/jdk15 -Xms8m23651 Resin -Xss1m -Dresin.home=/data/aoxj/resin -Dserver.root=/data/aoxj/resin -Djava.util.logging.manager=com.caucho.log.LogManagerImpl - Djavax.management.builder.initial=com.caucho.jmx.MBeanServerBuilderImpl sudo jps看到的进程数量最全 jps 192.168.0.77 列出远程服务器192.168.0.77机器所有的jvm实例,采用rmi协议,默认连接端口为1099 (前提是远程服务器提供jstatd服务) 注:jps命令有个地方很不好,似乎只能显示当前用户的java进程,要显示其他用户的还是只能用unix/linux的ps命令。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[{"name":"Linux命令","slug":"Linux命令","permalink":"http://example.com/tags/Linux%E5%91%BD%E4%BB%A4/"}]},{"title":"Linux下的性能检测工具","slug":"Linux性能检测工具","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Linux性能检测工具/","link":"","permalink":"http://example.com/2020/04/21/Linux%E6%80%A7%E8%83%BD%E6%A3%80%E6%B5%8B%E5%B7%A5%E5%85%B7/","excerpt":"","text":"这是一个叫做htop的工具,我看着还挺好用的,今天就来记录一波。首先,安装软件包: 1pacman -S htop 在终端输入htop来使用它。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"性能检测工具","slug":"性能检测工具","permalink":"http://example.com/tags/%E6%80%A7%E8%83%BD%E6%A3%80%E6%B5%8B%E5%B7%A5%E5%85%B7/"}]},{"title":"Linux设置静态ip","slug":"linux设置静态ip","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/linux设置静态ip/","link":"","permalink":"http://example.com/2020/04/21/linux%E8%AE%BE%E7%BD%AE%E9%9D%99%E6%80%81ip/","excerpt":"","text":"使用NetworkManager的nmtui图形化工具,在命令行输入: 1nmtui 以打开nmtui界面。注意,使用此命令需安装NetworkManager。进入界面后选择”编辑连接”,选择你要更改的网络,用键盘选择右边的”编辑”选项。然后会跳转到编辑页面,在ipv4那里(我需要设置的是ipv4的静态ip)将”自动”调整为”手动”,打开”显示”,输入静态ip,确认,退出。使用<font color=”red”iplink来查看对应网卡的ip是否更改,如果没有更改,那就重启一下。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"git fatal远程origin已经存在","slug":"OriginError","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/OriginError/","link":"","permalink":"http://example.com/2020/04/21/OriginError/","excerpt":"","text":"今天把博客迁到linux系统后连接仓库的时候出现了错误: fatal: 远程origin已经存在。下面是解决方案: 1git remote rm origin 然后重新remote即可。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"git","slug":"blog/git","permalink":"http://example.com/categories/blog/git/"}],"tags":[{"name":"git","slug":"git","permalink":"http://example.com/tags/git/"}]},{"title":"使用matplotlib绘制折线图","slug":"matplotlib绘折线图","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/matplotlib绘折线图/","link":"","permalink":"http://example.com/2020/04/21/matplotlib%E7%BB%98%E6%8A%98%E7%BA%BF%E5%9B%BE/","excerpt":"1.基本绘图使用matplotlib库绘制折线图,由于很简单,所以下面直接上代码: 123456789101112131415from matplotlib import pyplot# 数据在x轴位置,是一个可迭代对象x = range(2, 26, 2)# 数据在y轴的位置y = [15, 13, 14, 5, 17, 20, 25, 26, 26, 24, 22, 18]# x和Y的长度必须相等,如果不相等会报错print(len(x))print(len(y))# 绘图pyplot.plot(x, y)# 展示pyplot.show() 效果图如下:","text":"1.基本绘图使用matplotlib库绘制折线图,由于很简单,所以下面直接上代码: 123456789101112131415from matplotlib import pyplot# 数据在x轴位置,是一个可迭代对象x = range(2, 26, 2)# 数据在y轴的位置y = [15, 13, 14, 5, 17, 20, 25, 26, 26, 24, 22, 18]# x和Y的长度必须相等,如果不相等会报错print(len(x))print(len(y))# 绘图pyplot.plot(x, y)# 展示pyplot.show() 效果图如下: 2.绘图常用操作上面的是基本的绘图方法,那么下面是一些常用的操作: 设置图片大小,设置x轴、y轴刻度,以及保存图片12345678910111213141516171819202122from matplotlib import pyplot# 设置图片大小,元组中第一个参数是宽,第二个参数是高,dpi是清晰度pyplot.figure(figsize=(20, 8), dpi=80)# 数据在x轴位置,是一个可迭代对象x = range(2, 26, 2)# 数据在y轴的位置y = [15, 13, 14, 5, 17, 20, 25, 26, 26, 24, 22, 18]# x和Y的长度必须相等print(len(x))print(len(y))# x轴的刻度xtick = [i/2 for i in range(0, 49)]pyplot.xticks(xtick[::3])# y轴的刻度pyplot.yticks(range(min(y), max(y) + 1))# 绘图pyplot.plot(x, y)# 保存图片,必须先绘制再保存pyplot.savefig('./pic.png') 那么下面是效果图: 旋转字体和设置轴标签使用rotation参数可以旋转字体。 如果想要设置标签,可以使用: xlabel和ylabel,如: 12345678910111213141516171819from matplotlib import pyplotimport randomfont_style = "Kaiti"# a=[random.randint(20, 35) for i in range(120)],a表示10点到12点的每一分钟的气温,如何绘制折线图观察每分钟气温的变化情况?x = list(range(0, 120))y = [random.randint(20, 35) for i in range(120)]pyplot.figure(figsize=(20, 8), dpi=80)x_ticks = ["{}点:{}分".format(i, k) for i in range(10, 12) for k in range(0, 60)]# fontproperties指定字体,fontsize指定大小,rotation旋转pyplot.xticks(x[:: 10], x_ticks[:: 10], fontproperties=font_style, fontsize=10, rotation=0)# xlabel设置x轴标签pyplot.xlabel('时间', fontproperties=font_style, rotation=90)# ylabel设置y轴标签pyplot.ylabel('温度', fontproperties=font_style, rotation=0)pyplot.plot(x, y)pyplot.savefig('./template.png') 下面是效果图: 显示网格只需加上: 1pyplot.grid() 即可。 默认绘制的横线数量为y轴的刻度的个数,竖线为x轴的刻度的个数。 若是认为线条过于显眼,可以加上参数: alpha,如: 1pyplot.grid(alpha=0.4) 这个值位于0到1之间。不过如果设置为0,那么线条将不显示(因为完全透明了) 下面是代码: 1234567891011121314151617181920from matplotlib import pyplota = [1, 0, 1, 1, 2, 4, 3, 2, 3, 4, 4, 5, 6, 5, 4, 3, 3, 1, 1, 1]x_names = []font_style = 'Kaiti'for i in range(11, 31): x_names.append('{}岁'.format(i))print(x_names)pyplot.figure(figsize=(20, 8), dpi=80)pyplot.xticks(range(11, 31), x_names, fontproperties=font_style)pyplot.yticks(a, [str(i) + '个'for i in a], fontproperties=font_style)pyplot.xlabel('年龄(岁)', fontproperties=font_style)pyplot.ylabel('数量(个)', fontproperties=font_style, rotation=30)pyplot.grid()pyplot.plot(range(11, 31), a)pyplot.savefig('test_it.png') 下面是效果图: 绘制多条折线绘制多条折线十分的简单,只要再来一个pyplot()即可。下面是代码: 123456789101112131415161718192021from matplotlib import pyplota = [1, 0, 1, 1, 2, 4, 3, 2, 3, 4, 4, 5, 6, 5, 4, 3, 3, 1, 1, 1]b = [1, 0, 1, 3, 2, 0, 6, 2, 10, 4, 4, 5, 6, 5, 4, 3, 3, 1, 9, 0]x_names = []font_style = 'Kaiti'for i in range(11, 31): x_names.append('{}岁'.format(i))print(x_names)pyplot.figure(figsize=(20, 8), dpi=80)pyplot.xticks(range(11, 31), x_names, fontproperties=font_style)pyplot.yticks(a, [str(i) + '个' for i in a], fontproperties=font_style)pyplot.xlabel('年龄(岁)', fontproperties=font_style)pyplot.ylabel('数量(个)', fontproperties=font_style, rotation=30)pyplot.grid(alpha=0.1)pyplot.plot(range(11, 31), a)pyplot.plot(range(11, 31), b)pyplot.savefig('test_it.png') 下面是效果: 添加图例(标注线条)有的时候无法区分这些线条代表了什么,那么下面是标注线条的方法,你只需要在pyplot.plot(x, y)中加上一个参数label即可。 如: 1pyplot.plot(range(11, 31), a, label='Myself') 下面是全部代码: 12345678910111213141516171819202122from matplotlib import pyplota = [1, 0, 1, 1, 2, 4, 3, 2, 3, 4, 4, 5, 6, 5, 4, 3, 3, 1, 1, 1]b = [1, 0, 1, 3, 2, 0, 6, 2, 10, 4, 4, 5, 6, 5, 4, 3, 3, 1, 9, 0]x_names = []font_style = 'Kaiti'for i in range(11, 31): x_names.append('{}岁'.format(i))print(x_names)pyplot.figure(figsize=(20, 8), dpi=80)pyplot.xticks(range(11, 31), x_names, fontproperties=font_style)pyplot.yticks(a, [str(i) + '个' for i in a], fontproperties=font_style)pyplot.xlabel('年龄(岁)', fontproperties=font_style)pyplot.ylabel('数量(个)', fontproperties=font_style, rotation=30)pyplot.grid(alpha=0.1)pyplot.plot(range(11, 31), a, label='Myself')pyplot.plot(range(11, 31), b, label="MyClassmate")pyplot.legend()pyplot.savefig('test_it.png') 下面是效果: 图例显示中文如果想要使图例显示中文,你需要在pyplot.legend()中添加参数prop,并且这次传的值不能再是字符串了,应该是一个对象,请看代码: 123from matplotlib import font_managerfont_style = font_manager.FontProperties(fname='C:/Windows/Fonts/simkai.ttf') fname为你的字体的路径。 然后是让图例显示中文: 1pyplot.legend(prop=font_style) 下面是全部代码: 12345678910111213141516171819202122from matplotlib import pyplotfrom matplotlib import font_managera = [1, 0, 1, 1, 2, 4, 3, 2, 3, 4, 4, 5, 6, 5, 4, 3, 3, 1, 1, 1]b = [1, 0, 1, 3, 2, 0, 6, 2, 10, 4, 4, 5, 6, 5, 4, 3, 3, 1, 9, 0]x_names = []font_style = font_manager.FontProperties(fname='C:/Windows/Fonts/simkai.ttf')for i in range(11, 31): x_names.append('{}岁'.format(i))print(x_names)pyplot.figure(figsize=(20, 8), dpi=80)pyplot.xticks(range(11, 31), x_names, fontproperties=font_style)pyplot.yticks(a, [str(i) + '个' for i in a], fontproperties=font_style)pyplot.xlabel('年龄(岁)', fontproperties=font_style)pyplot.ylabel('数量(个)', fontproperties=font_style, rotation=30)pyplot.grid(alpha=0.1)pyplot.plot(range(11, 31), a, label='我')pyplot.plot(range(11, 31), b, label="我的同桌")pyplot.legend(prop=font_style)pyplot.savefig('test_it.png') 下面是效果图: 可以看到,显示出中文了。 更改图例位置在pyplot.legend()中加入loc参数,如loc=”upper right”是右上,loc=”upper left”是左上,loc=”center right”是右中,loc=”lower left”是左下。可以通过查看源码来获取参数。 这里就不举图片例子了。 更改线条样式改变颜色可以使用如’red’一样的单词,也可以使用16进制。 1pyplot.plot(range(11, 31), a, label='我', color='#00FF00') 改变线条样式 ‘-‘ solid line style‘–’ dashed line style‘-.’ dash-dot line style‘:’ dotted line style 如: 1pyplot.plot(range(11, 31), b, label="我的同桌", linestyle='--') 改变线条粗细设置linewidth参数,如: 1pyplot.plot(range(11, 31), b, label="我的同桌", linewidth=30) 3.刻度使用字符串并用中文显示先上全部代码: 12345678910111213from matplotlib import pyplotimport random# a=[random.randint(20, 35) for i in range(120)],a表示10点到12点的每一分钟的气温,如何绘制折线图观察每分钟气温的变化情况?x = list(range(0, 120))y = [random.randint(20, 35) for i in range(120)]pyplot.figure(figsize=(20, 8), dpi=80)# 列表推导式x_ticks = ["{}点:{}分".format(i, k) for i in range(10, 12) for k in range(0, 60)]pyplot.xticks(x[:: 10], x_ticks[:: 10], fontproperties='Kaiti', fontsize=10)pyplot.plot(x, y)pyplot.savefig('./template.png') 可以看到,在调用pyplot.xticks时传入了两个列表参数,一个是x, 一个是x_ticks,后面两个参数下面再解释,我们先来说这个x参数和x_ticks参数:如果想要使用字符串刻度,那么需要在传入元素是字符串的列表参数的时候,将元素是数字的列表的原x轴也传入进去。记得保证两者长度相同,不然显示出来的有可能会混乱(比如该显示1的地方显示3,并且乱了你还看不出来)。 1pyplot.xticks(元素是数字的列表, 元素是字符串的列表) fontproperties是指定字体,我这里使用的是楷体,似乎只要字体在Windows/Fonts 下就不需要指定字体路径了。fontsize是字体大小。 运行效果:","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"},{"name":"数据分析","slug":"blog/Python/数据分析","permalink":"http://example.com/categories/blog/Python/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"matplotlib","slug":"matplotlib","permalink":"http://example.com/tags/matplotlib/"},{"name":"折线图","slug":"折线图","permalink":"http://example.com/tags/%E6%8A%98%E7%BA%BF%E5%9B%BE/"}]},{"title":"nask汇编指令","slug":"nask汇编指令","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/nask汇编指令/","link":"","permalink":"http://example.com/2020/04/21/nask%E6%B1%87%E7%BC%96%E6%8C%87%E4%BB%A4/","excerpt":"","text":"DB指令是”define byte”的缩写,也就是往文件里直接写入1个字节的指令,小写的db也一样。只要有了DB指令,就可以做出任何数据。可以直接用它写字符串。在写字符串的时候,汇编语言会自动的查找字符串中每一个字符所对应的编码,然后把它们一个字节一个字节地排列起来。这个功能非常方便,也就是说,当我们想要变更输出信息的时候,就再也不用自己去查字符编码了。 RESB指令是”reverse byte”的略写,如果想要从现在的地址开始空出10个字节来,就可以写成RESB 10,意思是我们预约了这10个字节(可以想象成在对号入座的火车里,预定了10个连号座位)。而且nask不仅仅是把指定的地址空出来,它还会在空出来的地址上自动填入0x00。所以用这个指令可以输出很多个0x00。 RESB ox1fe-$中这个美元符号是个变量,可以告诉我们这一行现在的字节数。在程序里,前面已经输出了132字节,所以这里的$就是132,因此nask先用0x1fe减去132,得出378这一结果,然后连续输出378个字节的0x00。 那我们为什么不直接写378,而非要用$呢?只是因为如果将显示信息从”hello world”变成”thsi is a pen.”的话,中间要输出0x00的字节数也会随之变化。换句话说,我们必须保证软盘的第510字节(即第0x1fe字节)开始的地方是55 AA。如果在程序中使用美元符号($)的话,汇编语言会自动计算需要输入多少个00,我们也就可以很轻松的改写输出信息了。 数字前面加上0x,就成了十六进制数,不加0x,就是十进制数。这一点和C语言是一样的。 DW指令和DD指令是”define word”和”define double-word”的缩写,是DB指令的”堂兄弟”。word的本意是”单词”,但在计算机汇编语言的世界里,word指的是”16位”的意思,也就是2个字节。”double-word”是”32位”的意思,也就是4个字节。 ;是注释命令。 遗憾的是现在还不能显示汉字。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"30days","slug":"blog/30days","permalink":"http://example.com/categories/blog/30days/"}],"tags":[{"name":"30days","slug":"30days","permalink":"http://example.com/tags/30days/"},{"name":"nask","slug":"nask","permalink":"http://example.com/tags/nask/"}]},{"title":"解决Arch安装软件包提示无效或已损坏的软件包 (PGP 签名)","slug":"PGP签名损坏","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/PGP签名损坏/","link":"","permalink":"http://example.com/2020/04/21/PGP%E7%AD%BE%E5%90%8D%E6%8D%9F%E5%9D%8F/","excerpt":"","text":"解决Arch Linux下安装软件包时出现:无效或已损坏的软件包(PGP)签名 只需要一条命令: 1pacman -S archlinuxcn-keyring 执行这条命令似乎是因为配置了archlinuxcn源。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"pip升级失败与生成requirements.txt文件失败解决方案","slug":"pip升级失败与生成requirements失败","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/pip升级失败与生成requirements失败/","link":"","permalink":"http://example.com/2020/04/21/pip%E5%8D%87%E7%BA%A7%E5%A4%B1%E8%B4%A5%E4%B8%8E%E7%94%9F%E6%88%90requirements%E5%A4%B1%E8%B4%A5/","excerpt":"pip升级失败解决方案今天使用pip安装模块的时候,提示我pip有新版本了,我就随手升级了一下: 1pip install --upgrade pip 结果安装失败了。 123ERROR: Could not install packages due to an EnvironmentError: [WinError 5] 拒绝访问。: 'd:\\\\virtual environment\\\\for_django\\\\scripts\\\\pip.exe'Consider using the `--user` option or check the permissions. 然后输入pip,报错: 1ModuleNotFoundError: No module named 'pip' 啊这,这可怎么办? 那么下面就是解决方法","text":"pip升级失败解决方案今天使用pip安装模块的时候,提示我pip有新版本了,我就随手升级了一下: 1pip install --upgrade pip 结果安装失败了。 123ERROR: Could not install packages due to an EnvironmentError: [WinError 5] 拒绝访问。: 'd:\\\\virtual environment\\\\for_django\\\\scripts\\\\pip.exe'Consider using the `--user` option or check the permissions. 然后输入pip,报错: 1ModuleNotFoundError: No module named 'pip' 啊这,这可怎么办? 那么下面就是解决方法: 首先,输入命令: 1python -m ensurepip 安装成功: 123456Requirement already satisfied: setuptools in d:\\virtual environment\\for_django\\lib\\site-packages (46.1.3)Collecting pipInstalling collected packages: pipSuccessfully installed pip-10.0.1 然后,输入pip,提示: 1ModuleNotFoundError: No module named 'pip._internal.cli' 不急,我们接着输入下一条命令: 1python -m pip install --upgrade pip 成功: 12345Installing collected packages: pip Found existing installation: pip 10.0.1 Uninstalling pip-10.0.1: Successfully uninstalled pip-10.0.1Successfully installed pip-20.1.1 这时输入pip,成功提示: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849Usage: pip <command> [options]Commands: install Install packages. download Download packages. uninstall Uninstall packages. freeze Output installed packages in requirements format. list List installed packages. show Show information about installed packages. check Verify installed packages have compatible dependencies. config Manage local and global configuration. search Search PyPI for packages. cache Inspect and manage pip's wheel cache. wheel Build wheels from your requirements. hash Compute hashes of package archives. completion A helper command used for command completion. debug Show information useful for debugging. help Show help for commands.General Options: -h, --help Show help. --isolated Run pip in an isolated mode, ignoring environment variables and user configuration. -v, --verbose Give more output. Option is additive, and can be used up to 3 times. -V, --version Show version and exit. -q, --quiet Give less output. Option is additive, and can be used up to 3 times (corresponding to WARNING, ERROR, and CRITICAL logging levels). --log <path> Path to a verbose appending log. --proxy <proxy> Specify a proxy in the form [user:passwd@]proxy.server:port. --retries <retries> Maximum number of retries each connection should attempt (default 5 times). --timeout <sec> Set the socket timeout (default 15 seconds). --exists-action <action> Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort. --trusted-host <hostname> Mark this host or host:port pair as trusted, even though it does not have valid or any HTTPS. --cert <path> Path to alternate CA bundle. --client-cert <path> Path to SSL client certificate, a single file containing the private key and the certificate in PEM format. --cache-dir <dir> Store the cache data in <dir>. --no-cache-dir Disable the cache. --disable-pip-version-check Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. --no-color Suppress colored output --no-python-version-warning Silence deprecation warnings for upcoming unsupported Pythons. pip升级成功~ 生成requirements.txt文件失败解决方案升级成功后,我想生成个requirements.txt文件来记录一下我安装的模块,结果又报错了: 123(for_django) D:\\use_for_study\\None\\django123>pip freeze > requirements.txtWARNING: Could not generate requirement for distribution -ip 20.0.2 (d:\\virtual environment\\for_django\\lib\\site-packages): Parse error at "'-ip==20.'": Expected W:(abcd...) 这时打开我的虚拟环境目录,D:\\virtual environment\\for_django,进入lib文件夹下的site-packages文件夹,找到一个文件名里含有ip-20的文件夹(若是WARNING里ip==19.则找到文件名里含有ip-19的文件夹),删掉。 然后再次尝试生成文件: 1(for_django) D:\\use_for_study\\None\\django123>pip freeze >requirements.txt 成功生成requirements.txt文件~","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"踩坑","slug":"踩坑","permalink":"http://example.com/tags/%E8%B8%A9%E5%9D%91/"},{"name":"pip","slug":"pip","permalink":"http://example.com/tags/pip/"}]},{"title":"Linux下Python3安装twisted","slug":"python3安装twisted","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/python3安装twisted/","link":"","permalink":"http://example.com/2020/04/21/python3%E5%AE%89%E8%A3%85twisted/","excerpt":"","text":"一开始输入了: 1pip3 install twisted 报错了,后来百度了一下,发现得有root权限才行: 1sudo pip3 install twisted 这样就安装上了。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"Pyhon3安装turtle","slug":"python3安装turtle","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/python3安装turtle/","link":"","permalink":"http://example.com/2020/04/21/python3%E5%AE%89%E8%A3%85turtle/","excerpt":"","text":"参考python3.7安装turtle。先下载好turtle包:Turtle。解压缩,修改setup.py文件,将”except ValueError, ve”修改为:”except (ValueError, ve)”,然后安装twisted: 1sudo pip3 install twisted 然后使用: 1pip3 install -e turtle-0.0.2(文件夹名) 稍等一会儿,turtle就安装好了。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"使用python venv创建虚拟环境","slug":"python_venv","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/python_venv/","link":"","permalink":"http://example.com/2020/04/21/python_venv/","excerpt":"","text":"首先,创建虚拟环境: 1python3 -m venv your_env_name 然后,激活环境:Linux下: 1source your_env_name/bin/activate 因为我自己未尝试的不会写出来,所以这里暂时没有Windows下的激活方法。 退出虚拟环境: 1deactivate","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"生成器和迭代器","slug":"Python生成器和迭代器","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Python生成器和迭代器/","link":"","permalink":"http://example.com/2020/04/21/Python%E7%94%9F%E6%88%90%E5%99%A8%E5%92%8C%E8%BF%AD%E4%BB%A3%E5%99%A8/","excerpt":"","text":"转载自v3u.cn。 1.迭代的概念上一次输出的结果为下一次输入的初始值,重复的过程称为迭代,每次重复即一次迭代,并且每次迭代的结果是下一次迭代的初始值 2.可迭代的对象内置iter方法的,都是可迭代的对象。 list是可迭代对象,dict是可迭代对象,set也是可迭代对象。 3.迭代器1.为什么要有迭代器?对于没有索引的数据类型,必须提供一种不依赖索引的迭代方式。 2.迭代器定义:迭代器:可迭代对象执行iter方法,得到的结果就是迭代器,迭代器对象有next方法 它是一个带状态的对象,他能在你调用next()方法的时候返回容器中的下一个值,任何实现了iter和next()方法的对象都是迭代器,iter返回迭代器自身,next返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常 二、生成器1.定义生成器(generator)是一个特殊的迭代器,它的实现更简单优雅,yield是生成器实现next()方法的关键。它作为生成器执行的暂停恢复点,可以对yield表达式进行赋值,也可以将yield表达式的值返回。 也就是说,yield是一个语法糖,内部实现支持了迭代器协议,同时yield内部是一个状态机,维护着挂起和继续的状态。 yield的功能:1.相当于为函数封装好iter和next 2.return只能返回一次值,函数就终止了,而yield能返回多次值,每次返回都会将函数暂停,下一次next会从上一次暂停的位置继续执行 为什么说生成器是一种迭代器?Python 判断一个对象是否是迭代器的标准是看这个对象是否遵守迭代器协议 ,判断一个对象是否遵守迭代器协议主要看两个方面: 1对象首先得实现 iter 和 next 方法 2其次iter 方法必须返回它自己 而生成器恰好满足了这两个条件(可以自己写个生成器,然后调用生成器的这两个方法试试)。我们平常还会经常碰到另外一个概念:可迭代对象。可迭代对象就是可迭代的对象,可迭代的对象就是说我们可以从这个对象拿到一个迭代器。在 Python 中,iter 方法可以帮我们完成这个事情,也就是说,可迭代对象和迭代器满足这样一个关系:iter(iterable) -> iterator。 在 Python 中,list 是个可迭代对象,所以我们经常会写这样的代码: 123>>> l = [1, 2, 3]>>> for element in l:... print(element) 但你想过为什么我们可以这么写吗?为啥在 c 语言里面,我们访问数组元素的时候,必须要通过 index? 因为:list 是个可迭代对象,我们在 Python 中使用 for … in 时,Python 会给我们生成一个迭代器对象,而如上所说:迭代器是个数据流,它可以产生数据,我们一直从里面取数据就好了,而不需要我们在代码中维护 index,Python 已经通过迭代器给我们完成了这个事情。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"python五大排序算法","slug":"Python排序算法","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Python排序算法/","link":"","permalink":"http://example.com/2020/04/21/Python%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/","excerpt":"转载自v3u.cn 插入排序插入排序:插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序;首先将第一个作为已经排好序的,然后每次从后的取出插入到前面并排序; 时间复杂度:O(n²) 空间复杂度:O(1) 稳定性:稳定 1234567def insert_sort(ilist): for i in range(len(ilist)): for j in range(i): if ilist[i] < ilist[j]: ilist.insert(j, ilist.pop(i)) break return ilist 冒泡排序","text":"转载自v3u.cn 插入排序插入排序:插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序;首先将第一个作为已经排好序的,然后每次从后的取出插入到前面并排序; 时间复杂度:O(n²) 空间复杂度:O(1) 稳定性:稳定 1234567def insert_sort(ilist): for i in range(len(ilist)): for j in range(i): if ilist[i] < ilist[j]: ilist.insert(j, ilist.pop(i)) break return ilist 冒泡排序 冒泡排序:它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成 时间复杂度:O(n²) 空间复杂度:O(1) 稳定性:稳定 12345678910def bubble_sort(blist): count = len(blist) for i in range(0, count): for j in range(i + 1, count): if blist[i] > blist[j]: blist[i], blist[j] = blist[j], blist[i] return blistblist = bubble_sort([4,5,6,7,3,2,6,9,8])print(blist) 快排快速排序:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列 时间复杂度:O(nlog₂n) 空间复杂度:O(nlog₂n) 稳定性:不稳定 12345678910def quick_sort(qlist): if qlist == []: return [] else: qfirst = qlist[0] qless = quick_sort([l for l in qlist[1:] if l < qfirst]) qmore = quick_sort([m for m in qlist[1:] if m >= qfirst]) return qless + [qfirst] + qmoreqlist = quick_sort([4,5,6,7,3,2,6,9,8]) 选择排序选择排序:第1趟,在待排序记录r1 ~ r[n]中选出最小的记录,将它与r1交换;第2趟,在待排序记录r2 ~ r[n]中选出最小的记录,将它与r2交换;以此类推,第i趟在待排序记录r[i] ~ r[n]中选出最小的记录,将它与r[i]交换,使有序序列不断增长直到全部排序完毕 时间复杂度:O(n²) 空间复杂度:O(1) 稳定性:不稳定 12345678910def select_sort(slist): for i in range(len(slist)): x = i for j in range(i, len(slist)): if slist[j] < slist[x]: x = j slist[i], slist[x] = slist[x], slist[i] return slistslist = select_sort([4,5,6,7,3,2,6,9,8]) 归并排序归并排序:采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并 时间复杂度:O(nlog₂n) 空间复杂度:O(1) 稳定性:稳定 1234567891011121314151617181920212223def merge_sort(array): def merge_arr(arr_l, arr_r): array = [] while len(arr_l) and len(arr_r): if arr_l[0] <= arr_r[0]: array.append(arr_l.pop(0)) elif arr_l[0] > arr_r[0]: array.append(arr_r.pop(0)) if len(arr_l) != 0: array += arr_l elif len(arr_r) != 0: array += arr_r return array def recursive(array): if len(array) == 1: return array mid = len(array) // 2 arr_l = recursive(array[:mid]) arr_r = recursive(array[mid:]) return merge_arr(arr_l, arr_r) return recursive(array)","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"redis开启事务","slug":"redis事务","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/redis事务/","link":"","permalink":"http://example.com/2020/04/21/redis%E4%BA%8B%E5%8A%A1/","excerpt":"","text":"开启事务:(multi)命令行进入队列执行事务(exec)取消事务,放弃事务中的命令(discard)watch 监控事务下面是一组成功的事务的实际操作: 12345678910127.0.0.1:6379> multiOK127.0.0.1:6379> set object pythonQUEUED127.0.0.1:6379> get objectQUEUED127.0.0.1:6379> exec1) OK2) "python""" 可以看到,输入set命令和get命令后只是加入了队列,待到执行事务后才按照顺序执行。 下面也是一组实际操作,这次输入了错误的命令参数: 123456789101112127.0.0.1:6379> multiOK127.0.0.1:6379> set new fireQUEUED127.0.0.1:6379> get new fire(error) ERR wrong number of arguments for 'get' command127.0.0.1:6379> exec(error) EXECABORT Transaction discarded because of previous errors.127.0.0.1:6379> get new(nil)127.0.0.1:6379> 下面还是一组实际操作,这次输入了正确的指令,但是该指令用在了错误的地方: 12345678910111213127.0.0.1:6379> multiOK127.0.0.1:6379> set fire noneQUEUED127.0.0.1:6379> incr fireQUEUED127.0.0.1:6379> get fireQUEUED127.0.0.1:6379> exec1) OK2) (error) ERR value is not an integer or out of range3) "none"127.0.0.1:6379> 可以看到,指令执行了,不过在对应的错误指令的位置报了错。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"乱七八糟","slug":"blog/乱七八糟","permalink":"http://example.com/categories/blog/%E4%B9%B1%E4%B8%83%E5%85%AB%E7%B3%9F/"}],"tags":[]},{"title":"Vim文档","slug":"Vim文档","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Vim文档/","link":"","permalink":"http://example.com/2020/04/21/Vim%E6%96%87%E6%A1%A3/","excerpt":"","text":"有了这条地址应该就不用满网找vim教程了:sourceforge.net.(2021/03/04 9:35因为该博文当初未设置时间,导致了”浮出水面”的情况, 故此将文章时间设置为2020/12/01)。本篇完。","categories":[],"tags":[{"name":"vim","slug":"vim","permalink":"http://example.com/tags/vim/"}]},{"title":"使用vim编写C语言文件","slug":"vim编译C语言","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/vim编译C语言/","link":"","permalink":"http://example.com/2020/04/21/vim%E7%BC%96%E8%AF%91C%E8%AF%AD%E8%A8%80/","excerpt":"","text":"借鉴自https://blog.csdn.net/u011182346/article/details/84075246。1.安装gcc,不会安装的话建议百度。2.在终端命令行中输入: 1vim test.c 这条命令的作用是创建或编辑test.c文件。然后在其中输入c语言代码,例如: 123456#include <stdio.h>int main(){printf("Hello World\\n");return 0;} 应该没输错吧,这段代码就是输出一个Hello World字符串,由于我好久没写C了,所以连这个都记得不太清了,不过我确实是运行出来了。按下Esc键,然后输入:wq,作用是写入并退出。然后输入: 1gcc test.c 会提示你编译了文件。接着输入: 1./a.out 来执行。(2021/02/05: 这篇文章应该是我在2020年7月份到8月份之间写的,不知道当时的我为什么会发这篇文章。为了防止因为没有date导致日后修整博客时日期出错,将其日期设置为2020/08/01, 同时将分类从Linux移动至vim)","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vim","slug":"blog/vim","permalink":"http://example.com/categories/blog/vim/"}],"tags":[{"name":"vim","slug":"vim","permalink":"http://example.com/tags/vim/"}]},{"title":"Vue上传进度条","slug":"vue上传进度条","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/vue上传进度条/","link":"","permalink":"http://example.com/2020/04/21/vue%E4%B8%8A%E4%BC%A0%E8%BF%9B%E5%BA%A6%E6%9D%A1/","excerpt":"目的是展示进度条: 12<input type="file" @change="upload_qiniu"><Progress :percent="load_int" color="green"></Progress>","text":"目的是展示进度条: 12<input type="file" @change="upload_qiniu"><Progress :percent="load_int" color="green"></Progress> 12345678910111213141516171819202122232425262728293031323334353637383940<script> export default( data(){ return { load_percent: '', load_int: 0 } }, methods:{ upload_qiniu:function(e){ // 以下为七牛云上传文件 var file = e.target.files[0]; let param = new FormData; param.append('file', file, file.name); alert(file.name) param.append('token', this.token); const axios_qiniu = this.axios.create({withCredentials: false}); axios_qiniu({ url: 'http://up-z2.qiniu.com', method: 'post', data: param, timeout: 40000, // 上传过程中的方法 onUploadProgress:(e)=>{ var complete = (e.loaded / e.total) // 处理美化 if (complete < 1){} this.load_percent = (complete * 100).toFixed(2) + '%'; this.load_int = parseInt((complete * 100).toFixed(2)); } }).then(res=>{ // 手动赋值 this.load_percent = '100%'; this.load_int = 100; console.log(res.data); }) }, } )</script> 这样上传文件后进度条就会前进,在此说明一下这个Progress双标签使用的是heyui。推动进度关键点在于load_percent,它随着上传的进度而动(然而它的值并不是很准),为了防止文件上传完了而进度条还没跑完这一令人无语的现象出现,在文件上传完后将进度条设置为100,这样进度条就可以完美的运行了!!","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"},{"name":"上传进度条","slug":"上传进度条","permalink":"http://example.com/tags/%E4%B8%8A%E4%BC%A0%E8%BF%9B%E5%BA%A6%E6%9D%A1/"}]},{"title":"vue一次上传多个文件","slug":"Vue一次上传多个文件","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Vue一次上传多个文件/","link":"","permalink":"http://example.com/2020/04/21/Vue%E4%B8%80%E6%AC%A1%E4%B8%8A%E4%BC%A0%E5%A4%9A%E4%B8%AA%E6%96%87%E4%BB%B6/","excerpt":"Vue一次发送给后台多个文件,其实很简单: 在data里定义一个变量,然后给该变量赋值 new FormData();","text":"Vue一次发送给后台多个文件,其实很简单: 在data里定义一个变量,然后给该变量赋值 new FormData(); 然后就可以使用append()方法往里面添加添加数据了。 1234567<script>export default { data () { return { form_data: new FormData() }</script> 就这样,就定义好了一个随时可以添加内容的formdata,太方便了。 顺便写上js根据id取得文件的方法: 1var fire = document.getElementById('your_id').files[0] 通过这样的方法取得的文件,查看文件名需使用 fire.name 。也可以使用console.log(fire)来查看它的其它属性。 发送文件我这里使用的axios,需要安装: 1npm install axios --save 然后是我使用axios的代码: 12345678910111213// 给formdata添加内容this.form_data.append('name', this.good_name);this.form_data.append('price', this.price);this.form_data.append('params', params);this.form_data.append('cid', this.default_category);// 这里我的axios已经在main.js中注册了,所以可以使用,若是没有在mian.js中注册,需要在script标签下使用import axios from "axios", url是要提交到的地址,method中可以选择提交方式,如get, post等,data是提交的内容。之后的.then(res=>{})是提交后接到响应执行的内容,可以不写。this.axios({ url: 'http://127.0.0.1:8000/insert_goods/', method: 'post', data: this.form_data }).then(res => { this.$Message(res.data.message) }) 然后下面是我Django接收参数的代码: 12345678910# 添加商品class InsertGoods(APIView): def post(self, request): # 接收参数 name = request.POST.get('name', None) price = request.POST.get('price', None) params = request.POST.get('params', None) cid = request.POST.get('cid', None) video = request.FILES.get('video', None) return Response({'code': 200, 'msg': params}) 这里用的是restframework里的APIView和Response,若是没有使用restframework则在APIView的地方使用django.views.View,在Response那里使用django.http.JsonResponse。这里的name = request.POST.get(‘name’, None)的含义是:从post请求中得到name参数,并将其赋值给变量name,若是没有name参数,那么便给name一个默认的值,该值为None。video = request.FILES.get(‘video’, None)的意思是: 从请求中得到一个叫做video的文件(注意,这里使用的是request.FILES.get(),是用来接收文件的),并将其赋值给一个叫做video的变量,若是没有得到文件,便默认给video一个默认值,该默认值为None。 可能有的朋友有点疑惑,为什么要给默认值None?这个要视应用场景而定。另外说一下这里默认值None的作用,只需要一段代码我想大家就会明白: 123456noe = Noneif noe: print("It's not null")else: print("It's null") 输出结果为It’s null 。这样就明白了吧!默认给None可以方便判断(其实也是因为手懒不想写 == )。 本篇完结~(总感觉写的一点用也没有呢)","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"},{"name":"上传文件","slug":"上传文件","permalink":"http://example.com/tags/%E4%B8%8A%E4%BC%A0%E6%96%87%E4%BB%B6/"}]},{"title":"Vue实现画中画播放视频","slug":"Vue实现小窗口播放","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Vue实现小窗口播放/","link":"","permalink":"http://example.com/2020/04/21/Vue%E5%AE%9E%E7%8E%B0%E5%B0%8F%E7%AA%97%E5%8F%A3%E6%92%AD%E6%94%BE/","excerpt":"使用vue实现画中画(小窗口播放)的功能 html页面代码: 12<video src="http://q9ktbyjw1.bkt.clouddn.com/DARK%20SOULS%20III%202019_4_28%2011_16_57.mp4" id='video' width="100px" height='100px' controls="controls" autoplay='autoplay' muted loop='loop'>您的浏览器不支持video标签</video><button @click="into">{{mymsg}}</button>","text":"使用vue实现画中画(小窗口播放)的功能 html页面代码: 12<video src="http://q9ktbyjw1.bkt.clouddn.com/DARK%20SOULS%20III%202019_4_28%2011_16_57.mp4" id='video' width="100px" height='100px' controls="controls" autoplay='autoplay' muted loop='loop'>您的浏览器不支持video标签</video><button @click="into">{{mymsg}}</button> 参数: 12345data(){return {mymsg: '进入画中画模式'}} 函数: 123456789101112into:function(){if (video !== document.pictureInPictureElement) {// 尝试进入画中画模式video.requestPictureInPicture();this.mymsg = '退出画中画';} else {// 退出画中画document.exitPictureInPicture();this.mymsg = '进入画中画';}} 这样会自动进入画中画模式,进入后会在视频旁边提示退出画中画模式。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"},{"name":"画中画","slug":"画中画","permalink":"http://example.com/tags/%E7%94%BB%E4%B8%AD%E7%94%BB/"}]},{"title":"vue分页","slug":"Vue分页","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Vue分页/","link":"","permalink":"http://example.com/2020/04/21/Vue%E5%88%86%E9%A1%B5/","excerpt":"先来看一下后台的代码:","text":"先来看一下后台的代码: 123456789101112131415161718192021222324252627class GoodList(APIView): def get(self, request): # 从前台获得page的值,如果没有则默认返回3 print(request.GET.get('page', 3)) # 从前台获得page的值,如果没有则默认返回3 order_type = request.GET.get('order_type', 1) # 从前台获得page的值,如果没有则默认返回1,然后减一 start_page = int(request.GET.get('page', 1)) - 1 # 开始的文件位置 start_data = start_page * 4 # 结束的文件位置 end_data = (start_page + 1) * 4 # 从库中读取数据 all1 = Goods.objects.all() # 给出分页的数据 goods = all1[start_data: end_data] # 判断页数是否为整数,如果是整数则不作任何操作,如果不是整数则加一 if int(len(all1) / 4) != len(all1) / 4: # 非整数时的页数 page_long = int(len(all1) / 4) + 1 else: # 整数时页数 page_long = int(len(all1) / 4) # 序列化输出 goodser = GoodsSer(goods, many=True) # 返回值 return Response({'code': 200, 'page_long': page_long, 'data': goodser.data, 'item_num': len(all1)}) 注意,这里的APIView和Response是使用的restframework里的内容,如果没有使用restframework则将APIView替换为django.view.View,将Response替换成django.http.JsonResponse。 接下来是vue的代码。 先是html的代码: 123<div v-for="page in max_page" class="div-float"> <Button @click="click_jump(page)">{{page}}</Button> </div> 这里我使用的是Heyui的Button,和普通的button没有什么区别,只是美观了点。并且此button不能使用v-for,于是我在外面加了个div并使用了foat来使按钮们保持在同一条线上。 12345678910111213141516171819202122232425262728293031323334353637<script> export default{data(){ return { max_page: 1, page_num: 1, }}, method:{ // 根据点击button跳转 click_jump(page) { // 将page_num,即页数赋值为点击的button的页数 this.page_num = page // 获取商品列表 this.get_goods(); }, // 获取商品 get_goods() { // 发送axios请求 this.axios({ // 指定要发送的url url: 'http://127.0.0.1:8000/goods_list', // 选择发送方式 method: 'get', // 指定参数 params: {'page': this.page_num} }).then(res => { // 获取数据 this.goods_list = res.data.data; this.max_page = res.data.page_long; this.pagination.total = res.data.item_num; }) }, } }</script> 逻辑是这样的: 定义一个页数变量,默认值为1, 点击之后给页数变量赋值,然后根据页数变量从后台获取数据。 emmm, 分页逻辑很简单,相信大家看代码就会明白,那么本次博客就到这里了~~~","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[{"name":"Django","slug":"Django","permalink":"http://example.com/tags/Django/"},{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"},{"name":"分页","slug":"分页","permalink":"http://example.com/tags/%E5%88%86%E9%A1%B5/"}]},{"title":"Vue国际化","slug":"Vue国际化","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Vue国际化/","link":"","permalink":"http://example.com/2020/04/21/Vue%E5%9B%BD%E9%99%85%E5%8C%96/","excerpt":"这里所说的国际化,是根据访问者的浏览器语言来更改页面语言。 首先,需要导入一个插件: 1npm install vue-i18n --save","text":"这里所说的国际化,是根据访问者的浏览器语言来更改页面语言。 首先,需要导入一个插件: 1npm install vue-i18n --save 在main的js中注册该组件: 1234// 导入import VueI18n from 'vue-i18n'// 注册Vue.use(VueI18n) 在src目录下新建lang文件夹,在文件夹中新建zh.js和en.js文件 在两个文件中(如果不止是中文和英文的话就再新建对应的文件)输入对应的文本,具体内容按自己需求而定,这是我自己的zh.js文件内容: 1234567891011121314// 双语规范的变量(中文)export const m = { 'index':'美多商城', 'home': '主页', 'shop': '商店', 'page': '页面', 'contact': '联系我们!', 'register': '注册', 'login': '登录', 'logout': '登出', 'welcome': '欢迎您',} 这是我的en.js文件内容: 1234567891011121314// 双语规范的变量(英文)export const m= { 'index': 'Mei Duo SHop', 'home': 'Home', 'shop': 'Shop', 'page': 'Page', 'contact': 'Contact', 'register': 'Register', 'login': 'Login', 'logout': "Logout", 'welcome': 'Welcome!'} 然后再在main.js中加入以下内容,注意要放在上次写的语句下面: 1234567891011// 导入语言包const i18n = new VueI18n({ // 当前默认语言 locale: 'en', // 语言包声明 messages:{ 'zh': require('./lang/zh'), 'en': require('./lang/en'), }}) 在new Vue中加入以下内容: 1i18n 为了方便理解,我把我的new Vue内容写出来: 123456789new Vue({ el: '#app', router, i18n, components: { App }, template: '<App/>', render: h => h(App)}) 这个i18n就是上面定义的const i18n = new VueI18n中的i18n。 到了这里就差不多了,那么接下来该调用了,在需要国际化的地方输入以下内容: 1{{$t('m.register')}} 如: 123<div v-if="username===''"> <router-link to='/register'>{{$t('m.register')}}</router-link> <router-link to='/login'>{{$t('m.login')}}</router-link> 其中 1{{$t()}} 是固定语法,m是之前在js文件中定义的,register和login是之前在js文件中设置的键。注意加上引号。 为了方便访问者选用语言,这里我做出了这样的代码(这里使用了heyui,返回false或者true): 1<h-switch v-model="lang" @change="language_change">中/English</h-switch> 在data中添加lang变量: 12345data(){return {lang: 0}} 这里说明一下lang的值,因为使用的是heyui,所以默认是0,即”false”,不过即便不是使用的heyui,其也有存在的的必要,具体请看以下代码,这是在methods中: 1234567891011language_change:function(){ console.log(this.lang); if(this.lang === true){ this.$i18n.locale = 'en'; localStorage.setItem('lang', 'en'); } else if(this.lang === false){ this.$i18n.locale = 'zh'; localStorage.setItem('lang', 'zh'); } } 这里说一下为什么这样写: 之前已经提到过,heyui的h-switch标签返回false或true,因为v-model的缘故,变量lang也会变为false或true,所以根据这个原理来判断,如果不使用heyui可以写一个button,点击使lang变成true或false。 如果用户点击了按钮,那么便存在本地一个值,以后每次登陆就按照存在本地的值判断选择哪个语言。如果用户是第一次登陆,那么根据浏览器语言来自动选择语言: 12345678910111213141516mounted:function(){ if(localStorage.getItem('lang') === 'zh'){ this.lang = false; this.$i18n.locale = 'zh'; } else if(localStorage.getItem('lang') === 'en'){ this.lang = true; this.$i18n.locale = 'en'; } else if(navigator.languages[0]==='zh-CN'){ this.$i18n.locale='zh'; } else if(navigator.languages[0] === 'en'){ this.$i18n.locale = 'en'; } } navigator.languages[0]是你浏览器的首选语言,次选语言是navigator.languages[1],第三语言是navigator.languages[2]以此类推。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"},{"name":"国际化","slug":"国际化","permalink":"http://example.com/tags/%E5%9B%BD%E9%99%85%E5%8C%96/"}]},{"title":"win10+arch+BIOS+MBR双系统安装","slug":"win10+archlinux","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/win10+archlinux/","link":"","permalink":"http://example.com/2020/04/21/win10+archlinux/","excerpt":"本篇博客来讲win10 + arch linux双系统安装。这里博主用的是legacy,磁盘为MBR。 本来本篇博客应该在昨天就发出来的,但是我装好arch的时候发现自己一不小心把win10的引导项覆盖了。。。刚弄好引导项我就来发博客了。 那么首先,你当然得有一个镜像啦,这里是arch官网的download页面。下载好镜像后,需要制作镜像,这里我使用的是ventoy(就是之前我博客里提到过的超好用的工具)制作的U盘。然后在磁盘上弄出一块未分配空间,建议大于50G,我分出来了150G。 那么安装教程看这里: Installation guide。这个是官方的教程,讲的挺好的,本篇博客为一些没有经验的朋友来讲一下部分需要英文阅读的地方以及一些令人疑惑的地方,您应当主要参考官方文档,在部分感到疑惑的地方来看本篇博客。 1.live CD连接无线网络由于我是使用的无线网络,所以在进入Live CD后突然发现: 不对啊,我没有联网!那么怎么解决呢? 输入: 1wifi-menu 会弹出无线网络选择页面,在这里选择网络名以及","text":"本篇博客来讲win10 + arch linux双系统安装。这里博主用的是legacy,磁盘为MBR。 本来本篇博客应该在昨天就发出来的,但是我装好arch的时候发现自己一不小心把win10的引导项覆盖了。。。刚弄好引导项我就来发博客了。 那么首先,你当然得有一个镜像啦,这里是arch官网的download页面。下载好镜像后,需要制作镜像,这里我使用的是ventoy(就是之前我博客里提到过的超好用的工具)制作的U盘。然后在磁盘上弄出一块未分配空间,建议大于50G,我分出来了150G。 那么安装教程看这里: Installation guide。这个是官方的教程,讲的挺好的,本篇博客为一些没有经验的朋友来讲一下部分需要英文阅读的地方以及一些令人疑惑的地方,您应当主要参考官方文档,在部分感到疑惑的地方来看本篇博客。 1.live CD连接无线网络由于我是使用的无线网络,所以在进入Live CD后突然发现: 不对啊,我没有联网!那么怎么解决呢? 输入: 1wifi-menu 会弹出无线网络选择页面,在这里选择网络名以及输入密钥。(2020/09/18)大概是上个月的时候,liveCD里没有wifi-menu了,现在Arch Wiki里推荐使用iwd,用法戳这里:iwd。简体中文版链接:iwd。 2.fdisk操作在分区使用fdisk的时候,我来讲一下fdisk的操作: 使用: 1fdisk /dev/sda 来对磁盘进行操作,输入d为删除分区,此时输入对应的编号(如想要删除sda1就输入1,sda2就输入2, 默认是最大的数,如: 有sda1,sda2,sda3,如果不输入编号直接回车就会默认删除sda3)。 输入n为新建分区。之后会提示你是选择p,还是选择e,默认选择p。这里的p是主分区的意思,e是逻辑分区,那么默认的也就是主分区了。然后应该是选择编号,这个他会给你一个默认编号,你也可以自己设置。然后下一个应该是设置分区的起始值,这里直接回车即可(即选择默认)。这个设置完之后应该会让你决定分区的大小,首单词应该含有last,这个是最后一个选项,输入+50G会给该分区分配50G空间,默认是占用掉全部未分配空间。 若是弄错了也不要急,直接输入q就会不保存退出。输入w会保存并退出。 3.创建swap分区如果您需要创建swap分区,可以使用cfdisk,将光标选择至创建的swap分区(也可以直接用cfdisk创建分区。并且这里注意要看好了分区名,别弄错了),然后选择type里的linux / swap,这个选项后面还有一段英文,但是我忘了是啥了,不过不用担心,全部类型里只有这一个包含有linux / swap。 4.引导使用grub引导,首先使用: 1pacman -S grub 如果你是双盘,并且Windows在/dev/sda上,arch在/dev/sdb上,那么你应该使用: 1grub-install --target=i386-pc /dev/sdb 如果不使用sdb,windows的引导项会被覆盖掉,不过覆盖掉了也没关系,可以从arch linux启动windows,当然这些是后话了,我觉得最好还是别覆盖Windows的引导项。 然后生成配置文件: 1grub-mkconfig -o /boot/grub/grub.cfg 那么下面,就是解决windows引导项被覆盖的问题了。。。 这个令我无比困扰的问题,解决方法却很简单,您也不用去百度,看我的就行了(如果不行当我没说): windows引导项被覆盖掉,那么您开机启动的系统应该是arch linux。 不过不要急,先不要启动arch linux,这里先用arch linux的live cd启动(如果配置好了网络就忽略本段看下一段),连上网,挂载系统后使用arch-chroot进入系统,安装NetwrokManager,然后使用: 1systemtcl enable NetworkManager 在下次开机后使用: 1nmcli dev wifi connect wifi名字 password 密码 以后开机后都会连接这个WiFi。 那么下面是加上Windows引导项: 使用pacman安装os-prober: 1pacman -S os-prober 然后生成配置文件: 1grub-mkconfig -o /boot/grub/grub.cfg 注意看生成文件时的提示,若是里面没有Windows或者win10这一字符串(我忘了是什么了,反正肯定和Windows有关),那就再安装一个包: 1pacman -S ntfs-3g 然后再生成一次配置文件: 1grub-mkconfig -o /boot/grub/grub.cfg 这次里面就会有Windows或win10字符串了。然后重启: 1reboot 就会在arch 的选择页面看到出现了第三个选项: Windows Manager(好像是叫这个名字),选择该条目按enter确认即可启动Windows。 由于我忙了一天半了,所以博文中部分地方可能表达的不清楚或者语法错误,但是代码是没有问题的,那么不讲了,我该休息了。这两天真是累死我了。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"},{"name":"双系统","slug":"双系统","permalink":"http://example.com/tags/%E5%8F%8C%E7%B3%BB%E7%BB%9F/"}]},{"title":"Windows10连接服务器并传送文件","slug":"Windows 10下连接服务器并传送数据","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/Windows 10下连接服务器并传送数据/","link":"","permalink":"http://example.com/2020/04/21/Windows%2010%E4%B8%8B%E8%BF%9E%E6%8E%A5%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%B9%B6%E4%BC%A0%E9%80%81%E6%95%B0%E6%8D%AE/","excerpt":"","text":"Windows 10下连接服务器:首先,打开Powershell,然后输入: 1ssh username(用户名,服务器端的)@服务器的ip 然后会提示你输入密码,输对密码就可以成功连接啦! 那么接下来是传输文件的方法: 1scp -r D://use_for_study/untitled36(Windows下的文件路径) username(服务器端的用户名)@服务器ip:/home/website(服务器端的路径,是文件将被传输到的路径) 回车后输入服务器的密码,然后就会开始传输~","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"服务器","slug":"blog/服务器","permalink":"http://example.com/categories/blog/%E6%9C%8D%E5%8A%A1%E5%99%A8/"}],"tags":[{"name":"服务器","slug":"服务器","permalink":"http://example.com/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/"}]},{"title":"wine TIM Wechat输入中文","slug":"wineTim设置中文","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/wineTim设置中文/","link":"","permalink":"http://example.com/2020/04/21/wineTim%E8%AE%BE%E7%BD%AE%E4%B8%AD%E6%96%87/","excerpt":"","text":"转自manjaro/arch 解决wine 无法输入中文我使用的fcitx。找到:/opt/deepinwine/apps/Deepin-TIM/run.sh和/opt/deepinwine/apps/Deepin-WeChat/run.sh,在这两个文件最前面加入以下三行代码: 123export XMODIFIERS="@im=fcitx"export GTK_IM_MODULE="fcitx"export QT_IM_MODULE="fcitx" 重新运行程序即可输入中文。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"},{"name":"Wine","slug":"Wine","permalink":"http://example.com/tags/Wine/"}]},{"title":"关闭vim自动缩进","slug":"关闭vim自动缩进","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/关闭vim自动缩进/","link":"","permalink":"http://example.com/2020/04/21/%E5%85%B3%E9%97%ADvim%E8%87%AA%E5%8A%A8%E7%BC%A9%E8%BF%9B/","excerpt":"","text":"转载自Vim取消自动缩进。在/etc/vimrc文件添加以下代码: 123nnoremap <F2> :set invpaste paste?<CR>imap <F2> <C-O>:set invpaste paste?<CR>set pastetoggle=<F2> 保存后退出。然后在使用vim编辑文件想要关掉自动缩进的时候,只需要按下F2,就可以关闭了。再次按F2可以打开自动缩进。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[{"name":"vim","slug":"vim","permalink":"http://example.com/tags/vim/"}]},{"title":"多关键字查询","slug":"多关键字查询","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/多关键字查询/","link":"","permalink":"http://example.com/2020/04/21/%E5%A4%9A%E5%85%B3%E9%94%AE%E5%AD%97%E6%9F%A5%E8%AF%A2/","excerpt":"这里说的关键字查询,是要实现输入 fire water这两个词,然后后台将这两个词进行模糊查询。 Vue部分,用户在搜索框输入需要查询的内容后,前端将用户输入的内容进行判断,如果有空格,那么根据空格将字符串分开;如果没有空格,那么无需分开。 123456789// indexOF方法找到目标返回1,找不到目标返回-1, text是用户输入的数据if(this.text.indexOf(' ') !== -1){ // 将数据分开, 此方法返回的是一个对象而不是列表 this.text = this.text.split(' ') // 将数据转化,转化之后数据形如:["1", "2"] this.text = JSON.stringify(this.text) }// 获取后台数据 this.get_goods(); 前端的处理就这么简单,那么到后台了: 首先,获取参数:","text":"这里说的关键字查询,是要实现输入 fire water这两个词,然后后台将这两个词进行模糊查询。 Vue部分,用户在搜索框输入需要查询的内容后,前端将用户输入的内容进行判断,如果有空格,那么根据空格将字符串分开;如果没有空格,那么无需分开。 123456789// indexOF方法找到目标返回1,找不到目标返回-1, text是用户输入的数据if(this.text.indexOf(' ') !== -1){ // 将数据分开, 此方法返回的是一个对象而不是列表 this.text = this.text.split(' ') // 将数据转化,转化之后数据形如:["1", "2"] this.text = JSON.stringify(this.text) }// 获取后台数据 this.get_goods(); 前端的处理就这么简单,那么到后台了: 首先,获取参数: 12345text = request.GET.get('text', None) # 判断其是否为列表 if ',' in text: # 对字符串进行处理,将其转化为列表 text = request.GET.get('text').replace("[", '').replace("]", '').replace('"', '').split(',') 接下来就到了关键时刻,对查询出来的数据进行处理: 1234567891011# 如果text为列表,那么进行一下判断 if type(text) == list: # 声明一个空列表,用来容纳数据 all1 = [] # 根据关键字进行模糊查询 for i in text: my_list = Goods.objects.filter(name__contains=i).all() # 将查询到的数据添加进空列表 all1.extend(my_list) # 对数据进行去重操作 all1 = list(set(all1)) 这样就对数据处理好了,没有重复数据,不会出现在查询”一加手机壳”的时候将”一加”有关的所有数据查出来又将”手机壳”有关的所有数据查出来后拼接在一起可能导致的同一数据出现两次的情况(如数据名为: 一加手机壳)。 然后返回给前端数据: 123456# 这里我用了分页操作,若是没有分页则不用切片goods = all1[start_data: end_data]# 这里我使用了restframework.serializer的ModelSerializer,是一个序列化操作goodser = GoodsSer(goods, many=True)# page_long为页数,data为查询到的内容,item_num为内容长度return Response({'code': 200, 'page_long': page_long, 'data': goodser.data, 'item_num': len(all1)})","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"Django","slug":"Django","permalink":"http://example.com/tags/Django/"},{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"},{"name":"关键字查询","slug":"关键字查询","permalink":"http://example.com/tags/%E5%85%B3%E9%94%AE%E5%AD%97%E6%9F%A5%E8%AF%A2/"}]},{"title":"使deepinQQ显示图片","slug":"使deepinQQ显示图片","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/使deepinQQ显示图片/","link":"","permalink":"http://example.com/2020/04/21/%E4%BD%BFdeepinQQ%E6%98%BE%E7%A4%BA%E5%9B%BE%E7%89%87/","excerpt":"由于我之前一直在使用qq-linux,所以一直没有研究这个问题。然而最近QQ老是抽风,我每滚一次,它就扫码秒退一次…无奈之下,还是摸起了deepin的wineQQ。安装我就不说了哈,这篇博客解决的是无法加载图片的问题。 首先,禁用ipv6,摘抄自Arch下关闭ipv6的方法。输入以下命令: 12%sudo touch /etc/sysctl.d/ipv6.conf%sudo vim !$ 然后写入以下内容:","text":"由于我之前一直在使用qq-linux,所以一直没有研究这个问题。然而最近QQ老是抽风,我每滚一次,它就扫码秒退一次…无奈之下,还是摸起了deepin的wineQQ。安装我就不说了哈,这篇博客解决的是无法加载图片的问题。 首先,禁用ipv6,摘抄自Arch下关闭ipv6的方法。输入以下命令: 12%sudo touch /etc/sysctl.d/ipv6.conf%sudo vim !$ 然后写入以下内容: 1234#disable ipv6net.ipv6.conf.all.disable_ipv6 = 1net.ipv6.conf.<enp9s0>.disable_ipv6 = 1net.ipv6.conf.<lo>.disable_ipv6 = 1 保存退出。然后输入: 1sudo vim /etc/hosts 编辑hosts文件,在::1 localhost注释掉(在::1前面加上# )。这样就关闭ipv6了。然后重启系统。接下来的内容摘自deepin-wine-qq无法加载图片解决方案 – 东北小蟹蟹。然后输入: 1sudo rm -rf ~/.deepinwine/Deepin-QQ 用于清除QQ的缓存。之后重新打开QQ,就能够加载出头像和图片了。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"}],"tags":[{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"}]},{"title":"使用ventoy制作启动盘","slug":"使用ventoy制作系统启动盘","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/使用ventoy制作系统启动盘/","link":"","permalink":"http://example.com/2020/04/21/%E4%BD%BF%E7%94%A8ventoy%E5%88%B6%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%90%AF%E5%8A%A8%E7%9B%98/","excerpt":"最近发现了一个很好的、功能很强大的神器:Ventoy。这神器的功能到底有多强大呢?它能够让你在一个U盘里面启动多个系统。。。 使用也非常的简单,用ventoy制作过U盘后直接将iso文件拖入U盘即可。那么这里是ventoy的下载地址:Ventoy 使用方法: 打开ventoy,选择install,会提示你U盘将被格式化。确定。等读条完成后就可以使用了!将ISO文件直接拖入U盘即可。","text":"最近发现了一个很好的、功能很强大的神器:Ventoy。这神器的功能到底有多强大呢?它能够让你在一个U盘里面启动多个系统。。。 使用也非常的简单,用ventoy制作过U盘后直接将iso文件拖入U盘即可。那么这里是ventoy的下载地址:Ventoy 使用方法: 打开ventoy,选择install,会提示你U盘将被格式化。确定。等读条完成后就可以使用了!将ISO文件直接拖入U盘即可。 下面是实机演示: 将ISO文件拖进U盘后重启电脑,选择U盘启动。然后会看到以下内容: 这里随便选一个选项,就选kali吧: 成功进入引导页面~ 然后选择live system看看: 成功进入~这东西太好用了!爱了爱了。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"}],"tags":[{"name":"ventoy","slug":"ventoy","permalink":"http://example.com/tags/ventoy/"},{"name":"启动盘","slug":"启动盘","permalink":"http://example.com/tags/%E5%90%AF%E5%8A%A8%E7%9B%98/"}]},{"title":"网页切换白天黑夜模式","slug":"切换白天黑夜模式","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/切换白天黑夜模式/","link":"","permalink":"http://example.com/2020/04/21/%E5%88%87%E6%8D%A2%E7%99%BD%E5%A4%A9%E9%BB%91%E5%A4%9C%E6%A8%A1%E5%BC%8F/","excerpt":"网页切换白天黑夜模式,其实就是对一个变量进操作,举个栗子: 默认值是false, 进行判断,如果是false的话那么指定元素的颜色为白天的颜色,如果为true那么指定元素的颜色为黑夜的颜色。 那么为了方便理解,下面就上代码","text":"网页切换白天黑夜模式,其实就是对一个变量进操作,举个栗子: 默认值是false, 进行判断,如果是false的话那么指定元素的颜色为白天的颜色,如果为true那么指定元素的颜色为黑夜的颜色。 那么为了方便理解,下面就上代码: 1234567891011121314151617181920:root{ --bg-color:white; --title-color: black;}a.navbar-brand, a.logo { font-family: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif; color: var(--title-color) !important; font-size: 2rem; font-weight: bold; margin-top: 0; }.header { background: var(--bg-color); min-height: 7em; height: auto; border-radius: 0; width: 100%; color: #444342; padding-top: 1em; padding-bottom: 1em; border-bottom: 1px solid rgba(0, 0, 0, 0.05); } 这是在我的css文件中的代码,可以看到,我定义了两个变量: –bg-color, –title-color,分别在background和color这两个地方用到了它们。那么到这里只是定义了变量并给变量赋值(定义变量时 : 后面的值即为赋给变量的值),接下来是在js中改变值了,不过为了方便理解我先放上我的html代码: 1<h-switch v-model="color_model" @change="change_background">{{$t('m.day')}}/{{$t('m.night')}}</h-switch> 这里使用的是vue.js和heyui的 h-swith标签,效果是这样:, 当打开时(即按钮空白处为绿色时)返回true,再次点击按钮空白处重新变为白色,返回false。@change的意思是当发生改变时调用方法。 这是我在data(再次说明我使用的是vue.js)中设置的变量: 12345data(){ return { color_model: false }} 那么接下来是用js改变css的变量的值: 12345678910111213141516// 切换背景色 change_background:function(){ // 获取样式表 console.log(this.color_model) if(this.color_model){ var styles = getComputedStyle(document.documentElement); // 动态修改 document.documentElement.style.setProperty('--bg-color', '#292a2d'); document.documentElement.style.setProperty('--title-color', 'black'); }else{ getComputedStyle(document.documentElement); // 动态修改 document.documentElement.style.setProperty('--bg-color', 'white'); document.documentElement.style.setProperty('--title-color', 'black'); } } 这样就可以实现在点击时改变变量的值了,即可以实现切换白天黑夜模式了。 那么除了这种手动切换白天黑夜的方法,还有一种自动检测当前时间选择白天模式或是黑夜模式的方法: 1234567891011121314// 根据时间改变颜色 change_bg_by_time:function(){ let date = new Date(); let hour = date.getHours(); // 如果当前时间大于早上6点并小于下午6点,则切换成白天模式 if(hour>6 && hour<18){ this.color_model = false } else{ // 切换成黑夜模式 this.color_model = true } }, 由于我使用的是vue.js,所以直接在钩子方法mounted中调用了该函数以达到自动切换的目的。我的页面上是手动切换与自动切换共存的,所以不需要全部抄下来,根据需要使用即可。 那么本次就到这里了~","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"},{"name":"白天黑夜模式","slug":"白天黑夜模式","permalink":"http://example.com/tags/%E7%99%BD%E5%A4%A9%E9%BB%91%E5%A4%9C%E6%A8%A1%E5%BC%8F/"}]},{"title":"提升clone的速度","slug":"提升github速度","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/提升github速度/","link":"","permalink":"http://example.com/2020/04/21/%E6%8F%90%E5%8D%87github%E9%80%9F%E5%BA%A6/","excerpt":"","text":"网上大部分的方法都是修改HOST文件,说实话我觉得那样一点用也没有,或者也许我配置错了吧。在这里我们使用的是一个Github的镜像网站,叫做FastGithub,链接是:FastGitHub,查看项目中的README.md文件,在”已知的GitHub镜像(含失效站点)”中找到一个未失效的,不需要登录,在地址栏里输入项目在github上的路径即可。比如我使用https://github.bajins.com站点,我的个人主页在github上是https://github.com/Gray-Ice,那么我在https://github.bajins.com的个人主页就是https://github.bajins.com/Gray-Ice,通过更改路径的方法快速找到项目并找到clone链接,就可以提高git clone速度了。我试着clone了一下https://github.wuyanzheshui.workers.dev站点上的某个项目,我是100M宽带,速度居然有6MB/s-9MB/s,真的太舒服了。感谢这些站点的提供者。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"git","slug":"blog/git","permalink":"http://example.com/categories/blog/git/"}],"tags":[{"name":"git","slug":"git","permalink":"http://example.com/tags/git/"}]},{"title":"添加商品评论","slug":"添加商品评论","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/添加商品评论/","link":"","permalink":"http://example.com/2020/04/21/%E6%B7%BB%E5%8A%A0%E5%95%86%E5%93%81%E8%AF%84%E8%AE%BA/","excerpt":"首先先建一张表,该表拥有四个字段,其中有三个整型字段: id, gid(商品id), uid(用户id)。有一个字符串变量: content,用来存储评论。 那么首先","text":"首先先建一张表,该表拥有四个字段,其中有三个整型字段: id, gid(商品id), uid(用户id)。有一个字符串变量: content,用来存储评论。 那么首先在前端写一个textarea,以及一个用来提交的button。将该textarea绑定一个变量并设定最大字符数量,将该button绑定一个提价按钮。 123<textarea rows="10" v-autosize v-wordcount="100" v-model="comments"></textarea><br><Button color="blue" @click="submit_comment">评论</Button> 这里的Button标签使用的heyui,实际效果和button并无区别,只是美观。 textarea这里的v-wordcount=”100”,可以实现以下效果: 然后点击评论后判断一下字数,若是字数无误便提交至后台: 123456789101112131415161718192021222324252627// 提交评论 submit_comment(){ // this.comments是评论变量 if(this.comments.length > 100){ // Message是heyui提供的功能,其效果等同于alert this.$Message('您的评论超出了长度限制'); return false } else{ var form_data = new FormData(); // 将商品id添加到表单 form_data.append('gid',this.good_msg.id); // 将用户id添加到表单 form_data.append('uid',localStorage.getItem('uid')); // 将评论内容添加到表单 form_data.append('content', this.comments); // 提交至后台 this.axios({ url: 'http://127.0.0.1:8000/comment/', method: 'post', data: form_data }).then(res=>{ this.$Message(res.data.message); return false }) } }, 然后下面是后台的代码,这里我使用了反序列化。 123456789101112# 评论class CommentsView(APIView): def post(self, request): # 插入数据 comment = CommentsSer(data=request.data) if comment.is_valid(): # 提交 comment.save() return Response({'code': 200, 'message': '评论成功'}) else: return Response({'code': 200, 'message': '评论失败'})","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"}],"tags":[{"name":"Django","slug":"Django","permalink":"http://example.com/tags/Django/"},{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"},{"name":"商品评论","slug":"商品评论","permalink":"http://example.com/tags/%E5%95%86%E5%93%81%E8%AF%84%E8%AE%BA/"}]},{"title":"设置商品评论频率","slug":"设置商品评论频率","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/设置商品评论频率/","link":"","permalink":"http://example.com/2020/04/21/%E8%AE%BE%E7%BD%AE%E5%95%86%E5%93%81%E8%AF%84%E8%AE%BA%E9%A2%91%E7%8E%87/","excerpt":"","text":"本方法是通过ip来进行对评论频率的掌控。 首先,获取评论用户的ip: 1234if 'HTTP_X_FORWARDED_FOR' in request.META: ip = request.META.get('HTTP_X_FORWARDED_FOR')else: ip = request.META.get('REMOTE_ADDR') 然后判断redis数据库中是否有该ip,若是没有,则将该ip存入数据库; 若是有,返回评率速度过快。 12345if r.get('ip'): return Response({"code": 403, 'message': '您评论的速度太快了,请歇一歇'})r.set('ip', 'exist')r.expire('ip', 30)return Response({'code': 200, 'message': '评论成功'})","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"评论频率","slug":"评论频率","permalink":"http://example.com/tags/%E8%AF%84%E8%AE%BA%E9%A2%91%E7%8E%87/"}]},{"title":"Python进程线程","slug":"进程线程","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/进程线程/","link":"","permalink":"http://example.com/2020/04/21/%E8%BF%9B%E7%A8%8B%E7%BA%BF%E7%A8%8B/","excerpt":"转载自v3u.cn 进程,是执行中的计算机程序。也就是说,每个代码在执行的时候,首先本身即是一个进程。 一个进程具有:就绪,运行,中断,僵死,结束等状态(不同操作系统不一样)。","text":"转载自v3u.cn 进程,是执行中的计算机程序。也就是说,每个代码在执行的时候,首先本身即是一个进程。 一个进程具有:就绪,运行,中断,僵死,结束等状态(不同操作系统不一样)。 生命周期: 用户编写代码(代码本身是以进程运行的) 启动程序,进入进程“就绪”状态 操作系统调度资源,做“程序切换”,使得进程进入“运行”状态 结束/中断 特性 每个程序,本身首先是一个进程 运行中每个进程都拥有自己的地址空间、内存、数据栈及其它资源。 操作系统本身自动管理着所有的进程(不需要用户代码干涉),并为这些进程合理分配可以执行时间。 进程可以通过派生新的进程来执行其它任务,不过每个进程还是都拥有自己的内存和数据栈等。 进程间可以通讯(发消息和数据),采用 进程间通信(IPC) 方式。 说明 多个进程可以在不同的 CPU 上运行,互不干扰 同一个CPU上,可以运行多个进程,由操作系统来自动分配时间片 由于进程间资源不能共享,需要进程间通信,来发送数据,接受消息等 多进程,也称为“并行”。 进程间通信进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的。 进程队列queue不同于线程queue,进程queue的生成是用multiprocessing模块生成的。 在生成子进程的时候,会将代码拷贝到子进程中执行一遍,及子进程拥有和主进程内容一样的不同的名称空间。 multiprocess.Queue 是跨进程通信队列 常用方法 1234567q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.q.get_nowait():同q.get(False)q.put_nowait():同q.put(False)q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样 管道pipe管道就是管道,就像生活中的管道,两头都能进能出 默认管道是全双工的,如果创建管道的时候映射成False,左边只能用于接收,右边只能用于发送,类似于单行道 123456789101112import multiprocessingdef foo(sk): sk.send('hello world') print(sk.recv())if __name__ == '__main__': conn1,conn2=multiprocessing.Pipe() #开辟两个口,都是能进能出,括号中如果False即单向通信 p=multiprocessing.Process(target=foo,args=(conn1,)) #子进程使用sock口,调用foo函数 p.start() print(conn2.recv()) #主进程使用conn口接收 conn2.send('hi son') #主进程使用conn口发送 常用方法 123conn1.recv():接收conn2.send(obj)发送的对象。如果没有消息可接收,recv方法会一直阻塞。如果连接的另外一端已经关闭,那么recv方法会抛出EOFError。conn1.send(obj):通过连接发送对象。obj是与序列化兼容的任意对象注意:send()和recv()方法使用pickle模块对对象进行序列化 共享数据manageQueue和pipe只是实现了数据交互,并没实现数据共享,即一个进程去更改另一个进程的数据。 注:进程间通信应该尽量避免使用共享数据的方式 进程池开多进程是为了并发,通常有几个cpu核心就开几个进程,但是进程开多了会影响效率,主要体现在切换的开销,所以引入进程池限制进程的数量。 进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。 线程线程,是在进程中执行的代码。 一个进程下可以运行多个线程,这些线程之间共享主进程内申请的操作系统资源。 在一个进程中启动多个线程的时候,每个线程按照顺序执行。现在的操作系统中,也支持线程抢占,也就是说其它等待运行的线程,可以通过优先级,信号等方式,将运行的线程挂起,自己先运行。 使用 用户编写包含线程的程序(每个程序本身都是一个进程) 操作系统“程序切换”进入当前进程 当前进程包含了线程,则启动线程 多个线程,则按照顺序执行,除非抢占 特性 线程,必须在一个存在的进程中启动运行 线程使用进程获得的系统资源,不会像进程那样需要申请CPU等资源 线程无法给予公平执行时间,它可以被其他线程抢占,而进程按照操作系统的设定分配执行时间 每个进程中,都可以启动很多个线程 说明 多线程,也被称为”并发“执行。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[]},{"title":"解决vim无法复制内容到系统剪切板","slug":"解决无法复制内容到系统剪切板","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/解决无法复制内容到系统剪切板/","link":"","permalink":"http://example.com/2020/04/21/%E8%A7%A3%E5%86%B3%E6%97%A0%E6%B3%95%E5%A4%8D%E5%88%B6%E5%86%85%E5%AE%B9%E5%88%B0%E7%B3%BB%E7%BB%9F%E5%89%AA%E5%88%87%E6%9D%BF/","excerpt":"","text":"使用vim的时候用v选中字符串后发现无法用”+y复制到系统剪切板,百度一番后,得到了以下答案:1.重新编译vim,并且在编译之前还需要先编辑一下某些内容。2.安装gvim。我一开始是想重新编译的,但是就在我下好了vim的源码时,发现那篇博客找不着了。。。于是我决定使用gvim。在使用gvim的时候我是有些担心的,怕Vundle的插件还得再重新安装一遍,因为安装gvim需要先删除掉vim。但是我发现多虑了,拆卸vim安装gvim后Vundle的插件依然可以正常使用。使用以下命令安装gvim: 1pacman -S gvim 如果提示与vim冲突,是否删除vim,选择是。然后就可以复制到系统剪切板里了。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"vim","slug":"blog/vim","permalink":"http://example.com/categories/blog/vim/"}],"tags":[{"name":"vim","slug":"vim","permalink":"http://example.com/tags/vim/"}]},{"title":"又拍云&拖拽上传","slug":"又拍云拖拽上传","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/又拍云拖拽上传/","link":"","permalink":"http://example.com/2020/04/21/%E5%8F%88%E6%8B%8D%E4%BA%91%E6%8B%96%E6%8B%BD%E4%B8%8A%E4%BC%A0/","excerpt":"Vue代码: 页面代码(其中input框并不需要): 1234<div id='upload'> 拖拽上传 <input type="file" @change='yp_upload'></div>","text":"Vue代码: 页面代码(其中input框并不需要): 1234<div id='upload'> 拖拽上传 <input type="file" @change='yp_upload'></div> methods中的代码: 123456789101112131415161718192021222324252627onDrag(e){ e.stopPropagation(); e.preventDefault(); }, onDrop(e){ e.stopPropagation(); e.preventDefault(); // 通用自定义上传方法 this.yp_upload(e.dataTransfer.files); }, // 又拍云拖拽上传 yp_upload:function(files){ // 获取拖拽文件 let file = files[0]; // 声明参数 let param = new FormData(); param.append('file', file); param.append('username', localStorage.getItem('username')) // 声明头部信息 const headers = { 'Content-Type': 'multipart/form-data' }; // 发送请求 this.axios.post('http://127.0.0.1:8000/youpai/', param).then(res=>{ console.log(res) }) }, mounted中的代码: 12345678// 获取最新的token this.get_token(); // 注册拖拽容器 let upload = document.querySelector('#upload'); // 声明监听事件 upload.addEventListener('dragenter', this.onDrag, false); upload.addEventListener('dragover', this.onDrag, false); upload.addEventListener('drop', this.onDrop, false); 后台Python代码(使用Django框架): Views.py中: 123456789101112131415161718192021222324import upyunfrom rest_framework.response import Responsefrom rest_framework.views import APIViewclass YouPai(APIView): def post(self, request): # 接收参数 myfile = request.FILES.get('file', None) # 判断是否收到参数 if myfile: up = upyun.UpYun('应用名称', '管理员名称', '管理员密钥', endpoint=upyun.ED_AUTO) with open('./forup/' + myfile.name, 'wb') as f: for chunk in myfile.chunks(): f.write(chunk) # 流式传输 with open('./forup/' + myfile.name, 'rb') as f: res = up.put('/image/' + myfile.name, f, checksum=False) return Response({ 'msg': '上传成功', 'type': res['file-type'] }) return Response({'msg': '文件接收失败'}) urls.py中: 1path('youpai/', YouPai.as_view(), name='youpai'),","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"}],"tags":[{"name":"Django","slug":"Django","permalink":"http://example.com/tags/Django/"},{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"},{"name":"对象存储","slug":"对象存储","permalink":"http://example.com/tags/%E5%AF%B9%E8%B1%A1%E5%AD%98%E5%82%A8/"},{"name":"又拍云","slug":"又拍云","permalink":"http://example.com/tags/%E5%8F%88%E6%8B%8D%E4%BA%91/"}]},{"title":"组操作","slug":"组操作","date":"un22fin22","updated":"un22fin22","comments":true,"path":"2020/04/21/组操作/","link":"","permalink":"http://example.com/2020/04/21/%E7%BB%84%E6%93%8D%E4%BD%9C/","excerpt":"摘抄自Arch Wiki Users and groups。在/etc/group文件存储了系统中用户组的信息。使用groups命令查看用户所在组的名称:","text":"摘抄自Arch Wiki Users and groups。在/etc/group文件存储了系统中用户组的信息。使用groups命令查看用户所在组的名称: 1groups [用户名] 若省略用户名,默认显示当前用户所在组。id命令提供额外的信息,包括用户UID以及相关用户组GID: 1id [用户名] 查看所有组: 1cat /etc/group 使用groupadd创建新的组: 1groupadd [组名] 使用gpasswd将用户添加到组: 1gpasswd -a [用户名] [组名] 更改用户所属的组名,不变更GID: 1groupmod -n newname oldname 删除用户组: 1groupdel [组名] 将用户从组中移除: 1gpasswd -d [用户名] [组名] 如果用户已登录,必须重新登录使更改生效。","categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"}],"tags":[{"name":"Linux命令","slug":"Linux命令","permalink":"http://example.com/tags/Linux%E5%91%BD%E4%BB%A4/"}]}],"categories":[{"name":"blog","slug":"blog","permalink":"http://example.com/categories/blog/"},{"name":"RPA","slug":"blog/RPA","permalink":"http://example.com/categories/blog/RPA/"},{"name":"Python","slug":"blog/Python","permalink":"http://example.com/categories/blog/Python/"},{"name":"PyQt5","slug":"blog/PyQt5","permalink":"http://example.com/categories/blog/PyQt5/"},{"name":"乱七八糟","slug":"blog/乱七八糟","permalink":"http://example.com/categories/blog/%E4%B9%B1%E4%B8%83%E5%85%AB%E7%B3%9F/"},{"name":"flutter","slug":"blog/flutter","permalink":"http://example.com/categories/blog/flutter/"},{"name":"JavaScript","slug":"blog/JavaScript","permalink":"http://example.com/categories/blog/JavaScript/"},{"name":"Go","slug":"blog/Go","permalink":"http://example.com/categories/blog/Go/"},{"name":"life","slug":"life","permalink":"http://example.com/categories/life/"},{"name":"C","slug":"blog/C","permalink":"http://example.com/categories/blog/C/"},{"name":"RabbitMQ","slug":"blog/RabbitMQ","permalink":"http://example.com/categories/blog/RabbitMQ/"},{"name":"CSAPP","slug":"blog/CSAPP","permalink":"http://example.com/categories/blog/CSAPP/"},{"name":"计算机网络","slug":"blog/计算机网络","permalink":"http://example.com/categories/blog/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"},{"name":"前端","slug":"blog/前端","permalink":"http://example.com/categories/blog/%E5%89%8D%E7%AB%AF/"},{"name":"vue","slug":"blog/vue","permalink":"http://example.com/categories/blog/vue/"},{"name":"arch","slug":"blog/arch","permalink":"http://example.com/categories/blog/arch/"},{"name":"Linux","slug":"blog/Linux","permalink":"http://example.com/categories/blog/Linux/"},{"name":"elasticsearch","slug":"blog/elasticsearch","permalink":"http://example.com/categories/blog/elasticsearch/"},{"name":"Cpp","slug":"blog/Cpp","permalink":"http://example.com/categories/blog/Cpp/"},{"name":"Qt","slug":"blog/Cpp/Qt","permalink":"http://example.com/categories/blog/Cpp/Qt/"},{"name":"vim","slug":"blog/vim","permalink":"http://example.com/categories/blog/vim/"},{"name":"flask","slug":"blog/Python/flask","permalink":"http://example.com/categories/blog/Python/flask/"},{"name":"Termux","slug":"blog/Termux","permalink":"http://example.com/categories/blog/Termux/"},{"name":"Socket","slug":"blog/Socket","permalink":"http://example.com/categories/blog/Socket/"},{"name":"服务器","slug":"blog/服务器","permalink":"http://example.com/categories/blog/%E6%9C%8D%E5%8A%A1%E5%99%A8/"},{"name":"PyQt5","slug":"blog/Python/PyQt5","permalink":"http://example.com/categories/blog/Python/PyQt5/"},{"name":"数据分析","slug":"blog/Python/数据分析","permalink":"http://example.com/categories/blog/Python/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"},{"name":"friendlink","slug":"friendlink","permalink":"http://example.com/categories/friendlink/"},{"name":"博客日志","slug":"博客日志","permalink":"http://example.com/categories/%E5%8D%9A%E5%AE%A2%E6%97%A5%E5%BF%97/"},{"name":"resume","slug":"resume","permalink":"http://example.com/categories/resume/"},{"name":"Deepin","slug":"blog/Deepin","permalink":"http://example.com/categories/blog/Deepin/"},{"name":"git","slug":"blog/git","permalink":"http://example.com/categories/blog/git/"},{"name":"hugo","slug":"blog/hugo","permalink":"http://example.com/categories/blog/hugo/"},{"name":"Anaconda","slug":"blog/Anaconda","permalink":"http://example.com/categories/blog/Anaconda/"},{"name":"30days","slug":"blog/30days","permalink":"http://example.com/categories/blog/30days/"}],"tags":[{"name":"踩坑","slug":"踩坑","permalink":"http://example.com/tags/%E8%B8%A9%E5%9D%91/"},{"name":"vim","slug":"vim","permalink":"http://example.com/tags/vim/"},{"name":"Socket","slug":"Socket","permalink":"http://example.com/tags/Socket/"},{"name":"Arch","slug":"Arch","permalink":"http://example.com/tags/Arch/"},{"name":"PIL","slug":"PIL","permalink":"http://example.com/tags/PIL/"},{"name":"Django","slug":"Django","permalink":"http://example.com/tags/Django/"},{"name":"jwt","slug":"jwt","permalink":"http://example.com/tags/jwt/"},{"name":"Vue","slug":"Vue","permalink":"http://example.com/tags/Vue/"},{"name":"zsh","slug":"zsh","permalink":"http://example.com/tags/zsh/"},{"name":"服务器","slug":"服务器","permalink":"http://example.com/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/"},{"name":"PyQt5","slug":"PyQt5","permalink":"http://example.com/tags/PyQt5/"},{"name":"代理","slug":"代理","permalink":"http://example.com/tags/%E4%BB%A3%E7%90%86/"},{"name":"Flask","slug":"Flask","permalink":"http://example.com/tags/Flask/"},{"name":"matplotlib","slug":"matplotlib","permalink":"http://example.com/tags/matplotlib/"},{"name":"bluetooth","slug":"bluetooth","permalink":"http://example.com/tags/bluetooth/"},{"name":"图片合成","slug":"图片合成","permalink":"http://example.com/tags/%E5%9B%BE%E7%89%87%E5%90%88%E6%88%90/"},{"name":"对象存储","slug":"对象存储","permalink":"http://example.com/tags/%E5%AF%B9%E8%B1%A1%E5%AD%98%E5%82%A8/"},{"name":"Deepin","slug":"Deepin","permalink":"http://example.com/tags/Deepin/"},{"name":"双系统","slug":"双系统","permalink":"http://example.com/tags/%E5%8F%8C%E7%B3%BB%E7%BB%9F/"},{"name":"cv2","slug":"cv2","permalink":"http://example.com/tags/cv2/"},{"name":"压缩图片","slug":"压缩图片","permalink":"http://example.com/tags/%E5%8E%8B%E7%BC%A9%E5%9B%BE%E7%89%87/"},{"name":"水印","slug":"水印","permalink":"http://example.com/tags/%E6%B0%B4%E5%8D%B0/"},{"name":"钉钉第三方登录","slug":"钉钉第三方登录","permalink":"http://example.com/tags/%E9%92%89%E9%92%89%E7%AC%AC%E4%B8%89%E6%96%B9%E7%99%BB%E5%BD%95/"},{"name":"微博第三方登录","slug":"微博第三方登录","permalink":"http://example.com/tags/%E5%BE%AE%E5%8D%9A%E7%AC%AC%E4%B8%89%E6%96%B9%E7%99%BB%E5%BD%95/"},{"name":"selenium","slug":"selenium","permalink":"http://example.com/tags/selenium/"},{"name":"滑块验证","slug":"滑块验证","permalink":"http://example.com/tags/%E6%BB%91%E5%9D%97%E9%AA%8C%E8%AF%81/"},{"name":"百度文字识别","slug":"百度文字识别","permalink":"http://example.com/tags/%E7%99%BE%E5%BA%A6%E6%96%87%E5%AD%97%E8%AF%86%E5%88%AB/"},{"name":"redis","slug":"redis","permalink":"http://example.com/tags/redis/"},{"name":"生成图片验证码","slug":"生成图片验证码","permalink":"http://example.com/tags/%E7%94%9F%E6%88%90%E5%9B%BE%E7%89%87%E9%AA%8C%E8%AF%81%E7%A0%81/"},{"name":"git","slug":"git","permalink":"http://example.com/tags/git/"},{"name":"hugo","slug":"hugo","permalink":"http://example.com/tags/hugo/"},{"name":"Anaconda","slug":"Anaconda","permalink":"http://example.com/tags/Anaconda/"},{"name":"30days","slug":"30days","permalink":"http://example.com/tags/30days/"},{"name":"中间件","slug":"中间件","permalink":"http://example.com/tags/%E4%B8%AD%E9%97%B4%E4%BB%B6/"},{"name":"Docker","slug":"Docker","permalink":"http://example.com/tags/Docker/"},{"name":"hexo","slug":"hexo","permalink":"http://example.com/tags/hexo/"},{"name":"Linux命令","slug":"Linux命令","permalink":"http://example.com/tags/Linux%E5%91%BD%E4%BB%A4/"},{"name":"性能检测工具","slug":"性能检测工具","permalink":"http://example.com/tags/%E6%80%A7%E8%83%BD%E6%A3%80%E6%B5%8B%E5%B7%A5%E5%85%B7/"},{"name":"折线图","slug":"折线图","permalink":"http://example.com/tags/%E6%8A%98%E7%BA%BF%E5%9B%BE/"},{"name":"nask","slug":"nask","permalink":"http://example.com/tags/nask/"},{"name":"pip","slug":"pip","permalink":"http://example.com/tags/pip/"},{"name":"上传进度条","slug":"上传进度条","permalink":"http://example.com/tags/%E4%B8%8A%E4%BC%A0%E8%BF%9B%E5%BA%A6%E6%9D%A1/"},{"name":"上传文件","slug":"上传文件","permalink":"http://example.com/tags/%E4%B8%8A%E4%BC%A0%E6%96%87%E4%BB%B6/"},{"name":"画中画","slug":"画中画","permalink":"http://example.com/tags/%E7%94%BB%E4%B8%AD%E7%94%BB/"},{"name":"分页","slug":"分页","permalink":"http://example.com/tags/%E5%88%86%E9%A1%B5/"},{"name":"国际化","slug":"国际化","permalink":"http://example.com/tags/%E5%9B%BD%E9%99%85%E5%8C%96/"},{"name":"Wine","slug":"Wine","permalink":"http://example.com/tags/Wine/"},{"name":"关键字查询","slug":"关键字查询","permalink":"http://example.com/tags/%E5%85%B3%E9%94%AE%E5%AD%97%E6%9F%A5%E8%AF%A2/"},{"name":"ventoy","slug":"ventoy","permalink":"http://example.com/tags/ventoy/"},{"name":"启动盘","slug":"启动盘","permalink":"http://example.com/tags/%E5%90%AF%E5%8A%A8%E7%9B%98/"},{"name":"白天黑夜模式","slug":"白天黑夜模式","permalink":"http://example.com/tags/%E7%99%BD%E5%A4%A9%E9%BB%91%E5%A4%9C%E6%A8%A1%E5%BC%8F/"},{"name":"商品评论","slug":"商品评论","permalink":"http://example.com/tags/%E5%95%86%E5%93%81%E8%AF%84%E8%AE%BA/"},{"name":"评论频率","slug":"评论频率","permalink":"http://example.com/tags/%E8%AF%84%E8%AE%BA%E9%A2%91%E7%8E%87/"},{"name":"又拍云","slug":"又拍云","permalink":"http://example.com/tags/%E5%8F%88%E6%8B%8D%E4%BA%91/"}]}