
快速上手java项目:利用Baidu Comate,为java游戏代码优化
Comate是一款集成了百度强大文心一言技术的免费智能代码生成器。它不仅能够提供代码续写、代码补全、代码注释、代码解释、代码修复、单元测试、代码debug、注释生成代码、代码重构、修复建议、自然语言生成代码、代码问答以及长函数拆分等多项功能,而且能够显著提升开发者的编程效率,优化代码质量。
文章目录
1. 前言
近年来,随着人工智能技术的飞速发展,AIGC(人工智能生成内容)和大模型成为了科技圈和互联网行业的热门话题。它们以其强大的数据处理能力、高效的学习机制以及广泛的应用前景,赢得了广泛关注。我们可以在众多领域使用AI来帮助我们生成文本,寻找答案。但是对于编程代码领域,我们也同样在不断做尝试。
2023年8月,百度开放国内首个商用全场景智能编程助手 Comate X。同年10月份,百度Comate智能编程助手全面开放给大众使用。
Comate取自Coding Mate,寓意大家的AI编码伙伴。Comate融合了百度内部多年积累的编程现场大数据和外部开源代码和知识,可以帮助工程师在编写代码的时候实时推荐和生成代码。同时,Comate还结合了百度技术选型和编码规范,不仅可以帮助工程师更快的完成编码任务,代码的质量也更为出色。
今天带大家体验如何快速上手使用Baidu Comate智能代码助手
Baidu Comate智能代码助手安装链接:https://comate.baidu.com/zh/shopping
Comate是一款集成了百度强大文心一言技术的免费智能代码生成器。它不仅能够提供代码续写、代码补全、代码注释、代码解释、代码修复、单元测试、代码debug、注释生成代码、代码重构、修复建议、自然语言生成代码、代码问答以及长函数拆分等多项功能,而且能够显著提升开发者的编程效率,优化代码质量。
2.配置安装Comate
目前Baidu Comate智能代码助手支持 100+ 种主流的编程语言,同时也支持如 VS Code、IntelliJ IDEA、GoLand、PyCharm、WebStorm等主流编译器。兼容性很好
关于Baidu Comate智能代码助手的入门介绍,请看这篇文章,此次不在详细介绍入门
Baidu Comate智能代码助手入门介绍https://blog.csdn.net/qq_43475285/article/details/139013419
感兴趣的朋友可以登录https://comate.baidu.com/zh/download?index.html?track=csdn426获取Baidu Comate智能代码助手
3. Comate编程实战
在上述第二部分我们通过在vscode上使用Comate简单了解了其功能特性,下面我们通过一个java项目,了解如何在开发中使用Comate工具
测试工具:
操作系统版本:windows 11 专业版
IntelliJ IDEA 版本:2024.1.1
java版本:jre-8u411-windows-x64.exe
Java开发工具包:jdk1.8
Comate 版本:
项目代码:https://download.csdn.net/download/qq_43475285/19694306
3.1 环境配置
关于IDEA的使用安装,这里不做过多介绍,详情请了解IDEA及java官网
IDEA下载:https://www.jetbrains.com/idea/
java下载:https://www.java.com/zh-CN/
java安装配置教程:https://www.java.com/zh-CN/download/help/download_options.html
在配置完基本信息后,我们在IDEA中导入我们的java项目。
此java项目为一个单机版本坦克大战游戏,我们将通过使用Comate对项目代码进行优化和增加详细注释
运行,看一下效果,
加入我们作为一个小白,初次接触这个java项目,项目运行结果为一个坦克大战的单机游戏,但是今天游戏怎么玩并不是我们的关注重点,我需要快速上手了解代码的逻辑结构,以快速对其进行升级优化
我们手动浏览一下代码结构,编辑相对规范一些,但是注释相对较少,类引用逻辑有点混乱
对于新手来说很难快速看懂,那么怎样才能快速入门上手一个java项目呢
传统的方式如下
- 阅读代码结构:
- 了解项目的目录结构,重点关注主要的源代码文件、配置文件和资源文件。
- 阅读核心类和模块的代码,特别注意项目中的关键业务逻辑、框架使用方式以及重要的设计模式。
- 使用调试工具:学会使用IDE的调试功能,通过设置断点、步进执行等方法,深入理解代码的执行流程和数据流向。
- 查阅文档和注释:仔细阅读项目中的文档和代码注释。良好的注释能够大大加快你对项目理解的速度。
- 小步快跑:从小的功能或修复开始做起,逐步增加对项目的贡献。实践是学习最快的方式。
- 提问与交流:加入项目相关的沟通渠道(如Slack、钉钉群、邮件列表),不要害怕提问。团队成员的经验和解答往往能让你少走弯路。
- 持续学习:Java生态系统庞大,不断有新的框架和技术出现。保持好奇心,持续学习相关技术和最佳实践,对于快速适应新项目非常有帮助。
这次我们体验点不一样的,使用baidu Comate工具来快速帮助我们上手java项目
3.2 配置Comate工具
选择File–settings
然后选择Plugins,右侧搜索Baidu Comate,然后insatll
选择accepte

然后重启IDEA
重启之后,左下角提升我们需要登录
选择登录

然后返回IDEA,可以看到在左侧就可以使用我们的Comate工具了
3.3 使用Comate进行代码优化
3.3.1 代码注释优化
首先我需要Coamte帮我梳理一下代码逻辑,首先我们要选中需要分析的代码片段
例如我要了解Tree类的代码,并且需要帮我添加注释
我们可以对比一下前后的代码片段
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
//设置界面树和丛林
public class Tree {
public static final int width = 30;
public static final int length = 30;
int x, y;
TankClient tc ;
private static Toolkit tk = Toolkit.getDefaultToolkit();
private static Image[] treeImags = null;
static {
treeImags = new Image[]{
tk.getImage(CommonWall.class.getResource("Images/tree.gif")),
};
}
public Tree(int x, int y, TankClient tc) { //Tree的构造方法,传递x,y和tc对象
this.x = x;
this.y = y;
this.tc = tc;
}
public void draw(Graphics g) { //画出树
g.drawImage(treeImags[0],x, y, null);
}
}
Comate生成注释后的代码片段
// 导入必要的Java AWT库,用于图形界面的绘制和图像处理
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
// 定义一个名为Tree的公共类,用于表示游戏中的树和丛林元素
public class Tree {
// 定义树的宽度和长度常量,均为30像素
public static final int width = 30;
public static final int length = 30;
// 定义树的x和y坐标,表示树在游戏界面上的位置
int x, y;
// 定义一个TankClient类型的变量tc,用于与游戏客户端交互
TankClient tc;
// 获取系统默认的工具包,提供了一些基本的图像处理功能
private static Toolkit tk = Toolkit.getDefaultToolkit();
// 定义一个Image数组,用于存储树的图像资源
private static Image[] treeImags = null;
// 静态代码块,在类加载时执行,用于初始化treeImags数组
// 加载Images/tree.gif路径下的图像资源,并存储到treeImags数组的第一个位置
static {
treeImags = new Image[]{
tk.getImage(CommonWall.class.getResource("Images/tree.gif")),
};
}
// Tree类的构造方法,接收x,y坐标和TankClient对象作为参数
// 用于创建Tree对象,并初始化其位置和游戏客户端交互对象
public Tree(int x, int y, TankClient tc) {
this.x = x; // 初始化树的x坐标
this.y = y; // 初始化树的y坐标
this.tc = tc; // 初始化游戏客户端交互对象
}
// 定义一个公共的draw方法,接收Graphics对象作为参数
// 用于在游戏界面上绘制树
public void draw(Graphics g) {
// 使用Graphics对象的drawImage方法,将treeImags数组中的第一个图像(即树的图像)
// 绘制到游戏界面的指定位置(x, y)上
g.drawImage(treeImags[0], x, y, null);
}
}
良好的代码规范和注释标注对于一个大型代码项目来说十分的关键和必要,但是很多程序员并不喜欢写代码注释。通过Comate来生成代码注释可以极大的减轻程序员的工作压力,同时也能提高代码的易读性。
写代码注释的好处如下:
- 提高可读性:注释可以帮助其他开发者(或未来的你)更快地理解代码的功能和工作原理。良好的注释可以概述代码块、函数或类的目的,使得阅读代码就像阅读说明书一样直接明了。
- 促进团队协作:在团队开发环境中,注释是沟通的重要工具。它们帮助团队成员理解彼此的思路和实现方式,减少沟通成本,加快开发进度。
- 便于维护和调试:随着时间推移,项目会不断迭代和扩展,新加入的开发者或原作者在回顾旧代码时可能会感到困惑。注释能提供上下文信息,说明特定代码段为何如此编写,有助于快速定位问题和进行修改。
3.3.2 代码结构调优
除了生成注释外,还可以对我们编写的代码进行格式优化,来提高执行效率
在项目代码编辑栏中,对于我们创建的java类,comate提供函数注释,行间注释,生成单测,代码解释,调优建议等功能
函数注释
对于长函数,还可以进行自动拆分,并查看变更过程
public void draw(Graphics g) {
if (!live) {
if (!good) {
tc.tanks.remove(this); // 删除无效的
}
return;
}
if (good)
new DrawBloodbBar().draw(g); // 创造一个血包
switch (Kdirection) {
//根据方向选择坦克的图片
case D:
g.drawImage(tankImags[0], x, y, null);
break;
case U:
g.drawImage(tankImags[1], x, y, null);
break;
case L:
g.drawImage(tankImags[2], x, y, null);
break;
case R:
g.drawImage(tankImags[3], x, y, null);
break;
}
move(); //调用move函数
}
拆分后
/**
* 绘制坦克。
*
* @param g Graphics对象,用于在界面上进行绘制。
*/
public void draw(Graphics g) {
if (!live) {
handleDeadTank();
return;
}
if (good) {
createBloodPack(g);
}
drawTankImage(g);
move();
}
/**
* 处理死亡的坦克。
* 如果坦克无效,则从坦克列表中移除。
*/
private void handleDeadTank() {
if (!good) {
tc.tanks.remove(this); // 删除无效的
}
}
/**
* 创建一个血包。
*
* @param g Graphics对象,用于在界面上进行绘制。
*/
private void createBloodPack(Graphics g) {
new DrawBloodbBar().draw(g); // 创造一个血包
}
/**
* 根据坦克的方向绘制坦克的图片。
*
* @param g Graphics对象,用于在界面上进行绘制。
*/
private void drawTankImage(Graphics g) {
switch (Kdirection) {
case D:
g.drawImage(tankImags[0], x, y, null);
break;
case U:
g.drawImage(tankImags[1], x, y, null);
break;
case L:
g.drawImage(tankImags[2], x, y, null);
break;
case R:
g.drawImage(tankImags[3], x, y, null);
break;
}
}
如果我们认为生成的代码符合要求的话可以进行一键替换
运行一下生成的代码,看一下项目能否跑起来
3.3.3 自动生成测试案例
单元测试(Unit Testing)是软件开发过程中的重要一环,用于验证代码中的最小可测试单元是否按预期工作。
通过Comate可以自动生成测试样例
每个测试方法都遵循类似的结构:
- 准备测试数据:定义输入值和预期输出值。
- 调用被测试方法:使用输入值调用目标方法。
- 验证结果:使用JUnit的
assertEquals
方法比较实际结果与预期结果,确保它们相等。如果结果不符,则测试失败,并打印出错误信息。
生成的单测
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import java.awt.*;
public class TankTest {
private static Tank tank;
private static TankClient mockTankClient;
private static Graphics mockGraphics;
private static MockedStatic<Toolkit> mockToolkit;
private static final int TEST_X = 100;
private static final int TEST_Y = 100;
private static final boolean TEST_GOOD = true;
@BeforeAll
public static void setUpBeforeClass() throws Exception {
tank = new Tank(TEST_X, TEST_Y, TEST_GOOD);
mockTankClient = mock(TankClient.class);
mockGraphics = mock(Graphics.class);
mockToolkit = mockStatic(Toolkit.class);
mockToolkit.when(Toolkit::getDefaultToolkit).thenReturn(mockToolkit.mock());
}
@BeforeEach
public void setUp() {
tank.tc = mockTankClient;
tank.live = true;
}
@Test
public void testDraw_whenTankIsNotLive() {
tank.live = false;
tank.draw(mockGraphics);
verify(mockTankClient, never()).tanks();
verify(mockGraphics, never()).drawImage(any(), anyInt(), anyInt(), any());
}
@Test
public void testDraw_whenTankIsLiveAndGood() {
tank.draw(mockGraphics);
verify(mockTankClient, never()).tanks();
verify(mockGraphics, times(1)).drawImage(any(), anyInt(), anyInt(), any());
}
@Test
public void testDraw_whenTankIsLiveAndBad() {
tank.live = true;
tank.good = false;
tank.draw(mockGraphics);
verify(mockTankClient, times(1)).tanks();
verify(mockGraphics, never()).drawImage(any(), anyInt(), anyInt(), any());
}
@Test
public void testDraw_withDirectionDown() {
tank.direction = Direction.D;
tank.draw(mockGraphics);
verify(mockGraphics, times(1)).drawImage(any(), anyInt(), anyInt(), any());
}
@Test
public void testDraw_withDirectionUp() {
tank.direction = Direction.U;
tank.draw(mockGraphics);
verify(mockGraphics, times(1)).drawImage(any(), anyInt(), anyInt(), any());
}
@Test
public void testDraw_withDirectionLeft() {
tank.direction = Direction.L;
tank.draw(mockGraphics);
verify(mockGraphics, times(1)).drawImage(any(), anyInt(), anyInt(), any());
}
@Test
public void testDraw_withDirectionRight() {
tank.direction = Direction.R;
tank.draw(mockGraphics);
verify(mockGraphics, times(1)).drawImage(any(), anyInt(), anyInt(), any());
}
}
3.3.4 代码优化建议
对于编写的代码,comate还可以提供优化建议,对于大部分初级程序员来说,编写代码最主要考虑的是能够实现需求和功能,对于代码的编码规范,分离点,异常处理考虑还不够周到
comate可以自动生成优化建议,来提高代码的规范性,并自动生成建议代码
- 遵守编码规范:遵循Java编码规范,如使用驼峰命名法,保持代码风格一致。
- 分离关注点:随着项目复杂度增加,考虑将不同类型的计算(如加法、减法)拆分成不同的类或方法,遵循单一职责原则。
- 考虑异常处理:虽然当前示例简单,但在实际项目中应考虑输入验证及异常处理逻辑,比如对除法操作进行分母为零的检查。
针对这段代码,Comate做了如下优化
优化前
public void draw(Graphics g) {
if (!live) {
if (!good) {
tc.tanks.remove(this); // 删除无效的
}
return;
}
if (good)
new DrawBloodbBar().draw(g); // 创造一个血包
switch (Kdirection) {
//根据方向选择坦克的图片
case D:
g.drawImage(tankImags[0], x, y, null);
break;
case U:
g.drawImage(tankImags[1], x, y, null);
break;
case L:
g.drawImage(tankImags[2], x, y, null);
break;
case R:
g.drawImage(tankImags[3], x, y, null);
break;
}
move(); //调用move函数
}
优化后
代码分析:
空指针风险:在tc.tanks.remove(this);这行代码中,如果tc为null,则会抛出空指针异常。需要确保在调用draw方法之前tc已经被正确初始化。
代码风格:Kdirection的命名不符合Java的命名规范,通常变量名应使用小写字母开头,采用驼峰命名法。
逻辑拆分:draw方法中混合了绘制逻辑和移动逻辑(move()调用)。最好将这两个逻辑分开,以提高代码的可读性和可维护性。
资源管理:new DrawBloodbBar().draw(g);这行代码每次调用draw方法时都会创建一个新的DrawBloodbBar对象,这可能不是最高效的方式。如果DrawBloodbBar的创建成本较高,应考虑复用。
修复后的代码:
Java
复制
public void draw(Graphics g) {
if (!live) {
if (!good) {
if (tc != null) { // 添加空指针检查
tc.tanks.remove(this); // 删除无效的
}
}
return;
}
if (good) {
DrawBloodbBar bloodBar = new DrawBloodbBar(); // 提取为局部变量以复用
bloodBar.draw(g); // 创造一个血包
}
switch (direction) { // 修改变量名以符合命名规范
// 根据方向选择坦克的图片
case D:
g.drawImage(tankImags[0], x, y, null);
break;
case U:
g.drawImage(tankImags[1], x, y, null);
break;
case L:
g.drawImage(tankImags[2], x, y, null);
break;
case R:
g.drawImage(tankImags[3], x, y, null);
break;
}
}
// 假设move方法在同一个类中
public void move() {
// 移动逻辑
}
注意:我假设move()方法在同一个类中,并且DrawBloodbBar的实例可以在每次draw调用中重复创建(如果创建成本不高)。如果DrawBloodbBar的创建成本较高,应考虑将其提升为成员变量或使用其他复用策略。
优化点总结:
添加了空指针检查来增强代码的健壮性。
修改了变量名Kdirection为direction以符合Java命名规范。
将移动逻辑(move()调用)从draw方法中分离出来,尽管在这个修复示例中没有直接体现,但应在实际代码中进行此类拆分。
提取了DrawBloodbBar的实例为局部变量,以便在需要时进行进一步的优化,如对象复用。
3.3.5 帮你弄懂代码
在接手一个新java项目时,如果注释相对较少,我们可能不太容易明白一段代码的含义
通过Comate可以快速理解代码结构和功能,而且解释的十分通俗易懂,对于小白也很容易上手
代码举例
public void draw(Graphics g) {
if (!live) {
if (!good) {
tc.tanks.remove(this); // 删除无效的
}
return;
}
if (good)
new DrawBloodbBar().draw(g); // 创造一个血包
switch (Kdirection) {
//根据方向选择坦克的图片
case D:
g.drawImage(tankImags[0], x, y, null);
break;
case U:
g.drawImage(tankImags[1], x, y, null);
break;
case L:
g.drawImage(tankImags[2], x, y, null);
break;
case R:
g.drawImage(tankImags[3], x, y, null);
break;
}
move(); //调用move函数
}
这段代码是一个Java方法,用于绘制一个坦克对象。下面是对这段代码的详细解释:
方法签名:
Java
复制
public void draw(Graphics g)
这定义了一个公共方法draw,它接受一个Graphics对象作为参数。Graphics对象通常用于在Java的AWT或Swing中进行绘图。
检查坦克是否存活:
Java
复制
if (!live) {
if (!good) {
tc.tanks.remove(this); // 删除无效的
}
return;
}
首先,代码检查坦克是否live(即是否存活)。如果坦克不存活(!live为真),那么它进一步检查坦克是否good。如果坦克既不存活又不好(!good为真),那么它会从tc.tanks集合中移除自己。然后,方法返回,不再执行后续的绘图操作。
绘制血包:
Java
复制
if (good)
new DrawBloodbBar().draw(g); // 创造一个血包
如果坦克状态为good(可能是表示坦克的血量或状态良好),则创建一个新的DrawBloodbBar对象并调用其draw方法来绘制一个血包。这里可能有一个拼写错误,应该是DrawBloodBar而不是DrawBloodbBar,但这取决于实际的类名。
根据方向选择坦克的图片并绘制:
Java
复制
switch (Kdirection) {
// 根据方向选择坦克的图片
case D:
g.drawImage(tankImags[0], x, y, null);
break;
case U:
g.drawImage(tankImags[1], x, y, null);
break;
case L:
g.drawImage(tankImags[2], x, y, null);
break;
case R:
g.drawImage(tankImags[3], x, y, null);
break;
}
使用switch语句根据Kdirection的值(可能是一个枚举或常量,表示坦克的方向)来选择并绘制相应的坦克图片。tankImags可能是一个存储坦克图片的数组。每个case对应一个方向(D表示向下,U表示向上,L表示向左,R表示向右),并调用Graphics对象的drawImage方法来绘制对应的图片。
调用move方法:
Java
复制
move(); //调用move函数
最后,调用move方法。这个方法可能是用来更新坦克的位置或其他与移动相关的状态。
注意:
这段代码依赖于一些外部变量和类,如live、good、tc、tankImags、Kdirection、x、y以及move方法和DrawBloodbBar类。这些都没有在代码片段中给出,所以具体的实现和含义可能需要根据整个项目的上下文来理解。
代码中可能存在一些拼写错误或不一致的命名,如DrawBloodbBar可能是一个拼写错误。
tankImags数组应该已经被初始化并填充了相应的图片资源。
g.drawImage方法的第四个参数是ImageObserver,这里传递了null,这意味着图像绘制的完成不需要任何通知。在某些情况下,可能需要提供一个有效的ImageObserver来处理图像的异步加载。
3.3.6 帮你写代码
前几个功能都是在辅助我们优化或者更改已经编辑的代码,那么Comate在生成代码方面的能力如何?
观察我们的项目架构
基本上就是安装不同的功能分为不同的java类和函数,进行调用。现在我想要增加新的功能。例如游戏时间计算时间功能。当我结束一段游戏可以显示游戏时长。看一下生成的代码能否满足要求
发现Comate并不能在IDEA中直接为我们编写新文件,只能提供样例,最后的编写还需要我们手动完成
在Java项目中,你可以通过记录项目启动时的时间戳,并在需要时计算与当前时间的差值,来得到项目的运行时间。以下是一个简单的示例,展示如何在Java代码中增加计算项目启动时间的功能:
在项目的主类(或启动类)中,添加一个静态变量来保存启动时间戳。
在项目的入口方法(如main方法)中,初始化这个静态变量为当前时间戳。
提供一个静态方法,用于计算并返回项目已运行的时间。
下面是一个具体的实现:
Java
复制
public class MyApp {
// 静态变量,用于保存项目启动时间戳
private static long startTime;
public static void main(String[] args) {
// 初始化启动时间戳
startTime = System.currentTimeMillis();
// 你的项目代码...
// 在需要时,调用getRunningTime方法来获取项目已运行时间
long runningTime = getRunningTime();
System.out.println("项目已运行时间(毫秒): " + runningTime);
}
/**
* 计算并返回项目已运行的时间(单位:毫秒)
*/
public static long getRunningTime() {
long currentTime = System.currentTimeMillis();
return currentTime - startTime;
}
}
在这个示例中,startTime是一个静态变量,用于保存项目的启动时间戳。在main方法中,我们初始化startTime为当前时间戳。然后,在项目的任何位置,你都可以通过调用getRunningTime方法来获取项目已运行的时间(以毫秒为单位)。这个方法通过计算当前时间戳与启动时间戳的差值来得到已运行时间。
最后测试,使用baidu Comate助手优化完项目代码后,看一下能否正常执行
4. 使用感受
4.1 整体感受
整体上再体验玩Baidu Comate智能代码助手后,上手很简单,功能各方面都很完善。对于在开发过程中编写代码或者生成测试案例都有很大的帮助。Comate把代码理解、生成、优化等能力无缝集成到研发流程的各个环节,就像助理一样,帮助提升代码开发质量和效率。对开发者的代码编写效率有十分明显的提升。
尤其是可以针对我们的特定文件,特定网页等信息来准确的生成更贴切实际的答案。
但是也有一方面,我在插件中看到Comate可能需要收费,但是点击续费页面并没有显示收费相关信息,希望官方可以对是否收费进行明确

4.2 改进建议
- 提高代码生成效率
在使用Comate拆分函数时,尝试三次才执行成功,失败概率较大。且有时生成的代码和源代码没有发现区别
- 针对不同代码场景进行定制化优化
在使用Comate生成单测时,的确成功生成了,但单测并不能直接替换源代码,造成报错。并不是所有生成的代码都可以采纳就会替换源代码
- 代码风格统一性
在演示过程中我使用的代码都为java,但是Comate却生成了python代码,与要求不符
- 分析Token长度较短
在我想要分析一长段代码时,Comate报错,显示支持5120Token以下,希望Comate后续可以支持更长Token的分析能力
4.3 使用Comate会造成程序员失业吗
使用代码生成工具并不直接导致程序员失业,反而在多数情况下是提高了程序员的工作效率和项目的开发速度。这类工具可以帮助程序员自动生成标准化的代码片段、处理重复性高的任务、构建基础架构代码等,使得开发者能够更加专注于解决业务逻辑中的复杂问题、创新和优化系统设计。
所以我个人认为合理的使用Baidu Comate智能代码助手并不会造成失业,反而会提高我们的开发效率,不用重复造轮子浪费时间,把更多的精力用于提升代码结构和完整性。提高开发效率。
感兴趣的朋友可以登录https://comate.baidu.com/zh/download?index.html?track=csdn426获取Baidu Comate智能代码助手
2024年5月16日-30日,目前应该还可以领取京东卡等礼品,速度前来~
更多推荐
所有评论(0)