[{"content":"这个博客原来跑在 WordPress 上。前几天我把它整个搬到了 Hugo，静态托管在 Cloudflare Pages 上，顺手把「写文章」这件事也重新做了一遍。这篇算是迁移完成后的第一篇，把整条流水线 从头到尾记一遍：数据怎么搬、Hugo 怎么构建、文章怎么写、发布怎么做，以及中间踩过的坑。\n为什么要搬 不是为了追新，是几个很具体的成本问题：\nWordPress 要一整套 PHP + MySQL 才能发几十篇文章，备份、升级、插件都得操心； 文章在数据库里，想用 grep 找一句话都做不到； 静态站可以把每一篇文章、每一张图都放进 Git，改了什么一目了然，回滚就是 git revert。 真正的难点不是 Hugo——Hugo 半天就能学会。难点是把旧数据从 WordPress 里挖出来： 一份 mysqldump、一个 uploads 压缩包，还有一堆埋在 PHP 序列化字段里的配置。\n迁移思路：把\u0026quot;读不出来\u0026quot;变成\u0026quot;可 grep\u0026quot; 我写了一个叫 wp2md 的小工具，核心思路是分三层拆开，每层只干一件事：\n层 输入 输出 干什么 数据 mysqldump SQLite 解析 dump，还原成能查询的表 内容 SQLite output/*.md HTML → Markdown，保留 front matter 站点 output/ blog/ 生成 Hugo 的 content/ 与 static/media/ 拆开的好处是每层都能单独重跑、单独校验。图片路径对不上时，我只需要重跑媒体那一步， 不用把整条迁移链从头再走一遍。\n另外两个刻意的决定：\n只依赖 Python 标准库。 pyproject.toml 里 dependencies = []。移植这种一次性的 数据清洗脚本，最怕的就是半年后想改一处，环境却装不回来了。标准库版本放三年也能跑。 Hugo 二进制随项目走。 tools/hugo/hugo.exe（v0.167.0 extended）直接提交进仓库， 构建时零网络依赖。 最后的结果：68 篇已发布文章 + 2 篇私密草稿、4 条说说、49 个媒体文件（5.8 MiB）， 一次迁移完成。旧文章原来的固定链接 /archives/\u0026lt;id\u0026gt; 也全部换成了新的可读地址。\nHugo 是怎么构建的 Hugo 的心智模型很简单：content/ 是输入，public/ 是输出，中间的一切都是配置。\nblog/ ├── hugo.yaml 站点配置（baseURL、permalinks、菜单、主题参数） ├── content/ 文章和页面（Markdown + front matter） ├── static/ 原样拷贝进 public/ 的文件（我的图片都在 static/media/年/月/） ├── themes/PaperMod/ 主题（内联成普通文件，不是 git submodule） ├── layouts/ 覆盖主题的模板 └── public/ 构建产物，已在 .gitignore 里 几条配置值得说明：\npermalinks.posts = \u0026quot;/archives/:slug/\u0026quot; ——文章的网址完全由 front matter 里的 slug 决定。所以 slug 是个要慎重的字段，改了它等于换网址。 markup.goldmark.renderer.unsafe = true ——旧文章里混着 \u0026lt;br\u0026gt;、\u0026lt;u\u0026gt;、\u0026lt;sup\u0026gt; 这类原生 HTML，不开这个开关它们会被转义成字面文本。 outputs.home 加了 JSON ——给 PaperMod 的客户端搜索提供索引，纯静态站也能全文搜索。 主题内联而不是 submodule ——私有仓库 + 构建机的组合下，submodule 容易拉不下来， 内联之后构建机完全不需要访问 GitHub。 还覆盖了 4 个模板文件，原因都写在那几个文件的注释里，简单说两类：一是 PaperMod 还在用 Hugo 0.158 已经改名的 API（.Language.LanguageCode → .Language.Locale），二是把 \u0026gt; [!WARNING] 这种 GitHub 风格的提示块渲染成真正的样式化提示块，而不是字面显示 [!WARNING]。所以 Hugo 版本不能低于 0.158。\n踩过的坑 迁移的代码本身没出什么大问题，正式的坑全在发布环节。\n坑一：把\u0026quot;构建输出目录\u0026quot;填进了\u0026quot;根目录\u0026quot;。 Cloudflare 的「根目录（Root directory）」指的是仓库里的构建工作目录，不是产物目录， 而且它在 clone 完成后立刻检查，不存在就直接失败：\nCloning repository... Failed: root directory not found 真正的原因更绕：我当时是在 Workers 的流程里建应用，那个表单里有「构建命令 / 部署命令 / 构建令牌」，没有「构建输出目录」这一栏。于是我顺手把 public 填进了名字最像的「根目录」， 还带了前导斜杠。正确的是在 Pages 里建：它有「构建输出目录」，根目录留空。\n坑二：域名差一个字母。 baseURL 写的是 www.kutake.com，而我的域名是 www.kutake.top。现象极其迷惑—— 首页能正常打开，点任何一篇文章都跳到另一个域名上去。因为 baseURL 会被写死进所有 绝对链接：文章地址、封面图、sitemap、RSS、og:image 全是它。\n注意\n部署前一定确认 hugo.yaml 里的 baseURL 和正式域名逐字一致（含协议和结尾斜杠）。 这个错误不会让构建失败，只会让所有链接悄悄指向别处。\n文章怎么写 这部分我最想重做。原来在 WordPress 后台点来点去，现在：\njust new \u0026#34;文章标题\u0026#34; # 新建，默认草稿 just new \u0026#34;文章标题\u0026#34; --tags 生活,摄影 --categories 随笔 just new \u0026#34;文章标题\u0026#34; --cover /media/2026/10/cover.jpg just new \u0026#34;文章标题\u0026#34; --publish # 直接发布 它会生成 content/posts/YYYY-MM-DD-\u0026lt;slug\u0026gt;.md 并填好 front matter。字段的形状定义在 blog/archetypes/posts.md 里，所以想加字段只改那一个文件——命令行工具和 Hugo 原生的 hugo new content 用的是同一份骨架。当前一篇新文章的 front matter 长这样：\n--- title: \u0026#34;文章标题\u0026#34; date: 2026-10-14T04:21:21+08:00 draft: false slug: \u0026#34;wordpress-to-hugo\u0026#34; # 决定网址 author: \u0026#34;kutake\u0026#34; categories: [\u0026#34;折腾\u0026#34;] tags: [\u0026#34;Hugo\u0026#34;, \u0026#34;Cloudflare\u0026#34;] cover: image: \u0026#34;/media/2026/10/cover.png\u0026#34; alt: \u0026#34;文章标题\u0026#34; caption: \u0026#34;\u0026#34; --- 两个约定：\n图片统一放 blog/static/media/YYYY/MM/，引用时写站内绝对路径 /media/YYYY/MM/xxx.png。 和迁移过来的 49 张旧图保持同一套规则，不用记两套。 草稿不会上线。draft: true 的文章在本地预览里能看到，但不会进正式构建；写完把 draft 改成 false，或者建的时候就加 --publish。 编辑器方面，仓库里放了一份 VS Code 片段（post / fm / cover / alert / more / img， 敲前缀按 Tab 展开），用 Obsidian 的话把 content/posts/ 当 vault 打开也行——粘图片最顺手。\n怎么发布 发布就是一次 git push：\n写文章 → git push → Cloudflare Pages 构建 → 自动上线 (hugo --gc --minify) Cloudflare Pages 那边的配置是固定的四项：\n项目 值 Framework preset Hugo Build command hugo --gc --minify Build output directory public 环境变量 HUGO_VERSION 0.167.0 根目录留空（仓库根就是 Hugo 站点根）。HUGO_VERSION 必须设，否则 Cloudflare 可能用 一个低于 0.158 的 Hugo 版本，构建直接报错。\n本地想先看一眼：\njust serve # http://localhost:1313，含草稿，保存即刷新 just build # 构建到 blog/public/ just verify # 校验产物：媒体链接、编码、时区、提示块 just verify 是我特意加的——迁移最怕的是\u0026quot;看起来能打开，其实一半图片是断的\u0026quot;。 它现在会检查 61 个点，包括 101 处 /media/ 引用是否都命中真实文件。\n一个细节：重新生成不会删掉你的稿子 因为 blog/content/ 里的 68 篇老文章是脚本生成的，而新文章是手写的，两者混在 同一个目录里，所以有个隐患：重新跑一次迁移脚本，手写的文章会不会被一起删掉？\n会。原来的实现就是 shutil.rmtree(content) 一把清空。所以我给它加了一个清单机制： 脚本把每一轮自己写出的文件记进 blog/.generated-manifest.json，下次只删清单里的东西。 手写文章和手动加的封面不在清单里，永远不会被碰。 清单丢了（比如新克隆的仓库）就干脆 什么都不删，只打印一句提示。只有 just reset 才会真正清空——那是个需要刻意敲的命令。\n技巧\n判断标准很简单：凡是脚本生成的，都在清单里；凡是你在编辑器里敲出来的，都不在。\nAI 参与了多少 这个站点的迁移基本是我和一个 coding agent 一起做的，顺手记一下账，因为\u0026quot;AI 到底花了多少 token\u0026quot;这件事平时很少有人给出真实数字。\nAgent：DSH（DeepSeek Harness）的 Web 客户端，一个能读写文件、执行命令、开子代理的 编码 agent。 模型：deepseek-v4-flash-vision-exp。 token 消耗分两段（下表是本文定稿时的快照）：\n阶段 模型调用次数 累计 token 迁移（解析 dump、写迁移工具、生成 68 篇文章） — 55.1M 本文所在会话（排查发布问题、做工具链、写这篇） 134 15.24M 15.24M 这个数字乍看很吓人，拆开看就不吓人了：\n项目 token 占比 缓存命中的上下文（cacheRead） 15,040,000 98.7% 新读入的输入 52,417 0.3% 模型输出 146,841 1.0% 也就是说，每一步都要把之前的对话和读过的文件重新读一遍，平均每次调用的输入是 113k token 的上下文 + 394 token 的新内容。真正\u0026quot;生成\u0026quot;出来的只有 0.20M。 上下文长度才是成本的主项——这也是为什么让 agent 少读无关文件、把大任务拆成子代理， 比你换一个更便宜的模型更省。\n这两个数字不是估的：harness 会把每次模型调用的 usage 明细写进会话日志，把 134 条记录 加起来就是上表的合计。\n顺手记下来的几个技术坑 留两个纯粹给未来的自己看：\nHugo 的 replace 和 replaceRE 参数顺序不一样。 replace 是 replace INPUT OLD NEW，所以不能用管道接（管道会把值追加到末尾，于是你拿到的是 一个静默错误的结果）；replaceRE 是 replaceRE PATTERN REPLACEMENT INPUT，可以接。 我第一版骨架里管道接了 replace，生成出来的标题是 \u0026quot;-\u0026quot;。 shutil.copytree 不会覆盖已存在的目录。 加了上面的\u0026quot;清单机制\u0026quot;之后，某些路径下目标 目录不会被预先删除，于是 copytree 直接抛 FileExistsError。要加 dirs_exist_ok=True。 最后 现在这个博客是：一份 Markdown、一个 Git 仓库、一次 git push，构建在 Cloudflare 的边缘 节点上，没有数据库、没有后台、没有插件需要升级。\n发文章的成本从\u0026quot;登录后台 → 找编辑器 → 担心格式\u0026quot;变成了\u0026quot;打开编辑器 → 敲 just new → 写完 push\u0026quot;。对我来说这才是这次迁移最值的地方。\n后面要做的还有两件：把 baseURL 换成正式域名后重跑一遍 sitemap，以及给旧文章补上封面图。\n","permalink":"https://www.kutake.top/archives/wordpress-to-hugo/","summary":"\u003cp\u003e这个博客原来跑在 WordPress 上。前几天我把它整个搬到了 Hugo，静态托管在 Cloudflare\nPages 上，顺手把「写文章」这件事也重新做了一遍。这篇算是迁移完成后的第一篇，把整条流水线\n从头到尾记一遍：数据怎么搬、Hugo 怎么构建、文章怎么写、发布怎么做，以及中间踩过的坑。\u003c/p\u003e","title":"把 WordPress 博客搬进 Hugo：迁移、构建与发布全记录"},{"content":"在使用 Pydantic 进行数据验证和设置时，你可能会遇到一个问题：当多个类有共同字段时，Pydantic 可能无法正确地区分这些类。让我通过一个简单的例子来解释这个问题。\n案例 我这里是 pydantic==1.10.11 环境，不确定是否在其他版本存在。\nfrom pydantic import BaseModel class A(BaseModel): a: str class B(BaseModel): a: str b: int class C(BaseModel): c: list[A | B] c = C(c=[A(a=\u0026#34;a1\u0026#34;), B(a=\u0026#34;a2\u0026#34;, b=2)]) print([type(x) for x in c.c]) 输出结果为 [\u0026lt;class '__main__.A'\u0026gt;, \u0026lt;class '__main__.A'\u0026gt;]，这意味着 B 类型的实例被错误地识别为 A 类型的实例。\n解决方案 为了解决这个问题，我们可以使用 typing.Annotated 和 pydantic.Field 的 discriminator 参数。这允许我们指定一个字段作为区分不同类的标识符。\nfrom typing import Annotated, Literal, Union from pydantic import BaseModel, Field class A(BaseModel): t: Literal[\u0026#34;A\u0026#34;] = \u0026#34;A\u0026#34; a: str class B(BaseModel): t: Literal[\u0026#34;B\u0026#34;] = \u0026#34;B\u0026#34; a: str b: int t = Annotated[Union[A, B], Field(discriminator=\u0026#34;t\u0026#34;)] class C(BaseModel): c: list[t] c = C(c=[A(a=\u0026#34;a1\u0026#34;), B(a=\u0026#34;a2\u0026#34;, b=2)]) print([type(x) for x in c.c]) Field的 discriminator必须是所有可能类型的公共字段且为 Literal类型。\n更简便的方式 如果你不想为每个类添加额外的标识字段，你可以考虑将共同字段提取到一个单独的基类中，然后让其他类继承这个基类。\nfrom pydantic import BaseModel class A(BaseModel): a: str class B(A): # 继承有公共字段的基类 a: str b: int class C(BaseModel): c: list[A] # 只需要标识基类 c = C(c=[A(a=\u0026#34;a1\u0026#34;), B(a=\u0026#34;a2\u0026#34;, b=2)]) print([type(x) for x in c.c]) 输出 [\u0026lt;class '__main__.A'\u0026gt;, \u0026lt;class '__main__.B'\u0026gt;]，这样，你就可以在不添加额外字段的情况下解决问题，同时也保持了代码的整洁和可读性。\n","permalink":"https://www.kutake.top/archives/%E5%9C%A8%E6%9C%89%E5%85%B1%E5%90%8C%E5%AD%97%E6%AE%B5%E4%B8%8B%E7%9A%84pydantic%E7%B1%BB%E8%BD%AC%E6%8D%A2%E4%B8%8D%E6%AD%A3%E7%A1%AE%E9%97%AE%E9%A2%98/","summary":"\u003cp\u003e在使用 Pydantic 进行数据验证和设置时，你可能会遇到一个问题：当多个类有共同字段时，Pydantic 可能无法正确地区分这些类。让我通过一个简单的例子来解释这个问题。\u003c/p\u003e","title":"在有共同字段下的pydantic类转换不正确问题"},{"content":"前因 由于python中没有方便快捷生成美观图片的方案，所以我使用了jinja2模板生成html后交给playwright生成网页图片，这其中，图片资源可以使用base64格式解决，而字体加载就难了。\n首先尝试了使用 file://xxx.ttf的方式尝试让游览器直接从文件系统中得到字体文件；但很明显，由于跨域问题，游览器是不会加载本地文件的；那能不能解决跨域呢？比如搭一个后端而不是单纯的jinja2模板，或者关闭游览器的csp/CORS拦截。前者虽然可以解决问题，但是增加的不是一两个方法而是一个新后端应用，维护起来相当麻烦；那后者呢？我从playwright的issue页查到的launch args --disable-web-security和 --disable-site-isolation-trials都不能解决问题。但翻到了一个比较巧妙的解决方案：Page Route | Playwright Python\n方法 RouteAPI本身是处理网页的请求相关的逻辑，比如拦截和修改请求，在这其中就可以拦截对字体的请求，然后返回字体数据，至于跨域，请求拦截意味着可以随意设置域名，不用考虑跨域问题。\nfont: Path await page.route( f\u0026#34;**/{font.name}\u0026#34;, lambda route: route.fulfill(body=font.read_bytes()), ) 这样当请求中含有字体文件名时，playwright就会拦截并返回字体数据给页面。\n封装 为了方便调用，对逻辑进行一个封装来让使用更加方便\nasync def font_event(font_list: list[str | Path], page: Page, debug: bool = False): path_list = [Path(font) for font in font_list] for font in path_list: await page.route( f\u0026#34;**/{font.name}\u0026#34;, lambda route: route.fulfill(body=font.read_bytes()), ) name_list = [Path(font).name for font in font_list] if not debug: return async def print_request(request: Request): for font in name_list: if font in request.url: logger.debug(f\u0026#34;Loading font {font}...\u0026#34;) return async def print_response(response: Response): for font in name_list: if font in response.url: logger.debug(f\u0026#34;Font {font} loaded successfully.\u0026#34;) return async def print_request_failed(request: Request): for font in name_list: if font in request.url: logger.error(f\u0026#34;Font {font} loading failed.\u0026#34;) return page.on(\u0026#34;request\u0026#34;, print_request) page.on(\u0026#34;response\u0026#34;, print_response) page.on(\u0026#34;requestfailed\u0026#34;, print_request_failed) 这里还封装了 debug来追踪字体请求状态，至于jinja出来的html也可以再包一层来达到不修改模板改变字体的目的，这里就不演示了。\n","permalink":"https://www.kutake.top/archives/%E5%9F%BA%E4%BA%8Eroute%E6%9D%A5%E8%A7%A3%E5%86%B3playwright%E7%9A%84%E9%9D%99%E6%80%81html%E8%B5%84%E6%BA%90%E5%8A%A0%E8%BD%BD%E9%97%AE%E9%A2%98/","summary":"\u003ch2 id=\"前因\"\u003e前因\u003c/h2\u003e\n\u003cp\u003e由于python中没有方便快捷生成美观图片的方案，所以我使用了jinja2模板生成html后交给playwright生成网页图片，这其中，图片资源可以使用base64格式解决，而字体加载就难了。\u003c/p\u003e","title":"基于Route来解决playwright的静态HTML资源加载问题"},{"content":"下面这个例子是，当release分支有commit发生时，自动获取当前应用的版本信息，并使用缓存，分别构建前端和rust，并将构建好的包上传至指定tag的release上。在 platform处定义平台，这里我只选定了Win\nreleaseDraft让提交的release只是一个草稿，可以手动更改后发布。\n这里前端用的Yarn，根据项目实际依赖工具来更改，以及应用名。\nname: \u0026#34;publish\u0026#34; on: push: branches: - release # This is the example from the readme. # On each push to the `release` branch it will create or update a GitHub release, build your app, and upload the artifacts to the release. jobs: publish-tauri: permissions: contents: write strategy: fail-fast: false matrix: platform: [windows-latest] runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v4 - name: setup node uses: actions/setup-node@v4 with: node-version: 20 - name: install Rust stable uses: dtolnay/rust-toolchain@stable - name: Rust cache uses: swatinem/rust-cache@v2 with: workspaces: \u0026#39;./src-tauri -\u0026gt; target\u0026#39; - name: install dependencies (ubuntu only) if: matrix.platform == \u0026#39;ubuntu-20.04\u0026#39; run: | sudo apt-get update sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf - uses: actions/cache@v2 with: path: \u0026#39;**/node_modules\u0026#39; key: ${{ runner.os }}-node-modules-${{ hashFiles(\u0026#39;**/yarn.lock\u0026#39;) }} - name: install frontend dependencies run: yarn install # change this to npm or pnpm depending on which one you use - name: build frontend run: yarn cross-env VITE_NETWORK_PROCESSOR=tauri vite build - uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} with: tagName: 应用名-v__VERSION__ # the action automatically replaces \\_\\_VERSION\\_\\_ with the app version releaseName: \u0026#34;应用名 v__VERSION__\u0026#34; releaseBody: \u0026#34;See the assets to download this version and install.\u0026#34; releaseDraft: true prerelease: false ","permalink":"https://www.kutake.top/archives/%E4%BD%BF%E7%94%A8github-workflows%E6%9E%84%E5%BB%BAtauri%E5%BA%94%E7%94%A8/","summary":"\u003cp\u003e下面这个例子是，当release分支有commit发生时，自动获取当前应用的版本信息，并使用缓存，分别构建前端和rust，并将构建好的包上传至指定tag的release上。在 \u003ccode\u003eplatform\u003c/code\u003e处定义平台，这里我只选定了Win\u003c/p\u003e","title":"使用Github Workflows构建Tauri应用"},{"content":"问题 当尝试传递 Uint8Array类型的图片数据给rust后端时，会发生 invalid type: map, expected a sequence\n原因在于，tauri将 Uint8Array是作为对象而不是一个 Vec，要接受它，需要使用 HashMap\u0026lt;String, u8\u0026gt;来接收。\n#[tauri::command] fn run(data: HashMap\u0026lt;String, u8\u0026gt;){ ... } 另一种方法 Array.from(new Uint8Array(await file.data.arrayBuffer()));\n#[tauri::command] fn run(data: Vec\u0026lt;u8\u0026gt;){ ... } 转换为 number[]就可以了\n","permalink":"https://www.kutake.top/archives/tauriuint8array%E4%B8%8E-vec%E7%9A%84%E5%9D%91/","summary":"\u003ch2 id=\"问题\"\u003e问题\u003c/h2\u003e\n\u003cp\u003e当尝试传递 \u003ccode\u003eUint8Array\u003c/code\u003e类型的图片数据给rust后端时，会发生 \u003ccode\u003einvalid type: map, expected a sequence\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e原因在于，tauri将 \u003ccode\u003eUint8Array\u003c/code\u003e是作为对象而不是一个 \u003ccode\u003eVec\u003c/code\u003e，要接受它，需要使用 \u003ccode\u003eHashMap\u0026lt;String, u8\u0026gt;\u003c/code\u003e来接收。\u003c/p\u003e","title":"[Tauri]Uint8Array与 Vec\u003cu8\u003e的坑"},{"content":"使用tokio::sync::Semaphore 信号量可以保证在某一时刻最多运行指定数量的并发任务。\n使用信号量时，需在初始化时指定信号灯(tokio中的SemaphorePermit)的数量，每当任务要执行时，将从中取走一个信号灯，当任务完成时(信号灯被drop)会归还信号灯。当某个任务要执行时，如果此时信号灯数量为0，则该任务将等待，直到有信号灯被归还。因此，信号量通常用来提供类似于限量的功能。\n例如，信号灯数量为1，表示所有并发任务必须串行运行，这种模式和互斥锁是类似的。再例如，信号灯数量设置为2，表示最多只有两个任务可以并发执行，如果有第三个任务，则必须等前两个任务中的某一个先完成。\n例如：\nuse chrono::Local; use std::sync::Arc; use tokio::{ self, runtime::Runtime, sync::Semaphore, time::{self, Duration}}; fn now() -\u0026gt; String { Local::now().format(\u0026#34;%F %T\u0026#34;).to_string() } fn main() { let rt = Runtime::new().unwrap(); rt.block_on(async { // 只有3个信号灯的信号量 let semaphore = Arc::new(Semaphore::new(3)); // 5个并发任务，每个任务执行前都先获取信号灯 // 因此，同一时刻最多只有3个任务进行并发 for i in 1..=5 { let semaphore = semaphore.clone(); tokio::spawn(async move { let _permit = semaphore.acquire().await.unwrap(); println!(\u0026#34;{}, {}\u0026#34;, i, now()); time::sleep(Duration::from_secs(1)).await; }); } time::sleep(Duration::from_secs(3)).await; }); } 互斥锁 Mutex实现 这是我在没有发现信号灯机制前用的方法，问的gpt，案例来自我自己的项目\nlet tasks = Arc::new(Mutex::new(0)); let mut handles = vec![]; for args in args_list { let app_handle = app.app_handle(); let real_path_str_copy = real_path_str.clone(); let tasks_ref = Arc::clone(\u0026amp;tasks); let handle = tokio::spawn(async move { let mut guard = tasks_ref.lock().await; while *guard \u0026gt;= max_tasks { drop(guard); task::yield_now().await; guard = tasks_ref.lock().await; } *guard += 1; drop(guard); run_task(app_handle, \u0026amp;args, \u0026amp;real_path_str_copy) .await .unwrap(); let mut guard = tasks_ref.lock().await; *guard -= 1; }); handles.push(handle); } for handle in handles { handle.await.unwrap(); } 嗯，复杂多了，用互斥锁来获取当前运行了多少task，当 *guard \u0026gt;= max_tasks成立时，使用 task::tield_now().await;来把当前task的控制权移交给tokio而不往下执行。\n怕while判断时，guard已经被丢弃，还在控制权移交的下一行增加了获取锁，rust不是很熟练，不是很清楚这里有没有起作用，因为理论上每一次 while循环 guard都离开了作用域，也就是会被自动 drop丢弃。\n不管怎么说，It just work，并发确实被控制在了 max_tasks以内。\nasync move的语法创建一个异步block，会自动将所有用到的外部变量的所有权移交到block内，所以需要在定于block前复制一份。\n","permalink":"https://www.kutake.top/archives/tokio%E5%BC%82%E6%AD%A5task%E6%8E%A7%E5%88%B6%E5%B9%B6%E5%8F%91%E6%95%B0%E9%87%8F/","summary":"\u003ch3 id=\"使用tokiosyncsemaphore\"\u003e使用tokio::sync::Semaphore\u003c/h3\u003e\n\u003cp\u003e信号量可以保证在某一时刻最多运行指定数量的并发任务。\u003c/p\u003e\n\u003cp\u003e使用信号量时，需在初始化时指定信号灯(tokio中的SemaphorePermit)的数量，每当任务要执行时，将从中取走一个信号灯，当任务完成时(信号灯被drop)会归还信号灯。当某个任务要执行时，如果此时信号灯数量为0，则该任务将等待，直到有信号灯被归还。因此，信号量通常用来提供类似于限量的功能。\u003c/p\u003e","title":"Tokio异步task控制并发数量"},{"content":"当你想从Tauri调用终端并运行类似 ls的命令的话，需要使用 @tauri-apps/api/shell的api，步骤如下\n编辑 tauri.conf.json下的 tauri.allowlist.shell\n{ \u0026#34;tauri\u0026#34;: { \u0026#34;allowlist\u0026#34;: { \u0026#34;shell\u0026#34;: { \u0026#34;all\u0026#34;: true, // enable all shell APIs \u0026#34;execute\u0026#34;: true, // enable process spawn APIs \u0026#34;sidecar\u0026#34;: true, // enable spawning sidecars \u0026#34;open\u0026#34;: true // enable opening files/URLs using the default program } } } } 其他api暂时不讨论，只说 execute，假设我们需要使用git命令（案例来自官网）\n编辑 scope定义好需要用到的命令，要是没有定义则无法调用\n{ \u0026#34;scope\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;run-git-commit\u0026#34;, \u0026#34;cmd\u0026#34;: \u0026#34;git\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;commit\u0026#34;, \u0026#34;-m\u0026#34;, { \u0026#34;validator\u0026#34;: \u0026#34;\\\\S+\u0026#34; }] } ] } 开始调用该命令\nimport { Command } from \u0026#39;@tauri-apps/api/shell\u0026#39; const c = new Command(\u0026#39;run-git-commit\u0026#39;, [\u0026#39;commit\u0026#39;, \u0026#39;-m\u0026#39;, \u0026#39;the commit message\u0026#39;]) const std = await c.execute(); std.stderr std.stdout std.code std.signal 你可以将 args设置为 true来使js端可以传入任意参数\n需要注意的是，我没有找到使用fs上定义的相对路径参数来在终端内使用的方式，所以目前只能使用一般的相对路径或绝对路径，比如 ./img.png和 E://img.png，但格式上需要注意。\n对于我正在使用的win端来说， Command内访问路径只能使用 \\单斜杠，不能 \\\\或 /，具体原因我也不清楚。\n具体表现就是，当 console.log的时候，打印出来的是 \\\\，要是 \\\\\\\\的话原字符串才是 \\\\，打印出 \\\\代表是 \\\n","permalink":"https://www.kutake.top/archives/tauri-%E8%B0%83%E7%94%A8%E7%BB%88%E7%AB%AFapi/","summary":"\u003cp\u003e当你想从Tauri调用终端并运行类似 \u003ccode\u003els\u003c/code\u003e的命令的话，需要使用 \u003ccode\u003e@tauri-apps/api/shell\u003c/code\u003e的api，步骤如下\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e编辑 \u003ccode\u003etauri.conf.json\u003c/code\u003e下的 \u003ccode\u003etauri.allowlist.shell\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;tauri\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;allowlist\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;shell\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;all\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"c1\"\u003e// enable all shell APIs\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;execute\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"c1\"\u003e// enable process spawn APIs\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;sidecar\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"c1\"\u003e// enable spawning sidecars\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;open\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e \u003cspan class=\"c1\"\u003e// enable opening files/URLs using the default program\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e其他api暂时不讨论，只说 \u003ccode\u003eexecute\u003c/code\u003e，假设我们需要使用git命令（案例来自官网）\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e编辑 \u003ccode\u003escope\u003c/code\u003e定义好需要用到的命令，要是没有定义则无法调用\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;scope\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;run-git-commit\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;cmd\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;git\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;args\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;commit\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;-m\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nt\"\u003e\u0026#34;validator\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\\\\S+\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e}]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e开始调用该命令\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003eCommand\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"nx\"\u003efrom\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;@tauri-apps/api/shell\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003ec\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nx\"\u003eCommand\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;run-git-commit\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;commit\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;-m\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;the commit message\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003estd\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kr\"\u003eawait\u003c/span\u003e \u003cspan class=\"nx\"\u003ec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eexecute\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003estd\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003estderr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003estd\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003estdout\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003estd\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecode\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003estd\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003esignal\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e你可以将 \u003ccode\u003eargs\u003c/code\u003e设置为 \u003ccode\u003etrue\u003c/code\u003e来使js端可以传入任意参数\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e","title":"Tauri 调用终端api"},{"content":"tauri支持从$APPCONFIG, $APPDATA, $APPLOCALDATA, $APPCACHE, $APPLOG, $AUDIO, $CACHE, $CONFIG, $DATA, $LOCALDATA, $DESKTOP, $DOCUMENT, $DOWNLOAD, $EXE, $FONT, $HOME, $PICTURE, $PUBLIC, $RUNTIME, $TEMPLATE, $VIDEO, $RESOURCE, $APP, $LOG, $TEMP 访问并读或文件夹和文件，具体措施如下\n编辑 tauri.conf.json内的tauri.allowlist.fs\n{ \u0026#34;tauri\u0026#34;: { \u0026#34;allowlist\u0026#34;: { \u0026#34;fs\u0026#34;: { \u0026#34;all\u0026#34;: true, // enable all FS APIs \u0026#34;readFile\u0026#34;: true, \u0026#34;writeFile\u0026#34;: true, \u0026#34;readDir\u0026#34;: true, \u0026#34;copyFile\u0026#34;: true, \u0026#34;createDir\u0026#34;: true, \u0026#34;removeDir\u0026#34;: true, \u0026#34;removeFile\u0026#34;: true, \u0026#34;renameFile\u0026#34;: true, \u0026#34;exists\u0026#34;: true } } } } 可以直接 all: true 来开放所有tauri文件api，也可以单独开放某个api\n编辑 scope开放具体访问权限\n{ \u0026#34;tauri\u0026#34;: { \u0026#34;allowlist\u0026#34;: { \u0026#34;fs\u0026#34;: { \u0026#34;scope\u0026#34;: [\u0026#34;$APPDATA/img/*\u0026#34;] } } } } 注意，这里开放了 $APPDATA下的 database访问权限，这样可以阻止程序访问任何路径，除了 database\n从js中访问\nimport { readBinaryFile, BaseDirectory } from \u0026#39;@tauri-apps/api/fs\u0026#39;; // 可以是任何api // Read the image file in the `$APPDATA/avatar.png` path const contents = await readBinaryFile(\u0026#39;img/avatar.png\u0026#39;, { dir: BaseDirectory.AppData }); // 注意！一定要加上BaseDirectory.AppData 开放了哪个目录权限，就用 dir: BaseDirectory来指定访问路径，至少用绝对路径访问的方法我还没有找到，当然，这种相对路径访问的方式更加安全\n我一开始尝试的是绝对路径访问，结果一直报无法找到目录，各种更改配置文件都无法解决，最后才发现关键在 { dir: BaseDirectory.AppData }上，与 exe文件同路径的话（也就是安装目录），是 $Resource ，通过它可以访问安装目录下的文件。\n","permalink":"https://www.kutake.top/archives/tauri-%E6%96%87%E4%BB%B6%E8%AE%BF%E9%97%AEapi/","summary":"\u003cp\u003etauri支持从\u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#appconfigdir\"\u003e\u003ccode\u003e$APPCONFIG\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#appdatadir\"\u003e\u003ccode\u003e$APPDATA\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#applocaldatadir\"\u003e\u003ccode\u003e$APPLOCALDATA\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#appcachedir\"\u003e\u003ccode\u003e$APPCACHE\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#applogdir\"\u003e\u003ccode\u003e$APPLOG\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#audiodir\"\u003e\u003ccode\u003e$AUDIO\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#cachedir\"\u003e\u003ccode\u003e$CACHE\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#configdir\"\u003e\u003ccode\u003e$CONFIG\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#datadir\"\u003e\u003ccode\u003e$DATA\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#localdatadir\"\u003e\u003ccode\u003e$LOCALDATA\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#desktopdir\"\u003e\u003ccode\u003e$DESKTOP\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#documentdir\"\u003e\u003ccode\u003e$DOCUMENT\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#downloaddir\"\u003e\u003ccode\u003e$DOWNLOAD\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#executabledir\"\u003e\u003ccode\u003e$EXE\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#fontdir\"\u003e\u003ccode\u003e$FONT\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#homedir\"\u003e\u003ccode\u003e$HOME\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#picturedir\"\u003e\u003ccode\u003e$PICTURE\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#publicdir\"\u003e\u003ccode\u003e$PUBLIC\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#runtimedir\"\u003e\u003ccode\u003e$RUNTIME\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#templatedir\"\u003e\u003ccode\u003e$TEMPLATE\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#videodir\"\u003e\u003ccode\u003e$VIDEO\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#resourcedir\"\u003e\u003ccode\u003e$RESOURCE\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#appdir\"\u003e\u003ccode\u003e$APP\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/path#logdir\"\u003e\u003ccode\u003e$LOG\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://tauri.app/zh-cn/v1/api/js/os#tempdir\"\u003e\u003ccode\u003e$TEMP\u003c/code\u003e\u003c/a\u003e 访问并读或文件夹和文件，具体措施如下\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e编辑 \u003ccode\u003etauri.conf.json\u003c/code\u003e内的\u003ca href=\"https://tauri.app/v1/api/config/#allowlistconfig.fs\"\u003e\u003ccode\u003etauri.allowlist.fs\u003c/code\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;tauri\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;allowlist\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;fs\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;all\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"c1\"\u003e// enable all FS APIs\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;readFile\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;writeFile\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;readDir\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;copyFile\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;createDir\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;removeDir\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;removeFile\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;renameFile\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;exists\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e可以直接 \u003ccode\u003eall: true\u003c/code\u003e 来开放所有tauri文件api，也可以单独开放某个api\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e编辑 \u003ccode\u003escope\u003c/code\u003e开放具体访问权限\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;tauri\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;allowlist\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;fs\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;scope\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;$APPDATA/img/*\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e注意，这里开放了 \u003ccode\u003e$APPDATA\u003c/code\u003e下的 \u003ccode\u003edatabase\u003c/code\u003e访问权限，这样可以阻止程序访问任何路径，除了 \u003ccode\u003edatabase\u003c/code\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e从js中访问\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003ereadBinaryFile\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eBaseDirectory\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"nx\"\u003efrom\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;@tauri-apps/api/fs\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 可以是任何api\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Read the image file in the `$APPDATA/avatar.png` path\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003econtents\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kr\"\u003eawait\u003c/span\u003e \u003cspan class=\"nx\"\u003ereadBinaryFile\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;img/avatar.png\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003edir\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003eBaseDirectory\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eAppData\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 注意！一定要加上BaseDirectory.AppData\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e开放了哪个目录权限，就用 \u003ccode\u003edir: BaseDirectory\u003c/code\u003e来指定访问路径，至少用绝对路径访问的方法我还没有找到，当然，这种相对路径访问的方式更加安全\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e","title":"Tauri 文件访问api"},{"content":"前言 这是一次从创建项目到测试打包上传Pypi的过程，记录一下。\n创建项目 这里我是用的是pdm这个包管理工具，创建项目的命令是pdm init，跟着引导一步步走，之后会生成一个pyproject.toml文件，里面有项目的一些基本信息，比如作者、版本号等。下面是一个示例：\nmkdir project cd project pdm init 之后生成的目录大概如下：\n. ├── pyproject.toml ├── pdm.lock └── src ├── project | └── __init__.py └── tests └── __init__.py 安装依赖这些就不赘述了。\n注意将源码放在src/project目录下，这样打包的时候会自动将src/project目录下的文件打包，而不是将整个项目打包。而且要加上build信息\n[tool.pdm.build] includes = [\u0026#34;src/project\u0026#34;] [tool.pdm.version] source = \u0026#34;file\u0026#34; path = \u0026#34;src/project/__init__.py\u0026#34; 测试 用例测试 这里我是用的是pytest框架，用pdm add -d pytest来将pytest安装在dev依赖上，这样不会影响到用户pdm install project的安装。\n测试代码放在tests目录下，测试用例文件名以test_开头，用例方法也以test_开头，比如\n# tests/test_add.py def test_add(): assert add(1, 2) == 3 使用pytest命令来进行测试\nmock测试 当测试数据依赖于某些API，且不方便测试时获得，那么可以使用mock来模拟数据。pytest的mock框架是pytest-mock，用pdm add -d pytest-mock来安装。\n# tests/test_api.py import requests def api(): return requests.get(\u0026#34;https://example.com\u0026#34;).json() def test_request(mocker): mock_response = mocker.Mock() mock_response.json.return_value = {\u0026#34;code\u0026#34;: 0, \u0026#34;data\u0026#34;: \u0026#34;success\u0026#34;} mocker.patch(\u0026#34;requests.get\u0026#34;, return_value=mock_response) assert api() == {\u0026#34;code\u0026#34;: 0, \u0026#34;data\u0026#34;: \u0026#34;success\u0026#34;} Coverage覆盖率测试 pytest的coverage测试框架是pytest-cov，用pdm add -d pytest-cov来安装。测试用例不需要改变，但需要加上--cov=src指定覆盖率测试的目录；需要分支覆盖率，需要加上--cov-branch；如果还要测试报告，命令就变成了pytest --cov=src --cov-branch --cov-report=html:tests/report/coverage。注意这里要加上pytest-html依赖才能生成html报告。\nTox多Py版本兼容性测试 如果你要测试你的项目在不同Python版本下是否能正常运行，那么在PDM的环境下，正好可以使用tox-pdm来进行测试，pdm add -d tox-pdm，然后创建tox.ini文件，内容如下：\n[tox] env_list = py{38, 39, 310, 311} [testenv] groups = dev commands = your test commands here env_list是你要测试的Python版本，groups是测试组，commands是测试命令，比如pytest。\n持续集成测试 其实就是运用Github Actions，下面有案例。\n测试报告 不同版本不同的测试报告生成起来会挤在一堆，我自己的做法是写一个测试脚本：\n# tests/__init__.py import sys from pathlib import Path import pytest import toml py_version = f\u0026#34;{sys.version_info.major}.{sys.version_info.minor}\u0026#34; version = toml.loads(Path(\u0026#34;pyproject.toml\u0026#34;).read_text(encoding=\u0026#34;utf-8\u0026#34;))[\u0026#34;project\u0026#34;][ \u0026#34;version\u0026#34; ] def test(): path = f\u0026#34;v{version}/{py_version}\u0026#34; args = [ \u0026#34;--cov=src\u0026#34;, \u0026#34;--cov-branch\u0026#34;, f\u0026#34;--cov-report=html:tests/report/{path}/coverage\u0026#34;, f\u0026#34;--html=tests/report/{path}/report.html\u0026#34;, ] args.extend(sys.argv[1:]) # Pass additional arguments to pytest if any pytest.main(args) 然后在pyproject.toml上添加\n[tool.pdm.scripts] test = {call = \u0026#34;tests:test\u0026#34;} 这样就可以使用pdm test来运行测试了。\n最后在tox.ini上把commands改成pdm test，报告就分别存放了。\n打包上传Pypi pdm build是打包，pdm publish是上传。但这里我用一个更复杂的例子来说明，做到持续集成。\nname: Upload Python Package on: release: types: [published] workflow_dispatch: inputs: version: description: Version Number required: true permissions: contents: write jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ env.GITHUB_REF }} token: ${{ secrets.GH_TOKEN }} - name: Config Git Username run: | git config user.name github-actions[bot] git config user.email github-actions[bot]@users.noreply.github.com - name: Setup Python uses: actions/setup-python@v4 with: python-version: \u0026#34;3.9.x\u0026#34; - name: Install dependencies run: | pip install pdm - name: Change version run: | python ./bump-version.py ${{ github.event.inputs.version }} git add . git diff-index --quiet HEAD || git commit -m \u0026#34;:sparkles: ${{ github.event.inputs.version }}\u0026#34; git push - name: Build and Publish run: | pdm publish --username __token__ --password ${{ secrets.PYPI_API_TOKEN }} - name: Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} tag: v${{ github.event.inputs.version }} run: | gh release create \u0026#34;$tag\u0026#34; \\ --repo=\u0026#34;$GITHUB_REPOSITORY\u0026#34; \\ --title=\u0026#34;${GITHUB_REPOSITORY#*/} ${tag#v}\u0026#34; \\ --generate-notes 流程是，输入version值后，他会提取项目设置里设置的secrets.GH_TOKEN和secrets.PYPI_API_TOKEN来进行打包、发布和创建release。其中还使用bump-version.py处理了__init__.py上的__version__信息。其他更多的命令，请自行搜索。\n","permalink":"https://www.kutake.top/archives/%E4%BB%8E%E5%88%9B%E5%BB%BA%E9%A1%B9%E7%9B%AE%E5%88%B0%E6%B5%8B%E8%AF%95%E6%89%93%E5%8C%85%E4%B8%8A%E4%BC%A0pypi/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e这是一次从创建项目到测试打包上传Pypi的过程，记录一下。\u003c/p\u003e\n\u003ch2 id=\"创建项目\"\u003e创建项目\u003c/h2\u003e\n\u003cp\u003e这里我是用的是\u003ccode\u003epdm\u003c/code\u003e这个包管理工具，创建项目的命令是\u003ccode\u003epdm init\u003c/code\u003e，跟着引导一步步走，之后会生成一个\u003ccode\u003epyproject.toml\u003c/code\u003e文件，里面有项目的一些基本信息，比如作者、版本号等。下面是一个示例：\u003c/p\u003e","title":"从创建项目到测试打包上传Pypi"},{"content":"要是搜ubuntu+ffmpeg基本都会遇到这个ppa:savoury1/ffmpeg6，然后就被坑，正确的做法是，下\n这个\n基本上能搜到的源都已经需要付费了，不然无法直接安装最新的ffmpeg\n","permalink":"https://www.kutake.top/shuoshuo/509/","summary":"\u003cp\u003e要是搜ubuntu+ffmpeg基本都会遇到这个ppa:savoury1/ffmpeg6，然后就被坑，正确的做法是，下\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/BtbN/FFmpeg-Builds\"\u003e这个\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e基本上能搜到的源都已经需要付费了，不然无法直接安装最新的ffmpeg\u003c/p\u003e","title":"要是搜ubuntu+ffmpeg基本都会遇到这个ppa:savoury1/ffm…"},{"content":"一般 try: import numpy except ImportError: print(\u0026#34;numpy is not installed\u0026#34;) 更推荐 from importlib.util import find_spec if find_spec(\u0026#34;numpy\u0026#34;) is not None: print(\u0026#34;numpy is installed\u0026#34;) else: print(\u0026#34;numpy is not installed\u0026#34;) ","permalink":"https://www.kutake.top/archives/%E6%9B%B4%E4%BC%98%E9%9B%85%E7%9A%84%E5%8C%85%E5%8F%AF%E7%94%A8%E6%80%A7%E6%A3%80%E6%B5%8B/","summary":"\u003ch2 id=\"一般\"\u003e一般\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003enumpy\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eexcept\u003c/span\u003e \u003cspan class=\"ne\"\u003eImportError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;numpy is not installed\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"更推荐\"\u003e更推荐\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003eimportlib.util\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003efind_spec\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003efind_spec\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;numpy\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"ow\"\u003eis\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;numpy is installed\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;numpy is not installed\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"更优雅的包可用性检测"},{"content":"安装 pip install rich # PDM pdm add rich 文档 \u0026amp; 源码\n基础 from rich import print print(\u0026#34;Hello World\u0026#34;) Rich功能非常丰富，从日志处理到富文本显示甚至动态的终端界面。\n要使用Rich用做界面的话，一般是这样的：\nfrom rich.console import Console from rich.layout import Layout from rich.panel import console = Console() panel = Panel(name=\u0026#34;panel_name\u0026#34;) layout = Layout(panel,name=\u0026#34;layout_name\u0026#34;) 界面Layout和面板Panel是经常要用到的东西，Layout作为界面布局，Panel内展示元素并风格化，比如经常使用的表格Table；要实现动态变化而不是print需要用到Live。\n然后是一些小记录。\n进度条 Process 这里展示的是一个模拟文件下载的进度条\nfrom time import sleep from rich.panel import Panel from rich.progress import ( BarColumn, FileSizeColumn, Progress, TextColumn, TimeRemainingColumn, TransferSpeedColumn, ) class MyProgress(Progress): def get_renderables(self): yield Panel(self.make_tasks_table(self.tasks)) text_column = TextColumn(\u0026#34;{task.description}\u0026#34;) bar_column = BarColumn(bar_width=None) progress = MyProgress( text_column, \u0026#34;•\u0026#34;, FileSizeColumn(), bar_column, TransferSpeedColumn(), \u0026#34;•\u0026#34;, TimeRemainingColumn(), expand=True, ) # 描述文本 • 文件大小组件 进度条 速度展示 • 剩余时间 自动扩充 with progress: n = 0 file_size = 1024 * 1024 * 1024 task_id = progress.add_task(f\u0026#34;Downloading file {n}\u0026#34;, total=file_size) for n in range(100): progress.update(task_id, completed=n / 100 * file_size, description=f\u0026#34;{n}%\u0026#34;) sleep(0.1) 可以看到progress提供了非常多的工具来帮助构建进度条\n转圈动画 Spinner 一个基本的spinner\nfrom rich.spinner import Spinner spinner = Spinner(\u0026#34;dots\u0026#34;) 你可以将它装载到Table上，比如\nfrom rich.console import Console from rich.spinner import Spinner from rich.table import Table table = Table(title=\u0026#34;File\u0026#34;) table.add_column(\u0026#34;ID\u0026#34;, justify=\u0026#34;right\u0026#34;, style=\u0026#34;cyan\u0026#34;, no_wrap=True) table.add_column(\u0026#34;Name\u0026#34;, style=\u0026#34;magenta\u0026#34;) table.add_column(\u0026#34;Size\u0026#34;, justify=\u0026#34;right\u0026#34;, style=\u0026#34;green\u0026#34;) table.add_column(\u0026#34;Status\u0026#34;, justify=\u0026#34;center\u0026#34;) spinner = Spinner(\u0026#34;dots\u0026#34;) table.add_row(\u0026#34;1\u0026#34;, \u0026#34;1.txt\u0026#34;, \u0026#34;0.2MB\u0026#34;, spinner) table.add_row(\u0026#34;2\u0026#34;, \u0026#34;2.txt\u0026#34;, \u0026#34;1.1MB\u0026#34;, spinner) table.add_row(\u0026#34;3\u0026#34;, \u0026#34;3.txt\u0026#34;, \u0026#34;0.3MB\u0026#34;, spinner) table.add_row(\u0026#34;4\u0026#34;, \u0026#34;4.txt\u0026#34;, \u0026#34;0.8MB\u0026#34;, spinner) console = Console() console.print(table) 注意，此时你是无法看到旋转动画的，因为使用的是print，动画是需要在with console或者with live中操作\nimport time from rich.console import Console from rich.spinner import Spinner console = Console() spinner = Spinner(\u0026#34;dots\u0026#34;) with console.status(spinner): # 模拟进行中的操作 time.sleep(5) console.print(\u0026#34;Task completed!\u0026#34;) 此时动画正常\n配合Loguru输出日志 示例：来源Github\nfrom loguru import logger from rich.console import Console console = Console() logger.remove() def _log_formatter(record: dict) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;Log message formatter\u0026#34;\u0026#34;\u0026#34; color_map = { \u0026#34;TRACE\u0026#34;: \u0026#34;dim blue\u0026#34;, \u0026#34;DEBUG\u0026#34;: \u0026#34;cyan\u0026#34;, \u0026#34;INFO\u0026#34;: \u0026#34;bold\u0026#34;, \u0026#34;SUCCESS\u0026#34;: \u0026#34;bold green\u0026#34;, \u0026#34;WARNING\u0026#34;: \u0026#34;yellow\u0026#34;, \u0026#34;ERROR\u0026#34;: \u0026#34;bold red\u0026#34;, \u0026#34;CRITICAL\u0026#34;: \u0026#34;bold white on red\u0026#34;, } lvl_color = color_map.get(record[\u0026#34;level\u0026#34;].name, \u0026#34;cyan\u0026#34;) return ( \u0026#34;[not bold green]{time:YYYY/MM/DD HH:mm:ss}[/not bold green] | {level.icon}\u0026#34; + f\u0026#34; - [{lvl_color}]{{message}}[/{lvl_color}]\u0026#34; ) log_handler = logger.add( console.print, # type: ignore level=\u0026#34;DEBUG\u0026#34;, format=_log_formatter, # type: ignore colorize=True, ) logger.debug(\u0026#34;Debug message\u0026#34;) logger.info(\u0026#34;Info message\u0026#34;) logger.warning(\u0026#34;Warning message\u0026#34;) logger.error(\u0026#34;Error message\u0026#34;) 用户输入 Prompt password = Prompt.ask(\u0026#34;input your password (hideen)\u0026#34;, password=True) 注意，Prompt操作需要停止Live，比如live.stop()\nTable行数过多的一种解决方案 来自：StackOverFlow\n每次新增行，表格会始终展示最新行，要是超过控制台展示高度，则会截断上面的旧行；是一个很好的参考\nimport os import time from collections import deque from rich.console import Console from rich.layout import Layout from rich.live import Live from rich.table import Table def generate_table(rows): layout = Layout() console = Console() table = Table() table.add_column(\u0026#34;Time\u0026#34;) table.add_column(\u0026#34;Message\u0026#34;) rows = list(rows) # This would also get the height: # render_map = layout.render(console, console.options) # render_map[layout].region.height n_rows = os.get_terminal_size()[1] while n_rows \u0026gt;= 0: table = Table() table.add_column(\u0026#34;Time\u0026#34;) table.add_column(\u0026#34;Message\u0026#34;) for row in rows[-n_rows:]: table.add_row(*row) layout.update(table) render_map = layout.render(console, console.options) render_length = len(render_map[layout].render[-1]) if render_length \u0026gt; 2: # The table is overflowing n_rows -= 1 else: break return table width, height = os.get_terminal_size() messages = deque(maxlen=height - 4) # save space for header and footer with Live(generate_table(messages), refresh_per_second=5) as live: for i in range(100): time.sleep(0.2) messages.append((time.asctime(), f\u0026#34;Event {i:03d}\u0026#34;)) live.update(generate_table(messages)) 一个复杂界面的案例 来源：Github\n我修改了一点，以保证Type Hint正确\nimport logging import os from datetime import datetime from pathlib import Path from time import sleep import numpy as np from rich.console import Console from rich.layout import Layout from rich.live import Live from rich.panel import Panel from rich.progress import ( BarColumn, Progress, SpinnerColumn, TextColumn, TimeRemainingColumn, ) from rich.table import Table def get_file_handler(log_dir: Path) -\u0026gt; logging.FileHandler: log_format = ( \u0026#34;[%(asctime)s]-[%(funcName)s(%(lineno)d)]-[%(levelname)s]-[%(message)s]\u0026#34; ) log_file = log_dir / \u0026#34;test.log\u0026#34; file_handler = logging.FileHandler(log_file) file_handler.setFormatter(logging.Formatter(log_format)) return file_handler def get_logger(log_dir: Path, console: Console) -\u0026gt; logging.Logger: logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) logger.addHandler(get_file_handler(log_dir)) return logger def make_layout() -\u0026gt; Layout: layout = Layout(name=\u0026#34;root\u0026#34;) layout.split(Layout(name=\u0026#34;header\u0026#34;, size=3), Layout(name=\u0026#34;main\u0026#34;)) layout[\u0026#34;main\u0026#34;].split_row(Layout(name=\u0026#34;leftside\u0026#34;), Layout(name=\u0026#34;termoutput\u0026#34;)) layout[\u0026#34;leftside\u0026#34;].split_column( Layout(name=\u0026#34;stats\u0026#34;, ratio=2), Layout(name=\u0026#34;progbar\u0026#34;) ) return layout def get_stats() -\u0026gt; Table: rand_arr = np.random.randint(low=2, high=10, size=(10, 3)) + np.random.random( (10, 3) ) stats_table = Table( expand=True, show_header=True, header_style=\u0026#34;bold\u0026#34;, title=\u0026#34;[magenta][b]Hot Stats![/b]\u0026#34;, highlight=True, ) stats_table.add_column(\u0026#34;Column\u0026#34;, justify=\u0026#34;right\u0026#34;) stats_table.add_column(\u0026#34;Mean\u0026#34;, justify=\u0026#34;center\u0026#34;) stats_table.add_column(\u0026#34;Std\u0026#34;, justify=\u0026#34;center\u0026#34;) stats_table.add_column(\u0026#34;Max\u0026#34;, justify=\u0026#34;center\u0026#34;) stats_table.add_column(\u0026#34;Min\u0026#34;, justify=\u0026#34;center\u0026#34;) for col in range(rand_arr.shape[1]): stats_table.add_row( f\u0026#34;Col {col}\u0026#34;, f\u0026#34;{rand_arr[:, col].mean():.2f}\u0026#34;, f\u0026#34;{rand_arr[:, col].std():.2f}\u0026#34;, f\u0026#34;{rand_arr[:, col].max():.2f}\u0026#34;, f\u0026#34;{rand_arr[:, col].min():.2f}\u0026#34;, ) return stats_table # Old code for reading a the last line of a log file. # def get_last_log(logger) -\u0026gt; str: # log_file_path = logger.handlers[0].baseFilename # with open(log_file_path, \u0026#34;r\u0026#34;) as file: # log_entries = file.readlines() # last_entry = log_entries[-1].strip() if log_entries else \u0026#34;\u0026#34; # return last_entry class make_header: \u0026#34;\u0026#34;\u0026#34;Display header with clock.\u0026#34;\u0026#34;\u0026#34; def __rich__(self) -\u0026gt; Panel: grid = Table.grid(expand=True) grid.add_column(justify=\u0026#34;center\u0026#34;, ratio=1) grid.add_column(justify=\u0026#34;right\u0026#34;) grid.add_row( \u0026#34;[b]Super[/b] cool application\u0026#34;, datetime.now().ctime().replace(\u0026#34;:\u0026#34;, \u0026#34;[blink]:[/]\u0026#34;), ) return Panel(grid, style=\u0026#34;red on black\u0026#34;) class MainTableHandler(logging.Handler): def __init__(self, main_table: Table, layout: Layout, log_level: str): super().__init__() self.main_table = main_table self.log_list = [] self.layout = layout self.log_format = ( \u0026#34;%(asctime)s-(%(funcName)s)-%(lineno)d-%(levelname)s-[%(message)s]\u0026#34; ) self.setLevel(log_level) # Could set colors for levels here. def emit(self, record): record.asctime = record.asctime.split(\u0026#34;,\u0026#34;)[0] # msg = self.format(record) #if you want just the message info switch comment lines msg = self.log_format % record.__dict__ tsize = os.get_terminal_size().lines // 2 if len(self.log_list) \u0026gt; tsize: self.log_list.append(msg) self.log_list.pop(0) self.main_table = redraw_main_table(self.log_list) self.layout[\u0026#34;termoutput\u0026#34;].update(Panel(self.main_table, border_style=\u0026#34;red\u0026#34;)) else: self.main_table.add_row(msg) self.log_list.append(msg) def redraw_main_table(temp_list: list) -\u0026gt; Table: main_table = Table( expand=True, show_header=False, header_style=\u0026#34;bold\u0026#34;, title=\u0026#34;[blue][b]Log Entries[/b]\u0026#34;, highlight=True, ) main_table.add_column(\u0026#34;Log Entries\u0026#34;) for row in temp_list: main_table.add_row(row) return main_table def main(): console = Console(color_system=\u0026#34;truecolor\u0026#34;) logger = get_logger(Path.cwd(), console=console) main_table = Table( expand=True, show_header=False, header_style=\u0026#34;bold\u0026#34;, title=\u0026#34;[blue][b]Log Messages[/b]\u0026#34;, highlight=True, ) main_table.add_column(\u0026#34;Log Output\u0026#34;) my_progress_bar = Progress( SpinnerColumn(), TextColumn(\u0026#34;{task.description}\u0026#34;), BarColumn(), \u0026#34;time remain:\u0026#34;, TimeRemainingColumn(), TextColumn(\u0026#34;[progress.percentage]{task.percentage:\u0026gt;3.0f}%\u0026#34;), ) my_counter = range(30) my_task = my_progress_bar.add_task(\u0026#34;The jobs\u0026#34;, total=len(my_counter)) progress_table = Table.grid(expand=True) progress_table.add_row( Panel( my_progress_bar, title=\u0026#34;Super awesome progress bar\u0026#34;, border_style=\u0026#34;green\u0026#34;, padding=(1, 1), ) ) stats_table = get_stats() layout = make_layout() layout[\u0026#34;header\u0026#34;].update(make_header()) layout[\u0026#34;progbar\u0026#34;].update(Panel(progress_table, border_style=\u0026#34;green\u0026#34;)) layout[\u0026#34;termoutput\u0026#34;].update(Panel(main_table, border_style=\u0026#34;blue\u0026#34;)) layout[\u0026#34;stats\u0026#34;].update(Panel(stats_table, border_style=\u0026#34;magenta\u0026#34;)) with Live(layout, refresh_per_second=10, screen=True) as live: # Add MainTableHandler to logger logger.addHandler(MainTableHandler(main_table, layout, logger.level)) # type: ignore for count in my_counter: sleep(1) logger.info(f\u0026#34;I made it to count {count}\u0026#34;) my_progress_bar.update(my_task, completed=count) if count % 5 == 0: logger.warning(\u0026#34;Gettin some STATS\u0026#34;) stats_table = get_stats() layout[\u0026#34;stats\u0026#34;].update(Panel(stats_table, border_style=\u0026#34;magenta\u0026#34;)) logger.critical(\u0026#34;Stranger Danger\u0026#34;) live.refresh() logger.info(\u0026#34;Done logging.\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() 这个示例可以说好，常用的都有，是个非常好的参考。\n","permalink":"https://www.kutake.top/archives/rich-%E4%BD%BF%E7%94%A8py%E5%9C%A8%E7%BB%88%E7%AB%AF%E4%B8%8A%E5%B1%95%E7%A4%BA%E5%AF%8C%E6%96%87%E6%9C%AC%E7%95%8C%E9%9D%A2/","summary":"\u003ch2 id=\"安装\"\u003e安装\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003epip\u003c/span\u003e \u003cspan class=\"n\"\u003einstall\u003c/span\u003e \u003cspan class=\"n\"\u003erich\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# PDM\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003epdm\u003c/span\u003e \u003cspan class=\"n\"\u003eadd\u003c/span\u003e \u003cspan class=\"n\"\u003erich\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ca href=\"https://rich.readthedocs.io/en/latest/introduction.html\"\u003e文档\u003c/a\u003e \u0026amp; \u003ca href=\"https://github.com/Textualize/rich\"\u003e源码\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"基础\"\u003e基础\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003erich\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Hello World\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eRich功能非常丰富，从日志处理到富文本显示甚至动态的终端界面。\u003c/p\u003e\n\u003cp\u003e要使用Rich用做界面的话，一般是这样的：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003erich.console\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003erich.layout\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eLayout\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003erich.panel\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003econsole\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003epanel\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ePanel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;panel_name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elayout\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eLayout\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epanel\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;layout_name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e界面\u003ccode\u003eLayout\u003c/code\u003e和面板\u003ccode\u003ePanel\u003c/code\u003e是经常要用到的东西，\u003ccode\u003eLayout\u003c/code\u003e作为界面布局，\u003ccode\u003ePanel\u003c/code\u003e内展示元素并风格化，比如经常使用的表格\u003ccode\u003eTable\u003c/code\u003e；要实现动态变化而不是print需要用到\u003ccode\u003eLive\u003c/code\u003e。\u003c/p\u003e","title":"Rich 使用PY在终端上展示富文本界面"},{"content":"服务器快到期了，感觉可以用vercel+Hexo的方案解决服务器问题... 但wordpress主题好香啊...\n","permalink":"https://www.kutake.top/shuoshuo/501/","summary":"\u003cp\u003e服务器快到期了，感觉可以用vercel+Hexo的方案解决服务器问题... 但wordpress主题好香啊...\u003c/p\u003e","title":"服务器快到期了，感觉可以用vercel+Hexo的方案解决服务器问题... 但w…"},{"content":"需求 以我的bot来说：\nBot: ├─config ├─core ├─modules │ ├─required │ │ ├─BotManager │ │ ├─BotStatus │ │ ├─ExceptionCatch │ │ ├─... │ └─self │ ├─Modules1 │ ├─Modules2 │ ├─... ├─resources ├─service └─utils 模块统一放在modules下，required是bot运行必须的模块，self是可卸载安装的模块。\n当有模块涉及到需要数据库的操作时，我设想的是，每个模块的数据源不放在一起，如果是sqlite的话单独以db.sqlite的形式存放在本地，远程DB的话就不需要考虑存放。\n好处是，解耦（大概？），不同数据源可以带来性能提升，迁移维护时可以先关闭某个模块而不是整个程序。\n单说实现的话也挺简单，维护一个dict数据，使用模块名作为key，实际DB对象(存放数据源信息)作为value，之后就是建个类生成和管理。问题是模块需要有实际的DB对象，才能进行操作，能不能让模块和数据源建立联系，这样模块不需要自己持有DB，只构建实体类和调用公共CURD方法。\n模块统一放在modules下。其中，required是bot运行必须的模块，而self则是可卸载安装的模块。\n当某个模块涉及到需要数据库操作时，我设想将该模块的数据源单独存放。如果使用sqlite，则将其存储在本地，并命名为db.sqlite；如果使用远程DB，则无需考虑存储位置。\n这样做有以下好处：\n解耦（大概？） 不同数据源可以带来性能提升 迁移维护时可以先关闭某个模块而不是整个程序。 具体实现方法如下：\n维护一个字典数据结构； 使用模块名作为键值； 使用实际数据库对象作为值； 建立一个类来生成和管理字典。 但问题在于：每个模块都需要有实际数据库对象才能进行操作。因此我们需要让每个模块与其对应的数据库建立联系，并且只构建实体类和调用公共CURD方法即可。\n思路 模块名与模块所在的文件夹是一一对应的。这也可以让模块的文件夹名与数据源一一对应。当模块内的方法调用公共CURD方法时，它能够从模块文件夹名中找到数据源。核心就是inspect模块：\nimport inspect # 从当前调用帧中寻找上一个调用帧的代码所在的文件名 inspect.currentframe().f_back.f_code.co_filename 然后可以写个方法：\nimport inspect from pathlib import Path __current_dir = Path(__file__).parent def print_caller_info(): last_frame = None back_frame = None try: for _ in range(3): if not last_frame: last_frame = inspect.currentframe() assert last_frame back_frame = last_frame.f_back assert back_frame back_dir = Path(back_frame.f_code.co_filename).parent if back_dir == __current_dir: last_frame = back_frame continue else: print( f\u0026#34;Caller: {back_frame.f_code.co_name} in {back_frame.f_code.co_filename}\u0026#34; ) print(\u0026#34;loop end\u0026#34;) finally: del last_frame del back_frame 由于CURD方法的重复包装，有可能上上帧才是真正的模块方法。辨别方法就是对比该方法所在的文件夹和上个帧所在的文件夹。如果相同，则继续对比上一个；不一样则说明该帧为模块的调用帧。\n目前仅记录这一用法，不准备实际更新为这种方法，所以可能存在未知的错误。\n","permalink":"https://www.kutake.top/archives/%E4%B8%80%E4%B8%AApy%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90%E7%9A%84%E7%BB%9F%E4%B8%80%E5%85%A5%E5%8F%A3%E6%80%9D%E8%B7%AF/","summary":"\u003ch2 id=\"需求\"\u003e需求\u003c/h2\u003e\n\u003cp\u003e以我的bot来说：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eBot:\n├─config\n├─core\n├─modules\n│  ├─required\n│  │  ├─BotManager\n│  │  ├─BotStatus\n│  │  ├─ExceptionCatch\n│  │  ├─...\n│  └─self\n│      ├─Modules1\n│      ├─Modules2\n│      ├─...\n├─resources\n├─service\n└─utils\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e模块统一放在\u003ccode\u003emodules\u003c/code\u003e下，\u003ccode\u003erequired\u003c/code\u003e是bot运行必须的模块，\u003ccode\u003eself\u003c/code\u003e是可卸载安装的模块。\u003c/p\u003e","title":"一个PY多数据源的统一入口思路"},{"content":"Redis是一种高性能的内存数据库，它支持多种数据结构，如字符串、列表、集合、散列、有序集合等。Redis的优点是速度快，操作简单，功能丰富，但是它也有一个缺点，就是数据都存储在内存中，一旦服务器宕机或者重启，数据就会丢失。为了解决这个问题，Redis提供了两种持久化机制，分别是RDB（快照）和AOF（追加文件）。\nRDB RDB持久化是指定时将内存中的数据保存到磁盘上的一个二进制文件中，这个文件可以用来备份或者迁移数据。RDB的优点是文件紧凑，恢复速度快，而且可以设置不同的保存频率，根据数据的重要性和容忍的数据丢失程度来选择。RDB的缺点是在保存过程中会阻塞主进程，影响性能，而且如果在两次保存之间发生故障，会丢失最近的数据。\nAOF AOF持久化是将每一条执行的写命令追加到一个文本文件中，这个文件可以用来重放命令来恢复数据。AOF的优点是可以实现实时或者近实时的持久化，保证数据的完整性，而且文件格式简单，易于理解和修复。AOF的缺点是文件体积大，恢复速度慢，而且在写入过程中可能会出现写入失败或者数据不一致的情况。\nRDB\u0026amp;AOF Redis支持同时使用RDB和AOF两种持久化方式，这样可以兼顾数据安全和性能。当服务器启动时，如果存在AOF文件，Redis会优先使用AOF文件来恢复数据，因为AOF文件包含了更完整的数据。如果不存在AOF文件，Redis会使用RDB文件来恢复数据。\nRedis的持久化机制可以根据不同的场景和需求来配置和调整，具体的配置参数和使用方法可以参考官方文档：https://redis.io/topics/persistence。\n","permalink":"https://www.kutake.top/archives/redis%E6%8C%81%E4%B9%85%E5%8C%96%E7%AD%96%E7%95%A5/","summary":"\u003cp\u003eRedis是一种高性能的内存数据库，它支持多种数据结构，如字符串、列表、集合、散列、有序集合等。Redis的优点是速度快，操作简单，功能丰富，但是它也有一个缺点，就是数据都存储在内存中，一旦服务器宕机或者重启，数据就会丢失。为了解决这个问题，Redis提供了两种持久化机制，分别是RDB（快照）和AOF（追加文件）。\u003c/p\u003e","title":"Redis持久化策略"},{"content":"非多阶段 以node项目为例：\nFROM alpine RUN mkdir -p /opt/app WORKDIR /opt/app COPY package.json package-lock.json ./ RUN apk add --no-cache --update nodejs npm RUN apk add --no-cache \\ gcc \\ g++ \\ python3=3.10.10-r0 \\ make=4.3-r1 \\ pkgconf COPY . . RUN npm install --production EXPOSE 8080 CMD [\u0026#34;node\u0026#34;, \u0026#34;main.js\u0026#34;] 这样虽然可以使用，但是很多依赖是生产环境下不需要受用的，比如build阶段的gcc以及npm等等\n多阶段构建 doc： 多阶段构建 (docker.com)\n在Docker 17.05版本以后，就可以使用Multi-stage builds（多阶段构建）\nFROM alpine AS builder RUN mkdir -p /opt/app WORKDIR /opt/app COPY package.json package-lock.json ./ RUN apk add --no-cache --update nodejs npm RUN apk add --no-cache \\ gcc \\ g++ \\ python3=3.10.10-r0 \\ make=4.3-r1 \\ pkgconf RUN npm install --production FROM alpine RUN mkdir -p /opt/app WORKDIR /opt/app RUN apk add --no-cache --update nodejs COPY --from=builder /opt/app/node_modules ./node_modules COPY . . EXPOSE 8080 CMD [\u0026#34;node\u0026#34;, \u0026#34;main.js\u0026#34;] 将build与实际运行环境剥离出来了，第一个阶段作为builder，将node依赖install好，生成node_modules，然后在实际运行容器中COPY --from=builder /opt/app/node_modules ./node_modules将依赖复制进来，这样各种编译依赖就会被舍弃，npm也不用出现在运行环境中，减少了包体积\n这样做的好处不止是减少包体积，在其他项目中还可以隐藏源代码，生产环境只有编译好的可执行文件。\n","permalink":"https://www.kutake.top/archives/docker%E5%A4%9A%E9%98%B6%E6%AE%B5%E6%9E%84%E5%BB%BA/","summary":"\u003ch2 id=\"非多阶段\"\u003e非多阶段\u003c/h2\u003e\n\u003cp\u003e以node项目为例：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-dockerfile\" data-lang=\"dockerfile\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eFROM\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003ealpine\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eRUN\u003c/span\u003e mkdir -p /opt/app\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eWORKDIR\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e/opt/app\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eCOPY\u003c/span\u003e package.json package-lock.json ./\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eRUN\u003c/span\u003e apk add --no-cache --update nodejs npm\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eRUN\u003c/span\u003e apk add --no-cache \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        gcc \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        g++ \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nv\"\u003epython3\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e3.10.10-r0 \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nv\"\u003emake\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e4.3-r1  \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        pkgconf \u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eCOPY\u003c/span\u003e . .\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eRUN\u003c/span\u003e npm install --production\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eEXPOSE\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e8080\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eCMD\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;node\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;main.js\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这样虽然可以使用，但是很多依赖是生产环境下不需要受用的，比如build阶段的\u003ccode\u003egcc\u003c/code\u003e以及\u003ccode\u003enpm\u003c/code\u003e等等\u003c/p\u003e","title":"docker多阶段构建"},{"content":"描述 print(user) # name=user,age=18, ... session.commit() print(user) # empty 原因 sqlalchemy为了保持session内的数据同步，默认了expire_on_commit为True，它表现在：\n# 查询 b = session.query(User).filter(User.id==2).first() # 更改数据 b.name = \u0026#34;user_b\u0026#34; session.add(b) session.flush() session.commit() # commit后再次访问，其实是重新进行了select操作 b.name # user_b expire_on_commit可以用来更改 SQLAlchemy 的对象刷新机制，默认值为True即在session调用commit之后会主动将同一个session在commit之前查询得到的 ORM 对象的_sa_instance_state.expire属性设置为Flase，再次读取该对象属性时将重载这个对象，方法是重新调用之前的查询语句。\n这样做确实可以很方便的保持数据同步，但是实际中经常会把得到的数据返回到session以外，此时commit操作就会导致数据被清空。\n解决 session = Session(engine, expire_on_commit=False) 即可避免内存中的数据被清空和更改。\n","permalink":"https://www.kutake.top/archives/sqlalchemysqlmodel%E7%9A%84commit%E5%90%8E%E6%95%B0%E6%8D%AE%E4%B8%A2%E5%A4%B1%E9%97%AE%E9%A2%98/","summary":"\u003ch2 id=\"描述\"\u003e描述\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# name=user,age=18, ...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esession\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecommit\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# empty\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"原因\"\u003e原因\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003esqlalchemy\u003c/code\u003e为了保持\u003ccode\u003esession\u003c/code\u003e内的数据同步，默认了\u003ccode\u003eexpire_on_commit\u003c/code\u003e为\u003ccode\u003eTrue\u003c/code\u003e，它表现在：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 查询\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esession\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003equery\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efilter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eUser\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efirst\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 更改数据\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;user_b\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esession\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esession\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eflush\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esession\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecommit\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# commit后再次访问，其实是重新进行了select操作\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# user_b\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n    \u003cp\u003e\u003ccode\u003eexpire_on_commit\u003c/code\u003e可以用来更改 SQLAlchemy 的对象刷新机制，默认值为\u003ccode\u003eTrue\u003c/code\u003e即在\u003ccode\u003esession\u003c/code\u003e调用\u003ccode\u003ecommit\u003c/code\u003e之后会主动将同一个\u003ccode\u003esession\u003c/code\u003e在\u003ccode\u003ecommit\u003c/code\u003e之前查询得到的 ORM 对象的\u003ccode\u003e_sa_instance_state.expire\u003c/code\u003e属性设置为\u003ccode\u003eFlase\u003c/code\u003e，再次读取该对象属性时将重载这个对象，方法是重新调用之前的查询语句。\u003c/p\u003e\n\n  \u003c/blockquote\u003e","title":"sqlalchemy\u0026sqlmodel的commit后数据丢失问题"},{"content":"连接单个数据库 from typing import Optional from sqlmodel import Field, Session, SQLModel, create_engine # 表 class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str secret_name: str age: Optional[int] = None # 连接 engine = create_engine(\u0026#34;sqlite:///database.db\u0026#34;) # 建表 SQLModel.metadata.create_all(engine) 连接多个数据库 from typing import Optional from sqlmodel import Field, Session, SQLModel, create_engine from sqlmodel.main import registry # 表1 table1 = registry() class Hero1(SQLModel, table=True, registry=table1): id: Optional[int] = Field(default=None, primary_key=True) name: str secret_name: str age: Optional[int] = None # 表2 table2 = registry() class Hero2(SQLModel, table=True, registry=table2): id: Optional[int] = Field(default=None, primary_key=True) name: str secret_name: str age: Optional[int] = None # 连接1 engine1 = create_engine(\u0026#34;sqlite:///database1.db\u0026#34;) # 连接2 engine2 = create_engine(\u0026#34;sqlite:///database2.db\u0026#34;) # 建表1 Hero1.metadata.create_all(engine1) # 建表2 Hero2.metadata.create_all(engine2) 一点优化 显然这样建多表会很麻烦，一种优化方案是：\nclass Base1(SQLModel, registry=registry()): ... class Base2(SQLModel, registry=registry()): ... engine1 = create_engine(\u0026#34;sqlite:///database1.db\u0026#34;) engine2 = create_engine(\u0026#34;sqlite:///database2.db\u0026#34;) class Hero1(Base1, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str secret_name: str age: Optional[int] = None class Hero2(Base2, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str secret_name: str age: Optional[int] = None # 建表1 Base1.metadata.create_all(engine1) # 建表2 Base2.metadata.create_all(engine2) 此时无论有多少张表需要create都可以用基类的create_all来创建这个库下的所有表了\n而且多数据库时注意循环依赖问题\n","permalink":"https://www.kutake.top/archives/sqlmodel%E5%8D%95%E4%B8%8E%E5%A4%9A%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5/","summary":"\u003ch2 id=\"连接单个数据库\"\u003e连接单个数据库\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003etyping\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eOptional\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003esqlmodel\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eField\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eSession\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eSQLModel\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecreate_engine\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 表\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eHero\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSQLModel\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etable\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eOptional\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eField\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edefault\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eprimary_key\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003esecret_name\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eOptional\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 连接\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eengine\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecreate_engine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;sqlite:///database.db\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 建表\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eSQLModel\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emetadata\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecreate_all\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eengine\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"连接多个数据库\"\u003e连接多个数据库\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003etyping\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eOptional\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003esqlmodel\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eField\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eSession\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eSQLModel\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecreate_engine\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003esqlmodel.main\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eregistry\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 表1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003etable1\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eregistry\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eHero1\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSQLModel\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etable\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eregistry\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003etable1\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eOptional\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eField\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edefault\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eprimary_key\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003esecret_name\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eOptional\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 表2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003etable2\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eregistry\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eHero2\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSQLModel\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etable\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eregistry\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003etable2\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eOptional\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eField\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edefault\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eprimary_key\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003esecret_name\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eOptional\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 连接1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eengine1\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecreate_engine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;sqlite:///database1.db\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 连接2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eengine2\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecreate_engine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;sqlite:///database2.db\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 建表1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eHero1\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emetadata\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecreate_all\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eengine1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 建表2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eHero2\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emetadata\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecreate_all\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eengine2\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"一点优化\"\u003e一点优化\u003c/h2\u003e\n\u003cp\u003e显然这样建多表会很麻烦，一种优化方案是：\u003c/p\u003e","title":"sqlmodel单与多数据库连接"},{"content":"同步 # sync_session # example(id, name) e = example(name=\u0026#34;name1\u0026#34;) db.add(e) db.flush() print(e.id)\t# id = 1 异步 # async_session # example(id, name) e = example(name=\u0026#34;name1\u0026#34;) db.add(e) await db.flush() await db.commit() await db.refresh(e) print(e.id)\t# id = 1 ","permalink":"https://www.kutake.top/archives/sqlalchemy%E5%BC%82%E6%AD%A5%E4%B8%8E%E5%90%8C%E6%AD%A5%E6%96%B9%E5%BC%8F%E8%8E%B7%E5%8F%96%E8%87%AA%E5%A2%9Eid/","summary":"\u003ch2 id=\"同步\"\u003e同步\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# sync_session\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# example(id, name)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eexample\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;name1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eflush\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\t\u003cspan class=\"c1\"\u003e# id = 1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"异步\"\u003e异步\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# async_session\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# example(id, name)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eexample\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;name1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eflush\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecommit\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erefresh\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\t\u003cspan class=\"c1\"\u003e# id = 1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"sqlalchemy异步与同步方式获取自增id"},{"content":"问题 \u0026lt;insert id=\u0026#34;insertListIfNotExists\u0026#34; useGeneratedKeys=\u0026#34;true\u0026#34; keyProperty=\u0026#34;id\u0026#34;\u0026gt; INSERT IGNORE INTO `tag` (name, create_time) VALUES \u0026lt;foreach collection=\u0026#34;list\u0026#34; item=\u0026#34;item\u0026#34; separator=\u0026#34;,\u0026#34;\u0026gt; (#{item.name}, #{item.createTime}) \u0026lt;/foreach\u0026gt; \u0026lt;/insert\u0026gt; useGeneratedKeys与INSERT IGNORE配合后，若插入顺序是\nid:1 name: name1 id: null name: name2 id: null name: name3 返回的id会变成\nid: 2 name: name1 id: 3 name: name2 id: null name: name3 解决方法 请单个进行插入\n","permalink":"https://www.kutake.top/archives/myabtis-plusxml%E4%B8%AD%E8%BF%94%E5%9B%9E%E7%9A%84id%E9%A1%BA%E5%BA%8F%E9%97%AE%E9%A2%98/","summary":"\u003ch2 id=\"问题\"\u003e问题\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;insert\u003c/span\u003e \u003cspan class=\"na\"\u003eid=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;insertListIfNotExists\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003euseGeneratedKeys=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;true\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003ekeyProperty=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;id\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        INSERT IGNORE INTO\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            `tag` (name, create_time)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        VALUES\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;foreach\u003c/span\u003e \u003cspan class=\"na\"\u003ecollection=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;list\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eitem=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;item\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eseparator=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;,\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                (#{item.name}, #{item.createTime})\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;/foreach\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;/insert\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003euseGeneratedKeys\u003c/code\u003e与\u003ccode\u003eINSERT IGNORE\u003c/code\u003e配合后，若插入顺序是\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eid:1 name: name1\nid: null name: name2\nid: null name: name3\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e返回的id会变成\u003c/p\u003e","title":"[myabtis-plus]XML中返回的id顺序问题"},{"content":"未声明则不插入 create_time是自动注入字段\nxml是最终执行的sql命令，未声明加入则不会插入\n\u0026lt;insert id=\u0026#34;insertListIfNotExists\u0026#34; useGeneratedKeys=\u0026#34;true\u0026#34; keyProperty=\u0026#34;id\u0026#34;\u0026gt; INSERT IGNORE INTO `tag` (name) VALUES \u0026lt;foreach collection=\u0026#34;list\u0026#34; item=\u0026#34;item\u0026#34; separator=\u0026#34;,\u0026#34;\u0026gt; (#{item.name}) \u0026lt;/foreach\u0026gt; \u0026lt;/insert\u0026gt; 修改：\n\u0026lt;insert id=\u0026#34;insertListIfNotExists\u0026#34; useGeneratedKeys=\u0026#34;true\u0026#34; keyProperty=\u0026#34;id\u0026#34;\u0026gt; INSERT IGNORE INTO `tag` (name, create_time) VALUES \u0026lt;foreach collection=\u0026#34;list\u0026#34; item=\u0026#34;item\u0026#34; separator=\u0026#34;,\u0026#34;\u0026gt; (#{item.name}, #{item.createTime}) \u0026lt;/foreach\u0026gt; \u0026lt;/insert\u0026gt; ","permalink":"https://www.kutake.top/archives/myabtis-plusxml%E4%B8%AD%E7%9A%84%E5%AD%97%E6%AE%B5%E6%B3%A8%E5%85%A5%E4%B8%8D%E7%94%9F%E6%95%88/","summary":"\u003ch2 id=\"未声明则不插入\"\u003e未声明则不插入\u003c/h2\u003e\n\u003cp\u003ecreate_time是自动注入字段\u003c/p\u003e\n\u003cp\u003exml是最终执行的sql命令，未声明加入则不会插入\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;insert\u003c/span\u003e \u003cspan class=\"na\"\u003eid=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;insertListIfNotExists\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003euseGeneratedKeys=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;true\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003ekeyProperty=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;id\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        INSERT IGNORE INTO\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            `tag` (name)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        VALUES\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;foreach\u003c/span\u003e \u003cspan class=\"na\"\u003ecollection=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;list\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eitem=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;item\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eseparator=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;,\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                (#{item.name})\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;/foreach\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;/insert\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e修改：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;insert\u003c/span\u003e \u003cspan class=\"na\"\u003eid=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;insertListIfNotExists\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003euseGeneratedKeys=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;true\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003ekeyProperty=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;id\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        INSERT IGNORE INTO\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            `tag` (name, create_time)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        VALUES\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;foreach\u003c/span\u003e \u003cspan class=\"na\"\u003ecollection=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;list\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eitem=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;item\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eseparator=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;,\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                (#{item.name}, #{item.createTime})\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;/foreach\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;/insert\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"[myabtis-plus]xml中的字段注入不生效"},{"content":"模块化 我现在是这样的\nBot: └─config └─db └─modules └─utils config内包含所有模块用到的配置项\ndb内是实体类和业务语句的集合\nutils内是一些公共工具\n现在问题是，config与db非常难维护，一打开就是几百行语句，找起来都麻烦，数据库和配置项有一点不对整个bot就无法启动\n依照模块化的思想，所有与具体module有关的类都应在它自己的文件夹下，做到方便维护，Bot主体可以实现扩展并管理配置文件和database，甚至做到overload(很难)，这些是一开始写时没有想到的。\n实体类与pydantic重复 写db相关经常会构建两个差不多的类，一个数据库实体类，一个装载它的pydantic模型类，其实可以直接使用sqlmodel这个依赖进行一次性构建\npip install sqlmodel from typing import Optional from sqlmodel import Field, SQLModel class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str secret_name: str age: Optional[int] = None 非常方便\n文档：SQLModel (tiangolo.com)\n生命周期管理 很多模块有争对用户的功能，那就要做到基本的用户增删改查，以及给管理员的增删改查，不要想着偷懒，后面出问题会非常难受\n有些单例有初始化方法，初始化的位置与是否async要考虑好，常用的类可以交给launart管理\nfrom launart import ExportInterface, Launchable class TestService(Launchable): id: str = \u0026#34;test/init\u0026#34; @property def required(self) -\u0026gt; set[str | type[ExportInterface]]: return set() @property def stages(self) -\u0026gt; set[str]: return {\u0026#34;preparing\u0026#34;, \u0026#34;cleanup\u0026#34;} async def launch(self, _): print(\u0026#34;before preparing\u0026#34;) async with self.stage(\u0026#34;preparing\u0026#34;): print(\u0026#34;preparing\u0026#34;) print(\u0026#34;before cleanup\u0026#34;) async with self.stage(\u0026#34;cleanup\u0026#34;): print(\u0026#34;cleanup\u0026#34;) print(\u0026#34;after cleanup\u0026#34;) 交给launart就不用担心sync与async的冲突问题，还可以方便的获得具体的Service实例\n统一Session bot作为工具的集成，许多功能是需要发送get/post请求获取信息的，统一的Session可以节省一些开支，但是会区分用户以及cookies的功能需要注意，不然单例的Session在async的使用上会出现不可预知的错误，包括用户对不上等等\n依赖过重 一些基本的功能可以写个request就可以替代，却非得拉个库来实现，例如为了bilibili的视频解析而引入了biliapi，就导致了aiohttp的依赖冲突问题\n","permalink":"https://www.kutake.top/archives/bot%E7%9A%84%E4%B8%80%E4%BA%9B%E5%8F%8D%E6%80%9D/","summary":"\u003ch2 id=\"模块化\"\u003e模块化\u003c/h2\u003e\n\u003cp\u003e我现在是这样的\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-powershell\" data-lang=\"powershell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eBot\u003c/span\u003e\u003cspan class=\"err\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e└─\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e└─\u003c/span\u003e\u003cspan class=\"n\"\u003edb\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e└─\u003c/span\u003e\u003cspan class=\"n\"\u003emodules\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e└─\u003c/span\u003e\u003cspan class=\"n\"\u003eutils\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003econfig\u003c/code\u003e内包含所有模块用到的配置项\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003edb\u003c/code\u003e内是实体类和业务语句的集合\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003eutils\u003c/code\u003e内是一些公共工具\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e现在问题是，\u003ccode\u003econfig\u003c/code\u003e与\u003ccode\u003edb\u003c/code\u003e非常难维护，一打开就是几百行语句，找起来都麻烦，数据库和配置项有一点不对整个bot就无法启动\u003c/p\u003e","title":"bot的一些反思"},{"content":"PDM介绍 一个现代的 Python 包管理器，支持 PEP 582\n跟pipenv与poetry有着相同功能\n抛弃传统包管理器所需的虚拟环境 - PEP 582\n比poetry更快的依赖解析\n类似pnpm的中心化安装缓存\n插件系统\nPEP 621 元数据格式\n兼容 PEP 517 的构建后端，用于构建发布包(源码格式与 wheel 格式)\n实际使用中，由于各种IDE还没有插件上的支持，所以需要一点手动配置才可以使用，这点对于pipenv和poetry稍显劣势，不过估计也是迟早的事:)\n项目github：pdm-project/pdm (github.com)\n安装 linux:\ncurl -sSL https://raw.githubusercontent.com/pdm-project/pdm/main/install-pdm.py | python3 - windows:\n(Invoke-WebRequest -Uri https://raw.githubusercontent.com/pdm-project/pdm/main/install-pdm.py -UseBasicParsing).Content | python - 不建议使用pip，以保持与python根package的隔离\n开启PEP-582支持 PEP-582规定了一种依赖导入方式，它允许使用程序同目录中的__pypackages__作为优先依赖导入\nPDM-EXAMPLE └─__pypackages__ └─3.XX ├─include ├─lib │ ├─pydantic └─Scripts 目录结构如上，__pypackages__的优先级是比根site-packages还要高的，任何依赖都会优先在此寻找，使用pdm管理依赖时，依赖包就下载至此\n使用pdm开启PEP-582支持：\npdm --pep582 IDE支持 vscode ``指依赖的python版本，例`3.10` 将下面的配置添加至项目下的.vscode/settings.json\n{ \u0026#34;python.autoComplete.extraPaths\u0026#34;: [ \u0026#34;${workspaceFolder}/__pypackages__/\u0026lt;major.minor\u0026gt;/lib\u0026#34; ], \u0026#34;python.analysis.extraPaths\u0026#34;: [ \u0026#34;${workspaceFolder}/__pypackages__/\u0026lt;major.minor\u0026gt;/lib\u0026#34; ], } 同时确保开启了全局PEP-582支持\nPycharm 只需要将__pypackages__/\u0026lt;major.minor\u0026gt;/lib标记为源根Sources Root即可\n解释器设置为同一版本的python即可\n常用命令 初始化\n# 注意位置 \\PDM-EXAMPLE\\(在这里) pdm init 添加依赖\npdm add requests 设置pypi镜像\npdm config pypi.url https://test.pypi.org/simple 移除依赖\npdm remove requests 升级依赖\n# 升级所有依赖 pdm update # 单独升级 pdm update requests 安装pyproject.toml内所有依赖\npdm sync ","permalink":"https://www.kutake.top/archives/pdm-%E6%9B%B4%E7%8E%B0%E4%BB%A3%E7%9A%84python%E5%8C%85%E7%AE%A1%E7%90%86%E5%99%A8/","summary":"\u003ch2 id=\"pdm介绍\"\u003ePDM介绍\u003c/h2\u003e\n\u003cp\u003e一个现代的 Python 包管理器，支持 \u003ca href=\"https://www.python.org/dev/peps/pep-0582\"\u003ePEP 582\u003c/a\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e跟pipenv与poetry有着相同功能\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e抛弃传统包管理器所需的虚拟环境 - \u003ca href=\"https://www.python.org/dev/peps/pep-0582\"\u003ePEP 582\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e比poetry更快的依赖解析\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e类似pnpm的中心化安装缓存\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e插件系统\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ca href=\"https://www.python.org/dev/peps/pep-0621\"\u003ePEP 621\u003c/a\u003e 元数据格式\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e兼容 \u003ca href=\"https://www.python.org/dev/peps/pep-0517\"\u003ePEP 517\u003c/a\u003e 的构建后端，用于构建发布包(源码格式与 wheel 格式)\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e","title":"PDM-更现代的python包管理器"},{"content":"基本的上下文管理器 需要定义：\nclass ContextM: def __enter__(): dosomethings() def __exit__(): dosometings() class AsyncContextM: async def __aenter__(): await dosometings() async def __aexit__(): await dosometings() 调用时使用with ContextM()或async with AsyncContextM\nContextManager Python内置的一个很方便的生成上下文管理器的装饰器\nfrom contextlib import contextmanager, asynccontextmanager @contextmanager def context(): dosomethings_like_enter() yield dosomethings_like_exit() @asynccontextmanager async def async_context(): await dosometings_like_aenter() yield await dosometings_like_aexit() 以同样的方式调用即可\n","permalink":"https://www.kutake.top/archives/python%E4%B8%AD%E4%BE%BF%E6%8D%B7%E7%9A%84%E4%B8%8A%E4%B8%8B%E6%96%87%E7%AE%A1%E7%90%86%E5%99%A8contextmanager/","summary":"\u003ch2 id=\"基本的上下文管理器\"\u003e基本的上下文管理器\u003c/h2\u003e\n\u003cp\u003e需要定义：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-Python\" data-lang=\"Python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eContextM\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"fm\"\u003e__enter__\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\t\u003cspan class=\"n\"\u003edosomethings\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"fm\"\u003e__exit__\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\t\u003cspan class=\"n\"\u003edosometings\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\t\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eAsyncContextM\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"k\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"fm\"\u003e__aenter__\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\t\u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003edosometings\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"k\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"fm\"\u003e__aexit__\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\t\u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003edosometings\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e调用时使用\u003ccode\u003ewith ContextM()\u003c/code\u003e或\u003ccode\u003easync with AsyncContextM\u003c/code\u003e\u003c/p\u003e\n\u003ch2 id=\"contextmanager\"\u003eContextManager\u003c/h2\u003e\n\u003cp\u003ePython内置的一个很方便的生成上下文管理器的装饰器\u003c/p\u003e","title":"Python中便捷的上下文管理器contextmanager"},{"content":"还能整啥呢\n","permalink":"https://www.kutake.top/shuoshuo/bot%E7%A1%AE%E5%AE%9E%E5%A5%BD%E7%8E%A9/","summary":"\u003cp\u003e还能整啥呢\u003c/p\u003e","title":"bot确实好玩"},{"content":"日志重写 import logging import os import sys from pprint import pformat from loguru import logger from config import settings class InterceptHandler(logging.Handler): def emit(self, record): # Get corresponding Loguru level if it exists try: level = logger.level(record.levelname).name except ValueError: level = record.levelno # Find caller from where originated the logged message frame, depth = logging.currentframe(), 2 while frame.f_code.co_filename == logging.__file__: frame = frame.f_back depth += 1 logger.opt(depth=depth, exception=record.exc_info).log( level, record.getMessage() ) def format_record(record: dict) -\u0026gt; str: format_string = settings.config.LOG_FORMAT if record[\u0026#34;extra\u0026#34;].get(\u0026#34;payload\u0026#34;) is not None: record[\u0026#34;extra\u0026#34;][\u0026#34;payload\u0026#34;] = pformat( record[\u0026#34;extra\u0026#34;][\u0026#34;payload\u0026#34;], indent=4, compact=True, width=88 ) format_string += \u0026#34;\\n\u0026lt;level\u0026gt;{extra[payload]}\u0026lt;/level\u0026gt;\u0026#34; format_string += \u0026#34;{exception}\\n\u0026#34; return format_string # it requires app init or won\u0026#39;t be work def logger_init(): log_path_all = os.path.join(settings.config.log_path, settings.config.LOG_FILE) log_path_error = os.path.join(settings.config.log_error_path, settings.config.LOG_FILE) logging.getLogger().handlers = [InterceptHandler()] logger.configure( handlers=[{\u0026#34;sink\u0026#34;: sys.stdout, \u0026#34;level\u0026#34;: logging.DEBUG, \u0026#34;format\u0026#34;: format_record}]) # logger.add(config.LOG_DIR, encoding=\u0026#39;utf-8\u0026#39;, rotation=\u0026#34;9:46\u0026#34;) logger.add(log_path_all, rotation=\u0026#34;00:00\u0026#34;, retention=\u0026#34;2 week\u0026#34;, enqueue=True, diagnose=True, level=\u0026#34;DEBUG\u0026#34;, format=format_record ) logger.add(log_path_error, rotation=\u0026#34;00:00\u0026#34;, retention=\u0026#34;3 week\u0026#34;, enqueue=True, backtrace=True, diagnose=True, level=\u0026#34;ERROR\u0026#34;, format=format_record ) logger.debug(\u0026#39;日志系统已加载\\n\\n\\n\u0026#39;) logging.getLogger(\u0026#34;uvicorn.access\u0026#34;).handlers = [InterceptHandler()] 在app中调用 app = FastAPI( title=config.settings.title, debug=config.settings.config.DEBUG ) # app create frist, then init logger logger_init() origins = [ \u0026#34;http://127.0.0.1\u0026#34;, f\u0026#34;http://127.0.0.1:{config.settings.config.port}\u0026#34;, \u0026#34;http://localhost\u0026#34;, f\u0026#34;http://localhost:{config.settings.config.port}\u0026#34;, # \u0026#34;*\u0026#34; ] app.include_router(scraper.router) app.include_router(works.router) app.include_router(users.router) app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=[\u0026#34;*\u0026#34;], allow_headers=[\u0026#34;*\u0026#34;], ) if __name__ == \u0026#39;__main__\u0026#39;: uvicorn.run( app=\u0026#39;main:app\u0026#39;, host=\u0026#39;0.0.0.0\u0026#39;, port=config.settings.config.port, reload=True, # should be False at production access_log=True, ws_ping_interval=300, ws_ping_timeout=300 ) ","permalink":"https://www.kutake.top/archives/%E5%9C%A8fastapi%E4%B8%AD%E4%BD%BF%E7%94%A8loguru%E8%AE%B0%E5%BD%95%E6%97%A5%E5%BF%97/","summary":"\u003ch2 id=\"日志重写\"\u003e日志重写\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003elogging\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003eos\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003esys\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003epprint\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003epformat\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003eloguru\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003elogger\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003econfig\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003esettings\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eInterceptHandler\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elogging\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eHandler\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003eemit\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003erecord\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e# Get corresponding Loguru level if it exists\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003elevel\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003elogger\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elevel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erecord\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elevelname\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eexcept\u003c/span\u003e \u003cspan class=\"ne\"\u003eValueError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003elevel\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erecord\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elevelno\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e# Find caller from where originated the logged message\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eframe\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edepth\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003elogging\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecurrentframe\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ewhile\u003c/span\u003e \u003cspan class=\"n\"\u003eframe\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ef_code\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eco_filename\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"n\"\u003elogging\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"vm\"\u003e__file__\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eframe\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eframe\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ef_back\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003edepth\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003elogger\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eopt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edepth\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003edepth\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eexception\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003erecord\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eexc_info\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003elevel\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003erecord\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egetMessage\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003eformat_record\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erecord\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003edict\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eformat_string\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esettings\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLOG_FORMAT\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003erecord\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;extra\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;payload\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"ow\"\u003eis\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003erecord\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;extra\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;payload\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epformat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003erecord\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;extra\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;payload\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003eindent\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecompact\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ewidth\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e88\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eformat_string\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026lt;level\u0026gt;\u003c/span\u003e\u003cspan class=\"si\"\u003e{extra[payload]}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026lt;/level\u0026gt;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eformat_string\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e{exception}\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eformat_string\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# it requires app init or won\u0026#39;t be work\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003elogger_init\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003elog_path_all\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ejoin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esettings\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elog_path\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esettings\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLOG_FILE\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003elog_path_error\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ejoin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esettings\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elog_error_path\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esettings\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLOG_FILE\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003elogging\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egetLogger\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ehandlers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eInterceptHandler\u003c/span\u003e\u003cspan class=\"p\"\u003e()]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003elogger\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econfigure\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ehandlers\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e[{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;sink\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003esys\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estdout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;level\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003elogging\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eDEBUG\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;format\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eformat_record\u003c/span\u003e\u003cspan class=\"p\"\u003e}])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# logger.add(config.LOG_DIR, encoding=\u0026#39;utf-8\u0026#39;, rotation=\u0026#34;9:46\u0026#34;)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003elogger\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elog_path_all\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003erotation\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;00:00\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003eretention\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;2 week\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003eenqueue\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003ediagnose\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003elevel\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;DEBUG\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"nb\"\u003eformat\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eformat_record\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003elogger\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elog_path_error\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003erotation\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;00:00\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003eretention\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;3 week\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003eenqueue\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003ebacktrace\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003ediagnose\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003elevel\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;ERROR\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"nb\"\u003eformat\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eformat_record\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003elogger\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edebug\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;日志系统已加载\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\\n\\n\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003elogging\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egetLogger\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;uvicorn.access\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ehandlers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eInterceptHandler\u003c/span\u003e\u003cspan class=\"p\"\u003e()]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"在app中调用\"\u003e在app中调用\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eFastAPI\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esettings\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003edebug\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esettings\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eDEBUG\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# app create frist, then init logger\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elogger_init\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eorigins\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;http://127.0.0.1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;http://127.0.0.1:\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esettings\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eport\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;http://localhost\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;http://localhost:\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esettings\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eport\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# \u0026#34;*\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003einclude_router\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003escraper\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erouter\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003einclude_router\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eworks\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erouter\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003einclude_router\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eusers\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erouter\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eadd_middleware\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eCORSMiddleware\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eallow_origins\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eorigins\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eallow_credentials\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eallow_methods\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;*\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eallow_headers\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;*\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"vm\"\u003e__name__\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;__main__\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003euvicorn\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erun\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;main:app\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003ehost\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;0.0.0.0\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eport\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esettings\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eport\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003ereload\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e    \u003cspan class=\"c1\"\u003e# should be False at production\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eaccess_log\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003ews_ping_interval\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e300\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003ews_ping_timeout\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e300\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"在fastapi中使用loguru记录日志"},{"content":"以Depends的方式定义jwt from jose import JWTError, jwt from passlib.context import CryptContext from fastapi.security import OAuth2PasswordBearer from fastapi import Depends, HTTPException, status from db import Curd from typing import Optional from datetime import datetime from config import settings from utils import logger from .db import get_db from datetime import timedelta pwd_context = CryptContext(schemes=[\u0026#34;bcrypt\u0026#34;], deprecated=\u0026#34;auto\u0026#34;) oauth2_schema = OAuth2PasswordBearer(tokenUrl=\u0026#34;/users/login\u0026#34;) def hash_password(plain_password: str) -\u0026gt; str: return pwd_context.hash(plain_password) def verity_password(plain_password: str, hashed_password: str) -\u0026gt; bool: return pwd_context.verify(plain_password, hashed_password) async def jwt_authenticate_user(username: str, password: str): db = get_db() user = await Curd.get_user_by_name(await db.__anext__(), username) if not user: return False if not verity_password(password, user.password): return False return user def created_access_token(data: dict, expires_delta: Optional[timedelta] = None): to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta else: expire = datetime.utcnow() + timedelta(minutes=settings.config.ACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({\u0026#34;exp\u0026#34;: expire}) encoded_jwt = jwt.encode(claims=to_encode, key=settings.default.SECRET_KEY, algorithm=\u0026#34;HS256\u0026#34;) return encoded_jwt async def jwt_verity(token: str = Depends(oauth2_schema)): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=\u0026#34;Could not validate credentials\u0026#34;, headers={\u0026#34;WWW-Authenticate\u0026#34;: \u0026#34;Bearer\u0026#34;}, ) try: playload = jwt.decode(token=token, key=settings.default.SECRET_KEY, algorithms=[\u0026#34;HS256\u0026#34;]) except JWTError as e: logger.error(\u0026#34;验证出现错误:\\n\u0026#34;, e) raise credentials_exception else: return playload async def jwt_get_current_user(playload: dict = Depends(jwt_verity)): db = get_db() user_id = int(playload.get(\u0026#34;sub\u0026#34;)) user = await Curd.get_user_by_id(await db.__anext__(), user_id=user_id) return user async def jwt_verity_admin(playload: dict = Depends(jwt_verity)): db = get_db() user_id = int(playload.get(\u0026#34;sub\u0026#34;)) is_admin = playload.get(\u0026#34;is_admin\u0026#34;) if is_admin: user = await Curd.get_user_by_id(await db.__anext__(), user_id=user_id) if user.is_admin: return raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=\u0026#34;Could not validate credentials\u0026#34;, headers={\u0026#34;WWW-Authenticate\u0026#34;: \u0026#34;Bearer\u0026#34;}, ) 需要分发token的url router = APIRouter( prefix=\u0026#34;/users\u0026#34;, tags=[\u0026#34;user\u0026#34;], ) # it defind at dependencies.jwt # oauth2_schema = OAuth2PasswordBearer(tokenUrl=\u0026#34;/users/token\u0026#34;) @router.post(\u0026#34;/register\u0026#34;, response_model=UserToken) async def user_register(user: OAuth2PasswordRequestForm = Depends(), db: AsyncSession = Depends(get_db)): if await Curd.is_user_name_exists(db, user.username): raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=\u0026#34;username already exists\u0026#34;) hashed_password = hash_password(user.password) new_user = UserCreate(id=worker.get_id(), name=user.username, password=hashed_password) await Curd.register_user(db, new_user) @router.post(\u0026#34;/login\u0026#34;, response_model=UserToken) async def user_login(user: OAuth2PasswordRequestForm = Depends()): user = await jwt_authenticate_user(username=user.username, password=user.password) if not user: raise HTTPException( status.HTTP_401_UNAUTHORIZED, detail=\u0026#34;Incorrect username or password\u0026#34;, headers={\u0026#34;WWW-Authenticate\u0026#34;: \u0026#34;Bearer\u0026#34;}, ) access_token = created_access_token(data={\u0026#34;sub\u0026#34;: str(user.id)}) user_token = UserToken(username=user.name, access_token=access_token, token_type=\u0026#34;bearer\u0026#34;) return user_token 需要token进行访问限制的url router = APIRouter( prefix=\u0026#34;/works\u0026#34;, tags=[\u0026#34;works\u0026#34;], ) @router.get(\u0026#34;/get\u0026#34;, response_model=Works) async def test( order: int, nsfw: bool, page: int, sort: int, user: User = Depends(jwt_get_current_user), db: AsyncSession = Depends(get_db), ): works = await Curd.get_works(db, user.id, order, nsfw, page, sort) return works ","permalink":"https://www.kutake.top/archives/fastapi%E4%BD%BF%E7%94%A8jwt-token%E8%BF%9B%E8%A1%8C%E9%AA%8C%E8%AF%81/","summary":"\u003ch2 id=\"以depends的方式定义jwt\"\u003e以Depends的方式定义jwt\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003ejose\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eJWTError\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ejwt\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003epasslib.context\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eCryptContext\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003efastapi.security\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eOAuth2PasswordBearer\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003efastapi\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eHTTPException\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003estatus\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003edb\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eCurd\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003etyping\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eOptional\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003edatetime\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003edatetime\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003econfig\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003esettings\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003eutils\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003elogger\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003e.db\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eget_db\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003edatetime\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003etimedelta\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003epwd_context\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eCryptContext\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eschemes\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;bcrypt\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003edeprecated\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;auto\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoauth2_schema\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eOAuth2PasswordBearer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etokenUrl\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/users/login\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ehash_password\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eplain_password\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003epwd_context\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ehash\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eplain_password\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003everity_password\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eplain_password\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ehashed_password\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"nb\"\u003ebool\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003epwd_context\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003everify\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eplain_password\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ehashed_password\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ejwt_authenticate_user\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eusername\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epassword\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003edb\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eget_db\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003euser\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003eCurd\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_user_by_name\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"fm\"\u003e__anext__\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"n\"\u003eusername\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003eFalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003everity_password\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epassword\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epassword\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003eFalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003euser\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ecreated_access_token\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003edict\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eexpires_delta\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eOptional\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003etimedelta\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eto_encode\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecopy\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eexpires_delta\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eexpire\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edatetime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eutcnow\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eexpires_delta\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eexpire\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edatetime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eutcnow\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003etimedelta\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eminutes\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003esettings\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eACCESS_TOKEN_EXPIRE_MINUTES\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eto_encode\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eupdate\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;exp\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eexpire\u003c/span\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eencoded_jwt\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ejwt\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eencode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eclaims\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eto_encode\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003esettings\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edefault\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eSECRET_KEY\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ealgorithm\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;HS256\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eencoded_jwt\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ejwt_verity\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etoken\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eoauth2_schema\u003c/span\u003e\u003cspan class=\"p\"\u003e)):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ecredentials_exception\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eHTTPException\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003estatus_code\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003estatus\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eHTTP_401_UNAUTHORIZED\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003edetail\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Could not validate credentials\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eheaders\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;WWW-Authenticate\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Bearer\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eplayload\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ejwt\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edecode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etoken\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003etoken\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003esettings\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edefault\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eSECRET_KEY\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ealgorithms\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;HS256\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eexcept\u003c/span\u003e \u003cspan class=\"n\"\u003eJWTError\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003elogger\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eerror\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;验证出现错误:\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eraise\u003c/span\u003e \u003cspan class=\"n\"\u003ecredentials_exception\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eplayload\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ejwt_get_current_user\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eplayload\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003edict\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ejwt_verity\u003c/span\u003e\u003cspan class=\"p\"\u003e)):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003edb\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eget_db\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003euser_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eplayload\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;sub\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003euser\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003eCurd\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_user_by_id\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"fm\"\u003e__anext__\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"n\"\u003euser_id\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003euser_id\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003euser\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ejwt_verity_admin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eplayload\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003edict\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ejwt_verity\u003c/span\u003e\u003cspan class=\"p\"\u003e)):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003edb\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eget_db\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003euser_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eplayload\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;sub\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eis_admin\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eplayload\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;is_admin\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eis_admin\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003euser\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003eCurd\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_user_by_id\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"fm\"\u003e__anext__\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"n\"\u003euser_id\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003euser_id\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eis_admin\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eraise\u003c/span\u003e \u003cspan class=\"n\"\u003eHTTPException\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003estatus_code\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003estatus\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eHTTP_401_UNAUTHORIZED\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003edetail\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Could not validate credentials\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eheaders\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;WWW-Authenticate\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Bearer\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"需要分发token的url\"\u003e需要分发token的url\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003erouter\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eAPIRouter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eprefix\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/users\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003etags\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;user\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# it defind at dependencies.jwt\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# oauth2_schema = OAuth2PasswordBearer(tokenUrl=\u0026#34;/users/token\u0026#34;)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@router.post\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/register\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse_model\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eUserToken\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003euser_register\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eOAuth2PasswordRequestForm\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eAsyncSession\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eget_db\u003c/span\u003e\u003cspan class=\"p\"\u003e)):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003eCurd\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eis_user_name_exists\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eusername\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eraise\u003c/span\u003e \u003cspan class=\"n\"\u003eHTTPException\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003estatus_code\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003estatus\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eHTTP_400_BAD_REQUEST\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                            \u003cspan class=\"n\"\u003edetail\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;username already exists\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ehashed_password\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ehash_password\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epassword\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003enew_user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eUserCreate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eid\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eworker\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_id\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eusername\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epassword\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003ehashed_password\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003eCurd\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eregister_user\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003enew_user\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@router.post\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/login\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse_model\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eUserToken\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003euser_login\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eOAuth2PasswordRequestForm\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e()):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003euser\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003ejwt_authenticate_user\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eusername\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eusername\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epassword\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epassword\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eraise\u003c/span\u003e \u003cspan class=\"n\"\u003eHTTPException\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003estatus\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eHTTP_401_UNAUTHORIZED\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003edetail\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Incorrect username or password\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eheaders\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;WWW-Authenticate\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Bearer\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eaccess_token\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecreated_access_token\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;sub\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e)})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003euser_token\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eUserToken\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eusername\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                           \u003cspan class=\"n\"\u003eaccess_token\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eaccess_token\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                           \u003cspan class=\"n\"\u003etoken_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;bearer\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003euser_token\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"需要token进行访问限制的url\"\u003e需要token进行访问限制的url\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003erouter\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eAPIRouter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eprefix\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/works\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003etags\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;works\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@router.get\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/get\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse_model\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eWorks\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003etest\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eorder\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ensfw\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003ebool\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003epage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003esort\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eUser\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ejwt_get_current_user\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eAsyncSession\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eget_db\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eworks\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003eCurd\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_works\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eorder\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ensfw\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epage\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esort\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eworks\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"FastApi使用jwt-token进行验证"},{"content":"ORM ORM：对象关系映射（Object Relational Mapping，简称ORM，或O/RM，或O/R mapping)，用于实现面向对象编程语言里不同类型系统的数据之间的转换\nSqlalchemy 好像是python上运用最多的ORM框架，支持异步同步，多数据库连接，内置缓存机制\n官方文档：概述 — SQLAlchemy 1.4 Documentation (osgeo.cn)\n结构 db ├── __init__.py ├── curd.py ├── database.py ├── modules.py └── scheme.py 仅仅作为参考，curd存放具体的增删改查逻辑；database定义数据库类型，位置，同步与异步引擎；modules存放表模型；scheme存放供ORM外的逻辑进行交互的pydantic数据模型，关于为什么要ORM外的数据模型，后面会提到\n定义 \u0026#34;\u0026#34;\u0026#34; # database.py # 同步与异步ORM引擎支持 \u0026#34;\u0026#34;\u0026#34; from sqlalchemy import create_engine from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker, Session class DataBase: \u0026#34;\u0026#34;\u0026#34; - 同步Session - 异步AsyncSession \u0026#34;\u0026#34;\u0026#34; SQLALCHEMY_SYNC_DATABASE_URL = \u0026#34;sqlite:///./db.sqlite3\u0026#34; SQLALCHEMY_ASYNC_DATABASE_URL = \u0026#34;sqlite+aiosqlite:///./db.sqlite3\u0026#34; engine_sync = create_engine( SQLALCHEMY_SYNC_DATABASE_URL, connect_args={\u0026#34;check_same_thread\u0026#34;: False} ) engine_async = create_async_engine( SQLALCHEMY_ASYNC_DATABASE_URL, connect_args={\u0026#34;check_same_thread\u0026#34;: False} ) SessionSync = sessionmaker(autocommit=False, autoflush=False, bind=engine_sync) SessionAsync = sessionmaker(class_=AsyncSession, autocommit=False, autoflush=False, bind=engine_async) Base = declarative_base() # 使用sync引擎创建表 @classmethod def init_database(cls): # use none async engine to create all table from .modules import Base Base.metadata.create_all(bind=cls.engine_sync) 使用pymysql：\nmysql+pymysql://\u0026lt;username\u0026gt;:\u0026lt;password\u0026gt;@\u0026lt;host\u0026gt;/\u0026lt;dbname\u0026gt;[?\u0026lt;options\u0026gt;] 表模型 这是我正在使用的一个模型，对应modules.py\nclass Modules(Base): \u0026#34;\u0026#34;\u0026#34; Saya模组状态 \u0026#34;\u0026#34;\u0026#34; __tablename__ = \u0026#34;modules\u0026#34; id = Column(Integer, primary_key=True, autoincrement=True)\t# 主键和自增 name = Column(String, index=True, unique=True)\t# 索引和唯一约束 author = Column(String, nullable=True)\t# 可为null description = Column(String, nullable=True) arg_description = Column(String, nullable=True) usage = Column(String, nullable=True) file_name = Column(String, unique=True, index=True) is_enable = Column(Boolean, default=True)\t# 默认为true is_exist = Column(Boolean, default=True) create_date = Column(Date, default=date.today)\t# today加（），则默认为启动时的静态时间，不加则为调用时的动态时间 外键表的定义\nclass BlockListFriend(Base): \u0026#34;\u0026#34;\u0026#34; 模组调用好友黑名单 \u0026#34;\u0026#34;\u0026#34; __tablename__ = \u0026#34;block_list_friend\u0026#34; friend_id = Column(Integer, index=True, primary_key=True) module = Column(String, ForeignKey(\u0026#34;modules.name\u0026#34;), primary_key=True) ORM外数据模型 注意\n你可以不定义对应的pydantic模型，但需要注意增删改查的调用方式，为什么？在注意中会进行说明\nfrom pydantic import BaseModel from datetime import date, datetime from typing import Optional class ModulesBase(BaseModel): id: Optional[int] = 0 name: str author: Optional[str] = None description: Optional[str] = None arg_description: Optional[str] = None usage: Optional[str] = None file_name: str is_enable: bool = True is_exist: bool = True create_date: Optional[date] = None 增删改查 同步方法：\n@classmethod def add_module_sync(cls, db: Session, file_name: str, ....): new_module = Moudles(file_name=file_name, .....) db.add(new_module) db.commit() @classmethod def get_module_by_file_name_sync(cls, db: Session, file_name: str) -\u0026gt; Optional[Modules]: module = db.query(Modules).filter_by(file_name=file_name).one_or_none() return module @classmethod def update_module_sync(cls, db: Session, file_name: str, somevalue:....): db.query(Modules).filter_by(file_name=file_name).update({\u0026#34;key\u0026#34;:\u0026#34;values\u0026#34;}) db.commit() @classmethod def delete_module_sync(cls, db: Session, file_name: str): db.query(Modules).filter_by(file_name=file_name).delete() db.commit() 异步方法：\n@classmethod async def get_module_by_name_async(cls, db: AsyncSession, module: str) -\u0026gt; Optional[ModulesBase]: res = await db.execute(select(Modules).where(Modules.name == module).limit(1)) module = res.one_or_none() if module: return ModulesBase(**module[0].__dict__) return None @classmethod async def insert_module_async(cls, db: AsyncSession, name: str, author: str, description: str, arg_description: str, usage: str, file_name: str, init_enable_status: bool = True): await db.execute(insert(Modules).values( name=name, author=author, description=description, arg_description=arg_description, usage=usage, file_name=file_name)) await db.commit() @classmethod async def update_module_async(cls, db: AsyncSession, module_dict: dict): update_module = update(Modules).where(Modules.id == id_).values(module_dict) await db.execute(update_module) await db.commit() @classmethod async def delete_module_by_name(cls, db: AsyncSession, moudle_name: str): await db.execute(delete(Modules).where(Modules.name == moudle_name)) await db.commit() 操作上需要使用\nasync with DataBase.SessionAsync() as db: 以获取异步session对象，同步则是\nwith DataBase.SessionSync() as db: 使用上下文管理器获取session的好处是，不需要手动去`db.close()`进行关闭，十分推荐 sort、count、join等操作 先放一个我已经不打算推进的项目方法：\n_order_dict = { 1: models.Work.release, 2: models.Work.add_date, 3: models.Work.dl_count, 4: models.Work.price, 5: models.Work.review_count, 6: models.Work.rate_average, 7: models.work_review.c.rating } @classmethod async def get_works(cls, db: Session, user_id: int, order: int, nsfw: bool = True, page: int = 1, sort: int = 1): \u0026#34;\u0026#34;\u0026#34; get all works、按一定规则得到所有作品 Order(排序规则)： 1: by release time,按照发售日期 2: by add date,按照收录入库日期 3: by dl count,按照售出数量 4: by price, 按照价格 5: by review count,按照评论数量 6: by rate average,按照平均评分 7: by user rate,按照用户对作品的评分 Sort(升降): 0: Asce,升序 1: Desc,降序 \u0026#34;\u0026#34;\u0026#34; time1 = process_time() # order = text(\u0026#34;-\u0026#34; + cls._order_dict[order] if sort else cls._order_dict[order]) order = desc(cls._order_dict[order]) if sort else cls._order_dict[order] execute = select( models.Work.id, models.Work.title, models.Circle, models.Work.nsfw, models.Work.release, models.Work.dl_count, models.Work.price, models.Work.review_count, models.Work.rate_count, models.Work.rate_average, models.Work.rate_count_detail, models.work_review.c.review_text.label(\u0026#39;review\u0026#39;), models.work_review.c.rating.label(\u0026#39;rating\u0026#39;), models.work_review.c.created_date.label(\u0026#39;review_date\u0026#39;), models.work_review.c.progress.label(\u0026#39;progress\u0026#39;), ).outerjoin( models.Circle, models.Work.circle_id == models.Circle.id, ).outerjoin( models.work_review, models.work_review.c.user_id == user_id, ) if not nsfw: execute = execute.where(models.Work.nsfw == False) execute = execute.order_by(order).slice( (page - 1) * settings.config.page_size, page * settings.config.page_size ) res = await db.execute(execute) works = res.all() out_works = [] work_lens = await db.execute(select(func.count(models.Work.id))) work_lens = work_lens.first()[0] for work in works: tags = await db.execute(select(models.Tag).join( models.work_tag ).where( models.work_tag.c.work_id == work.id, models.Tag.id == models.work_tag.c.tag_id )) tags = [tag[0] for tag in tags.all()] s = schemas.Work( **work, tags=tags, ) out_works.append(s) print(f\u0026#34;process_time: {process_time() - time1}\u0026#34;) return schemas.Works( work_list=out_works, pagination=schemas.Pagination( current_page=page, page_size=settings.config.page_size, totalCount=work_lens ) ) select(func.count(models.Work.id) 计数 await db.execute(select(models.Tag).join(models.work_tag) 多表 order_by() desc(module.key) 排序 module.key.label('name') 对某个字段重命名 slice((page - 1) * settings.config.page_size, page * settings.config.page_size) 使用偏移进行分页 可见实现一个复杂的查询还是不怎么轻松的，代码量也不会小，不过，这个方法本身是很不堪入目的，所有操作都挤在一起，没有对逻辑进行切割，这里仅仅作为展示和记录\n注意 异步操作方法返回的不是模组对象本身 注意到await后面还加了个[0]么，这是因为他们返回的都是tuple类型，也就是：\nreturn type : (module, ) 查询多值的时候：\nreturn type : [(module, ), (module, ), (module, ) ....] 业务数据与ORM数据分离 为什么呢，先给个例子：\nasync def all_test(): db = DataBase.SessionAsync() res = await db.execute(select(modules.Modules)) module = res.all() n = 0 for i in module: print(f\u0026#34;数据：{module[n][0].__dict__}\u0026#34;) await db.execute(update(modules.Modules).where(modules.Modules.id==i[0].id).values({\u0026#34;name\u0026#34;: f\u0026#34;t1t11{n}\u0026#34;})) await db.commit() n += 1 await db.close() loop = asyncio.get_event_loop() loop.run_until_complete(all_test()) 拿到列表后遍历改变name字段，很普通的逻辑，看起来挺正常的对吧，但是一运行就会报错：\nFile \u0026#34;C:\\Users\\name\\AppData\\Local\\pypoetry\\Cache\\virtualenvs\\project-VkH3dFMM-py3.9\\lib\\site-packages\\sqlalchemy\\util\\_concurrency_py3k.py\u0026#34;, line 67, in await_only raise exc.MissingGreenlet( sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called; can\u0026#39;t call await_() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/14/xd2s) 查看下print()出来的内容：\n数据：{\u0026#39;_sa_instance_state\u0026#39;: \u0026lt;sqlalchemy.orm.state.InstanceState object at 0x048BB748\u0026gt;, \u0026#39;id\u0026#39;: 2, \u0026#39;usage\u0026#39;: None, ...} 数据：{\u0026#39;_sa_instance_state\u0026#39;: \u0026lt;sqlalchemy.orm.state.InstanceState object at 0x048BB778\u0026gt;} 是不是少了什么，第一次遍历的模型实例数据清晰可见，但第二个模型实例的数据呢？它除了一个InstanceState对象之外数据全丢失了！\n由此可以得出结论：\n当commit操作结束后，所有得到的模型实例都会被刷新掉，只剩下一个没有数据的空壳对象，这就是为什么建议对业务数据和ORM数据分离，把模型实例的数据用pydantic模型等保存下来才不会被commit操作刷新掉数据\n多对多关系的第三表 work_tag = Table( \u0026#39;work_tag\u0026#39;, Base.metadata, Column(\u0026#34;work_id\u0026#34;, Integer, ForeignKey(\u0026#34;works.id\u0026#34;), primary_key=True), Column(\u0026#34;tag_id\u0026#34;, Integer, ForeignKey(\u0026#34;tags.id\u0026#34;), primary_key=True), ) class Tag(Base): __tablename__ = \u0026#34;tags\u0026#34; id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String, unique=True) # works = relationship(\u0026#34;Work\u0026#34;, secondary=work_tag, back_populates=\u0026#34;tags\u0026#34;, lazy=\u0026#34;dynamic\u0026#34;) works = relationship(\u0026#34;Work\u0026#34;, secondary=work_tag, lazy=\u0026#34;dynamic\u0026#34;) class Work(Base): __tablename__ = \u0026#34;works\u0026#34; id = Column(Integer, primary_key=True) root_folder = Column(String) dir = Column(String) title = Column(String) .... tags = relationship(\u0026#34;Tag\u0026#34;, secondary=work_tag, back_populates=\u0026#34;works\u0026#34;, lazy=\u0026#34;dynamic\u0026#34;) 这是定义多对多第三表的方式，当然你若是不用relationship去定义关系映射，而只使用ForeignKey的话，是可以不使用这种方式的\n而且relationship也没看到有异步的方式调用，就算同步方式也够复杂了，就不打算整了 ","permalink":"https://www.kutake.top/archives/sqlalchemy-%E5%85%B3%E4%BA%8Epyhtonorm%E7%9A%84%E5%90%84%E7%A7%8D%E4%BA%8B/","summary":"\u003ch2 id=\"orm\"\u003eORM\u003c/h2\u003e\n\u003cp\u003eORM：对象关系映射（Object Relational Mapping，简称ORM，或O/RM，或O/R mapping)，用于实现面向对象编程语言里不同类型系统的数据之间的转换\u003c/p\u003e","title":"Sqlalchemy-关于PyhtonORM的各种事"},{"content":"搁浅一阵子吧\n","permalink":"https://www.kutake.top/shuoshuo/bot%E6%84%9F%E8%A7%89%E6%B2%A1%E5%95%A5%E5%8F%AF%E7%8E%A9%E7%9A%84%E4%BA%86/","summary":"\u003cp\u003e搁浅一阵子吧\u003c/p\u003e","title":"bot感觉没啥可玩的了"},{"content":" 1.什么是算法？ 算法是计算机科学中的基本概念，是对特定问题的求解步骤\n2.什么是算法的复杂度？ 分为时间复杂度和空间复杂度，前者一般是指程序运行从开始到结束所需的时间，后者一般是指对应的程序从运行开始到结束所需的储存空间\n3.数据结构中的逻辑结果可分为哪几种？ 线性结构 树形结构 图结构 集合结构 4.线性表的顺序存储、链式存储在以下五个方面有何差异？ 存储空间分配方式： 顺序存储结构是讲逻辑上的相关的数据元素依次存储在地址连续的存储空间中； 链式存储结构中，数据元素的存储位置并不能体现它们之间的逻辑关系。 存储密度： 顺序存储密度大（=1），存储空间利用率高 链式存储密度小（\u0026lt;1），存储空间利用率低 根据位置访问元素的时间复杂度： 顺序存储O(1) 链式存储O(n) 插入、删除操作的时间复杂度： 顺序存储O(n) 链式存储O(1) 适用情形： 顺序存储适宜于做查找这样的静态操作 链式存储适宜做插入、删除这样的动态操作 5.线性表的特点有哪些？ 存在唯一的一个被称作“第一个”的数据元素，表头元素； 存在唯一的一个被称作“最后一个”的数据元素。表尾元素； 除第一个之外，结构中的每个数据元素均只有一个前驱； 除最后一个外，结构中的每个数据元素均只有一个后继。 6.在长度为n的顺序表中的第i个位置处插入一个元素，需要移动多少个元素？ n-i+1\n7.理解算法：线性表La和Lb，将两个表合并成一个新的线性表并存于La中。 练习：已知La、Lb和以下函数（可直接使用） int Length(LinkList list){} //获取list链表的长度 int getLocByValue(LinkList list,int x) {} //查元素x在 链表list中的位置，如果不存在返回0 int getNodeByLoc(LinkList list,int i) {} //查链表list 中 i位置的元素 请完成connect方法，补全将两个表合并成一个新的线性表 存于La中的代码：\nLinkList connect(LinkList La,LinkList Lb){ LinkList Lc; Init(Lc, Length(La)+Length(Lb)); for(int i=0;i\u0026lt;=Length(La)-1;i++){ Insert(Lc,i,getNodeByLoc(La,i)); } for(int i=0;i\u0026lt;=Length(Lb)-1;i++){ value = getNodeByL(Lb,i); if(value = 0){break;} for(int j=0;j\u0026lt;=Length(Lc)-1;i++){ if(value \u0026lt; getNodeByL(Lc,j)){ Insert(Lc,i,value); break; } } } return Lc; } 8.带头结点的单链表和不带头结点的单链表为空的条件分别是？带头结点的循环单链表为空的条件是？ 带头结点：head-\u0026gt;next==NULL 不带头结点：head == NULL 带头结点的循环单链表：head-\u0026gt;next==head 9.在单链表中插入结点的算法中，指针如何修改 将需要插入节点的next指针指向后一个节点 将前面的节点的next指针指向需要插入的节点 10.理解单链表中插入新结点的算法 已知方法 getElem(List list, int i)可获取链表list上位置i处的结点。请写出 创建新结点S，并将结点插入到 位置i之前的代码： // i之前即为i-1 Node S; Node bNode = getElem(list, i-1); S.next = bNode; Node fNode = getElem(list, i-2); fNode.next = S; 11.理解双向链表中插入新结点的算法 将S的prev指向a结点 将S的next指向b结点 将a的next指向S结点 将b的prev指向S结点 已知方法getElem(List list, int i)可获取链表list上位置i处的结点。请写出矿建新节点S，并将结点插入到位置I之前的代码。 Node S; Node b = getElem(list, i); Node a = getElem(list, i-1); S.prev = a; S.next = b; a.next = S; b.prev = S; 12.理解栈和队列的操作特点： 先进后出，先进先出；已经进栈顺序，求可能的出栈顺序。\n链栈相对于顺序栈的优点是什么？ 使用链式栈的优点在于它能够克服用数组实现的顺序栈空间利用率不高的特点 使用链栈通常不会出现栈满的情况，因为链栈是动态申请内存的 练习：一个栈的入栈顺序为ABCDE, 则出栈顺序不可能是： A.EDCBA B.DECBA C.DCEAB D.ABCDE\n答：C\n13.理解算法：顺序栈的进栈操作 先要判断栈S是否为满，若不满再将记录栈顶的下标变量top加1，再将进栈元素放进栈顶位置上\n14.循环单链表的特点是什么？ 链表中最后一个结点的指针域不再是结束标志，而是指向整个链表的第一个结点，从而使链表形成一个环。和单链表相同，循环单链表也有带头结点和不带头结点两种。带头结点的循环单链表实现插入和删除操作较为方便，且更加适用。\n15.循环队列中进行插入和删除操作后，其头尾指针如何变化？ 插入：头指针front不变，尾指针rear = (rear + 1)%len(list) 删除： 头指针front = (front + 1)%len(list) 根据循环队列的容量及头尾指针，求循环队列中元素的个数。 (length + rear - front) % length 16.用哪种数据结构来实现括号匹配的算法？ 使用栈结构\n假设表达式中包含三种括号：圆括号、方括号和花括号，它们可互相嵌套，编写一个检查表达式中括号是否配对的函数。\n依次读取字符串 若读取的是左括号，入栈 若读取的是右括号，且与栈顶的左括号匹配，则左括号出栈，否则不合法 若读取结束后，栈内依然有任何元素存在，则不合法 若读取结束后，栈内无元素存在，则括号匹配合法 ","permalink":"https://www.kutake.top/archives/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%A4%8D%E4%B9%A0/","summary":"\u003c!-- toc --\u003e\n\u003ch2 id=\"1什么是算法\"\u003e1.什么是算法？\u003c/h2\u003e\n\u003cp\u003e算法是计算机科学中的基本概念，是对特定问题的求解步骤\u003c/p\u003e\n\u003ch2 id=\"2什么是算法的复杂度\"\u003e2.什么是算法的复杂度？\u003c/h2\u003e\n\u003cp\u003e分为时间复杂度和空间复杂度，前者一般是指程序运行从开始到结束所需的时间，后者一般是指对应的程序从运行开始到结束所需的储存空间\u003c/p\u003e","title":"数据结构复习"},{"content":" 前言 Pycharm好像是3.X版本上的登录验证，IEDA应该是2.3，登录验证已经无法使用插件来绕过，这里记录一下新的方法。\n此方法来源依旧是eval-reset插件的作者，使用到的开源项目地址或网站：\nhttps://github.com/ja-netfilter/ja-netfilter[ja-netfilter/ja-netfilter: A javaagent framework (github.com)](https://github.com/ja-netfilter/ja-netfilter) zfkun/ja-netfilter-mymap-plugin: A plugin for the ja-netfilter, it can replace strings dynamically. (github.com) https://jetbra.in/: 临时许可证 顺便整了个一键脚本，只适用于windows，注意解压到非中文目录中\n下载-》https://wwc.lanzouo.com/iF2K4y5qb0b\n运行里面的run.exe即可\n最后的效果：\n绕过登录验证 新版本配置文件位置已改变\n首先下载上面ja-netfilter，放置到一个非中文路径的文件夹下，假设你已经登录（若偏不使用账号登录，可以使用上面的临时许可证），在窗口上方帮助-\u0026gt;编辑自定义VM选项 中引入ja-netfilter\n# eval -javaagent:D:/ja-netfilter/ja-netfilter.jar 然后在jar文件的同级目录内新建一个janf_config.txt：\n新版本(2.0.1)配置文件位置已经改变，变成了：\n一个插件对应一个conf文件，所以下面的DNS和URL都要放进对应的文件里面\n[DNS] EQUAL,jetbrains.com [URL] PREFIX,https://account.jetbrains.com/lservice/rpc/validateKey.action 至此，重启并点击注册页面的开始试用即为成功，实际上不点也行，许可证显示过了日期也是可以使用的\npy脚本 这里顺便贴一下脚本代码，注意！直接拿出来运行时不行的，需要下载上面的zip包\nimport os print(\u0026#34;本脚本会自动注册IntelliJIdea, PyCharm, CLion系列软件\\n请存放于非中文路径中\\n ！！！！！！！只适用于登录验证版本！！！！！！！！\\n\u0026#34;) os.system(\u0026#39;pause\u0026#39;) get_user = \u0026#34;echo %username%\u0026#34; user_name = os.popen(get_user).read().strip() print(user_name) jb_path = f\u0026#34;C:/Users/{user_name}/AppData/Roaming/JetBrains\u0026#34; # jb_path = f\u0026#34;./test\u0026#34; software_list = [(\u0026#39;IntelliJIdea\u0026#39;, \u0026#39;idea64.exe.vmoptions\u0026#39;), (\u0026#39;PyCharm\u0026#39;, \u0026#39;pycharm64.exe.vmoptions\u0026#39;), (\u0026#39;CLion\u0026#39;, \u0026#39;clion64.exe.vmoptions\u0026#39;)] vm_file_path_list = [] pwd = os.getcwd().replace(\u0026#39;\\\\\u0026#39;, \u0026#39;/\u0026#39;) ja_path = f\u0026#34;{pwd}/ja-netfilter.jar\u0026#34; add_str = f\u0026#34;-javaagent:{ja_path}\u0026#34; for dir_ in os.listdir(jb_path): for i in software_list: if dir_.startswith(i[0]): vm_file = jb_path + \u0026#34;/\u0026#34; + dir_ + f\u0026#34;/{i[1]}\u0026#34; if os.path.exists(vm_file): with open(vm_file, \u0026#34;r+\u0026#34;) as f: agaent_exists = False text = f.read().splitlines(keepends=True) for line in text: if line.strip().startswith(\u0026#34;-javaagent\u0026#34;): agaent_exists = True print(f\u0026#34;{dir_}的-javaagent已设置，将跳过，自行检查\u0026#34;) break if not agaent_exists: # text.append(f\u0026#34;\\n# eval \\n-javaagent:{ja_path}\u0026#34;) f.writelines([f\u0026#34;\\n# eval \\n-javaagent:{ja_path}\u0026#34;]) print(f\u0026#34;{dir_}注册成功\u0026#34;) else: with open(vm_file, \u0026#34;w\u0026#34;) as f: f.write(f\u0026#34;\\n# eval \\n-javaagent:{ja_path}\u0026#34;) print(f\u0026#34;{dir_}注册成功\u0026#34;) print(\u0026#34;\\n\u0026#34;) print(\u0026#34;请自行在软件内点击 开始试用 进行激活\u0026#34;) print(\u0026#34;\\n\u0026#34;) os.system(\u0026#39;pause\u0026#39;) 更新 12/26：打包的时候加上了-w，整得报错了 12/30：版本更新 ","permalink":"https://www.kutake.top/archives/jetbrains-%E7%B3%BB%E5%88%97%E5%85%B3%E4%BA%8E%E7%99%BB%E5%BD%95%E9%AA%8C%E8%AF%81%E7%9A%84%E5%B1%8F%E8%94%BD%E6%96%B9%E6%B3%95/","summary":"\u003c!-- toc --\u003e\n\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003ePycharm好像是3.X版本上的登录验证，IEDA应该是2.3，登录验证已经无法使用插件来绕过，这里记录一下新的方法。\u003c/p\u003e\n\u003cp\u003e此方法来源依旧是eval-reset插件的作者，使用到的开源项目地址或网站：\u003c/p\u003e","title":"JetBrains 系列关于登录验证的屏蔽方法"},{"content":"Linux FTP Docker Apache 基本的排错 检查端口是否打开 检查服务/容器是否启动 检查防火墙是否关闭 ftp中检查配置文件中的 ip 是否替换为了自己的ip docker中注意容器的命名冲突 ","permalink":"https://www.kutake.top/archives/%E8%80%83%E8%AF%95%E7%9B%B8%E5%85%B3/","summary":"\u003ch2 id=\"linux\"\u003eLinux\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.kutake.top/2021/12/15/ftp%e7%9a%84%e9%85%8d%e7%bd%ae%e4%b8%8e%e9%81%87%e5%88%b0%e7%9a%84%e9%97%ae%e9%a2%98/\"\u003eFTP\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.kutake.top/2021/12/13/docker-2021-12-10/\"\u003eDocker\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.kutake.top/2021/12/17/centos%e7%9a%84apache%e9%83%a8%e7%bd%b2web/\"\u003eApache\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"基本的排错\"\u003e基本的排错\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e检查端口是否打开\u003c/li\u003e\n\u003cli\u003e检查服务/容器是否启动\u003c/li\u003e\n\u003cli\u003e检查防火墙是否关闭\u003c/li\u003e\n\u003cli\u003eftp中检查配置文件中的 ip 是否替换为了自己的ip\u003c/li\u003e\n\u003cli\u003edocker中注意容器的命名冲突\u003c/li\u003e\n\u003c/ul\u003e","title":"考试相关"},{"content":" 前言 Apache程序是目前拥有很高市场占有率的Web服务程序之一，这里演示如何安装，并访问一个简单的index.html\n开整 首先安装apache的软件包：\nyum install httpd -y apache默认使用80口，国内环境下由于缺少备案，80口是不被云服务商开放的\n所以我们需要配置文件，换个端口：\nvim /etc/httpd/conf/httpd.conf 往下翻，修改 Listen 后面的数字，改成你想要使用的端口，我这里改成8888端口\n然后在在apache的默认路径内打开一个index.html：\nvim /var/www/html/index.html 随便写进去点东西\n\u0026lt;h1\u0026gt;Hello World\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;This is a test\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;这是个html测试\u0026lt;/p\u0026gt; 保存后，避免出现防火墙问题，建议先关闭防火墙：\nsystemctl stop firewalld 最后记得在安全组里打开你刚刚设置的apache端口！我这里设置的是8888，云服务商为阿里云，可以参考下图：\n启动apache服务：\nsystemctl start httpd 然后就可以去游览器访问了，输入你的服务器ip:8888/index.html\n","permalink":"https://www.kutake.top/archives/centos%E7%9A%84apache%E9%83%A8%E7%BD%B2web/","summary":"\u003c!-- toc --\u003e\n\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eApache\u003c/strong\u003e程序是目前拥有很高市场占有率的Web服务程序之一，这里演示如何安装，并访问一个简单的index.html\u003c/p\u003e\n\u003ch2 id=\"开整\"\u003e开整\u003c/h2\u003e\n\u003cp\u003e首先安装apache的软件包：\u003c/p\u003e","title":"Centos的Apache部署web"},{"content":" 安装 # 安装 yum install vsftpd -y yum install ftp -y # 启用 systemctl restart vsftpd systemctl enable vsftpd # 处理配置文件 mv /etc/vsftpd/vsftpd.conf /etc/vsftpd/vsftpd.conf.bak grep -v \u0026#34;#\u0026#34; /etc/vsftpd/vsftpd.conf.bak \u0026gt; /etc/vsftpd/vsftpd.conf # 关闭SELinux setenforce 0 修改配置文件：\nvim /etc/vsftpd/vsftpd.conf 将以下内容复制进去（注意！将18行 pasv_address 等号后面的ip改为你自己云服务器的ip地址！！）\nanonymous_enable=NO local_enable=YES local_root=/ftp/local chroot_local_user=YES chroot_list_enable=YES chroot_list_file=/etc/vsftpd/chroot_list allow_writeable_chroot=YES write_enable=YES local_umask=022 dirmessage_enable=YES xferlog_enable=YES connect_from_port_20=NO xferlog_std_format=YES listen=YES pasv_enable=YES pasv_address=ip pasv_min_port=27000 pasv_max_port=28000 pasv_promiscuous=YES pam_service_name=vsftpd 创建用户：\nuseradd -s /sbin/nologin user1 passwd user1 #当提示密码不满足需求时，请忽略并再输入一次同样的密码 修改配置文件chroot_list：\n# 打开 vim /etc/vsftpd/chroot_list 将以下内容添加进去\nroot user1 创建文件并修改权限：\n# 创建文件夹 mkdir -p /ftp/local/test # 修改权限 chmod -R o+w /ftp 修改/etc/pam.d/vsftpd配置\nvim /etc/pam.d/vsftpd 找到 pam_shells.so 并注释掉这行， 效果如下：\n#%PAM-1.0 session optional pam_keyinit.so force revoke auth required pam_listfile.so item=user sense=deny file=/etc/vsftpd/ftpusers onerr=succeed # auth required pam_shells.so auth include password-auth account include password-auth session required pam_loginuid.so session include password-auth 记得开放端口20/21,27000/28000，可以参考下图：\n以防万一出现防火墙问题，先关闭防火墙：\nsystemctl stop firewalld 之后重启即可：\nsystemctl restart vsftpd 访问： 在资源管理器内输入ftp://ip 后输入账号密码即可（本教程使用的用户名为user1和root，尽量使用user1）\n截图时没有添加文件\n提示： 创建文件 touch 文件名 ；创建文件夹 mkdir 文件夹名\n遇到的问题 linux操作环境下，ftp命令行可拉取可上传文件，完全正常\nwindows操作环境下：\n资源管理器内ftp://ip，显示文件，拉取上传一切正常 cmd内使用ftp命令下，可以连接，但无法显示文件，也无法上传和拉取文件 ","permalink":"https://www.kutake.top/archives/ftp%E7%9A%84%E9%85%8D%E7%BD%AE%E4%B8%8E%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98/","summary":"\u003c!-- toc --\u003e\n\u003ch2 id=\"安装\"\u003e安装\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e# 安装\nyum install vsftpd -y\nyum install ftp -y\n\n# 启用\nsystemctl restart vsftpd\nsystemctl enable vsftpd\n\n# 处理配置文件\nmv /etc/vsftpd/vsftpd.conf /etc/vsftpd/vsftpd.conf.bak\ngrep -v \u0026#34;#\u0026#34; /etc/vsftpd/vsftpd.conf.bak \u0026gt; /etc/vsftpd/vsftpd.conf\n\n# 关闭SELinux\nsetenforce 0\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e修改配置文件：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003evim /etc/vsftpd/vsftpd.conf\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e将以下内容复制进去（\u003cstrong\u003e注意！将18行 pasv_address 等号后面的ip改为你自己云服务器的ip地址！！\u003c/strong\u003e）\u003c/p\u003e","title":"FTP的配置与遇到的问题"},{"content":" Docker 是一个用于开发，交付和运行应用程序的开放平台。Docker 使您能够将应用程序与基础架构分开，从而可以快速交付软件。借助 Docker，您可以与管理应用程序相同的方式来管理基础架构。通过利用 Docker 的方法来快速交付，测试和部署代码，您可以大大减少编写代码和在生产环境中运行代码之间的延迟。\n菜鸟教程--docker的优点\n前言 这是对一个linux作业完成过程的记录，要求如下\n在dockers上搭建tomcat服务器，放置一个你之前的war包，并验证成功，截图附带mac地址。 在云服务上搭建私有仓库，将服务器制作成镜像，然后再你们第三方的系统（本机，虚拟机，云服务器都可以）上安装docker，并拉取你自身的镜像，验证成功，截图附带mac地址 本身并不复杂，可以说相当简单和入门，但在环境上有点问题，比如第二个环境要求在云服务器上搭建私有库，并使用第三方的系统，拉取私有库镜像并验证成功，总所周知，公网服务器的流量和速度是非常宝贵的，一般的1M小水管服务器的速度实在是难以支撑拉取一个几百M的镜像文件\n所以第三方系统这个条件先直接跳过了\n若你使用的是虚拟机，那更加好办了，多开虚拟机就可以解决，反正内网速度可一点都不用担心，就是对电脑的硬件有小小的要求。\n不过本人使用的是云服务器环境，大致过程是一致的，只是云服务器要多出一点操作而已。\n前置要求 虚拟机或者云服务器有一个即可 一个用于tomcat的war包 安装Docker 若你是虚拟机，请点击这里跳转到安装部分\n连接服务器 假设你已经用各种方式购买了一台centos的云服务器，现在我们需要连接上它，才能进行各种各样的操作，而虚拟机直接开机就行。\n下载\u0026amp;安装FinalShell fainalshell是一个SSH工具（非限制，有其他替代工具一样可行），这里用于操作云服务器和上传文件至云服务器\n下载链接：http://www.hostbuf.com/downloads/finalshell_install.exe\n安装过程就不进行演示\n使用 FinalShell 连接云服务器 打开FinalShell操作\n最后点击确定即可\n不知道ip和密码的话，进入你所在的云服务商的控制台界面，查看服务器详细信息，这里以阿里云为例：\n确认后\n最后显示这样连接部分就完成了！\n我这里密码输错了一次\n我这里密码输错了一次\n之后的部分的截图演示我就只放代码部分了\n安装 在安装前使用\nsu 命令在获取root权限，需要输入root密码\nDocker安装命令如下：\ncurl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun 也可以使用国内 daocloud 一键安装命令：\ncurl -sSL https://get.daocloud.io/docker | sh 这样就安装成功了，之后输出下面那个命令即可启动docker\nservice docker start Docker换源 由于Docker默认拉取的是国外源的镜像仓库，所以我们需要换成国内源保存拉取速度\n配置文件位于/etc/docker/daemon.json，若是不存在，请自行创建(vim命令可直接创建)，使用\nvim /etc/docker/daemon.json 来打开这个json文件，将以下内容覆盖进去\n{ \u0026#34;registry-mirrors\u0026#34;: [\u0026#34;https://docker.mirrors.ustc.edu.cn\u0026#34;] } 提示：按i键进入insert模式修改内容后，按Esc进入命令行输入:wq保存\n关于如何使用vim来写入和保存文件，详情可以百度，也可以看这里→https://www.runoob.com/linux/linux-vim.html\n修改完成后使用\nservice docker restart 对docker进行重启\n至此完成Docker的所有安装和前置工作\n完成要求1：部署一个tomcat 首先需要一个tomcat镜像才能进行操作，输入下面的命令进行拉取tomcat镜像：\ndocker pull tomcat 拉取需要一点时间，耐心等待完成\n然后用这个tomcat镜像启动一个容器：\ndocker run -d -p 8080:8080 --name mytomcat tomcat 解释下这条命令的含义\ndocker run tomcat：启动一个以tomcat为镜像的容器 -d：后台运行 -p 8080:8080 ：将容器的8080端口开放到宿主机的8080端口上，以便访问 --name mytoncat ：将容器命名为mytomcat 返回了一串字符就是成功启动了\n拿出你准备好的war包\n注意拖拽时的目录！不然你会找不到文件\n拖拽后无需任何操作即上传成功\n然后需要把war包放入容器中tomcat的webapps文件夹内：\ndocker cp test.war mytomcat:/usr/local/tomcat/webapps 这个命令是指，将test.war这个文件复制进名为mytomcat的容器的 /usr/local/tomcat/webapps 路径内，此命令没有任何输出即为成功\n最后使用此重启容器：\ndocker restart mytomcat 至此，若你使用的是虚拟机，部署环节就结束了，可以使用游览器进行验证。\n但云服务器还需要一个操作，那就是打开服务器的8080端口\n演示为阿里云，其他云服务商大致相同\n访问连接验证你的成果吧！连接为ip:8080/war项目\n这只是个以前上课的小例子\n这只是个以前上课的小例子\n完成要求2：部署私有库 我们在拉取镜像的时候，其实是在拉取公共镜像库里的镜像，当我们需要对自己的项目进行打包上传时，直接上传公共仓库显然不太合适，于是Docker允许我们在本地搭建一个私有仓库，这里使用registry\n拉取registry镜像：\ndocker pull registry:2 运行一个registry容器：\ndocker run -d -v /opt/registry:/var/lib/registry -p 5000:5000 --name myregistry registry:2 此时你可以访问ip:5000/v2进行验证，但云服务器不要忘记打开端口，这里就不演示\n要求是将自己的容器打包成镜像上传，于是先要将要求1中运行的mytomcat容器打包为镜像：\ndocker commit mytomcat mytomcat:v1 使用docker image ls进行验证，得到输出\n[root@iZwz ~]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE mytomcat v1 eaa2d1ecc9f4 6 seconds ago 680MB tomcat latest 24207ccc9cce 4 days ago 680MB registry 2 b8604a3fe854 4 weeks ago 26.2MB 可以看到mytomcat:v1已经存在了，这个时候我们要对这个镜像进行tag标识要推送到私有库的镜像：\ndocker tag mytomcat:v1 localhost:5000/mytomcat:v1 继续使用ls查看镜像，可以看到已经多出了一个localhost开头的镜像：\n[root@iZwz91gvv1j6jku0ez9hmkZ ~]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE mytomcat v1 eaa2d1ecc9f4 41 minutes ago 680MB localhost:5000/mytomcat v1 eaa2d1ecc9f4 41 minutes ago 680MB tomcat latest 24207ccc9cce 4 days ago 680MB registry 2 b8604a3fe854 4 weeks ago 26.2MB 接下来将这个你打包好的镜像推送到私有库内：\ndocker push localhost:5000/mytomcat:v1 等待推送完成：\n[root@iZwz91gvv1j6jku0ez9hmkZ ~]# docker push localhost:5000/mytomcat:v1 The push refers to repository [localhost:5000/mytomcat] 8096205ddd5d: Pushed 0a62582891d7: Pushed 516aad68a0ec: Pushed f79d29235239: Pushed 5c81f9330d99: Pushed 927f9fcef4cf: Pushed a81f1846a0d2: Pushed 3b441d7cb46b: Pushed d3710de04cb3: Pushed 91f7336bbfff: Pushed e2e8c39e0f77: Pushed v1: digest: sha256:027b4b6270ba0c0667c1966dc1c372be3fbc1ed279e21bd5883879b009a9693b size: 2632 推送部分大功告成！！\n然后我们进行拉取私有库的操作\n先将localhost的本地镜像删除（这里并不会删除私有库内的镜像）：\ndocker image rm localhost:5000/mytomcat:v1 然后重新从私有库拉取：\ndocker pull localhost:5000/mytomcat:v1 进行 docker image ls 操作查看下是否拉取成功：\n[root@iZwz91gvv1j6jku0ez9hmkZ ~]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE mytomcat v1 eaa2d1ecc9f4 47 minutes ago 680MB localhost:5000/mytomcat v1 eaa2d1ecc9f4 47 minutes ago 680MB tomcat latest 24207ccc9cce 4 days ago 680MB registry 2 b8604a3fe854 4 weeks ago 26.2MB 恭喜完成！\n","permalink":"https://www.kutake.top/archives/docker-2021-12-10/","summary":"\u003c!-- toc --\u003e\n\u003cblockquote\u003e\n    \u003cp\u003eDocker 是一个用于开发，交付和运行应用程序的开放平台。Docker 使您能够将应用程序与基础架构分开，从而可以快速交付软件。借助 Docker，您可以与管理应用程序相同的方式来管理基础架构。通过利用 Docker 的方法来快速交付，测试和部署代码，您可以大大减少编写代码和在生产环境中运行代码之间的延迟。\u003c/p\u003e\n\u003cp\u003e菜鸟教程--docker的优点\u003c/p\u003e\n\n  \u003c/blockquote\u003e","title":"Docker-2021/12/10"},{"content":"CREATE TABLE Student( Sno Char(3) NOT NULL PRIMARY KEY, Sname CHAR(3) NOT NULL, Ssex CHAR(2) NOT NULL, Sbirthday datetime, Class CHAR(5) ) CREATE TABLE Teacher( Tno CHAR(3) PRIMARY KEY, Tname CHAR(4) NOT NULL, Tsex CHAR(2) NOT NULL, Tbirthday datetime, Prof CHAR(6), Depart VARCHAR(10) NOT NULL ) CREATE TABLE Course( Cno CHAR(5) NOT NULL PRIMARY KEY, Cname VARCHAR(10) NOT NULL, Tno CHAR(3) NOT NULL, FOREIGN KEY (Tno) REFERENCES Teacher(Tno) ) CREATE TABLE Score( Sno CHAR(3) NOT NULL, Cno CHAR(5) NOT NULL, Degree Decimal(4,1) FOREIGN KEY (Sno) REFERENCES Student(Sno), FOREIGN KEY (Cno) REFERENCES Course(Cno) ) ","permalink":"https://www.kutake.top/archives/note-%E6%95%B0%E6%8D%AE%E5%BA%93%E5%8E%9F%E7%90%86%E4%B8%8E%E5%BA%94%E7%94%A8p113-sql%E8%A1%A8%E5%88%9B%E5%BB%BA/","summary":"\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eCREATE TABLE Student(\n\tSno Char(3) NOT NULL PRIMARY KEY,\n\tSname CHAR(3) NOT NULL,\n\tSsex CHAR(2) NOT NULL,\n\tSbirthday datetime,\n\tClass CHAR(5)\n)\n\nCREATE TABLE Teacher(\n\t\tTno CHAR(3) PRIMARY KEY,\n\t\tTname CHAR(4) NOT NULL,\n\t\tTsex CHAR(2) NOT NULL,\n\t\tTbirthday datetime,\n\t\tProf CHAR(6),\n\t\tDepart VARCHAR(10) NOT NULL\n)\n\nCREATE TABLE Course(\n\tCno CHAR(5) NOT NULL PRIMARY KEY,\n\tCname VARCHAR(10) NOT NULL,\n\tTno CHAR(3) NOT NULL,\n\tFOREIGN KEY (Tno) REFERENCES Teacher(Tno)\n\t)\n\nCREATE TABLE Score(\n\t\tSno CHAR(3) NOT NULL,\n\t\tCno CHAR(5) NOT NULL,\n\t\tDegree Decimal(4,1)\n\t\tFOREIGN KEY (Sno) REFERENCES Student(Sno),\n\t\tFOREIGN KEY (Cno) REFERENCES Course(Cno)\n)\n\u003c/code\u003e\u003c/pre\u003e","title":"Note: 数据库原理与应用P113 sql表创建"},{"content":" 思路 C++实在是弄不懂\n代码 from typing import List, Optional class Node: \u0026#34;\u0026#34;\u0026#34; 结点 \u0026#34;\u0026#34;\u0026#34; def __init__(self): self.name: Optional[str] = None self.weight: Optional[int] = None self.left_child: Optional[Node] = None self.right_child: Optional[Node] = None self.parent: Optional[Node] = None class NodeCode: \u0026#34;\u0026#34;\u0026#34; 结点的编码 \u0026#34;\u0026#34;\u0026#34; def __init__(self, name: str, code: str): self.name = name self.code = code def __str__(self): return f\u0026#34;{self.name}:{self.code}\u0026#34; class HaffumanTreeCode: \u0026#34;\u0026#34;\u0026#34; 哈夫曼树的编码 \u0026#34;\u0026#34;\u0026#34; def __init__(self): self.code_list: List[NodeCode] = [] def append(self, code: NodeCode): self.code_list.append(code) def code(self, massage: str): \u0026#34;\u0026#34;\u0026#34; 针对编码表编码 :param massage: 需要编码的信息 :return: \u0026#34;\u0026#34;\u0026#34; code = \u0026#34;\u0026#34; for i in massage: for n in self.code_list: if n.name == i: code += n.code break # raise ValueError(f\u0026#34;\u0026#39;{i}\u0026#39;不存在\u0026#34;) return code def decode(self, code: str): \u0026#34;\u0026#34;\u0026#34; 针对编码表解码 :param code:需要解码的哈夫曼码 :return: \u0026#34;\u0026#34;\u0026#34; out = \u0026#34;\u0026#34; i = 0 while code: code_need_match = code[0:i+1] for n in self.code_list: if n.code == code_need_match: out += n.name code = code[i+1:] i = 0 break i += 1 return out def __str__(self): \u0026#34;\u0026#34;\u0026#34; 打印 :return: \u0026#34;\u0026#34;\u0026#34; out = \u0026#34;\u0026#34; for i in self.code_list: out += i.__str__() + \u0026#34;\\n\u0026#34; return out.strip() class HaffumanTree: \u0026#34;\u0026#34;\u0026#34; 哈夫曼树 \u0026#34;\u0026#34;\u0026#34; def __init__(self): self.root = Node() self.nodes: List[Node] = [] self.nodes_raw: List[Node] = [] self.codes: HaffumanTreeCode = HaffumanTreeCode() def inser(self, newnode: Node): for i in range(len(self.nodes)): if self.nodes[i].weight \u0026lt; newnode.weight: self.nodes.insert(i, newnode) return self.nodes.append(newnode) def build_tree(self, name_list: List[str], weight_list: List[int]): \u0026#34;\u0026#34;\u0026#34; 创建哈夫曼树 :param name_list: 字符列表 :param weight_list: 权重列表 :return: \u0026#34;\u0026#34;\u0026#34; # 队每个字符生成结点 while len(name_list) \u0026gt; 0: max_weight = max(weight_list) max_index = weight_list.index(max_weight) max_name = name_list[max_index] newnode = Node() newnode.name = max_name newnode.weight = max_weight self.nodes.append(newnode) name_list.remove(max_name) weight_list.remove(max_weight) # 保存源结点 self.nodes_raw = self.nodes[:] # 创建新结点并生成树 while len(self.nodes) \u0026gt; 2: left = self.nodes.pop() right = self.nodes.pop() newnode = Node() newnode.weight = left.weight + right.weight newnode.left_child = left newnode.right_child = right left.parent = newnode right.parent = newnode # 根据权重插入 self.inser(newnode) left = self.nodes.pop() right = self.nodes.pop() self.root.weight = left.weight + right.weight self.root.left_child = left self.root.right_child = right left.parent = self.root right.parent = self.root def make_code(self): \u0026#34;\u0026#34;\u0026#34; 生成编码表 :return: \u0026#34;\u0026#34;\u0026#34; while len(self.nodes_raw): node = self.nodes_raw.pop() name = node.name code = \u0026#34;\u0026#34; while node.parent != self.root: parent = node.parent if parent.left_child == node: code = \u0026#34;0\u0026#34; + code elif parent.right_child == node: code = \u0026#34;1\u0026#34; + code node = parent if self.root.left_child == node: code = \u0026#34;0\u0026#34; + code elif self.root.right_child == node: code = \u0026#34;1\u0026#34; + code self.codes.append(NodeCode(name, code)) def decode(self, code: str): return self.codes.decode(code) def code(self, massage: str): return self.codes.code(massage) if __name__ == \u0026#39;__main__\u0026#39;: h = HaffumanTree() name_list = [\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;c\u0026#34;, \u0026#34;d\u0026#34;, \u0026#34;e\u0026#34;] wight_list = [5, 11, 6, 21, 19] h.build_tree(name_list, wight_list) h.make_code() print(f\u0026#34;编码表：\\n{h.codes}\u0026#34;) print(f\u0026#39;bcacce编码为：{h.code(\u0026#34;bcacce\u0026#34;)}\u0026#39;) print(f\u0026#39;01001000001001解码为： {h.decode(\u0026#34;0100100000100110\u0026#34;)}\u0026#39;) ","permalink":"https://www.kutake.top/archives/%E5%93%88%E5%A4%AB%E6%9B%BC%E7%BC%96%E7%A0%81%E7%9A%84%E5%AE%9E%E7%8E%B0python/","summary":"\u003c!-- toc --\u003e\n\u003ch2 id=\"思路\"\u003e思路\u003c/h2\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/media/2021/11/%E5%93%88%E5%A4%AB%E6%9B%BC%E7%BC%96%E7%A0%81%E5%AE%9E%E7%8E%B0%EF%BC%88python%EF%BC%89-1024x332.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cdel\u003eC++实在是弄不懂\u003c/del\u003e\u003c/p\u003e\n\u003ch2 id=\"代码\"\u003e代码\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003efrom typing import List, Optional\n\nclass Node:\n    \u0026#34;\u0026#34;\u0026#34;\n    结点\n    \u0026#34;\u0026#34;\u0026#34;\n    def __init__(self):\n        self.name: Optional[str] = None\n        self.weight: Optional[int] = None\n        self.left_child: Optional[Node] = None\n        self.right_child: Optional[Node] = None\n        self.parent: Optional[Node] = None\n\nclass NodeCode:\n    \u0026#34;\u0026#34;\u0026#34;\n    结点的编码\n    \u0026#34;\u0026#34;\u0026#34;\n    def __init__(self, name: str, code: str):\n        self.name = name\n        self.code = code\n\n    def __str__(self):\n        return f\u0026#34;{self.name}:{self.code}\u0026#34;\n\nclass HaffumanTreeCode:\n    \u0026#34;\u0026#34;\u0026#34;\n    哈夫曼树的编码\n    \u0026#34;\u0026#34;\u0026#34;\n    def __init__(self):\n        self.code_list: List[NodeCode] = []\n\n    def append(self, code: NodeCode):\n        self.code_list.append(code)\n\n    def code(self, massage: str):\n        \u0026#34;\u0026#34;\u0026#34;\n        针对编码表编码\n        :param massage: 需要编码的信息\n        :return:\n        \u0026#34;\u0026#34;\u0026#34;\n        code = \u0026#34;\u0026#34;\n        for i in massage:\n            for n in self.code_list:\n                if n.name == i:\n                    code += n.code\n                    break\n            # raise ValueError(f\u0026#34;\u0026#39;{i}\u0026#39;不存在\u0026#34;)\n        return code\n\n    def decode(self, code: str):\n        \u0026#34;\u0026#34;\u0026#34;\n        针对编码表解码\n        :param code:需要解码的哈夫曼码\n        :return:\n        \u0026#34;\u0026#34;\u0026#34;\n        out = \u0026#34;\u0026#34;\n        i = 0\n        while code:\n            code_need_match = code[0:i+1]\n            for n in self.code_list:\n                if n.code == code_need_match:\n                    out += n.name\n                    code = code[i+1:]\n                    i = 0\n                    break\n            i += 1\n        return out\n\n    def __str__(self):\n        \u0026#34;\u0026#34;\u0026#34;\n        打印\n        :return:\n        \u0026#34;\u0026#34;\u0026#34;\n        out = \u0026#34;\u0026#34;\n        for i in self.code_list:\n            out += i.__str__() + \u0026#34;\\n\u0026#34;\n        return out.strip()\n\nclass HaffumanTree:\n    \u0026#34;\u0026#34;\u0026#34;\n    哈夫曼树\n    \u0026#34;\u0026#34;\u0026#34;\n    def __init__(self):\n        self.root = Node()\n        self.nodes: List[Node] = []\n        self.nodes_raw: List[Node] = []\n        self.codes: HaffumanTreeCode = HaffumanTreeCode()\n\n    def inser(self, newnode: Node):\n        for i in range(len(self.nodes)):\n            if self.nodes[i].weight \u0026lt; newnode.weight:\n                self.nodes.insert(i, newnode)\n                return\n        self.nodes.append(newnode)\n\n    def build_tree(self, name_list: List[str], weight_list: List[int]):\n        \u0026#34;\u0026#34;\u0026#34;\n        创建哈夫曼树\n        :param name_list: 字符列表\n        :param weight_list: 权重列表\n        :return:\n        \u0026#34;\u0026#34;\u0026#34;\n        # 队每个字符生成结点\n        while len(name_list) \u0026gt; 0:\n            max_weight = max(weight_list)\n            max_index = weight_list.index(max_weight)\n            max_name = name_list[max_index]\n            newnode = Node()\n            newnode.name = max_name\n            newnode.weight = max_weight\n            self.nodes.append(newnode)\n            name_list.remove(max_name)\n            weight_list.remove(max_weight)\n\n        # 保存源结点\n        self.nodes_raw = self.nodes[:]\n\n        # 创建新结点并生成树\n        while len(self.nodes) \u0026gt; 2:\n            left = self.nodes.pop()\n            right = self.nodes.pop()\n            newnode = Node()\n            newnode.weight = left.weight + right.weight\n            newnode.left_child = left\n            newnode.right_child = right\n            left.parent = newnode\n            right.parent = newnode\n            # 根据权重插入\n            self.inser(newnode)\n\n        left = self.nodes.pop()\n        right = self.nodes.pop()\n        self.root.weight = left.weight + right.weight\n        self.root.left_child = left\n        self.root.right_child = right\n        left.parent = self.root\n        right.parent = self.root\n\n    def make_code(self):\n        \u0026#34;\u0026#34;\u0026#34;\n        生成编码表\n        :return:\n        \u0026#34;\u0026#34;\u0026#34;\n        while len(self.nodes_raw):\n            node = self.nodes_raw.pop()\n            name = node.name\n            code = \u0026#34;\u0026#34;\n            while node.parent != self.root:\n                parent = node.parent\n                if parent.left_child == node:\n                    code = \u0026#34;0\u0026#34; + code\n                elif parent.right_child == node:\n                    code = \u0026#34;1\u0026#34; + code\n                node = parent\n            if self.root.left_child == node:\n                code = \u0026#34;0\u0026#34; + code\n            elif self.root.right_child == node:\n                code = \u0026#34;1\u0026#34; + code\n            self.codes.append(NodeCode(name, code))\n\n    def decode(self, code: str):\n        return self.codes.decode(code)\n\n    def code(self, massage: str):\n        return self.codes.code(massage)\n\nif __name__ == \u0026#39;__main__\u0026#39;:\n    h = HaffumanTree()\n    name_list = [\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;c\u0026#34;, \u0026#34;d\u0026#34;, \u0026#34;e\u0026#34;]\n    wight_list = [5, 11, 6, 21, 19]\n    h.build_tree(name_list, wight_list)\n    h.make_code()\n    print(f\u0026#34;编码表：\\n{h.codes}\u0026#34;)\n    print(f\u0026#39;bcacce编码为：{h.code(\u0026#34;bcacce\u0026#34;)}\u0026#39;)\n    print(f\u0026#39;01001000001001解码为： {h.decode(\u0026#34;0100100000100110\u0026#34;)}\u0026#39;)\n\u003c/code\u003e\u003c/pre\u003e","title":"哈夫曼编码的实现(python)"},{"content":" 项目地址：xinntao/Real-ESRGAN: Real-ESRGAN aims at developing Practical Algorithms for General Image Restoration. (github.com)\n中文介绍：Real-ESRGAN/README_CN.md at master · xinntao/Real-ESRGAN (github.com)\n使用方法 先进行下载，后解压缩，解压后文件如图\n在这个文件夹路径内输入cmd，回车\n输入cmd后回车\n就来到了命令终端：\n之后把要放大的图片放在这个文件夹内，比如我这里是test.jpg\n之后在命令行内输入命令\n./realesrgan-ncnn-vulkan.exe -i test.jpg -o 方法后的图片名.png -n realesrgan-x4plus-anime 最后的-n realesrgan-x4plus-anime指使用动漫专用放大模型进行放大，对动漫图片效果更好，如果是普通图片则去掉即可\n若出现'.' 不是内部或外部命令，也不是可运行的程序，则将命令变为\nrealesrgan-ncnn-vulkan.exe -i test.jpg -o 方法后的图片名.png -n realesrgan-x4plus-anime 如图\n输出100%即为放大完成\n","permalink":"https://www.kutake.top/archives/real-esrgan%E8%85%BE%E8%AE%AF%E7%9A%84%E7%B1%BBwiafu2x%E4%BA%8C%E6%AC%A1%E5%85%83%E5%9B%BE%E7%89%87%E6%94%BE%E5%A4%A7%E7%AE%97%E6%B3%95/","summary":"\u003c!-- toc --\u003e\n\u003cp\u003e项目地址：\u003ca href=\"https://github.com/xinntao/Real-ESRGAN\"\u003exinntao/Real-ESRGAN: Real-ESRGAN aims at developing Practical Algorithms for General Image Restoration. (github.com)\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e中文介绍：\u003ca href=\"https://github.com/xinntao/Real-ESRGAN/blob/master/README_CN.md\"\u003eReal-ESRGAN/README_CN.md at master · xinntao/Real-ESRGAN (github.com)\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"使用方法\"\u003e使用方法\u003c/h2\u003e\n\u003cp\u003e先进行\u003ca href=\"https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/realesrgan-ncnn-vulkan-20210901-windows.zip\"\u003e下载\u003c/a\u003e，后解压缩，解压后文件如图\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/media/2021/11/image.png\"\u003e\u003c/p\u003e\n\u003cp\u003e在这个文件夹路径内输入\u003ccode\u003ecmd\u003c/code\u003e，回车\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/media/2021/11/image-1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e输入cmd后回车\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e就来到了命令终端：\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/media/2021/11/image-2.png\"\u003e\u003c/p\u003e\n\u003cp\u003e之后把要放大的图片放在这个文件夹内，比如我这里是test.jpg\u003c/p\u003e","title":"Real-ESRGAN腾讯的类WIAFU2X二次元图片放大算法"},{"content":" 也许是我这种小白在反向代理中最常面临的问题 Nas上挂载过很多自用的站点，提供包括影音、docker管理、qB客户端之类的服务，在穿透到云端并使用Nginx进行反向代理时经常会遇到各种各样无法访问的问题：\n在Portainer的容器中无法进入命令行，日志无法实时更新 qB的web客户端永远无法登陆成功 Pcs客户端无法打开 各种依赖websocket的服务 首先找到问题所在都很玄学，因为除非是非常具体的问题，否则连百度谷歌都找不到一点痕迹。\n比如qB的web客户端无法登陆，百度到的都是什么忘记密码、密码错误之类的模糊问答，很难翻到正确的解决方案。\n解决方案 最开始发现问题所在是因为Portainer的命令行问题，也可能是因为问题足够具体，我直接在Portainer的github issue page中发现了答案：Unable to console into containers · Issue #1887 · portainer/portainer (github.com)\n按照提示在Nginx配置参数里加入proxy_set_header属性后，成功解决问题。\n使用Nginx做WebSockets代理教程\nWebSockets应用程序会在客户端和服务器之间建立一个长连接，使得开发实时应用很容易。HTTP的Upgrade协议头机制用于将连接从HTTP连接升级到WebSocket连接，Upgrade机制使用了Upgrade协议头和Connection协议头。反向代理服务器在支持WebSocket协议方面面临着一些挑战。挑战之一是WebSocket是一个逐段转发（hop-by-hop）协议，因此当代理服务器拦截到来自客户端的Upgrade请求时，代理服务器需要将自己的Upgrade请求发送给后端服务器，包括适合的请求头。而且，由于WebSocket连接是长连接，与传统的HTTP端连接截然不同，故反向代理服务器还需要允许这些连接处于打开（Open）状态，而不能因为其空闲就关闭了连接。\n","permalink":"https://www.kutake.top/archives/nginx%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E4%B8%AD%E6%B6%89%E5%8F%8Awebsocket%E7%9A%84%E9%97%AE%E9%A2%98/","summary":"\u003c!-- toc --\u003e\n\u003ch2 id=\"也许是我这种小白在反向代理中最常面临的问题\"\u003e也许是我这种小白在反向代理中最常面临的问题\u003c/h2\u003e\n\u003cp\u003eNas上挂载过很多自用的站点，提供包括影音、docker管理、qB客户端之类的服务，在穿透到云端并使用Nginx进行反向代理时经常会遇到各种各样无法访问的问题：\u003c/p\u003e","title":"Nginx反向代理中涉及Websocket的问题"},{"content":" 远程文件访问需求 家里的Nas里面有音乐、电影、动漫啥的需要在外面进行访问，如果是单纯的视听，那么解决方法非常多，docker就可以部署很多成品的影音娱乐应用，再对端口进行穿透即可访问。但我这里有个需求，有些动漫需要用第三方客户端加载弹幕才看的输出，这个第三方客户端也没有对远程视频文件进行支持，所以还是选择了将文件挂载到本地环境的方法。\n像SMB、FTP、NFS、Webdav都是解决这个问题的方法，但除了Webdav之外都有个问题，这些协议并非使用一个固定的端口，在速度上，似乎Webdav也是最好的选择（求大神解答关于NFS,SMB,WebDAV问题 - 知乎 (zhihu.com)），于是最终决定使用WebDav作为远程访问协议。\n出现问题 无法访问（非真实ip）\n设置的过程还算轻松，毕竟WebDav走的都是一个端口，设置好权限和端口就能开始访问了。但第一个问题就出现了，win10提示无法访问\n既然https不行，那就试试http吧，一通操作之后，连上了！迫不及待的进行测试，打开图片，OK；打开音乐，OK；打开视频，报错……\n这是为什么？\n进行一个百度的搜索，终于找到了问题所在，原链接指路\n原来是windows会限制webdav的最大传输文件大小，修改下注册表，改成4G，重启，再测试一下打开视频，好消息是，能成功看了！坏消息是，加载非常慢……\n慢的原因其实很好找，打开任务管理器，可以看到webclient一直在跑带宽，很容易就能猜到这是在把文件缓存入本地磁盘。这就很不合理了，视频本身就不小，还只能全部缓存完毕后才能播放，就不能像web一样看视频么。\n解决卡顿 还是万能的百度，找到了一个webdav客户端，RaiDrive\n配置上后，播放什么都非常丝滑，可见第三方客户端对大文件传输进行了专门的优化，拖拉进度条也只需要一点加载时间，体验跟web端的播放没什么区别。\n就是这个容量显示有点……\n","permalink":"https://www.kutake.top/archives/win%E4%B8%8B%E6%8C%82%E8%BD%BDwebdav%E5%8D%A1%E9%A1%BF%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/","summary":"\u003c!-- toc --\u003e\n\u003ch2 id=\"远程文件访问需求\"\u003e远程文件访问需求\u003c/h2\u003e\n\u003cp\u003e家里的Nas里面有音乐、电影、动漫啥的需要在外面进行访问，如果是单纯的视听，那么解决方法非常多，docker就可以部署很多成品的影音娱乐应用，再对端口进行穿透即可访问。但我这里有个需求，有些动漫需要用第三方客户端加载弹幕才看的输出，这个第三方客户端也没有对远程视频文件进行支持，所以还是选择了将文件挂载到本地环境的方法。\u003c/p\u003e","title":"Win下挂载Webdav卡顿问题的解决方法"},{"content":" Openwrt 按照我浅显的了解，这是一个开源的路由系统，本质就是个用在路由器上的linux，具体的作用非常多，上网管控、广告屏蔽、dns管理、KMS破解、网易云灰色歌曲解锁、内网穿肉、游戏加速、分流分拨等等等等非常多的功能就不一一介绍。\n这里只记录一下基础的设置，这样不用每次出毛病就得到处找教程\n安装 我的方法是W10PE+physdiskwrite写盘（当然具体方法还有非常多）\n使用PE的U盘，并提前将软件和镜像拷贝进U盘，开机使用F11、F12等案件选择引导进入PE系统\n# 将exe和img放在同一文件夹 # cmd physdiskwrite -u openwrt.img # 选择要写入的硬盘，一般为0 0 # y确定 y # 等待写盘完成，重启 具体的img各种论坛非常多，这里贴个我在用的\nOpenWrt ipv6/docker/大全版/精简版/旁路由版 - 软路由，x86系统，openwrt(x86) 等 - 恩山无线论坛\n192.168.1.1 admin password 这个插件的好处就是不需要像原本一样过多的设置，ADhome和smartDNS中内置了很多选项，甚至端口和外置硬盘挂载也是自动进行的（还是会习惯性的确认\n设置 我是作为旁路由使用，主路由设置会不一样，首先需要让电脑进行访问：\n使用网线或其他内网环境让物理网络上与OP设备连接，修改电脑本地ip和网关\n192.168.1.2 192.168.1.1 # 当然只要是1.x就行 保存后进入游览器，输入192.168.1.1即可访问\n酸酸乳 其实用OP是为了什么当然很明显，但在ADhome+smartDNS的环境下需要非常注意。\n建议酸酸乳独立于smartDNS和ADhome，让它使用自己内置的dns进行解析，从功能上说，若是使用smartDNS进行解析，那么最终的结果是本机到目标的最快结果，而不是vps到目标的最快结果，这反而会让解析变得慢（当然论坛上是有解决方法的，但老实说对小白的我来说很苛刻）。\n跟ADhome的组合更是有奇怪的bug，我都记不清有几次遇到list模式不生效了，黑白名单的设置也很玄学，有时生效有时不生效，具体原因都找不到\nsmartDNS 其实不需要设置非常多的dns，两三个就可以了，太多的话可能会导致游览器出现重定向地狱（打开网页先找不到，再重定向去到正确的网页），当然要是想设置多点其实也没啥太大的问题~\n配合ADhome的话重定向选择无\nADhome 上游dns填smartDNS，关闭dns缓存，订阅一些规则，除了找规则可能耗点时间，其他算简单\n广告终结者使用的拦截规则，基于ChinaList+EasyList修正维护 anti-AD命中率最高列表 halflife合并乘风 EasyPrivacy隐私相关 GoodbyeAds 大全去广告 YouTube-去广告 以上推荐一些好用的规则\n最后 dns里填上ADgome的端口，将本地的DNS缓存设置为0，主路由与旁路由进行网关互指，结束\n扩容相关 记录这篇的目的其实就是因为扩容失败，翻了各种各样的教程，无一成功，第一次直接把系统整崩，整个重装\n从挂载Overlay到U盘，再到直接复制整个系统到U盘进行U盘启动，都失败了，我现在只能怀疑是U盘的问题\n弄了一个晚上，实在没心思去解决AD和smartDNS的各种奇怪问题，于是记录下一个大概的思路，方便以后的更改\n","permalink":"https://www.kutake.top/archives/openwrt-%E4%BB%8E%E5%AE%89%E8%A3%85%E5%88%B0%E6%8F%92%E4%BB%B6%E8%AE%BE%E7%BD%AE%E8%AE%B0%E5%BD%95/","summary":"\u003c!-- toc --\u003e\n\u003ch2 id=\"openwrt\"\u003eOpenwrt\u003c/h2\u003e\n\u003cp\u003e按照我浅显的了解，这是一个开源的路由系统，本质就是个用在路由器上的linux，具体的作用非常多，上网管控、广告屏蔽、dns管理、KMS破解、网易云灰色歌曲解锁、内网穿肉、游戏加速、分流分拨等等等等非常多的功能就不一一介绍。\u003c/p\u003e","title":"Openwrt-从安装到插件设置记录"},{"content":" 安装 pip install PyMySQL 连接 db = pymysql.connect(host=\u0026#34;192.168.1.1\u0026#34;, port=3306, user=\u0026#34;root\u0026#34;, password=\u0026#34;password\u0026#34;, database=\u0026#34;database_name\u0026#34;) cursor = db.cursor() 查/增 sql_insert = \u0026#34;\u0026#34;\u0026#34; INSERT INTO 表名(id, name, path) VALUES (%s, %s, %s) \u0026#34;\u0026#34;\u0026#34; # 无论数据库数据类型为int或date，全部使用py的字符串形式插入，所以使用%s cursor.execute(sql_insert, (user_id, name, path)) # 这种可以有效避免因敏感字符而导致出现语法错误 db.commit() # 进行提交，否则无效 # 当数据源为http相关时，注意转义 import html from pymysql.converters import escape_string safe_data = escape_string(html.unescape(unsafe_data)) # unescape转义html字符，escape转义为可直接插入数据库的安全字符串 # 多数据一次插入 cursor.executemany(sql, info) # info可为list，循环按顺序进行插入 db.commit() 以及 因为图库已经快20g了，为了管理方便，顺便就上了mysql做数据管理，但有个问题，pymysql非异步，可能会造成bot的io堵塞，现在的数据量查询速度还算快，但要是时间久了就不一定；\n另外，pymysql非异步，但bot中的操作是安全的，暂时不存在两个及以上的模块对mysql进行操作。\n2021-11更新 另一个插件也上了数据库作查重，用一个pymysql实例对象的调用来实现的，到目前为止没有出现问题\n","permalink":"https://www.kutake.top/archives/pymysql%E5%9F%BA%E7%A1%80%E4%BD%BF%E7%94%A8/","summary":"\u003c!-- toc --\u003e\n\u003ch2 id=\"安装\"\u003e安装\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003epip install PyMySQL\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"连接\"\u003e连接\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003edb = pymysql.connect(host=\u0026#34;192.168.1.1\u0026#34;, port=3306, user=\u0026#34;root\u0026#34;, password=\u0026#34;password\u0026#34;, database=\u0026#34;database_name\u0026#34;)\ncursor = db.cursor()\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"查增\"\u003e查/增\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003esql_insert = \u0026#34;\u0026#34;\u0026#34;\n                    INSERT INTO 表名(id, name, path)\n                    VALUES (%s, %s, %s)\n                    \u0026#34;\u0026#34;\u0026#34;\n# 无论数据库数据类型为int或date，全部使用py的字符串形式插入，所以使用%s\ncursor.execute(sql_insert, (user_id, name, path)) # 这种可以有效避免因敏感字符而导致出现语法错误\n\ndb.commit() # 进行提交，否则无效\n\n# 当数据源为http相关时，注意转义\nimport html\nfrom pymysql.converters import escape_string\n\nsafe_data = escape_string(html.unescape(unsafe_data)) # unescape转义html字符，escape转义为可直接插入数据库的安全字符串\n\n# 多数据一次插入\ncursor.executemany(sql, info) # info可为list，循环按顺序进行插入\ndb.commit()\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"以及\"\u003e以及\u003c/h2\u003e\n\u003cp\u003e因为图库已经快20g了，为了管理方便，顺便就上了mysql做数据管理，但有个问题，pymysql非异步，可能会造成bot的io堵塞，现在的数据量查询速度还算快，但要是时间久了就不一定；\u003c/p\u003e","title":"PyMySQL基础使用"},{"content":" 起因 因为Gitlab各种设置实在是繁琐+奇怪，受不鸟还是转回gogs了，秒重启+秒开真的舒服，Gitlab的消耗我的小Nas实在是受不起。\n但有个很奇怪的事情，stmp设置完重启之后没一点反应，后台一直没有开启对应的功能。\n这里补充一个配置文件详细项，Gogs的配置文件里并不是所有功能都有显示false或true，需要自己填写\n解决方法 最终的答案在这里找到的：配置文件变更\n# 原配置文件 [mailer] ENABLED = true HOST = smtp.qq.com:465 FROM = USER = PASSWD = # 实际有效的配置文件 [email] ENABLED = true HOST = smtp.qq.com:465 FROM = USER = PASSWORD = 具体开始出现这个错误的版本不清楚，只能期待下后续的修复了\n目前看gogs的更新频率挺低的，可能无望...\n","permalink":"https://www.kutake.top/archives/gogs-0-13dev%E7%89%88%E6%9C%AC%E9%82%AE%E4%BB%B6%E6%9C%8D%E5%8A%A1%E8%AE%BE%E7%BD%AE%E5%A4%B1%E8%B4%A5%E6%96%B9%E6%B3%95/","summary":"\u003c!-- toc --\u003e\n\u003ch2 id=\"起因\"\u003e起因\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e因为Gitlab各种设置实在是繁琐+奇怪\u003c/strong\u003e，受不鸟还是转回gogs了，秒重启+秒开真的舒服，Gitlab的消耗我的小Nas实在是受不起。\u003c/p\u003e","title":"Gogs-0.13Dev版本邮件服务设置失败方法"},{"content":" 自建Git - Gogs/Gitlab 代码修修改改多了，就有了版本控制的需求，Github是最好的选择，但对于个人来说，代码里面有非常重要的隐私信息，又需要方便的进行分享，于是就转到了自建git服务器。\nGogs：go语言编写的git服务，简单而强大，支持多用户；适合个人开发者 Gitlab：功能非常全面的git服务，适合公司与个人开发者的使用；资源消耗大 最开始我选择了Gogs，主要是gitlab资源消耗太大，小机子支撑不起内存消耗。目前更换成了8G内存，于是转到了Gitlab，毕竟界面美观且功能完整\n从自建邮箱到配置文件 搭gitlab的过程很顺利，项目从gogs转移到gitlab上也很方便，但有个问题，gitlab的账户邮箱是需要进行验证的，找遍了面板设置之后，百度到了是需要对配置文件gitlab.rb进行修改。然后！我看到了宝塔面板的邮局管理器插件。\n宝塔邮局管理器：多域，多用户邮局，支持SMTP/IMAP/POP3/HTTP接口，需要25端口 好家伙，那我岂不是可以拥有一个自定义域名的邮箱了！？立马设置上。从MX解析到TXT防垃圾，设置域名用户再修改gitlab配置文件，弄了一堆之后，邮件发不出去\n中间翻着百度，配置文件改来改去，试了几次之后，我想着是不是25端口不给开，去了腾讯云的文档看了下，发现除了不支持默认开放25端口之外，也不允许用本机进行邮件发送，否则腾讯云有理由进行永久封禁。那没办法，还是用qq的stmp吧。改了几遍，最终还是发不出去...\n无语的错误 到处百度之后，了解到gitlab默认是使用sendmail的，只有配置了STMP之后，才会使用。\n进去容器，开启一个终端，输入命令\ngitlab-rails console # 进入gitlab控制台 ActionMailer::Base.delivery_method # 查询邮件服务依赖 =\u0026gt; :sendmail # 返回 好家伙，这怎么用的sendmail，我不是配置了STMP么？\n于是我终于发现了问题所在\n# STMP配置原文 # gitlab_rails[\u0026#39;smtp_enable\u0026#39;] = true # gitlab_rails[\u0026#39;smtp_address\u0026#39;] = \u0026#34;smtp.qq.com\u0026#34; # gitlab_rails[\u0026#39;smtp_port\u0026#39;] = 465 # gitlab_rails[\u0026#39;smtp_user_name\u0026#39;] = \u0026#34;123456789@qq.com\u0026#34; # gitlab_rails[\u0026#39;smtp_password\u0026#39;] = \u0026#34;qqpass\u0026#34; # gitlab_rails[\u0026#39;smtp_domain\u0026#39;] = \u0026#34;smtp.qq.com\u0026#34; # gitlab_rails[\u0026#39;smtp_authentication\u0026#39;] = \u0026#34;login\u0026#34; # gitlab_rails[\u0026#39;smtp_enable_starttls_auto\u0026#39;] = true # gitlab_rails[\u0026#39;smtp_tls\u0026#39;] = true 这里有#，是因为配置文件所有代码都是#开头，我一直没注意到，#是有注释的意义的，我一直没有删除注释，修改来修改去的做了一大堆无用功...\n把注释取消，重启，一次成功。\n好吧......\n","permalink":"https://www.kutake.top/archives/%E6%97%A0%E8%AF%AD%E7%9A%84%E6%8A%98%E8%85%BE%E8%AE%B0%E5%BD%95-gitlab%E7%9A%84stmp%E6%9C%8D%E5%8A%A1%E8%AE%BE%E7%BD%AE/","summary":"\u003c!-- toc --\u003e\n\u003ch2 id=\"自建git---gogsgitlab\"\u003e自建Git - Gogs/Gitlab\u003c/h2\u003e\n\u003cp\u003e代码修修改改多了，就有了版本控制的需求，Github是最好的选择，但对于个人来说，代码里面有非常重要的隐私信息，又需要方便的进行分享，于是就转到了自建git服务器。\u003c/p\u003e","title":"无语的折腾记录-Gitlab的STMP服务设置"},{"content":" 轻量web服务 介绍 FastAPI 是一个用于构建 API 的现代、快速（高性能）的 web 框架，使用 Python 3.6+ 并基于标准的 Python 类型提示。\nFastAPI 框架，高性能，易于学习，高效编码，生产可用\n文档地址：fastapi\n使用 安装 pip install fastapi pip install uvicorn # 启动方式 构建app # main.py from fastapi import FastAPI app = FastAPI() @app.get(\u0026#34;/\u0026#34;) async def root(): return {\u0026#34;message\u0026#34;: \u0026#34;Hello World\u0026#34;} 启动 uvicorn main:app --reload 或者以代码方式启动：\n# main.py uvicorn.run(app, host=\u0026#34;127.0.0.1\u0026#34;, port=8000, log_level=\u0026#34;debug\u0026#34;, loop=\u0026#34;asyncio\u0026#34;) 访问 在游览器内输入localhost:8000即可\n作为saya模块使用并进行交互 此处仅仅为了进行web相关的学习，目前不具备任何实用的功能\n模块目录 我尝试使用bootstrap作为web基础，但似乎无法访问到...\nstatic中存放静态文件，templates中存放模板html文件\nroot@Graia:/home/bot/modules# tree WebApi/ WebApi/ ├── __init__.py ├── api.py ├── static │ ├── css │ │ ├── bootstrap-grid.css │ │ ├── ...... │ │ └── bootstrap.rtl.min.css.map │ └── js │ ├── @eaDir │ │ ├── bootstrap.bundle.js@SynoEAStream │ │ ├── ...... │ │ └── bootstrap.min.js@SynoEAStream │ ├── ...... │ └── bootstrap.min.js.map └── templates ├── index.html ├── log.html └── modules.html 运行方式 fastapi是一个处理请求和发送的循环，无法在已经是个loop的brocast环境中运行，也许有注入运行的方法但是我没有找到；最后使用了线程方式运行一个fastapi服务\ndef run_api(): app_host = route_path.split(\u0026#39;:\u0026#39;) # uvicorn.run(app, host=\u0026#34;192.168.0.5\u0026#34;, port=8045, log_level=\u0026#34;debug\u0026#34;, loop=\u0026#34;asyncio\u0026#34;) uvicorn.run(app, host=app_host[0], port=int(app_host[1]), log_level=\u0026#34;debug\u0026#34;, loop=\u0026#34;asyncio\u0026#34;) def thred_run_api(saya: Saya): global apisaya # 此处是为了导入saya模块对象 apisaya = saya run_loop_thread = Thread(target=run_api) run_loop_thread.start() 功能\u0026amp;实现 由于是学习目的，目前仅尝试了三个页面\n一个显示运行时间的主页 一个显示运行了多少模块的展示页 一个实时显示日志的页面 主要框架我使用了bootstrap页面生成器，然后对着需求修改html和添加js即可\nJinja2模板渲染 需要实现根据请求发送带着信息的html页面，就需要模板来渲染\n在html文件上：\n\u0026lt;div class=\u0026#34;jumbotron\u0026#34;\u0026gt; \u0026lt;h1\u0026gt; 欢迎来到CUAVbot \u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt; 本次运行时间：{{ now_runtime }} \u0026lt;/p\u0026gt; \u0026lt;p\u0026gt; 总共运行时间：{{ all_runtime }} \u0026lt;/p\u0026gt; \u0026lt;p\u0026gt; \u0026lt;/div\u0026gt; 定义路由：\n@app.get(\u0026#34;/\u0026#34;) async def get_index(request: Request): return t.TemplateResponse( \u0026#34;index.html\u0026#34;, { \u0026#34;request\u0026#34;: request, \u0026#34;now_runtime\u0026#34;: \u0026#34;显示本次运行时长\u0026#34;, \u0026#34;all_runtime\u0026#34;: \u0026#34;显示总运行时长\u0026#34; } ) 这样每次请求就会发送对应的信息\n主页 如上，仅展示标题和运行时间\n模块页面 在html上定义表格，并在tbody内等待渲染（safe标签让Jinja2不对内容进行转义，可正常显示html标签）\n\u0026lt;table class=\u0026#34;table\u0026#34;\u0026gt; \u0026lt;thead\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;th\u0026gt; 编号 \u0026lt;/th\u0026gt; \u0026lt;th\u0026gt; 模块 \u0026lt;/th\u0026gt; \u0026lt;th\u0026gt; 作者 \u0026lt;/th\u0026gt; \u0026lt;th\u0026gt; 说明 \u0026lt;/th\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/thead\u0026gt; \u0026lt;tbody\u0026gt;{{ sayatable | safe }}\u0026lt;/tbody\u0026gt; 定义路由：\n@app.get(\u0026#34;/modules\u0026#34;) async def get_modules(request: Request): table_str = \u0026#39;\u0026#39; modules_temp = \u0026#34;\u0026lt;tr {}\u0026gt;\u0026lt;td\u0026gt;{}\u0026lt;/td\u0026gt;\u0026lt;td\u0026gt;{}\u0026lt;/td\u0026gt;\u0026lt;td\u0026gt;{}\u0026lt;/td\u0026gt;\u0026lt;td\u0026gt;{}\u0026lt;/td\u0026gt;\u0026lt;/tr\u0026gt;\u0026#34; class_temp = \u0026#39;class=\u0026#34;success\u0026#34;\u0026#39; channels = apisaya.channels trager = 1 for channel_name, channel_object in channels.items(): table_str += modules_temp.format( class_temp if (trager % 2) == 0 else \u0026#34;\u0026#34;, trager, channel_object._name, channel_object._author[0], channel_object._description.replace(\u0026#34;\\n\u0026#34;, \u0026#34;\u0026lt;br /\u0026gt;\u0026#34;) # 注意用换行标签替换字符 ) trager += 1 return t.TemplateResponse( \u0026#34;modules.html\u0026#34;, {\u0026#34;request\u0026#34;: request, \u0026#34;sayatable\u0026#34;: table_str} ) 效果\n日志页面 要求实时显示的话对我这种小白就有点复杂，首先是尝试在html中嵌入js来循环请求日志，但失败了\n最后是用了websockets方式来循环请求，请求是服务端来发送的，前端只进行接收\n首先在html上：\n\u0026lt;div id=\u0026#34;bot_log\u0026#34;\u0026gt; \u0026lt;/div\u0026gt; ...... \u0026lt;script type=\u0026#34;text/javascript\u0026#34;\u0026gt; function WebSocketTest(){ var ws = new WebSocket(\u0026#34;ws://localhost:8045/log/flash\u0026#34;); ws.onmessage = function (evt){ document.getElementById(\u0026#34;bot_log\u0026#34;).innerHTML = evt.data; } } WebSocketTest() \u0026lt;/script\u0026gt; WebSocket.onmessage是定义对接收信息的处理\n然后定义一个连接管理对象\nclass ConnectionManager: def __init__(self): # 存放激活的ws连接对象 self.active_connections: List[WebSocket] = [] async def connect(self, ws: WebSocket): # 等待连接 await ws.accept() # 存储ws连接对象 self.active_connections.append(ws) def disconnect(self, ws: WebSocket): # 关闭时 移除ws对象 self.active_connections.remove(ws) manager = ConnectionManager() 最后定义路由\nlog_file_path = os.popen(\u0026#34;ls -t ./log/ | head -n 1\u0026#34;).read() @app.websocket(\u0026#34;/log/flash\u0026#34;) async def flash_log(websocket: WebSocket): await manager.connect(websocket) try: while True: log_str = os.popen(f\u0026#34;tail -n 100 ./log/{log_file_path}\u0026#34;).read().replace(\u0026#34;\\n\u0026#34;, \u0026#34;\u0026lt;br /\u0026gt;\u0026#34;) await websocket.send_text(log_str) await asyncio.sleep(5) except WebSocketDisconnect: manager.disconnect(websocket) @app.route(\u0026#39;/log\u0026#39;) async def get_log(request: Request): return t.TemplateResponse( \u0026#34;log.html\u0026#34;, {\u0026#34;request\u0026#34;: request, \u0026#34;base_href\u0026#34;: base_href} ) 很简陋但是也算完成了需求\n一些坑 fastapi的websockets在虚拟环境下运行似乎会报错，不知道是什么原因，但就是一直报错弄的差点放弃了websockets\n","permalink":"https://www.kutake.top/archives/fastapi%E5%BA%94%E7%94%A8-bot%E4%B8%AD%E6%8C%82%E8%BD%BDweb%E6%9C%8D%E5%8A%A1/","summary":"\u003c!-- toc --\u003e\n\u003ch2 id=\"轻量web服务\"\u003e轻量web服务\u003c/h2\u003e\n\u003ch3 id=\"介绍\"\u003e介绍\u003c/h3\u003e\n\u003cblockquote\u003e\n    \u003cp\u003eFastAPI 是一个用于构建 API 的现代、快速（高性能）的 web 框架，使用 Python 3.6+ 并基于标准的 Python 类型提示。\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eFastAPI 框架，高性能，易于学习，高效编码，生产可用\u003c/em\u003e\u003c/p\u003e\n\n  \u003c/blockquote\u003e","title":"FastApi应用-bot中挂载web服务"},{"content":"https://www.youtube.com/watch?v=T3FpwfUlUnc\n","permalink":"https://www.kutake.top/archives/18%E5%B2%81%E7%9A%84%E6%B5%81%E6%B0%B4%E7%BA%BF/","summary":"\u003cp\u003e\u003ca href=\"https://www.youtube.com/watch?v=T3FpwfUlUnc\"\u003ehttps://www.youtube.com/watch?v=T3FpwfUlUnc\u003c/a\u003e\u003c/p\u003e","title":"《18岁的流水线》"},{"content":" 起不动的主板 这玩意是去年十一月淘宝入手的，到手上差不多半年，中间开过穿透，开过docker，还用了AD guard和smart DNS，替代我之前N1的软路由工作，运行的也很稳，J1900的cpu占用都很少上40%，直到现在......\n到家了之后发现没网，一点点排除之后，除了宽带到期之外，软路由也不工作了，插上电cpu风扇也不转了\n先是把配件拆了重装了一遍，没反应；然后联系客服尝试了放电，短接开关，依旧不行。\n这玩意就这样崩了，也是很神奇，到现在不清楚是什么原因。\n临时方案 软路由没有，设备还是要上网的，先后尝试了两个方案\nDocker解决需求 v*r是有很多docker镜像的，直接使用docker运行一个，再把所有设备使用socks5或http代理来实现上网\n这个方案缺点很多，配置文件还得自己修改，手机还无法实现原生的http代理，各种折腾，用了几个小时，准备换个方案\n群辉虚拟Openwrt 准备一个Op系统镜像，先在群辉安装Virtual Machine Manager，然后前往映像，新增一个硬盘镜像，选择op镜像创建；前往网络，依据需求创建端口，由于我是做旁路由所以只需要一个LAN就行了\n然后就可以创建了，记得使用导入，选择op所在的硬盘映像，其他随意。\n启动之后vi etc/config/network修改默认ip，重启之后就可以进去了\n这个方案算是解决了燃眉之急，但是太占系统资源，cpu上还有很多docker容器的负担，好处的话就是体验还算完整\n最后 咸鱼捡了个新的，希望这个能撑久点吧\n","permalink":"https://www.kutake.top/archives/%E8%AE%B0%E5%BD%95-%E8%BD%AF%E8%B7%AF%E7%94%B1%E4%BB%8A%E5%A4%A9%E7%BF%BB%E8%BD%A6%E4%BA%86/","summary":"\u003c!-- toc --\u003e\n\u003ch2 id=\"起不动的主板\"\u003e起不动的主板\u003c/h2\u003e\n\u003cp\u003e这玩意是去年十一月淘宝入手的，到手上差不多半年，中间开过穿透，开过docker，还用了AD guard和smart DNS，替代我之前N1的软路由工作，运行的也很稳，J1900的cpu占用都很少上40%，直到现在......\u003c/p\u003e","title":"记录-软路由今天翻车了"},{"content":"介绍 Mediawiki是非常著名的开源wiki程序，比起之前的Dokuwiki功能要更完善，也更专业，使用PHP+SQL开发，适合用于构建百科、知识库、在线文档、个人笔记之类的应用。它的优点如下\n美观的界面和可视化编辑器 拥有丰富的官方插件支持 官方文档非常完善，对中文支持也很友好 部署 我依旧是使用docker部署的，手动部署环境本次不介绍~~（不会）~~\nDocker操作 官方文档：mediawiki (docker.com)\n首先进行docker pull 拉取镜像：\ndocker pull mediawiki 直接运行（数据持久化将在后面进行）\ndocker run --name mediawiki -p 8080:80 -d mediawiki 打开http://ip:8080就可以打开初始界面，开始对账户、数据库、权限、站点链接等等设置完毕后，会自动下载LocalSettings.php，注意！这个时候不要直接进入wiki站点，将它上传至容器或云服务内，路径为/var/www/html/LocalSettings.php\n使用docker cp命令将整个html持久化保存\ndocker cp mediawiki:/var/www/html /path_to_save_html 删除容器后，以新的方式启动容器并挂载html目录\ndocker rm -f mediawiki docker run --name mediawiki -p 8080:80 -v /path_to_save_html:/var/www/html -d mediawiki 刷新后即可\n常用设置 站点名和url等 #LocalSettings.php #站点名 $wgSitename = \u0026#34;站点名\u0026#34;; #站点url $wgServer = \u0026#34;http://站点url\u0026#34;; #左上角logo $wgLogos = [ \u0026#39;1x\u0026#39; =\u0026gt; \u0026#34;$wgResourceBasePath/resources/assets/wiki.png\u0026#34; ]; url去index.php #LocalSettings.php $wgScriptPath = \u0026#34;\u0026#34;; $wgArticlePath = \u0026#34;/wiki/$1\u0026#34;; $wgUsePathInfo = true; 原本是只有$wgScriptPath = \u0026quot;\u0026quot;;，刷新后链接从http://your_wiki.com/index.php变成http://your_wiki.com/wiki\n添加插件 插件的设置也是在LocalSettings.php，插件文件储存位置在/html/extensions/\n注意！有的插件需要额外设置，请查看该插件的官方文档进行安装\n大多数的安装：\nwfLoadExtension( \u0026#39;插件名\u0026#39; ); 即可\n例如安装Ehco，一个提供通知系统的插件\n首先下载Echo包，放入/html/extensions/文件夹，在LocalSettings.php中添加wfLoadExtension( 'Echo' );\n注意！Echo默认版本需要Mediawiki core 1.36+版本，不满足版本条件需要下载其他Echo安装包\nEcho还需要运行更新脚本：$ php update.php\n最后前往特殊页面：版本页面可以看到是否安装成功\n设置STMP邮箱 $wgEnableEmail = true; $wgEnableUserEmail = true; $wgEmergencyContact = \u0026#34;123456@qq.com\u0026#34;; $wgPasswordSender = \u0026#34;123456@qq.com\u0026#34;; $wgSMTP = array( \u0026#39;host\u0026#39; =\u0026gt; \u0026#34;ssl://smtp.qq.com\u0026#34;, // qq 企业邮箱要求加密连接 \u0026#39;IDHost\u0026#39; =\u0026gt; \u0026#34;exmail.qq.com\u0026#34;, \u0026#39;port\u0026#39; =\u0026gt; 465, \u0026#39;auth\u0026#39; =\u0026gt; true, // 需要登录 \u0026#39;username\u0026#39; =\u0026gt; \u0026#34;123456@qq.com\u0026#34;, // 提供 SMTP 服务的邮箱账号 \u0026#39;password\u0026#39; =\u0026gt; \u0026#34;password\u0026#34; // SMTP 认证的密码. ); Nginx之类的设置就不赘述\n","permalink":"https://www.kutake.top/archives/mediawiki-%E6%9B%B4%E4%B8%93%E4%B8%9A%E7%9A%84wiki%E7%BD%91%E7%AB%99%E7%A8%8B%E5%BA%8F/","summary":"\u003ch2 id=\"介绍\"\u003e介绍\u003c/h2\u003e\n\u003c!-- toc --\u003e\n\u003cp\u003e\u003ca href=\"https://www.mediawiki.org/wiki/MediaWiki\"\u003eMediawiki\u003c/a\u003e是非常著名的开源wiki程序，比起之前的Dokuwiki功能要更完善，也更专业，使用PHP+SQL开发，适合用于构建百科、知识库、在线文档、个人笔记之类的应用。它的优点如下\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e美观的界面和可视化编辑器\u003c/li\u003e\n\u003cli\u003e拥有丰富的官方插件支持\u003c/li\u003e\n\u003cli\u003e官方文档非常完善，对中文支持也很友好\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"部署\"\u003e部署\u003c/h2\u003e\n\u003cp\u003e我依旧是使用docker部署的，手动部署环境本次不介绍~~（不会）~~\u003c/p\u003e","title":"Mediawiki - 更专业的wiki网站程序"},{"content":"起因\u0026amp;需求 挺早之前有个整跑团的群友需要个地方存剧本和设定，也就是个小wiki，刚好最近用开源框架撘站也变得熟练了，于是到处搜索了一下，决定使用Dokuwiki来完成\n为什么使用Dokuwiki呢，它有以下优点\n轻量，简单，甚至不需要数据库 丰富插件和主题支持，虽然大多更新频率非常慢 Wiki站该有的都有，支持多人编辑 但也有缺点\n编辑器是真的丑（ 对中文的支持不友好 页面管理不便 总结来说Dokuwiki还是适合个人使用，当然要是站长会PHP就更好了，非常方便进行二次开发\n部署 Dokuwiki有docker镜像，直接部署能方便很多，docker在各种平台上的安装方式就不赘述\n镜像地址：bitnami/dokuwiki (docker.com)\n首先拉取镜像\ndocker pull bitnami/dokuwiki 然后建立建立一个新的网络环境，最开始直接默认桥接，但是一直无法启动，然后按照档案建立network之后就行了\ndocker network create dokuwiki-network 然后就可以直接运行了\ndocker run -d --name dokuwiki \\ -p 8080:8080 -p 8443:8443 \\ --env ALLOW_EMPTY_PASSWORD=yes \\ --network dokuwiki-network \\ --volume /path/to/dokuwiki-persistence:/bitnami/dokuwiki \\ bitnami/dokuwiki:latest 打开http://ip:8080就可以看到界面了\n默认账号user，默认密码bitnami1\n之后还要配置Nginx之类的，就不再赘述\n配置\u0026amp;优化 主题 Bootstrap3 Template是我翻来覆去能找到的最好看的主题了，连带着编辑器都没那么丑了~~（但还是丑）~~\n插件 ACL Manager：进行访问控制权限管理的插件 Add New Page：一个新增页面的插件，让新建页面变得简单 Indexmenu Plugin：一个侧面导航栏插件 Markdown Page Plugin：提供Markdowm支持 配置的修改 下载和启用插件之后还没有结束，主题下载完成后需要在配置设置里面进行修改模板项保存\n增加侧边导航和新建页面按钮需要新建文件，位于\\path_to\\dokuwiki\\data\\pages\\sidebar.txt，内容为\n===== 导航目录 ===== {{indexmenu\u0026gt;..|navbar}} ===== 添加新页面 ===== {{NEWPAGE}} 保存即可\n想避免游客进行页面修改操作，可以在ACL控制器里面对ALL账户进行修改，仅勾选读，保存即可\n","permalink":"https://www.kutake.top/archives/dokuwiki%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BAwiki/","summary":"\u003ch2 id=\"起因需求\"\u003e起因\u0026amp;需求\u003c/h2\u003e\n\u003c!-- toc --\u003e\n\u003cp\u003e挺早之前有个整跑团的群友需要个地方存剧本和设定，也就是个小wiki，刚好最近用开源框架撘站也变得熟练了，于是到处搜索了一下，决定使用Dokuwiki来完成\u003c/p\u003e","title":"Dokuwiki搭建个人wiki"},{"content":"需求 我有几个小网站需要穿透出去，为了方便使用和安全，选择了直接使用Nginx实现反向代理和ssl的部署，另外还有一些简单的Nginx防护\n反代实现 加密访问（仅本人访问） SSL部署 Nginx防护恶意请求 反代实现 server { listen 80; server_name www.your_url.com; location / { proxy_pass http://local456:port/; } } 这样就在80端口实现了对server_name 的反代操作，server_name填写外部请求的url，proxy_pass填写内部你需要反代出去的url\n当你对www.your_url.com进行访问的时候，nginx就会转发到http://local456:port/上，就实现了最简单的反向代理\n注意需要在系统内部防火墙内开放端口\n加密访问 有的站点不需要给别人访问，仅提供给自己，于是就有了加密的需求\n这个方法是在百度上翻到的\nserver { listen 80; server_name www.your_url.com; auth_basic \u0026#34;Authorization\u0026#34;; auth_basic_user_file /www/server/pass/www.your_url.com.pass; location / { proxy_pass http://local456:port/; } } .pass文件可以使用htpasswd,openssl进行生成，添加完后重新启动Nginx就可以了\n当输入url进行访问时，会弹出小窗口提示需要输入账号密码进行验证，输入正确的账号密码即可访问成功\nSSL部署 首先需要进行证书的申请，我选择从https://freessl.cn/进行申请\n输入邮箱，选择RSA和DNS验证，进行一键申请，接着输入你的域名解析供应商的APIkey，它会自动进行解析参数的添加并在验证成功后删除\n之后在KeyManager上的证书管理界面，点击证书的详情，然后再右下角点击查看PEM，对证书和私钥分别进行复制粘贴保存，分别命名为fullchain.pem和privkey.pem，上传至云\n然后开始对Nginx进行修改\nserver { listen 80; listen 443 ssl; server_name www.your_url.com; auth_basic \u0026#34;Authorization\u0026#34;; auth_basic_user_file /www/server/pass/www.your_url.com.pass; location / { proxy_pass http://local456:port/; } ssl_certificate /path_to_pem/fullchain.pem; ssl_certificate_key /path_to_pem/privkey.pem; if ($server_port !~ 443){ rewrite ^(/.*)$ https://$host$1 permanent; } } 首先开启对443口的监听，分别在ssl_certificate和ssl_certificate_key上填写相应文件的路径，最后写个重定向，当请求非443端口时，强制重定向到443的https链接上\n最后保存重启，验证是否成功\n简单的Nginx防护 拦截特定UA if ($http_user_agent ~ \u0026#34;Apache\\-HttpClient\u0026#34;) { return 403; } 或者空UA\nif ($http_user_agent = \u0026#39;\u0026#39;) { return 403; } 拦截SQL注入 if ($query_string ~* (\\$|\u0026#39;|--|[+|(%20)]union[+|(%20)]|[+|(%20)]insert[+|(%20)]|[+|(%20)]drop[+|(%20)]|[+|(%20)]truncate[+|(%20)]|[+|(%20)]update[+|(%20)]|[+|(%20)]from[+|(%20)]|[+|(%20)]grant[+|(%20)]|[+|(%20)]exec[+|(%20)]|[+|(%20)]where[+|(%20)]|[+|(%20)]select[+|(%20)]|[+|(%20)]and[+|(%20)]|[+|(%20)]or[+|(%20)]|[+|(%20)]count[+|(%20)]|[+|(%20)]exec[+|(%20)]|[+|(%20)]chr[+|(%20)]|[+|(%20)]mid[+|(%20)]|[+|(%20)]like[+|(%20)]|[+|(%20)]iframe[+|(%20)]|[\\\u0026lt;|%3c]script[\\\u0026gt;|%3e]|javascript|alert|webscan|dbappsecurity|style|confirm\\(|innerhtml|innertext)(.*)$) { return 555; } 来源：如何配置Nginx防止SQL注入、XSS攻击？ - 幸运摩登 - 博客园 (cnblogs.com)\n防盗链 location ~ .*\\.(jpg|jpeg|gif|png|js|css)$ { expires 30d; valid_referers your_url.com; if ($invalid_referer){ return 404; } } 当referer不为your_url.com的时候，资源请求直接返回404，防止外部访问\n还有防CC，防外国IP访问，等等就不赘述\n","permalink":"https://www.kutake.top/archives/nginx%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E5%92%8Cssl%E9%83%A8%E7%BD%B2/","summary":"\u003ch2 id=\"需求\"\u003e需求\u003c/h2\u003e\n\u003c!-- toc --\u003e\n\u003cp\u003e我有几个小网站需要穿透出去，为了方便使用和安全，选择了直接使用Nginx实现反向代理和ssl的部署，另外还有一些简单的Nginx防护\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e反代实现\u003c/li\u003e\n\u003cli\u003e加密访问（仅本人访问）\u003c/li\u003e\n\u003cli\u003eSSL部署\u003c/li\u003e\n\u003cli\u003eNginx防护恶意请求\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"反代实现\"\u003e反代实现\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eserver\n{\n    listen 80;\n    server_name www.your_url.com;\n    location / {\n        proxy_pass http://local456:port/;\n        }\n}\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e这样就在80端口实现了对\u003ccode\u003eserver_name \u003c/code\u003e的反代操作，\u003ccode\u003eserver_name\u003c/code\u003e填写外部请求的url，\u003ccode\u003eproxy_pass\u003c/code\u003e填写内部你需要反代出去的url\u003c/p\u003e","title":"Nginx反向代理和SSL部署"},{"content":"原因 申请不到公网ip，短期内也不考虑换宽带 就算申请到了也封80/443，我这个菜鸡无法解决这种高难度问题 将Nas上的wordpress穿透到云上的方案折腾而且不好用，现在考虑直接部署至云上 过程 看别人推荐，用了腾讯的香港轻量云，体验非常不错\n但wordpress迁移过程十分难受，大大小小的毛病都碰上了，这里就不细说~~（说出来丢人）~~\n还顺利上了https，就很舒服\n最后 又水了一篇\n","permalink":"https://www.kutake.top/archives/%E5%8D%9A%E5%AE%A2%E8%BF%81%E7%A7%BB%E8%87%B3%E9%A6%99%E6%B8%AF%E8%85%BE%E8%AE%AF%E8%BD%BB%E9%87%8F%E5%BA%94%E7%94%A8%E4%BA%91%E7%9A%84%E8%AE%B0%E5%BD%95/","summary":"\u003ch2 id=\"原因\"\u003e原因\u003c/h2\u003e\n\u003c!-- toc --\u003e\n\u003col\u003e\n\u003cli\u003e申请不到公网ip，短期内也不考虑换宽带\u003c/li\u003e\n\u003cli\u003e就算申请到了也封80/443，我这个菜鸡无法解决这种高难度问题\u003c/li\u003e\n\u003cli\u003e将Nas上的wordpress穿透到云上的方案折腾而且不好用，现在考虑直接部署至云上\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"过程\"\u003e过程\u003c/h2\u003e\n\u003cp\u003e看别人推荐，用了腾讯的香港轻量云，体验非常不错\u003c/p\u003e","title":"博客迁移至香港腾讯轻量应用云的记录"},{"content":"一般的多条件判断 if 判断1 and 判断2 and 判断3: run() 当语句非常长的时候，就显得难懂且不美观\nall()方法多条件判断 if all([条件1, 条件2, 条件3]): run() all()传入一个可迭代对象，当所有条件为True时，返回True\n","permalink":"https://www.kutake.top/archives/all-%E4%B8%80%E4%B8%AA%E4%BE%BF%E5%88%A9%E7%9A%84%E5%A4%9A%E6%9D%A1%E4%BB%B6%E5%88%A4%E6%96%AD%E6%96%B9%E6%B3%95/","summary":"\u003ch2 id=\"一般的多条件判断\"\u003e一般的多条件判断\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eif 判断1 and 判断2 and 判断3:\n    run()\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e当语句非常长的时候，就显得难懂且不美观\u003c/p\u003e\n\u003ch2 id=\"all方法多条件判断\"\u003eall()方法多条件判断\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eif all([条件1, 条件2, 条件3]):\n    run()\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eall()传入一个可迭代对象，当所有条件为True时，返回True\u003c/p\u003e","title":"all() 一个便利的多条件判断方法"},{"content":"这个bot写了有段小时间了，一直没有做个总结之类的，正好最近使用了saya, 代码算是可以看了点，于是就准备做个小总结。\nBot是什么 输入 - 处理 - 结果\n大致上就是如此，发送一句话，bot根据这句话进行处理，然后得到预期结果。很明显，bot就是IO密集型程序，使用异步的方式是最便捷也是最高效的，这里就要说下开发框架\nMirai：一个在全平台下运行，提供 QQ Android 协议支持的高效率机器人库（Kotlin） Mirai Console Loader：模块化、轻量级且支持完全自定义的 Mirai 加载器 **Mirai-API-http：**一个插件 提供HTTP API供所有语言使用mirai Graia Application for mirai-api-http：一个设计精巧, 协议实现完备的, 基于 mirai-api-http 的即时聊天软件自动化框架 Saya：一个Graia的模块化控制器 Mirai是主要的QQ协议支持，通俗理解就是负责登录一个QQ账号，操作这个账号的所有活动，从消息接收到发送，从全体禁言到修改群设置，都可以进行操作。它负责的就是与QQ协议进行交互（当然也可以直接写bot，但我不会kotlin）。\n启动一个Mirai服务是个挺麻烦的事情，特别是对于我这种没有接触过java的小白，所以就有**Mirai Console Loader**这种Mirai启动器，一键启动一个完整的Mirai服务，不用关心这里面发生了什么。\nMirai是Kotlin编写的，而我只会点python，这要怎么写呢......。这里就要说到**Mirai-API-http**了，它能将Mirai的服务以http-api的形式进行扩展，只要能进行http交互，那么就能使用任意一种语言，通过它操作Mirai。\n有了协议，有了交互方式，就到了使用python进行具体的逻辑编写的时候了，于是就用上了**Graia。它作为一个框架，规范了与Mirai-API-http**交互的行为，提供了众多的工具给开发者，让开发者的操作便利了许多，不用去烦恼具体的交互逻辑而去专注于bot业务编写上。\n当需要的功能多了，管理这么多的功能又成了一件麻烦事，于是加入了**Saya**，能把具体的功能实现包装成一个个saya模块，不仅让文件结构和功能逻辑更加清晰，还让分享功能变得便捷。\n这些加在一起，便构成了CUAVbot的整个框架\nCUAVbot地址：https://github.com/kuttakke/CUAVbot\n功能和实现 写bot这件事让我收获最多的一件事，就是对python异步的理解加深。bot有可能在任何地方遇到io阻塞而导致不必要的等待，文件读写、网络请求等待操作若不进行异步操作，很大可能会使bot遇上长时间的IO阻塞而停滞，所以异步是编写bot的最要注意的点。用异步去进行各种功能的支撑，能做到快速响应，不必为了等待其他IO的回应而阻塞整个程序。\n另一件事就是函数输入参数的规范，这里面涉及到Typing这个python模块，既帮助了后续代码维护的便捷，也能让编译器更好的理解参数类型从而进行提示。\n最后一件以前经常被忽略的事，就是代码优化。我这个bot是使用docker挂载在Nas上的，cpu只能说是够用，如果写功能的时候不注意对各种资源消耗进行优化，那么反应慢将会是一个非常令人苦恼的问题。具体到这个bot，我不能说它写的足够完美~~（甚至挺烂的）~~，但至少它是我第一次注意着优化而编写的程序，第一次主动考虑这节省资源而编写的程序。\n其余的地方其实并不复杂，看懂这个bot的代码也并不是非常难，就不再赘述。\n总结 这个bot技术含量没多少，但对我的意义还算不小，它让我对开源社区这一概念有了理解，对api、异步、git、docker、类、python、网络、编程思维都有了不一样的理解，我很庆幸我能用浅薄的编程知识把写bot给坚持了下去，并持续学习、收获。\n","permalink":"https://www.kutake.top/archives/cuavbot-%E4%B8%80%E4%B8%AApython%E6%9E%84%E6%88%90%E7%9A%84qq%E6%9C%BA%E5%99%A8%E4%BA%BA/","summary":"\u003cp\u003e这个bot写了有段小时间了，一直没有做个总结之类的，正好最近使用了\u003ca href=\"https://github.com/GraiaProject/Saya\"\u003esaya\u003c/a\u003e, \u003cdel\u003e代码算是可以看了点\u003c/del\u003e，于是就准备做个小总结。\u003c/p\u003e\n\u003c!-- toc --\u003e\n\u003ch2 id=\"bot是什么\"\u003eBot是什么\u003c/h2\u003e\n\u003cp\u003e\u003cem\u003e输入 - 处理 - 结果\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e大致上就是如此，发送一句话，bot根据这句话进行处理，然后得到预期结果。很明显，bot就是IO密集型程序，使用异步的方式是最便捷也是最高效的，这里就要说下开发框架\u003c/p\u003e","title":"CUAVbot--一个python构成的qq机器人"},{"content":"永久换源 pip config set global.index-url https:// 临时换源 pip install --index-url https:// module pip install module -i https:// 常用源 清华大学 https://pypi.tuna.tsinghua.edu.cn/simple/ 阿里云 http://mirrors.aliyun.com/pypi/simple/ 中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/ 豆瓣(douban) http://pypi.douban.com/simple/ 中国科学技术大学 http://pypi.mirrors.ustc.edu.cn/simple/ 其中中国科技大学还挺好\n","permalink":"https://www.kutake.top/archives/pip-set%E6%8D%A2%E6%BA%90/","summary":"\u003ch2 id=\"永久换源\"\u003e永久换源\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003epip config set global.index-url https://\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"临时换源\"\u003e临时换源\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003epip install --index-url https:// module\npip install module -i https://\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"常用源\"\u003e常用源\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e清华大学 \u003ca href=\"https://pypi.tuna.tsinghua.edu.cn/simple/\"\u003ehttps://pypi.tuna.tsinghua.edu.cn/simple/\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e阿里云 \u003ca href=\"http://mirrors.aliyun.com/pypi/simple/\"\u003ehttp://mirrors.aliyun.com/pypi/simple/\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e中国科技大学 \u003ca href=\"https://pypi.mirrors.ustc.edu.cn/simple/\"\u003ehttps://pypi.mirrors.ustc.edu.cn/simple/\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e豆瓣(douban) \u003ca href=\"http://pypi.douban.com/simple/\"\u003ehttp://pypi.douban.com/simple/\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e中国科学技术大学 \u003ca href=\"http://pypi.mirrors.ustc.edu.cn/simple/\"\u003ehttp://pypi.mirrors.ustc.edu.cn/simple/\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e其中中国科技大学还挺好\u003c/p\u003e","title":"pip 换源与临时换源"},{"content":"一个网页不需要登录，就可以随意访问网站的所有具体信息的话，那就好比是一个敞开了大门的网站，摆好酒桌好吃好喝招待爬虫脚本们，数据提取的成本非常低。于是比较常规的网站，都会使用账号登录来提高网站对于每个想要查看网站数据的门槛，所以模拟登陆的问题就是必修课\n我目前接触到的模拟登录方式就两种，一个是解析登录接口，查看具体提交的表单，密码的加密方式，用到的一般是request这样的库。优点是运行快，缺点是开发成本大；第二个就是selenium这种自动化测试工具模拟一个正常用户的具体登录操作：打开，鼠标移动、点击，输入账号密码，最后点击登录，这种方式的有点是省事，不需要慢慢查看具体提交的表单信息，但在缺点上除了运行慢之外，一般还有一个不小的麻烦，就是形式复杂的验证码。\n说实话第一种实现方式，过于复杂，基本没有接触（对于大流量网站），于是主要还是说明第二种实现方式：selenium库和验证码破解\nselenium的模拟操作上，无非就是webdriver.find_element_by_XXX(\u0026quot;\u0026quot;)找到某个input元素，用.send_keys(\u0026quot;user or password\u0026quot;)输入后，使用.click()来点击登录，这上面一般没有问题。\n但是最大的麻烦一般就会在.click()操作后出现，那就是验证码...\n注意\n图片缺失：2020/08/weibo_V.png 原因：uploads 归档中不存在此路径的文件 原链接：https://kutake.site/wp-content/uploads/2020/08/weibo_V.png\n比如微博的验证码\n当你打开F12，找到验证码的请求url时，会发现一个问题，那就是每次请求返回的图片结果都不一样，所以我们使用selenium的截图功能来实现\n具体流程：\n打开页面 输入账号密码 点击登录弹出验证码 webdriver.save_screenshot截全图 find定位验证码元素 .location获取验证码元素坐标 依据坐标使用.crop剪裁 得到的图片使用云打码平台等识别验证码 输入，点击登录 验证码处理 如果使用靠谱的打码平台，那可能不需要使用这种手段对得到的验证码图片进行处理，但如果是使用百度阿里等图片识别平台，那就需要进行转灰和二值化验证码操作\n注意\n图片缺失：2020/08/convert_ver.png 原因：uploads 归档中不存在此路径的文件 原链接：https://kutake.site/wp-content/uploads/2020/08/convert_ver.png\n普通验证码转灰\n注意\n图片缺失：2020/08/convert.jpg 原因：uploads 归档中不存在此路径的文件 原链接：https://kutake.site/wp-content/uploads/2020/08/convert.jpg\n转灰\n注意\n图片缺失：2020/08/pcode.jpg 原因：uploads 归档中不存在此路径的文件 原链接：https://kutake.site/wp-content/uploads/2020/08/pcode.jpg\n复杂背景验证码\n就像这样\n可以方便模型更好识别出文字，字母或者数字\n代码 #weibo_login_sele.py from selenium import webdriver import time from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait import requests from selenium.common.exceptions import NoSuchElementException from PIL import Image import matplotlib.pyplot as plt from aip import AipOcr def main(): test_webdriver = webdriver.Chrome() test_webdriver.maximize_window() test_webdriver.get(\u0026#34;https://weibo.com/login.php\u0026#34;) user=\u0026#34;user\u0026#34; passwd = \u0026#34;pass\u0026#34; element_user = WebDriverWait(test_webdriver,5,0.5).until(EC.presence_of_element_located((By.XPATH,\u0026#34;//input[@id=\u0026#39;loginname\u0026#39;]\u0026#34;))) time.sleep(0.5) element_user.send_keys(user) element_pw = WebDriverWait(test_webdriver,5,0.5).until(EC.presence_of_element_located((By.XPATH,\u0026#34;//input[@name=\u0026#39;password\u0026#39;]\u0026#34;))) time.sleep(0.5) element_pw.click() time.sleep(0.5) element_pw.send_keys(passwd) #test_webdriver.find_element_by_xpath(\u0026#34;//a[@node-type=\u0026#39;submitBtn\u0026#39;]\u0026#34;).click() time.sleep(2.5) e = test_webdriver.find_element_by_xpath(\u0026#34;//img[@node-type=\u0026#39;verifycode_image\u0026#39;]\u0026#34;) time.sleep(0.5) img = \u0026#34;weibo.png\u0026#34; element_verify = e time.sleep(1) test_webdriver.save_screenshot(img) print(element_verify.location) print(element_verify.size) left = element_verify.location[\u0026#39;x\u0026#39;] top = element_verify.location[\u0026#39;y\u0026#39;] right = element_verify.location[\u0026#39;x\u0026#39;] + element_verify.size[\u0026#39;width\u0026#39;] bottom = element_verify.location[\u0026#39;y\u0026#39;] + element_verify.size[\u0026#39;height\u0026#39;] im = Image.open(img) verifycode_image = im.crop((left,top,right,bottom)) ver = verifycode_image.convert(\u0026#34;L\u0026#34;) ver.save(\u0026#34;convert_ver.png\u0026#34;) get_api() #test_webdriver.quit() #saveImgLight() def saveImgLight(): im = Image.open(\u0026#34;code.jpg\u0026#34;) #plt.imshow(im) #plt.show() im1 = im.crop((0,0,344,343)) im2 = im.crop((0,343,115,382)) im2.save(\u0026#34;word.jpg\u0026#34;) im1.save(\u0026#34;pcode.jpg\u0026#34;) #转灰 im3 = im1.convert(\u0026#34;L\u0026#34;) im3.save(\u0026#34;convert.jpg\u0026#34;) #二值化 #im4 = im1.point(lambda p:p * 4) #im4.save(\u0026#34;point.jpg\u0026#34;) def get_api(): APP_ID = \u0026#39;ID\u0026#39; API_KEY = \u0026#39;KEY\u0026#39; SECRET_KEY = \u0026#39;S_KEY\u0026#39; client = AipOcr(APP_ID, API_KEY, SECRET_KEY) options = {} with open(\u0026#34;convert_ver.png\u0026#34;,\u0026#34;rb\u0026#34;) as f1: p1=f1.read() #with open(\u0026#34;word.jpg\u0026#34;,\u0026#34;rb\u0026#34;) as f2: #p2=f2.read() options[\u0026#34;language_type\u0026#34;] = \u0026#34;ENG\u0026#34; lo1 = client.accurate(p1,options) #lo2 = client.accurate(p2,options) print(lo1) #print(lo2) main() ","permalink":"https://www.kutake.top/archives/%E6%A8%A1%E6%8B%9F%E7%99%BB%E5%BD%95%E7%9B%B8%E5%85%B3/","summary":"\u003cp\u003e一个网页不需要登录，就可以随意访问网站的所有具体信息的话，那就好比是一个敞开了大门的网站，摆好酒桌好吃好喝招待爬虫脚本们，数据提取的成本非常低。于是比较常规的网站，都会使用账号登录来提高网站对于每个想要查看网站数据的门槛，所以模拟登陆的问题就是必修课\u003c/p\u003e","title":"模拟登录相关"},{"content":"确定需求 DLsite是全球最大的同人贩卖网站，里面包括漫画、音声、动画、游戏等等来自不同国家不同地区不同语言以及不同形式的二次元创作作品，这个项目暂定是爬取home下的所有漫画信息作为统计数据，链接为：\nhttps://www.dlsite.com/home/works/comic\n上万数据量的爬取，首选的框架肯定是异步的scrapy，速度非常快，非常适合庞大数据量的抓取\n使用scrapy startproject test_rj和scrapy genspider rj_test来得到一个项目，设置好setting和start_urls之后就可以正式开始了\n首先是定位，前期尝试中，无论是bs4定位还是xpath定位都无效，才发现第一次get返回的html与实际F12中标签元素有差别，于是这里不上F12控制台，毕竟所有信息都在第一次get请求之后就返回了，无需解析js或者json拿取数据，而且这个站点在反爬方面还是比较松的，实践中可以不需要携带User-Again，甚至默认带上Scrapy标签也行，几秒钟内请求上百次也不会封锁ip，但为了保险起见，上万数据量的爬取必定需要伪装手段为本机做点保护手段\n大致思路 代码 Scrapy项目结构：\nE:. │ .gitignore │ scrapy.cfg │ ├─log │ scrapy.log │ ├─python_uav │ .gitignore │ └─test_rj │ items.py │ main.py │ middlewares.py │ pipelines.py │ settings.py │ __init__.py │ ├─spiders │ │ rj_test.py │ │ __init__.py │ │ │ └─__pycache__ │ rj_test.cpython-37.pyc │ __init__.cpython-37.pyc │ └─__pycache__ items.cpython-37.pyc middlewares.cpython-37.pyc pipelines.cpython-37.pyc settings.cpython-37.pyc __init__.cpython-37.pyc 首先在items.py中定义爬取的数据：\n# Define here the models for your scraped items # # See documentation in: # https://docs.scrapy.org/en/latest/topics/items.html import scrapy class TestRjItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() title = scrapy.Field() circle = scrapy.Field() ID = scrapy.Field() data = scrapy.Field() sname = scrapy.Field() keyword_creater = scrapy.Field() GEN = scrapy.Field() type_ =scrapy.Field() category = scrapy.Field() other = scrapy.Field() pages = scrapy.Field() genre = scrapy.Field() file_ = scrapy.Field() event = scrapy.Field() 然后在spiders.rj_test.py中：\nimport scrapy from test_rj.items import TestRjItem import logging class RjTestSpider(scrapy.Spider): name = \u0026#39;rj_test\u0026#39; allowed_domains = [\u0026#39;dlsite.com\u0026#39;] start_urls = [\u0026#39;https://www.dlsite.com/home/works/comic\u0026#39;] def parse(self, response): logging.debug(response.request.headers) url_list = response.xpath(\u0026#39;//a[@class=\u0026#34;work_thumb_inner\u0026#34;]/@href\u0026#39;) for i in url_list: rj_url = i.extract() yield scrapy.Request(url=rj_url,callback=self.get_rj_info) next_page = response.xpath(\u0026#39;//td[@class=\u0026#34;page_no\u0026#34;]/ul/li[8]/a/@href\u0026#39;).extract_first() if next_page: yield scrapy.Request(url=next_page,callback=self.parse) def get_rj_info(self,response): logging.debug(response.request.headers) def span_info(i): info_str = i.xpath(\u0026#39;./td//span/text()\u0026#39;).extract() len_ = len(info_str) if len_ == 1: return info_str[0] elif len_ == 0: return \u0026#39;无\u0026#39; else: return info_str def div_a_info(i): info_str = i.xpath(\u0026#39;./td/div/a/text()\u0026#39;).extract() len_ = len(info_str) if len_ == 1: return info_str[0] elif len_ == 0: return \u0026#39;无\u0026#39; else: return info_str def a_info(i): info_str = i.xpath(\u0026#39;./td/a/text()\u0026#39;).extract() len_ = len(info_str) if len_ == 1: return info_str[0] elif len_ == 0: return \u0026#39;无\u0026#39; else: return info_str def td_info(i): info_str = i.xpath(\u0026#39;./td/text()\u0026#39;).extract() len_ = len(info_str) if len_ == 1: return info_str[0] elif len_ == 0: return \u0026#39;无\u0026#39; else: return info_str def div_info(i): info_str = i.xpath(\u0026#39;./td/div/text()\u0026#39;).extract() len_ = len(info_str) if len_ == 1: return info_str[0] elif len_ == 0: return \u0026#39;无\u0026#39; else: return info_str def span_a_info(i): info_str = i.xpath(\u0026#39;./td/span/a/text()\u0026#39;).extract() len_ = len(info_str) if len_ == 1: return info_str[0] elif len_ == 0: return \u0026#39;无\u0026#39; else: return info_str rj = TestRjItem() title = response.xpath(\u0026#39;//h1[@id=\u0026#34;work_name\u0026#34;]/a/text()\u0026#39;).extract_first() rj_id = response.xpath(\u0026#39;//div[@id=\u0026#34;work_left\u0026#34;]/div/@data-product-id\u0026#39;).extract_first() maker = response.xpath(\u0026#39;//span[@itemprop=\u0026#34;brand\u0026#34;]/a/text()\u0026#39;).extract_first() rj[\u0026#34;title\u0026#34;] = title rj[\u0026#34;circle\u0026#34;] = maker rj[\u0026#34;ID\u0026#34;] = rj_id info = response.xpath(\u0026#39;//table[@id=\u0026#34;work_outline\u0026#34;]/tr\u0026#39;) for i in info: info_name = i.xpath(\u0026#39;./th/text()\u0026#39;).extract_first() if info_name == \u0026#39;贩卖日\u0026#39;: rj[\u0026#34;data\u0026#34;] = a_info(i) elif info_name == \u0026#39;系列名\u0026#39;: rj[\u0026#34;sname\u0026#34;] = a_info(i) elif info_name == \u0026#39;作者\u0026#39;: rj[\u0026#34;keyword_creater\u0026#34;] = a_info(i) elif info_name == \u0026#39;年龄指定\u0026#39;: rj[\u0026#34;GEN\u0026#34;] = span_info(i) elif info_name == \u0026#39;作品类型\u0026#39;: rj[\u0026#34;category\u0026#34;] = span_info(i) elif info_name == \u0026#39;文件形式\u0026#39;: rj[\u0026#34;type_\u0026#34;] = span_info(i) elif info_name == \u0026#39;其他\u0026#39;: rj[\u0026#34;other\u0026#34;] = span_info(i) elif info_name == \u0026#39;页数\u0026#39;: rj[\u0026#34;pages\u0026#34;] = td_info(i) elif info_name == \u0026#39;分类\u0026#39;: rj[\u0026#34;genre\u0026#34;] = div_a_info(i) elif info_name == \u0026#39;文件容量\u0026#39;: rj[\u0026#34;file_\u0026#34;] = div_info(i) elif info_name == \u0026#39;活动\u0026#39;: rj[\u0026#34;event\u0026#34;] = span_a_info(i) yield rj 之后是将获得的数据进行持久化储存的pipelines.py：\n# Define your item pipelines here # # Don\u0026#39;t forget to add your pipeline to the ITEM_PIPELINES setting # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html # useful for handling different item types with a single interface from itemadapter import ItemAdapter import MySQLdb import logging class TestRjPipeline: def __init__(self): host = (\u0026#39;localhost\u0026#39;) user = (\u0026#39;root\u0026#39;) pass_ = (\u0026#39;123456\u0026#39;) db = (\u0026#39;sqlforpy\u0026#39;) self.connect = MySQLdb.connect(host,user,pass_,db,charset=\u0026#39;utf8mb4\u0026#39;) self.cursor = self.connect.cursor() def process_item(self, item, spider): def get_data(items_): data = dict(items_) rj={\u0026#39;ID\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;title\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;circle\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;keyword_creater\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;data\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;sname\u0026#39;:\u0026#39;无\u0026#39;\\ ,\u0026#39;GEN\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;type_\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;category\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;other\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;pages\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;genre\u0026#39;:\u0026#39;无\u0026#39;,\\ \u0026#39;file_\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;event\u0026#39;:\u0026#39;无\u0026#39;} for k in data.items(): rj[k[0]] = k[1] return rj rj = get_data(item) self.connect.ping(True) sql = r\u0026#34;\u0026#34;\u0026#34;insert into dlsite_cg_copy1(RJ,标题,社团,作者,发售日,系列名,年龄指定,文件形式,作品类型,其他,页数,分类,文件容量,活动)VALUES(\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;)\u0026#34;\u0026#34;\u0026#34;% (rj[\u0026#39;ID\u0026#39;],rj[\u0026#39;title\u0026#39;],rj[\u0026#39;circle\u0026#39;],rj[\u0026#39;keyword_creater\u0026#39;],rj[\u0026#39;data\u0026#39;],rj[\u0026#39;sname\u0026#39;],rj[\u0026#39;GEN\u0026#39;],rj[\u0026#39;type_\u0026#39;],rj[\u0026#39;category\u0026#39;],rj[\u0026#39;other\u0026#39;],rj[\u0026#39;pages\u0026#39;],rj[\u0026#39;genre\u0026#39;],rj[\u0026#39;file_\u0026#39;],rj[\u0026#39;event\u0026#39;]) try: self.cursor.execute(sql) self.connect.commit() except: logging.debug(\u0026#39;%s写入数据库出现错误！\u0026#39;%rj[\u0026#39;ID\u0026#39;]) logging.debug(\u0026#39;内容为:\\n{}\\n\u0026#39;.format(rj)) self.connect.rollback() logging.debug(\u0026#39;此次保存操作结束\u0026#39;) def close_spider(self, spider): self.cursor.close() self.connect.close() 之后再middlewares.py中定义伪装：\n# Define here the models for your spider middleware # # See documentation in: # https://docs.scrapy.org/en/latest/topics/spider-middleware.html from scrapy import signals import random import requests import logging # useful for handling different item types with a single interface from itemadapter import is_item, ItemAdapter class use_agent_(object): def process_request(self,request,spider): user_agent_list=[ \u0026#39;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.15 Safari/537.36 Edg/85.0.564.8\u0026#39; \u0026#34;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 OPR/26.0.1656.60\u0026#34;, \u0026#34;Opera/8.0 (Windows NT 5.1; U; en)\u0026#34;, \u0026#34;Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50\u0026#34;, \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.50\u0026#34;, \u0026#34;Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0\u0026#34;, \u0026#34;Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10\u0026#34;, \u0026#34;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2\u0026#34;, \u0026#34;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36\u0026#34;, \u0026#34;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11\u0026#34;, \u0026#34;Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16\u0026#34;, \u0026#34;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36\u0026#34;, \u0026#34;Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko\u0026#34;, \u0026#34;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11\u0026#34;, \u0026#34;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER\u0026#34;, \u0026#34;Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)\u0026#34;, \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)\u0026#34;, \u0026#34;Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)\u0026#34;, \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)\u0026#34;, \u0026#34;Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 SE 2.X MetaSr 1.0\u0026#34;, \u0026#34;Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; SE 2.X MetaSr 1.0)\u0026#34;, \u0026#34;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Maxthon/4.4.3.4000 Chrome/30.0.1599.101 Safari/537.36\u0026#34;, \u0026#34;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 UBrowser/4.0.3214.0 Safari/537.36\u0026#34; ] agent = random.choice(user_agent_list) request.headers[\u0026#39;User_Agent\u0026#39;] = agent class TestRjSpiderMiddleware: # Not all methods need to be defined. If a method is not defined, # scrapy acts as if the spider middleware does not modify the # passed objects. @classmethod def from_crawler(cls, crawler): # This method is used by Scrapy to create your spiders. s = cls() crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) return s def process_spider_input(self, response, spider): # Called for each response that goes through the spider # middleware and into the spider. # Should return None or raise an exception. return None def process_spider_output(self, response, result, spider): # Called with the results returned from the Spider, after # it has processed the response. # Must return an iterable of Request, or item objects. for i in result: yield i def process_spider_exception(self, response, exception, spider): # Called when a spider or process_spider_input() method # (from other spider middleware) raises an exception. # Should return either None or an iterable of Request or item objects. pass def process_start_requests(self, start_requests, spider): # Called with the start requests of the spider, and works # similarly to the process_spider_output() method, except # that it doesn’t have a response associated. # Must return only requests (not items). for r in start_requests: yield r def spider_opened(self, spider): spider.logger.info(\u0026#39;Spider opened: %s\u0026#39; % spider.name) class TestRjDownloaderMiddleware: # Not all methods need to be defined. If a method is not defined, # scrapy acts as if the downloader middleware does not modify the # passed objects. @classmethod def from_crawler(cls, crawler): # This method is used by Scrapy to create your spiders. s = cls() crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) return s def process_request(self, request, spider): # Called for each request that goes through the downloader # middleware. # Must either: # - return None: continue processing this request # - or return a Response object # - or return a Request object # - or raise IgnoreRequest: process_exception() methods of # installed downloader middleware will be called return None def process_response(self, request, response, spider): # Called with the response returned from the downloader. # Must either; # - return a Response object # - return a Request object # - or raise IgnoreRequest return response def process_exception(self, request, exception, spider): # Called when a download handler or a process_request() # (from other downloader middleware) raises an exception. # Must either: # - return None: continue processing this exception # - return a Response object: stops process_exception() chain # - return a Request object: stops process_exception() chain pass def spider_opened(self, spider): spider.logger.info(\u0026#39;Spider opened: %s\u0026#39; % spider.name) class ippool(object): def __init__(self): self.proxy_url = \u0026#39;proxy_url\u0026#39; def get_proxy(self): try: response = requests.get(self.proxy_url) if response.status_code == 200: p = response.text.strip() logging.debug(p) ip = {\u0026#34;http\u0026#34;:\u0026#34;http://\u0026#34;+p,\u0026#34;https\u0026#34;:\u0026#34;https://\u0026#34;+p} r = requests.get(\u0026#34;https://www.baidu.com\u0026#34;,proxies=ip,timeout=4) if r.status_code == 200: return p except: logging.debug(\u0026#39;get proxy again ...\u0026#39;) return self.get_proxy() def process_request(self,request,spider): proxy = self.get_proxy() if proxy: logging.debug(\u0026#34;=====\u0026#34;+\u0026#39;使用代理:\u0026#39;+proxy+\u0026#34;=====\u0026#34;) request.meta[\u0026#39;proxy\u0026#39;] = \u0026#39;https://{}\u0026#39;.format(proxy) def process_response(self,request,response,spider): if response.status != 200: logging.debug(\u0026#39;再次请求：\u0026#39;) request.meta[\u0026#39;proxy\u0026#39;] = \u0026#39;https://{}\u0026#39;.format(self.get_proxy()) return request return response 最后就是setting.py：\n# Scrapy settings for test_rj project # # For simplicity, this file contains only settings considered important or # commonly used. You can find more settings consulting the documentation: # # https://docs.scrapy.org/en/latest/topics/settings.html # https://docs.scrapy.org/en/latest/topics/downloader-middleware.html # https://docs.scrapy.org/en/latest/topics/spider-middleware.html BOT_NAME = \u0026#39;test_rj\u0026#39; SPIDER_MODULES = [\u0026#39;test_rj.spiders\u0026#39;] NEWSPIDER_MODULE = \u0026#39;test_rj.spiders\u0026#39; # Crawl responsibly by identifying yourself (and your website) on the user-agent #USER_AGENT = \u0026#39;test_rj (+http://www.yourdomain.com)\u0026#39; # Obey robots.txt rules ROBOTSTXT_OBEY = False # Configure maximum concurrent requests performed by Scrapy (default: 16) #CONCURRENT_REQUESTS = 32 # Configure a delay for requests for the same website (default: 0) # See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay # See also autothrottle settings and docs #DOWNLOAD_DELAY = 3 # The download delay setting will honor only one of: #CONCURRENT_REQUESTS_PER_DOMAIN = 16 #CONCURRENT_REQUESTS_PER_IP = 16 # Disable cookies (enabled by default) #COOKIES_ENABLED = False # Disable Telnet Console (enabled by default) #TELNETCONSOLE_ENABLED = False # Override the default request headers: DEFAULT_REQUEST_HEADERS = { \u0026#39;Accept\u0026#39;: \u0026#39;text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\u0026#39;, \u0026#39;Accept-Language\u0026#39;: \u0026#39;zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6\u0026#39;, \u0026#39;user-agent\u0026#39;: \u0026#39;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.15 Safari/537.36 Edg/85.0.564.8\u0026#39; } # Enable or disable spider middlewares # See https://docs.scrapy.org/en/latest/topics/spider-middleware.html #SPIDER_MIDDLEWARES = { # \u0026#39;test_rj.middlewares.TestRjSpiderMiddleware\u0026#39;: 543, #} # Enable or disable downloader middlewares # See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html DOWNLOADER_MIDDLEWARES = { #\u0026#39;test_rj.middlewares.TestRjDownloaderMiddleware\u0026#39;: 543, \u0026#39;test_rj.middlewares.use_agent_\u0026#39;:300, \u0026#39;test_rj.middlewares.ippool\u0026#39;:301, } # Enable or disable extensions # See https://docs.scrapy.org/en/latest/topics/extensions.html #EXTENSIONS = { # \u0026#39;scrapy.extensions.telnet.TelnetConsole\u0026#39;: None, #} # Configure item pipelines # See https://docs.scrapy.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { \u0026#39;test_rj.pipelines.TestRjPipeline\u0026#39;: 300, } # Enable and configure the AutoThrottle extension (disabled by default) # See https://docs.scrapy.org/en/latest/topics/autothrottle.html #AUTOTHROTTLE_ENABLED = True # The initial download delay #AUTOTHROTTLE_START_DELAY = 5 # The maximum download delay to be set in case of high latencies #AUTOTHROTTLE_MAX_DELAY = 60 # The average number of requests Scrapy should be sending in parallel to # each remote server #AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 # Enable showing throttling stats for every response received: #AUTOTHROTTLE_DEBUG = False # Enable and configure HTTP caching (disabled by default) # See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings #HTTPCACHE_ENABLED = True #HTTPCACHE_EXPIRATION_SECS = 0 #HTTPCACHE_DIR = \u0026#39;httpcache\u0026#39; #HTTPCACHE_IGNORE_HTTP_CODES = [] #HTTPCACHE_STORAGE = \u0026#39;scrapy.extensions.httpcache.FilesystemCacheStorage\u0026#39; LOG_LEVEL = \u0026#39;DEBUG\u0026#39; log_file_path = (\u0026#39;log/scrapy.log\u0026#39;) LOG_FILE = log_file_path 为了方便执行，再写一个main.py作为执行文件\nfrom scrapy import cmdline cmdline.execute(\u0026#34;scrapy crawl rj_test\u0026#34;.split()) log中的效果\n由于用上了代理，速度肯定会慢上不少，但是速度还是可以的，大概15秒有100次请求和保存那样\n","permalink":"https://www.kutake.top/archives/scrapy%E9%9A%8F%E6%9C%BAuser-again%E9%9A%8F%E6%9C%BA%E9%AB%98%E5%8C%BF%E4%BB%A3%E7%90%86ip%E7%88%AC%E5%8F%96%E5%85%A8%E7%90%83%E6%9C%80%E5%A4%A7%E7%9A%84%E5%90%8C%E4%BA%BA%E7%BD%91%E7%AB%99%E4%B8%8A/","summary":"\u003ch2 id=\"确定需求\"\u003e确定需求\u003c/h2\u003e\n\u003c!-- toc --\u003e\n\u003cp\u003eDLsite是全球最大的同人贩卖网站，里面包括漫画、音声、动画、游戏等等来自不同国家不同地区不同语言以及不同形式的二次元创作作品，这个项目暂定是爬取home下的所有漫画信息作为统计数据，链接为：\u003c/p\u003e","title":"Scrapy+随机User-Again+随机高匿代理ip爬取全球最大的同人网站上万条信息"},{"content":"首先放上piplines的代码\nfrom itemadapter import ItemAdapter import MySQLdb import logging class TestRjPipeline: def __init__(self): host = (\u0026#39;localhost\u0026#39;) user = (\u0026#39;root\u0026#39;) pass_ = (\u0026#39;123456\u0026#39;) db = (\u0026#39;sqlforpy\u0026#39;) self.connect = MySQLdb.connect(host,user,pass_,db,charset=\u0026#39;utf8mb4\u0026#39;) self.cursor = self.connect.cursor() def process_item(self, item, spider): def get_data(items_): data = dict(items_) rj={\u0026#39;ID\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;title\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;circle\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;keyword_creater\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;data\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;sname\u0026#39;:\u0026#39;无\u0026#39;\\ ,\u0026#39;GEN\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;type_\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;category\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;other\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;pages\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;genre\u0026#39;:\u0026#39;无\u0026#39;,\\ \u0026#39;file_\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;event\u0026#39;:\u0026#39;无\u0026#39;} for k in data.items(): rj[k[0]] = k[1] return rj rj = get_data(item) sql = r\u0026#34;\u0026#34;\u0026#34;insert into dlsite_cg_copy1(RJ,标题,社团,作者,发售日,系列名,年龄指定,文件形式,作品类型,其他,页数,分类,文件容量,活动)VALUES(\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;)\u0026#34;\u0026#34;\u0026#34;% (rj[\u0026#39;ID\u0026#39;],rj[\u0026#39;title\u0026#39;],rj[\u0026#39;circle\u0026#39;],rj[\u0026#39;keyword_creater\u0026#39;],rj[\u0026#39;data\u0026#39;],rj[\u0026#39;sname\u0026#39;],rj[\u0026#39;GEN\u0026#39;],rj[\u0026#39;type_\u0026#39;],rj[\u0026#39;category\u0026#39;],rj[\u0026#39;other\u0026#39;],rj[\u0026#39;pages\u0026#39;],rj[\u0026#39;genre\u0026#39;],rj[\u0026#39;file_\u0026#39;],rj[\u0026#39;event\u0026#39;]) try: self.cursor.execute(sql) self.connect.commit() except: logging.debug(\u0026#39;%s写入数据库出现错误！\u0026#39;%rj[\u0026#39;ID\u0026#39;]) logging.debug(\u0026#39;内容为:\\n{}\\n\u0026#39;.format(rj)) self.connect.rollback() logging.debug(\u0026#39;此次保存操作结束\u0026#39;) def close_spider(self, spider): self.cursor.close() self.connect.close() start之后，总是只能保存第一条信息，然后一直显示2006错误\n能保存第一条的话，那问题应该不是出在数据包太大的问题上\n猜测可能是在第一次连接之后，数据库的连接开始出现问题，于是使用：\nself.connect.ping(True) 来检测数据库是否连接，断开的话自动连接\n于是问题得到解决\n大概原理： python链接mysql中没有长链接的概念，但我们可以利用mysql的ping机制，来实现长链接功能~\n思路:\n1 python mysql 的ping 函数会校验链接的可用性,如果连接不可用将会产生异常\n2 利用这一特性，构造一个连接丢失的循环，不断尝试连接数据库，直到连接恢复\n3 使用这样的机制不需要关闭数据库功能,对于驻留进程，有大量数据进行写操作时，很有用途\n","permalink":"https://www.kutake.top/archives/scrapymysql2006%E9%94%99%E8%AF%AF%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/","summary":"\u003cp\u003e首先放上piplines的代码\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003efrom itemadapter import ItemAdapter\nimport MySQLdb\nimport logging\n\nclass TestRjPipeline:\n    def __init__(self):\n        host = (\u0026#39;localhost\u0026#39;)\n        user = (\u0026#39;root\u0026#39;)\n        pass_ = (\u0026#39;123456\u0026#39;)\n        db = (\u0026#39;sqlforpy\u0026#39;)\n        self.connect = MySQLdb.connect(host,user,pass_,db,charset=\u0026#39;utf8mb4\u0026#39;)\n        self.cursor = self.connect.cursor()\n    def process_item(self, item, spider):\n        def get_data(items_):\n            data = dict(items_)\n            rj={\u0026#39;ID\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;title\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;circle\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;keyword_creater\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;data\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;sname\u0026#39;:\u0026#39;无\u0026#39;\\\n                ,\u0026#39;GEN\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;type_\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;category\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;other\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;pages\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;genre\u0026#39;:\u0026#39;无\u0026#39;,\\\n                    \u0026#39;file_\u0026#39;:\u0026#39;无\u0026#39;,\u0026#39;event\u0026#39;:\u0026#39;无\u0026#39;}\n            for k in data.items():\n                rj[k[0]] = k[1]\n            return rj\n        rj = get_data(item)\n        sql = r\u0026#34;\u0026#34;\u0026#34;insert into dlsite_cg_copy1(RJ,标题,社团,作者,发售日,系列名,年龄指定,文件形式,作品类型,其他,页数,分类,文件容量,活动)VALUES(\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;)\u0026#34;\u0026#34;\u0026#34;% (rj[\u0026#39;ID\u0026#39;],rj[\u0026#39;title\u0026#39;],rj[\u0026#39;circle\u0026#39;],rj[\u0026#39;keyword_creater\u0026#39;],rj[\u0026#39;data\u0026#39;],rj[\u0026#39;sname\u0026#39;],rj[\u0026#39;GEN\u0026#39;],rj[\u0026#39;type_\u0026#39;],rj[\u0026#39;category\u0026#39;],rj[\u0026#39;other\u0026#39;],rj[\u0026#39;pages\u0026#39;],rj[\u0026#39;genre\u0026#39;],rj[\u0026#39;file_\u0026#39;],rj[\u0026#39;event\u0026#39;])\n        try:\n            self.cursor.execute(sql)\n            self.connect.commit()\n        except:\n            logging.debug(\u0026#39;%s写入数据库出现错误！\u0026#39;%rj[\u0026#39;ID\u0026#39;])\n            logging.debug(\u0026#39;内容为:\\n{}\\n\u0026#39;.format(rj))\n            self.connect.rollback()\n        logging.debug(\u0026#39;此次保存操作结束\u0026#39;)\n\n    def close_spider(self, spider):\n        self.cursor.close()\n        self.connect.close()\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003estart之后，总是只能\u003cstrong\u003e保存第一条信息\u003c/strong\u003e，然后一直显示\u003cstrong\u003e2006\u003c/strong\u003e错误\u003c/p\u003e","title":"scrapy+mysql:2006错误解决方法"},{"content":"在写scrapy的时候遇到的一个坑，准备了一个由“无”元素作为value的字典作为蓝本，遍历从scrapy上得到的items字典数据，把其中有的元素放到第一个作为蓝本的字典中，于是这里就有两个操作，读取第二个字典，修改第一个字典\nrj={......} data={......} for k,v in data: rj[k] = v 于是出现了ValueError: too many values to unpack错误\n在以前就有过在遍历字典的时候改变字典的值导致错误，这应该也是代码不规范的原因\n换一种方式：\nrj={......} data={......} for i in data.items(): rj[i[0]] = i[1] 这样就没有问题了\n","permalink":"https://www.kutake.top/archives/%E5%85%B3%E4%BA%8Evalueerror-too-many-values-to-unpack%E9%94%99%E8%AF%AF/","summary":"\u003cp\u003e在写scrapy的时候遇到的一个坑，准备了一个由“无”元素作为value的字典作为蓝本，遍历从scrapy上得到的items字典数据，把其中有的元素放到第一个作为蓝本的字典中，于是这里就有两个操作，读取第二个字典，修改第一个字典\u003c/p\u003e","title":"关于ValueError: too many values to unpack错误"},{"content":"为什么要做 还在做汉化组的扒图的时候，一直觉得comic walker这个站点的漫画拔起来非常麻烦，因为它自带一个阅读器界面，异步加载图片，而且无法右键，甚至在网页元素中，也无法直接保存图片（这应该是漫画网站的常见配置了），只能一个个在控制台中拖动图片至新的标签页才能加载图片并保存，十分麻烦，正好学习python中，就写了这个小工具。\n四个选项皆无法保存图片\n找到真实图片的加载方式 先搞清楚漫画的图片是如何加载的，才能进行写步骤代码的操作，先进入任意漫画阅读器界面中，进入F12控制台中并使用监视器查看详细的加载步骤\n可以很容易就找到图像位置并看到具体的请求url，第一步首先尝试此url是否为图片的真实url，复制url，新建标签页，输入后回车得到404，可见请求url并非真实的图片url\n得到的只是404\n可以说，绝大多数常见的漫画网站基本都是把链接放在json内再各种手段加密，从而得到一个经过前端处理的图片，具体图片的请求url也是经过js随机产生的，这就是连控制台内右键都无法保存图片的原因。\n再返回到控制台，可以看到三个图片下面有三个显示为奇怪的乱码的文件，且每次进行翻页的操作，这个迷之文件也和图片一起，一一对应地加载，很明显这个文件似乎跟最终图片有关，更进一步也印证了这个观点，每次刷新下，图片url都会改变，但迷之文件的url是固定的。\n此时从json文件中下手，仔细寻找一番，可以再KDCW_MF03201766010001_68和frames这两个文件中发现储存的漫画数据\n其中最重要的便是frames，里面有每个迷之乱码文件的url和一段对应的drm_hash码，很明显是用这一串hash对乱码文件进行了某种计算后才能得到原始图片\n当然到了这一地步，应该是查看具体的js代码，查看具体是做了什么样的计算，但很可惜本人没有学过js，看不懂js脚本，所以试了各种方法和手段都无法得到图片\n于是具体的解密方法就要请出万能的github了，经过一番搜索，我找到了一个由@vtbk 写的开源爬虫项目，其中就有对迷之文件解密的操作\n具体来说，就是经过url得到的乱码文件转换为二进制文件，把hash的前16位也转化为二进制文件，再对这两个数据进行集合计算，得到两个文件中不同时存在的数据，结果的二进制文件，就是漫画原图。\n开始写代码 由于本代码是给仍在comic walker进行扒图操作的汉化组组员使用的，所以需要考虑到便捷性，于是有了个大致的思路\nimport os import requests import json from itertools import cycle from PIL import Image from io import BytesIO from bs4 import BeautifulSoup #得到漫画url def get_comic_url(): url = input(\u0026#39;请输入漫画所在网址\\n例：https://comic-walker.com/contents/detail/KDCW_KS01200137010000_68/\\n\u0026#39;) return url #传入漫画url，返回由章节标题和cid组成的字典，以及漫画标题 def get_chpter_cid_title(comic_url): response = request(comic_url).content html_text = BeautifulSoup(response,\u0026#39;html.parser\u0026#39;,from_encoding=\u0026#39;utf-8\u0026#39;) souce_chapter = html_text.find(class_=\u0026#34;ac-content-inner\u0026#34;).find_all(\u0026#39;a\u0026#39;) comic_title = html_text.find(class_=\u0026#34;comicIndex-box\u0026#34;).find(\u0026#39;h1\u0026#39;).get_text() cid = [] chpter_title = [] for chapter in souce_chapter: cid.append(chapter.attrs[\u0026#34;href\u0026#34;].split(\u0026#39;=\u0026#39;)[-1]) chpter_title.append(chapter.attrs[\u0026#34;title\u0026#34;].replace(comic_title,\u0026#39;\u0026#39;).replace(\u0026#39; \u0026#39;,\u0026#39;\u0026#39;)) return dict(zip(chpter_title,cid)),comic_title #传入漫画标题以及由章节标题和cid组成的字典，返回经过筛选的路径列表和cid字典 def check_path(comic_title:str,chpter_cid:dict): main_path = get_comic_path() title_path = os.path.join(main_path,comic_title) if os.path.exists(title_path):#漫画文件夹存在 creat_chapter_path_list = [] need_del_key = [] for key in chpter_cid.keys():#筛选需要更新的章节 chapter_path = os.path.join(title_path,key) if os.path.exists(chapter_path): need_del_key.append(key) else: creat_chapter_path_list.append(chapter_path) for key in need_del_key: del chpter_cid[key] if chpter_cid == {}: print(\u0026#39;此漫画无可更新章节\u0026#39;) #需要下载的字典为空，无可用更新，退出 os.system(\u0026#39;pause\u0026#39;) print(\u0026#39;开始在 %s 下创建以下文件夹:\u0026#39;%title_path) for creat_chapter_path in creat_chapter_path_list:#开始创建文件夹 os.mkdir(creat_chapter_path) print(\u0026#39;%s创建成功\u0026#39;%creat_chapter_path) return creat_chapter_path_list,chpter_cid else:#漫画文件夹不存在 os.mkdir(title_path) print(\u0026#39;开始在 %s 下创建以下文件夹:\u0026#39;%title_path) chapter_path_list = [] for key in chpter_cid.keys(): chapter_path = os.path.join(title_path,key) chapter_path_list.append(chapter_path) os.mkdir(chapter_path) print(\u0026#39;%s创建成功\u0026#39;%chapter_path) return chapter_path_list,chpter_cid #读取本地储存好的路径 def path_open_read(): with open(\u0026#39;./path.txt\u0026#39;,\u0026#39;r\u0026#39;) as f: path = f.read() return path #写入路径并保存为txt def path_open_write(path): with open(\u0026#39;./path.txt\u0026#39;,\u0026#39;w\u0026#39;) as f: f.write(path) return None #检查漫画储存路径 def get_comic_path(): if os.path.exists(\u0026#39;./path.txt\u0026#39;): return path_open_read() else: while 1: path = input(\u0026#39;请输入存储漫画的文件夹路径:\\n\u0026#39;) if os.path.exists(path): path_open_write(path) return path else: print(\u0026#39;路径不存在，请检查并重新输入\u0026#39;) #请求 def request(url: str): #headers = {\u0026#39;User-Agent\u0026#39;: \u0026#39;User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36\u0026#39;} return requests.get(url) #传入json形式的frames，得到未解密的原图 def fetch_images_as_bytes(frames): print(\u0026#39;开始查找图片：\u0026#39;) images = [] i=1 for frame in frames: url = frame[\u0026#39;meta\u0026#39;][\u0026#39;source_url\u0026#39;] image_as_bytes = request(url).content images.append(image_as_bytes) print(\u0026#39;查找第%d张图片\u0026#39;%i) i += 1 print(\u0026#39;此章节查找结束\u0026#39;) return images #传入json形式的frames，得到drm_hash码 def get_drm_hashes_from_frames(frames): return [frame[\u0026#39;meta\u0026#39;][\u0026#39;drm_hash\u0026#39;] for frame in frames] #传入未解密的原图和hash码，返回解密后的二进制列表 def solve(sources, drm_hashes): images = [] for source, drm_hash in zip(sources, drm_hashes): decoded_image_bytes = bytearray() for a, b in zip(source, cycle(bytes.fromhex(drm_hash[0:16]))): decoded_image_bytes.append(a ^ b) decoded_image = Image.open(BytesIO(decoded_image_bytes)) images.append(decoded_image) return images #按文件名和路径依次保存解密好的图片 def save(images,chapter_path): print(\u0026#39;开始保存图片：\u0026#39;) for i, image in enumerate(images): filename = str(i+1) + \u0026#39;.jpg\u0026#39; filename = filename.rjust(7, \u0026#39;0\u0026#39;) filepath = os.path.join(chapter_path,filename) image.save(filepath) print(\u0026#39;保存第%s张图片\u0026#39;%i) print(\u0026#39;此章节保存结束\u0026#39;) #传入cid，得到章节frames的请求url def get_url_frames(cid): url_frames = \u0026#39;https://manga-api.nicoseiga.jp/api/v1/comicwalker/episodes/{}/frames\u0026#39;.format(cid) return url_frames #运行 def run(): print(\u0026#39;使用时请打开代理全局模式，卡死时请直接退出并检查代理设置\u0026#39;) comic_url = get_comic_url() chpter_cid,comic_title = get_chpter_cid_title(comic_url) chapter_path_list,chpter_cid_checked = check_path(comic_title,chpter_cid) #cid_list = [valus for key,valus in chpter_cid_checked.items()] i = 0 chapter_num = len(chpter_cid_checked) for cid in chpter_cid_checked.values(): url_frames = get_url_frames(cid) chapter_frames = json.loads(request(url_frames).content)[\u0026#39;data\u0026#39;][\u0026#39;result\u0026#39;] images = solve(fetch_images_as_bytes(chapter_frames), get_drm_hashes_from_frames(chapter_frames)) save(images,chapter_path_list[i]) i += 1 if i == chapter_num: print(\u0026#39;漫画保存完毕\u0026#39;) else: print(\u0026#39;剩余{}个章节\u0026#39;.format(chapter_num-i)) run() os.system(\u0026#39;pause\u0026#39;) 由于环境和IDE的问题，把所有功能都放在了一个文件下，并打好包后传给了组员，进过几次尝试之后，下载成功。\n存在的问题 检测到本地path.txt文件后没有验证里面的路径是否存在 对网络环境要求苛刻，需要全局代理 下载与保存是按每一话而不是每一张图，导致错误后维护性差 目前来说，作为一个小汉化组自用的小工具是够用了，后续或许会更新其他站点的爬取规则\n","permalink":"https://www.kutake.top/archives/python%E5%AE%9E%E6%88%98comic-walker%E6%BC%AB%E7%94%BB%E5%A4%9A%E7%AB%A0%E8%8A%82%E7%88%AC%E8%99%AB/","summary":"\u003ch2 id=\"为什么要做\"\u003e为什么要做\u003c/h2\u003e\n\u003cp\u003e还在做汉化组的扒图的时候，一直觉得comic walker这个站点的漫画拔起来非常麻烦，因为它自带一个阅读器界面，异步加载图片，而且无法右键，甚至在网页元素中，也无法直接保存图片（这应该是漫画网站的常见配置了），只能一个个在控制台中拖动图片至新的标签页才能加载图片并保存，十分麻烦，正好学习python中，就写了这个小工具。\u003c/p\u003e","title":"实战：comic walker漫画多章节爬虫"},{"content":"#写成json的文本文件内容，转换为xls文件 import xlwt import os import json def get_(path): with open(path,\u0026#39;r\u0026#39;,encoding=\u0026#39;utf-8\u0026#39;) as f: content=f.read() return content def json_(str): d=json.loads(str) d=sorted(d.items(),key=lambda item:item[0],reverse=False) return d def json_16(str): d=json.loads(str) return d def save_(d,path): workbook=xlwt.Workbook(encoding=\u0026#39;utf-8\u0026#39;) sheet1=workbook.add_sheet(\u0026#39;sheet1\u0026#39;) r=0 c=1 for key,value in d: sheet1.write(r,0,key) for i in value: sheet1.write(r,c,i) c+=1 r+=1 c=1 workbook.save(path) def save_16(d,path): workbook=xlwt.Workbook(encoding=\u0026#39;utf-8\u0026#39;) sheet1=workbook.add_sheet(\u0026#39;sheet1\u0026#39;) for a,b in enumerate(d): for c,v in enumerate(b): sheet1.write(a,c,v) workbook.save(path) if __name__ == \u0026#34;__main__\u0026#34;: p1=r\u0026#39;E:\\vscode\\code\\testwork\\txtfile\\5.txt\u0026#39; p2=r\u0026#39;E:\\vscode\\code\\testwork\\txtfile\\5.xls\u0026#39; save_16(json_16(get_(p1)),p2) print(\u0026#39;END\u0026#39;) ","permalink":"https://www.kutake.top/archives/python%E7%BB%83%E4%B9%A0%E9%A2%989json%E6%96%87%E4%BB%B6%E8%BD%ACxls%E6%96%87%E4%BB%B6/","summary":"\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e#写成json的文本文件内容，转换为xls文件\nimport xlwt\nimport os\nimport json\ndef get_(path):\n    with open(path,\u0026#39;r\u0026#39;,encoding=\u0026#39;utf-8\u0026#39;) as f:\n        content=f.read()\n    return content\ndef json_(str):\n    d=json.loads(str)\n    d=sorted(d.items(),key=lambda item:item[0],reverse=False)\n    return d\ndef json_16(str):\n    d=json.loads(str)\n    return d\ndef save_(d,path):\n    workbook=xlwt.Workbook(encoding=\u0026#39;utf-8\u0026#39;)\n    sheet1=workbook.add_sheet(\u0026#39;sheet1\u0026#39;)\n    r=0\n    c=1\n    for key,value in d:\n        sheet1.write(r,0,key)\n        for i in value:\n            sheet1.write(r,c,i)\n            c+=1\n        r+=1\n        c=1\n    workbook.save(path)\ndef save_16(d,path):\n    workbook=xlwt.Workbook(encoding=\u0026#39;utf-8\u0026#39;)\n    sheet1=workbook.add_sheet(\u0026#39;sheet1\u0026#39;)\n    for a,b in enumerate(d):\n        for c,v in enumerate(b):\n            sheet1.write(a,c,v)\n    workbook.save(path)\nif __name__ == \u0026#34;__main__\u0026#34;:\n    p1=r\u0026#39;E:\\vscode\\code\\testwork\\txtfile\\5.txt\u0026#39;\n    p2=r\u0026#39;E:\\vscode\\code\\testwork\\txtfile\\5.xls\u0026#39;\n    save_16(json_16(get_(p1)),p2)\n    print(\u0026#39;END\u0026#39;)\n\u003c/code\u003e\u003c/pre\u003e","title":"json文件转xls文件"},{"content":"import re import jieba import collections import numpy as np import wordcloud # 词云展示库 from PIL import Image # 图像处理库 import matplotlib.pyplot as plt # 图像展示库 def remove(): with open(\u0026#39;remove_words.txt\u0026#39;,\u0026#39;r\u0026#39;,encoding=\u0026#39;utf-8\u0026#39;) as f: txt=f.read() words = re.findall(r\u0026#39;[^\\s]+\u0026#39;,txt) list_=[] for i in words: if i in list_: continue else: list_.append(i) return list_ def get_words(path): f=open(path,encoding=\u0026#39;utf-8\u0026#39;) txt=f.read() f.close() words = jieba.lcut(txt) remove_word=remove() reword = [] for word in words: if len(word) == 1: continue elif word in remove_word: continue else: reword.append(word) word_counts=collections.Counter(reword) return word_counts #统计词频 def wordcount(word_counts): top=word_counts.most_common(20) print(top) def wordc(impath,words): mask=np.array(Image.open(impath)) wc = wordcloud.WordCloud( font_path=\u0026#39;C:/Windows/Fonts/simhei.ttf\u0026#39;, # 设置字体格式 mask=mask, # 设置背景图 max_words=600, # 最多显示词数 max_font_size=130 # 字体最大值 ) wc.generate_from_frequencies(words) # 从字典生成词云 image_colors = wordcloud.ImageColorGenerator(mask) # 从背景图建立颜色方案 wc.recolor(color_func=image_colors) # 将词云颜色设置为背景图方案 plt.imshow(wc) # 显示词云 plt.axis(\u0026#39;off\u0026#39;) # 关闭坐标轴 plt.show() # 显示图像 wc.to_file(\u0026#39;ff.png\u0026#39;) if __name__ == \u0026#34;__main__\u0026#34;: path=r\u0026#39;E:\\vscode\\code\\ff.txt\u0026#39; impath=r\u0026#39;E:\\vscode\\code\\topdodo_2.jpg\u0026#39; wordc(impath,get_words(path)) ","permalink":"https://www.kutake.top/archives/python%E5%AE%9E%E6%88%98%E8%AF%8D%E4%BA%91%E7%94%9F%E6%88%90/","summary":"\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eimport re\nimport jieba\nimport collections\nimport numpy as np\nimport wordcloud # 词云展示库\nfrom PIL import Image # 图像处理库\nimport matplotlib.pyplot as plt # 图像展示库\ndef remove():\n    with open(\u0026#39;remove_words.txt\u0026#39;,\u0026#39;r\u0026#39;,encoding=\u0026#39;utf-8\u0026#39;) as f:\n        txt=f.read()\n    words = re.findall(r\u0026#39;[^\\s]+\u0026#39;,txt)\n    list_=[]\n    for i in words:\n        if i in list_:\n            continue\n        else:\n            list_.append(i)\n    return list_\ndef get_words(path):\n    f=open(path,encoding=\u0026#39;utf-8\u0026#39;)\n    txt=f.read()\n    f.close()\n    words = jieba.lcut(txt)\n    remove_word=remove()\n    reword = []\n    for word in words:\n        if len(word) == 1:\n            continue\n        elif word in remove_word:\n            continue\n        else:\n            reword.append(word)\n    word_counts=collections.Counter(reword)\n    return word_counts\n#统计词频\ndef wordcount(word_counts):\n    top=word_counts.most_common(20)\n    print(top)\ndef wordc(impath,words):\n    mask=np.array(Image.open(impath))\n    wc = wordcloud.WordCloud(\n        font_path=\u0026#39;C:/Windows/Fonts/simhei.ttf\u0026#39;, # 设置字体格式\n        mask=mask, # 设置背景图\n        max_words=600, # 最多显示词数\n        max_font_size=130 # 字体最大值\n    )\n    wc.generate_from_frequencies(words) # 从字典生成词云\n    image_colors = wordcloud.ImageColorGenerator(mask) # 从背景图建立颜色方案\n    wc.recolor(color_func=image_colors) # 将词云颜色设置为背景图方案\n    plt.imshow(wc) # 显示词云\n    plt.axis(\u0026#39;off\u0026#39;) # 关闭坐标轴\n    plt.show() # 显示图像\n    wc.to_file(\u0026#39;ff.png\u0026#39;)\nif __name__ == \u0026#34;__main__\u0026#34;:\n    path=r\u0026#39;E:\\vscode\\code\\ff.txt\u0026#39;\n    impath=r\u0026#39;E:\\vscode\\code\\topdodo_2.jpg\u0026#39;\n    wordc(impath,get_words(path))\n\u003c/code\u003e\u003c/pre\u003e","title":"实战：词云生成"},{"content":"下载了上百个RJ后，如何根据声优或分类喜好来找到自己喜欢的作品就成了个问题，于是诞生出了这个代码\n问题拆分 定位需求 查询RJ主要是在对该RJ文件夹对应网页中的标题、声优、制作社团与分类，拿到具体数据后，对数据进行查询操作。\n大致方法 遍历RJ文件所在的文件夹可以使用os，提取其中的RJ号可使用re正则表达式，根据提取到的RJ号使用request模块模拟打开网站，再使用bs4对标题、声优、制作社团、分类进行提取，提取的数据保存乳本地mysql数据库中方便查询，此处使用MySQLdb库\n代码 import requests from bs4 import BeautifulSoup import re import os import MySQLdb def get_url(rj): url=\u0026#39;https://www.dlsite.com\u0026#39; req_link= url + \u0026#34;/maniax/work/=/product_id/{}.html\u0026#34; url_rj=req_link.format(rj) return url_rj def get_html(url): headers = { \u0026#39;Host\u0026#39;:\u0026#39;www.dlsite.com\u0026#39;, \u0026#39;Accept\u0026#39;:\u0026#39;text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\u0026#39;, \u0026#39;Referer\u0026#39;:\u0026#39;https://www.dlsite.com/maniax/\u0026#39;, \u0026#39;Accept-Language\u0026#39;:\u0026#39;zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6\u0026#39;, \u0026#39;Accept-Encoding\u0026#39;:\u0026#39;gzip, deflate, br\u0026#39;, \u0026#34;User-Agent\u0026#34;:\u0026#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36\u0026#34; } response=requests.get(url,headers=headers) dl = BeautifulSoup(response.text,\u0026#39;html.parser\u0026#39;) return dl #第一次响应返回的html中，’h1‘标签中的标题，返回结果为标题字符串 def get_title(html): i=html.find(\u0026#39;h1\u0026#39;) if i: title=str.strip(i.get_text()) if title == \u0026#39;\u0026#39;: return \u0026#39;不明\u0026#39; else: return title else: return \u0026#39;不明\u0026#39; def get_maker(html): i=html.find(itemprop=\u0026#34;brand\u0026#34;) if i: maker=str.strip(i.get_text()) if maker == \u0026#39;\u0026#39;: return \u0026#39;不明\u0026#39; else: return maker else: return \u0026#39;不明\u0026#39; def get_seyuu(html): seyuu=html.find(id=\u0026#34;work_outline\u0026#34;) if seyuu: s=str.strip(seyuu.get_text()) i=re.findall(r\u0026#39;[声][优][\\n]+[^\\n]+[\\n]{2}\u0026#39;,s) if i == []: return \u0026#39;不明\u0026#39; else: b=i[0].replace(\u0026#39;\\n\u0026#39;,\u0026#39;\u0026#39;).replace(\u0026#39;声优\u0026#39;,\u0026#39;\u0026#39;).replace(\u0026#39; \u0026#39;,\u0026#39;\u0026#39;) return b else: return \u0026#39;不明\u0026#39; def get_genre(html): g=html.find(class_=\u0026#34;main_genre\u0026#34;) if g: genre=str.strip(g.get_text()) if genre == \u0026#39;\u0026#39;: return \u0026#39;不明\u0026#39; else: return genre else: return \u0026#39;不明\u0026#39; def check_rj(rj): host = (\u0026#39;localhost\u0026#39;) user = (\u0026#39;root\u0026#39;) pass_ = (\u0026#39;123456\u0026#39;) db = (\u0026#39;sqlforpy\u0026#39;) connect = MySQLdb.connect(host,user,pass_,db,charset=\u0026#39;utf8\u0026#39;) cursor = connect.cursor() sql = \u0026#39;SELECT RJ FROM infor_voice\u0026#39; cursor.execute(sql) result = cursor.fetchall() rj_ = [] n_ = 0 for i in rj: r_=(i,) if r_ not in result: rj_.append(i) n_ += 1 elif r_ in result: print(\u0026#39;%s已存在\u0026#39;%i) print(\u0026#39;将写入%d条信息\u0026#39;%n_) return rj_ def sql(rj,title,seyuu,maker,genre): host = (\u0026#39;localhost\u0026#39;) user = (\u0026#39;root\u0026#39;) pass_ = (\u0026#39;123456\u0026#39;) db = (\u0026#39;sqlforpy\u0026#39;) connect = MySQLdb.connect(host,user,pass_,db,charset=\u0026#39;utf8\u0026#39;) cursor = connect.cursor() sql = \u0026#34;\u0026#34;\u0026#34;insert into infor_voice(RJ,标题,声优,社团,分类)VALUES\\ (\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;,\u0026#34;%s\u0026#34;)\u0026#34;\u0026#34;\u0026#34;% (rj,title,seyuu,maker,genre) try: cursor.execute(sql) connect.commit() except: print(\u0026#39;%s写入数据库出现错误！\u0026#39;%rj) connect.rollback() connect.close() def get_rj(path): list_=[] for root,dirs,files in os.walk(path): for i in dirs: if i.strip().startswith(\u0026#39;RJ\u0026#39;): rj=re.findall(\u0026#39;[Rr][jJ][0-9]+\u0026#39;,i)[0] list_.append(rj) return list(set((list_))) def run_(path): print(\u0026#39;查重开始\u0026#39;) rj=check_rj(get_rj(path)) print(\u0026#39;查重结束\u0026#39;) for i in rj: print(\u0026#39;开始搜索%s\u0026#39;%i) url=get_url(i) html=get_html(url) sql(i,get_title(html),get_seyuu(html),get_maker(html),get_genre(html)) print(\u0026#39;%s保存完毕\u0026#39;%i) print(\u0026#39;结束\u0026#39;) if __name__ == \u0026#34;__main__\u0026#34;: path=r\u0026#39;H:\\Down\\For\\Ad\u0026#39; run_(path) 一些笔记 本地RJ中使用了list(set(list))方法对提取到的RJ号进行去重复，在爬取前还先将数据库中原本存在的RJ号进行提取并与本地文件夹中的RJ号对比，提取非重复部分进行第二次去重，减少爬取数量。\n有些RJ作品下架导致无法查询具体信息，将返回“不明”作为补充（此处代码可简化），还有RJ作品中无声优或社团信息，也将返回“不明”（此处有待优化）\n写入本地数据库中，若因未知错误出现无法写入的状况将提示出错，并显示具体RJ号\n一些问题 下架作品查询重复，浪费时间 声优信息也出现在作品描述里面，可进行替代 无法反向对只有RJ号的本地文件夹进行命名操作 只有标题而无RJ前缀的文件夹将无法查询 后续补齐无法对本地数据库进行批量修改更新 ","permalink":"https://www.kutake.top/archives/rj%E7%88%AC%E8%99%AB%E4%B8%8A%E4%BC%A0%E6%9C%AC%E5%9C%B0sql/","summary":"\u003cp\u003e下载了上百个RJ后，如何根据声优或分类喜好来找到自己喜欢的作品就成了个问题，于是诞生出了这个代码\u003c/p\u003e\n\u003ch2 id=\"问题拆分\"\u003e\u003cstrong\u003e问题拆分\u003c/strong\u003e\u003c/h2\u003e\n\u003ch3 id=\"定位需求\"\u003e定位需求\u003c/h3\u003e\n\u003cp\u003e查询RJ主要是在对该RJ文件夹对应网页中的标题、声优、制作社团与分类，拿到具体数据后，对数据进行查询操作。\u003c/p\u003e","title":"实战：RJ爬虫并上传本地SQL"},{"content":"#四位数数字字母验证码，显示进行输入判断验证 from PIL import Image, ImageFilter, ImageFont,ImageDraw import os import random import string import matplotlib.pyplot as plt # ImageFilter:Python中的图像滤波，主要对图像进行平滑、锐化、边界增强等滤波处理。 #随机选取一个数字字母，含大小写 def get_word(): word = string.ascii_letters+string.digits return random.choice(word) #字体像素随机颜色 def font_color(): #random.randint()在范围内取随机数 return(random.randint(32,255),random.randint(23,255),random.randint(32,255)) #背景像素随机颜色 def b_color(): return(random.randint(64,255),random.randint(64,255),random.randint(64,255)) #生成验证码图像，返回一个字符串，表示验证码 def imag(path): w = 60*4 h = 60 d_word=\u0026#39;\u0026#39; #新建图像对象 img = Image.new(\u0026#39;RGB\u0026#39;,(w,h),(255,255,255)) #字体对象 font = ImageFont.truetype(r\u0026#39;H:\\gay化\\汉化\\字体\\4书法 - 2\\FZ宋s刻d本r秀d楷繁.ttf\u0026#39;,60) #类似于画笔 draw = ImageDraw.Draw(img) for i in range(w): for j in range(h): #画背景，用随机颜色填充每个像素 draw.point((i,j),fill=b_color()) for i in range(4): word=get_word() #draw.text方法进行字符绘图，()内：绘图位置，字符，字体，颜色 draw.text((i*60+10,5),word,font=font,fill=font_color()) #单个验证码相加 d_word = d_word + word #保存验证码图像 img.save(path,\u0026#39;jpeg\u0026#39;) #返回验证码 return d_word #一个输入函数，调用时返回输入内容 def get_input(): return str(input(\u0026#39;请输入验证码:\\n\u0026#39;)) #运行 if __name__ == \u0026#34;__main__\u0026#34;: path = (\u0026#39;test.jpg\u0026#39;) str_=imag(path) test_img=Image.open(path) plt.figure(\u0026#39;test\u0026#39;) plt.imshow(test_img) plt.show() #四次机会 for i in range(4): if str.lower(str_) == str.lower(get_input()): print(\u0026#39;验证成功\u0026#39;) break else: if i == 3: print(\u0026#39;错误次数已用完！\u0026#39;) else: print(\u0026#39;错误！还有%d次机会\u0026#39;%(3-i)) #结束后删除验证码图像 os.remove(path) ","permalink":"https://www.kutake.top/archives/python%E7%BB%83%E4%B9%A0%E9%A2%987%E7%94%9F%E6%88%90%E5%9B%9B%E4%BD%8D%E9%AA%8C%E8%AF%81%E7%A0%81%E5%B9%B6%E9%AA%8C%E8%AF%81/","summary":"\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e#四位数数字字母验证码，显示进行输入判断验证\nfrom PIL import Image, ImageFilter, ImageFont,ImageDraw\nimport os\nimport random\nimport string\nimport matplotlib.pyplot as plt\n#  ImageFilter:Python中的图像滤波，主要对图像进行平滑、锐化、边界增强等滤波处理。\n#随机选取一个数字字母，含大小写\ndef get_word():\n    word = string.ascii_letters+string.digits\n    return random.choice(word)\n#字体像素随机颜色\ndef font_color():\n    #random.randint()在范围内取随机数\n    return(random.randint(32,255),random.randint(23,255),random.randint(32,255))\n#背景像素随机颜色\ndef b_color():\n    return(random.randint(64,255),random.randint(64,255),random.randint(64,255))\n#生成验证码图像，返回一个字符串，表示验证码\ndef imag(path):\n    w = 60*4\n    h = 60\n    d_word=\u0026#39;\u0026#39;\n    #新建图像对象\n    img = Image.new(\u0026#39;RGB\u0026#39;,(w,h),(255,255,255))\n    #字体对象\n    font = ImageFont.truetype(r\u0026#39;H:\\gay化\\汉化\\字体\\4书法 - 2\\FZ宋s刻d本r秀d楷繁.ttf\u0026#39;,60)\n    #类似于画笔\n    draw = ImageDraw.Draw(img)\n    for i in range(w):\n        for j in range(h):\n            #画背景，用随机颜色填充每个像素\n            draw.point((i,j),fill=b_color())\n    for i in range(4):\n        word=get_word()\n        #draw.text方法进行字符绘图，()内：绘图位置，字符，字体，颜色\n        draw.text((i*60+10,5),word,font=font,fill=font_color())\n        #单个验证码相加\n        d_word = d_word + word\n    #保存验证码图像\n    img.save(path,\u0026#39;jpeg\u0026#39;)\n    #返回验证码\n    return d_word\n#一个输入函数，调用时返回输入内容\ndef get_input():\n    return str(input(\u0026#39;请输入验证码:\\n\u0026#39;))\n#运行\nif __name__ == \u0026#34;__main__\u0026#34;:\n    path = (\u0026#39;test.jpg\u0026#39;)\n    str_=imag(path)\n    test_img=Image.open(path)\n    plt.figure(\u0026#39;test\u0026#39;)\n    plt.imshow(test_img)\n    plt.show()\n    #四次机会\n    for i in range(4):\n        if str.lower(str_) == str.lower(get_input()):\n            print(\u0026#39;验证成功\u0026#39;)\n            break\n        else:\n            if i == 3:\n                print(\u0026#39;错误次数已用完！\u0026#39;)\n            else:\n                print(\u0026#39;错误！还有%d次机会\u0026#39;%(3-i))\n    #结束后删除验证码图像\n    os.remove(path)\n\u003c/code\u003e\u003c/pre\u003e","title":"生成四位验证码并验证"},{"content":"#设定一个分辨率，使一个文件夹内所有大于该分辨率的图像都进行等比例缩放 import os import re from PIL import Image #设置目录 f_path = \u0026#39;E:/vscode/code/testwork/testFile\u0026#39; #设置iPhone5的分辨率 i_high = 640 i_width = 1136 #使用os.listdir()方法，将目录内的图像文件名，以列表的形式存入list list=os.listdir(f_path) #设置计数初始值 count = 0 #对每个文件进行判断 for pic in list: #path为完整的图像文件路径 path=f_path+\u0026#39;/\u0026#39;+pic #使用Image.open()方法，打开图像文件 im=Image.open(path) #im.size方法获得图像的宽高，分别存入w,h w,h=im.size #当宽度大于i5 if w \u0026gt; i_width: #这里判断：原图像哪边大则依据哪边作为等比缩放比例 if w\u0026gt;h: w_new = i_width h_new = int(h*i_width/w) else: w_new = int(w*i_high/h) h_new = i_high print(\u0026#39;%s被修改\u0026#39; %pic) #使用resize()方法修改分辨率，((w,h),此处Image.ANTIALIAS表示高质量) out = im.resize((w_new,h_new),Image.ANTIALIAS) #使用re.sub方法，在字符串pic中，开头至倒数第四位字符替换为添加_new的字符串 newpic = re.sub(pic[:-4],pic[:-4]+\u0026#39;_new\u0026#39;,pic) #处理后的图像的完整保存路径 new_path = f_path+\u0026#39;/\u0026#39;+newpic #save方法保存 out.save(new_path) #处理完毕计数+1 count = count + 1 elif h \u0026gt; i_high: if w\u0026gt;h: w_new = i_width h_new = int(h*i_width/w) else: w_new = int(w*i_high/h) h_new = i_high print(\u0026#39;%s被修改\u0026#39; %pic) out = im.resize((w_new,h_new),Image.ANTIALIAS) newpic = re.sub(pic[:-4],pic[:-4]+\u0026#39;_new\u0026#39;,pic) new_path = f_path+\u0026#39;/\u0026#39;+newpic out.save(new_path) count = count + 1 print(\u0026#39;处理结束\\n总共有%d个文件被处理\u0026#39; %count) ","permalink":"https://www.kutake.top/archives/python%E7%BB%83%E4%B9%A0%E9%A2%984%E5%9B%BE%E7%89%87%E7%AD%89%E6%AF%94%E4%BE%8B%E7%BC%A9%E6%94%BE/","summary":"\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e#设定一个分辨率，使一个文件夹内所有大于该分辨率的图像都进行等比例缩放\nimport os\nimport re\nfrom PIL import Image\n#设置目录\nf_path = \u0026#39;E:/vscode/code/testwork/testFile\u0026#39;\n#设置iPhone5的分辨率\ni_high = 640\ni_width = 1136\n#使用os.listdir()方法，将目录内的图像文件名，以列表的形式存入list\nlist=os.listdir(f_path)\n#设置计数初始值\ncount = 0\n#对每个文件进行判断\nfor pic in list:\n    #path为完整的图像文件路径\n    path=f_path+\u0026#39;/\u0026#39;+pic\n    #使用Image.open()方法，打开图像文件\n    im=Image.open(path)\n    #im.size方法获得图像的宽高，分别存入w,h\n    w,h=im.size\n    #当宽度大于i5\n    if w \u0026gt; i_width:\n        #这里判断：原图像哪边大则依据哪边作为等比缩放比例\n        if w\u0026gt;h:\n            w_new = i_width\n            h_new = int(h*i_width/w)\n        else:\n            w_new = int(w*i_high/h)\n            h_new = i_high\n\n        print(\u0026#39;%s被修改\u0026#39; %pic)\n        #使用resize()方法修改分辨率，((w,h),此处Image.ANTIALIAS表示高质量)\n        out = im.resize((w_new,h_new),Image.ANTIALIAS)\n        #使用re.sub方法，在字符串pic中，开头至倒数第四位字符替换为添加_new的字符串\n        newpic = re.sub(pic[:-4],pic[:-4]+\u0026#39;_new\u0026#39;,pic)\n        #处理后的图像的完整保存路径\n        new_path = f_path+\u0026#39;/\u0026#39;+newpic\n        #save方法保存\n        out.save(new_path)\n        #处理完毕计数+1\n        count = count + 1\n    elif h \u0026gt; i_high:\n        if w\u0026gt;h:\n            w_new = i_width\n            h_new = int(h*i_width/w)\n        else:\n            w_new = int(w*i_high/h)\n            h_new = i_high\n        print(\u0026#39;%s被修改\u0026#39; %pic)\n        out = im.resize((w_new,h_new),Image.ANTIALIAS)\n        newpic = re.sub(pic[:-4],pic[:-4]+\u0026#39;_new\u0026#39;,pic)\n        new_path = f_path+\u0026#39;/\u0026#39;+newpic\n        out.save(new_path)\n        count = count + 1\n\nprint(\u0026#39;处理结束\\n总共有%d个文件被处理\u0026#39; %count)\n\u003c/code\u003e\u003c/pre\u003e","title":"图片等比例缩放实现"},{"content":"最开始的看到的python基础习题之一，现在顺便扩展一点，练习xlrd、pandas以及matplotlib的练习\n数据来源 来自于联通官网的通话数据下载，文件为某年某月的通话记录详情，格式为xls**[图片缺失: 2020/06/QQ截图20200627215145.png]**\n使用到的库 import os import xlrd import re from functools import reduce import pandas as pd import matplotlib.pyplot as plt 具体方法 使用os.listdir()方法得到所有月份的文件路径，每个路径用xlrd.open_workbook()和workbook.sheet_by_index(0)来获取sheet对象，并使用worksheet.nrows和worksheet.row_values( )[ ]方法获取以及定位内容；\n使用正则表达式提取内容中的分秒数据并进行计算，进行插入月份操作后得到一个由月份、总时长、通话次数、平均时长的list对象；\n对list对象使用pd.DataFrame(list,index=month,columns=[ '月','总时长(秒)','次数','平均(秒/每次)' ])得到DataFrame对象；\n对DataFrame对象使用.plot(x=,y=,kind='bar')和plt.show()方法得到直方图\n代码 import os import xlrd import re from functools import reduce import pandas as pd import matplotlib.pyplot as plt class tongxun(object): def __init__(self,path): self.path = path def run(self): def get_dirs(path):#返回一个由文件完整路径组成的列表 list_=[] for i in os.listdir(path): file_path = path + \u0026#39;\\\\\u0026#39; + i list_.append(file_path) return list_ def readxls(path):#从文件中返回一个sheet，输入为文件路径 workbook = xlrd.open_workbook(path) worksheet = workbook.sheet_by_index(0) return worksheet def get_date(worksheet):#从sheet中获取通话时长列表 nrows = worksheet.nrows time = [] for i in range(nrows): if i == 0: pass else: time.append(worksheet.row_values(i)[3]) return time def get_intTime(time_list):#利用正则把分和秒提取出来，输入列表返回列表 time = [] for i in time_list: s = re.search(r\u0026#39;[0-9]+[分]\u0026#39;,i) if s == None: m = int(re.search(r\u0026#39;[0-9]+[秒]\u0026#39;,i).group().replace(\u0026#39;秒\u0026#39;,\u0026#39;\u0026#39;)) time.append(m) else: s_m = int(s.group().replace(\u0026#39;分\u0026#39;,\u0026#39;\u0026#39;)) m = 60*s_m + int(re.search(r\u0026#39;[0-9]+[秒]\u0026#39;,i).group().replace(\u0026#39;秒\u0026#39;,\u0026#39;\u0026#39;)) time.append(m) return time def get_SumNumAvg(time_all):#实现计算，输入为str列表，返回和、总数、平均的int列表 time = [] time_every = [] for i in time_all: time.append(get_intTime(i)) for n in time: time_sum = reduce(lambda x,y:x+y,n) time_num = len(n) time_avg = round(time_sum / time_num,1) time_every.append([time_sum,time_num,time_avg]) return time_every def insert_month(SumNumAvg):#列表中插入月份 c=1 for i in SumNumAvg: i.insert(0,c) c += 1 return SumNumAvg def get_dateFrame(time_SumNumAvg):#利用列表生成一个dateFrame对象 month = list(range(0,len(time_SumNumAvg))) df = pd.DataFrame(time_SumNumAvg,index=month,columns=[\u0026#39;月\u0026#39;,\u0026#39;总时长(秒)\u0026#39;,\u0026#39;次数\u0026#39;,\u0026#39;平均(秒/每次)\u0026#39;]) return df #此处运行 time_all = [] for i in get_dirs(self.path): w=readxls(i) time_all.append(get_date(w)) time_SumNumAvg = insert_month(get_SumNumAvg(time_all)) data=get_dateFrame(time_SumNumAvg) while 1:#选择图表类型 input_ = int(input(\u0026#39;\\n输入你想要的图表(填对应数字)：\\n1：总时长\\n2：次数\\n3：平均数\\n\u0026#39;)) if input_ == 1: y_ = \u0026#39;总时长(秒)\u0026#39; break elif input_ == 2: y_ = \u0026#39;次数\u0026#39; break elif input_ == 3: y_ = \u0026#39;平均(秒/每次)\u0026#39; break else: print(\u0026#39;输入错误，请重新输入\u0026#39;) data.plot(x=\u0026#39;月\u0026#39;,y=y_,kind=\u0026#39;bar\u0026#39;) plt.show() if __name__ == \u0026#34;__main__\u0026#34;: y_2020=tongxun(r\u0026#39;C:\\Users\\14495\\Desktop\\通信记录\u0026#39;) y_2020.run() 笔记 xlrd是处理文档的库，xlrd.open_workbook()可以得到一个workbook对象(一个xls文件)，wordkbook.sheet_by_index()可以以索引的方式得到一个workbook内的sheet对象，使用worksheet.nrows方法得到一个由sheet内每行内容组成的list，使用for循环遍历提取即可\nreduce(function,list)方法可对list中每两个紧挨的元素进行function处理，把得到的结果再与下一位元素进行function处理，以此往复得到一个结果。具体为使用匿名函数lambda以及reduce方法进行求和计算。\nround(int,1)方法可得到一个保留一位小数的int数字，具体为求平均通话时长中保留一位小数。\nlist.insert(data,0)方法可在list的0位置上插入data元素，具体为将月份插入list中每一行的开头。\npandas.DataFrame(list_or_dir,index=month,colums=[ '月','总时长(秒)','次数','平均(秒/每次)' ])得到一个由list或dir为数据、索引为month、list每行对应元素为colums一致的dataframe数据对象。\n使用DataFrame对象的.plot(x='月',y=' 平均(秒/每次) ',kind='bar')得到以月份为x轴，以平均数为y轴的直方图。\n更新6/28 import os import xlrd import re from functools import reduce import pandas as pd import matplotlib.pyplot as plt import tkinter as tk from tkinter import filedialog def get_path(): win=tk.Tk() #filetypes = [(\u0026#39;xls files\u0026#39;,\u0026#39;.xls\u0026#39;)] answer = filedialog.askdirectory(parent=win,initialdir=os.getcwd(),title=\u0026#39;选择一个存放通话文件的文件夹(内容为.xls文件)\u0026#39;) print(answer) return answer def get_dirs(path):#返回一个由文件完整路径组成的列表 list_=[] for i in os.listdir(path): file_path = path + \u0026#39;\\\\\u0026#39; + i list_.append(file_path) return list_ def readxls(path):#从文件中返回一个sheet，输入为文件路径 workbook = xlrd.open_workbook(path) worksheet = workbook.sheet_by_index(0) return worksheet # name = worksheet.name # nrows = worksheet.nrows # ncols = worksheet.ncols def get_date(worksheet):#从sheet中获取通话时长列表 nrows = worksheet.nrows time = [] for i in range(nrows): if i == 0: pass else: time.append(worksheet.row_values(i)[3]) return time def get_intTime(time_list): time = [] for i in time_list: s = re.search(r\u0026#39;[0-9]+[分]\u0026#39;,i) if s == None: m = int(re.search(r\u0026#39;[0-9]+[秒]\u0026#39;,i).group().replace(\u0026#39;秒\u0026#39;,\u0026#39;\u0026#39;)) time.append(m) else: s_m = int(s.group().replace(\u0026#39;分\u0026#39;,\u0026#39;\u0026#39;)) m = 60*s_m + int(re.search(r\u0026#39;[0-9]+[秒]\u0026#39;,i).group().replace(\u0026#39;秒\u0026#39;,\u0026#39;\u0026#39;)) time.append(m) return time def get_SumNumAvg(time_all): time = [] time_every = [] for i in time_all: time.append(get_intTime(i)) for n in time: time_sum = reduce(lambda x,y:x+y,n) time_num = len(n) time_avg = round(time_sum / time_num,1) time_every.append([time_sum,time_num,time_avg]) return time_every def insert_month(SumNumAvg): c=1 for i in SumNumAvg: i.insert(0,c) c += 1 return SumNumAvg \u0026#39;\u0026#39;\u0026#39; def get_dateFrame(time_SumNumAvg): month = list(range(1,len(time_SumNumAvg)+1)) w = len(time_SumNumAvg[0]) h = len(time_SumNumAvg) t = [[0 for b in range(h)]for d in range(w)] count_2 = 0 for i in time_SumNumAvg: count_1 = 0 for n in i: t[count_1][count_2]=n count_1 += 1 count_2 += 1 df = pd.DataFrame(t,index=[\u0026#39;总时长(秒)\u0026#39;,\u0026#39;次数\u0026#39;,\u0026#39;平均(秒/每次)\u0026#39;],columns=month) return df \u0026#39;\u0026#39;\u0026#39; def get_dateFrame(time_SumNumAvg): month = list(range(0,len(time_SumNumAvg))) df = pd.DataFrame(time_SumNumAvg,index=month,columns=[\u0026#39;月\u0026#39;,\u0026#39;总时长(秒)\u0026#39;,\u0026#39;次数\u0026#39;,\u0026#39;平均(秒/每次)\u0026#39;]) return df def run(): path = get_path() time_all = [] for i in get_dirs(path): w=readxls(i) time_all.append(get_date(w)) time_SumNumAvg = insert_month(get_SumNumAvg(time_all)) data=get_dateFrame(time_SumNumAvg) while 1:#选择图表类型 input_ = int(input(\u0026#39;\\n输入你想要的图表(填对应数字)：\\n1：总时长\\n2：次数\\n3：平均数\\n\u0026#39;)) if input_ == 1: y_ = \u0026#39;总时长(秒)\u0026#39; break elif input_ == 2: y_ = \u0026#39;次数\u0026#39; break elif input_ == 3: y_ = \u0026#39;平均(秒/每次)\u0026#39; break else: print(\u0026#39;输入错误，请重新输入\u0026#39;) data.plot(x=\u0026#39;月\u0026#39;,y=y_,kind=\u0026#39;bar\u0026#39;) plt.show() if __name__ == \u0026#34;__main__\u0026#34;: run() 加入了TK用于文件对话框返回一个路径\n由于面向对象写的很烂，暂时先去掉了，之后打算弄个图形界面\n","permalink":"https://www.kutake.top/archives/python%E7%BB%83%E4%B9%A0%E9%80%9A%E8%AF%9D%E7%BB%9F%E8%AE%A1%E5%8F%8A%E7%AE%80%E6%98%93%E5%9B%BE%E8%A1%A8%E5%88%B6%E4%BD%9C/","summary":"\u003cp\u003e最开始的看到的python基础习题之一，现在顺便扩展一点，练习xlrd、pandas以及matplotlib的练习\u003c/p\u003e\n\u003ch2 id=\"数据来源\"\u003e数据来源\u003c/h2\u003e\n\u003cp\u003e来自于联通官网的通话数据下载，文件为某年某月的通话记录详情，格式为xls**[图片缺失: 2020/06/QQ截图20200627215145.png]**\u003c!-- broken-image: 2020/06/QQ截图20200627215145.png --\u003e\u003c/p\u003e","title":"实战：通话统计及简易图表制作"},{"content":"#2020/3/10 #设置一个屏蔽词文本，输入一段字符串，将其中的屏蔽词转换成* import re #参数为路径，返回整个屏蔽词文本 def get_f(path): f = open(path,\u0026#39;r\u0026#39;,encoding=\u0026#39;utf-8\u0026#39;) art=f.read() f.close() return art #参数为字符串，返回一个屏蔽词列表 def get_list(str): list_=[] word = re.findall(r\u0026#39;[^\\s]+\u0026#39;,str) for i in word: list_.append(i.lower()) return list_ #参数i为输入，b为屏蔽词列表 def pri(i,b): ww=[] ww.append(i) h=0 for n in b: if re.findall(n,ww[h]) != []: s=\u0026#39;*\u0026#39;*len(n) p = re.sub(n,s,ww[h]) h+=1 ww.append(p) if h != 0: #print(\u0026#39;共替换了%d个词\u0026#39;%h) return ww[h] else: return i #run if __name__ == \u0026#34;__main__\u0026#34;: path=r\u0026#39;E:\\vscode\\code\\ban.txt\u0026#39; b=get_list(get_f(path)) #print(b) i=str(input(\u0026#39;输入：\\n\u0026#39;)) print(pri(i,b)) ","permalink":"https://www.kutake.top/archives/python%E7%BB%83%E4%B9%A0%E9%A2%988%E6%96%87%E6%9C%AC%E8%AF%86%E5%88%AB%E5%B1%8F%E8%94%BD%E8%AF%8D%E8%BD%AC%E6%8D%A2/","summary":"\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e#2020/3/10\n#设置一个屏蔽词文本，输入一段字符串，将其中的屏蔽词转换成*\nimport re\n#参数为路径，返回整个屏蔽词文本\ndef get_f(path):\n    f = open(path,\u0026#39;r\u0026#39;,encoding=\u0026#39;utf-8\u0026#39;)\n    art=f.read()\n    f.close()\n    return art\n#参数为字符串，返回一个屏蔽词列表\ndef get_list(str):\n    list_=[]\n    word = re.findall(r\u0026#39;[^\\s]+\u0026#39;,str)\n    for i in word:\n        list_.append(i.lower())\n    return list_\n#参数i为输入，b为屏蔽词列表\ndef pri(i,b):\n    ww=[]\n    ww.append(i)\n    h=0\n    for n in b:\n        if re.findall(n,ww[h]) != []:\n            s=\u0026#39;*\u0026#39;*len(n)\n            p = re.sub(n,s,ww[h])\n            h+=1\n            ww.append(p)\n    if h != 0:\n        #print(\u0026#39;共替换了%d个词\u0026#39;%h)\n        return ww[h]\n    else:\n        return i\n#run\nif __name__ == \u0026#34;__main__\u0026#34;:\n    path=r\u0026#39;E:\\vscode\\code\\ban.txt\u0026#39;\n    b=get_list(get_f(path))\n    #print(b)\n    i=str(input(\u0026#39;输入：\\n\u0026#39;))\n    print(pri(i,b))\n\u003c/code\u003e\u003c/pre\u003e","title":"文本识别屏蔽词转换"},{"content":"#生成自定义数量由大小写字母与数字随机组成的号码/激活码，然后存入mysql数据库 import random import string import MySQLdb #获取号码 def GenKey(length): #length为号码长度 chars = string.ascii_letters + string.digits #string.ascii.letters是26个英文字母的大小写[a-z A-Z],string.digits为[0-9]相加作为字典 return \u0026#39;\u0026#39;.join([random.choice(chars) for i in range(length)]) #拿出chars内的一个字符并重复进行length次，将得到的字符与空字符串相加，得到长度为length的号码 #保存号码 def SaveKey(content): f = open(\u0026#39;Result Key.txt\u0026#39;, \u0026#39;a\u0026#39;) #打开Result Key.txt，a表示“打开一个文件用于追加。如果该文件已存在，文件指针将会放在文件的结尾。” f.write(content) #写入一个content，此处为号码 f.write(\u0026#39;\\n\u0026#39;) #换行 f.close() #关闭 #保存至mysql数据库 def save_sql(code_nu): host = (\u0026#34;localhost\u0026#34;) user = (\u0026#34;root\u0026#34;) pass_ = (\u0026#34;123456\u0026#34;) db = (\u0026#34;sqlforpy\u0026#34;) #建立连接 connect = MySQLdb.connect(host,user,pass_,db) #使用cursor()方法获取操作游标 （在操作游标中才可进行数据库操作 cursor = connect.cursor() #使用insert into进行插入数据 sql = \u0026#34;insert into code(code)VALUES(\u0026#39;%s\u0026#39;)\u0026#34; % code_nu #执行sql语句 cursor.execute(sql) #使用commit提交到数据库执行保存 connect.commit() #关闭连接 connect.close() if __name__ == \u0026#39;__main__\u0026#39;: length=int(input(\u0026#34;\\n请输入需要的号码个数：\u0026#34;)) for i in range(length): value = GenKey(20) print (value) #SaveKey(value) save_sql(value) ","permalink":"https://www.kutake.top/archives/python%E7%BB%83%E4%B9%A0%E9%A2%982%E9%9A%8F%E6%9C%BA%E7%A0%81%E7%94%9F%E6%88%90/","summary":"\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e#生成自定义数量由大小写字母与数字随机组成的号码/激活码，然后存入mysql数据库\nimport random\nimport string\nimport MySQLdb\n#获取号码\ndef GenKey(length):     #length为号码长度\n    chars = string.ascii_letters + string.digits    #string.ascii.letters是26个英文字母的大小写[a-z A-Z],string.digits为[0-9]相加作为字典\n    return \u0026#39;\u0026#39;.join([random.choice(chars) for i in range(length)])\n    #拿出chars内的一个字符并重复进行length次，将得到的字符与空字符串相加，得到长度为length的号码\n#保存号码\ndef SaveKey(content):\n    f = open(\u0026#39;Result Key.txt\u0026#39;, \u0026#39;a\u0026#39;)\n    #打开Result Key.txt，a表示“打开一个文件用于追加。如果该文件已存在，文件指针将会放在文件的结尾。”\n\n    f.write(content)\n    #写入一个content，此处为号码\n\n    f.write(\u0026#39;\\n\u0026#39;)\n    #换行\n    f.close()\n    #关闭\n#保存至mysql数据库\ndef save_sql(code_nu):\n    host = (\u0026#34;localhost\u0026#34;)\n    user = (\u0026#34;root\u0026#34;)\n    pass_ = (\u0026#34;123456\u0026#34;)\n    db = (\u0026#34;sqlforpy\u0026#34;)\n    #建立连接\n    connect = MySQLdb.connect(host,user,pass_,db)\n    #使用cursor()方法获取操作游标 （在操作游标中才可进行数据库操作\n    cursor = connect.cursor()\n    #使用insert into进行插入数据\n    sql = \u0026#34;insert into code(code)VALUES(\u0026#39;%s\u0026#39;)\u0026#34; % code_nu\n    #执行sql语句\n    cursor.execute(sql)\n    #使用commit提交到数据库执行保存\n    connect.commit()\n    #关闭连接\n    connect.close()\nif __name__ == \u0026#39;__main__\u0026#39;:\n    length=int(input(\u0026#34;\\n请输入需要的号码个数：\u0026#34;))\n    for i in range(length):\n        value = GenKey(20)\n        print (value)\n        #SaveKey(value)\n        save_sql(value)\n\u003c/code\u003e\u003c/pre\u003e","title":"随机码生成"},{"content":"#读取一个纯英文的文本文件，统计其中每个单词的出现次数，并将次数和其单词本身存入mysql数据库 import re import MySQLdb #读取文件信息 filename=\u0026#34;word.txt\u0026#34; #使用open方法操作文本文件，r表示以只读方式打开文件。文件的指针将会放在文件的开头 f=open(filename,\u0026#39;r\u0026#39;) #使用f.read方法返回整个文本文件给article， artical=f.read() #使用f.close关闭文件 f.close() #将文本中的所有英文单词筛选出来 去掉标点和其他文本符号 并且将单词都小写保存在数组List中 List=[] #\u0026#39;[a-zA-Z]+\u0026#39;正则表达式，表示大小写a-z出现一次或连续出现，这里表达一个单词 #使用re.findall方法，从article中获取单词，作为list存入word中 word=re.findall(\u0026#39;[a-zA-Z]+\u0026#39;,artical) for i in word: List.append(i.lower()) #把单词与出现的次数存入数据库（我也不知道为什么要存进去:) def save_sql(word,numbers): host = (\u0026#34;localhost\u0026#34;) user = (\u0026#34;root\u0026#34;) pass_ = (\u0026#34;123456\u0026#34;) db = (\u0026#34;sqlforpy\u0026#34;) #建立连接 connect = MySQLdb.connect(host,user,pass_,db) #使用cursor()方法获取操作游标 （在操作游标中才可进行数据库操作 cursor = connect.cursor() #使用insert into进行插入数据 sql = \u0026#34;insert into count_word(word,numbers)VALUES(\u0026#39;%s\u0026#39;,\u0026#39;%d\u0026#39;)\u0026#34; % (word,numbers) #执行sql语句 try: cursor.execute(sql) #使用commit提交到数据库执行保存 connect.commit() except: #发生错误时回滚 connect.rollback() #关闭连接 connect.close() #统计单词数目 print(\u0026#34;%s总共有%d个单词\u0026#34; %(filename,List.__len__())) #统计各个单词出现的频率，并且按照字典顺序输出并将内容写入文件中 #f=open(\u0026#34;result.txt\u0026#34;,\u0026#39;w\u0026#39;) List=sorted(List) i=0 for i in range(0,List.__len__()-1): #使用count方法获得字符串出现的次数 count=List.count(List[i]) if(List[i]!=List[i+1]): #print(List[i]+\u0026#34; \u0026#34;+str(count)+\u0026#34;\\n\u0026#34;) #f.write(List[i]+\u0026#34; \u0026#34;+str(count)+\u0026#34;\\n\u0026#34;) save_sql(List[i],count) ","permalink":"https://www.kutake.top/archives/python%E7%BB%83%E4%B9%A0%E9%A2%983%E7%BB%9F%E8%AE%A1%E8%8B%B1%E6%96%87%E5%8D%95%E8%AF%8D%E8%AF%8D%E9%A2%91/","summary":"\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e#读取一个纯英文的文本文件，统计其中每个单词的出现次数，并将次数和其单词本身存入mysql数据库\nimport  re\nimport MySQLdb\n#读取文件信息\nfilename=\u0026#34;word.txt\u0026#34;\n#使用open方法操作文本文件，r表示以只读方式打开文件。文件的指针将会放在文件的开头\nf=open(filename,\u0026#39;r\u0026#39;)\n#使用f.read方法返回整个文本文件给article，\nartical=f.read()\n#使用f.close关闭文件\nf.close()\n#将文本中的所有英文单词筛选出来 去掉标点和其他文本符号  并且将单词都小写保存在数组List中\nList=[]\n#\u0026#39;[a-zA-Z]+\u0026#39;正则表达式，表示大小写a-z出现一次或连续出现，这里表达一个单词\n#使用re.findall方法，从article中获取单词，作为list存入word中\nword=re.findall(\u0026#39;[a-zA-Z]+\u0026#39;,artical)\nfor i in word:\n    List.append(i.lower())\n#把单词与出现的次数存入数据库（我也不知道为什么要存进去:)\ndef save_sql(word,numbers):\n    host = (\u0026#34;localhost\u0026#34;)\n    user = (\u0026#34;root\u0026#34;)\n    pass_ = (\u0026#34;123456\u0026#34;)\n    db = (\u0026#34;sqlforpy\u0026#34;)\n    #建立连接\n    connect = MySQLdb.connect(host,user,pass_,db)\n    #使用cursor()方法获取操作游标 （在操作游标中才可进行数据库操作\n    cursor = connect.cursor()\n    #使用insert into进行插入数据\n    sql = \u0026#34;insert into count_word(word,numbers)VALUES(\u0026#39;%s\u0026#39;,\u0026#39;%d\u0026#39;)\u0026#34; % (word,numbers)\n    #执行sql语句\n    try:\n        cursor.execute(sql)\n        #使用commit提交到数据库执行保存\n        connect.commit()\n    except:\n        #发生错误时回滚\n        connect.rollback()\n    #关闭连接\n    connect.close()\n#统计单词数目\nprint(\u0026#34;%s总共有%d个单词\u0026#34; %(filename,List.__len__()))\n#统计各个单词出现的频率，并且按照字典顺序输出并将内容写入文件中\n#f=open(\u0026#34;result.txt\u0026#34;,\u0026#39;w\u0026#39;)\nList=sorted(List)\ni=0\nfor i in range(0,List.__len__()-1):\n    #使用count方法获得字符串出现的次数\n    count=List.count(List[i])\n    if(List[i]!=List[i+1]):\n        #print(List[i]+\u0026#34;  \u0026#34;+str(count)+\u0026#34;\\n\u0026#34;)\n        #f.write(List[i]+\u0026#34;  \u0026#34;+str(count)+\u0026#34;\\n\u0026#34;)\n        save_sql(List[i],count)\n\u003c/code\u003e\u003c/pre\u003e","title":"统计英文单词词频"},{"content":"#在一个文件夹内存放着几个纯英文文本，统计其中出现次数最多的单词 import os import re f_dir=\u0026#39;E:/vscode/code/testwork/txtfile\u0026#39; f_list=os.listdir(f_dir) List=[] dict={} #传入文件名，返回文本文件路径 def get_f(name): return f_dir+\u0026#39;/\u0026#39;+name #传入文件路径，返回文件内文本（字符串 def open_f(fdir): f = open(fdir,\u0026#39;r\u0026#39;,encoding=\u0026#39;UTF-8\u0026#39;) artical=f.read() f.close() return artical #传入字符串，找出其中的单词统一小写，并以list形式添加到List中 def get_w(file): word=re.findall(\u0026#39;[a-zA-Z]+\u0026#39;,file) for i in word: List.append(i.lower()) #传入一个list，将其排序后，提取每个单词与其出现的次数，以字典形式存放于dict def get_c(w_list): s_list=sorted(w_list) i = 0 for i in range(0,s_list.__len__()-1): count=s_list.count(s_list[i]) if(s_list[i]!=s_list[i+1]): dict[s_list[i]]=count if __name__ == \u0026#34;__main__\u0026#34;: art=\u0026#39;\u0026#39; for i in f_list: f_path=get_f(i) art=art+open_f(f_path) get_w(art) get_c(List) #用max方法找出字典内values最大的值 highest=max(dict.values()) print(\u0026#39;所有文件共有%d个单词\u0026#39;%List.__len__()) print(\u0026#39;出现最多次的单词：\u0026#39;) #用最大值去提取与其相匹配的key，得到一个或多个出现次数最高的单词 print([k for k,v in dict.items() if v == highest]) print(\u0026#39;总共出现了%d次\u0026#39;%highest) ","permalink":"https://www.kutake.top/archives/python%E7%BB%83%E4%B9%A0%E9%A2%985%E7%BB%9F%E8%AE%A1%E6%96%87%E4%BB%B6%E5%A4%B9%E5%86%85%E6%89%80%E6%9C%89%E6%96%87%E4%BB%B6%E7%9A%84%E8%AF%8D%E9%A2%91/","summary":"\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e#在一个文件夹内存放着几个纯英文文本，统计其中出现次数最多的单词\nimport os\nimport  re\nf_dir=\u0026#39;E:/vscode/code/testwork/txtfile\u0026#39;\nf_list=os.listdir(f_dir)\nList=[]\ndict={}\n#传入文件名，返回文本文件路径\ndef get_f(name):\n    return f_dir+\u0026#39;/\u0026#39;+name\n#传入文件路径，返回文件内文本（字符串\ndef open_f(fdir):\n    f = open(fdir,\u0026#39;r\u0026#39;,encoding=\u0026#39;UTF-8\u0026#39;)\n    artical=f.read()\n    f.close()\n    return artical\n#传入字符串，找出其中的单词统一小写，并以list形式添加到List中\ndef get_w(file):\n    word=re.findall(\u0026#39;[a-zA-Z]+\u0026#39;,file)\n    for i in word:\n        List.append(i.lower())\n#传入一个list，将其排序后，提取每个单词与其出现的次数，以字典形式存放于dict\ndef get_c(w_list):\n    s_list=sorted(w_list)\n    i = 0\n    for i in range(0,s_list.__len__()-1):\n        count=s_list.count(s_list[i])\n        if(s_list[i]!=s_list[i+1]):\n            dict[s_list[i]]=count\nif __name__ == \u0026#34;__main__\u0026#34;:\n    art=\u0026#39;\u0026#39;\n    for i in f_list:\n        f_path=get_f(i)\n        art=art+open_f(f_path)\n\n    get_w(art)\n    get_c(List)\n    #用max方法找出字典内values最大的值\n    highest=max(dict.values())\n    print(\u0026#39;所有文件共有%d个单词\u0026#39;%List.__len__())\n    print(\u0026#39;出现最多次的单词：\u0026#39;)\n    #用最大值去提取与其相匹配的key，得到一个或多个出现次数最高的单词\n    print([k for k,v in dict.items() if v == highest])\n    print(\u0026#39;总共出现了%d次\u0026#39;%highest)\n\u003c/code\u003e\u003c/pre\u003e","title":"统计文件夹内所有文件的词频"},{"content":"#有一个文件夹内存放着代码文件，统计所有文件的代码行，注释行和空行的总数 import os def j_anno(line): if line.strip().startswith(\u0026#34;#\u0026#34;): return True else: return False def j_annos(line): if line.strip().startswith(\u0026#34;\u0026#39;\u0026#39;\u0026#39;\u0026#34;) or line.strip().startswith(\u0026#39;\u0026#34;\u0026#34;\u0026#34;\u0026#39;): return True else: return False path=\u0026#39;E:/vscode/code/testwork\u0026#39; code_line=0 comm_line=0 space_line=0 for root,dirs,files in os.walk(path): for item in files: f_path=os.path.join(root,item) f_exten=os.path.splitext(f_path)[1] if f_exten == \u0026#39;.py\u0026#39;: with open(f_path,\u0026#39;r\u0026#39;,encoding=\u0026#39;utf-8\u0026#39;) as fp: #print(\u0026#39;key:\u0026#39;,key) #print(\u0026#39;value:\u0026#39;,value) while True: line=fp.readline() if not line: break elif j_anno(line): comm_line +=1 elif j_annos(line): while True: line=fp.readline() if j_annos(line): break else: comm_line +=1 elif line.strip(): code_line+=1 else: space_line+=1 #test print(\u0026#39;目录：%s总共有:\\n%d行代码\\n%d行注释\\n%d个空行\u0026#39;%(path,code_line,comm_line,space_line)) ","permalink":"https://www.kutake.top/archives/python%E7%BB%83%E4%B9%A0%E9%A2%986%E7%BB%9F%E8%AE%A1%E6%96%87%E4%BB%B6%E5%A4%B9%E5%86%85%E6%89%80%E6%9C%89%E4%BB%A3%E7%A0%81%E6%96%87%E4%BB%B6%E7%9A%84%E4%BB%A3%E7%A0%81%E8%A1%8C/","summary":"\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e#有一个文件夹内存放着代码文件，统计所有文件的代码行，注释行和空行的总数\nimport os\ndef j_anno(line):\n    if line.strip().startswith(\u0026#34;#\u0026#34;):\n        return True\n    else:\n        return False\ndef j_annos(line):\n    if line.strip().startswith(\u0026#34;\u0026#39;\u0026#39;\u0026#39;\u0026#34;) or line.strip().startswith(\u0026#39;\u0026#34;\u0026#34;\u0026#34;\u0026#39;):\n        return True\n    else:\n        return False\npath=\u0026#39;E:/vscode/code/testwork\u0026#39;\ncode_line=0\ncomm_line=0\nspace_line=0\nfor root,dirs,files in os.walk(path):\n    for item in files:\n        f_path=os.path.join(root,item)\n        f_exten=os.path.splitext(f_path)[1]\n        if f_exten == \u0026#39;.py\u0026#39;:\n                with open(f_path,\u0026#39;r\u0026#39;,encoding=\u0026#39;utf-8\u0026#39;) as fp:\n                    #print(\u0026#39;key:\u0026#39;,key)\n                    #print(\u0026#39;value:\u0026#39;,value)\n                    while True:\n                        line=fp.readline()\n                        if not line:\n                            break\n                        elif j_anno(line):\n                            comm_line +=1\n\n                        elif j_annos(line):\n                            while True:\n                                line=fp.readline()\n                                if j_annos(line):\n                                    break\n                                else:\n                                    comm_line +=1\n                        elif line.strip():\n                            code_line+=1\n                        else:\n                            space_line+=1\n#test\nprint(\u0026#39;目录：%s总共有:\\n%d行代码\\n%d行注释\\n%d个空行\u0026#39;%(path,code_line,comm_line,space_line))\n\u003c/code\u003e\u003c/pre\u003e","title":"统计文件夹内所有代码文件的代码行、注释行和空行"},{"content":"#加入随机salt的哈希运算验证 import random from hashlib import sha256 from hmac import HMAC #意义在于，随机halt哈希混淆运算后的数据极难被反推出密码原文，彩虹字典的暴力破解难度极大 #参数r_pass为密码原文，salt可选，返回一个带slat与加密后的字符串 def get_pass(r_pass,salt=None): if salt == None: salt=sha256(str(random.random()).encode(\u0026#39;utf-8\u0026#39;)).hexdigest()[-8:] #先生成一个HMAC()对象，然后再调用digest()生成hash之后的结果 password=HMAC(salt.encode(\u0026#39;utf-8\u0026#39;),r_pass.encode(\u0026#39;utf-8\u0026#39;),sha256).hexdigest() return (\u0026#39;%s$%s\u0026#39;%(salt,password)) #参数r_pass为再次输入的原文密码，password为第一次哈希后的字符串 def check_pass(r_pass,password): salt=password.split(\u0026#39;$\u0026#39;)[0] return password == get_pass(r_pass,salt) #run if __name__ == \u0026#34;__main__\u0026#34;: password=input(\u0026#39;请输入密码：\\n\u0026#39;) p=get_pass(password) while True: pass_=input(\u0026#39;请再次输入密码：\\n\u0026#39;) if check_pass(pass_,p): print(\u0026#39;密码正确\u0026#39;) break else: print(\u0026#39;密码错误\u0026#39;) ","permalink":"https://www.kutake.top/archives/python%E7%BB%83%E4%B9%A0%E9%A2%9810%E5%93%88%E5%B8%8C%E5%8A%A0%E5%AF%86%E9%AA%8C%E8%AF%81/","summary":"\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e#加入随机salt的哈希运算验证\nimport random\nfrom hashlib import sha256\nfrom hmac import HMAC\n#意义在于，随机halt哈希混淆运算后的数据极难被反推出密码原文，彩虹字典的暴力破解难度极大\n#参数r_pass为密码原文，salt可选，返回一个带slat与加密后的字符串\ndef get_pass(r_pass,salt=None):\n    if salt == None:\n        salt=sha256(str(random.random()).encode(\u0026#39;utf-8\u0026#39;)).hexdigest()[-8:]\n    #先生成一个HMAC()对象，然后再调用digest()生成hash之后的结果\n    password=HMAC(salt.encode(\u0026#39;utf-8\u0026#39;),r_pass.encode(\u0026#39;utf-8\u0026#39;),sha256).hexdigest()\n    return (\u0026#39;%s$%s\u0026#39;%(salt,password))\n#参数r_pass为再次输入的原文密码，password为第一次哈希后的字符串\ndef check_pass(r_pass,password):\n    salt=password.split(\u0026#39;$\u0026#39;)[0]\n    return password == get_pass(r_pass,salt)\n#run\nif __name__ == \u0026#34;__main__\u0026#34;:\n    password=input(\u0026#39;请输入密码：\\n\u0026#39;)\n    p=get_pass(password)\n    while True:\n        pass_=input(\u0026#39;请再次输入密码：\\n\u0026#39;)\n        if check_pass(pass_,p):\n            print(\u0026#39;密码正确\u0026#39;)\n            break\n        else:\n            print(\u0026#39;密码错误\u0026#39;)\n\u003c/code\u003e\u003c/pre\u003e","title":"哈希加密验证尝试"},{"content":"from PIL import Image,ImageFont,ImageDraw import sys reload(sys) #必须要reload sys.setdefaultencoding(\u0026#39;utf-8\u0026#39;) #字符编码改为utf8 headPath=r\u0026#34;\u0026#34; #头像图片路径 output=r\u0026#34;\u0026#34; #处理后输出路径 fontPath=r\u0026#34;\u0026#34; #字体路径 headFile=\u0026#34;\u0026#34; #头像文件 outFile=\u0026#34;\u0026#34; #输出文件 #打开图片建立画布 image=Image.open(headPath+headFile,r) draw=ImageDraw.Draw(image) #由图片大小确定字体大小 fontsize=min(image.size)/4 #增加文字 fontobj=ImageFont.truetype(font=fontPath+\u0026#34;AdobeHeisistd-Regular.otf\u0026#34;,size=fontsize,index=0,encoding=\u0026#34;,filename=None)# \u0026#34;\u0026#34;\u0026#34; \u0026#34;\u0026#34;\u0026#34; from PIL import Image,ImageFont,ImageDraw#从PIL库导入所需模块 import sys reload(sys) #必须要reload sys.setdefaultencoding(\u0026#39;utf-8\u0026#39;)#字符编码改为utf8 headPath=r\u0026#34;E:\\\\vscode\\\\code\\\\testwork\\\\testFile\\\\\u0026#34; #头像图片路径 outputPath=r\u0026#34;C:E:\\\\vscode\\\\code\\\\testwork\\\\testFile\\\\output\\\\\u0026#34; #处理后输出路径 fontPath=r\u0026#34;C:\\\\Windows\\\\Fonts\\\\\u0026#34; #字体路径 headFile=\u0026#34;head.jpg\u0026#34; #头像文件 outFile=\u0026#34;output.jpg\u0026#34; #输出文件 #打开图片，建立画布 image=Image.open(headPath+headFile,\u0026#39;r\u0026#39;) draw=ImageDraw.Draw(image) #由图片大小确定字体大小 fontsize=min(image.size)/4 #增加文字 fontobj=ImageFont.truetype(font=fontPath+\u0026#34;AdobeHeitiStd-Regular.otf\u0026#34;,size=fontsize,index=0,encoding=\u0026#39;\u0026#39;,filename=None)#实例字体对象 draw.text((image.size[0]-fontsize,0),text=\u0026#34;5\u0026#34;,fill=(255,0,0),font=fontobj,anchor=None)#用draw对象的text()方法添加文字 image.save(outputPath+outFile)#保存图片 \u0026#34;\u0026#34;\u0026#34; #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2016/12/12 上午10:13 # @Author : hukezhu # @Site : # @File : 0000:添加未读消息.py # @Software: PyCharm from PIL import Image, ImageDraw, ImageFont #创建图片对象 headImage = Image.open(\u0026#39;E:/vscode/code/testwork/testFile/head.jpg\u0026#39;) #获取图片对象的宽高 w,h = headImage.size #创建字体对象 font = ImageFont.truetype(\u0026#39;C:/Windows/Fonts/Arial.ttf\u0026#39;,int(h/4)) #绘制圆形 ImageDraw.Draw(headImage).pieslice([(w/3*2,0),(w,h/3)],0,360,fill=\u0026#39;red\u0026#39;) ImageDraw.Draw(headImage).text((w*0.76,h*0.02),\u0026#39;3\u0026#39;,font = font, fill = \u0026#39;white\u0026#39;) #展示绘制结果(使用系统默认的图片浏览器) #headImage.show() #保存绘制结果 headImage.save(\u0026#39;E:/vscode/code/testwork/testFile/wode.jpg\u0026#39;) ","permalink":"https://www.kutake.top/archives/python%E7%BB%83%E4%B9%A0%E9%A2%981%E6%9C%AA%E8%AF%BB%E6%B6%88%E6%81%AF%E5%9B%BE%E6%A0%87%E6%A8%A1%E6%8B%9F/","summary":"\u003cpre tabindex=\"0\"\u003e\u003ccode\u003efrom PIL import Image,ImageFont,ImageDraw\nimport sys\nreload(sys) #必须要reload\nsys.setdefaultencoding(\u0026#39;utf-8\u0026#39;) #字符编码改为utf8\nheadPath=r\u0026#34;\u0026#34;\n#头像图片路径\noutput=r\u0026#34;\u0026#34;\n#处理后输出路径\nfontPath=r\u0026#34;\u0026#34;\n#字体路径\nheadFile=\u0026#34;\u0026#34;     #头像文件\noutFile=\u0026#34;\u0026#34;      #输出文件\n#打开图片建立画布\nimage=Image.open(headPath+headFile,r)\ndraw=ImageDraw.Draw(image)\n#由图片大小确定字体大小\nfontsize=min(image.size)/4\n#增加文字\nfontobj=ImageFont.truetype(font=fontPath+\u0026#34;AdobeHeisistd-Regular.otf\u0026#34;,size=fontsize,index=0,encoding=\u0026#34;,filename=None)#\n\u0026#34;\u0026#34;\u0026#34;\n\u0026#34;\u0026#34;\u0026#34;\nfrom PIL import Image,ImageFont,ImageDraw#从PIL库导入所需模块\nimport sys\nreload(sys)   #必须要reload\nsys.setdefaultencoding(\u0026#39;utf-8\u0026#39;)#字符编码改为utf8\nheadPath=r\u0026#34;E:\\\\vscode\\\\code\\\\testwork\\\\testFile\\\\\u0026#34;\n#头像图片路径\noutputPath=r\u0026#34;C:E:\\\\vscode\\\\code\\\\testwork\\\\testFile\\\\output\\\\\u0026#34;\n#处理后输出路径\nfontPath=r\u0026#34;C:\\\\Windows\\\\Fonts\\\\\u0026#34;\n#字体路径\nheadFile=\u0026#34;head.jpg\u0026#34;           #头像文件\noutFile=\u0026#34;output.jpg\u0026#34;          #输出文件\n#打开图片，建立画布\nimage=Image.open(headPath+headFile,\u0026#39;r\u0026#39;)\ndraw=ImageDraw.Draw(image)\n#由图片大小确定字体大小\nfontsize=min(image.size)/4\n#增加文字\nfontobj=ImageFont.truetype(font=fontPath+\u0026#34;AdobeHeitiStd-Regular.otf\u0026#34;,size=fontsize,index=0,encoding=\u0026#39;\u0026#39;,filename=None)#实例字体对象\ndraw.text((image.size[0]-fontsize,0),text=\u0026#34;5\u0026#34;,fill=(255,0,0),font=fontobj,anchor=None)#用draw对象的text()方法添加文字\nimage.save(outputPath+outFile)#保存图片\n\u0026#34;\u0026#34;\u0026#34;\n#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n# @Time    : 2016/12/12 上午10:13\n# @Author  : hukezhu\n# @Site    :\n# @File    : 0000:添加未读消息.py\n# @Software: PyCharm\nfrom PIL import Image, ImageDraw, ImageFont\n#创建图片对象\nheadImage = Image.open(\u0026#39;E:/vscode/code/testwork/testFile/head.jpg\u0026#39;)\n#获取图片对象的宽高\nw,h = headImage.size\n#创建字体对象\nfont = ImageFont.truetype(\u0026#39;C:/Windows/Fonts/Arial.ttf\u0026#39;,int(h/4))\n#绘制圆形\nImageDraw.Draw(headImage).pieslice([(w/3*2,0),(w,h/3)],0,360,fill=\u0026#39;red\u0026#39;)\nImageDraw.Draw(headImage).text((w*0.76,h*0.02),\u0026#39;3\u0026#39;,font = font, fill = \u0026#39;white\u0026#39;)\n#展示绘制结果(使用系统默认的图片浏览器)\n#headImage.show()\n#保存绘制结果\nheadImage.save(\u0026#39;E:/vscode/code/testwork/testFile/wode.jpg\u0026#39;)\n\u003c/code\u003e\u003c/pre\u003e","title":"未读消息图标模拟"},{"content":"SQL简介 SQL = Structured Query Language\n总的来说，SQL语言定义了这么几种操作数据库的能力：\nDDL：Data Definition Language DDL允许用户定义数据，也就是创建表、删除表、修改表结构这些操作。通常，DDL由数据库管理员执行。 DML：Data Manipulation Language DML为用户提供添加、删除、更新数据的能力，这些是应用程序对数据库的日常操作。 DQL：Data Query Language DQL允许用户查询数据，这也是通常最频繁的数据库日常操作 数据库类别 关系型数据库(SQL) 非关系型数据库(NoSQL) - MongoDB, Cassandra, Dynamo 关系模型 主键 主键是关系表中记录的唯一标识。选取主键的一个基本原则是：不使用任何业务相关的字段作为主键。\nALTER TABLE \u0026lt;student\u0026gt; ADD CONSTRAINT \u0026lt;pk_id\u0026gt; PRIMARY KEY (\u0026lt;id\u0026gt;); 外键 外键是用来关联 2 个表结构的，表直接的约束分为以下 3 种：\n一对一 一对多 - 外键建立在‘多’的表中(即从表) 多对多 - 需要建立中间表 创建外键 ALTER TABLE \u0026lt;student\u0026gt; ADD CONSTRAINT \u0026lt;fk_class_id\u0026gt; FOREIGN KEY (\u0026lt;class_id\u0026gt;) REFERENCES classes (\u0026lt;id\u0026gt;); 删除外键 ALTER TABLE \u0026lt;students\u0026gt; DROP FOREIGN KEY \u0026lt;fk_class_id\u0026gt;; 索引 根据业务要求，像身份号、手机号、邮箱地址等，它们具有业务含义不易作为主键，但是又具有唯一性：即不能出现两条记录存储了同一个身份证号。这个时候，就可以给该列添加一个唯一约束。\nALTER TABLE \u0026lt;students\u0026gt; ADD CONSTRAINT \u0026lt;uni_name\u0026gt; UNIQUE (\u0026lt;name\u0026gt;); SQL 查询 不带 FROM 子句的 SELECT 语句个有用的用途，就是用来判断当前到数据库的连接是否有效。许多检测工具会执行一条 SELECT 1; 来测试数据库连接。\n基本查询 - SELECT * FROM \u0026lt;students\u0026gt; 条件查询 - SELECT * FROM \u0026lt;students\u0026gt; WHERE id = 1 投影查询 - SELECT id, name FROM \u0026lt;students\u0026gt; 排序 - SELECT * FROM \u0026lt;students\u0026gt; ORDER BY id DESC NOTE: OREDER BY 要在 WHERE 条件后 分页查询 - SELECT * FROM \u0026lt;students\u0026gt; LIMIT \u0026lt;10\u0026gt; OFFSET \u0026lt;0\u0026gt; pageSize - LIMIT pageIndex - OFFSET=pageSize * (pageIndex - 1), pageIndex 从 1 开始，而 OFFSET 从 0 开始。 聚合查询 聚合函数：COUNT(), SUM(), AVG(), MAX(), MIN() 通过 GROUP BY 进行分组 SELECT \u0026lt;class_id\u0026gt;, \u0026lt;gender\u0026gt;, COUNT(*) num FROM \u0026lt;students\u0026gt; GROUP BY \u0026lt;class_id\u0026gt;, \u0026lt;gender\u0026gt;; 多表查询 - SELECT * FROM \u0026lt;students\u0026gt;, \u0026lt;classes\u0026gt;; 获得的集合为 A 表行数 * B 表行数 连接查询 内连接(INNER JOIN) 外连接(OUTER JOIN) 左连接(LEFT OUTER JOIN) 右连接(RIGHT OUTER JOIN) 全连接(FULL OUTER JOIN) INNER JOIN LEFT OUTER JOIN RIGHT OUTER JOIN FULL OUTER JOIN 两张表都存在的记录 左表存在的记录 右表存在的记录 左右表都存在的记录 [图片缺失: 2020/02/INNER_JOIN.png] [图片缺失: 2020/02/LEFT_OUTER_JOIN.png] [图片缺失: 2020/02/RIGHT_OUTER_JOIN.png] [图片缺失: 2020/02/FULL_OUTER_JOIN.png] SQL 修改 关系数据库的基本操作就是增删改查，即 CRUD：Create、Retrieve、Update、Delete。\n对于增、删、改，对应的SQL语句分别是：\nINSERT：插入新记录 - INSERT INTO \u0026lt;表名\u0026gt; (字段1, 字段2, ...) VALUES (值1, 值2, ...); UPDATE：更新已有记录 - UPDATE \u0026lt;表名\u0026gt; SET 字段1=值1, 字段2=值2, ... WHERE ...; DELETE：删除已有记录 - DELETE FROM \u0026lt;表名\u0026gt; WHERE ...; 常用 SQL 语句 数据库 + 表操作 SQL 语句 SQL 语句 含义 SHOW DATABASES 列出所有数据库 CREATE DATABASE \u0026lt;database_name\u0026gt; 创建数据库 DROP DATABASE \u0026lt;database_name\u0026gt; 删除数据库 USE \u0026lt;database_name\u0026gt; 切换当前数据库 SHOW TABLES 列出所有表 DESC \u0026lt;table_name\u0026gt; 查看表结构 SHOW CREATE TABLE \u0026lt;table_name\u0026gt; 查看创建表的SQL语句 DROP TABLE \u0026lt;table_name\u0026gt; 删除表 ALTER TABLE \u0026lt;table_name\u0026gt; ADD COLUMN \u0026lt;column_name\u0026gt; VARCHAR(10) NOT NULL 新增列 ALTER TABLE \u0026lt;table_name\u0026gt; CHANGE COLUMN \u0026lt;column_name\u0026gt; VARCHAR(20) NOT NULL 修改列属性 ALTER TABLE \u0026lt;table_name\u0026gt; DROP COLUMN \u0026lt;column_name\u0026gt; 删除列 EXIT 退出 MYSQL 连接 实用 SQL 语句 插入或替换 REPLACE INTO \u0026lt;students\u0026gt; (\u0026lt;id\u0026gt;, \u0026lt;class_id\u0026gt;, \u0026lt;...\u0026gt;) VALUES(\u0026lt;1\u0026gt;, \u0026lt;1\u0026gt;, \u0026lt;...\u0026gt;) 若 id=1 记录不存在，插入新纪录； 若 id=1 记录存在，当前 id=1 记录被删除，然后再插入新纪录。 插入或更新 INSERT INTO \u0026lt;students\u0026gt; (\u0026lt;id\u0026gt;, \u0026lt;class_id\u0026gt;, \u0026lt;name\u0026gt;, \u0026lt;...\u0026gt;) VALUES(\u0026lt;1\u0026gt;, \u0026lt;1\u0026gt;, \u0026lt;'xiao_min'\u0026gt;, \u0026lt;...\u0026gt;) ON DUPLICATE KEY UPDATE \u0026lt;name\u0026gt;=\u0026lt;'xiao_min'\u0026gt;, \u0026lt;...\u0026gt;; 若 id=1 记录不存在，插入新纪录； 若 id=1 记录存在，当前 id=1 记录被更新，更新字段有 UPDATE 指定。 插入或忽略 INSERT IGNORE INTO \u0026lt;students\u0026gt; (\u0026lt;id\u0026gt;, \u0026lt;class_id\u0026gt;, \u0026lt;...\u0026gt;) VALUES(\u0026lt;1\u0026gt;, \u0026lt;1\u0026gt;, \u0026lt;...\u0026gt;); 若 id=1记录不存在，INSERT 语句将插入新记录，否则，不执行任何操作。 快照 CREATE TABLE \u0026lt;students_of_class1\u0026gt; SELECT * FROM \u0026lt;students\u0026gt; WHERE \u0026lt;class_id\u0026gt;=\u0026lt;1\u0026gt;; 通过查询集合创建新表 事务操作 多条语句作为一个整体进行操作的功能，被称为数据库事务。数据库事务可以确保该事务范围内的所有操作都可以全部成功或者全部失败。如果事务失败，那么效果就和没有执行这些 SQL 一样，不会对数据库数据有任何改动。\nBEGIN 开启一个事务 COMMIT 提交一个事务 ROLLBACK 回滚事务 隔离级别 脏读 DIRTY READ： 查询同一记录，2 次结果不一样，读到还未 commit 的数据结果； 不可重复读 NON REPEATABLE READ： 查询同一记录，2 次结果不一样，读到的结果在另一事务 commit/rollback 之间； 幻读 PHANTOM READ：查询同一记录，2 次结果是一样，但是会出现 事务 A 新增的列，事务 B 读不到(SELECT)，但是却可以更新(UPDATE)，更新后才可以读到。 ISOLATION LEVEL DIRTY READ NON REPEATABLE READ PHANTOM READ READ UNCOMMITTED YES YES YES READ COMMITTED - YES YES Repeatable Read - - YES Serializable - - - Serializable 虽然隔离级别最高，但是效率会大大下降。\nhttps://github.com/v-tawe/StudyNotes/blob/master/SQL/sql_study_notes.md https://www.liaoxuefeng.com/wiki/1177760294764384\n","permalink":"https://www.kutake.top/archives/sql%E7%AC%94%E8%AE%B0/","summary":"\u003ch2 id=\"sql简介\"\u003e\u003cstrong\u003eSQL简介\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eSQL = Structured Query Language\u003c/p\u003e\n\u003cp\u003e总的来说，SQL语言定义了这么几种操作数据库的能力：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eDDL：Data Definition Language\n\u003cul\u003e\n\u003cli\u003eDDL允许用户定义数据，也就是创建表、删除表、修改表结构这些操作。通常，DDL由数据库管理员执行。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eDML：Data Manipulation Language\n\u003cul\u003e\n\u003cli\u003eDML为用户提供添加、删除、更新数据的能力，这些是应用程序对数据库的日常操作。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eDQL：Data Query Language\n\u003cul\u003e\n\u003cli\u003eDQL允许用户查询数据，这也是通常最频繁的数据库日常操作\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"数据库类别\"\u003e\u003cstrong\u003e数据库类别\u003c/strong\u003e\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e关系型数据库(SQL)\u003c/li\u003e\n\u003cli\u003e非关系型数据库(NoSQL) - MongoDB, Cassandra, Dynamo\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"关系模型\"\u003e\u003cstrong\u003e关系模型\u003c/strong\u003e\u003c/h2\u003e\n\u003ch3 id=\"主键\"\u003e\u003cstrong\u003e主键\u003c/strong\u003e\u003c/h3\u003e\n\u003cp\u003e主键是关系表中记录的唯一标识。选取主键的一个基本原则是：不使用任何业务相关的字段作为主键。\u003c/p\u003e","title":"SQL笔记"},{"content":"一个普通的个人博客，随便写点东西，记点笔记\n","permalink":"https://www.kutake.top/about/","summary":"\u003cp\u003e一个普通的个人博客，随便写点东西，记点笔记\u003c/p\u003e","title":"くたけ"}]