CrazyAirhead

疯狂的傻瓜,傻瓜也疯狂——傻方能执著,疯狂才专注!

0%

Solon 集成 Aifei

说明

2026-06-06 Aifei 框架正式发布了。波总在五一的时候已经放出了 aifei 的正式版和 vip的订阅,作为第一批用户,用上了 aifei-vip-arch 和 aifei-vip,趁着五一学习了一波,顺便让还让AI 生成了Aifei-dev 的 skills,感兴趣的同学可以试一试,https://gitee.com/CrazyAirhead/aifei-dev。当然为了验证这个 skills 我也是做了个示例的,简单的截两张图,看看效果:

img

img

因为 Aifei 刚发布,还有很多生态没有起来,而我日常使用的都是 Solon,而且 Solon 生态已经比较完善,又因为非常喜欢 Aifei,于是先把 aifei-enjoy 和 aifei-db 集成到 Solon 生态里面。Aifei 的完全使用就边用边学吧。

集成 aifei-enjoy

由我贡献的 solon-view-aifei-enjoy 插件已经被合并到 Solon 主版本,将会在Solon 的 4.x 版本发布,https://solon.noear.org/article/1456。

因为 Solon 原来就集成了 enjoy,enjoy 又是波总比较新的作品,此次 aifei-enjoy,主要是调整包名,没有太大的变动。所以集成 aifei-enjoy,主要是在 solon-view-enjoy 插件的基础上修复包名。

有一点需要注意的就是enjoy 不支持 Directive 工厂,Solon 的作者自己做一个 enjoy 的扩展,也就是 solon-view-enjoy 不是使用原版的 enjoy 的 jar 包。在 aifei-enjoy 中,提供了原生的支持,所以可以优先使用 Solon 的容器对象。

1
2
3
4
5
6
7
8
9
10
Engine.setDirectiveFactory(
(cls) -> {
Directive directive = Objects.requireNonNull(Solon.context()).getBean(cls);
if (directive != null) {
return directive;
}

// 兜底
return ClassUtil.newInstance(cls);
});

集成 aifei-db

aifei-db-solon-plugin 的插件,我还在开发中,主体功能是基于 activerecord-solon-plugin 改造的,因为 aifei-db 相对于 activerecord 有较大的变动,我要多验证下,暂时还没有签入和提交 PR。

img

相对于 ActiveRecord,aifei-db 中的 AifeiRow 做了比较多的增强,因此注册AifeiRow被简化。

1
2
3
4
5
6
7
8
9
10
11
/**
* @author airhead
*/
public class ModelManager {
@Getter static Set<Class<? extends AifeiRow<?>>> modelSet = new LinkedHashSet<>();

@SuppressWarnings("unchecked")
public static void addModel(Table table, AifeiRow<?> model) {
modelSet.add((Class<? extends AifeiRow<?>>) model.getClass());
}
}

另外需要注意的是 aifei-core 不包含 Slf4jLogFactory,因此需要自己扩展一个。

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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
package top.godenyear.ray.framework.aifei.db;

import cn.aifei.log.Log;
import cn.aifei.log.LogFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Aifei LogFactory 的 Slf4j 桥接实现。 解决 aifei-core 不含 Slf4jLogFactory 的问题。
*
* @author airhead
*/
public class Slf4jLogFactory implements LogFactory {

@Override
public Log getLog() {
return getLog(LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME));
}

@Override
public Log getLog(Class<?> clz) {
return getLog(LoggerFactory.getLogger(clz));
}

@Override
public Log getLog(String name) {
return getLog(LoggerFactory.getLogger(name));
}

private Log getLog(Logger slf4jLogger) {
return new Log() {
@Override
public String getName() {
return slf4jLogger.getName();
}

@Override
public boolean isTraceEnabled() {
return slf4jLogger.isTraceEnabled();
}

@Override
public void trace(String msg, Throwable t) {
slf4jLogger.trace(msg, t);
}

@Override
public void trace(String msg) {
slf4jLogger.trace(msg);
}

@Override
public void trace(String format, Object arg) {
slf4jLogger.trace(format, arg);
}

@Override
public void trace(String format, Object arg1, Object arg2) {
slf4jLogger.trace(format, arg1, arg2);
}

@Override
public void trace(String format, Object... args) {
slf4jLogger.trace(format, args);
}

@Override
public void trace(java.util.function.Supplier<String> supplier) {
if (isTraceEnabled()) {
slf4jLogger.trace(supplier.get());
}
}

@Override
public boolean isDebugEnabled() {
return slf4jLogger.isDebugEnabled();
}

@Override
public void debug(String msg, Throwable t) {
slf4jLogger.debug(msg, t);
}

@Override
public void debug(String msg) {
slf4jLogger.debug(msg);
}

@Override
public void debug(String format, Object arg) {
slf4jLogger.debug(format, arg);
}

@Override
public void debug(String format, Object arg1, Object arg2) {
slf4jLogger.debug(format, arg1, arg2);
}

@Override
public void debug(String format, Object... args) {
slf4jLogger.debug(format, args);
}

@Override
public void debug(java.util.function.Supplier<String> supplier) {
if (isDebugEnabled()) slf4jLogger.debug(supplier.get());
}

@Override
public boolean isInfoEnabled() {
return slf4jLogger.isInfoEnabled();
}

@Override
public void info(String msg, Throwable t) {
slf4jLogger.info(msg, t);
}

@Override
public void info(String msg) {
slf4jLogger.info(msg);
}

@Override
public void info(String format, Object arg) {
slf4jLogger.info(format, arg);
}

@Override
public void info(String format, Object arg1, Object arg2) {
slf4jLogger.info(format, arg1, arg2);
}

@Override
public void info(String format, Object... args) {
slf4jLogger.info(format, args);
}

@Override
public void info(java.util.function.Supplier<String> supplier) {
if (isInfoEnabled()) slf4jLogger.info(supplier.get());
}

@Override
public boolean isWarnEnabled() {
return slf4jLogger.isWarnEnabled();
}

@Override
public void warn(String msg, Throwable t) {
slf4jLogger.warn(msg, t);
}

@Override
public void warn(String msg) {
slf4jLogger.warn(msg);
}

@Override
public void warn(String format, Object arg) {
slf4jLogger.warn(format, arg);
}

@Override
public void warn(String format, Object arg1, Object arg2) {
slf4jLogger.warn(format, arg1, arg2);
}

@Override
public void warn(String format, Object... args) {
slf4jLogger.warn(format, args);
}

@Override
public void warn(java.util.function.Supplier<String> supplier) {
if (isWarnEnabled()) {
slf4jLogger.warn(supplier.get());
}
}

@Override
public boolean isErrorEnabled() {
return slf4jLogger.isErrorEnabled();
}

@Override
public void error(String msg, Throwable t) {
slf4jLogger.error(msg, t);
}

@Override
public void error(String msg) {
slf4jLogger.error(msg);
}

@Override
public void error(String format, Object arg) {
slf4jLogger.error(format, arg);
}

@Override
public void error(String format, Object arg1, Object arg2) {
slf4jLogger.error(format, arg1, arg2);
}

@Override
public void error(String format, Object... args) {
slf4jLogger.error(format, args);
}

@Override
public void error(java.util.function.Supplier<String> supplier) {
if (isErrorEnabled()) {
slf4jLogger.error(supplier.get());
}
}
};
}
}

处理序列化问题

不管是 ActiveRecord 中 Model,还是Aifei 中的 AifeiRow,AifeiModel,他的底层数据结构是 Map,通过Bean 方法包装后进行 Bean 的操作,而 AifeiModel 更进一步使用了链式的 setter。

在 Solon 文档 https://solon.noear.org/article/18 中说到,Model 转 Json 输入的方法,不过在我的使用过程中体验不是很好。

1
2
3
4
5
6
7
8
9
10
11
//应用加载完成事件
@Componentpublic
class AppPluginLoadEndEventListener implements EventListener<AppPluginLoadEndEvent> {
@Overridepublic void onEvent(AppPluginLoadEndEvent e) throws Throwable {
//定制 json 序列化输出(使用新的处理接管 "@json" 指令)
e.app().renders().register("@json", (data, ctx) -> {
String json = JFinalJson.getJson().toJson(data);
ctx.outputAsJson(json);
});
}
}

这次在进一步整合 Aifei-db 的过程中,算是找到了一些比较好的集成方式,可以参看Solon 的 issue,重点涉及 EntityConverter 和 PropsConverterExt。

https://gitee.com/opensolon/solon/issues/IJSD9V https://gitee.com/opensolon/solon/issues/IJSCEX

主体的思路是通过完整的替换序列化插件,从而减少扩展链路和调整的地方。

Snack4StringSerializer

mime 为空时也使用JSON序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 是否匹配
*
* @param ctx 请求上下文
* @param mime 内容类型
*/
@Override
public boolean matched(Context ctx, String mime) {
if (mime == null || mime.isEmpty()) {
return true;
} else {
return mime.contains(label) || mime.startsWith(MimeType.APPLICATION_X_NDJSON_VALUE);
}
}

Snack4EntityConverter

changeEntityDo 时使用JSON 进行转换,而不是交给 ClassUtil 处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Snack 实体转换器
*
* @author noear
* @since 3.6
*/
public class Snack4EntityConverter extends AbstractStringEntityConverter<Snack4StringSerializer> {
@Override
protected Object changeEntityDo(Context ctx, ParamWrap p, String name, Class<?> type)
throws Exception {
Props props = new Props().addAll(ctx.paramMap());
Options options = serializer.getDeserializeConfig().getOptions();
return ONode.ofBean(props, options).toBean(type);
}
}

针对 AifeiRow 提供相应的 encoder

ModelEncoder

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
/**
* @author airhead
*/
public class ModelEncoder<T extends AifeiRow<?>> implements ObjectPatternEncoder<T> {
@Override
public ONode encode(EncodeContext ctx, T value, ONode node) {
if (value == null) {
return node;
}

return encode(node, value.data());
}

@Override
public boolean canEncode(Object value) {
return value instanceof AifeiRow;
}

protected ONode encode(ONode node, Map<String, Object> map) {
if (CollUtil.isEmpty(map)) {
return node;
}

node.fill(map);

return node;
}
}

配置 Snack4StringSerializer

为AifeiRow 指定 ModelEncoder

1
2
3
4
5
6
7
8
context.getBeanAsync(
Snack4StringSerializer.class,
serializer -> {
serializer.getDeserializeConfig().addFeatures(Feature.Decode_AllowUseSetter);
serializer.getSerializeConfig().addFeatures(Feature.Write_UseSmlCamelStyle);

serializer.addEncoder(AifeiRow.class, new ModelEncoder<>());
});

小结

经过以上的处理,现在应该能在 Solon 里面愉快的玩耍 aifei-enjoy 和 aifei-db了,enjoy 模版,SQL 模板,原来想要的都回来了。虽然 Solon 比 Aifei 的概念会多一些,但整体还是非常克制的,因此即便多了一些 Controller的逻辑,对于AI 的消耗也不会增加太多。

补充

关于 aifei 的一些尝试,可以仓库代码 https://github.com/crazy-airhead/aifei

  1. 扩展 aifei-json-snack4,替代fastjson2,使用 snack4 进行处理 JSON。
  2. 扩展 aifei-feathttp, 替代undertow,使用 feathttp(原来的smarthttp)作为底层服务器。

欢迎关注我的其它发布渠道