跳到主要内容

· 阅读需 5 分钟
Bowen Zhang

背景

因为一直在使用 hexo 自建博客,最近又切换到了 docusaurus ,但是又想同时发布到博客园,所以需要一个工具能将 md 文件直接发布到博客园,所以写了一个 node 自动化上传脚本 ,同时方便需要的人借鉴使用(2023年2月更新) 下面简单描述下 MetaWeblog 协议的使用

博客园的 MetaWeblog 协议的使用

原资料地址

背景资料地址:https://www.cnblogs.com/caipeiyu/p/5354341.html

想实现自己的文章一处编写,多处发布到各大平台(比如博客园,CSDN)等要怎么实现呢。需要由这些组成:

  1. 文章管理:一个管理文章知识的平台(网站),在这里撰写,编辑文章。比如:写博客的客户端软件,博客园等。
  2. 第三方网站(平台)具有开放的 API 接口,比如博客园的 metaWebBlog。
  3. 同步服务:读取文章,调开放的 API,将文章发布出去。

一般来说,写文章的软件很容易获得,如果目标平台再有开放接口,我们可以将文章通过接口进行发布。

博客园支持 metaWebBlog 接口,使得可以接收来自 接口 的文章

1. metaWebBlog 概述

MetaWeblog API(MWA)是一个 Blog 程序接口标准。通过 MetaWeblog API,博客平台可以对外公布 blog 提供的服务,从而允许外面的程序新建,编辑,删除,发布 bolg。

MetaWeblog 使用 xml-RPC 作为通讯协议。

XML-RPC 是一个远程过程调用(远端程序呼叫)(remote procedure call,RPC)的分布式计算协议,通过 XML 将调用函数封装,并使用 HTTP 协议作为传送机制。一个 XML-RPC 消息就是一个请求体为 xml 的 http-post 请求,被调用的方法在服务器端执行并将执行结果以 xml 格式编码后返回。

简单理解就是:在 HTTP 请求 中,发送 xml 格式描述的“调用指令”,如果调用成功,会收到 xml 格式描述的“执行结果”。

2. 博客园文章相关接口

  • blogger.getUsersBlogs —— 获取用户博客信息
  • metaWeblog.getRecentPosts —— 获取最近的文章
  • metaWeblog.getPost —— 获取文章内容
  • metaWeblog.newPost —— 添加文章
  • metaWeblog.editPost —— 编辑文章
  • blogger.deletePost —— 删除文章

还有一些关于 文章分类 的接口,可以在其接口文档中找到。

2.1 接口说明

在 博客园 设置页面的地步可以找到 API 接口的说明,类似这样:

https://rpc.cnblogs.com/metaweblog/{userName}

上面的 {userName} 替换成实际的用户名。

下文仅说明“请求的接口和参数”,响应内容在发送成功后一看便知。

2.2 发送方式

使用 nodejs 完成文件读写和接口调用

资料

index.js


/*
* @Description: 批量将hexo中的md文件上传博客园
* @Autor: Bowen
* @Date: 2021-10-09 16:56:43
* @LastEditors: Bowen
* @LastEditTime: 2023-01-11 10:09:47
*/

const fs = require("fs").promises;
const path = require("path");
const crypto = require("crypto");
const matter = require("gray-matter");
const { newPost, editPost } = require("./api");

// 发布所有的文章
async function handleAllPushPost(dirPath) {
let files = await fs.readdir(dirPath);
for (const fileName of files) {
const filePath = path.resolve(dirPath, fileName);
// dir 继续递归
let stats = await fs.stat(filePath);
if (stats.isDirectory()) {
await handleAllPushPost(filePath);
continue;
}
console.log("[********************************]");
console.log("[fileName]", fileName);
// await new Promise((r) => setTimeout(r, 1000), true);
await handlePushPost(filePath);
}
}

// 根据path修改或者新建文章
async function handlePushPost(filePath) {
const fileName = path.basename(filePath);
// 解析 md 文件
const grayMatterFile = matter.read(filePath);
const { data, content } = grayMatterFile;
if (!data || !data.title) return;
// 获取当前哈希值 对比 之前的 哈希
const hash = crypto.createHash("sha256");
hash.update(content);
let nowContentHash = hash.digest("hex");
let { cnblogs, hash: contentHash } = data;
if (contentHash && contentHash == nowContentHash) {
console.log("[hash值未变退出当前循环]");
return;
}
// yaml中添加 hash
data.hash = nowContentHash;
// 文章数据
const categories = Array.isArray(data.tags) ? data.tags : [];
// TODO: data.? 看自己的 md 文档是如何配置
const post = {
description: content,
title: data.title,
// 注意 要以 Markdown 格式发布 必须在 categories 中 添加 "[Markdown]"
categories: ["[Markdown]"].concat(categories),
};
let res;
// 编辑
if (cnblogs && cnblogs.postid) {
console.log("[-------------修改-------------]");
try {
res = await editPost(cnblogs.postid, post, true);
} catch (error) {
console.log("[修改失败]", error.message);
throw Error(error.message);
}
console.log("[修改成功]", res);
} else {
console.log("[-------------新建-------------]");
data.cnblogs = {};
try {
res = await newPost(post, true);
} catch (error) {
console.log("[上传失败]", error.message);
throw Error(error.message);
}
console.log("[上传成功]", res);
// yaml中添加 postid
data.cnblogs.postid = res;
}
// 回写数据
const str = grayMatterFile.stringify();
await fs.writeFile(filePath, str);
console.log("[回写成功]", fileName);
// 等待 1分钟 后继续下一个
await new Promise((r) => setTimeout(r, 3500, true));
}

(async () => {
await handleAllPushPost("C:/bowen/product/new-blog/docs");
// await handleAllPushPost("C:/bowen/product/new-blog/blog");
})();


api.js

const MetaWeblog = require("metaweblog-api");
const apiUrl = "https://rpc.cnblogs.com/metaweblog/username"; // use your blog API instead
const metaWeblog = new MetaWeblog(apiUrl);

const username ="username"
const password ="password || token"
const blogid ='blogid' // 通过 getUsersBlogs 查询
const appKey =''
const numberOfPosts =1

module.exports = {
getUsersBlogs:()=> metaWeblog.getUsersBlogs(appKey, username, password),
getRecentPosts:()=> metaWeblog.getRecentPosts(blogid, username, password, numberOfPosts),
getCategories:()=> metaWeblog.getCategories(blogid, username, password),
getPost:(postid)=> metaWeblog.getPost(postid, username, password),
newPost: (post, publish)=> metaWeblog.newPost(blogid, username, password, post, publish),
editPost: ( postid ,post, publish)=> metaWeblog.editPost(postid, username, password, post, publish),
deletePost: ()=> metaWeblog.deletePost(appKey, postid, username, password, publish),
}

· 阅读需 2 分钟
Bowen Zhang

借鉴文档

Typora配置picgo-core自动上传图片,及picgo-core上传组件开发

PicGo插件开发文档

写在前面

为什么要自定义一个上传插件呢 因为 gitee 用不了了😔 觉得图片还是放到自己的服务器上安全 使用过web-uploader插件但是有问题调不通我的接口,于是自己写个上传插件方便一些 我的自定义上传插件 :picgo-plugin-bitbw-upload 插件地址

· 阅读需 2 分钟
Bowen Zhang

Vue项目部署新版本后提示用户刷新浏览器

vue-cli 创建带pwd的项目

vue create pwa-product

# 选择 pwd

修改 src/registerServiceWorker.js

registerServiceWorker

/* eslint-disable no-console */

import { register } from "register-service-worker";

if (process.env.NODE_ENV === "production" && navigator.serviceWorker) {
register(`${process.env.BASE_URL}service-worker.js`, {
ready() {
console.log(
"App is being served from cache by a service worker.\n" +
"For more details, visit https://goo.gl/AFskqB"
);
},

registered(registration) {
console.log("Service worker has been registered.");
// 通过测试新的服务工作线程来定期检查应用更新
setInterval(() => {
registration.update();
}, 1000); // 这里为了测试 每秒检查一次
},
cached() {
console.log("Content has been cached for offline use.");
},
updatefound() {
console.log("New content is downloading.");
},
// 有个更新 通知页面更新
updated(registration) {
console.log("New content is available; please refresh.");
// 创建一个自定义事件
const event = new CustomEvent("swupdatefound", { detail: registration });
// 触发这个事件
document.dispatchEvent(event);
},
offline() {
console.log(
"No internet connection found. App is running in offline mode."
);
},
error(error) {
console.error("Error during service worker registration:", error);
},
});

let refreshing;
// 监听需要更新事件 调用 window.location.reload()
navigator.serviceWorker.addEventListener("controllerchange", function () {
if (refreshing) return;

window.location.reload();

refreshing = true;
});
}

在页面中添加事件监听

在 main.js 或 store 中添加事件监听

// 添加对应自定义事件的监听
document.addEventListener("swupdatefound", (e) => {
// 提示用户刷新
let res = confirm("新内容可用,请刷新");
if (res) {
// e.detail == registration
// waiting.postMessage({ type: "SKIP_WAITING" }) 是固定写法
// 用于触发更新 navigator.serviceWorker.addEventListener("controllerchange"...)
e.detail.waiting.postMessage({
type: "SKIP_WAITING",
});
}
});

为什么 waiting.postMessage({ type: "SKIP_WAITING" }) 会触发更新

在打包下的 dist 目录下service-worker.js 看到一下代码

// self 是 worker 子线程的标准
// waiting.postMessage({ type: "SKIP_WAITING" }) 会触发下面事件监听
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
// 调用 触发 controllerchange
self.skipWaiting();
}
});

· 阅读需 7 分钟
Bowen Zhang

js 函数的防抖(debounce)与节流(throttle)

原文:函数防抖和节流;

序言:

我们在平时开发的时候,会有很多场景会频繁触发事件,比如说搜索框实时发请求,onmousemove, resize, onscroll 等等,有些时候,我们并不能或者不想频繁触发事件,咋办呢?这时候就应该用到函数防抖和函数节流了!

准备材料:

<div
id="content"
style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"
></div>

<script>
let num = 1;
let content = document.getElementById("content");

function count() {
content.innerHTML = num++;
}
content.onmousemove = count;
</script>

这段代码, 在灰色区域内鼠标随便移动,就会持续触发 count() 函数,导致的效果如下:

img

接下来我们通过防抖和节流限制频繁操作。

函数防抖(debounce)

短时间内多次触发同一事件,只执行最后一次,或者只执行最开始的一次,中间的不执行。

// 非立即执行版
function debounce(func, wait) {
let timer;
return function () {
let context = this; // 注意 this 指向
let args = arguments; // arguments中存着e

if (timer) clearTimeout(timer);

timer = setTimeout(() => {
func.apply(this, args);
}, wait);
};
}

我们是这样使用的:

content.onmousemove = debounce(count, 1000);

非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。效果如下:img

// 立即执行版
function debounce(func, wait) {
let timer;
return function () {
let context = this; // 这边的 this 指向谁?
let args = arguments; // arguments中存着e

if (timer) clearTimeout(timer);

let callNow = !timer;

timer = setTimeout(() => {
timer = null;
}, wait);

if (callNow) func.apply(context, args);
};
}

立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。用法同上,效果如下:

img

// 合成版
/**
* @desc 函数防抖
* @param func 目标函数
* @param wait 延迟执行毫秒数
* @param immediate true - 立即执行, false - 延迟执行
*/
function debounce(func, wait, immediate) {
let timer;
return function () {
let context = this,
args = arguments;

if (timer) clearTimeout(timer);
if (immediate) {
let callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait);
if (callNow) func.apply(context, args);
} else {
timer = setTimeout(() => {
func.apply(context, args);
}, wait);
}
};
}

节流(throttle)

指连续触发事件但是在 n 秒中只执行一次函数。即 2n 秒内执行 2 次... 。节流如字面意思,会稀释函数的执行频率。

同样有两个版本,时间戳和定时器版。

// 时间戳版
function throttle(func, wait) {
let previous = 0;
return function () {
let now = Date.now();
let context = this;
let args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
};
}

使用方式如下:

content.onmousemove = throttle(count, 1000);

效果如下:

img

可以看到,在持续触发事件的过程中,函数会立即执行,并且每 1s 执行一次。

// 定时器版
function throttle(func, wait) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args);
}, wait);
}
};
}

用法同上,效果如下:

img

可以看到,在持续触发事件的过程中,函数不会立即执行,并且每 1s 执行一次,在停止触发事件后,函数还会再执行一次。

我们应该可以很容易的发现,其实时间戳版和定时器版的节流函数的区别就是,时间戳版的函数触发是在时间段内开始的时候,而定时器版的函数触发是在时间段内结束的时候。

同样地,我们也可以将时间戳版和定时器版的节流函数结合起来,实现双剑合璧版的节流函数。

/**
* @desc 函数节流
* @param func 函数
* @param wait 延迟执行毫秒数
* @param type 1 表时间戳版,2 表定时器版
*/
function throttle(func, wait, type) {
if (type === 1) {
let previous = 0;
} else if (type === 2) {
let timeout;
}
return function () {
let context = this;
let args = arguments;
if (type === 1) {
let now = Date.now();

if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
} else if (type === 2) {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args);
}, wait);
}
}
};
}

附录:

  关于节流/防抖函数中 context(this) 的指向解析:

首先,在执行 throttle(count, 1000) 这行代码的时候,会有一个返回值,这个返回值是一个新的匿名函数,因此 content.onmousemove = throttle(count,1000); 这句话最终可以这样理解:

content.onmousemove = function() {
let now = Date.now();
let context = this;
let args = arguments;
...
console.log(this)
}

到这边为止,只是绑定了事件函数,还没有真正执行,而 this 的具体指向需要到真正运行时才能够确定下来。所以这个时候如果我们把前面的 content.onmousemove 替换成 var fn 并执行 fn fn() ,此时内部的 this 打印出来就会是 window 对象。

其次,当我们触发 onmousemove 事件的时候,才真正执行了上述的匿名函数,即 content.onmousemove() 。此时,上述的匿名函数的执行是通过 对象.函数名() 来完成的,那么函数内部的 this 自然指向 对象。

最后,匿名函数内部的 func 的调用方式如果是最普通的直接执行 func() ,那么 func 内部的 this 必然指向 window ,虽然在代码简单的情况下看不出什么异常(结果表现和正常一样),但是这将会是一个隐藏 bug,不得不注意啊!所以,我们通过匿名函数捕获 this,然后通过 func.apply() 的方式,来达到 content.onmousemove = func 这样的效果。

可以说,高阶函数内部都要注意 this 的绑定。

输入防抖节流

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<input class="input" value="" type="text" />
<button class="btn">点击</button>
<script>
document
.querySelector(".input")
.addEventListener("input", throttle(handleFn, 500, true));
let index = 1;
function handleFn(e) {
console.log("index", index);
console.log("handleFn", e, this.value);
index++;
}
// 防抖 间隔delay时间内多次触发,只执行第一次,或最后一次
function debounce(fn, delay, triggerNow = false) {
let timer = null;
return function (...arg) {
const context = this;
// clearTimeout 保证只能首次 或者最后一次被执行,否则在规定delay的时间内都会被清理掉
if (timer) clearTimeout(timer);
// 立即执行版 一段delay时间内多次触发只有第一次被执行
if (triggerNow) {
let first = !timer;
if (first) fn.call(context, ...arg);
timer = setTimeout(() => {
// 恢复 timer 以便首次执行
timer = null;
}, delay);
// 最后一次执行版 一段delay时间内多次触发只有最后一次被执行
} else {
timer = setTimeout(() => {
fn.call(context, ...arg);
}, delay);
}
};
}
// 节流 一段delay时间出发多次,只执行一次 ,fristTrigger 第一次执行 最后一次执行
function throttle(fn, delay, fristTrigger = false) {
let timer = null; // 定时器默认为null
let last = 0; // 最后一次时间戳默认为0
return function (...arg) {
const context = this;
if (fristTrigger) {
// 一定时间内执行第一次
// 当前时间戳 - 之前时间戳 > delay 再执行
const cur = Date.now();
if (cur - last > delay) {
fn.call(context, ...arg);
last = cur;
}
} else {
// 没有定时器再执行 执行清空定时器 ,一定时间内执行最后一次
if (!timer) {
timer = setTimeout(() => {
timer = null;
fn.call(context, ...arg);
}, delay);
}
}
};
}
</script>
</body>
</html>

· 阅读需 3 分钟
Bowen Zhang

var 变量提升

var 申明的变量会被提升到当前作用域的最顶端 所谓作用域是 function 这样的函数才算是, if else for 等带 {} 的是没有作用域的无论嵌套多少层都会提升

(function () {
console.log(i); //var i 被提升到这里 结果是 undefind ,如果没提升就会报 Uncaught ReferenceError: i is not defined
if (false) {
if (false) {
// for 中 var i = 0; 会被提到最上面
for (var i = 0; i < 10; i++) {}
}
}
})();
var name = 'World!';
(function () {
if (typeof name === 'undefined') {
var name = 'Jack'; // 这个会被提升到当前作用域的最顶端 var name
console.log('Goodbye ' + name);
} else {
console.log('Hello ' + name);
}
})();

Array(3) 和 Array.from({length:3})

Array(3) 是创建了一个 length: 3 没有经过初始化的数组,数组每一项其实是empty也就是未经初始化 Array.from({length:3}) 是创建了一个 length: 3 经过初始化的数组,数组每一项是 undefind

使用时的区别 Array.prototype.map filter forEach 等, 只有数组中被初始化过元素才会被触发,也就是说未经初始化的元素压根不会被遍历到

手写 async await


// 包装 generator 函数
function asyncToGenerator(generatorFunc) {
// 返回的是一个新的函数
return function () {
// 先调用generator函数 生成迭代器
// 对应 var gen = testG()
const gen = generatorFunc.apply(this, arguments);
// 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的
// var test = asyncToGenerator(testG)
// test().then(res => console.log(res))
return new Promise((resolve, reject) => {
// 内部定义一个step函数 用来一步一步的跨过yield的阻碍
// key有next和throw两种取值,分别对应了gen的next和throw方法
// arg参数则是用来把promise resolve出来的值交给下一个yield
function step(key, arg) {
let generatorResult;

// 这个方法需要包裹在try catch中
// 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误
try {
generatorResult = gen[key](arg);
} catch (error) {
return reject(error);
}
// gen.next() 得到的结果是一个 { value, done } 的结构
const { value, done } = generatorResult;
if (done) {
// 如果已经完成了 就直接resolve这个promise
// 这个done是在最后一次调用next后才会为true
// 以本文的例子来说 此时的结果是 { done: true, value: 'success' }
// 这个value也就是generator函数最后的返回值
return resolve(value);
} else {
// 除了最后结束的时候外,每次调用gen.next()
// 其实是返回 { value: Promise, done: false } 的结构,

// 这里要注意的是Promise.resolve可以接受一个promise为参数
// 并且这个promise参数被resolve的时候,这个then才会被调用
return Promise.resolve(
// 这个value对应的是yield后面的promise
value
).then(
/*
// value这个promise被resove的时候,就会执行next
// 并且只要done不是true的时候 就会递归的往下解开promise
// 对应
gen.next().value.then(value => {
gen.next(value).value.then(value2 => {
gen.next()
// 此时done为true了 整个promise被resolve了
// 最外部的test().then(res => console.log(res))的then就开始执行了
}) */
function onResolve(val) {
step("next", val);
},
// 如果promise被reject了 就再次进入step函数
// 不同的是,这次的try catch中调用的是gen.throw(err)
// 那么自然就被catch到 然后把promise给reject掉啦
function onReject(err) {
step("throw", err);
}
);
}
}
step("next");
});
};
}
// 模拟 async 的 generator 函数
function* generatorFunc() {
yield new Promise((resolve) =>
setTimeout(() => {
console.log("Promise padding");
resolve("Promise res");
}, 100)
);
console.log("Promise end");
return "res";
}
// 使用
var asyncFn = asyncToGenerator(generatorFunc);
asyncFn().then((res) => console.log(res));

· 阅读需 5 分钟
Bowen Zhang

1、ngrok

ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。

官网地址:

https://ngrok.com

使用步骤:

(1)进入 ngrok 官网注册账号,并下载 ngrok。

(2)在目标机器上,填写授权码,运行 ngrok。

./ngrok authtoken 授权码

(3)将 HTTP 隧道转发到本地端口 80,如下图,通过外网域名即可访问到本地 80 端口。

./ngrok http 80

img

2、frp

frp 是一个高性能的反向代理应用,可以帮助您轻松地进行内网穿透,对外网提供服务,支持 tcp, http, https 等协议类型,并且 web 服务支持根据域名进行路由转发。

git 项目地址:

https://github.com/fatedier/frp

使用步骤:

(1)将 frps 及 frps.ini 放到有公网 IP 的机器上,修改 frps.ini 文件,配置一个名为 ssh 的反向代理:

# frps.ini[common]bind_port = 7000
[ssh]listen_port=6000auth_token=123

(2)启动 frps:

./frps -c ./frps.ini

(3)将 frpc 及 frpc.ini 放到处于内网环境的机器上,修改 frpc.ini 文件,设置 frps 所在服务器的 IP 为 x.x.x.x:

# frpc.ini[common]server_addr = 192.168.172.131server_port = 7000auth_token =123
[ssh]type = tcplocal_ip = 127.0.0.1local_port = 22remote_port = 6000

(4)启动 frpc:

./frpc -c ./frpc.ini

(5)通过 ssh 访问内网服务器,假设用户名为 test:

ssh -p 6000 test@x.x.x.x

3、Sunny-Ngrok

一条命令解决的外网访问内网问题,无需任何配置,一条命令让外网访问您的内网不再是距离。Ngrok 客户端,支持 Windows、Linux、Mac,提供 python、PHP 等版本。

官网地址:

https://www.ngrok.cc

使用步骤:

(1) 开通 http 隧道,设置隧道协议、本地端口等信息。

img

(2)通过隧道 id 启动隧道。

img

(3)在公网上访问域名,即可访问到内网的网站。

img

4、Natapp

基于 ngrok 的内网映射工具,免费版本提供 http,tcp,udp 全隧道穿透、随机域名/随机 TCP,UDP 端口、不定时强制更换域名/端口和自定义本地端口。

下载地址:

https://natapp.cn

(1)购买隧道,设置隧道协议和本地端口。

img

(2)下载客户端,到目标主机,运行 natapp。

img

(3)将 natapp 分配的网址复制下来,在浏览器中访问,可以看到内网穿透成功了!

img

5、Earthworm 内网穿透

EW 是一套便携式的网络穿透工具,具有 SOCKS v5 服务架设和端口转发两大核心功能,可在复杂网络环境下完成网络穿透。

支持 Windows、Linux、MacOS、Arm-Linux。

项目地址:

http://rootkiter.com/EarthWorm

使用方法:

(1)正向 SOCKS v5 服务器,适用于目标网络边界存在公网 IP 且可任意开监听端口。

./ew -s ssocksd -l 1080

(2)反弹 SOCKS v5 服务器,适用于目标网络边界不存在公网 IP,需要通过反弹方式创建 socks 代理

 a) 先在一台具有公网 ip 的主机A上运行以下命令: ./ew -s rcsocks -l 1080 -e 8888
b) 在目标主机B上启动 SOCKS v5 服务 并反弹到公网主机的 8888端口 ./ew -s rssocks -d 1.1.1.1 -e 8888

(3)多级级联,适用于多层级的复杂网络环境,将内网深层的服务转发到外网。

复制代码

## 二级级联测试用例

# 方式一:利用lcx_tran $ ./ew -s ssocksd -l 9999 $ ./ew -s lcx_tran -l 1080 -f 127.0.0.1 -g 9999
# 方式二: $ ./ew -s lcx_listen -l 1080 -e 8888 $ ./ew -s ssocksd -l 9999 $ ./ew -s lcx_slave -d 127.0.0.1 -e 8888 -f 127.0.0.1 -g 9999

## 三级级联测试用例
$ ./ew -s rcsocks -l 1080 -e 8888 $ ./ew -s lcx_slave -d 127.0.0.1 -e 8888 -f 127.0.0.1 -g 9999 $ ./ew -s lcx_listen -l 9999 -e 7777 $ ./ew -s rssocks -d 127.0.0.1 -e 7777

复制代码

6、reDuh

ReDuh 是一个通过 HTTP 协议建立隧道传输各种其他数据的工具,通过在服务器上传 JSP/PHP/ASP 脚本,就可以轻易地连接到服务器后面的主机。

github 项目地址:

https://github.com/sensepost/reDuh

(1)上传服务端 jsp 脚本到目标机器

img

(2)使用 reDuh 客户端连接服务端脚本上传后的地址。

java -jar reDuhClient.jar http://192.168.172.1/reDuh.jsp

img

7、reGeorg

reGeorg 是 reDuh 的继承者,利用了会话层的 socks5 协议,结合 Proxifier 使用效率会更高。

下载地址:

https://github.com/sensepost/reGeorg

使用方法:

(1)将 tunnel 脚本(aspx | ashx | jsp | php)上传到 Web 服务器,访问显示“Georg says, ‘All seems fine’“,表示脚本运行正常。

img

(2)在攻击者机器上,启动 reGeorgSocksProxy.py,监听 9999 端口,看到 Checking if Georg is ready,确认正常运行。

img

8、Tunna

Tunna 是一款神奇的工具,它可以通过 HTTP 封装隧道通信任何 TCP,以及用于绕过防火墙环境中的网络限制。

github 项目地址:

https://github.com/SECFORCE/Tunna

img

9、sSocks

sSocks 是一个 socks 代理工具套装,可用来开启 socks 代理服务,支持 socks5 验证,支持 IPV6 和 UDP,并提供反向 socks 代理服务,即将远程计算机作为 socks 代理服务端,反弹回本地,极大方便内网的渗透测试。

下载地址:

http://sourceforge.net/projects/ssocks/

使用方法:

(1)正向代理

./ssocksd --bind 192.168.172.131 --port 1234

(2)反向代理

## 监听6020端口转发到6010./rcsocks -l 6020 -p 6010 -vv
## 反向连接代理主机6010端口./rssocks -s 192.168.172.131:6010 -vv

· 阅读需 1 分钟
Bowen Zhang

typora,postman,vscode 等都是使用 electron 构建而成的桌面应用

这篇文章主要记录下 typora 日常使用中的技巧

win 命令行添加 typora 直接打开 md 文档的方法

添加 path 环境变量

C:\app\Typora\bin

新版的 typora 没有 bin 目录 那就直接C:\app\Typora

注意:添加环境变量后需要重启计算机

通过修改注册表,在右键新建菜单中添加 typora

  1. win+R 调出运行窗口,输入 regedit,弹出注册表窗口
  2. 在窗口的搜索栏中输入 Computer\HKEY_CLASSES_ROOT .md(有的可能是 计算机\HKEY_CLASSES_ROOT .md),按回车,找到.md 项
  3. 点击.md,双击右栏的默认项,弹出编辑框后在“数值数据”框中输入“Typora.md”,点击确定
  4. OpenWithProgids 查看是否有 \Typora.exe
  5. 点击.md,右键新建->项,取名“ShellNew”
  6. 点击 ShellNew,右键新建->字符串值,取名“NullFile” 至此,完成所有步骤,回到桌面刷新一下,右键新建菜单查看结果。

总结:其实就是两步 (1)修改.md 的默认值 (2)添加 ShellNew,添加 NullFile

image-20210427142049929

原文:https://blog.csdn.net/m0_46588308/article/details/105919561