-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 146 KB
/
Copy pathcontent.json
File metadata and controls
1 lines (1 loc) · 146 KB
1
{"meta":{"title":"Interesting","subtitle":"因吹斯汀的博客","description":"记录编程和生活","author":"xu-ux","url":"https://xu-ux.github.io","root":"/"},"pages":[{"title":"","date":"2023-07-06T06:35:52.773Z","updated":"2023-07-06T06:35:52.773Z","comments":true,"path":"404.html","permalink":"https://xu-ux.github.io/404.html","excerpt":"","text":"404 很抱歉,您访问的页面不存在 可能是输入地址有误或该地址已被删除"},{"title":"","date":"2023-07-06T06:35:52.833Z","updated":"2023-07-06T06:35:52.833Z","comments":false,"path":"about/index.html","permalink":"https://xu-ux.github.io/about/index.html","excerpt":"","text":"关于后端开发者,每天的工作就是搬砖,生活平平淡淡,普通人罢了 一些开源项目感兴趣的小伙伴可以逛一下","author":"Xuux"},{"title":"","date":"2023-07-06T06:35:52.833Z","updated":"2023-07-06T06:35:52.833Z","comments":false,"path":"artitalk/index.html","permalink":"https://xu-ux.github.io/artitalk/index.html","excerpt":"","text":""},{"title":"所有分类","date":"2023-07-06T06:35:52.833Z","updated":"2023-07-06T06:35:52.833Z","comments":true,"path":"categories/index.html","permalink":"https://xu-ux.github.io/categories/index.html","excerpt":"","text":""},{"title":"友人帐","date":"2023-07-06T06:35:52.833Z","updated":"2023-07-06T06:35:52.833Z","comments":true,"path":"friends/index.html","permalink":"https://xu-ux.github.io/friends/index.html","excerpt":"各位友人只争朝夕,不负韶华DearXuan编程,算法Lmx0学如逆水行舟,不进则退。","text":"各位友人只争朝夕,不负韶华DearXuan编程,算法Lmx0学如逆水行舟,不进则退。 友链格式本站信息申请须知title: # 名称 avatar: # 头像 url: # 链接 screenshot: # 截图 escription: # 描述 title: Interesting url: https://xu-ux.github.io/ avatar: https://gcore.jsdelivr.net/gh/xu-ux/assets@main/blog/avatar/xiaoxiong128x128.svg screenshot: https://raw.githubusercontent.com/xu-ux/assets/main/blog/img/screenshot.png description: 记录编程和生活 如何添加 在这个页面的评论区留言就行,看到我就会加上去,我的你也加一下,信息都给了 图片的处理 尽可能将头像和快照压缩到100KB以内,以提高加载速度.以下是avatar和screenshot的推荐尺寸: avatar: 150x150 (宽x高) screenshot: 540x360 (宽x高)","author":"xuux"},{"title":"","date":"2023-07-06T06:35:52.845Z","updated":"2023-07-06T06:35:52.845Z","comments":true,"path":"mylist/index.html","permalink":"https://xu-ux.github.io/mylist/index.html","excerpt":"","text":""},{"title":"","date":"2021-09-21T01:55:57.000Z","updated":"2023-07-06T06:35:52.845Z","comments":true,"path":"/other/covid-19/index.html","permalink":"https://xu-ux.github.io/other/covid-19/index.html","excerpt":"","text":""},{"title":"所有标签","date":"2023-07-06T06:35:52.845Z","updated":"2023-07-06T06:35:52.845Z","comments":true,"path":"tags/index.html","permalink":"https://xu-ux.github.io/tags/index.html","excerpt":"","text":""},{"title":"","date":"2021-09-21T01:55:57.000Z","updated":"2023-07-06T06:35:52.845Z","comments":true,"path":"more/catchCat/index.html","permalink":"https://xu-ux.github.io/more/catchCat/index.html","excerpt":"","text":"游戏规则 点击小圆点,围住小猫。 你点击一次,小猫走一次。 直到你把小猫围住(赢),或者小猫走到边界并逃跑(输)。"},{"title":"","date":"2022-09-22T01:55:57.000Z","updated":"2023-07-06T06:35:52.845Z","comments":false,"path":"p/bookmarks/index.html","permalink":"https://xu-ux.github.io/p/bookmarks/index.html","excerpt":"","text":"🔖收藏的书签🔖 电路AI 从零开始打造一台简易计算机 从零开始打造一台简易计算机从零开始打造一台可运行的简易计算机专题系列结合一个免费开源的 线上数字电路模拟器(仿真器),, 从最基本的继电器(晶体管)功能讲起, 到最终完成一个可以批量执行指令的简易计算机.跟随文中的步骤, 即可亲手在线上打造一台可运行的简易的计算机, 在此过程中, 将获得对计算机底层工作原理的一个深刻理解.这些问题包括:计算机是怎么表示数的?计算机是怎么进行转码, 译码的?计算机是怎么做输入, 输出的?计算机是怎么做加法, 减法的?计算机是怎么存储数据的?计算机是怎么计数的?计算机是怎么进行比较与选择的?计算机是怎么在时钟信号的驱动下连续工作的?计算机是怎么批量处理数据的?计算机是怎么进行停机的?计算机是怎么进行指令译码的?计算机是怎么把数据和指令存储在一起并批量执行的?计算机是怎么共用公共的地址及数据总线的?…等等 Wokwi 电路仿真 Wokwi文档地址Wokwi是一个在线的电子电路仿真器。你可以使用它来仿真Arduino、ESP32和许多其他流行的电路板、元器件以及传感器。这里为你准备了可以体验Wokwi的一些快速示例:Arduino Uno “Hello World”Blink an LED on ESP32Monitor the weather on ATtiny85Control 32 Servos with Arduino MegaAnimate an LED Matrix with FastLED7 Segment Counter with MicroPython on ESP32 刘焕勇 https://liuhuanyong.github.io/https://blog.csdn.net/lhy2014https://github.com/liuhuanyong 实时语音克隆 MockingBirdDEMO VIDEO | Wiki教程 | 训练教程特性🌍 中文 支持普通话并使用多种中文数据集进行测试:aidatatang_200zh, magicdata, aishell3, biaobei, MozillaCommonVoice, data_aishell 等🤩 PyTorch 适用于 pytorch,已在 1.9.0 版本(最新于 2021 年 8 月)中测试,GPU Tesla T4 和 GTX 2060🌍 Windows + Linux 可在 Windows 操作系统和 linux 操作系统中运行(苹果系统M1版也有社区成功运行案例)🤩 Easy & Awesome 仅需下载或新训练合成器(synthesizer)就有良好效果,复用预训练的编码器/声码器,或实时的HiFi-GAN作为vocoder🌍 Webserver Ready 可伺服你的训练结果,供远程调用 DiffSinger 歌声合成 DiffSinger原作者的官方仓库:https://github.com/MoonInTheRiver/DiffSinger OpenVPI团队的第三方fork仓库:https://github.com/openvpi/DiffSinger DiffSinger社区声码器企划:https://openvpi.github.io/vocoders/ OpenSVIP项目:https://openvpi.github.io参考文献:《DiffSinger: Singing Voice Synthesis via Shallow Diffusion Mechanism》,第一作者浙江大学刘静林 文献下载地址:https://ojs.aaai.org/index.php/AAAI/article/download/21350/21099 NovelAI WebUI和环境:https://github.com/AUTOMATIC1111/stable-diffusion-webui插件:https://github.com/Mikubill/sd-webui-controlnet模型:https://huggingface.co/lllyasviel/ControlNet/tree/main B站开源模型Real-CUGAN Real Cascade U-Nets for Anime Image Super Resolution🔥 Real-CUGAN🔥 是一个使用百万级动漫数据进行训练的,结构与Waifu2x兼容的通用动漫图像超分辨率模型。它支持2x\\3x\\4x倍超分辨率,其中2倍模型支持4种降噪强度与保守修复,3倍/4倍模型支持2种降噪强度与保守修复。Real-CUGAN 为Windows用户打包了一个可执行环境。同时目前已有Windows-GUI与Web版本可使用。对比Waifu2x(CUNet)Real-ESRGAN(Anime6B)Real-CUGAN训练集私有二次元训练集,量级与质量未知私有二次元训练集,量级与质量未知百万级高清二次元patch dataset推理耗时(1080P)Baseline2.2x1x效果(见对比图)无法去模糊,artifact去除不干净锐化强度最大,容易改变画风,线条可能错判,虚化区域可能强行清晰化更锐利的线条,更好的纹理保留,虚化区域保留兼容性大量windows-APP使用,VapourSynth支持,Caffe支持,PyTorch支持,NCNN支持PyTorch支持,VapourSynth支持,NCNN支持同Waifu2x,结构相同,参数不同,与Waifu2x无缝兼容强度调整仅支持多种降噪强度不支持已完成4种降噪程度版本和保守版,未来将支持调节不同去模糊、去JPEG伪影、锐化、降噪强度尺度仅支持1倍和2倍仅支持4倍已支持2倍、3倍、4倍,1倍训练中","author":"xuux"},{"title":"","date":"2021-09-22T01:55:57.000Z","updated":"2023-07-06T06:35:52.845Z","comments":true,"path":"p/messages/index.html","permalink":"https://xu-ux.github.io/p/messages/index.html","excerpt":"","text":"📝留言板 🍨欢迎你的来访,请遵守相关法律法规,🚯文明灌水,谢谢合作~","author":"Xuux"}],"posts":[{"title":"Java VisualVM 插件","slug":"Java-VisualVM-插件","date":"2023-03-19T09:44:35.000Z","updated":"2023-07-06T06:35:52.797Z","comments":true,"path":"post/89d77c24/","link":"","permalink":"https://xu-ux.github.io/post/89d77c24/","excerpt":"在我们安装的JDK中,提供了一个很棒的JVM调优工具,也就是 Java VisualVM,通过它我们能够看到很多关于我们Java程序的信息。当然借助插件可以实现更为强大的调测功能,比如使用VisualGC查看 Eden,Survivor From, Survivor To区的空间使用情况,以及排查程序中那些对象造成OOM。","text":"在我们安装的JDK中,提供了一个很棒的JVM调优工具,也就是 Java VisualVM,通过它我们能够看到很多关于我们Java程序的信息。当然借助插件可以实现更为强大的调测功能,比如使用VisualGC查看 Eden,Survivor From, Survivor To区的空间使用情况,以及排查程序中那些对象造成OOM。 VisualVM位置我们可以通过找到安装JDK的目录 D:\\Java\\jdk1.8\\bin 或者使用cmd命令来打开图形化界面 jvisualvm 启动完成后,会有这样一个界面 安装插件下载插件首先我们需要到Visual的 插件官网 下载,我们需要找到自己的JDK版本 这里提供多种插件选择 VIsualGC插件,是能够让我们通过图形化的页面,来查看我们的堆内存,以及各区使用情况 MBeans浏览器插件提供的功能类似于JConsole中的MBeans浏览器:显示应用程序的MBean,显示值,操作和通知。在VisualVM中,浏览器得到了进一步的改进,以提供更好的可用性并支持最新的JMX功能。 下载Github超时可以使用该工具,https://gh-proxy.tenma.work/ 下载完成后,我们把插件放在下面目录下 C:\\Users\\<用户>\\AppData\\Roaming\\VisualVM\\8u131 安装然后在到我们刚刚打开的Visual VM图形化页面,点击工具 -> 插件 然后在点击已下载 -> 添加插件,找到文件,然后选择安装 效果 检测安装成功后,我们通过写一个代码来进行检测 /** * OOM测试 * */ public class OOMTest { public static void main(String[] args) throws InterruptedException { List<Integer> list = new ArrayList<>(); while(true) { Thread.sleep(10); list.add(2140008000); } } } 配置启动的JVM参数 -Xms100m -Xmx100m 最后点击Visual GC查看我们的堆内存情况 等待一会后,我们发现S1区中,有了对象,说明JVM已经进行了第一次新生代GC 第二次,S0区中存在对象 多次GC","categories":[{"name":"编程","slug":"编程","permalink":"https://xu-ux.github.io/categories/%E7%BC%96%E7%A8%8B/"},{"name":"Java","slug":"编程/Java","permalink":"https://xu-ux.github.io/categories/%E7%BC%96%E7%A8%8B/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://xu-ux.github.io/tags/Java/"},{"name":"Jvm","slug":"Jvm","permalink":"https://xu-ux.github.io/tags/Jvm/"},{"name":"VisualVM","slug":"VisualVM","permalink":"https://xu-ux.github.io/tags/VisualVM/"},{"name":"插件","slug":"插件","permalink":"https://xu-ux.github.io/tags/%E6%8F%92%E4%BB%B6/"}],"author":"xuux"},{"title":"CloudFlare接管域名,并搭建代理","slug":"CloudFlare接管域名,并搭建代理","date":"2023-03-14T12:27:23.000Z","updated":"2023-07-06T06:35:52.785Z","comments":true,"path":"post/2d5c3914/","link":"","permalink":"https://xu-ux.github.io/post/2d5c3914/","excerpt":"我手上只有阿里云的域名,所以这里我将使用阿里云的域名,作为cloudflare接管的域名;并将演示如何设置代理Worker","text":"我手上只有阿里云的域名,所以这里我将使用阿里云的域名,作为cloudflare接管的域名;并将演示如何设置代理Worker 1、接管域名登录:https://dash.cloudflare.com/ 添加站点 添加后,选择计划,这里个人开发者选免费的就行,企业开发者自行抉择 会自动扫描你设置的域名解析 扫描结束后,继续 提示修改名称服务器,并提供了修改的服务器地址 darl.ns.cloudflare.com eloise.ns.cloudflare.com 阿里云设置域名DNS服务器进入阿里云,进入域名控制台 点击域名管理,修改DNS服务器地址为 darl.ns.cloudflare.com eloise.ns.cloudflare.com 如图所示: 进入cloudflare,点击完成,检查域名服务器,此过程可能耗时10-30分钟 2.创建Workers,代理openai本次代理openai的接口需要用到Workers 点击创建服务 其代码来源于 const TELEGRAPH_URL = 'https://api.openai.com'; addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)) }) async function handleRequest(request) { const url = new URL(request.url); url.host = TELEGRAPH_URL.replace(/^https?:\\/\\//, ''); const modifiedRequest = new Request(url.toString(), { headers: request.headers, method: request.method, body: request.body, redirect: 'follow' }); const response = await fetch(modifiedRequest); const modifiedResponse = new Response(response.body, response); // 添加允许跨域访问的响应头 modifiedResponse.headers.set('Access-Control-Allow-Origin', '*'); return modifiedResponse; } 将上述代码复制到Worker中,并点击保存并部署 这时我们点击这个Worker的触发器,可以得到一个地址 https://openai.xuux.workers.dev/ workers.dev在中国大陆区域已经被污染无法访问,所以需要自有域名进行解析 3.自有域名解析Workers路由点击网站,可以看我们添加的站点已经解析成功,说明已经被CloudFlare接管了 选择Workers路由 添加一条记录,结果如下图 DNS解析添加一条CNAME记录 解析成功 PS C:\\Windows\\system32> ping openai.tenma.work 正在 Ping openai.tenma.work [2606:4700:3032::ac43:aa6a] 具有 32 字节的数据: 来自 2606:4700:3032::ac43:aa6a 的回复: 时间=179ms 来自 2606:4700:3032::ac43:aa6a 的回复: 时间=179ms 来自 2606:4700:3032::ac43:aa6a 的回复: 时间=179ms 来自 2606:4700:3032::ac43:aa6a 的回复: 时间=177ms 2606:4700:3032::ac43:aa6a 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失), 往返行程的估计时间(以毫秒为单位): 最短 = 177ms,最长 = 179ms,平均 = 178ms","categories":[{"name":"技术","slug":"技术","permalink":"https://xu-ux.github.io/categories/%E6%8A%80%E6%9C%AF/"},{"name":"云服务","slug":"技术/云服务","permalink":"https://xu-ux.github.io/categories/%E6%8A%80%E6%9C%AF/%E4%BA%91%E6%9C%8D%E5%8A%A1/"}],"tags":[{"name":"反向代理","slug":"反向代理","permalink":"https://xu-ux.github.io/tags/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86/"},{"name":"CloudFlare","slug":"CloudFlare","permalink":"https://xu-ux.github.io/tags/CloudFlare/"},{"name":"域名解析","slug":"域名解析","permalink":"https://xu-ux.github.io/tags/%E5%9F%9F%E5%90%8D%E8%A7%A3%E6%9E%90/"},{"name":"代理","slug":"代理","permalink":"https://xu-ux.github.io/tags/%E4%BB%A3%E7%90%86/"}],"author":"xuux"},{"title":"ChatGPT和New Bing体验以及ChatGPT-3.5-API初上手","slug":"ChatGPT和New-Bing体验以及ChatGPT-3-5-API初上手","date":"2023-03-04T08:27:08.000Z","updated":"2023-07-06T06:35:52.773Z","comments":true,"path":"post/b81a54e5/","link":"","permalink":"https://xu-ux.github.io/post/b81a54e5/","excerpt":"最近是ChatGPT大火的一段时间,我当然也趁着空闲时间,体验了一把AI带来的自然语言处理的震撼。在网上冲浪时简单使用了一下ChatGPT和New Bing,第一眼,技术革命!","text":"最近是ChatGPT大火的一段时间,我当然也趁着空闲时间,体验了一把AI带来的自然语言处理的震撼。在网上冲浪时简单使用了一下ChatGPT和New Bing,第一眼,技术革命! 问几个问题首先我分别对ChatGPT和New Bing提问:你怎么看待AI对人类社会的影响? 两个模型回答的基本差不多,New Bing会提供它引用的资料 你认为未来会出现什么样的新型AI应用? 你对自己的定义是什么? 测测创造力这里我将New Bing的设置为:更具有创造力 我让它以自己为原型,讲了一个童话故事。 ChatGPT也说了一个故事,但感觉没New Bing说的好😂 ChatGPT一般不会被中断会话,而New Bing这边,微软稍稍干涉了下,一般和New Bing交流都在6句话以内,如果还涉及到某些关键字或者不良引导,就会被强制中断。 比较遗憾没能第一时间体验到未阉割版的New Bing,主要还是申请后,在候补列表里面待的太久了。 ChatGPT Demo最近OpenAPI已经放开了GPT3.5的模型的接口,所以我也是立马写了个Demo,试试效果 Demo地址 创建key 接口文档 接口调用需要翻墙 效果 其他方式个人使用cloudflare搭建了反向代理 以下地址无需翻墙 接口地址:https://openai.tenma.work/ 文档地址 curl https://api.openai.com/v1/chat/completions \\ -H 'Content-Type: application/json' \\ -H 'Authorization: Bearer YOUR_API_KEY' \\ -d '{ "model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "Hello!"}] }' 切换后: curl https://openai.tenma.work/v1/chat/completions \\ -H 'Content-Type: application/json' \\ -H 'Authorization: Bearer YOUR_API_KEY' \\ -d '{ "model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "Hello!"}] }' 返回结果: {"id":"chatcmpl-6txxF8VX6KwzTv7mhcZe9JnlTqsw4","object":"chat.completion","created":1678796525,"model":"gpt-3.5-turbo-0301","usage":{"prompt_tokens":9,"completion_tokens":12,"total_tokens":21},"choices":[{"message":{"role":"assistant","content":"\\n\\nHello there! How can I assist you today?"},"finish_reason":"stop","index":0}]}","categories":[{"name":"技术","slug":"技术","permalink":"https://xu-ux.github.io/categories/%E6%8A%80%E6%9C%AF/"},{"name":"AI","slug":"技术/AI","permalink":"https://xu-ux.github.io/categories/%E6%8A%80%E6%9C%AF/AI/"}],"tags":[{"name":"AI","slug":"AI","permalink":"https://xu-ux.github.io/tags/AI/"},{"name":"NewBing","slug":"NewBing","permalink":"https://xu-ux.github.io/tags/NewBing/"},{"name":"ChatGPT","slug":"ChatGPT","permalink":"https://xu-ux.github.io/tags/ChatGPT/"},{"name":"OpenAPI","slug":"OpenAPI","permalink":"https://xu-ux.github.io/tags/OpenAPI/"},{"name":"NLP","slug":"NLP","permalink":"https://xu-ux.github.io/tags/NLP/"}],"author":"xuux"},{"title":"禁止跳转cn.bing.com小技巧","slug":"禁止跳转cn-bing-com小技巧","date":"2023-02-13T07:52:32.000Z","updated":"2023-07-06T06:35:52.833Z","comments":true,"path":"post/2ffe26de/","link":"","permalink":"https://xu-ux.github.io/post/2ffe26de/","excerpt":"中国大陆访问www.bing.com/new却被重定向到cn.bing.com,使用gooreplacer浏览器插件避免重定向","text":"中国大陆访问www.bing.com/new却被重定向到cn.bing.com,使用gooreplacer浏览器插件避免重定向 Gooreplacer插件Gooreplacer 最初为解决国内无法访问 Google 资源(Ajax、API 等)导致页面加载速度巨慢而生,新版在此基础上,增加了更多实用功能,可以方便用户屏蔽某些请求,修改 HTTP 请求 / 响应 的 headers。 下载插件 微软Edge插件商店 Zip Releases 打开 Gooreplacer 的设置界面,选择请求 / 请求头项目,新建一条规则,完成后在设置中全局启用 请求头:打勾启用 点击右侧按钮新增一条规则: 动作类型 键名 键值 匹配模式 匹配类型 是否开启 操作 修改 x-forwarded-for 1.1.1.1 ^http(s?)://(www\\.)?bing\\.com/?(.*) 正则表达式 是 如下图: 申请候补New Binghttps://www.bing.com/new","categories":[{"name":"技术","slug":"技术","permalink":"https://xu-ux.github.io/categories/%E6%8A%80%E6%9C%AF/"},{"name":"AI","slug":"技术/AI","permalink":"https://xu-ux.github.io/categories/%E6%8A%80%E6%9C%AF/AI/"}],"tags":[{"name":"AI","slug":"AI","permalink":"https://xu-ux.github.io/tags/AI/"},{"name":"NewBing","slug":"NewBing","permalink":"https://xu-ux.github.io/tags/NewBing/"},{"name":"ChatGPT","slug":"ChatGPT","permalink":"https://xu-ux.github.io/tags/ChatGPT/"},{"name":"浏览器插件","slug":"浏览器插件","permalink":"https://xu-ux.github.io/tags/%E6%B5%8F%E8%A7%88%E5%99%A8%E6%8F%92%E4%BB%B6/"}],"author":"xuux"},{"title":"折腾路由器FIR302M,刷Breed固件","slug":"折腾路由器FIR302M-刷Breed固件","date":"2022-12-07T11:27:15.000Z","updated":"2023-07-06T06:35:52.809Z","comments":true,"path":"post/2edf191f/","link":"","permalink":"https://xu-ux.github.io/post/2edf191f/","excerpt":"闲来无事,折腾下这个从箱子底翻出来的路由器FIR302M,应该是7年前买的,致逝去的青春买来好像是为了中继Wifi吧,2015年的事情了","text":"闲来无事,折腾下这个从箱子底翻出来的路由器FIR302M,应该是7年前买的,致逝去的青春买来好像是为了中继Wifi吧,2015年的事情了 感触有点多,这饱经风霜的伊拉克成色外观😅 现在都已经到了Wifi7技术了,按代数,它还在Wifi4,老爷爷辈 看看刷了第三方固件后,能否老骥伏枥,焕发第二春 1.刷入Breed Web(不死)恢复控制台 Breed 是由网友开发的一个号称“不死”的引导加载程序。只要路由器成功刷入 Breed 之后,随意更换路由器系统就不容易刷坏了,同时也具备了备份路由器系统和更改MAC的功能 Breed作者的仓库 https://breed.hackpascal.net/ 刷入‘不死’固件是这台路由器能否刷第三方固件的前提 那接下来跟着步骤走就完事了 1.1进入路由器后台界面,修改密码路由器初始lan 是192.168.1.1 帐号密码admin 连接好互联网 设置管理员密码和用户名为12345678,这个是为后面telnet登录准备的 1.2进入系统诊断界面访问 http://192.168.1.1/goform/Diagnosis?pingAddr=192.168.1.100|echo""|telnetd http://192.168.1.1/goform/Diagnosis?pingAddr=192.168.1.100|echo""|telnetd 成功后会出现系统诊断界面,如果要输入密码就输入密码 不要关闭 1.3进入CMD命令提示符程序进入cmd或者powershell 键入命令,这一步之前,先让电脑安装telnet功能服务 telnet 192.168.1.1 出现端口错误,重新执行1.2步骤 1.4输入用户名密码登入路由器后台,用户名输入:12345678,密码也是12345678(输入的密码不会显示的) 然后回车,登录成功 1.5下载Breed固件cd /tmp wget http://breed.hackpascal.net/breed-mt7620-fir302m.bin 出现错误 ,这个内部的wget不支持https,所以我们等用另外一种方式实现 本地方式那我们换本地方式上传,先去下载这个breed-mt7620-fir302m.bin;还要下一个chfs软件,它能提供一个服务端 下载以下资料breed下载地址:https://breed.hackpascal.net/breed-mt7620-fir302m.bin chfs下载地址:http://iscute.cn/tar/chfs/2.0/gui-chfs-windows.zip解压可以得到chfsgui.exe 下载后,放到同一个目录下,我这里是放到了F盘的FIR302M文件夹下 打开chfsgui.exe程序 设置共项目目录为FIR302M 此刻我们点击右上角的地址http://192.168.123.203:10088/ (每个人地址都不同,可能以192.168.1.开头) 能访问到一个文件管理界面 重新下载既然服务端搭建好了,breed-mt7620-fir302m.bin也可以访问了,那就重新下载 cd /tmp wget http://192.168.123.203:10088/chfs/shared/FIR302M/breed-mt7620-fir302m.bin 1.6写入固件等待下载上一步下载成功后,输入以下命令,回车 mtd_write write breed-mt7620-fir302m.bin Bootloader 等一会儿,后面变成[W] 1.7进入Breed Web路由器断电 左手按复位键,右手插电源,左手按住10秒,别松 10s后,在浏览器页面输入 192.168.1.1 进入breed web 如果进不去,设置电脑网卡的静态ip为192.168.1.2 网关为192.168.1.1 掩码255.255.255.0 然后进入第一件事情,就是备份EEPROM和编程器固件 2.Breed Web恢复控制台输入第三方固件第三方固件都是网上和论坛找到,我这里就使用hiboy开发的固件 老毛子Padavan刷入 hiboy作者的固件:https://opt.cn2qq.com/padavan/ 2.1上传非常简单,点击固件更新,上传固件 2.2更新 2.3更新完成不要断电,还记的之前电脑设置过静态IP吗,改成动态IP 3.访问新固件后台老毛子Padavan管理地址:192.168.123.1,用户名:admin,密码:admin2.4G无线信号:PDCN,5G无线信号PDCN_5G,无线密码都是1234567890 输入地址,弹出登录框 输入admin和admin登录成功 3.1支持IPTV 3.2支持无线桥接 4.最后感谢这些在路由器固件中开发、维护的作者,正是他们的辛勤劳动,才能让我手里这个老将也能焕发新生","categories":[{"name":"生活","slug":"生活","permalink":"https://xu-ux.github.io/categories/%E7%94%9F%E6%B4%BB/"},{"name":"数码","slug":"生活/数码","permalink":"https://xu-ux.github.io/categories/%E7%94%9F%E6%B4%BB/%E6%95%B0%E7%A0%81/"}],"tags":[{"name":"技术","slug":"技术","permalink":"https://xu-ux.github.io/tags/%E6%8A%80%E6%9C%AF/"},{"name":"数码","slug":"数码","permalink":"https://xu-ux.github.io/tags/%E6%95%B0%E7%A0%81/"},{"name":"路由器","slug":"路由器","permalink":"https://xu-ux.github.io/tags/%E8%B7%AF%E7%94%B1%E5%99%A8/"}],"author":"xuux"},{"title":"LocalDate、LocalTime、LocalDateTime的常用API和计算","slug":"LocalDate、LocalTime、LocalDateTime的常用API和计算","date":"2021-12-03T06:07:09.000Z","updated":"2023-07-06T06:35:52.805Z","comments":true,"path":"post/8f766de6/","link":"","permalink":"https://xu-ux.github.io/post/8f766de6/","excerpt":"","text":"LocalDate、LocalTime、LocalDateTime的常用API和计算从Java 8开始,java.time包提供了新的日期和时间API,主要涉及的类型有: 本地日期和时间:LocalDateTime,LocalDate,LocalTime; 带时区的日期和时间:ZonedDateTime; 时刻:Instant; 时区:ZoneId,ZoneOffset; 时间间隔:Duration。 以及一套新的用于取代SimpleDateFormat的格式化类型DateTimeFormatter。 本文仅仅简述部分接口,详细还请了解官方API文档 一、概述LocalDateTime表示日期和时间 LocalDate用来表示日期 LocalTIme表示时间,大概就是LocalDateTime = LocalDate + LocalTime ZonedDateTime带时区的日期和时间 他们的关系: 常用解析LocalTime.of(int hour,int minute,int second)// 指定时、分、秒,参数不可缺省 LocalDate.of(int year,int mouth,int day)// 指定年、月、日,参数不可缺省,这里指定月份的话是从1开始,与Calendar不同,Calendar的月份是从0开始,这也就是LocalDate比Calendar处理时间要方便的多。 LocalDate.of(int year,MOUTH mouth,int day)// 指定年、月、日 LocalDateTime.of(nt year,int mouth,int day,int hour,int minute,int second,int nanoOfSecond)// 指定年、月、日、时、分、秒、纳秒,参数可缺省 LocalDateTime.of(nt year,MOUTH mouth,int day,int hour,int minute,int second,int nanoOfSecond) LocalDateTime.of(LocalDate localDate,LocalTime localTime)// 指定localDate localTime组合成日期时间 这里我们使用常用构造打印一下: public static void main(String args[] ){ // 当前日期和时间 LocalDateTime localDateTime = LocalDateTime.now(); // 转换到当前日期 LocalDate localDate = localDateTime.toLocalDate(); // 转换到当前时间 LocalTime localTime = localDateTime.toLocalTime(); // LocalDate的部分构造 LocalDate localDate1 = LocalDate.now(); LocalDate localDate2 = LocalDate.of(2020, 02, 20); LocalDate localDate3 = LocalDate.parse("2020-02-20"); LocalDate localDate4 = LocalDate.of(2020, Month.FEBRUARY, 20); // LocalDate的部分构造 LocalTime localTime1 = LocalTime.now(); LocalTime localTime2 = LocalTime.parse("07:30"); LocalTime localTime3 = LocalTime.of(7, 30); LocalTime localTime4 = LocalTime.of(7, 29, 59, 999000123); // LocalDateTime的部分构造 LocalDateTime localDateTime1 = LocalDateTime.of(localDate1, localTime1); LocalDateTime localDateTime2 = LocalDateTime.now(); LocalDateTime localDateTime3 = LocalDateTime.now(ZoneId.of("America/Chicago")); LocalDateTime localDateTime4 = LocalDateTime.parse("2020-02-20T08:15:59"); // 获取时区 ZoneId romeZone = ZoneId.of("Europe/Rome"); ZoneId defaultZone = TimeZone.getDefault().toZoneId(); // 获取当前时刻 Instant instant = Instant.now(); // 获取日期和日期时间 LocalDate localDate5 = LocalDate.of(2020, Month.OCTOBER, 31); LocalDateTime localDateTime5 = LocalDateTime.of(2020, Month.OCTOBER, 31, 13, 45); ZonedDateTime zonedDateTime1 = localDate5.atStartOfDay(romeZone); ZonedDateTime zonedDateTime2 = localDateTime5.atZone(romeZone); ZonedDateTime zonedDateTime3 = instant.atZone(romeZone); ZonedDateTime zonedDateTime4 = instant.atZone(defaultZone); } 输出: ======================================= localDate = 2021-09-16 localTime = 09:59:50.002 localDateTime = 2021-09-16T09:59:50.002 ======================================= localDate1 = 2021-09-16 localDate2 = 2020-02-20 localDate3 = 2020-02-20 localDate4 = 2020-02-20 ======================================= localTime1 = 09:59:50.016 localTime2 = 07:30 localTime3 = 07:30 localTime4 = 07:29:59.999000123 ======================================= localDateTime1 = 2021-09-16T09:59:50.016 localDateTime2 = 2021-09-16T09:59:50.016 localDateTime3 = 2021-09-15T20:59:50.017 localDateTime4 = 2020-02-20T08:15:59 ======================================= zonedDateTime1 = 2020-10-31T00:00+01:00[Europe/Rome] zonedDateTime2 = 2020-10-31T13:45+01:00[Europe/Rome] zonedDateTime3 = 2021-09-16T04:20:52.591+02:00[Europe/Rome] zonedDateTime4 = 2021-09-16T10:20:52.591+08:00[Asia/Shanghai] ======================================= 这里的新API全部按照[ISO 8601](./日期与时间基本概念和格式介绍.md#ISO 8601)格式打印,本地日期和时间通过now()获取到的总是以当前默认时区返回的,和旧API不同,LocalDateTime、LocalDate和LocalTime默认严格按照ISO 8601规定的日期和时间格式进行打印。 JDBC对新类型的支持date -> LocalDate time -> LocalTime timestamp -> LocalDateTime 二、LocalDate①常用的构造方法 // 常用的构造方法 LocalDate localDate1 = LocalDate.of(2020, 6, 30);//2020-06-30 LocalDate localDate2 = LocalDate.parse("2020-06-30");//2020-06-30 LocalDate localDate3_1 = LocalDate.now();//2021-09-16 LocalDate localDate3_2 = LocalDate.now(ZoneId.of("Europe/Paris"));//2021-09-16 LocalDate localDate4 = LocalDate.ofYearDay(2020, 160);//2020-06-08 now()默认是获取当前系统所在的时区,也可以通过设置ZoneId来设定 除了LocalDate还有一些辅助的类YearMonth、Month、 MonthDay YearMonth yearMonth = YearMonth.now();// 2021-09 Month month = Month.from(yearMonth);// SEPTEMBER MonthDay monthDay = MonthDay.of(month, 1);// --09-01 ②常用方法先看看一个localDate对象能够给我们提供什么 // 创建LocalDate对象 LocalDate date = LocalDate.of(2020, 6, 15);//2020-06-15 // 年份 int year = date.getYear();//2020 // 月份 Month month = date.getMonth();//JUNE // 获取该月的第几天 int dayOfMoneth = date.getDayOfMonth();//15 // 获取该年的第几天 int dayOfYear = date.getDayOfYear();//167 // 获取星期几 DayOfWeek dow = date.getDayOfWeek();//MONDAY // 月份的长度 int length = date.lengthOfMonth();//30 // 是否是闰年 boolean leap = date.isLeapYear();//true // 年份 int year2 = date.get(ChronoField.YEAR);//2020 // 月份 int month2 = date.get(ChronoField.MONTH_OF_YEAR);//6 // 获取该月的第几天 int day2 = date.get(ChronoField.DAY_OF_MONTH);//15 // 获取星期几 int week2 = date.get(ChronoField.DAY_OF_WEEK);//1 // 对齐周,若周一为每月1号,这该周处于第几周 int alignWeek = date.get(ChronoField.ALIGNED_WEEK_OF_MONTH);//3 // 对齐天,若周一为每月1号,这该天是对齐周的第几天 int alignDay = date.get(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH);//1 ③计算相关 /************************* 加减计算 ****************************/ // 增加天数,date1往后增加50天 LocalDate date3 = date1.plusDays(50);//2020-08-19 // 减少天数,date1往前推10天 LocalDate date4 = date1.minusDays(10);//2020-06-20 // 增加周,date1往后增加1周 LocalDate date5 = date1.plusWeeks(1);//2020-07-07 // 增加月,date1往后增加6个月 LocalDate date6 = date1.plus(6, ChronoUnit.MONTHS);//2020-12-30 // 减少月,date1往前推2个月 LocalDate date7 = date1.minus(2, ChronoUnit.MONTHS);//2020-04-30 // 减少年,date1往前推3年 LocalDate date8 = date1.minusYears(3);//2017-06-30 // 增加年,date1往后推2年 LocalDate date9 = date1.plusYears(2);//2022-06-30 // 间隔天数,计算date3和date1相差多少天 long interval = date3.toEpochDay() - date1.toEpochDay();//50 // 间隔日期 Period between = Period.between(date1, date2);// 2月+16天 /************************* 调整和操纵 ****************************/ // 指定年份 LocalDate date20 = date1.withYear(1997);//1997-06-30 // 指定当月第几天 LocalDate date21 = date1.withDayOfMonth(25);//2020-06-25 // 指定月份 LocalDate date22 = date1.with(ChronoField.MONTH_OF_YEAR, 9);//2020-09-30 // 该月第一天 LocalDate firstDay1 = date2.withDayOfMonth(1);//2020-09-01 // 使用TemporalAdjusters工厂方法,获取该月第一天 LocalDate firstDay2 = date2.with(TemporalAdjusters.firstDayOfMonth());//2020-09-01 // 该月最后一天 LocalDate endDay = date2.with(TemporalAdjusters.lastDayOfMonth());//2020-09-30 // 该年最后一天 LocalDate endDayOfYear = date2.with(TemporalAdjusters.lastDayOfYear()); //2020-12-31 // 将日期调整为在调整日期之后指定的星期几的第一次出现 LocalDate nextSameDay = date2.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));//2020-09-20 // 取date2的第1月的第一个周一 LocalDate firstMondayOfMONDAY = date2.withMonth(1).with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)); // 2019-01-06 /************************* 间隔值计算 ****************************/ LocalDate localDate = LocalDate.of(2018, 1, 1); LocalDate today = LocalDate.now(); // 获取相差的天数 long betweenDays = localDate.until(today, ChronoUnit.DAYS);// 1355 // 获取相差的星期数 long betweenWeeks = localDate.until(today, ChronoUnit.WEEKS);// 193 // 获取相差的年 long betweenYears = localDate.until(today, ChronoUnit.YEARS);// 3 // 获取相差的世纪 LocalDate pass = LocalDate.of(1911, 1, 1); long betweenCenturies = pass.until(today, ChronoUnit.CENTURIES);// 1 // 获取相差的几十年 10年为1个单位 LocalDate passDecades = LocalDate.of(2000, 1, 1); long betweenDecades = passDecades.until(today, ChronoUnit.DECADES);// 2 // 获取相差的纪元 LocalDate passEras = LocalDate.of(2000, 1, 1); long betweenEras = passEras.until(today, ChronoUnit.ERAS);// 0 // 获取相差几个小时 LocalDateTime localDateTime = LocalDateTime.parse("2020-09-15T14:26:48"); LocalDateTime nowDateTime = LocalDateTime.now(); long betweenHours = localDateTime.until(nowDateTime, ChronoUnit.HOURS);// 8807 关于TemporalAdjusters可以看TemporalAdjuster 匹配周期性的日期 // 计算周期性的日期,比如9·18国家纪念日 MonthDay memorialDay = MonthDay.of(9, 18); MonthDay nowDay = MonthDay.from(LocalDate.now()); // 今天是否是9月8号 boolean isNow = memorialDay.equals(nowDay); // false // 间隔几天 int betweenDay = Math.abs(memorialDay.getDayOfMonth() - nowDay.getDayOfMonth());// 1 三、LocalTimeⅠ.常用方法 /*********** 创建LocalTime **********/ LocalTime time = LocalTime.of(15, 27, 30);// 15:27:30 int hour = time.getHour();// 15 int minute = time.getMinute();// 27 int second = time.getSecond();// 30 LocalTime time1 = LocalTime.parse("16:27:30.915002654");// 16:27:30.915002654 LocalTime time2 = LocalTime.parse("16:27:30");// 16:27:30 LocalTime time3 = LocalDateTime.now().toLocalTime();// 16:40:04.408 /*********** 计算 **********/ // 增加60秒 LocalTime time4 = time.plus(60, ChronoUnit.SECONDS);// 15:28:30 // 减少10分钟 LocalTime time5 = time.plus(-10, ChronoUnit.MINUTES);// 15:17:30 // 增加3秒 LocalTime time6 = time.minus(Duration.ofSeconds(3));// 15:27:27 Ⅱ. 日期类、时间类、日期时间类通用方法通过LocalDate、LocalTime我们可以看到他们的某些API基本都有 方 法 名 是否是静态方法 方法描述 from 是 依据传入的 Temporal 对象创建对象实例 now 是 依据系统时钟创建 Temporal 对象 of 是 由 Temporal 对象的某个部分创建该对象的实例 parse 是 由字符串创建 Temporal 对象的实例 atOffset 否 将 Temporal 对象和某个时区偏移相结合 atZone 否 将 Temporal 对象和某个时区相结合 format 否 使用某个指定的格式器将Temporal 对象转换为字符串(Instant 类不提供该方法) get 否 读取 Temporal 对象的某一部分的值 minus 否 创建 Temporal 对象的一个副本,通过将当前 Temporal 对象的值减去一定的时长创建该副本 plus 否 创建 Temporal 对象的一个副本,通过将当前 Temporal 对象的值加上一定的时长创建该副本 with 否 以该 Temporal 对象为模板,对某些状态进行修改创建该对象的副本 四、LocalDateTime①常用构造/*********** 创建LocalDateTime **********/ LocalDate date = LocalDate.of(2020, 1, 1);// 2020-01-01 LocalTime time = LocalTime.of(10, 56, 30);// 10:56:30 LocalDateTime dt1 = LocalDateTime.of(2020, Month.FEBRUARY, 27, 13, 45, 20);// 2020-02-27T13:45:20 LocalDateTime dt2 = LocalDateTime.of(date, time);// 2020-01-01T10:56:30 LocalDateTime dt3 = date.atTime(9, 40, 36);// 2020-01-01T09:40:36 LocalDateTime dt4 = date.atTime(time);// 2020-01-01T10:56:30 LocalDateTime dt5 = time.atDate(date);// 2020-01-01T10:56:30 LocalDateTime dt6 = LocalDateTime.parse("2020-10-01T10:01:59");// 2020-10-01T10:01:59 ②计算操纵/*********** 计算LocalDateTime **********/ LocalDateTime now = LocalDateTime.now();// 2021-09-17T17:14:59.601 // 增加指定值 LocalDateTime dateTime = now.plusYears(1) .plusMonths(2L) .plusWeeks(4) .plusDays(10) .plusHours(18) .plusMinutes(12) .plusSeconds(1) .plusNanos(600000L); // 2022-12-26T11:27:00.601600 // 增加半天 LocalDateTime dateTime1 = now.plus(1, ChronoUnit.HALF_DAYS);// 2021-09-18T05:14:59.601 // 增加2天 LocalDateTime dateTime2 = now.plus(2, ChronoUnit.DAYS);// 2021-09-19T17:14:59.601 // 世纪 LocalDateTime dateTime3 = now.plus(1, ChronoUnit.CENTURIES);// 2121-09-17T17:14:59.601 // 增加二十年 LocalDateTime dateTime4 = now.plus(2, ChronoUnit.DECADES);// 2041-09-17T17:14:59.601 // 减少1千年 LocalDateTime dateTime5 = now.minus(1, ChronoUnit.MILLENNIA);// 1021-09-17T17:14:59.601 // 永恒,执行不成功 LocalDateTime dateTime6 = now.plus(1, ChronoUnit.FOREVER); // 微秒,执行不成功 LocalDateTime dateTime7 = now.plus(1, ChronoUnit.MICROS); // 时代,执行不成功 LocalDateTime dateTime8 = now.plus(1, ChronoUnit.ERAS); /*********** 操纵LocalDateTime **********/ LocalDateTime localDateTime = LocalDateTime.of(2020, Month.FEBRUARY, 27, 13, 45, 20);// 2020-02-27T13:45:20 LocalDateTime dateTime9 = localDateTime.withYear(2021); // 2021-02-27T13:45:20 LocalDateTime dateTime10 = localDateTime.withMonth(2); // 2020-02-27T13:45:20 LocalDateTime dateTime11 = localDateTime.withDayOfMonth(1); // 2020-02-01T13:45:20 LocalDateTime dateTime12 = localDateTime.with(TemporalAdjusters.lastDayOfMonth());// 2020-02-29T13:45:20 LocalDateTime dateTime13 = localDateTime.with(TemporalAdjusters.firstDayOfMonth());// 2020-02-01T13:45:20 // 设置为当前所处时间的周一 LocalDateTime dateTime14 = localDateTime.with(ChronoField.DAY_OF_WEEK, 1);// 2020-02-24T13:45:20 // 日期设置月为3,并且设置为当月的第一个周二 LocalDateTime dateTime15 = localDateTime.with(ChronoField.MONTH_OF_YEAR, 3) .with(TemporalAdjusters.firstInMonth(DayOfWeek.TUESDAY));// 2020-03-03T13:45:20 ③时间操作API 序号 函数 返回 描述 1 plus LocalDateTime 加上指定数量的时间得到的值 2 plusDays LocalDateTime 加上指定天数得到的值 3 plusHours LocalDateTime 加上指定小时数得到的值 4 plusMinutes LocalDateTime 加上指定分钟数得到的值 5 plusMonths LocalDateTime 加上指定月数得到的值 6 plusNanos LocalDateTime 加上指定纳秒数得到的值 7 plusSeconds LocalDateTime 加上指定秒数得到的值 8 plusWeeks LocalDateTime 加上指定星期数得到的值 9 plusYears LocalDateTime 加上指定年数得到的值 10 with LocalDateTime 指定字段更改为新值后的拷贝 11 withDayOfMonth LocalDateTime 月的第几天更改为新值后的拷贝 12 withDayOfYear LocalDateTime 年的第几天更改为新值后的拷贝 13 withHour LocalDateTime 小时数更改为新值后的拷贝 14 withMinute LocalDateTime 分钟数更改为新值后的拷贝 15 withMonth LocalDateTime 月份更改为新值后的拷贝 16 withNano LocalDateTime 纳秒数更改为新值后的拷贝 17 withSecond LocalDateTime 秒数更改为新值后的拷贝 18 withYear LocalDateTime 年份更改为新值后的拷贝 ④获取时间属性值API 序号 函数 描述 1 get 得到LocalDateTime的指定字段的值 2 getDayOfMonth 得到LocalDateTime是月的第几天 3 getDayOfWeek 得到LocalDateTime是星期几 4 getDayOfYear 得到LocalDateTime是年的第几天 5 getHour 得到LocalDateTime的小时 6 getLong 得到LocalDateTime指定字段的值 7 getMinute 得到LocalDateTime的分钟 8 getMonth 得到LocalDateTime的月份,获取的结果是月份的枚举值 9 getMonthValue 得到LocalDateTime的月份,从1到12 10 getNano 得到LocalDateTime的纳秒数 11 getSecond 得到LocalDateTime的秒数 12 getYear 得到LocalDateTime的年份 ⑤其他API 序号 函数 描述 类别 1 atOffset 结合LocalDateTime和ZoneOffset创建一个OffsetDateTime 时区相关 2 atZone 结合LocalDateTime和指定时区创建一个ZonedDateTime 时区相关 3 ofEpochSecond 根据秒数(从1970-01-0100:00:00开始)创建LocalDateTime 实例方法 4 ofInstant 根据Instant和ZoneId创建LocalDateTime 实例方法 5 compareTo 比较两个LocalDateTime,小于返回-1,等于返回0,大于返回1 比较方法 6 isAfter 判断LocalDateTime是否在指定LocalDateTime之后 比较方法 7 isBefore 判断LocalDateTime是否在指定LocalDateTime之前 比较方法 8 isEqual 判断两个LocalDateTime是否相等 比较方法 9 format 格式化LocalDateTime生成一个字符串 格式化方法 10 from 转换TemporalAccessor为LocalDateTime 格式化方法 11 parse 解析字符串得到LocalDateTime 格式化方法 12 isSupported 判断LocalDateTime是否支持指定时间字段或单元 其他 13 toString 返回LocalDateTime的字符串表示 其他 14 range 返回指定时间字段的数值范围范围(ChronoField) 其他 15 truncatedTo 返回LocalDateTime截取到指定时间单位的拷贝,如果无法截断抛DateTimeException,如果不支持截断单位抛UnsupportedTemporalTypeException 其他 16 until 计算LocalDateTime和另一个LocalDateTime之间的时间差,可以指定时间单位(ChronoUnit) 其他 17 adjustInto 将目标对象调整为指定的时间对象,指定对象调用方法,目标对象作为参数 其他 18 query 使用指定的查询查询此日期时间,查询LocalDateTime(TemporalQueries) 其他 五、TemporalAdjuster①TemporalAdjusters工厂类中的方法 方 法 名 方法描述 dayOfWeekInMonth 创建一个新的日期,它的值为同一个月中每一周的第几天 firstDayOfMonth 创建一个新的日期,它的值为当月的第一天 firstDayOfNextMonth 创建一个新的日期,它的值为下月的第一天 firstDayOfNextYear 创建一个新的日期,它的值为明年的第一天 firstDayOfYear 创建一个新的日期,它的值为当年的第一天 firstInMonth 创建一个新的日期,它的值为同一个月中,第一个符合星期几要求的值 lastDayOfMonth 创建一个新的日期,它的值为当月的最后一天 lastDayOfNextMonth 创建一个新的日期,它的值为下月的最后一天 lastDayOfNextYear 创建一个新的日期,它的值为明年的最后一天 lastDayOfYear 创建一个新的日期,它的值为今年的最后一天 lastInMonth 创建一个新的日期,它的值为同一个月中,最后一个符合星期几要求的值 next/previous 创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星期几要求的日期 nextOrSame/previousOrSame 创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星期几要求的日期,如果该日期已经符合要求,直接返回该对象 ②TemporalAdjuster接口 LocalDate localDate1 = LocalDate.of(2020, 10, 1); LocalDate localDate2 = localDate1.with(temporal -> { // 获取星期几 DayOfWeek week = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK)); int dayToAdd = 1; if (DayOfWeek.FRIDAY.equals(week)) dayToAdd = 3; else if (DayOfWeek.SATURDAY.equals(week)) dayToAdd = 2; // 周五和周六分别加3天和2天,其余加1天 return temporal.plus(dayToAdd, ChronoUnit.DAYS); }); System.out.println(localDate2);// 2020-10-02 六、DateTimeFormatterⅠ.一般用法LocalDate date = LocalDate.of(2020, 10, 1); LocalDateTime dateTime = LocalDateTime.now(); ZonedDateTime zonedDateTime = ZonedDateTime.now(); // 自带的格式化器 String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);// 20201001 String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);// 2020-10-01 // 解析字符串 LocalDate date1 = LocalDate.parse("20200918", DateTimeFormatter.BASIC_ISO_DATE);// 2020-09-18 LocalDate date2 = LocalDate.parse("2018-01-15", DateTimeFormatter.ISO_LOCAL_DATE);// 2018-01-15 // 自定义格式化器 DateTimeFormatter timeFormatter1 = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.CHINA); DateTimeFormatter timeFormatter2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日"); DateTimeFormatter timeFormatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); DateTimeFormatter timeFormatter4 = DateTimeFormatter.ofPattern("MM月dd日"); DateTimeFormatter timeFormatter5 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm ZZZZ"); DateTimeFormatter zhFormatter = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA); DateTimeFormatter usFormatter = DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm", Locale.US); String s3 = date.format(timeFormatter1);// 1. 十月 2020 String s4 = date.format(timeFormatter2);// 2020年10月01日 //String s5 = date.format(timeFormatter3);// 报错,没有时间可以格式化 String s6 = dateTime.format(timeFormatter3);// 2021-09-17 17:44:17 String s7 = date.format(timeFormatter4);// 10月01日 LocalDate date4 = LocalDate.parse(s3, timeFormatter1);//2018-10-31 //LocalDate date5 = LocalDate.parse(s7, timeFormatter4);// 报错,缺少年 String s8 = zonedDateTime.format(usFormatter);// Fri, September/17/2021 17:48 String s9 = zonedDateTime.format(zhFormatter);// 2021 九月 17 星期五 17:48 String s10 = zonedDateTime.format(timeFormatter5);// 2021-09-17T17:48 GMT+08:00 Ⅱ.自定义 ZonedDateTime zonedDateTime = ZonedDateTime.now(); DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder() .appendText(ChronoField.DAY_OF_MONTH) .appendLiteral("-") .appendText(ChronoField.MONTH_OF_YEAR) .appendLiteral("-") .appendText(ChronoField.YEAR) .appendLiteral("-") .appendZoneRegionId() .parseCaseInsensitive() .toFormatter(Locale.CHINA); String format = zonedDateTime.format(italianFormatter);//17-九月-2021-Asia/Shanghai LocalDate parse = LocalDate.parse(format, italianFormatter);//2021-09-17 七、ZoneId、ZoneOffset、ZonedDateTime ①时间戳: 是指格林威治(地球零时区)时间1970年01月01日00时00分00秒起至现在的总秒数,这个时间戳,在地球的各个地方都是一致的; ②时区:由于地球的自转,根据接收太阳光照的顺序将地球划分成24个区,从而方便当地人的生产生活,每个时区相差一小时,可以根据时间戳和时区计算当地的时间。格林威治处于零时区,北京处于东八区,因此,北京时间比格林威治时间早8个小时 ③UTC时间:就是零时区的时间,它的全称是Coordinated Universal Time ,即世界协调时间。另一个常见的缩写是GMT,即格林威治标准时间,格林威治位于 零时区,因此,我们平时说的UTC时间和GMT时间在数值上面都是一样的 类java.time.ZoneId是原有的java.util.TimeZone类的替代品 ①ZoneId1. 获取时区 // 获取默认时区 ZoneId systemZoneId = ZoneId.systemDefault(); // 获取上海时区 ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai"); // 获取巴黎时区 ZoneId parisZoneId = ZoneId.of("Europe/Paris"); // 获取所有合法的“区域/城市”字符串 Set<String> zoneIds = ZoneId.getAvailableZoneIds(); 2. 和TimeZone的转换 // 转换 ZoneId oldToNewZoneId = TimeZone.getDefault().toZoneId(); 3. 时区结合其他日期时间api// 时区结合其他日期时间api ZonedDateTime zonedDateTime = ZonedDateTime.of(LocalDateTime.now(parisZoneId), shanghaiZoneId); // 打印:2021-09-17T04:58:36.247+08:00[Asia/Shanghai] ②ZoneOffset首先我们要明确北京时间比UTC快8个小时,所以应该是GMT+8:00 格林威治标准时间(GMT) 地区:英国 皇家格林尼治天文台 时区:UTC/GMT 0 (零时区) 北京时差:现在格林威治时间比北京时间晚8小时 ZoneOffset表示与格林威治/ UTC的时区偏移量;以当前时间和世界标准时间(UTC)/格林威治时间(GMT)的偏差来计算日期时间 // 偏移 ZoneOffset beijing = ZoneOffset.of("+08:00");// +08:00 ZoneOffset utc = ZoneOffset.of("+00:00");// Z // 获取UTC时间 LocalDateTime utcTime = LocalDateTime.now(ZoneOffset.UTC);// 2021-09-17T03:30:05.972 // 获取带时区和偏移的时间 OffsetDateTime offsetDateTime = OffsetDateTime.of(utcTime, utc);// 2021-09-17T03:30:05.972Z // 获取带时区的时间 ZonedDateTime utcDateTime = offsetDateTime.toZonedDateTime();// 2021-09-17T03:30:05.972Z // 转为本地时区的时间 ZonedDateTime localDateTime = utcDateTime.withZoneSameInstant(ZoneId.of("Asia/Shanghai")); // 2021-09-17T11:30:05.972+08:00[Asia/Shanghai] ③ZonedDateTimeZonedDateTime对象由两部分构成,LocalDateTime和ZoneId,其中2021-09-17T04:58:36.247部分为LocalDateTime,+08:00[Asia/Shanghai]部分为ZoneId。 所以,总的来说: ZonedDateTime是带时区的日期和时间,可用于时区转换; ZonedDateTime和LocalDateTime可以相互转换。 1. 创建方法LocalDateTime ldt = LocalDateTime.of(2020, 9, 15, 10, 30, 17);// 2020-09-15T10:30:17 ZonedDateTime zonedDateTime1 = ldt.atZone(ZoneId.systemDefault());// 2020-09-15T10:30:17+08:00[Asia/Shanghai] ZonedDateTime zonedDateTime2 = ldt.atZone(ZoneId.of("America/New_York"));// 2020-09-15T10:30:17-04:00[America/New_York] 2. 转换方法// 以中国时区获取当前时间: ZonedDateTime shangHai = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));// 2021-09-17T11:46:26.960+08:00[Asia/Shanghai] // 转换为纽约时间: ZonedDateTime newYork = shangHai.withZoneSameInstant(ZoneId.of("America/New_York"));// 2021-09-16T23:46:26.960-04:00[America/New_York] 转换为LocalDateTime时,直接丢弃了时区信息。 LocalDateTime shangHaiDateTime = shangHai.toLocalDateTime();// 2021-09-17T11:48:26.050 LocalDateTime newYorkDateTime = newYork.toLocalDateTime();// 2021-09-16T23:48:26.050 3. 计算航班时间某航线从北京飞到纽约需要13小时15分钟,请根据北京起飞日期和时间计算到达纽约的当地日期和时间 // 起飞时间 LocalDateTime departure = LocalDateTime.parse("2020-09-15T18:30:58"); int hours = 13; int minutes = 15; ZonedDateTime departureZoned = ZonedDateTime.of(departure,ZoneId.of("Asia/Shanghai")); // 通过ZonedDateTime计算并转换时区 ZonedDateTime arrivalZoned = departureZoned.plusHours(hours).plusMinutes(minutes).withZoneSameInstant(ZoneId.of("America/New_York")); LocalDateTime arrival = arrivalZoned.toLocalDateTime(); System.out.println(departure + " -> " + arrival); // 2020-09-15T18:30:58 -> 2020-09-15T19:45:58 八、InstantInstant是时间线上的一个点,表示一个时间戳。Instant可以精确到纳秒,这超过了long的最大表示范围,所以在Instant的实现中是分成了两部分来表示,一部分是seconds,表示从1970-01-01 00:00:00开始到现在的秒数,另一个部分是nanos,表示纳秒部分。以下是创建Instant的两种方法: Instant now = Instant.now(); //获取当前时刻的时间戳,结果为:2020-04-02T07:30:23.811Z; Instant instant = Instant.ofEpochSecond(600, 1000000); //ofEpochSecond()方法的第一个参数为秒,第二个参数为纳秒,上面的代码表示从1970-01-01 00:00:00开始后一分钟的10万纳秒的时刻, //其结果为:1970-01-01T00:10:00.001Z。 九、Period和Duration Period : 用于计算两个日期(年月日)间隔。 Duration : 用于计算两个时间(秒,纳秒)间隔。 Duration minutes1 = Duration.ofMinutes(2);// 2分钟间隔 Duration minutes2 = Duration.of(6, ChronoUnit.MINUTES); // 6分钟间隔 Period days = Period.ofDays(20); Period weeks = Period.ofWeeks(5); Period period = Period.of(1, 2, 3); //1年2月3天 P1Y2M3D ①Period参与计算结合plus()和minus()进行日期的加减 LocalDate localDate = LocalDate.parse("2020-05-10"); LocalDate date1 = localDate.plus(Period.ofDays(5));// 2020-05-15 LocalDate date2 = localDate.plus(Period.ofMonths(2));// 2020-07-10 ②计算间隔值,差值 LocalDate localDate = LocalDate.parse("2020-05-10"); LocalDate today = LocalDate.now(); Period period = Period.between(localDate, today);// 1年4月7天 // 相差天数 long between1 = ChronoUnit.DAYS.between(localDate, today);// 495 // 相差月份 long between2 = ChronoUnit.MONTHS.between(localDate, today);// 16 ③Duration LocalTime startTime = LocalTime.of(6, 30, 0); LocalTime endTime = startTime.plusSeconds(120); LocalTime endTime2 = startTime.plusNanos(6 * 1000000000L); long betweenSec1 = Duration.between(startTime, endTime).getSeconds();// 120 long betweenSec2 = ChronoUnit.SECONDS.between(startTime, endTime);// 120 long betweenSec3 = Duration.between(startTime, endTime2).getSeconds();// 6 ④Duration中的API 方 法 名 是否是静态方法 方法描述 between 是 创建两个时间点之间的 interval from 是 由一个临时时间点创建 interval of 是 由它的组成部分创建 interval 的实例 parse 是 由字符串创建 interval 的实例 addTo 否 创建该 interval 的副本,并将其叠加到某个指定的 temporal 对象 get 否 读取该 interval 的状态 isNegative 否 检查该 interval 是否为负值,不包含零 isZero 否 检查该 interval 的时长是否为零 minus 否 通过减去一定的时间创建该 interval 的副本 multipliedBy 否 将 interval 的值乘以某个标量创建该 interval 的副本 negated 否 以忽略某个时长的方式创建该 interval 的副本 plus 否 以增加某个指定的时长的方式创建该 interval 的副本 subtractFrom 否 从指定的 temporal 对象中减去该 interval 十、新API和Date、Calendar的相互转换LocalDateTime 与 Date 的相互转化由于 LocalDate、LocalTime 或者只含有日期,或者只含有时间,因此,不能和Date直接进行转化。 // Date 转化成 LocalDateTime public static LocalDateTime dateToLocalDate(Date date) { Instant instant = date.toInstant(); ZoneId zoneId = ZoneId.systemDefault(); return instant.atZone(zoneId).toLocalDateTime(); } // LocalDateTime 转化成 Date public static Date localDateTimeToDate(LocalDateTime localDateTime) { ZoneId zoneId = ZoneId.systemDefault(); ZonedDateTime zdt = localDateTime.atZone(zoneId); return Date.from(zdt.toInstant()); } 基本都是先转换成Instant,然后在进行互转 LocalDateTime localDateTime1 = LocalDateTime.ofInstant(new Date().toInstant(), ZoneId.systemDefault());// 2021-09-17T15:51:14.842 LocalDateTime localDateTime2 = LocalDateTime.ofInstant(Calendar.getInstance().toInstant(), ZoneId.systemDefault());// 2021-09-17T15:51:14.845 // epochSecond - 从1970-01-01T00:00:00Z的时代开始的秒数 ; nanoOfSecond - 秒内的纳秒,从0到999,999,999;offset - 区域偏移,不为空 LocalDateTime localDateTime3 = LocalDateTime.ofEpochSecond(LocalDateTime.now().toEpochSecond(ZoneOffset.of("+08:00")), 0, ZoneOffset.UTC);// 2021-09-17T07:51:16 (取UTC实际应该是这个值) LocalDateTime localDateTime4 = LocalDateTime.ofEpochSecond(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC), 0, ZoneOffset.UTC);// 2021-09-17T15:51:16 十一、旧项目JDK8之前的版本替换方案Threeten对于从Java 7或Java 6这些老项目来说可以使用Threeten ,然后可以像在上面java 8一样使用相同的功能,一旦你迁移到java 8 只需要修改你的包路径代码而无需变更: <dependency> <groupId>org.threeten</groupId> <artifactId>threetenbp</artifactId> <version>LATEST</version> </dependency> Joda-TimeJava 8 日期和时间库的另一种替代方案是Joda-Time库。事实上,Java 8 Date Time API由Joda-Time库(Stephen Colebourne)和Oracle共同领导。该库提供了Java 8 Date Time项目中支持的几乎所有功能。通过在项目中引用以下pom依赖项就可以立即使用: <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>LATEST</version> </dependency>","categories":[{"name":"编程","slug":"编程","permalink":"https://xu-ux.github.io/categories/%E7%BC%96%E7%A8%8B/"},{"name":"Java","slug":"编程/Java","permalink":"https://xu-ux.github.io/categories/%E7%BC%96%E7%A8%8B/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://xu-ux.github.io/tags/Java/"},{"name":"java.time包","slug":"java-time包","permalink":"https://xu-ux.github.io/tags/java-time%E5%8C%85/"}],"author":"xuux"},{"title":"Maven插件之spotify的docker-maven-plugin和dockerfile-maven-plugin,docker部署","slug":"Maven插件之spotify的docker-maven-plugin和dockerfile-maven-plugin,docker部署","date":"2021-11-17T02:38:59.000Z","updated":"2023-07-06T06:35:52.805Z","comments":true,"path":"post/dbaa3326/","link":"","permalink":"https://xu-ux.github.io/post/dbaa3326/","excerpt":"docker-maven-plugin能有效帮助我们在面对数量众多的微服务项目时,自动化构建和容器化部署,提高部署效率。","text":"docker-maven-plugin能有效帮助我们在面对数量众多的微服务项目时,自动化构建和容器化部署,提高部署效率。 docker-maven-plugin文档dockerfile-maven-plugin文档禁止转载!!!禁止转载!!!禁止转载!!!首发地址:https://x.xuux.top 一、修改docker配置修改宿主机的docker配置,让其可以远程访问 vi /lib/systemd/system/docker.service # 在ExecStart=后添加配置 ‐H tcp://0.0.0.0:2375 ‐H unix:///var/run/docker.sock 修改后配置如下: ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock 刷新配置重启服务: # 通知docker服务做出的修改 systemctl daemon-reload # 重启docker服务 systemctl restart docker 接下来测试一下看是否能连接到docker api ps -ef | grep docker [root@localhost ~]# curl -X GET http://192.168.122.22:2375/info {"ID":"XHRK:S4AH:7YDS:X43Y:BFCZ:XQ5N:VBIV:K6V4:SUVK:VFPM:JJYS:KZBB","Containers":7,"ContainersRunning":4,"ContainersPaused":0,"ContainersStopped":3,"Images":7,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","xfs"],["Supports d_type","true"],["Native Overlay Diff","true"],["userxattr","false"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","ipvlan","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","local","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"KernelMemoryTCP":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":true,"IPv4Forwarding":true,"BridgeNfIptables":false,"BridgeNfIp6tables":false,"Debug":false,"NFd":50,"OomKillDisable":true,"NGoroutines":55,"SystemTime":"2021-11-11T15:21:22.728142607+08:00","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","CgroupVersion":"1","NEventsListener":0,"KernelVersion":"3.10.0-957.el7.x86_64","OperatingSystem":"CentOS Linux 7 (Core)","OSVersion":"7","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":[],"AllowNondistributableArtifactsHostnames":[],"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":["https://pee6w651.mirror.aliyuncs.com/"],"Secure":true,"Official":true}},"Mirrors":["https://pee6w651.mirror.aliyuncs.com/"]},"NCPU":2,"MemTotal":2928689152,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"localhost.localdomain","Labels":[],"ExperimentalBuild":false,"ServerVersion":"20.10.7","Runtimes":{"io.containerd.runc.v2":{"path":"runc"},"io.containerd.runtime.v1.linux":{"path":"runc"},"runc":{"path":"runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"d71fcd7d8303cbf684402823e425e9dd2e99285d","Expected":"d71fcd7d8303cbf684402823e425e9dd2e99285d"},"RuncCommit":{"ID":"b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7","Expected":"b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7"},"InitCommit":{"ID":"de40ad0","Expected":"de40ad0"},"SecurityOptions":["name=seccomp,profile=default"],"Warnings":["WARNING: API is accessible on http://0.0.0.0:2375 without encryption.\\n Access to the remote API is equivalent to root access on the host. Refer\\n to the 'Docker daemon attack surface' section in the documentation for\\n more information: https://docs.docker.com/go/attack-surface/","WARNING: bridge-nf-call-iptables is disabled","WARNING: bridge-nf-call-ip6tables is disabled"]} 1.2 启动失败的问题启动报错Job for docker.service failed because the control process exited with error code. See "systemctl status docker.service" and "journalctl -xe" for details. [root@localhost ~]# systemctl start docker Job for docker.service failed because the control process exited with error code. See "systemctl status docker.service" and "journalctl -xe" for details. [root@localhost ~]# systemctl start docker.service Job for docker.service failed because the control process exited with error code. See "systemctl status docker.service" and "journalctl -xe" for details. [root@localhost ~]# systemctl status docker.service ● docker.service - Docker Application Container Engine Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled) Active: failed (Result: start-limit) since 四 2021-11-11 14:54:01 CST; 1min 14s ago Docs: https://docs.docker.com Process: 23534 ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock ‐H tcp://0.0.0.0:2375 ‐H unix:///var/run/docker.sock (code=exited, status=1/FAILURE) Main PID: 23534 (code=exited, status=1/FAILURE) 11月 11 14:54:01 localhost.localdomain systemd[1]: start request repeated too quickly for docker.service 11月 11 14:54:01 localhost.localdomain systemd[1]: Failed to start Docker Application Container Engine. 11月 11 14:54:01 localhost.localdomain systemd[1]: Unit docker.service entered failed state. 11月 11 14:54:01 localhost.localdomain systemd[1]: docker.service failed. 11月 11 14:54:13 localhost.localdomain systemd[1]: start request repeated too quickly for docker.service 11月 11 14:54:13 localhost.localdomain systemd[1]: Failed to start Docker Application Container Engine. 11月 11 14:54:13 localhost.localdomain systemd[1]: docker.service failed. 11月 11 14:54:18 localhost.localdomain systemd[1]: start request repeated too quickly for docker.service 11月 11 14:54:18 localhost.localdomain systemd[1]: Failed to start Docker Application Container Engine. 11月 11 14:54:18 localhost.localdomain systemd[1]: docker.service failed. [root@localhost ~]# netstat -ano | grep 2375 tcp6 0 0 :::2375 :::* LISTEN off (0.00/0/0) 1.3 解决启动失败因为修改的是/usr/lib/systemd/system/docker.service下的服务配置文件:ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock 那么就应该加载该目录下的system服务systemctl start docker.service [root@localhost ~]# systemctl daemon-reload [root@localhost ~]# systemctl stop docker.service Warning: Stopping docker.service, but it can still be activated by: docker.socket [root@localhost ~]# ps -ef | grep docker root 47375 18279 0 15:13 pts/0 00:00:00 grep --color=auto docker [root@localhost ~]# systemctl start docker.service [root@localhost ~]# systemctl status docker.service ● docker.service - Docker Application Container Engine Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled) Active: active (running) since 四 2021-11-11 15:13:43 CST; 37s ago Docs: https://docs.docker.com Main PID: 47559 (dockerd) CGroup: /system.slice/docker.service ├─47559 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock ├─47751 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 6379 -container-ip 172.17.0.2 -container-port 6379 ├─47756 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 6379 -container-ip 172.17.0.2 -container-port 6379 ├─47783 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 3306 -container-ip 172.17.0.3 -container-port 3306 ├─47788 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 3306 -container-ip 172.17.0.3 -container-port 3306 ├─47822 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 9000 -container-ip 172.17.0.4 -container-port 9000 ├─47836 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 9000 -container-ip 172.17.0.4 -container-port 9000 ├─47884 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 30443 -container-ip 172.17.0.5 -container-port 443 ├─47893 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 30443 -container-ip 172.17.0.5 -container-port 443 ├─47924 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 30080 -container-ip 172.17.0.5 -container-port 80 └─47939 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 30080 -container-ip 172.17.0.5 -container-port 80 11月 11 15:13:43 localhost.localdomain dockerd[47559]: time="2021-11-11T15:13:43.097948827+08:00" level=info msg="ClientConn switching balancer to \\"pick_...ule=grpc 11月 11 15:13:43 localhost.localdomain dockerd[47559]: time="2021-11-11T15:13:43.103540123+08:00" level=info msg="[graphdriver] using prior storage driver: overlay2" 11月 11 15:13:43 localhost.localdomain dockerd[47559]: time="2021-11-11T15:13:43.109459318+08:00" level=info msg="Loading containers: start." 11月 11 15:13:43 localhost.localdomain dockerd[47559]: time="2021-11-11T15:13:43.226585154+08:00" level=info msg="Default bridge (docker0) is assigned wit...address" 11月 11 15:13:43 localhost.localdomain dockerd[47559]: time="2021-11-11T15:13:43.944106192+08:00" level=info msg="Loading containers: done." 11月 11 15:13:43 localhost.localdomain dockerd[47559]: time="2021-11-11T15:13:43.973773238+08:00" level=info msg="Docker daemon" commit=b0f5bc3 graphdrive...=20.10.7 11月 11 15:13:43 localhost.localdomain dockerd[47559]: time="2021-11-11T15:13:43.973916900+08:00" level=info msg="Daemon has completed initialization" 11月 11 15:13:43 localhost.localdomain dockerd[47559]: time="2021-11-11T15:13:43.999505683+08:00" level=info msg="API listen on /var/run/docker.sock" 11月 11 15:13:43 localhost.localdomain systemd[1]: Started Docker Application Container Engine. 11月 11 15:14:12 localhost.localdomain systemd[1]: Current command vanished from the unit file, execution of the command list won't be resumed. Hint: Some lines were ellipsized, use -l to show in full. 二、修改防火墙其他centos7防火墙配置我这里是虚拟机,防火墙服务关闭了,所以无需配置下面的信息,生产环境自行配置 [root@localhost ~]# systemctl status firewalld ● firewalld.service - firewalld - dynamic firewall daemon Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled) Active: inactive (dead) Docs: man:firewalld(1) 修改防火墙策略,允许访问2375端口: #开放2375/tcp端口 firewall-cmd --zone=public --add-port=2375/tcp --permanent #更新防火墙的设置,使上面的修改生效 firewall-cmd --reload 查看防火墙开放状态: #查看所有打开的端口 firewall-cmd --zone=public --list-ports 三、使用插件3.1 docker-maven-plugin插件 官方Github (1)不使用Dockerfile <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>1.2.2</version> <configuration> <imageName>${project.artifactId}:${project.version}</imageName> <baseImage>jdk1.8</baseImage> <baseImage>xuux/small-jre8:1.0</baseImage> <entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> <dockerHost>http://192.168.122.22:2375</dockerHost> </configuration> </plugin> 执行 build 操作mvn clean package docker:build 执行 build 完成后 push 镜像:mvn clean package docker:build -DpushImage 执行 build 并 push 指定 tag 的镜像mvn clean package docker:build -DpushImageTag 注意:这里必须指定至少一个 imageTag,它可以配置到 POM 中,也可以在命令行指定。 指定方式有两种: 命令行指定如下:mvn clean package docker:build -DpushImageTags -DdockerImageTags=imageTag_1 -DdockerImageTags=imageTag_2 POM 文件中指定: <build> <plugins> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>1.2.2</version> <configuration> <imageName>${project.artifactId}:${project.version}</imageName> <baseImage>xuux/small-jre8:1.0</baseImage> <entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> <dockerHost>http://192.168.122.22:2375</dockerHost> <!--指定tag--> <imageTags> <imageTag>imageTag_1</imageTag> <imageTag>imageTag_2</imageTag> </imageTags> </configuration> </plugin> </plugins> </build> (2)使用Dockerfile<plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>1.2.2</version> <configuration> <imageName>${project.artifactId}:${project.version}</imageName> <!-- 指定 Dockerfile 路径--> <dockerDirectory>${basedir}/docker</dockerDirectory> <!-- 这里是复制 jar 包到 docker 容器指定目录配置,也可以写到 Docokerfile 中 --> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> <dockerHost>http://192.168.122.22:2375</dockerHost> <!--指定tag--> <imageTags> <imageTag>1.0.1</imageTag> </imageTags> </configuration> </plugin> Dockerfile文件: FROM xuux/small-jre8:1.0 MAINTAINER xu_ux test@email.com WORKDIR /opt CMD ["java", "-version"] ENTRYPOINT ["java", "-jar", "${project.build.finalName}.jar"] 执行命令:mvn clean package docker:build -DpushImageTag [INFO] Building image demo:0.0.1-SNAPSHOT Step 1/5 : FROM xuux/small-jre8:1.0 ---> 4fa1f55350fb Step 2/5 : MAINTAINER xu_ux test@email.com ---> Running in c8b2e5b012c4 Removing intermediate container c8b2e5b012c4 ---> 14353903927c Step 3/5 : WORKDIR /opt ---> Running in d8dfcf2f63a6 Removing intermediate container d8dfcf2f63a6 ---> 6e980ad6882a Step 4/5 : CMD ["java", "-version"] ---> Running in 9bae114258a6 Removing intermediate container 9bae114258a6 ---> 212ff76fe2a2 Step 5/5 : ENTRYPOINT ["java", "-jar", "${project.build.finalName}.jar"] ---> Running in 2f59b4c7ac0a Removing intermediate container 2f59b4c7ac0a ---> 07c262894893 ProgressMessage{id=null, status=null, stream=null, error=null, progress=null, progressDetail=null} Successfully built 07c262894893 Successfully tagged demo:0.0.1-SNAPSHOT [root@localhost jre1.8.0_311]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE demo 0.0.1-SNAPSHOT 07c262894893 About a minute ago 224MB demo 1.0.1 07c262894893 About a minute ago 224MB (3)绑定 Docker 命令到 Maven各个阶段github上已经很详细了,大家可以上去看看官方文档 <build> <plugins> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>1.2.2</version> <configuration> ...... </configuration> <executions> <execution> <id>build-image</id> <phase>package</phase> <goals> <goal>build</goal> </goals> </execution> <execution> <id>tag-image</id> <phase>package</phase> <goals> <goal>tag</goal> </goals> <configuration> <image>${project.artifactId}:latest</image> <newName>${project.artifactId}:${project.version}</newName> </configuration> </execution> <execution> <id>push-image</id> <phase>deploy</phase> <goals> <goal>push</goal> </goals> <configuration> <imageName>${project.artifactId}:${project.version}</imageName> </configuration> </execution> </executions> </plugin> </plugins> </build> 执行mvn package时,执行 build、tag 操作执行mvn deploy时,执行build、tag、push 操作如果我们想跳过 docker 某个过程时,只需要: -DskipDockerBuild 跳过 build 镜像 -DskipDockerTag 跳过 tag 镜像 -DskipDockerPush 跳过 push 镜像 -DskipDocker 跳过整个阶段 例如:我们想执行 package 时,跳过 tag 过程,那么就需要mvn package -DskipDockerTag 3.2 dockerfile-maven-plugin插件 官方推荐使用dockerfile-maven-plugindocker-maven-plugin:We recommend you use dockerfile-maven instead.注意:需要结合Dockerfile文件,设置环境变量 1.设置环境变量(必须)环境变量名称:DOCKER_HOST变量值:tcp://192.168.122.22:2375(根据实际IP地址来) export DOCKER_HOST=tcp://localhost:2375 2. xml配置 <plugin> <groupId>com.spotify</groupId> <artifactId>dockerfile-maven-plugin</artifactId> <version>1.4.13</version> <configuration> <repository>${project.artifactId}</repository> <tag>${project.version}</tag> <buildArgs> <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE> </buildArgs> </configuration> </plugin> 或者 <plugin> <groupId>com.spotify</groupId> <artifactId>dockerfile-maven-plugin</artifactId> <version>1.4.13</version> <executions> <execution> <id>default</id> <goals> <goal>build</goal> <goal>push</goal> </goals> </execution> </executions> <configuration> <repository>${project.artifactId}</repository> <tag>${project.version}</tag> <buildArgs> <JAR_FILE>${project.build.finalName}.jar</JAR_FILE> </buildArgs> </configuration> </plugin> 该插件的配置比docker-maven-plugin更简单 repository:指定docker镜像的repo名字 tag:指定docker镜像的tag buildArgs:可以指定一个或多个变量,传递给Dockerfile,在Dockerfile中通过ARG指令进行引用 在execution中同时指定build和push目标:当运行mvn package时,会自动执行build目标,构建Docker镜像。当运行mvn deploy命令时,会自动执行push目标,将Docker镜像push到Docker仓库。 3. 创建Dockerfile文件Dockerfile文件必须在根目录下,与pom.xml文件同级 FROM xuux/small-jre8:1.0 MAINTAINER xu_ux test@email.com WORKDIR /opt CMD ["java", "-version"] ENTRYPOINT ["java", "-jar", "${project.build.finalName}.jar"] 4. 其他使用方法:https://github.com/spotify/dockerfile-maven/blob/master/docs/usage.md 四、问题<baseImage>jdk1.8</baseImage> pull access denied for jdk1.8, repository does not exist or may require 'docker login': denied: requested access to the resource is denied 原因:报这个错是因为docker上没有jdk1.8镜像文件。 解决方法:换成xuux/small-jre8 <baseImage>xuux/small-jre8</baseImage> docker pull xuux/small-jre8 参考:Maven 插件之 docker-maven-plugin 的使用","categories":[{"name":"开发","slug":"开发","permalink":"https://xu-ux.github.io/categories/%E5%BC%80%E5%8F%91/"},{"name":"工具","slug":"开发/工具","permalink":"https://xu-ux.github.io/categories/%E5%BC%80%E5%8F%91/%E5%B7%A5%E5%85%B7/"},{"name":"Maven","slug":"开发/工具/Maven","permalink":"https://xu-ux.github.io/categories/%E5%BC%80%E5%8F%91/%E5%B7%A5%E5%85%B7/Maven/"}],"tags":[{"name":"SpringBoot","slug":"SpringBoot","permalink":"https://xu-ux.github.io/tags/SpringBoot/"},{"name":"Maven","slug":"Maven","permalink":"https://xu-ux.github.io/tags/Maven/"},{"name":"Docker","slug":"Docker","permalink":"https://xu-ux.github.io/tags/Docker/"}],"author":"xuux"},{"title":"SpringBoot集成企业微信群机器人(运维报警)","slug":"SpringBoot集成企业微信群机器人(运维报警)","date":"2021-11-09T01:40:40.000Z","updated":"2023-07-06T06:35:52.809Z","comments":true,"path":"post/7160ab8a/","link":"","permalink":"https://xu-ux.github.io/post/7160ab8a/","excerpt":"之前有集成过钉钉群机器人🤖报警,这次主要是SpringBoot集成企业微信群机器人报警。","text":"之前有集成过钉钉群机器人🤖报警,这次主要是SpringBoot集成企业微信群机器人报警。 声明:1.本篇文章不涉及任何业务数据2.禁止转载!!!禁止转载!!!禁止转载!!!相关文档:企业微信群机器人配置说明 1. 配置import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.List; /** * @descriptions: 配置参数 * @author: xucl */ @Component @Getter @Setter @ConfigurationProperties(prefix = "notice") public class NoticeProperties { private String wechatKey; private List<String> phoneList; } 这个key的获取方式:群机器人配置说明 yaml文件配置数据: notice: ####### 企业微信群机器人key wechat-key: xxxxxxxxx-xxx-xxx-xxxx-xxxxxxxxxx ####### 需要@的群成员手机号 phone-list: 1.2 Http客户端配置这里用的Forest ,感觉还挺强大灵活 官方文档,如果你喜欢使用httpClient或者okHttp建议你看看forest ;如果你更喜欢RestTemplate,那就使用RestTemplate。 POM依赖<!-- 轻量级HTTP客户端框架 --> <dependency> <groupId>com.dtflys.forest</groupId> <artifactId>forest-spring-boot-starter</artifactId> <version>1.5.3</version> </dependency> yaml文件配置日志打开关闭请参考自己的业务需要 ## 轻量级HTTP客户端框架forest forest: # 配置底层API为 okhttp3 backend: okhttp3 # 连接池最大连接数,默认值为500 max-connections: 1000 # 每个路由的最大连接数,默认值为500 max-route-connections: 500 # 请求超时时间,单位为毫秒, 默认值为3000 timeout: 3000 # 连接超时时间,单位为毫秒, 默认值为2000 connect-timeout: 3000 # 请求失败后重试次数,默认为0次不重试 retry-count: 1 # 单向验证的HTTPS的默认SSL协议,默认为SSLv3 ssl-protocol: SSLv3 # 打开或关闭日志,默认为true logEnabled: true # 打开/关闭Forest请求日志(默认为 true) log-request: true # 打开/关闭Forest响应状态日志(默认为 true) log-response-status: true # 打开/关闭Forest响应内容日志(默认为 false) log-response-content: true 发送Http使用前请注意 在 Spring Boot 项目中调用接口#只要在Spring Boot的配置类或者启动类上加上@ForestScan注解,并在basePackages属性里填上远程>接口的所在的包名,加入@ForestScansrc/main/java/MyApp.java @SpringBootApplication @Configuration @ForestScan(basePackages = "com.yoursite.client") public class MyApp { ... } Forest 会扫描@ForestScan注解中basePackages属性指定的包下面所有的接口,然后会将符合条件的接口进行动态代理并注入到 Spring 的上下文中。 友情提示:1.5.1以后版本可以跳过此步,不需要 @ForestScan 注解来指定扫描的包范围 发送请求的客户端 public interface NoticeClient { /** * 企业微信机器人 发送 https 请求 * * @param keyValue * @return */ @Post( url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={keyValue}", headers = { "Accept-Charset: utf-8", "Content-Type: application/json" }, dataType = "json" ) ForestResponse<JsonObject> weChatNotice(@Var("keyValue") String keyValue, @JSONBody Map<String, Object> body ); } 2.定义服务2.1 接口/** * @descriptions: 消息通知接口 * @author: xucl */ public interface NoticeService { /** * 发送错误信息至群机器人 * @param throwable * @param msg */ void sendError(Throwable throwable,String msg); /** * 发送文本信息至群机器人 * @param msg */ void sendByMd(String msg); /** * 发送md至群机器人 * @param msg 文本消息 * @param isAtALL 是否@所有人 true是 false否 */ void sendByText(String msg,boolean isAtALL); } 2.2 工具实现注意:这里的包名(com.github)填自己项目内的包名 import com.dtflys.forest.http.ForestResponse; import com.google.gson.JsonObject; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * @descriptions: 通知实现 * @author: xucl */ @Service @Slf4j public class NoticeServiceImpl implements NoticeService { @Value("${spring.application.name}") private String appName; @Value("${spring.profiles.active}") private String env; @Autowired private NoticeClient noticeClient; @Autowired private NoticeProperties noticeProperties; /** * 发送错误信息至群机器人 * * @param throwable * @param msg */ @Override public void sendError(Throwable throwable, String msg) { String errorClassName = throwable.getClass().getSimpleName(); if (StringUtils.isBlank(msg)){ msg = throwable.getMessage() == null ? "出现null值" : throwable.getMessage(); } ByteArrayOutputStream baos = new ByteArrayOutputStream(); throwable.printStackTrace(new PrintStream(baos)); String bodyStr = StringComUtils.limitStrNone(regexThrowableStr(baos.toString()),450); String md = getMdByTemplate(appName, env, errorClassName, msg, bodyStr); sendByMd(md); } /** * 发送文本信息至群机器人 * * @param msg */ @Override public void sendByMd(String msg) { try { Map<String, Object> params = buildMdParams(msg); ForestResponse<JsonObject> response = noticeClient.weChatNotice(noticeProperties.getWechatKey(), params); log.debug("WeChatRobo-Send Error:{} Status:{}",response.isError(),response.getStatusCode()); } catch (Exception e) { log.error("WeChatRobot-发送文本消息异常 body:{}",msg,e); } } /** * 发送md至群机器人 * * @param msg */ @Override public void sendByText(String msg,boolean isAtALL) { try { Map<String, Object> params = buildTextParams(msg,noticeProperties.getPhoneList(),isAtALL); ForestResponse<JsonObject> response = noticeClient.weChatNotice(noticeProperties.getWechatKey(), params); log.debug("WeChatRobo-Send Error:{} Status:{}",response.isError(),response.getStatusCode()); } catch (Exception e) { log.error("WeChatRobot-发送文本消息异常 body:{}",msg,e); } } /** * 构建发送文本消息格式的参数 * @param phoneList @群用户 * @param isAtALL 是否@所有人 * @return */ private Map<String,Object> buildTextParams(String text, List<String> phoneList, boolean isAtALL){ Map<String,Object> params = new HashMap<>(); Map<String,Object> data = new HashMap<>(); data.put("content",text); if (isAtALL){ phoneList.add("@all"); } if (CollectionUtils.isNotEmpty(phoneList)){ data.put("mentioned_mobile_list", phoneList); } params.put("msgtype","text"); params.put("text",data); return params; } /** * 构建发送markdown消息格式的参数 * * @param md * @return */ private Map<String,Object> buildMdParams(String md){ Map<String,Object> params = new HashMap<>(); Map<String,Object> data = new HashMap<>(); data.put("content",md); params.put("msgtype","markdown"); params.put("markdown",data); return params; } private String regexThrowableStr(String str){ try { // 注意:这里的包名(com.github)填自己项目内的包名 String pattern = "(com)(\\\\.)(github)(.{10,200})(\\\\))"; Pattern r = Pattern.compile(pattern); Matcher m=r.matcher(str); List<String> list = new ArrayList<>(); while (m.find()) { list.add(m.group()); } if (CollectionUtils.isEmpty(list)){ return str; } String s = list.stream().collect(Collectors.joining("\\n")); return s; } catch (Exception e) { return str; } } private String getMdByTemplate(String appName,String env,String errorClassName,String msg,String bodyStr){ String titleTpl = "### 异常告警通知\\n#### 应用:%s\\n#### 环境:<font color=\\"info\\">%s</font>\\n##### 异常:<font color=\\"warning\\">%s</font>\\n"; String bodyTpl = "\\nMsg:%s\\nDetail:\\n>%s"; String footerTpl = "\\n<font color=\\"comment\\">%s</font>"; String title = String.format(titleTpl, appName, env, errorClassName); String body = String.format(bodyTpl, msg,bodyStr); String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")); String footer = String.format(footerTpl, dateStr); return title.concat(body).concat(footer); } } 使用到的工具方法: /** * 限制文本描述 * * @param content 内容或问题 * @param charNumber 长度 * @return */ public static String limitStrNone(String content ,int charNumber){ if (StringUtils.isNotBlank(content)){ if (content.length() > charNumber){ String substring = content.substring(0, charNumber); return substring; }else { return content; } } return ""; } 最终效果:","categories":[{"name":"编程","slug":"编程","permalink":"https://xu-ux.github.io/categories/%E7%BC%96%E7%A8%8B/"},{"name":"SpringBoot","slug":"编程/SpringBoot","permalink":"https://xu-ux.github.io/categories/%E7%BC%96%E7%A8%8B/SpringBoot/"}],"tags":[{"name":"SpringBoot","slug":"SpringBoot","permalink":"https://xu-ux.github.io/tags/SpringBoot/"},{"name":"企业微信","slug":"企业微信","permalink":"https://xu-ux.github.io/tags/%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1/"},{"name":"运维","slug":"运维","permalink":"https://xu-ux.github.io/tags/%E8%BF%90%E7%BB%B4/"}],"author":"xuux"},{"title":"SpringBoot集成Nacos2.0.3","slug":"SpringBoot集成Nacos2.0.3","date":"2021-10-20T01:10:50.000Z","updated":"2023-07-06T06:35:52.805Z","comments":true,"path":"post/1828d9c7/","link":"","permalink":"https://xu-ux.github.io/post/1828d9c7/","excerpt":"","text":"本篇记录了关于Nacos-server2.0.3的搭建过程(VMware和CentOS7),以及SpringBoot整合Nacos,已搭建的可以跳过第一节看第三节的整合过程。 记录和分享也是一种学习的方式,希望大家多多探讨相关技术问题。 本文首发地址:https://xu.vercel.app/post/2021/10/20/ebe0fd672cfa55b6/ 一、搭建nacos-server2.0.3 官方搭建文档 下载编译好的包cd /opt wget https://github.com/alibaba/nacos/releases/download/2.0.3/nacos-server-2.0.3.tar.gz tar -xvf nacos-server-2.0.3.tar.gz cd nacos/bin 启动命令# Linux/Unix/Mac # 启动命令(standalone代表着单机模式运行,非集群模式): sh startup.sh -m standalone # 如果您使用的是ubuntu系统,或者运行脚本报错提示[[符号找不到,可尝试如下运行: bash startup.sh -m standalone # Windows # 启动命令(standalone代表着单机模式运行,非集群模式): startup.cmd -m standalone Centos7下启动提示[root@localhost bin]# sh startup.sh -m standalone /usr/java/jdk1.8/bin/java -Djava.ext.dirs=/usr/java/jdk1.8/jre/lib/ext:/usr/java/jdk1.8/lib/ext -Xms512m -Xmx512m -Xmn256m -Dnacos.standalone=true -Dnacos.member.list= -Xloggc:/opt/nacos2.0.3/nacos/logs/nacos_gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -Dloader.path=/opt/nacos2.0.3/nacos/plugins/health,/opt/nacos2.0.3/nacos/plugins/cmdb -Dnacos.home=/opt/nacos2.0.3/nacos -jar /opt/nacos2.0.3/nacos/target/nacos-server.jar --spring.config.additional-location=file:/opt/nacos2.0.3/nacos/conf/ --logging.config=/opt/nacos2.0.3/nacos/conf/nacos-logback.xml --server.max-http-header-size=524288 nacos is starting with standalone nacos is starting,you can check the /opt/nacos2.0.3/nacos/logs/start.out 关闭服务器# Linux/Unix/Mac sh shutdown.sh # Windows shutdown.cmd # 或者双击shutdown.cmd运行文件 # 或者kill -15 进程id 请求测试[root@localhost bin]# curl -v http://127.0.0.1:8848/nacos * About to connect() to 127.0.0.1 port 8848 (#0) * Trying 127.0.0.1... * Connected to 127.0.0.1 (127.0.0.1) port 8848 (#0) > GET /nacos HTTP/1.1 > User-Agent: curl/7.29.0 > Host: 127.0.0.1:8848 > Accept: */* > < HTTP/1.1 302 < Location: http://127.0.0.1:8848/nacos/ < Transfer-Encoding: chunked < Date: Wed, 20 Oct 2021 01:27:10 GMT < * Connection #0 to host 127.0.0.1 left intact 默认访问地址和密码 http://127.0.0.1:8848/nacos nacos/nacos VMware设置由于我的CentOS7是安装在虚拟机内(需要设置静态IP),若要宿主机所在局域网其他用户访问使用,还需要使用NAT功能,如果仅仅是个人测试,可以忽略该步骤 选择编辑-虚拟网络编辑器 选择更改设置,授权 选择NAT模式下的NAT设置 添加端口映射 使用添加按钮,增加端口映射,另外需要额外开放9848 9849端口,我的虚拟机是192.168.122.22,大家的可能有所不同,如果是动态分配的,还请将centos设置为静态ip,再尝试NAT 当nacos客户端升级为2.x版本后,新增了gRPC的通信方式,新增了两个端口。这两个端口在nacos原先的端口上(默认8848),进行一定偏移量自动生成. 端口 与主端口的偏移量 描述9848 1000 客户端gRPC请求服务端端口,用于客户端向服务端发起连接和请求9849 1001 服务端gRPC请求服务端端口,用于服务间同步等 二、Nacos简单使用登录上我们刚刚启动的nacos(nacos/nacos),大概界面如下 由于我们是单机版,所以集群列表里面只有一台服务器 2.1 新建命名空间新建一个命名空间,待会儿,我们整合时就使用这个名为springboot的命名空间 2.2 新增配置Data ID在 Nacos Spring Cloud 中,dataId 的完整格式如下: ${prefix}-${spring.profiles.active}.${file-extension} prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。 spring.profiles.active 即为当前环境对应的 profile,详情可以参考 Spring Boot文档。 注意:当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension} file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 properties 和 yaml 类型。 Group默认为DEFAULT_GROUP,可以对不同类型的微服务配置文件进行分组管理。配置文件通过,可以用作多环境、多模块、多版本之间区分配置。SpringCloudspring.cloud.nacos.config.group=GROUP SpringBootnacos.config.group=GROUP Namespace推荐使用命名空间来区分不同环境的配置,因为使用profiles或group会是不同环境的配置展示到一个页面,而Nacos控制台对不同的Namespace做了Tab栏分组展示 添加如下配置: yaml文件如下: server: port: 8082 servlet: context-path: /demo session: timeout: 7200s # 开启gzip compression: enabled: true mime-types: application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css,text/javascript min-response-size: 2KB 添加完成,为后面整合做准备 三、SpringBoot整合Nacos2.0.3 [官方文档](Nacos Spring Boot 快速开始) Nacos 2.0.3版本发布,继续提升集群稳定性及升级稳定性 官方demo 注意官方博客中的【客户端支持2.0】,所以这次我们整合的的包应该是0.2.10,因为版本 0.2.x.RELEASE 对应的是 Spring Boot 2.x 版本,版本 0.1.x.RELEASE 对应的是 Spring Boot 1.x 版本。 目前Golang、C#、Cpp、nacos-spring-boot,客户端已经支持了nacos2.0的grpc能力,欢迎大家试用 nacos-spring-boot v0.1.10 nacos-spring-boot v0.2.10 nacos-sdk-go v2.0.0-Alpha.1 nacos-sdk-csharp v1.1.0 nacos-sdk-cpp v1.0.8 Python的多语言客户端正在紧张开发中,相信很快能与大家见面。 Nodejs部分欢迎社区小伙伴一起参与建设~ 3.1 注意事项版本问题整合过程中发现nacos客户端在初始化NacosBootConfigurationPropertiesBinder时,如果spring-boot版本太高,可能报错,本人从2.5.x降级为2.3.12.RELEASE才正常 nested exception is java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata 服务器读取nacos配置出错如果nacos的配置中有中文 直接运行jar可能会报错 编码不同导致的 org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputExcept 解决方案是 java 命令增加 编码选项 -Dfile.encoding=utf-8 整合其他cloud的客户端如果你并不是使用nacos-spring-boot v-x.x.x而是如下配置 <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>${nacos.version}</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>${nacos.version}</version> </dependency> 注意bootstrap.yml不生效的问题,可通过以下方式解决 在启动时增加环境变量的配置 spring.cloud.bootstrap.enabled = true 或者 spring.config.use-legacy-processing = true 3.2 配置管理demo项目地址:https://github.com/xucux/nacos-config-discovery-demo/tree/main/nacos-config-discovery-springboot 注意:我这边没有配置启动类中的@NacosPropertySource(dataId = "example", autoRefreshed = true)这个注解 application.yml spring: application: name: nacos-config-discovery profiles: active: dev config: use-legacy-processing: true application-dev.yml namespace: 24373c8c-3894-419b-83eb-b6b9433a9721就是上文中创建的名称为springboot的命名空间 nacos: discovery: server-addr: 192.168.122.22:8848 username: nacos password: nacos config: server-addr: 192.168.122.22:8848 username: nacos password: nacos namespace: 24373c8c-3894-419b-83eb-b6b9433a9721 data-id: nacos-config-discovery-dev.yaml auto-refresh: true group: TEST_GROUP type: yaml bootstrap: enable: true log-enable: true 启动项目,控制台输出 3.3 服务注册使用注解@PostConstruct,在服务启动后自动向Nacos服务注册 @Configuration public class NacosRegisterConfiguration { @Value("${server.port}") private int serverPort; @Value("${spring.application.name}") private String applicationName; @NacosInjected private NamingService namingService; @PostConstruct public void registerInstance() throws NacosException { namingService.registerInstance(applicationName, "127.0.0.1", serverPort, "DEFAULT"); } } 启动应用后,在Nacos管理界面可以看到新注册的服务实例 3.4 服务调用模拟调用获取学生信息 创建一个项目nacos-config-discovery-producer 详细工程地址:https://github.com/xucux/nacos-config-discovery-demo 主要使用nacos-config-discovery-springboot调用nacos-config-discovery-producer 启动两个工程,可以发现nacos上已经出现了两个服务: 在nacos-config-discovery-springboot中创建一个Controller,作为对外api和接口消费者 @Slf4j @RestController @RequestMapping("/student") public class StudentController { @NacosInjected private NamingService namingService; private RestTemplate restTemplate = new RestTemplate(); /** * 获取学生信息 * @param id * @return */ @GetMapping("/info/{id}") public Object getStudentInfo(@PathVariable("id")Integer id){ Map<String,Object> info = new HashMap<>(); info.put("id",id); info.put("info",queryStudentInfo(id)); return info; } private JSONObject queryStudentInfo(Integer id) { try { if (namingService != null) { // 选择user_service服务的一个健康的实例(可配置负载均衡策略) Instance instance = namingService.selectOneHealthyInstance("nacos-producer-service"); // 拼接请求接口url并请求选取的实例 String url = "http://" + instance.getIp() + ":" + instance.getPort() + "/demo/student/detail/"+id; ResponseEntity<JSONObject> entity = restTemplate.getForEntity(url, JSONObject.class); return entity.getBody(); } } catch (Exception e) { log.error("查询学生数据失败", e); } return null; } } 在nacos-config-discovery-producer中创建一个Controller,作为服务生产者 @Slf4j @RestController @RequestMapping("/student") public class StudentController { /** * 获取学生信息 * @param id * @return */ @GetMapping("/detail/{id}") public Object getStudentInfo(@PathVariable("id")Integer id){ Map<String,Object> info = new HashMap<>(); info.put("id",id); info.put("userName","test"); info.put("nickName","小明"); return info; } } 调用接口 [root@localhost bin]# curl -X GET http://192.168.1.212:8082/demo/student/info/1 {"id":1,"info":{"nickName":"小明","id":1,"userName":"test"}}","categories":[{"name":"编程","slug":"编程","permalink":"https://xu-ux.github.io/categories/%E7%BC%96%E7%A8%8B/"},{"name":"SpringBoot","slug":"编程/SpringBoot","permalink":"https://xu-ux.github.io/categories/%E7%BC%96%E7%A8%8B/SpringBoot/"}],"tags":[{"name":"SpringBoot","slug":"SpringBoot","permalink":"https://xu-ux.github.io/tags/SpringBoot/"},{"name":"Nacos","slug":"Nacos","permalink":"https://xu-ux.github.io/tags/Nacos/"}],"author":"xuux"},{"title":"详解java中的byte类型","slug":"详解java中的byte类型","date":"2021-10-12T01:10:57.000Z","updated":"2023-07-06T06:35:52.833Z","comments":true,"path":"post/a1877a46/","link":"","permalink":"https://xu-ux.github.io/post/a1877a46/","excerpt":"byte,即字节,由8位的二进制组成。在Java中,byte类型的数据是8位带符号的二进制数。 在计算机中,8位带符号二进制数的取值范围是[-128, 127],所以在Java中,byte类型的取值范围也是[-128, 127]。","text":"byte,即字节,由8位的二进制组成。在Java中,byte类型的数据是8位带符号的二进制数。 在计算机中,8位带符号二进制数的取值范围是[-128, 127],所以在Java中,byte类型的取值范围也是[-128, 127]。 取值范围分析为什么是-128到127 首先1字节8位,还要加上符号位,这个好理解比如:0000 0001 表示10进制 11000 0001 表示10进制 -1 但是上面的这个负数并不是计算机存在内存中的数据,存在内存中的数据是原码的补码,实际存的是: 关于原码补码反码看这里 1000 0001 原码 1111 1110 反码 1111 1111 补码 这个才是-1 那最大的补码是: byte的最大正数就是 01111111(最高位必须是0),也就是 127 那最小的补码是: 首先是负数 1000 0000 (补码)-> 1111 1111(反码) -> 1000 0000(原码) 还原原码后 -128 1111 1111 (补码) 还原原码后 -1 由此我们可以看出来二进制从 00000000 到01111111到10000000到 11111111 即 十进制从 0 到 127 到 -128 到 -1。 int a = 0; int b = 127; int c = -128; int d = -1; System.out.println(String.format("%8s", Integer.toBinaryString(a & 0xFF)).replace(' ', '0')); System.out.println(String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0')); System.out.println(String.format("%8s", Integer.toBinaryString(c & 0xFF)).replace(' ', '0')); System.out.println(String.format("%8s", Integer.toBinaryString(d & 0xFF)).replace(' ', '0')); 输出 00000000 01111111 10000000 11111111 原码,反码,补码注意:我们这里举列的原码和反码只是为了求负数的补码,在计算机中没有原码,反码的存在,只有补码。 一.原码1.正数的原码就是它的本身 假设使用一个字节存储整数,整数10的原码是:0000 1010 2.负数用最高位是1表示负数 假设使用一个字节存储整数,整数-10的原码是:1000 1010 二.反码1.正数的反码跟原码一样 假设使用一个字节存储整数,整数10的反码是:0000 1010 2.负数的反码是负数的原码按位取反(0变1,1变0),符号位不变 假设使用一个字节存储整数,整数-10的反码是:1111 0101 三.补码(再次强调,补码才是在计算机中的存储形式。)1.正数的补码和原码一样 假设使用一个字节存储整数,整数10的补码是:0000 1010(第三次强调:这一串是10这个整数在计算机中存储形式) 2.负数的补码是负数的反码加1 假设使用一个字节存储整数,整数-10的补码是:1111 0110(第三次强调:这一串是-10这个整数在计算机中存储形式) 四.在计算机中,为什么不用原码和反码,而是用补码呢?因为在使用原码,反码在计算时不准确,使用补码计算时才准确。 1.使用原码计算10-10 0000 1010 #(10的原码) + 1000 1010 #(-10的原码) ------------------------------------------------------------ 1001 0100 #(结果为:-20,很显然按照原码计算答案是否定的。) 2.使用反码计算10-10 0000 1010 #(10的反码) + 1111 0101 #(-10的反码) ------------------------------------------------------------ 1111 1111 #(计算的结果为反码,我们转换为原码的结果为:1000 0000,最终的结果为:-0,很显然按照反码计算答案也是否定的。) 3.使用补码计算10-10 0000 1010 # (10的补码) + 1111 0110 # (-10的补码) ------------------------------------------------------------ 1 0000 0000 # (由于我们这里使用了的1个字节存储,因此只能存储8位,最高位(第九位)那个1没有地方存,就被丢弃了。因此,结果为:0)","categories":[{"name":"编程","slug":"编程","permalink":"https://xu-ux.github.io/categories/%E7%BC%96%E7%A8%8B/"},{"name":"Java","slug":"编程/Java","permalink":"https://xu-ux.github.io/categories/%E7%BC%96%E7%A8%8B/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://xu-ux.github.io/tags/Java/"},{"name":"Byte","slug":"Byte","permalink":"https://xu-ux.github.io/tags/Byte/"}],"author":"xuux"},{"title":"hexo常用命令和Front-matter以及配置","slug":"hexo常用命令和Front-matter以及配置","date":"2021-10-10T01:20:35.000Z","updated":"2023-07-06T06:35:52.809Z","comments":true,"path":"post/c541a81d/","link":"","permalink":"https://xu-ux.github.io/post/c541a81d/","excerpt":"有东西被加密了, 请输入密码查看.","text":"6e10c03224e9194b6237dcea130985e0060b5af50646539262f5042c250b49c752adcc924a9181b2a62f140bb7ac0c7e76ead901c517c293a8b7c8b2d138aa9bb2a5cce2d3a0c7fc790feff9564eb1af054009a82d5dd055da437ed2dad1c5ef414630915cb7f9fdfb88ce33adecb14659f1f0aa980ba84b697020e262d0560d7cdb3b753eb7e254e55953ebededf6ceded6f230aef199d39667751efdc5aedbef7e92ccb72ae31fed7089cfdb6667d05b6a8849352cf04220916531109510fb3be8993629ab5cf7308faaa81e074b440542f0a8a89881fa3ea35599cd4c35575b62d3f521efd5ca11120636c25ffc6447133729888f5bf3db1933c651b4372d98e9dd5d96b77bd3d8ca9cee10af52f4ac8f44ff7a9634051d207188812a173521b5e06eccaf00444f0ca101f31c55ca7f30860ffcad8a870976223f99f322a5eebacb2028a6786b0907d62dc9c44d153806514e7a00040d33d435075f17a60ede0b17def1f36186eed84720a34835fda34ed7f68a858690ec399204bb4e93f8793bc581f3e9ad7920f5e51f42507ad2ff52a3ed8369011f8d2f092e2dc8c1f8120af6fa5dd5952481abc71aa2ab37cce4c4781d85cfa1a85cfc7b76899790a6ef1ad4bab4daf128f07b24e50ce2ccd4ea7d6577196b2120230a8394c5dab0d3b5ad0e855d71a92d1cb5b6acbb3acf2dd4341028e47313f769d5e7d22731946c76b6eb8bad1a0e78af6b7384cfb29a73f1449e04f8923cecd48578c5434c6b3e8a22d1986917076297b46ae9511118edd0a0d0f621fb876d8136e43a9a568c8110f35ce1c524801dfbdc39554e2edcb377880c42c76cf44b993a4749709ce55e039ce529a400a6ed95cb4d6d2c4d1e30dffcf69cb6463023c5d9c9f785df44096dd368530fd8b93ad44657629fb2a40e072dbc9704838b0024266764aaea4288ba30b408a0a6c66393e55a3945f81b1f4ba2a9d589ce1967957a37d1542405908a9c57ab2a2b9e1c52f4299d5dc5836a7fe87f46d28b58e822979de9db9920668d6a1d8effa6123ec9169f1ea8287e0db281da988d3ab0333b004588a7487e49be066ff8e2a114415d85c89ad3ee84f33d3d9e8fddc3da1cbbd99964daad1086e0a0da585e764b4c0355a607d1c357ba1c8dff7ded0d114d63507eec55fdd0bcaebdb22ce5b847365be98b780c2dc272c36da5a520aef537d548fe32a823adbddaca8374462ab6b4bbf336d5552685b73d219856c5cde7fb9f79ab7c8d42bdb6b6d99fbe358e26909087abc749d1391699992ba312aabe4e1896e31cb00f566d8f13fe64fd2ef14a9eb306f6195330b89883188936387f6af5a7c2ba6268000db4947635fbcb9cd4caaf9f44e696e2ed373a8bd0b579281e4230a0be68ba01675b24981ce13aa16fde84ad733336eb3d9a95044012c4fb91132621f51cc9fe15d5ceef5bd7ddb9494578fc4e27940773cca384a7e3e6bb41f5a4ac27c165b457d39ad3f10ea4840c9dc6f8cceea5c661b8a30b5aac075d8435d86470fbc6d51e0099d1953b907ed0ea9a7bd75ad4e9db3a35917a566ac6b91a3dff929a72285d9ccc8d3e7651716b5ebc042f67c2a98fe48ef7099f9eae4a961c3124c93e1ec9ab2931e8e79afc023c9e6a5199c9033b64f37e3948933294541d4af21c9bfd620ae351d4676375b9ccc7a97863c18eee3dc439f7a0af6003866eefd6950b0230666ae627e73d34422a9e806b79318bc875408663b2b3d883447e0eb89ad38adf72e5dd28071628e0609cef059e578aa090c1fa068a9ac90b3c507d3f216a4276d4bcdbcd973c2ffd62301f46f6b5c63aa6cda776cb45d8c96325096eb1377f95a726e72abecb36ca6f84c3745b6422ed4c4c37ec1fdb730f2fb5400c9c0e7d5d77bf114254940928ecf0e3c3cbcd967bb75cc52d2f6274c549d1422eb35bd5fdcb50153ee841e1e9fb7e355a5ef06a99532f816362e90a191170c31a8c85a966d76c237341d033ded3b5eb05176a2d465cc408012b0738699ef658085f3c9c79bb7bfe013824ec9d3dbee9aab61fdd2a2f8115c077b86fa1ac7359dbea0faf8254bfb3d53db221694fde985421a71eb8f5d048023d3f12a3d6150c5826a5065f52a95cd5bcca03fe3491a987bbeca0197c57a4f58d033a7d62458efa3a0354a3a0c8e25fbcfb78e965ec47ca0a4e8b7db6c74210013f57231cbfcc514233036e179f08e280abfcf2a4e4dc652bd63c38d7c6c87f61c47abc2910728e02b8b931f39c5a75e0df099e04db152bc66a0150893c2d8f4e68b6f587d7297d040298f3dabcedaf730d82aaad5b60daa1bcdf534909d7f6c6335a4f7815b489e5bbe0f6f8ede9427962f7a75c477b00adeede246cd614877baefa739f11cb8f25d97bfe231a3783e36e32d6c6e889105beba39649a9408ff9f9a51a60ad169206902b13895f6d89d1a28dffb6da6e34d4c667c1649987963681acb1d26c7aaac4548831e55e95cfd41939acbc2bfe3e38238a335d8fc37f1d3d6a940419c39f44c1b2d2d9dd616994fa6a855d33d44bc9654f3a4f7762106571b022f65d294a1e9f3b4ae70513c676f91a3b7373d53f81a7ab22a6c94a203cf8609f110ac5cee0f57b670ec64248667f730e15465741b26a0659bd9ee5a5b454802cc44c097395f5e68b674786c4501b3a8e4fdba8410e1d97432844233ba8dcd5f52a6ba2c92aaaa11709f3b12899190fd396711fb4b0149d182ed03f44ddd641720c32766ffc71586269e1435214c812d79161c1cc509131cb57b0ab677d6cd84b2ac964db418791ba58351bb6740dcd2478747dda0e887918a338d7dcd7356cfa7fb5dfa3c405a02c09d8f21ea0f28e8dd69787abcc5386149d48319c8b62c727492ea215dad213acc99dbe146f562b7e399e0e66c849769e2531160a8a57176eb64f793e19fdf819a657f1913fa5090cba5377655fe550fd67936678fbabe994f8bc848e12da81fe217cda4debd065148ad71f2ebf7d9c1e19fd671e9e9aa88c39524988c6251fdb0c36103b1af30f4742096ea4d490e5e14dfe9842dcbd21c0d68bd5ae0e2f21016856f69ba9ff608a14732ec207e77ff7d73b3a635f1af997fb8b9acc36e53a4d70a02788d81d851a7eec6e9e1dc4a92e3227c133be36199e6c5a6ac7d5ac460b4ff74e911613158e42d00504a5b72ed4b1c631d743635dcdc8dc19acb1e28f7dcc50c51a84469fbf84cd4157f681fd558968563f2ef2f1ba5fee85a6289b8602b8ee984f1b50f3e80cfccc071a21e050e5a8dc0bb276dda52ca36da2d235b457978ad3aeb94f1d307e5f01c916b484660ef9d1d43100082ffe8fac7b3c313c63502af12694866d84ecef3feeb7d9e6ec5ceec4c169f5f7eb4e2a202aad306f6499fb7043de6d8c57ac67e4f469ace082ae97f91b7acd912fb2714615c97d2f2f317689c19ba21c2f3472438cd5e3832d93b65c8230b3483ba8be9985995534d98c730affa83c31a25f286573259b0812c0195fcb4811d2c43ea421b859f9879552bcb8ad2a1374c8efb9a26cd7263f9127383f9cd94c9331980a77e913fc12f1f56257b4fcdbe0c76662de9af6c006ed14aab4820ce37676c6ddbfb33f9211bbae6714613ffc3517fd583c7d10b446259341a07eb44273adf230d54852990a47c8996806a1028e6f02d83ee75c317326d18dad9d470ced258cc72ee09b27f600687d998b3501b34df41809c4b1fb38c90158a8679c7615f48b0e23d773b89ca773be4c5fac8abe499468a0f0d0b6c1b4ca2426cae26bd73fec61ea8c24433054fffcb4d55acf51ff44bd257d92e22a9f21a58776cb7d77d23b9cba547be5779b6e8fc0baa33b4ee65aacc71ffb02e51b06de5a01c6129889d34066b3f145e54d2a57dc7bdf30db13b080f1a2815523335b0377a93530b464d5aafc619476372866c969b5f9218ce9cfc9ad56d6c7e072c0db50acc6e9365f3c389e3e583c02e3abc38b47acd37c1df94ec07728906122a21e8af14077d1717fd432be866be3cf81136532bbd1e56000167249a1f3dde7ee1024666dfe6b97b820c2549e36527294182d1bdad948be14945c344f77df7d648a1c0ed3e8ee365f8c23d604873c2be12ff38b1dfc89403fd8566d403d96145d6f5beb0dbd1f10bb0960cbe55eb4464e52fdd192c2377718098663c08f4ef7866f3e0356fb8e60e61a9f5219482ff8ad73c4df133fb176ae03ac6ad2784d38166da05266cc03dd74c294436e795c3d5cdb22006647c106f81c311727f23f15cd1670142bfaf324d7818ac7419e55aabb3addab58e541714d51cc2b67e2d2ed5d94622a0a7e8e57f2c79ec87a721ea32b717be89ad7e1af230522a38b79e7a0b515c9832f32854d1e2fd0f535bc4ec5e53b83662220c38a8123e9dc94a67a6d65666d5a0919de13b406a2dc9892d7ba559bff41f932128704fbddeb30cd2c200a6e10455718d589ff60780b1442b0aef0d2184b5442510c585579a1faf1e6164338023a1cf90684ad8eddfaa9c1d5d2aee1e3622b32f804e73e0ba240e825858220cb70790207a0be240b1dca76e55663e2ad2c77b20041e4653abde3013be0b95f3986ad154bba0371b045a7589d9be9c61098248859a329034a1b88ef2ca44ac63793e42d04cbc29d53a807c8c97da296ca3c534c25fd03be1fad194b5ca51db54d8350d7c7fe19d421c403ed308a130d31763c83d11e2b77625eafadb198e2993b662221b8cb0f9c9ac575d38d4b3452689f1e1150a86a0b73361c50fb35cc84958b7720304f021c6b5685404a251d6369ea9f4269b3e286fd4ab5fe5bfa1c9e4ed76036f63885c419908619b1fc6e151a9e068824cf7b5cf2e5ce8a3783904aaf69380e544735fe9202dbd26ba67eaeae8d1bb3e889ad86f7fbdec7d1c1e6288bb79df82d05e9201edc3f89672f90904f1286d0093f083a2ec150e1d9b4121dad272e7a09f5e66dee6ebcc7fa6253d53d777f4b61eeac67042a3af54bca82129bff73f658863e7fd1c006a62955c2c81f9038723160c94f8afd68a353ff6793bff537c17ef734a853c338154272d734307bbf5227a3a2d5cbd651bb9ddf556a4c30e6e4e7818b69b427e0bf56f76041160dc2859ed1595e56cfadc3b5652b833324af5d9f2b8e6ff8231341bfba849c629de0469f7f18d893e66e584fa9f756b26fa65d6f1a9e8fe7fa9da275c1e1fcf76e6b88a438b19c722b03e0c37be4834731e1dc2fbfbb8b97f6d0f539efcd893900aeba229d5ac862ab1ca3fb7ad813f6b73ab6fa41931d3a323d3b211422e7abd9ec33d8370988d4bebc810e763ef7419447793892df392f490e6c93e7c2d5c93df66b8b000da6d4ea3746fc40553d03a9eb3a28dd5bf80274dc4d55478012c824c36877c36706959bee5574a38cc81e40bdeb30eb7b8fa82c51d32138e91b60dde23034f240eb2f215df32fe1cfc550199c88a21d90c5cfe463f5cde3250a0c00377486b0e974714c121243487018a6469c82a5eccb476ea7c1b67bcb20d437a62704fa3a50fa341f9c6971de99fb76e37a00dcf2048d29b3a23ed6637f93e776ac260856ca27d327759fbd63732e9f648fee7f5d0222c106bcaa8c60c1370850e5ce02de3ef4a235631f7d573217e5d83c3da1b1df045f58cf298bd326b1dcb4c44f1ac4a0881845a3604ed60d9c2d222a71f73ba5137d81bbafc78b3bd4c52bac76b003dc0db4d8172a4a26d2d147d602962ac237204b8b4746c45803d8f7cb03643c4c583632ae434517e0aec4cee528bcab49c8ff5213d16ebfea0b9fc7781823274b3fb7e2cd2a901ccdfe5e53de6a3b46c9152f276cefdbdf25d32705e7b98e8b816ebb20dfab0932468a8c925d59762c0d22ed05c27f8d04ff82188a8c2fae3b761ac0f29865cec5ab8fd6dcf2e50ba10f91ceba58fe619ab761184313bee5b3842e1ff8145297cdd8eea9aae31acd718e1bcf7ce7fda34643e6dfbef0320b853f3516d19165608d5ba6dcddec70ef0d3f58ea7b25643e35171011299db8aa3b3dcaebf007cd001215b02fd9c4ced85aa712c629b27a3362d6c5cb710ded958b3cd6d4bb3aed2ca4e1c34ba033783c3c6f44806873d0e500fb32e127119d5681403d425d376e38ac91b85d01e15c2f76c5fb96db0721b47e667dbd10a29a565824616b3778dada82b0407d9788ac11c01cd1bcf05bdb4895e952b4611ab71a69b4069d0b8e60e48181bbff9910e76d7a50b22c3bec8fa60b7a35de96c7ae6b9648f8fca6ad6354dcea0a8b161ab74da4acc6e91de7d75e0d27122ca640f1ff6883223bf191a43eb124da1323ce083a672d8d01320829d12dd235417fb6b5f79ece888a2f75110b33479c4572330542fd1f8e01ffda0853c1e2b928da4f5d2ea00b0460f94fbe8b2e5b2cdd7ddab189ed1f6440df8fbf16da7960220e532a5795e62ef1a7d812c6101bbedc1ea6ade4bf6b61b56c0c8137eec007651c871d2360a17b6fb4122d1c5b3754bfcb16813cc45d66c1fc943bfcb93d0ee96d45657215c2fbf88ed44a0344ff91431e7b777096b0c31c55bdcd2a5c80a9d5e2217abbeb9d0d12711f2275f3912891feca0cd62f9cf19cac126be4260a16e18f9a5cc09f76e141d5f4cd485e244bdc1e4ae10bcf3762c9cc4867243caf562a15976eea679903b3645c00b6ffdd6c294be2ee7fefc23a88ed781fc26673d4388143c36b6974253bf80d22432e1cffca5b64058c6e091c6626dda2cb8b7c66ecf660ca1704d3fe189abe14b253db316690f07f6778537132815a0d0b06f06562aa1ab87fb7cab9e45a4a731a0506184536513a5d2fdb0668c62d5e8d9757f25c9a72b45b8c8e05fd604ecfca0a34be54d943b451b8835711a120adc1a1f8b43e018b055e4a4e65b21ddf580f550c9ed89b2bba553a74ec704a1340bc637ad7f639d0c0e435f68fb8d9b840251dffffef334406f2a3a9c0e10852fa846553002a738ab4f9b43643f50755eec32d2df881e302cdd86912a0cebf5e825716e9b5dcee408ea2a23dadfaec453109b46db87b745b5a0e305202093f467624d8be7c693ad1dd582d3de390f053e8d9b8317eb0d17fad1ce669e058496368f4b43711f0264f167772047f2b81df2021cdf4f184cd746f51463574bf7081907fabeff25366aa5544a64b32a0552b159a63430f114a1434a132ee052d58ca47920baa1190a2459ecaedfbf92a7784570ddc9b6aac762ed8f9c197545413239fa1dc71e7cb10cc68919b7a63c15bf787e82303b0ddc2638ee9c28563a4889a3392782d47c21a018b7353ab3a9ad7cee80167fe969b8bb09eb8712e58b20715aa89e743dbeb711c0447a11092c682905153bd0237d597d9dcd7ad82fda0a43b6185702f3778564684c949a38dadeecf2497baf2af97f3425066b1478fafb398f032dac63562631d34fbcd9fba95740625d3806d91c5d3d164f8f160fdae1533bda74d14f0adfc0da8e837ef098a951fa2cb009f6d690a1712d91ca0a66bc01274b7c8a8a64c47d917130f84beaa535c5c6a03c04ee4efefbc189e18fd2a83411ef5988efd6fb46a58fdccb2f2387174b7e839aa6a414913ec2c0a87dea67eccc3c072f0d1e31001d76639b4f31dd509acf265fb559ea86d2d3485a2a5413f6b8745d9ba95c85fd8a931c1f2a46306ad3ef231d939b30ac22aada751742aae1e893d8eecd644771ef21f659b84d5a5d70a8a8a3bf6c3dd61c5717a44a9cb1887a764df7e7204391bd650298ed812e8e0e530f68a11d4347726f004d7124d1c597609bb65ec7fe7ee12f961e52b4de1e1611a3724f792b4afa2f425f4dbb893eb2eafecbacdf929457682e8d8a014836d13593053939097f49996ed792ad7df5c29e1fc2588ba8e8ac71d5a96502377bbdc40303846126b93ee3a9873f933b4efda8eb8f0be4103b85aafbbe3f715442e43a7fa30b04da7e545610d19cf97d401c28caea126c2cbab905df7e8d43097d2de04d9af8187b3a69c8b678622a0d66749ec1364f005d8ab7f4a5f5cf4ccc95b4b4b906b4d54cf2597663577ffe54fcc6327762aeaacd34c3fbc047c54ef0b4061721ce27df0e8325e3c8c75a216c5c0d114ec377cb8534bc075ccf292ac1e8f0bae09374989373ba6c8caf923e9354bf7e33e094b4c53d2718da879b8e43e6ce2d17cafe6b13346cdd17c6a225d12b56a31682bb707fc2d86e2e737148ee1f97e647d59b1080c95a1c113a3daf34a690d484174215349ffaab533c900702d0cdc9e767c4ed5dc008427e0fa57b23810f1d04bde17e8d5d7b11bd078401c798a41956c47febd0a8ed67afea491a8b090a3d2ee75b4668a335f3611ac9a7ee58dd2c9627163347b6f08cecf19e7bc13d79a9cfd432dd617addaf83a7b9b1a9f52f9da50bd974c471fd805e3df5bc481ebdfc815abb3401a904d34f95fdcc4a54d792c8799a77379453a351034b4bff2da55ed2033070dd9ed76ca9fd5c0a99792267cb613ba964e648e2114c4d22a07346288f03a96e0af4d9365ac129a41659e75de4f86bd2eb91fdca6280fb04fecbe78d68e93bb7b63fd8781c39e3ba4b01fede813520c400f417591c03f0e2ed448f0534f9b7ac53a1a8722efe9afae2c29baa3b020dc8c9c1c82b92479e005aeba5d2e388c817cd097d0cf0440075ade7c2881a66e23648619f49ff8225607812ce27784807e39cabeb6329b05c74745d8364b23b9ff422831cfca48356a28e1e43cf54a8614a98147d422a9e7a6769fefc2dd13474d5cc7038cb9eecfeaa6703a93a6ea048780d150c2574b692f051e9b7a9ea1325a3a2d3aaaf044b2c0a356e6085e15920f68e6bb424fd1acaddbd94bf23af7f82192474558b9228f7769b02a4b44fc9dd7e204699bb803ef54bc526f7822d8acf8d3b7cff0ab7839144ba82c2ea2912b4f52bf51c5d2183bc68b7cc6eb91129f0d6d0bd550cde54f6b119fc70d8609a818da621c34dc280efaa2837cd8d37e27b2c09b62153f9e52181828ccdde4c51e621eb6af5c6cd182752b2772b90cc470af92c6fedd6739097120cad01c461b410efbade162189e9d931e13af868ed2f5cf503eb455e227f6ee588eeadebf37806062b5dca68937ef1ccde75e7cd7c2c86cacfa00bf5542d18af0f950b5d1af42933c282d61cfc5edd1c2c38f34e4239f0282ff9b75c7df967ccd0487ff30bc42d2efba2d2ba134707f9bce24494d95078e0145001d2e5efc7a2aad7ffd81b394b9c20ba722b69a1c7a17d84d12a6308d7ef82c96f79f704e4ec3a3037c1221b6fb29cea891ec6bc6be45aacef19d4e1b5ad7cc47127153653ed5d7f8368a814233c7650160b701d92a42e875aee53962dcb56227bf2e7225f9dec1ee9e281713e74cf66ebd2db491dbeeefe35b0efc6eba99d5a8b147b9a2ed0b50247cf481eed88201586c60a48711914f522845b3d89f8ff4ffe54823e74523afa9b197d746bef53b0f6cb558d194ce89647fa46da2db94bc040570ab02f3ff7d10edbd59460b21c815d8e095c3881d37d6656358d62ae62e533535bff99bee96bb2a7724d410863c08baff615a12ea30e17bcc54081212db856392cbe11639b7bb2bdff2ef017f824e0ef6edef85beb93383d144a6be6f41f545f407e8445b9bb2fb6a4360be8f3981ac1a00427c2fffbac6aa3785d9bb73e92767ee5ccd0311a8ee6d7be4331af798b1465e2b83a9b52fe73f7bbe0fdbbf1381abceea2d56c12a63616e1dd5d5bd86cae5de2f424b1f959f2f2afea08486fa352ecc499d90a92ab3f5f41a59dd7b2f942fbb95742a89523dd7702f4da5873c97489762cd97193c2890d4058bedadb20e9c437e0624291a68b572e01d6d0e2e34d1601b634671523d4e33d0b8496995488885e5a968423c52ba6dd7ee13154c40c6d5a0f82aedcef2ac8f7097ecf6244fd3cb3558f4d96951d1e35bfb125eb550378b5dda4cffd348dbcff614616d8c0280b9448f367602d69971a9cddcb4ae8d1346dba2ca90f6446658d8bc814338f9bf59fe93cdd1b6a5ef58c436a70082acf7ba80fe21dcc5f95a33b0769ea922a7bd7dd6df3b26d3b51b88abdbd4ab12088b6eaab8e4e0c7ac5aaef6b3d4acbab5820564507fccdbfb49ad2f31f4d61e49b9d3383b53ae9d4bac1ff4328a195eeec670ce296c58fa2a73c7eef28af73af5f823545f74de2f376bdc3b06e1fda42c87de8af2821f8a3bd6f916dd6c798f55e49f19b8a61bb909f64b7f3d52b59cbbca4180a004a22e0f5863b20679f1c65547c885f42c1d6aecce281369f9dab3b3607fdc1a118ff6156c21ef8efa8d6651e435ce6a0836150c9b851758a6f052b9adc47ac09873576587862a8c87cdbafad47ac82b3285963c039340b4a09c7726fc4dedac943ff4af3831c7cd9d13672d7c196fca09f8e66061489f8937beec253a6e5e3e695ad04aa9e532960f7f696fbbd252beb58cc830dc7144c761c0c13205967283855ba2c5a61120d04de7e7d5cf98e8869e1d68ebe587058563370b74f8f9fd26fa7f456f0d83877e25ed950239819a31d4932066fbc49eb5365a0e7ef4037d8509f1b8df76f202056c29f762ffb6ded483d60d5159fcf8fa80563e636101e94f90e118095532e3b0efbed999a524d8be00a75089a9722d93176c4c4541b84686a207e163ab62f955206867b17d69179dc7eb2f15b35a52ba4e09e1a2aaacbc2c2de20faf8e12912ce9d00444011eabceb730e3750269b78ff3a929dc469a0603bc69cc5f974687d003ce2246ad443a9697d426f754d8fab367efd9a823d256d77c68714247d318855881c21fdb1b8fb2fc60fc3b979a1eee007f0ad09fb477dbdf773b67ea661a3986c20e44f57542ac2ff047f386baeb117450190736bbb273f98991c0ef8ca8692e61d00b46fdafff11c865f771563d1fe196e94fe620a96ac200a470afa3f426af3c7bfd062feb3a21bac6fceb3d1844306fa625364d5864a25def1f5d0de08671098fd3969c5477c5e3fea02932a4b572159748ca0afc19a2e41143caef2fc4554b9eabaaf9ba6a9dc4221597faf07b6ca4e621d4a49743f50bea2985d3813341c9dbfa1fdecb8ca21f576c6e0fe28e3c660c96c5f32d9978e80730dd777b420941e64a6d2058a99abc83a739cbe9b90b6601834eaaba3bb7600c1d7106dc215e0d323f5eb0e25213ce81aca78b543ea23674f0924a46306281c0efa1a160135902f3c2c87ac450b13bc3577f87c37d1009986cecab0e0db5308e2eb6487a36c84b279f151fa668aee467cc81c9437ad480e9a40af83f70d3017a4b4d6dde897495b00b503ee8731d68246a3b75003cead4a031ebe5287270a2166fba3f614df416c34aa8b106a11e956f014e54278997b1cdd5cf34414e79dba4d51931142ff35733f1ffd805a044465bf46838953cc2bf3f4dddd4cafc62936a33b8bbca80d2b73fe7306d07399735de177b80436244e6087ff3b497c7d9c50e9b38c9bf0bab3ddc3b7265e66e24656c3c7e2f55141ba7d69252c5936cc08cb8e7bc0d37fd5548997ec0d8dcc051f73223fe1aebc9b32e412dc87df1816287e50e17e00e2e10f07d37106064041316ea1e7bf319015c4f305ef97368ea998471b724f2730fb08dd6f616f6c73fb1d2860d7189e51d1a63ed33626b794126f37a7ba8e94627ede5407a5e7f9aa9280b77b37061010ce87f210238b85e80265e694812fc15bc953bd063c7d21bea108e11f2440b5d7d9632b49797c7185edf0d0b6818ee5c8318530e477da3a5afc787d4eeffd59f196ac1a28544021c1b943fe366dbc2e6539259549b2d0a59bb886ee50f9c37376cca84093f215001dc0a5e6a4ecfeeb60f7f8de452e62a5939f7b36c6a03613d9b7f0441d2a63b51220a5713f6bbf0dfff1d3a723c8d440886613a178c993f56e039fb5c625d83a01776dd7949755c8f0f13550a9d18e87bc3b690aace672ef2e83dd41512453a8c37445e4ddb6a37908ba631cad08f78c6607c2480259968d06bcd74f3e41446abd65e3a29643983e4a62682ef53c5ab9fabf4f69a4a513e9740a697f8c178040482b417cf0b8a7781a5afa52b5ca1308587a5962813ace212964d3c9846aadb3ca41432152482e04fce75155f94a6a0c1c3f7eb0d6d0ddd349ad1ee9a6823d23805dc88ff758ef17ec882f0d29915ba6238f1561b3d7becd11798548aee6cfd022d1afc24c052681cae4492dce10afc77419c7a23defa5e2b44f0c5495c56f6643c81162604c086945160837b759460645a3a66d5ad918929c70f63eeaea4c61d4b73dc7490b421ef08d82d59ce1bb13d8dee5c8e32718a722015a746a6fcd25c0b1202945b5b58cff91f08b5310c57d463a50638ebd51e335c2378b388b1803a868cf1cc3c08438963d18b0a94e49e2cfc5812660956a7808bad5b33007007c62716bdbbc0f3d726279038df91e96f09191e65989515a1e418d8f9a2e49f844088b6c23bf7db2c28dec11a0e12bb0e8ddba8ba68596ca62c995cf4706104ad67911b354d4923046ebd2ed781ad6f9cd68d1724ba0cfaad8b2305f19b1aafd709f5412cb1fb3501e2a1a689a0d62c0770ef9c3feb9b5dc22cc8c7a15486e478911f69ad82202e595f45b5d90b77ac04748322ade53b01fb7d8aa2f4f0b4d93cbaf2997991afe56af674e568c1e343fb3f200693d9cf355e31609f0738baa711fbad43594dae564b64a1eced880a1a2b8acd8ee4b767bafed2c41dfd19a1283c2bd437393e84a920965e1080ff9a57aa4f634f614100abd1a9c47bb2f5b1cb3ed63acb610e5927c11e11a9918ac08b18563238284fdb2d8581c8d192cb4bbd6c3e5324fb3b30e92722bd3200ee7fbff0e6201cf9515ae011d451cfd3d2ebf214c73ca122d3d3ad61a39c70938524b1acfd86c7998323a35453379b32cd824d75d6bbe4204276277809ee795e94ccd796b85d99432f559f458f2e943049e51912ecc62d2bb37ab8bc9a80e8998d2d04f8bd68d1915b00816458d50debf41818ae25f6c8d085a8f7cdb2305770dca569584f86156dae7be0781c65fc8f194351394c3c100dbb5c6d48b47717ee540197fdd563b46e0b14fa81c640a451aa221ef5443cd5fdf93b85c9acb1d78aa13827c553aee555f4a1c35c1c5fef4f15cd900e0a7de872c1656c4c12c506a9ee0b29193634d1f2718ca73778a8d60a8e0a0f476f9b3c5ffbacadcbc010a5678555610ab45d006507545d99e3ccfc75fbb2626877581e6fb721b1d234bfe8b0554b74992372e4d062e0fa4d42f252d176732838eb102afdc3a4e55284e3312126c108a754501c3bb9509fa0fc908661fbf389184ea6670c28355d7ef0deccdc0f2c6f90495c03cbcd6e05174ad3ad34962452dc3d8edc6e4d5d6f8d6c2f1881268940c02f87d0ffe82cce346e4bcac7cb236add85238be786c9a0a639bcae422985beaa7b8c4698b782188b7f897dc74da7ce79d01a69c5152e4b61ac1fe80c1bac2e099b81133b485e599d03826b1097e333f0b2801702780849b6a2b8af50af305cefc97e843aa43462dba09eb737e061cbb531ffd9bf3d745ed26120de3ab155927e94cef4dd642ba6b9c6673cec614aff11219f45c61d930326863b0e4b16071780a925adbc91c9a4f4c26e2510bf43134b537f12bb6b3983544b77cefaf8a333b09ab02490f6833c5e5dccd55cfe49ef55515ee72bf48633b9187a1cfa343a018300d7763a9c8aa4f4aa6a6ba64f1d68f4ab4581e26e945b1bc52f300527eba22d00d6f831d0e227b692f74a52d88ec5d2f7faf0f4414f11f725357ce971ac8b6870b852e9929bd6a8f90f2861740d604a4dba0f3876a9819066a337148bab485e4bb8ce47a6be46a95be4ceccc27eb90c6e6083e1ce1dd37e19e23832a66291a8e68217e31558f57e10b1bd567d22b22a8bd9cdfa79ecfa5ea1e525e730c234d34b4761fd1eefae95c9e85b5822fa147e54f310c36bdda7600d850a2b3c4b1cacba9224bcd2b237fe795b85282a69108c40220e1dd5ff4b1e5fbcd39cf5b7bdcb434ace9b60e693cb5a42b2304edf3228bbe82e9237ea790c57c8399a9a9d24b8c86564c5a460181ad00d01f6842cc4c840e515de07c6572ecbacb123ea24db07bf4bc84266c84087841e53e7176ec4b320cb5548350c68a16eedb6b78b462579424b356d6b446df76f32afa810923e0d79bb55640b50673d9b242b18759784ac0c506219220fc4e0225eabd6918781bde54710e64a0a02b45855ab8fa2dd4cd38adbb96a550bc777d1747ba150ef69c69fa46c0c790135d740b7303621493373020c7571438dc0b22f23f07d828798c12942124420ce3e742be9304f33b0f2f3ad6ddc05d90710b098a51516d7ad369f40a79d0f5254669092a7d37c75b1710dd77c54cb77dd0f698d2d67910be19513a3bfb5cd82ff70509460496caa5f2fe2e1216d2e9c7730fe365f760e443bcb7f24224a1a4a07eca0de7c17db9d2451306c4ec8bac5cd55b6e20e73437d4626581b9da81e9a9b8c8a86557f24311d64643582dc3aa0865a99170e6204ebec6d88d470a8723c02931cb5de2471808439dd10d86d9efc91813aff3894677ae5c131a63cbe6edfa2b1ef4a2311571c535996138507b4ee5429030d6589725c8e58bed0b9675214e7e2e4a15b0f39e263266541e72def6f88b0092a297636474fbb67372f0c62cb5187c953e4a25c1993fde17997445666268648967242e594fb25c324949df4318887b99160cd878b0e701d85552e9e34fb1e067985ea150e8cb739cc691f1f137d2dfe374485f958e810295faf4d72e3d057aeb2dc16d8d53e492b2a4ae3fca157a026b0d6b25656b1554aa9828eef6cd2fe2f804eeeb867ae9cfce5accd29a72810a8c052ffc4b4867565729d7f1e8e1198caf7bfa7fabee098ca593d749cbc1b8a929f 您好, 这里需要密码.","categories":[{"name":"搭建","slug":"搭建","permalink":"https://xu-ux.github.io/categories/%E6%90%AD%E5%BB%BA/"},{"name":"博客","slug":"搭建/博客","permalink":"https://xu-ux.github.io/categories/%E6%90%AD%E5%BB%BA/%E5%8D%9A%E5%AE%A2/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://xu-ux.github.io/tags/Hexo/"},{"name":"Blog","slug":"Blog","permalink":"https://xu-ux.github.io/tags/Blog/"},{"name":"加密","slug":"加密","permalink":"https://xu-ux.github.io/tags/%E5%8A%A0%E5%AF%86/"}],"author":"Xuux"},{"title":"hexo使用grunt实现自动化","slug":"hexo使用grunt实现自动化","date":"2021-09-27T07:28:03.000Z","updated":"2023-07-06T06:35:52.809Z","comments":true,"path":"post/47fc5844/","link":"","permalink":"https://xu-ux.github.io/post/47fc5844/","excerpt":"","text":"本文介绍hexo使用grunt实现一些自动化操作。 开发过前端或者node.js的同学对grunt应该不陌生,如果对grunt不熟悉可略过本文。 开始使用hexo来处理静态博客时我就遇到了问题,我的文章已经写了很多篇了,都是markdown格式,而且托管在github上了,然而hexo并不支持导入文章。 好在我发现只要把markdown文件拷贝到hexo里的source/_posts,hexo就会解析,我可以考虑直接把所有文章直接拷贝到这个目录。 但是另外一个问题又出现了,问题在于存在两份一模一样的源文件,一份我托管在github上,一份在source/_posts,后期文章改动的话,两边不同步,维护很费力。 后来想到的方法就是利用自动化工具来处理源文件的拷贝,博客的部署等一些操作。由于对grunt比较熟悉,所以使用了grunt。 如果对grunt不熟悉,可以前往grunt 网站 准备 切换到hexo博客目录 执行以下命令安装grunt cli: npm install -g grunt-cli 执行以下命令安装grunt: npm install grunt --save-dev 执行以下命令安装插件:npm install --save-dev grunt-bg-shell grunt-contrib-clean grunt-contrib-copy grunt-contrib-watch grunt-rewrite grunt-zip grunt-shell load-grunt-tasks 新建Gruntfile.js 新建raw目录 完成后,我的hexo博客里的目录结构是这样子的: grunt插件说明以下为使用到的插件: 插件 作用 grunt-bg-shell 在后台运行shell命令 grunt-contrib-clean 删除文件和目录 grunt-contrib-copy 拷贝文件和目录 grunt-contrib-watch 监测文件的新增、修改与删除并运行对应的任务 grunt-rewrite 文件特定内容的替换 grunt-shell 运行shell命令 grunt-zip zip压缩 load-grunt-tasks 自动加载grunt插件 Gruntfile.js 编写新建Gruntfile.js,以下是我的Gruntfile.js的内容: // see: https://gruntjs.com/sample-gruntfile module.exports = function(grunt) { var config = { pkg: grunt.file.readJSON('package.json'), pathConfig: { raw: 'raw', posts: 'source/_posts', }, clean: { posts: { src: ['<%= pathConfig.posts %>/'], }, }, copy: { main: { files: [{ expand: true, cwd: '<%= pathConfig.raw %>', src: '**/*.md', dest: '<%= pathConfig.posts %>', flatten: true, filter: function(filepath) { // var patterns = ['---\\ntitle:']; var patterns = ['^---$']; var matchRegex = function(filepath, patterns) { var content = grunt.file.read(filepath); return patterns.some(function(pattern){ var regex = new RegExp(pattern, 'm'); // var regex = new RegExp(pattern); return regex.test(content); }); }; return matchRegex(filepath, patterns); }, }], }, }, watch: { raw: { files: ['<%= pathConfig.raw %>/**/*.md'], tasks: ['copy:main'], options: { // spawn: false, }, }, }, bgShell: { hexoServer: { cmd: 'hexo server', bg: true, }, }, shell: { gitClone: { command: 'git clone git@github.com:xxxxxxx/my_blog.git <%= pathConfig.raw %>/my_blog' }, gitPullRaw: { command: 'cd ./raw/my_blog && git pull' }, hexoGenerate: { command: 'hexo g', }, hexoClean: { command: 'hexo clean', }, }, rewrite: { abbrlink: { src: '<%= pathConfig.raw %>/**/*.md', editor: function(contents, filepath){ const crypto = require('crypto'); const hash = crypto.createHash('sha256'); hash.update(contents); var hashValue = hash.digest('hex'); return contents.replace(/abbrlink: 3fb9c7a4f247726d/g, "abbrlink: " + hashValue.substring(0, 16)); } }, }, zip: { dist: { src: [ 'public/**/*', ], dest: 'blog.zip' } } }; grunt.initConfig(config); require('load-grunt-tasks')(grunt); // 运行grunt init 调用此任务 grunt.registerTask('init', [ 'shell:gitClone' ]); grunt.registerTask('RawToPosts', [ 'shell:gitPullRaw', 'rewrite:abbrlink', 'copy:main', ]); // 运行 grunt 或者 grunt default 调用此任务 grunt.registerTask('default', [ 'clean:posts', 'RawToPosts', 'bgShell:hexoServer', 'watch', ]); // 运行grunt build调用此任务 grunt.registerTask('build', [ 'clean:posts', 'RawToPosts', 'shell:hexoClean', 'shell:hexoGenerate', 'zip:dist' ]); }; 上述Gruntfile.js中有三个比较重要的任务: grunt init grunt grunt build grunt init 任务是调用shell:gitClone 把我的博客源文件从github上拉取下来,放到raw/my_blog目录,这个命令只需要执行一次,后期不再需要。 grunt 任务用于日常本地写博客,它的子任务分别是: clean:posts: 删除source/_posts下的所有文件 RawToPosts: 从github拉取更新到raw/my_blog,并拷贝符合条件的文件到source/_posts bgShell:hexoServer: 执行hexo server,启动hexo本地服务 watch: 监测文件变化,辅助hexo server刷新 grunt build 任务用于发布博客,它的子任务分别是: clean:posts: 删除source/_posts下的所有文件 RawToPosts: 从github拉取更新到raw/my_blog,并拷贝符合条件的文件到source/_posts shell:hexoClean: 执行hexo clean shell:hexoGenerate: 执行hexo genetate zip:dist: 打包public成为blog.zip 通过上述grunt自动化脚本,我保持了博客文件与hexo博客环境相分离的目标,我只需要运行grunt build,grunt会自动帮我拉取最新的博客文件并最终生成hexo目标文件。 这样做的好处是以后不使用hexo时,我可以很方便转移我的博客。 其他如果是使用github pages来托管博客的同学,可以把grunt build的zip:dist换成自动部署博客的任务,就能一键部署啦。","categories":[{"name":"搭建","slug":"搭建","permalink":"https://xu-ux.github.io/categories/%E6%90%AD%E5%BB%BA/"},{"name":"博客","slug":"搭建/博客","permalink":"https://xu-ux.github.io/categories/%E6%90%AD%E5%BB%BA/%E5%8D%9A%E5%AE%A2/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://xu-ux.github.io/tags/Hexo/"},{"name":"Blog","slug":"Blog","permalink":"https://xu-ux.github.io/tags/Blog/"}],"author":"xuux"},{"title":"记录一次NPM发包,以及NPM账号邮箱验证问题","slug":"记录一次NPM发包,以及NPM账号邮箱验证问题","date":"2021-09-25T01:20:35.000Z","updated":"2023-07-06T06:35:52.833Z","comments":true,"path":"post/e7f1ea75/","link":"","permalink":"https://xu-ux.github.io/post/e7f1ea75/","excerpt":"记录一次NPM发包,以及NPM账号邮箱验证问题。主要是点击确认邮箱地址时,还得使用上次登录成功的会话,这点没注意到,浪费了很多时间。并且导致push npm时一直失败。","text":"记录一次NPM发包,以及NPM账号邮箱验证问题。主要是点击确认邮箱地址时,还得使用上次登录成功的会话,这点没注意到,浪费了很多时间。并且导致push npm时一直失败。 一、发包①切换仓库需要使用官方的仓库,而不是镜像仓库https://registry.npm.taobao.org/,需要设置https而不是http npm config set registry=https://registry.npmjs.org ② 发布 登录 npm login 发布命令 npm publish 发布成功后切换为镜像 npm config set registry=https://registry.npm.taobao.org/ 最终报错403问题 结果如下: npm ERR! code E403 npm ERR! 403 403 Forbidden - PUT https://registry.npmjs.org/hexo-tag-codepen-x - Forbidden npm ERR! 403 In most cases, you or one of your dependencies are requesting npm ERR! 403 a package version that is forbidden by your security policy. npm ERR! A complete log of this run can be found in: npm ERR! D:\\Program Files\\nodejs\\node_cache\\_logs\\2021-09-24T23_45_16_660Z-debug.log 403问题:npm未完成邮箱验证 百度了一下,应该是注册账户时,没有去激活邮箱导致的。 所以我立马去NPM进行再次邮箱验证,npm发送了一封验证邮件(此时是在windows上操作的) 邮件中存在激活地址: https://www.npmjs.com/verify/f932abfa-63f3-415c-9213-88236039af03 二、 NPM账户邮箱激活失败在手机的邮箱APP内找到邮件 点击激活地址后出现 looks like something unexpected occurred! diagnostics id: 693fe9a57df761ac~2c11 实际时没有激活成功的,此时npm publish还是会失败 三、解决方案最终,将链接复制到已登录npm账户的windows浏览器中,才完成激活邮箱 再次尝试npm publish,发布成功! npm notice package: hexo-tag-codepen-x@1.0.0 npm notice === Tarball Contents === npm notice 1.1kB LICENSE npm notice 1.8kB index.js npm notice 675B package.json npm notice 2.4kB README.md npm notice 2.3kB zh/README.md npm notice === Tarball Details === npm notice name: hexo-tag-codepen-x npm notice version: 1.0.0 npm notice package size: 3.4 kB npm notice unpacked size: 8.3 kB npm notice shasum: 872e466bab2a7e8be9a3df9aca9431e847047160 npm notice integrity: sha512-GN9ssDQ/Uy7SG[...]kfrgF6YXiOodw== npm notice total files: 5 npm notice + hexo-tag-codepen-x@1.0.0 四、其他,尝试发布到Github Packages Github发包 注意: GitHub Packages 只支持作用域内的 npm 包。 作用域内的包具有名称格式 @owner/name。 作用域内的包总是以 @ 符号开头。 您可能需要更新 package.json 中的名称以使用作用域内的名称。 例如,"name": "@codertocat/hello-world-npm"。 ㈠ 命令式发布首先需要配置package.json"publishConfig": { "registry": "https://npm.pkg.github.com/" }, 获取token这里的密码是Personal access tokens 需要的权限为: npm login --registry=https://npm.pkg.github.com Username: [USERNAME] # 如:xu-ux Password: [TOKEN] # 如:**************** Email: (this IS public) [PUBLIC-EMAIL-ADDRESS] # 如:xu_uxo@163.com Logged in as xu-ux on https://npm.pkg.github.com/. 发布报错npm ERR! code E404 npm ERR! 404 Not Found - PUT https://npm.pkg.github.com/hexo-tag-codepen-x npm ERR! 404 npm ERR! 404 'hexo-tag-codepen-x@1.0.0' is not in the npm registry. npm ERR! 404 You should bug the author to publish it (or use the name yourself!) npm ERR! 404 npm ERR! 404 Note that you can also install from a npm ERR! 404 tarball, folder, http url, or git url. 解决GitHub Packages 只支持作用域内的 npm 包 所以修改包名 "name": "@xu-ux/hexo-tag-codepen-x", 再次发布,成功 npm notice package: @xu-ux/hexo-tag-codepen-x@1.0.0 npm notice === Tarball Contents === npm notice 1.1kB LICENSE npm notice 1.8kB index.js npm notice 756B package.json npm notice 2.5kB README.md npm notice 2.4kB zh/README.md npm notice === Tarball Details === npm notice name: @xu-ux/hexo-tag-codepen-x npm notice version: 1.0.0 npm notice package size: 3.4 kB npm notice unpacked size: 8.5 kB npm notice shasum: 6a387fe4e65edeb8c8e2111fb267c1e4c3032313 npm notice integrity: sha512-bjL6fWv1AazIb[...]iIHOVQ7iefKVQ== npm notice total files: 5 npm notice + @xu-ux/hexo-tag-codepen-x@1.0.0 ㈡ 配置发布除了使用命令式输入Token外(每次都要输入比较麻烦) 还可以将Token添加至~/.npmrc,win用户是路径C://users/current-user //npm.pkg.github.com/:_authToken=TOKEN 在package.json所在目录创建一个.npmrc文件,并添加以下内容 registry=https://npm.pkg.github.com/GitHub用户名 发布 可以直接使用npm publish,也不用在package.json填写publishConfig ㈢ 安装需要注意的是,无论发布包还是安装包都需要授权,也就是上述生成Token以及配置的过程,否则无法安装指定的包。 Ⅰ.已配置~/.npmrc已经配置了.npmrc文件的,可以直接使用如下命令 npm install @xu-ux/hexo-tag-codepen-x@1.0.0 --registry=https://npm.pkg.github.com/ Ⅱ.使用命令方式首先登录 npm login --registry=https://npm.pkg.github.com > Username: USERNAME > Password: TOKEN > Email: PUBLIC-EMAIL-ADDRESS 再安装 npm install @xu-ux/hexo-tag-codepen-x@1.0.0","categories":[{"name":"开发","slug":"开发","permalink":"https://xu-ux.github.io/categories/%E5%BC%80%E5%8F%91/"},{"name":"工具","slug":"开发/工具","permalink":"https://xu-ux.github.io/categories/%E5%BC%80%E5%8F%91/%E5%B7%A5%E5%85%B7/"}],"tags":[{"name":"Npm","slug":"Npm","permalink":"https://xu-ux.github.io/tags/Npm/"},{"name":"NodeJs","slug":"NodeJs","permalink":"https://xu-ux.github.io/tags/NodeJs/"}],"author":"xuux"}],"categories":[{"name":"编程","slug":"编程","permalink":"https://xu-ux.github.io/categories/%E7%BC%96%E7%A8%8B/"},{"name":"Java","slug":"编程/Java","permalink":"https://xu-ux.github.io/categories/%E7%BC%96%E7%A8%8B/Java/"},{"name":"技术","slug":"技术","permalink":"https://xu-ux.github.io/categories/%E6%8A%80%E6%9C%AF/"},{"name":"云服务","slug":"技术/云服务","permalink":"https://xu-ux.github.io/categories/%E6%8A%80%E6%9C%AF/%E4%BA%91%E6%9C%8D%E5%8A%A1/"},{"name":"AI","slug":"技术/AI","permalink":"https://xu-ux.github.io/categories/%E6%8A%80%E6%9C%AF/AI/"},{"name":"生活","slug":"生活","permalink":"https://xu-ux.github.io/categories/%E7%94%9F%E6%B4%BB/"},{"name":"数码","slug":"生活/数码","permalink":"https://xu-ux.github.io/categories/%E7%94%9F%E6%B4%BB/%E6%95%B0%E7%A0%81/"},{"name":"开发","slug":"开发","permalink":"https://xu-ux.github.io/categories/%E5%BC%80%E5%8F%91/"},{"name":"工具","slug":"开发/工具","permalink":"https://xu-ux.github.io/categories/%E5%BC%80%E5%8F%91/%E5%B7%A5%E5%85%B7/"},{"name":"Maven","slug":"开发/工具/Maven","permalink":"https://xu-ux.github.io/categories/%E5%BC%80%E5%8F%91/%E5%B7%A5%E5%85%B7/Maven/"},{"name":"SpringBoot","slug":"编程/SpringBoot","permalink":"https://xu-ux.github.io/categories/%E7%BC%96%E7%A8%8B/SpringBoot/"},{"name":"搭建","slug":"搭建","permalink":"https://xu-ux.github.io/categories/%E6%90%AD%E5%BB%BA/"},{"name":"博客","slug":"搭建/博客","permalink":"https://xu-ux.github.io/categories/%E6%90%AD%E5%BB%BA/%E5%8D%9A%E5%AE%A2/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://xu-ux.github.io/tags/Java/"},{"name":"Jvm","slug":"Jvm","permalink":"https://xu-ux.github.io/tags/Jvm/"},{"name":"VisualVM","slug":"VisualVM","permalink":"https://xu-ux.github.io/tags/VisualVM/"},{"name":"插件","slug":"插件","permalink":"https://xu-ux.github.io/tags/%E6%8F%92%E4%BB%B6/"},{"name":"反向代理","slug":"反向代理","permalink":"https://xu-ux.github.io/tags/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86/"},{"name":"CloudFlare","slug":"CloudFlare","permalink":"https://xu-ux.github.io/tags/CloudFlare/"},{"name":"域名解析","slug":"域名解析","permalink":"https://xu-ux.github.io/tags/%E5%9F%9F%E5%90%8D%E8%A7%A3%E6%9E%90/"},{"name":"代理","slug":"代理","permalink":"https://xu-ux.github.io/tags/%E4%BB%A3%E7%90%86/"},{"name":"AI","slug":"AI","permalink":"https://xu-ux.github.io/tags/AI/"},{"name":"NewBing","slug":"NewBing","permalink":"https://xu-ux.github.io/tags/NewBing/"},{"name":"ChatGPT","slug":"ChatGPT","permalink":"https://xu-ux.github.io/tags/ChatGPT/"},{"name":"OpenAPI","slug":"OpenAPI","permalink":"https://xu-ux.github.io/tags/OpenAPI/"},{"name":"NLP","slug":"NLP","permalink":"https://xu-ux.github.io/tags/NLP/"},{"name":"浏览器插件","slug":"浏览器插件","permalink":"https://xu-ux.github.io/tags/%E6%B5%8F%E8%A7%88%E5%99%A8%E6%8F%92%E4%BB%B6/"},{"name":"技术","slug":"技术","permalink":"https://xu-ux.github.io/tags/%E6%8A%80%E6%9C%AF/"},{"name":"数码","slug":"数码","permalink":"https://xu-ux.github.io/tags/%E6%95%B0%E7%A0%81/"},{"name":"路由器","slug":"路由器","permalink":"https://xu-ux.github.io/tags/%E8%B7%AF%E7%94%B1%E5%99%A8/"},{"name":"java.time包","slug":"java-time包","permalink":"https://xu-ux.github.io/tags/java-time%E5%8C%85/"},{"name":"SpringBoot","slug":"SpringBoot","permalink":"https://xu-ux.github.io/tags/SpringBoot/"},{"name":"Maven","slug":"Maven","permalink":"https://xu-ux.github.io/tags/Maven/"},{"name":"Docker","slug":"Docker","permalink":"https://xu-ux.github.io/tags/Docker/"},{"name":"企业微信","slug":"企业微信","permalink":"https://xu-ux.github.io/tags/%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1/"},{"name":"运维","slug":"运维","permalink":"https://xu-ux.github.io/tags/%E8%BF%90%E7%BB%B4/"},{"name":"Nacos","slug":"Nacos","permalink":"https://xu-ux.github.io/tags/Nacos/"},{"name":"Byte","slug":"Byte","permalink":"https://xu-ux.github.io/tags/Byte/"},{"name":"Hexo","slug":"Hexo","permalink":"https://xu-ux.github.io/tags/Hexo/"},{"name":"Blog","slug":"Blog","permalink":"https://xu-ux.github.io/tags/Blog/"},{"name":"加密","slug":"加密","permalink":"https://xu-ux.github.io/tags/%E5%8A%A0%E5%AF%86/"},{"name":"Npm","slug":"Npm","permalink":"https://xu-ux.github.io/tags/Npm/"},{"name":"NodeJs","slug":"NodeJs","permalink":"https://xu-ux.github.io/tags/NodeJs/"}]}