Nicksxs's Blog

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

在 org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration 中进行配置解析,其中这一行就是解析 mappers

1
mapperElement(root.evalNode("mappers"));

具体的代码会执行到这

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 这里解析的不是 package
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// 根据 resource 和 url 还有 mapperClass 判断
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// resource 不为空其他为空的情况,就开始将 resource 读成输入流
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
// 初始化 XMLMapperBuilder 来解析 mapper
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}

然后再是 parse 过程

1
2
3
4
5
6
7
8
9
10
11
12
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 解析 mapper 节点,也就是下图中的mapper
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}

parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}

image

继续往下走

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
// 处理cache 和 cache 应用
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
// 因为我们是个 sql 查询,所以具体逻辑是在这里面
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}

然后是

1
2
3
4
5
6
7
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
// 然后没有 databaseId 就走到这
buildStatementFromContext(list, null);
}

继续

1
2
3
4
5
6
7
8
9
10
11
12
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
// 创建语句解析器
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 解析节点
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}

这个代码比较长,做下简略,只保留相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");

if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}

String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);


// 简略前后代码,主要看这里,创建 sqlSource

SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);


然后根据 LanguageDriver,我们这用的 XMLLanguageDriver,先是初始化

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
  @Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
// 初始化有一些逻辑
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
// 特别是这,我这次特意在 mapper 中加了 foreach,就是为了说下这一块的解析
initNodeHandlerMap();
}
// 设置各种类型的处理器
private void initNodeHandlerMap() {
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("where", new WhereHandler());
nodeHandlerMap.put("set", new SetHandler());
nodeHandlerMap.put("foreach", new ForEachHandler());
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());
}

初始化解析器以后就开始解析了

1
2
3
4
5
6
7
8
9
10
11
public SqlSource parseScriptNode() {
// 先是解析 parseDynamicTags
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}

但是这里可能做的事情比较多

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
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<>();
// 获取子节点,这里可以把我 xml 中的 SELECT 语句分成三部分,第一部分是 select 到 in,然后是 foreach 部分,最后是\n结束符
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
// 第一个节点是个纯 text 节点就会走到这
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
// 在 content 中添加这个 node
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
// 第二个节点是个带 foreach 的,是个内部元素节点
String nodeName = child.getNode().getNodeName();
// 通过 nodeName 获取处理器
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
// 调用处理器来处理
handler.handleNode(child, contents);
isDynamic = true;
}
}
// 然后返回这个混合 sql 节点
return new MixedSqlNode(contents);
}

再看下 handleNode 的逻辑

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
    @Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 又会套娃执行这里的 parseDynamicTags
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
String collection = nodeToHandle.getStringAttribute("collection");
Boolean nullable = nodeToHandle.getBooleanAttribute("nullable");
String item = nodeToHandle.getStringAttribute("item");
String index = nodeToHandle.getStringAttribute("index");
String open = nodeToHandle.getStringAttribute("open");
String close = nodeToHandle.getStringAttribute("close");
String separator = nodeToHandle.getStringAttribute("separator");
ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, nullable, index, item, open, close, separator);
targetContents.add(forEachSqlNode);
}
// 这里走的逻辑不一样了
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<>();
// 这里是 foreach 内部的,所以是个 text_node
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
// 第一个节点是个纯 text 节点就会走到这
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
// 判断是否动态是根据代码里是否有 ${}
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
// 所以还是会走到这
// 在 content 中添加这个 node
contents.add(new StaticTextSqlNode(data));
}
// 最后继续包装成 MixedSqlNode
// 再回到这里
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 处理 foreach 内部的各个变量
String collection = nodeToHandle.getStringAttribute("collection");
Boolean nullable = nodeToHandle.getBooleanAttribute("nullable");
String item = nodeToHandle.getStringAttribute("item");
String index = nodeToHandle.getStringAttribute("index");
String open = nodeToHandle.getStringAttribute("open");
String close = nodeToHandle.getStringAttribute("close");
String separator = nodeToHandle.getStringAttribute("separator");
ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, nullable, index, item, open, close, separator);
targetContents.add(forEachSqlNode);
}

再回过来

1
2
3
4
5
6
7
8
9
10
11
12
public SqlSource parseScriptNode() {
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
// 因为在 foreach 节点处理时直接是把 isDynamic 置成了 true
if (isDynamic) {
// 所以是个 DynamicSqlSource
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}

这里就做完了预处理工作,真正在执行的执行的时候还需要进一步解析

因为前面讲过很多了,所以直接跳到这里

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
  @Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
// 都知道是在这进去
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}

@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
// 前面也讲过这个,
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// 包括这里,是调用的org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// 然后是获取 BoundSql
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}

// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}

return boundSql;
}
// 因为前面讲了是生成的 DynamicSqlSource,所以也是调用这个的 getBoundSql
@Override
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
// 重点关注着
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
// 继续是这个 DynamicSqlNode 的 apply
public boolean apply(DynamicContext context) {
contents.forEach(node -> node.apply(context));
return true;
}
// 看下面的图

image

我们重点看 foreach 的逻辑

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
@Override
public boolean apply(DynamicContext context) {
Map<String, Object> bindings = context.getBindings();
final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings,
Optional.ofNullable(nullable).orElseGet(configuration::isNullableOnForEach));
if (iterable == null || !iterable.iterator().hasNext()) {
return true;
}
boolean first = true;
// 开始符号
applyOpen(context);
int i = 0;
for (Object o : iterable) {
DynamicContext oldContext = context;
if (first || separator == null) {
context = new PrefixedContext(context, "");
} else {
context = new PrefixedContext(context, separator);
}
int uniqueNumber = context.getUniqueNumber();
// Issue #709
if (o instanceof Map.Entry) {
@SuppressWarnings("unchecked")
Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
applyIndex(context, mapEntry.getKey(), uniqueNumber);
applyItem(context, mapEntry.getValue(), uniqueNumber);
} else {
applyIndex(context, i, uniqueNumber);
applyItem(context, o, uniqueNumber);
}
// 转换变量名,变成这种形式 select * from student where id in
// (
// #{__frch_id_0}
// )
contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
if (first) {
first = !((PrefixedContext) context).isPrefixApplied();
}
context = oldContext;
i++;
}
applyClose(context);
context.getBindings().remove(item);
context.getBindings().remove(index);
return true;
}
// 回到外层就会调用 parse 方法, 把#{} 这段替换成 ?
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql;
if (configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
sql = parser.parse(originalSql);
}
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

image

可以看到这里,然后再进行替换

image

真实的从 ? 替换成具体的变量值,是在这里
org.apache.ibatis.executor.SimpleExecutor#doQuery
调用了

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
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// -------------------------->
// 替换变量
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}

上次说 nas 的方案我是在 win10 下使用vmware workstation 搭建的黑裙虚拟机,采用 sata 物理磁盘直通的方式,算是跑通了黑裙的基础使用模式,但是后来发现的一个问题是之前没考虑到的,我买了不带 f 的处理器就是为了核显能做硬解,但是因为 cpu 是通过 vmware 虚拟的,目前看来是没法直通核显的,我是使用的 jellyfin 套件,一开始使用是默认的刮削方式,而且把电视剧当成了电影在刮削,所以基本不能看,后面使用了 tmm 作为刮削工具,可以手动填写 imdb 的id 来进行搜索,一般比较正式的剧都可以在豆瓣上找到的,然后让 jellyfin 只作为媒体管理器,但是前面的问题还是没解决,所以考虑了下可以在win10 下直接运行 jellyfin,媒体目录使用挂载在 nas 里的盘,这样 jellyfin 就能直接调用核显了,也算是把 win10 本身给利用起来了,并且文件的管理还是在黑裙中。
现在想来其实我这个方案还是不太合理,cpu 性能有点过剩想通过虚拟机的形式进行隔离使用,但是购买带核显的 cpu 最大的目的却没有实现,如果是直接裸机部署黑裙的话,真的是觉得 cpu 有点太浪费了,毕竟 passmark评分有 1w3 的cpu,只用来跑黑裙,所以网上的很多建议也是合理的,不过我可能是 win10 用的比较多了,还是习惯有 win 的环境。

之前这个机器已经算是跑起来了,虽然不是很完善也不是最佳实践,不过这篇可能也不算是失败经历了,因为最后成功跑起来了,在没法装最新版的 exsi 的情况下,并且我后面买的华硕 z370 主板点不亮,所以我也有点死心就直接用Windows 下装 vmware workstation 装虚拟机,然后直通硬盘来做 nas,这样可能对于其他人来说是很垃圾的方案,不过因为我很多常用软件的都是在 Windows 环境下的,并且纯黑裙的环境会比较浪费,相比一些同学在群晖里安装 Windows 虚拟机,我觉得还是反过来比较好,毕竟 vmware 做虚拟机应该是比群晖专业点,不过这个方案也有一些问题
第一种方式是直接找网上同学分享的处理好引导的 vmx,这种我碰到了一个问题就是在打开虚拟机安装群晖系统.pat的时候会提示“无法安装此文件,文件可能已损坏”,这其实不是真的文件已损坏,应该是群晖在做校验的时候存在什么条件没有通过,尝试了断网等方式都不成功,所以后来就用了比较釜底抽薪的方案,直接使用大佬开源的 arpl 引导制作工具
第二种方式一开始是躺在我 B 站收藏夹里,有个 up 制作的,做得很细致,也把很多细节也解释了,过程其实不难,就是按步骤一步步执行,但是一开始选择了 918+的系统在我的方案里安装不了,会提示无法安装,经过视频下的评论的知道,尝试使用 920+的系统就顺利安装成功了,这里唯一的区别就是在添加硬盘的时候要选择物理磁盘,然后 vmware 给出的硬盘选项是Physical0, Physical1,记得别选错了,然后在启动后我租了 raid5,4 块 4T 的盘,可以组成一个 10T 多一点的存储空间,打算用来作为比较长读写的区域,更大的盘可能就会作为只读区域,减小写入量。后面还有一些问题待解决,一个是电源,考虑换个稍好一点,因为目前看下来电源风扇的噪音比较大,还有就是主板,最近看中了微星的 z390,不过价格比较贵,打算慢慢蹲蹲看。

之前在绍兴家里的一条宽带送了个小米路由器 4A,正好原来的小米路由器 3 不知道为啥经常断流不稳定,而且只支持百兆,这边用了 200M 的宽带,感觉也比较浪费,所以就动了这个心思,但是还是有蛮多坑的,首先是看到了一篇文章,写的比较详细,
看到的就是这篇文章
这里使用的是 OpenWRTInvasion 这个项目来破解 ssh,首先这里有个最常见的一个问题,就是文件拉不到,所以有一些可行的方法就是自己起一个http 服务,可以修改脚本代码,直接从这个启动的 http 服务拉取已经下载下的文件,就这个问题我就尝试了很多次,还有就是这个 OpenWRTInvasion 最后一个支持 Windows 的版本就是 0.0.7,后面的版本其实做了很多的优化解决了文件的问题,一开始碰到的问题是本地起了文件服务但是没请求,或者请求了但后续 ssh 没有正常破解,我就换了 Mac 用最新版本的OpenWRTInvasion来尝试进行破解,发现还是不行,结果查了不少资料发现最根本的问题是这个路由器的新版本就不支持这种破解了,因为这个路由器新的版本都是 v2 版本,也就是2.30.x 版本的系统了,原来支持的是 2.28.x 的这些系统,后来幸好是找到了这个版本的系统支持的另一个恩山大神的文章,根据这个文章提供的工具进行破解就成功了,但是破解要多尝试几次,我第一次是失败的,小米路由器 4A 千兆版的版本号也会写作 R4Av2,在搜索一些资料的时候也可以用这个型号去搜,可能也是另一种黑话,路由器以前刷过梅林,padavan,还是第一次刷 openwrt,都已经忘了以前是怎么刷的来着,感觉现在越来越难刷了,特别是 ssh,想给我的 ax6 刷个 openwrt,发现前提是需要先有一个 openwrt 的路由器,简直了,变成先有鸡还是先有蛋的问题了,所以我把这个小米 4A 刷成 openwrt 也有这个考虑,毕竟 4A 配置上不太高,openwrt 各种插件可能还跑不起来,权当做练手和到时候用来开 AX6 的工具了。

上次记录了前面的一些失败经验,最重要的点还没提到,先发一下配置单
cpu i7-8700k
主板 技嘉 z370m-ds3h
内存 光威 ddr4-3200Mhz
硬盘 京东京造 512g
散热 利民 PA120
电源 先马平头哥额定 550w
机箱 爱国者半岛铁盒 F10
cpu 跟主板是板 U 套装某鱼买的二手的,说实话如果不是后面的网卡问题,这个板 U 套装还是比较良心的,一次点亮(以前没组装过,还不知道有点不亮的情况,后面就体验到了),但是这里就出现了一个很大的坑,因为我这次是想要在裸机上装 exsi,然后看到了群里苏大的一篇 exsi 最新版本 8 的镜像构建文章,硬件也不是很旧,就想着用最新的系统,镜像写进 ventoy 后启动发现报错找不到网卡,这会我还没发现问题的严重性,想着按一些教程打个驱动进去就好了,而且我还以为驱动只要跟镜像 iso 放一块就行了,后面随着深入了解就知道要把驱动打进 iso 镜像里,但是找了一通发现我的网卡是瑞昱的 RTL8168,这个型号的板载网卡,走的是 PCIE 通道,有驱动的最后支持的系统是 exsi6.7,再往后就没有完整打包好的社区版驱动可以使用了,所以这是踩的第一个大坑,照理这个事情也没这么大问题,退回来 6.7 不就行了,问题恰恰是我那时候还不懂,又想用更新的系统,所以就在网上搜了半天,发现华硕的 z370 tuf gaming 系列是用的 intel 的网卡,社区的网卡驱动对 intel 的网卡支持比较好,所以想着还是换个主板算了,其实还有不少选择,买个 pcie 的 intel 网卡或者 usb 的其他千兆网卡,有个说出来可能比较难理解的,usb 的社区版驱动反而比 pcie 的支持得广,pcie 的还是只支持 intel 的。
在某多多上买了个二手的 z370 tuf gaming 主板,结果踩到了第二个坑,可能比较小白的经验是,前面因为买的板 U 套装,他 cpu 是直接装在主板上邮给我的,所以我没装过 cpu,这回买来这块二手的华硕主板对我来说是第一次装 cpu,不过好像难度不大,一下就装好了,但结果就很惨,就是点不亮,散热器风扇会转,但是键盘灯不亮,而且散热风扇还转得很快,我还试着把内存换个槽,结果四个槽都不行,这个时候就很害怕了,看上去这家店也不像是太坑的,毕竟大量地在卖,所以我就很担心是不是前面 cpu 装的不对,把针脚什么的搞坏了,这个时候已经搞到晚上很迟了,但还是忍不住又装回原来的技嘉主板试了下,幸好能正常点亮,算了,还是就用技嘉这块主板吧,接口配置稍微差了点,网卡也不支持最新版的 exsi,所以我就用 vmware workstation 了,在 win10 的 lstc 上装一个,有点性能损耗就损耗吧,反正我也不暴力使用,能跑跑其他 Ubuntu 虚拟机啥的就可以了,或者回到前面的结果,可以装 6.7 的,网上带了瑞昱网卡驱动的 exsi6.7 的镜像挺多的,可以自己打一个或者用别人打包好的。折腾不止踩坑不止呐。

0%