本文探讨了豆包编程助手在提高编程生产力方面的应用。使用豆包助手实现MQTT协议的服务端代码。它还提供了代码修复、补全、注释生成等功能,但存在一些限制,如对第三方库的理解不足。豆包助手在简单逻辑代码生成和代码注释方面表现出色,但在复杂场景下需要用户进一步修正。插件的交互设计和语言选择也有待改进。
MQTT概念
主题:MQTT 使用主题来过滤消息,类似于电子邮件中的标签。订阅者可以订阅一个或多个主题,并接收到所有匹配该主题的消息。
消息:MQTT 中的消息是数据传输的基本单元,包含主题、有效载荷(Payload)和一些控制信息。
客户端:任何可以发送或接收 MQTT 消息的设备或应用程序都可以被称为客户端。
服务器:MQTT 服务器,也称为代理,是消息的中转站。它接收来自客户端的消息,过滤这些消息,并将它们传递给订阅了相应主题的客户端。
发布/订阅模型:MQTT 采用发布/订阅模式,允许客户端发布消息到特定的主题,而其他客户端可以订阅这些主题来接收消息。
服务质量:MQTT 定义了三种服务质量级别,分别是:
QoS 0:最多一次,消息可能丢失,但不重复。
QoS 1:至少一次,确保消息至少送达一次,可能会重复。
QoS 2:只有一次,确保消息只送达一次。
MQTT相关文章:
豆包编程助手安装
在线安装官网上有,我就不赘述了,提供离线安装教程。
idea离线安装教程:
settings
->plugins
->installed
->设置->Install Plugin from Disk
->选择已经下载好的marscode-1.2.0.0.zip
豆包idea插件:
https://pan.quark.cn/s/ecccb0effcb4(30天有效,如果失效可以关注公众号【java程序猿技术】发送安装包获取,路径:软件/ai-code-copilot/字节豆包)
豆包代码生成功能测试
豆包 MarsCode 编程助手可以根据你的自然语言描述生成对应的代码片段。
在输入框中用自然语言描述你的需求。
点击 发送 按钮或敲击回车键。
豆包代码助手实现MQTT服务端
场景一
用户:
使用Netty实现mqtt服务端,实现CONNECT、CONNACK、PUBLISH、SUBSCRIBE、SUBACK、UNSUBSCRIBE、UNSUBACK、PINGREQ、PINGRESP、DISCONNECT
MarsCode:
好的,以下是使用Netty实现MQTT服务端的示例代码,包括了CONNECT、CONNACK、PUBLISH、SUBSCRIBE、SUBACK、UNSUBSCRIBE、UNSUBACK、PINGREQ、PINGRESP、DISCONNECT
等报文的处理逻辑。
首先,你需要在项目中引入Netty的依赖:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.76.Final</version>
</dependency>
然后,创建一个Netty服务器来处理MQTT协议的连接和消息:
代码省略。。。。。。
接下来,创建一个MqttBrokerHandler类来处理MQTT协议的各种报文:
代码省略。。。。。。
在这个示例中,我们创建了一个简单的MQTT代理,它能够处理CONNECT、CONNACK、PUBLISH、SUBSCRIBE、SUBACK、UNSUBSCRIBE、UNSUBACK、PINGREQ、PINGRESP、DISCONNECT
等报文。你可以根据实际需求扩展这个代理,例如实现消息的持久化、用户认证、访问控制等功能。
场景二
用户:
定义mqtt客户端对象,用来记录用户名、channelId、订阅信息、连接时间、ip、断开连接时间
MarsCode:
在MQTT协议中,客户端对象通常包含一些基本信息,如用户名、客户端ID、订阅信息、连接时间、IP地址以及断开连接时间等。以下是一个简单的Java类定义,用于表示MQTT客户端对象:
代码省略。。。。。。
这个类提供了基本的属性设置和获取方法,你可以根据实际需求扩展它,例如添加更多的属性或方法来处理客户端的行为。在实际应用中,你可能需要一个数据结构来存储这些客户端对象,以便管理连接到MQTT代理的所有客户端。
场景三
用户:
在handleConnect方法中实现客户端账号密码校验,校验通过则记录客户端信息MqttClient,如果验证失败则关闭回话,且1分钟内超过三次验证失败则锁账号5分钟
MarsCode:
好的,根据你的需求,你需要在 handleConnect 方法中实现客户端账号密码校验,并且记录客户端信息。如果验证失败,则关闭会话,并且在 1 分钟内超过三次验证失败则锁定账号 5 分钟。以下是修改后的代码:
代码省略。。。。。。
在这个修改后的代码中,我们添加了一个 userPasswords 静态 Map 来存储预设的用户名和密码,并且添加了一个 failedAttempts 静态 Map 来记录每个用户名的失败尝试次数。在 handleConnect 方法中,我们检查用户名和密码是否匹配,如果匹配则记录客户端信息并发送 CONNACK 消息;如果不匹配,则记录失败次数并在超过三次失败后锁定账号。
场景四
用户:
handleSubscribe 方法把客户端订阅的topic记录到MqttClient
MarsCode:
好的,根据你的需求,你需要在 handleSubscribe 方法中记录客户端订阅的主题。以下是修改后的代码:
代码省略。。。。。。
在这个修改后的代码中,我们添加了一个 clients 静态 Map 来存储客户端信息,并且在 handleSubscribe 方法中记录了客户端订阅的主题。当客户端订阅或取消订阅主题时,我们会更新 MqttClient 对象中的订阅信息。
MarsCode最后输出代码汇总:
public class MqttBroker {
private static final Logger logger = LoggerFactory.getLogger(MqttBroker.class);
private final int port;
public MqttBroker(int port) {
this.port = port;
}
public void start() throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(
new MqttDecoder(),
new MqttEncoder(),
new MqttBrokerHandler()
);
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.bind(port).sync();
logger.info("MQTT broker started on port {}", port);
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
int port = 1883; // 默认端口
if (args.length > 0) {
port = Integer.parseInt(args[0]);
}
new MqttBroker(port).start();
}
}
public class MqttClient {
private String username;
private String clientId;
private List<String> subscriptions;
private Date connectTime;
private String ipAddress;
private Date disconnectTime;
// 构造方法
public MqttClient(String username, String clientId, List<String> subscriptions, String ipAddress) {
this.username = username;
this.clientId = clientId;
this.subscriptions = subscriptions;
this.connectTime = new Date();
this.ipAddress = ipAddress;
}
// Getters and Setters for the fields
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public List<String> getSubscriptions() {
return subscriptions;
}
public void setSubscriptions(List<String> subscriptions) {
this.subscriptions = subscriptions;
}
public Date getConnectTime() {
return connectTime;
}
public void setConnectTime(Date connectTime) {
this.connectTime = connectTime;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public Date getDisconnectTime() {
return disconnectTime;
}
public void setDisconnectTime(Date disconnectTime) {
this.disconnectTime = disconnectTime;
}
}
public class MqttBrokerHandler extends SimpleChannelInboundHandler<MqttMessage> {
private static final Logger logger = LoggerFactory.getLogger(MqttBrokerHandler.class);
private static final Map<String, String> userPasswords = new HashMap<>();
private static final Map<String, Integer> failedAttempts = new HashMap<>();
private static final long LOCKOUT_DURATION = 5 * 60 * 1000; // 5 minutes in milliseconds
private static final Map<String, MqttClient> clients = new HashMap<>();
static {
// 假设这里有一些预设的用户名和密码
userPasswords.put("user1", "password1");
userPasswords.put("user2", "password2");
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, MqttMessage msg) throws Exception {
if (msg instanceof MqttConnectMessage) {
handleConnect(ctx, (MqttConnectMessage) msg);
} else if (msg instanceof MqttPublishMessage) {
handlePublish(ctx, (MqttPublishMessage) msg);
} else if (msg instanceof MqttSubscribeMessage) {
handleSubscribe(ctx, (MqttSubscribeMessage) msg);
} else if (msg instanceof MqttUnsubscribeMessage) {
handleUnsubscribe(ctx, (MqttUnsubscribeMessage) msg);
} else if (msg instanceof MqttPingReqMessage) {
handlePingReq(ctx, (MqttPingReqMessage) msg);
} else if (msg instanceof MqttDisconnectMessage) {
handleDisconnect(ctx, (MqttDisconnectMessage) msg);
} else {
logger.warn("Unknown MQTT message type: {}", msg.getClass().getName());
}
}
private void handleConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) {
String username = msg.payload().userName();
String password = msg.payload().passwordInBytes()!= null? new String(msg.payload().passwordInBytes()) : null;
if (userPasswords.containsKey(username) && userPasswords.get(username).equals(password)) {
// 验证通过,记录客户端信息
MqttClient client = new MqttClient(username, msg.variableHeader().clientIdentifier(), null, ctx.channel().remoteAddress().toString());
clients.put(client.getClientId(), client);
MqttConnAckMessage connAck = MqttMessageBuilders.connAck()
.sessionPresent(false)
.returnCode(MqttConnectReturnCode.CONNECTION_ACCEPTED)
.build();
ctx.writeAndFlush(connAck);
} else {
// 验证失败,记录失败次数
failedAttempts.put(username, failedAttempts.getOrDefault(username, 0) + 1);
if (failedAttempts.get(username) >= 3) {
// 超过三次失败,锁定账号
failedAttempts.put(username, 0);
// 这里可以实现锁定账号的逻辑,例如记录锁定时间
// lockedAccounts.put(username, new Date().getTime() + LOCKOUT_DURATION);
}
MqttConnAckMessage connAck = MqttMessageBuilders.connAck()
.sessionPresent(false)
.returnCode(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD)
.build();
ctx.writeAndFlush(connAck);
ctx.close();
}
}
private void handlePublish(ChannelHandlerContext ctx, MqttPublishMessage msg) {
// 处理发布消息
// 这里可以根据需要实现消息的路由和分发逻辑
logger.info("Received PUBLISH message: {}", msg);
// 获取发布消息的主题
String topic = msg.variableHeader().topicName();
// 假设这里有一个方法可以获取所有已连接的客户端
List<MqttClient> allClients = getAllClients();
// 遍历所有客户端,检查是否有匹配的订阅
for (MqttClient client : allClients) {
List<String> subscriptions = client.getSubscriptions();
for (String subscription : subscriptions) {
// 进行简单的字符串匹配,如果需要更复杂的匹配逻辑,可以使用正则表达式或其他方法
if (subscription.equals(topic) || subscription.matches(topic.replaceAll("/+", "/.*"))) {
// 找到了匹配的订阅,将消息发送给该客户端
ctx.writeAndFlush(msg);
break; // 停止遍历当前客户端的订阅,因为已经找到了匹配
}
}
}
}
private void handleSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage msg) {
// 处理订阅请求
MqttSubAckMessage subAck = MqttMessageBuilders.subAck()
.messageId(msg.variableHeader().messageId())
.payload(MqttQoS.AT_LEAST_ONCE, MqttQoS.AT_LEAST_ONCE)
.build();
ctx.writeAndFlush(subAck);
// 记录客户端订阅的主题
String clientId = ctx.channel().id().asShortText();
MqttClient client = clients.get(clientId);
if (client!= null) {
List<String> topics = msg.payload().topics();
client.setSubscriptions(topics);
}
}
private void handleUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage msg) {
// 处理取消订阅请求
MqttUnsubAckMessage unsubAck = MqttMessageBuilders.unsubAck()
.messageId(msg.variableHeader().messageId())
.build();
ctx.writeAndFlush(unsubAck);
// 移除客户端订阅的主题
String clientId = ctx.channel().id().asShortText();
MqttClient client = clients.get(clientId);
if (client!= null) {
List<String> topics = msg.payload().topics();
client.getSubscriptions().removeAll(topics);
}
}
private void handlePingReq(ChannelHandlerContext ctx, MqttPingReqMessage msg) {
// 处理心跳请求
MqttPingRespMessage pingResp = MqttMessageBuilders.pingResp()
.build();
ctx.writeAndFlush(pingResp);
}
private void handleDisconnect(ChannelHandlerContext ctx, MqttDisconnectMessage msg) {
// 处理断开连接请求
String clientId = ctx.channel().id().asShortText();
MqttClient client = clients.remove(clientId);
if (client!= null) {
// 可以在这里记录客户端的断开连接时间等信息
client.setDisconnectTime(new Date());
}
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.error("Exception caught in MqttBrokerHandler", cause);
ctx.close();
}
}
代码是否可以直接运行?
尴尬的是代码很多地方都出错的,下面逐一列出代码出错的地方,并尝试使用豆包的代码修复功能,看是否可以修复。

豆包代码修复测试
当代码中存在错误时,窗口中的代码文件名称会高亮,名称右侧会提示错误数量。
选中有问题代码片段后,在对话框中输入自然语言描述(例如:修复选中的代码片段)或 /fix 指令,然后点击右侧的 发送 按钮或敲击键盘上的回车键。豆包 MarsCode 编程助手将为选中的代码片段进行智能修复。
MqttBrocker代码出错地方

原因分析:
从idea给出的提示MqttEncoder的无参构造方法是私有的(private)。
进入源码也可以看出MqttEncoder的无参构造方法是私有的,但是提供一个名为 INSTANCE 的静态常量,它是 MqttEncoder 类的一个实例(单例模式)。

使用豆包代码修复功能:
选中报错代码,并输入指令: /fix

但是我们知道的MqttEncoder是Netty源码提供的,是无法改成public的,显然豆包AI并没有给出正确的修复方法。那么我尝试引导豆包看能不能按照我们意图修正代码。
选中报错代码,并输入指令:/fix MqttEncoder的构造方法属于private的,是否可以更换其他方式实例化它呢

二次引导后,豆包输出的修复方案还是和第一次一模一样,显然它没有真正理解MqttEncoder源码,这里我选择自己手动修正。

MqttBrokerHandler报错代码

原因分析:
很严然MqttPingReqMessage、MqttDisconnectMessage这两个类是不存的。很有可能是豆包编程助手生造成来的。
使用豆包代码修复功能:

豆包只是给出修复错误的方向,但没有给出具体实现方式。目前豆包编程助手面对这种复杂场景并没有那么从容。这里还是选择自己修改。

豆包代码补全功能测试
豆包 MarsCode 编程助手可以帮助你自动补全后续代码。
场景一:在对应代码处,敲击回车键换行。豆包 MarsCode 编程助手会阅读并理解当前代码,然后自动补全后续代码。

场景二:在对应位置添加注释,然后敲击回车键换行。豆包 MarsCode 编程助手会阅读并理解代码注释,然后直接生成代码。

场景二通过注释补全代码这个功能对很多场景下具备很高的效率的。
豆包代码编辑功能测试
豆包 MarsCode 编程助手可以编辑指定代码,包括重构、优化、修改部分逻辑等。你可以通过自然语言描述代码编辑需求。
-
选中代码片段,然后在对话框中输入自然语言描述,例如 “优化选中的代码片段”。 -
点击 发送 按钮或敲击回车键。
优化handlePublish方法
场景一
优化handlePublish方法

场景二
代码中的subscription.matches(topic.replaceAll("/+", "/.*"))
正则表达式是无法满足MQTT的topic的。
MQTT的topic的通配符规则:MQTT 主题通配符包含单层通配符 + 及多层通配符 #,主要用于客户端一次订阅多个主题。【注意:通配符只能用于订阅,不能用于发布】

场景三
我们希望通过正则表达实现topic匹配。

豆包代码解释功能测试
选中代码片段后,你可以向豆包 MarsCode 编程助手发送代码解释指令。编程助手将解释选中的代码片段。发送指令方式如下:
-
在侧边对话视图的输入框中输入自然语言描述,例如:解释选中的代码片段; -
在侧边对话视图的输入框中输入 /explain 指令; -
选中代码片段后右键,然后在快捷菜单中选择 MarsCode > Explain Code; -
点击代码顶部(Visual Studio Code)或左侧 Logo 中(JetBrains)的 Explain 按钮。
场景一
用于解析业务代码,使我们快速了解业务,并解决问题。
解析一下 handleConnect(ChannelHandlerContext ctx, MqttConnectMessage msg)

场景二
解析复杂的源码,使我们对源码有个粗略了解,根据自己实质情况选择需要深入理解代码阅读。
er:SpringBoot的run方法解读

豆包代码注释生成功能测试
选中代码片段后,向豆包 MarsCode 编程助手发送代码注释生成指令。编程将为选中的代码片段生成代码注释。
发送指令的方式如下:
-
在侧边对话视图的输入框中输入自然语言描述,例如:为所选代码片段生成注释; -
在侧边对话视图的输入框中输入 /doc 指令; -
点击代码顶部(Visual Studio Code)或左侧 Logo 中(JetBrains)的 Doc 按钮。

锐评
使用交互锐评
-
图标交互还不错,但是只有三个指令选择

-
选择代码右键只出现三个指令,其它指令无法选择。

-
复制、插入、新建文件这几个图标交互我比较喜欢使用的

-
首次安装插件时候,语言的选择是auto时候默认是英文的,但是豆包是需要登录,应该具备根据用户地区选择语言,不然这个auto就失去意义。

代码生成
优点
简单逻辑代码生成是不错的,特别是不需要依赖其它库的情况。
场景类型的代码,根据你的自然语言描述生成对应的代码片段。但是需要你根据实质情况补充完整代码。
编写一些SQL语句,是非常好用的。
缺点
它会根据三方库的上下文,生造出一些不存的类。
无法真正理解三方库类的,给出一些类的不正确用法。
代码注释生成功
这个功能个人觉得是可以无脑用的
代码解释
优点
在前期粗略阅读源码时候,可以提供不错帮助。
英文不好的程序员,是一个两用的功能,翻译及代码逻辑介绍。
缺点
代码解释还是提留在代码本身解释,不具备结合框架整体及上下文去深入了解源码。特别是开源并且应用很广的框架(er:java中spring),豆包应该结合spring框架本身给出答案。
代码补全
场景二:在对应位置添加注释,然后敲击回车键换行。豆包 MarsCode 编程助手会阅读并理解代码注释,然后直接生成代码。这个场景下代码补全是非常好用的。
场景一:在对应代码处,敲击回车键换行。豆包 MarsCode 编程助手会阅读并理解当前代码,然后自动补全后续代码。这个场景我个人觉得比不上idea本身代码提示功能。
代码编辑
个人觉得这个功能还不错,在我们原来的代码上给出另外一种代码编写方式,我们可以选择需要或者不需要。
但是比如想把代码写得更优雅还是需要提升个人修为(设计模式、oop、DDD等知识),目前AI提供的能力有限。
单元测试生成
这次没有使用到,暂时不做评论。
其它
源码代码:
https://gitee.com/xmhzzz/emqx-practice/tree/master/mars-code-mqtt-brocker
豆包idea插件:
https://pan.quark.cn/s/ecccb0effcb4(30天有效,如果失效可以关注公众号【java程序猿技术】发送安装包获取,路径:软件/ai-code-copilot/字节豆包)
豆包官方地址:
https://www.marscode.cn/home
关注公众【java程序猿技术】获取更多有资料