有本地主机在内网192.168.1.10,目标主机在内网192.168.22.10,有公网ip的中转服务器131.72.6.154。内网无法通过公网直接访问。
所以本地主机要发送数据给目标主机的端口5900,就需要反向端口转发。

在目标主机192.168.22.10上运行

1
ssh -vNCR 17780:192.168.22.10:5900 itdog@131.72.6.154 -p 22

表示目标主机192.168.22.10登录ssh到中转服务器,并且中转服务器131.72.6.154的17780端口会转发到目标主机的192.168.22.10的5900端口。
然后本地主机192.168.1.10访问中转服务器主机131.72.6.154的17780端口,这些数据将转发给目标主机的5900端口。

打开PuTTYgen

点击Generate,在对话框的空白处移动鼠标生成密钥,右键复制公钥。保存公钥和私钥(Save public key,Save private key)。

打开putty

用密码登录服务器,新建~/.ssh/authorized_keys。编辑该文件,粘贴复制的公钥。

另外再打开putty

点击菜单的Connection => SSH => Auth => Credentials => Private key file for authentication,选择保存的私钥。保存这个配置,即可无密码登录服务器了。

这样能使用密钥登录服务器后,就可以配置不允许密码登录了(注意不允许密码登录也会导致使用其他电脑使用密码登录不上服务器)

使用的是电信光猫,外网访问本地地址(功能相当于ipv4的内网穿透)。

和安装光猫的师傅套套近乎,师傅应该就会把光猫的管理员账户和密码告诉我们。

登录光猫管理员账户后,在安全=>防火墙中把”启用IPV6 SESSION”关闭

就能在外网访问本地电脑的ipv6地址了。

打开本地电脑的windows的远程桌面开关,就可以在外网使用windows自带的远程桌面访问本地电脑了,无需安装第三方应用。

如果仍然无法访问本地ipv6地址,可能是路由器也有防火墙。一般路由器改成桥接模式可以关闭路由器的防火墙,从而外网使用ipv6访问本地地址。

再加上ddns动态域名解析,就可以实现使用域名访问本地电脑了。cloudflare ddns

或者使用nodejs(版本22),以下是更新dns的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// 修改下面的四个变量
// https://dash.cloudflare.com/profile/api-tokens 在这个页面获取Global API Key
import {parse} from "url";
import http from "http";
import https from "https";
import fs from "fs";

var sub = ["域名前缀"]; // 如 ["www"] // 单个IP绑定多个域名则为多个元素的数组,如: ['a', 'b']
var domain = ["主域名"]; // 如 ["qq.com"]
var email = "你的邮箱"; // 如 "abc@qq.com"
var authKey = "Global API Key" // 如 "123456789..."

var auth = {
"X-Auth-Email": email,
"X-Auth-Key": authKey
};

function log(txt) {
fs.appendFile('ddns.txt', (txt + '\n'), err => {
if (err) {
console.error(err)
return
}
});
}


const requestIpv6 = http.get({ 'host': 'api6.ipify.org', 'port': 80, 'path': '/', family: 6 }, function (resp) {
if (resp.statusCode != 200) {
log("non-200 response status code:", resp.statusCode);
return;
}
else {
resp.on('data', function (ip) {
log('get ipv6 success:200');
log("My public IP address is: " + ip);


const url = parse("https://api.cloudflare.com/client/v4/zones/5926705fb3db4a7f866714500ef5a12c/dns_records");
if (url.hash) {
url.path += url.hash;
}
url.method = "GET";
url.headers = auth;

const getDNSList = https.request(url, (DNSListRes) => {
let DNSListBody = '';
DNSListRes.setEncoding("utf8");
DNSListRes.on("data", (resBody) => {
DNSListBody += resBody;
});
DNSListRes.on("end", () => {
log("dns list success");
const getDNSList = JSON.parse(DNSListBody).result;
for (let i = 0; i < sub.length; i++) {
let domainTotal = sub[i] + '.' + domain;
getDNSList.forEach((record) => {

if (record.name === domainTotal && ip.toString() !== record.content) {
makeRequest(ip, domainTotal, "PUT", record);
}
else if (record.name === domainTotal && ip.toString() === record.content) {
log(domainTotal + ":ipv6 doesn't need update.")
}
})
if (getDNSList.filter((record) => record.name === domainTotal).length === 0) {
makeRequest(ip, domainTotal, "POST", { id: '' }); // POST不需要id,设置为空
}


};
});

});
getDNSList.on("error", (error) => {
log(String(error));
});
getDNSList.end();
});
}
});
requestIpv6.on('error', (error) => {
log(String(error));
})

function makeRequest(ipA, domainTotalA, method, record) {
let putBody = {
"content": ipA.toString(),
"name": domainTotalA,
"proxied": false,
"type": "AAAA"
}

let urlPut = parse("https://api.cloudflare.com/client/v4/zones/5926705fb3db4a7f866714500ef5a12c/dns_records/" + record.id);
urlPut.method = method;
urlPut.headers = {
...auth,
"Content-Type": "application/json"
};
const putDNS = https.request(urlPut, (PutDNSRes) => {
let putDNSBody = '';
PutDNSRes.setEncoding("utf8");
PutDNSRes.on("data", (putRes) => {
putDNSBody += putRes;
});
PutDNSRes.on("end", () => {
log(JSON.stringify(JSON.parse(putDNSBody), null, '\t'));
});

});
putDNS.on("error", (error) => {
log(String(error));
});
putDNS.end(JSON.stringify(putBody));
}

如果使用windows系统,则可以使用任务计划程序,设置开机一分钟之后执行脚本、和设置重复间隔一小时执行脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Const ForAppending = 8

Set objFSO = CreateObject("Scripting.FileSystemObject")

' 修改下面一行
Set objFile = objFSO.OpenTextFile([修改为nodejs的输出日志的绝对位置], ForAppending)

objFile.WriteLine Now
objFile.WriteLine("start")
objFile.Close

' 修改下面一行
strCommand = "cmd /c node [修改为nodejs的程序的绝对位置]"
CreateObject("Wscript.Shell").Run strCommand, 0, true

CMR 硬盘不要分区! 直接扫,SMR 硬盘需要写满再扫,空盘会作弊,读的是缓存。

写满磁盘:fsutil file createNew <文件名> <长度>

电源

cpu供电双8pin匹配主板cpu供电接口
gpu供电双8pin

主板

m2接口、pcie接口、sata接口是否冲突
高端型号风扇散热与显卡位置大小是否冲突
内存是否4个插槽
网卡速率

机箱

是否atx m-atx itx
前置usb3
硬盘位几个

显卡

供电
显卡长度匹配机箱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// @name            Bilibili Evolved
// @description 强大的哔哩哔哩增强脚本
// @updateURL https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/dist/bilibili-evolved.user.js
// @downloadURL https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/dist/bilibili-evolved.user.js
// @version 2.10.2

// 修改为

// @name Bilibili Evolved (Local)
// @description Bilibili Evolved (本地)
// @version 300.0

// 在require后面添加

// @require file://<硬盘位置>/Bilibili-Evolved/dist/bilibili-evolved.dev.user.js

Ctrl+Shift+b运行任务

模板语法

reactive() 只适用于对象,它不能持有如 string、number 或 boolean 这样的原始类型,对解构操作不友好。ref() 则可以接受任何类型。ref对象解包后不用使用.value
v-bind:<attribute> :class
v-on也可以@

v-for
v-bind:[attributeName]
@submit.prevent

v-if
v-else​
v-else-if
v-show

<template>

v-if v-for同时使用优先级不明显,v-if优先级更高,无法访问v-for的变量,会报错。使用computed计算filter后再用v-for

v-model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 计算属性
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})

// 挂载后处理原生dom
onMounted(() => {
ElementRef.value.textContent = 'mounted!'
})

// 副作用
watch(todoId, callback)

// 定义获取的属性值
defineProps(['title'])

// 属性传入事件
const emit = defineEmits(['response'])
emit('response', 'hello from child')
<ChildComp @response="(msg) => childMsg = msg" />

事件处理:@keyup.alt.enter="clear"

<tr is="vue:blog-post-row"></tr>

<BlogPost v-bind="Object" />

我们可以通过设定defineOptions({ inheritAttrs: false })和使用 v-bind=”$attrs” 来实现属性透传到非直接子组件

父元素:<slot name="header"></slot>子元素:<template #header>
slot子元素添加样式<div v-if="$slots.footer" class="card-footer">

依赖注入:组件后代提供数据

1
2
3
4
5
6
// 祖先元素处
const message = ref('hello')
provide('message', message)

//后代元素处
const message = inject('message')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// 定义默认值
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})

// 选项式API路由
$route.fullPath
$router.push('/about')

// 组合式API
import { useRoute, useRouter } from 'vue-router'
const router = useRouter()
const route = useRoute()
const search = computed({
get() {
// route.query
return route.query.search ?? ''
},
set(search) {
router.replace({ query: { search } })
},
})
router.push()
router.replace()
router.go(n)

// 指向/user/erina
<router-link :to="{ name: 'profile', params: { username: 'erina' } }">
User profile
</router-link>

// id路由
const routes = [
// 动态字段以冒号开始
{ path: '/users/:id', component: User },
]
{{ $route.params.id }}

// 对路由变化做出响应...
import { watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
watch(() => route.params.id, (newId, oldId) => {
// 对路由变化做出响应...
})


const routes = [
{
path: '/user/:id',
component: User,
children: [
// 当 /user/:id 匹配成功
// UserHome 将被渲染到 User 的 <router-view> 内部
{ path: '', component: UserHome },
{
// 当 /user/:id/profile 匹配成功
// UserProfile 将被渲染到 User 的 <router-view> 内部
path: 'profile',
component: UserProfile,
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 将被渲染到 User 的 <router-view> 内部
path: 'posts',
component: UserPosts,
},
],
},
]
<!-- User.vue -->
<template>
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view />
</div>
</template>

// 请注意redirect,导航守卫并没有应用在跳转路由上,而仅仅应用在其目标上。在上面的例子中,在 /home 路由中添加 beforeEnter 守卫不会有任何效果。

// 以 / 开头,以使嵌套路径中的路径成为绝对路径

// 登录重定向
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})

// 登录重定向
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]

// 路由重定向
function removeQueryParams(to) {
if (Object.keys(to.query).length)
return { path: to.path, query: {}, hash: to.hash }
}
function removeHash(to) {
if (to.hash) return { path: to.path, query: to.query, hash: '' }
}
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: [removeQueryParams, removeHash],
},
{
path: '/about',
component: UserDetails,
beforeEnter: [removeQueryParams],
},
]

// 路由懒加载,代码分片
const UserDetails = () => import('./views/UserDetails.vue')
const router = createRouter({
// ...
routes: [
{ path: '/users/:id', component: UserDetails }
// 或在路由定义里直接使用它
{ path: '/users/:id', component: () => import('./views/UserDetails.vue') },
],
})

路由

layout.js相当于公共父组件,page.js相当于react-router的index组件

form

zod + server actions

useSearchParams, usePathname, and useRouter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { useSearchParams, usePathname, useRouter } from 'next/navigation';

export default function Search() {
const searchParams = useSearchParams();
const pathname = usePathname();
const { replace } = useRouter();

function handleSearch(term: string) {
const params = new URLSearchParams(searchParams);
if (term) {
params.set('query', term);
} else {
params.delete('query');
}
replace(`${pathname}?${params.toString()}`);
}
return (
<input
className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
placeholder={placeholder}
onChange={(e) => {
handleSearch(e.target.value);
}}
defaultValue={searchParams.get('query')?.toString()}
/>
)
}

Metadata

pnpm不能删除多余的包

0%