Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

我们在使用 ssh 连接的使用有一个很好用功能,就是端口转发,而且使用的方式也很多样,比如我们经常用 vscode 来做远程开发的话,一般远程连接就可以基于 ssh,前面也介绍过 vscode 的端口转发,并且可以配置到 .ssh/config 配置文件里,只不过最近在一次使用的过程中发现了一个问题,就是在一台 Ubuntu 的某云服务器上想 ssh 到另一台服务器上,并且做下端口映射,但是发现报了个错,

1
bind: Cannot assign requested address

查了下这个问题,猜测是不是端口已经被占用了,查了下并不是,然后想到是不是端口是系统保留的,

1
sysctl -a |grep port_range

结果中

1
net.ipv4.ip_local_port_range = 50000    65000      -----意味着50000~65000端口可用

发现也不是,没有限制,最后才查到这个原因是默认如果有 ipv6 的话会使用 ipv6 的地址做映射
所以如果是命令连接做端口转发的话,

1
ssh -4 -L 11234:localhost:1234 x.x.x.x

使用-4来制定通过 ipv4 地址来做映射
如果是在 .ssh/config 中配置的话可以直接指定所有的连接都走 ipv4

1
2
Host *
AddressFamily inet

inet代表 ipv4,inet6代表 ipv6
AddressFamily 的所有取值范围是:”any”(默认)、”inet”(仅IPv4)、”inet6”(仅IPv6)。
另外此类问题还可以通过 ssh -v 来打印更具体的信息

上次就比较简单的讲了使用,这块也比较简单,因为封装得不是很复杂,首先我们从 select 作为入口来看看,这个具体的实现,

1
2
3
4
5
String selectSql = new SQL() {{
SELECT("id", "name");
FROM("student");
WHERE("id = #{id}");
}}.toString();

SELECT 方法的实现,

1
2
3
4
5
public T SELECT(String... columns) {
sql().statementType = SQLStatement.StatementType.SELECT;
sql().select.addAll(Arrays.asList(columns));
return getSelf();
}

statementType是个枚举

1
2
3
public enum StatementType {
DELETE, INSERT, SELECT, UPDATE
}

那这个就是个 select 语句,然后会把参数转成 list 添加到 select 变量里,
然后是 from 语句,这个大概也能猜到就是设置下表名,

1
2
3
4
public T FROM(String table) {
sql().tables.add(table);
return getSelf();
}

往 tables 里添加了 table,这个 tables 是什么呢
这里也可以看下所有的变量,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
StatementType statementType;
List<String> sets = new ArrayList<>();
List<String> select = new ArrayList<>();
List<String> tables = new ArrayList<>();
List<String> join = new ArrayList<>();
List<String> innerJoin = new ArrayList<>();
List<String> outerJoin = new ArrayList<>();
List<String> leftOuterJoin = new ArrayList<>();
List<String> rightOuterJoin = new ArrayList<>();
List<String> where = new ArrayList<>();
List<String> having = new ArrayList<>();
List<String> groupBy = new ArrayList<>();
List<String> orderBy = new ArrayList<>();
List<String> lastList = new ArrayList<>();
List<String> columns = new ArrayList<>();
List<List<String>> valuesList = new ArrayList<>();

可以看到是一堆 List 先暂存这些sql 片段,然后再拼装成 sql 语句,
因为它重写了 toString 方法

1
2
3
4
5
6
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sql().sql(sb);
return sb.toString();
}

调用的 sql 方法是

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
public String sql(Appendable a) {
SafeAppendable builder = new SafeAppendable(a);
if (statementType == null) {
return null;
}

String answer;

switch (statementType) {
case DELETE:
answer = deleteSQL(builder);
break;

case INSERT:
answer = insertSQL(builder);
break;

case SELECT:
answer = selectSQL(builder);
break;

case UPDATE:
answer = updateSQL(builder);
break;

default:
answer = null;
}

return answer;
}

根据上面的 statementType判断是个什么 sql,我们这个是 selectSQL 就走的 SELECT 这个分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private String selectSQL(SafeAppendable builder) {
if (distinct) {
sqlClause(builder, "SELECT DISTINCT", select, "", "", ", ");
} else {
sqlClause(builder, "SELECT", select, "", "", ", ");
}

sqlClause(builder, "FROM", tables, "", "", ", ");
joins(builder);
sqlClause(builder, "WHERE", where, "(", ")", " AND ");
sqlClause(builder, "GROUP BY", groupBy, "", "", ", ");
sqlClause(builder, "HAVING", having, "(", ")", " AND ");
sqlClause(builder, "ORDER BY", orderBy, "", "", ", ");
limitingRowsStrategy.appendClause(builder, offset, limit);
return builder.toString();
}

上面的可以看出来就是按我们常规的 sql 理解顺序来处理
就是select ... from ... where ...这样子
再看下 sqlClause 的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void sqlClause(SafeAppendable builder, String keyword, List<String> parts, String open, String close,
String conjunction) {
if (!parts.isEmpty()) {
if (!builder.isEmpty()) {
builder.append("\n");
}
builder.append(keyword);
builder.append(" ");
builder.append(open);
String last = "________";
for (int i = 0, n = parts.size(); i < n; i++) {
String part = parts.get(i);
if (i > 0 && !part.equals(AND) && !part.equals(OR) && !last.equals(AND) && !last.equals(OR)) {
builder.append(conjunction);
}
builder.append(part);
last = part;
}
builder.append(close);
}
}

这里的拼接方式还需要判断 AND 和 OR 的判断逻辑,其他就没什么特别的了,只是where 语句中的 lastList 不知道是干嘛的,好像只有添加跟赋值的操作,有知道的大神也可以评论指导下

mybatis 还有个比较有趣的功能,就是使用 SQL 类生成 sql,有点类似于 hibernate 或者像 php 的 laravel 框架等的,就是把sql 这种放在 xml 里或者代码里直接写 sql 用对象的形式

select语句

比如这样

1
2
3
4
5
6
7
8
public static void main(String[] args) {
String selectSql = new SQL() {{
SELECT("id", "name");
FROM("student");
WHERE("id = #{id}");
}}.toString();
System.out.println(selectSql);
}

打印出来就是

1
2
3
SELECT id, name
FROM student
WHERE (id = #{id})

应付简单的 sql 查询基本都可以这么解决,如果习惯这种模式,还是不错的,
其实以面向对象的编程模式来说,这样是比较符合面向对象的,先不深入的解析这块的源码,先从使用角度讲一下

比如 update 语句

1
2
3
4
5
String updateSql = new SQL() {{
UPDATE("student");
SET("name = #{name}");
WHERE("id = #{id}");
}}.toString();

打印输出就是

1
2
3
UPDATE student
SET name = #{name}
WHERE (id = #{id})

insert 语句

1
2
3
4
5
6
String insertSql = new SQL() {{
INSERT_INTO("student");
VALUES("name", "#{name}");
VALUES("age", "#{age}");
}}.toString();
System.out.println(insertSql);

打印输出

1
2
3
INSERT INTO student
(name, age)
VALUES (#{name}, #{age})

delete语句

1
2
3
4
5
String deleteSql = new SQL() {{
DELETE_FROM("student");
WHERE("id = #{id}");
}}.toString();
System.out.println(deleteSql);

打印输出

1
2
DELETE FROM student
WHERE (id = #{id})

开始修老房子又可以更新这个系列了,比较无聊,就是帮着干点零活的记录,这次过去起的比较早,前几天是在翻新瓦片,到这次周六是收尾了,到了的时候先是继续筛了沙子,上周也筛了,就是只筛了一点点,筛沙子的那个像纱窗一样的还是用一扇中空的中间有一根竖档的门钉上铁丝网做的,就是沙子一直放在外面,原来是有袋子装好的,后来是风吹雨打在上面的都已经破掉,还夹杂了很多树叶什么的,需要过下筛,并且前面都是下雨天,沙子都是湿的,不太像我以前看村里有人造房子筛沙子那样,用铲子铲上去就自己都下去的,湿的就是会在一坨,所以需要铲得比较少,然后撒的比较开,这个需要一点经验,然后如果有人一起的话就可以用扫把按住扫一下,这样就会筛得比较有效,不至于都滑下去,沙子本来大部分是可以筛出来的,还有一点就是这种情况网筛需要放得坡度小一点,不然就更容易直接往下调,袋子没破的就不用过筛了,只是湿掉了的是真的重,筛完了那些破掉了的袋子里的沙子,就没有特别的事情要做了,看到大工在那打墙,有一些敲下来的好的砖头就留着,需要削一下上面的混凝土,据大工说现在砖头要七八毛一块了,后面能够重新利用还是挺值钱的,我跟 LD 就用泥刀和铁锹的在那慢慢削,砖头上的泥灰有的比较牢固有的就像直接是沙子,泥刀刮一下就下来了,有的就结合得比较牢固,不过据说以前的砖头工艺上还比较落后,这个房子差不多是三十年前了的,砖头表面都是有点不平,甚至变形,那时候可能砖头是手工烧制的,现在的砖头比较工艺可好多了,不过可能也贵了很多,后来老丈人也过来了,指导了我们拌泥灰,就是水泥和黄沙混合,以前小时候可喜欢玩这个了,可是就也搞不清楚这个是怎么搅拌的,只看见是水泥跟黄沙围城一圈,中间放水,然后一点点搬进去,首先需要先把干的水泥跟黄沙进行混合,具体的比例是老丈人说的,拌的方式有点像堆沙堆,把水泥黄沙铲起来堆起来,就一直要往这个混合堆的尖尖上堆,这样子它自己滑下来能更好地混合,来回两趟就基本混合均匀了,然后就是跟以前看过的,中间扒拉出一个空间放水,然后慢慢把周围的混合好的泥沙推进去,需要注意不要太着急,旁边的推进去太多太快就会漏水出来,一个是会把旁边的地给弄脏,另一个也铲不回水,然后就是推完所有的都混合水了,就铲起来倒一下,再将铲子翻过来捣几下,后面我们就去吃饭了,去了一家叫金记的,又贵又不太好吃的店,就是离得比较近,六七个人只有六七个菜,吃完要四百多,也是有点离谱了。
下午是重头戏,其实本来倒没啥事,就说帮忙搞下靠背(就是踢脚线上面到窗台附近的用木头还有其他材料的装饰性的),都撬撬掉,但是真的是有点离谱了,首先是撬棒真的很重,20 斤的重量(网上查的,没有真的查过),抡起来还要用力地铲进去,因为就是破坏性的要把整个都撬掉,对于我这种又没技巧又没力气的非专业选手,抡起撬棍铲两下手就开始痛了,只是也比较犟,不想刚开始弄就说太重了要休息,后面都完全靠的一点倔强劲撑着,看着里面的工艺感觉也是不容易的,直着横着的木条有好多,竖的一整条,每隔三五十公分,横着的就是三五十公分,每根都要用钉子钉起来,然后外层好像是贴上去,在同一个面的开了头之后就能靠着蛮力往下撬,但是到了转角就又要重新开头,而且最上面一根横条跟紧邻的那一块,大概十几公分,是横着的三条钉在一起,真的是大力都出不了奇迹了,用撬棍的一头用力地敲打都很震手,要从下面往上铲进去撬开一点,然后再从上面往下敲打,这里比较重要的是要小心钉子,我这次运气比较好,踩下去已经扎到了,不过正好在脚趾缝里,没有扎到脚,还是要小心的,做完这个我的手真的是差不多废了,上臂的疼痛已经动一下就受不了了,后面有撬下了最下面当踢脚线的小瓷砖,这个房子估计中间修过一次,两批水泥糊的,新的那批粘的特别牢,敲敲打打了半天才下来一点点,锤子敲上去跟一整块石头一样,震手又没有进展,整个搞完,楼上又在敲墙了,下面的灰尘也是从没见过,我一直在那洒水都完全没有缓解,就上去跟 LD 一起拣砖头,手痛到只能抬两块砖头都会痛了。
回到家里开始越来越痛,两个手就完全没法动了,应该也是肌肉拉伤了,我这样是没足够的力气也不会什么技巧,像大工说的,他们也累也难,只是为了赚钱,不过他们有了经验跟技巧,会注意怎么使力不容易受伤,怎么样比较省力,还有一点就是即使这么累,他们一般也下午五点半就下班了,真的很累了,至少还有不少时间可以回家休息,而我们的职业呢,就像 LD 说的回家就像住酒店,就只是来洗澡睡个觉,希望能改善吧

上周因为一些事情没回去,买好了内存条这周才回去用,结果不知道是 U 盘的问题还是什么其他原因原来装的那个系统起不来,然后想用之前一直用的 LD 的笔记本,结果 LD 的老笔记本也起不来了,一直卡在启动界面,猜测可能是本身也比较老了,用的机械硬盘会不会被我一直动而损坏了,所以就想搞个老毛桃跟新搞一个启动盘,结果这周最大的坑就在这了,在我的 15 寸 mbp 上用etcher做了个 win10 的启动盘,可能是分区表格式是 GUID,不是 MBR 的,老电脑不能识别,就像装个虚拟机来做启动盘,结果像老毛桃不支持这种用虚拟机的,需要本地磁盘,然后我就有用 bootcamp 安装了个 win10,又没网络,查了下网络需要用 bootcamp 下载 Windows Support 软件,下了好久一直没下完,后来又提示连不上苹果服务器,好不容易下完了,开启 win10 后,安装到一半就蓝屏了,真的是够操蛋的,在这个时候真的很需要有一个 Windows 机器的支持,LD 的那个笔记本也跟我差不多老了,虽然比较慢但之前好歹也还可以用,结果这下也被我弄坏了,家里就没有其他机器可以支持了,有点巧妇难为无米之炊的赶脚,而且还是想再折腾下我的老电脑,后面看看打算做几个 U 盘,分工职责明确,一个老毛桃的PE 盘,老毛桃的虽然不是很纯净的,但是是我一直以来用过内置工具比较全,修复启动功能比较强大的一个 pe,没用过 winpe,后续可以考虑使用下,一个 win7 的启动盘,因为是老电脑,如果后面是新的电脑可以考虑是 win10,一个 win to go 盘,这样我的 mbp 就可以直接用 win10 了,不过想来也还是要有一台 win 机器才比较好,不然很多时候都是在折腾工具,工欲善其事必先利其器,后面也可以考虑能够更深入的去了解主板的一些问题,我原来的那个主板可能是一些小的电阻等问题,如果能自己学习解决就更好了,有时候研究下还是有意思的。

0%