opencode4-在已有项目中增加修改功能

0 阅读9分钟

本节目标

本节还是以开源项目RuoYi-Vue-Plus 为例,看看如何对已有项目添加和修改功能的一个完整流程。这个开源项目地址:gitee.com/dromara/Ruo…

  • 了解/new命令作用
  • 了解 /init 命令作用
  • 了解 AGENTS.md文件的重要性
  • 仅增加后端接口,不实现前端页面

先让RuoYi-Vue-Plus 跑起来

这个步骤,我们也利用 AI 帮我们做很多事情。比如:

  • 数据库的初始化工作
  • 配置修改工作

这里我选用的大模型是 claude sonnet 4.6 模型。为什么订阅 GitHub copilot ,就是因为它其中包含 Claude 这几个编程模型。非常好用!其中 sonnet 模型用于一般编程工作;而 opus 用于框架设计和复杂编程工作。不过这里是需要科学上网才能使用。这个要想用的话,需要自行解决。

数据库初始化

按照上节中提到的思路,让 opencode 通过官方文档进行学习。自动帮我们进行数据库初始化工作。

官方学习文档:plus-doc.dromara.org/#/_readme

  • 提示词

    帮我在远程的 mysql 数据库上创建本项目所需的数据库。
    官方学习文档:https://plus-doc.dromara.org/#/_readme
    我的 mysql 数据库的相关信息如下:
    mysql服务器 ip:your mysql server ip
    端口:3306
    用户名:root
    密码:your password
    根据官网文档介绍,帮我创建所需的库和表吧。这里特别说明下,我仅仅需要ruoyi-vue-plus的基础库表。
    
  • 执行结果

    image-20260408100112376

    补充说明:

    其实我开始用的是一个opencode免费的模型MiniMax M2.5 Free模型。结果这个模型,告诉我无法直接操作远程的mysql数据库。它找不到对应的mysql客户端工具。但是用claude的模型它就能通过安装Node.js的mysql2包来连接mysql数据库。虽然免费的模型最后也能给我所有执行的脚本和完整步骤,但是依然没有“全自动”创建库表。所以,一个好的模型是灵魂。

配置文件修改

由于还是在这个上下文中沟通,所以不用再特别强调官方学习文档地址了。

  • 提示词

    帮我修改dev环境的配置文件,仅仅保留最基础的功能,其他功能都先关闭。
    修改数据库连接为刚才创建的库连接。
    redis相关信息如下:
    host: your redis host ip
    port: 6379
    password: your redis password
    
  • 执行结果

    image-20260408104853993

在 idea 中跑起来

这个 5.X 版本最低也要 JDK17 版本,我的环境是 JDK21 的。完全满足。启动最后打印的内容如下:

image-20260408105719054

/new 命令

在 opencode 中,/new表示开始新的会话。这个命令会清空上下文记忆。也就是再次和模型对话时省 token 了。但是也失去了历史的上下文记忆了。特别适合在新功能开发;或者提交代码后的 bug 修复。总之是任务告一段落了。

image-20260408111054946

和在UI 中点击新建会话一样:

image-20260408111013457

我们这里用/new是表示,前期的准备告一段落了。在和大模型沟通的上下文中不用包含历史对话信息了。

/init 命令

这个命令会给项目做一个全面扫描,了解项目后生成一个 AGENTS.md文档。如果已经存在这个AGENTS.md文档,再次执行/init 命令时,该命令会尝试在其基础上进行补充。我们来执行这个命令,并且大概看下它生成的文档。记住哈,这一步很重要,新加功能和修改已有功能都基于此文档中定义的规则和约束进行的。

看下执行结果:

image-20260408133725474

看起来还不错,只不过都是英文的。我们可以让 opencode 转为中文。当然为了避免这种情况,我们可以在全局规则中增加简体中文的交互规则。

AGENTS.md 文件

这个AGENTS.md文件,你可以理解成大模型的参考手册。是用于定义、配置和规范 AI Agent(智能体)的行为准则与技术细节的文档。这个文件可以是在项目下的,也可以在用户目录下的。一般在用户目录下的AGENTS.md就是全局的规则文件了。

生成全局 AGENTS.md文件

这步我们可以在 opencode 中直接让它来帮我们创建出来。

  • 提示词

    在全局规则中增加始终使用简体中文与我交流
    
  • 执行结果

    image-20260408140847156

  • 全局规则文件内容

    C:\Users\mayuanfei.config\opencode\AGENTS.md

    # 全局规则
    ​
    始终使用简体中文与用户交流。
    
  • 可以增加更多自己的配置信息

    # 语言要求 
    * 始终使用简体中文与我交流。 
    * 思考过程(Thinking)和最终回答都必须使用中文。 
    * 即便我用英文提问,也请用中文回答。
    ​
    # JAVA环境
    * JDK21在/Users/mayuanfei/.sdkman/candidates/java/21.0.8-zulu目录。默认JDK21环境。
    * JDK8在/Users/mayuanfei/.sdkman/candidates/java/8.0.442-zulu。根据当前项目使用。
    ​
    # MAVEN环境
    * maven在/Users/mayuanfei/apache-maven-3.8.3
    * maven的默认配置文件在/Users/mayuanfei/apache-maven-3.8.3/conf/settings-jdk21.xml
    * 编译项目时加上-DskipTests,来排除测试类。
    ​
    # 编码整体要求
    * 一个方法不能超过40行
    * 如果是JDK21的项目,语法尽量使用JDK21新的语法。
    

    注意:这个全局规则文件尽量不要太长,因为它是所有项目都要遵循的规则。

项目AGENTS.md文件

尽量将全局配置的内容和项目配置的区分开。尽量不要配置冲突项。比如:全局配置一个方法不能超过40行代码;而你在项目的规则文件中定义一个方法不能超过20行。那么本地的约束>全局的配置。

  • 翻译为中文

    把当前项目的AGENTS.md文档都翻译为中文
    
  • 添加自己的规则

    这个可以按照自己平时项目中的规则,增加相应的约束。比如我这里在公有方法和私有方法之间增加个分割线。

    ## 公私有方法分割线
    在类的公有方法和私有方法 中间加上"//////////////////////////////////////公私有方法分割线//////////////////////////////////"。例如:
    ​
    ```java
    public String publicA() {
    return "";
    }
    public String publicB() {
    return "";
    }
    ​
    //////////////////////////////////////公私有方法分割线//////////////////////////////////private String privateA() {
    ​
    }
    private String privateB() {
    ​
    }
    ```
    但是如下情况不用加分割线标识:
    1. 接口类不用加
    2. 如果一个类中只有public方法,那么结尾不用加
    

增加新接口

业务功能

这里假设做一个商品类别的增删改查接口吧。商品类别的表名:t_goods_category。结构如下:

字段名数据类型约束备注
idbigintPK, 自增分类ID (雪花算法值)
parent_idbigintNot Null父分类ID (一级分类为 0)
namevarchar(64)Not Null分类名称
leveltinyintNot Null层级 (1-一级, 2-二级, 3-三级)
sort_orderintDefault 0排序值 (数值越小越靠前)
iconvarchar(255)-图标地址
is_visibletinyint(1)Default 1是否显示 (0-隐藏, 1-显示)
remarkvarchar(200)-备注说明
create_timedatetimeNot Null创建时间
update_timedatetime-更新时间
deleted_flagtinyint(1)Default 0逻辑删除 (0-正常, 1-已删除)

这个表就是在 md 文档中直接进行设计的。下面也会用这个设计内容直接创建表和测试数据。

创建表和测试数据

这步还是利用 opencode 帮我们来创建。

  • 执行/new 来创建一个新会话

  • 提示词

    结合当前项目 dev 环境配置的 mysql连接相关配置信息。做如下事情:
    1. 在库中创建t_goods_category表,表结构如下:
    | **字段名**       | **数据类型**   | **约束**  | **备注**                      |
    | ---------------- | -------------- | --------- | ----------------------------- |
    | **id**           | `bigint`       | PK, 自增  | 分类ID (雪花算法值)           |
    | **parent_id**    | `bigint`       | Not Null  | 父分类ID (一级分类为 0)       |
    | **name**         | `varchar(64)`  | Not Null  | 分类名称                      |
    | **level**        | `tinyint`      | Not Null  | 层级 (1-一级, 2-二级, 3-三级) |
    | **sort_order**   | `int`          | Default 0 | 排序值 (数值越小越靠前)       |
    | **icon**         | `varchar(255)` | -         | 图标地址                      |
    | **is_visible**   | `tinyint(1)`   | Default 1 | 是否显示 (0-隐藏, 1-显示)     |
    | **remark**       | `varchar(200)` | -         | 备注说明                      |
    | **create_time**  | `datetime`     | Not Null  | 创建时间                      |
    | **update_time**  | `datetime`     | -         | 更新时间                      |
    | **deleted_flag** | `tinyint(1)`   | Default 0 | 逻辑删除 (0-正常, 1-已删除)   |
    2. 在t_goods_category表中创建 10 条测试数据
    
  • 创建结果

    在 ry-vue 库中多了一个 t_goods_category 表,并且已经有 10 条测试记录了。

    image-20260408160137812

    image-20260408160106020

生成商品分类表的接口方法

  • 提示词

    结合当前项目的开发规范,在项目的合适包中,生成针对t_goods_category 表的增删改查接口。
    

    image-20260408164247016

  • 生成代码结果

    image-20260408164040468

测试生成的新代码

  • 让框架不验证权限

    我要通过reqable工具来请求/goods/category/listAll,帮我设置一下不校验相关的权限。
    

    image-20260408165356992

    这里看到它用@SaIgnore 注解来忽略权限校验了

  • 通过 reqable 测试接口

    image-20260408165510906

    说明:

    我是一边操作一边来写这篇文档的。这里能一次测试通过,也的确是没有想到。说明这个 opus 模型是真的厉害。

浏览下生成的代码

package org.dromara.system.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.system.domain.GoodsCategory;
import org.dromara.system.domain.bo.GoodsCategoryBo;
import org.dromara.system.domain.vo.GoodsCategoryVo;
import org.dromara.system.mapper.GoodsCategoryMapper;
import org.dromara.system.service.IGoodsCategoryService;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;

/**
 * 商品分类 服务层实现
 *
 * @author mayuanfei
 */
@RequiredArgsConstructor
@Service
public class GoodsCategoryServiceImpl implements IGoodsCategoryService {

    private final GoodsCategoryMapper baseMapper;

    /**
     * 分页查询商品分类列表
     *
     * @param bo        查询条件
     * @param pageQuery 分页参数
     * @return 商品分类分页列表
     */
    @Override
    public TableDataInfo<GoodsCategoryVo> selectPageCategoryList(GoodsCategoryBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<GoodsCategory> lqw = buildQueryWrapper(bo);
        Page<GoodsCategoryVo> page = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(page);
    }

    /**
     * 查询商品分类详情
     *
     * @param id 分类ID
     * @return 商品分类信息
     */
    @Override
    public GoodsCategoryVo selectCategoryById(Long id) {
        return baseMapper.selectVoById(id);
    }

    /**
     * 查询商品分类列表
     *
     * @param bo 查询条件
     * @return 商品分类集合
     */
    @Override
    public List<GoodsCategoryVo> selectCategoryList(GoodsCategoryBo bo) {
        LambdaQueryWrapper<GoodsCategory> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    /**
     * 新增商品分类
     *
     * @param bo 商品分类信息
     * @return 结果
     */
    @Override
    public int insertCategory(GoodsCategoryBo bo) {
        GoodsCategory category = MapstructUtils.convert(bo, GoodsCategory.class);
        return baseMapper.insert(category);
    }

    /**
     * 修改商品分类
     *
     * @param bo 商品分类信息
     * @return 结果
     */
    @Override
    public int updateCategory(GoodsCategoryBo bo) {
        GoodsCategory category = MapstructUtils.convert(bo, GoodsCategory.class);
        return baseMapper.updateById(category);
    }

    /**
     * 删除商品分类
     *
     * @param id 分类ID
     * @return 结果
     */
    @Override
    public int deleteCategoryById(Long id) {
        return baseMapper.deleteById(id);
    }

    /**
     * 批量删除商品分类
     *
     * @param ids 需要删除的分类ID
     * @return 结果
     */
    @Override
    public int deleteCategoryByIds(Long[] ids) {
        return baseMapper.deleteByIds(Arrays.asList(ids));
    }

    //////////////////////////////////////公私有方法分割线//////////////////////////////////

    /**
     * 构建查询条件
     *
     * @param bo 查询条件对象
     * @return LambdaQueryWrapper
     */
    private LambdaQueryWrapper<GoodsCategory> buildQueryWrapper(GoodsCategoryBo bo) {
        LambdaQueryWrapper<GoodsCategory> lqw = Wrappers.lambdaQuery();
        lqw.eq(bo.getParentId() != null, GoodsCategory::getParentId, bo.getParentId());
        lqw.like(StringUtils.isNotBlank(bo.getName()), GoodsCategory::getName, bo.getName());
        lqw.eq(bo.getLevel() != null, GoodsCategory::getLevel, bo.getLevel());
        lqw.eq(bo.getIsVisible() != null, GoodsCategory::getIsVisible, bo.getIsVisible());
        lqw.orderByAsc(GoodsCategory::getSortOrder);
        return lqw;
    }

}

说明:

1.生成的代码很符合咱们在 AGENTS.md 文件定义的公私有方法中有分割线。

2.但是很多生成的代码在 ruoyi-system 模块中了。理论上需要新增自己的业务模块。

本节总结

  • 我们用 RuoYi-Vue-Plus 做示例项目,用 opencode 让它跑起来。
  • 了解一个项目的最快方式就是让 AI 直接告诉你
  • /new 新开一个会话
  • /init 创建或更新 AGENTS.md 文件
  • 全局AGENTS.md是给所有项目的规约
  • 一个好用的大模型,是愉快 coding 的前提