1. Vibe Coding 编程困局
近期在高强度依赖 Cursor 编写项目代码的过程中,逐渐暴露出一些共性问题:
- Cursor 难以有效复用项目内已有能力,倾向于重复创建功能函数;
- 在持续迭代完善功能时,Cursor 容易写出重复逻辑,甚至破坏既有架构;
- 对整体项目结构缺乏全局把握,容易做出“局部最优但破坏整体”的改动;
- 在技术实现方案上,有时会采用与个人习惯明显不一致、比较“奇怪”的实现方式。
直觉上的补救方式,是在每次对话中尽可能详细地描述上下文、拆分改进计划。但这样既耗费时间,又消耗大量 token,体验并不好——与其如此,不如自己动手编码更高效。因此,更理想的方案是:通过一套通用、可复用的“规则系统”来约束 Cursor 的行为。
2. Cursor Rules:用规则“驯服” Cursor
目前最常用的 AI 工具是 Cursor。它支持通过 Cursor Rules 来约束模型的行为习惯。我们可以在项目根目录下创建 .cursor/rules/ 文件夹,按不同角色拆分规则文件,让模型自动遵守项目约定。
以 Spring Boot + MyBatis-Plus 项目为例,先看一下早期的 *.cursorrules 方案,然后再说明如何用 .mdc 文件做精细化拆分。
2.1 controller-class-conventions.mdc
controller-class-conventions.mdc:
---
description: Governs the structure and functionality of Controller classes for REST API endpoints.
globs: **/src/main/java/com/essay/controller/*.java
---
- All controller classes must be annotated with @RestController
- All controller classes must be annotated with @RequestMapping to define the base path
- Controller class names should follow the XxxController format
- Must use @RequiredArgsConstructor (Lombok) for dependency injection via final fields, avoiding @Autowired
- Must use @Slf4j (Lombok) for logging
- All request handler methods must return Result<T> wrapper for consistent API responses
- Must use proper HTTP method annotations: @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping
- Request body parameters must be validated using @Valid annotation
- Use @RequestBody for POST/PUT requests, @RequestParam for query parameters, @PathVariable for path parameters
- Never expose entity objects directly in API responses; always use DTO or VO classes
- Controllers should only handle request/response transformation and delegate business logic to Service layer
- Use UserContextHolder.getUserId() to get current authenticated user ID from JWT context
- Add meaningful log messages for important operations: log.info(), log.warn(), log.error()
- Use HttpServletRequest parameter when you need to access request headers, IP address, or URI
- Exception handling should be done by GlobalExceptionHandler; controllers should throw BusinessException for business errors
- API documentation comments should include: description, usage scenarios, prerequisites, parameters, and return values
- For pagination queries, use PageQueryDTO and return PageResultVO<T>
- File upload endpoints should validate file size, type, and name before processing
2.2 dto-conventions.mdc
dto-conventions.mdc:
---
description: Sets standards for Data Transfer Objects (DTOs) and Value Objects (VOs), typically records or @Data classes, including parameter validation.
globs:
- **/src/main/java/com/essay/dto/*.java
- **/src/main/java/com/essay/vo/*.java
---
- Must be of type record, unless specified in a prompt otherwise.
- Must specify a compact canonical constructor to validate input parameter data (not null, blank, etc., as appropriate).
2.3 mapper-class-conventions.mdc
mapper-class-conventions.mdc:
---
description: Governs the structure and functionality of Mapper classes.
globs: **/src/main/java/com/essay/mapper/*.java
---
- Mapper interfaces must be annotated with @Mapper
- Mapper interfaces must be declared as interfacetypes
- Unless otherwise specified, Mapper interfaces must extend com.baomidou.mybatisplus.core.mapper.BaseMapper<Entity, ID>
- Interface names should follow the XxxMapperformat, corresponding to the entity class name
- For complex multi-table JOIN queries, results must be mapped to a dedicated DTO (or VO) rather than a single entity class
- It is recommended to use the @Selectannotation with Java text blocks (JDK 15+) for writing complex multi-table query SQL, improving readability and maintainability
- When joined tables have columns with the same name, you must use ASin the SQL statement to assign a unique alias to each column to ensure accurate mapping to DTO properties
- For complex mappings where field names do not directly correspond to property names, or which involve nested objects, it is strongly recommended to use MyBatis's <resultMap>in XML files for precise mapping
- All list queries that might return large amounts of data must use MyBatis-Plus's Page<T>object for pagination, ensuring the pagination plugin (PaginationInnerInterceptor) is correctly configured
- Use the select()method of QueryWrapperor LambdaQueryWrapperto explicitly specify the columns to be queried, avoiding SELECT *to reduce network transmission and memory consumption
- Never execute database queries within loops to avoid N+1 query problems; for one-to-many data, use a JOIN query or batch processing methods provided by MyBatis-Plus at the Service layer
- To reduce code redundancy, common complex query conditions can be encapsulated into defaultmethods within the Mapper interface, adhering to the DRY principle
- For commonly used conditional construction logic that can be reused across multiple Mappers, extract it into static methods of a utility class
- Prefer using LambdaQueryWrapperover QueryWrapperfor type-safe query construction that avoids column name typos
- Ensure all mapper interfaces are properly scanned either by using @MapperScanon the application class or individual @Mapperannotations on each interface
2.4 spring-boot-configuration.mdc
spring-boot-configuration.mdc:
---
description: Governs application logic design in Spring Boot projects, defining the roles and responsibilities of RestControllers, Services, Repositories, and DTOs.
globs:
- **/src/main/java/com/essay/**/*
- **/src/main/java/com/essay/config/*.java
- **/src/main/java/com/essay/filter/*.java
- **/src/main/resources/application*.yml
---
- Framework: Java Spring Boot 3 Maven with Java 17/21 Dependencies: Spring Web, MyBatis-Plus, Lombok, MySQL driver
- All request and response handling must be done only in RestController.
- All database operation logic must be done in ServiceImpl classes, which must use methods provided by Mapper.
- RestControllers cannot autowire Mapper directly unless absolutely beneficial to do so.
- ServiceImpl classes cannot query the database directly and must use Mapper methods, unless absolutely necessary.
- Data carrying between RestControllers and ServiceImpl classes, and vice versa, must be done only using DTOs.
- Entity classes must be used only to carry data out of database query executions.
需要注意:最初 Cursor 使用的是单一的 *.cursorrules 文件,将所有项目规则集中在一起。这种方式在项目简单时尚可接受,但随着业务变复杂,单文件规则越来越难维护,也很难针对不同目录、不同角色精细控制行为。
因此,新版本推荐改用按文件拆分的 *.mdc 规则体系:
- 通过
globs精确匹配项目中的目标路径; - 每个
.mdc文件只约束一种角色(如 Controller / Mapper / DTO 等); - 让 Cursor 在补全代码时自然“对号入座”,减少架构跑偏的概率。
2.5 早期 .cursorrules 示例
下面这段是早期基于 *.cursorrules 思路整理的一份规则示例,可作为整体风格与粒度的参考:
AI Persona:
You are an experienced Senior Java Developer. You always adhere to SOLID principles, DRY principles, KISS principles, and YAGNI principles. You always follow OWASP best practices. You break down tasks into the smallest units and approach problem-solving in a step-by-step manner.
2. Technology Stack
Framework: Spring Boot 3 with Maven and Java 21
Dependencies: mybatis-plus-spring-boot3-starter (Version 3.5.x or higher), Spring Web, MyBatis-Plus, Lombok, MySQL Driver (or other database drivers as needed)
Java Version: 17 or higher
3. Application Logic Design
3.1 All request and response handling must be done only in the RestController.
3.2 All database operation logic must be implemented in ServiceImpl classes, which must use methods provided by Mapper interfaces.
3.3 RestController classes should not autowire Mapper interfaces directly unless there is a clear, significant benefit.
3.4 ServiceImpl classes must not contain raw SQL; they should use methods from Mapper interfaces or MyBatis-Plus's built-in conditions wrapper.
3.5 Data transfer between RestController and ServiceImpl classes must be done using Data Transfer Objects (DTOs).
3.6 Entity classes (annotated with @TableName) are primarily used to carry data from database query executions.
4. Entities (Replaces JPA @Entity)
4.1 Annotate entity classes with @TableName("table_name") to map the Java class to the database table.
4.2 Annotate entity classes with Lombok's @Data to generate getters, setters, and other boilerplate code.
4.3 Annotate the primary key field with @TableId. The most common strategy for auto-increment databases is type = IdType.AUTO.
4.4 For field mapping, if the database column name uses underscores and the Java field uses camelCase, MyBatis-Plus will automatically map them if map-underscore-to-camel-case is set to true in the configuration. Otherwise, use @TableField("column_name") for explicit mapping.
4.5 Use validation annotations (e.g., @Size, @NotEmpty, @Email) from the jakarta.validation package.
5. Mapper (Replaces JPA Repository)
5.1 Mapper interfaces must be annotated with @Mapper or the package must be scanned using @MapperScan("com.example.mapper") on the application class.
5.2 Mapper interfaces must extend BaseMapper<T>, where T is the entity class. This provides automatic CRUD methods like selectById, insert, updateById, deleteById, selectList.
5.3 For custom SQL queries, use MyBatis annotations (e.g., @Select, @Update) or XML mapping files. The @MapperScan annotation in the configuration or the mapper-locations property in application.yml should point to the XML files if used.
5.4 For complex multi-table join queries that return a custom result set, a DTO can be used as the return type with a custom method.
6. Service Layer
6.1 Create a Service interface.
6.2 All service interface method implementations must be in ServiceImpl classes that implement the service interface.
6.3 All ServiceImpl classes must be annotated with @Service.
6.4 Dependencies in ServiceImpl classes are typically injected via constructor injection or @Autowired.
6.5 Return objects of ServiceImpl methods should be DTOs, not entity classes, unless absolutely necessary.
6.6 For logic requiring existence checks, use the appropriate Mapper method with a conditional wrapper.
6.7 For multiple sequential database operations, use @Transactional (from jakarta.transaction) to ensure atomicity.
7. Data Transfer Object (DTO)
7.1 DTOs should be implemented as Java record types for immutability and simplicity, unless specified otherwise.
7.2 In the compact canonical constructor of the record, validate input parameters (e.g., check for not null, not blank).
8. RestController
8.1 Annotate controller classes with @RestController.
8.2 Specify class-level API routes with @RequestMapping, e.g., @RequestMapping("/api/user").
8.3 Use @GetMapping for fetch operations, @PostMapping for creation, @PutMapping for updates, and @DeleteMapping for deletion. Keep paths resource-based (e.g., /users/{id}), avoiding verbs like /create, /update, /delete, /get, or /edit.
8.4 Dependencies in the controller are typically injected via constructor injection or @Autowired.
8.5 Method return objects should be of type ResponseEntity<ApiResponse<T>>.
8.6 All class method logic should be implemented within a try...catch block.
8.7 Exceptions caught in catch blocks must be handled by a global exception handler.
9. Configuration
9.1 Dependency Compatibility
It is crucial to use the correct starter. For Spring Boot 3, you must use mybatis-plus-spring-boot3-starter version 3.5.x or higher to ensure compatibility with Jakarta EE and Java 17+.
9.2 Application Configuration (application.yml)
Example configuration:
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_name?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: username
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:/mapper//.xml
type-aliases-package: com.example.entity
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
9.3 MyBatis-Plus Interceptor Configuration
This configuration is essential for enabling plugins like pagination.
Example configuration class:
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
10. ApiResponse Class
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> {
private String result; // "SUCCESS" or "ERROR"
private String message; // Descriptive message
private T data; // The response payload object from the service layer, if successful
}
11. GlobalExceptionHandler Class
@RestControllerAdvice
public class GlobalExceptionHandler {
public static ResponseEntity<ApiResponse<?>> errorResponseEntity(String message, HttpStatus status) {
ApiResponse<?> response = new ApiResponse<>("ERROR", message, null);
return new ResponseEntity<>(response, status);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ApiResponse<?>> handleIllegalArgumentException(IllegalArgumentException ex) {
return errorResponseEntity(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
}
12. Key MyBatis-Plus Features to Utilize
12.1 Conditional Wrappers: Use QueryWrapper or, preferably, LambdaQueryWrapper to build dynamic queries in a type-safe manner.
12.2 Pagination: After configuring the PaginationInnerInterceptor, use the Page object for paginated queries.
12.3 Logical Deletion: Annotate a field with @TableLogic; MyBatis-Plus will automatically manage soft deletes.
12.4 Auto Fill: Implement the MetaObjectHandler interface to automatically populate fields like createTime and updateTime.
This guide is based on best practices and specifications derived from common development patterns and community knowledge.
3. VS Code + Copilot:规则的第二层
除了 Cursor 之外,还可以通过 VS Code + GitHub Copilot 搭建第二层规则体系,形成“多代理、多工具统一遵守同一套约定”的效果。
VS Code 中对自定义指令文件的官方说明如下(原文摘录,链接:https://code.visualstudio.com/docs/copilot/customization/custom-instructions#_type-of-instructions-files):
Type of instructions files
VS Code supports multiple types of Markdown-based instructions files. If you have multiple types of instructions files in your project, VS Code combines and adds them to the chat context, no specific order is guaranteed.
*A single .github/copilot-instructions.md file*
Automatically applies to all chat requests in the workspace
Stored within the workspace
*One or more .instructions.md files*
Conditionally apply instructions based on file type or location by using glob patterns
Stored in the workspace or user profile
*One or more AGENTS.md files*
Useful if you work with multiple AI agents in your workspace
Automatically applies to all chat requests in the workspace or to specific subfolders (experimental)
Stored in the root of the workspace or in subfolders (experimental)
Whitespace between instructions is ignored, so the instructions can be written as a single paragraph, each on a new line, or separated by blank lines for legibility.
To reference specific context in your instructions, such as files or URLs, you can use Markdown links.
3.1 .github/copilot-instructions.md
.github/copilot-instructions.md 是整个工作区层面的“总说明书”。建议使用下述提示词,让 Copilot 自动分析当前代码库,并在此文件中生成 / 更新项目级指令,再结合已有的 .cursor/rules/*.mdc 规则进行细化约束:
Analyze this codebase to generate or update .github/copilot-instructions.md for guiding AI coding agents.
Focus on discovering the essential knowledge that would help an AI agents be immediately productive in this codebase. Consider aspects like:
The "big picture" architecture that requires reading multiple files to understand - major components, service boundaries, data flows, and the "why" behind structural decisions
Critical developer workflows (builds, tests, debugging) especially commands that aren't obvious from file inspection alone
Project-specific conventions and patterns that differ from common practices
Integration points, external dependencies, and cross-component communication patterns
Source existing AI conventions from **/{.github/copilot-instructions.md,AGENT.md,AGENTS.md,CLAUDE.md,.cursorrules,.windsurfrules,.clinerules,.cursor/rules/**,.windsurf/rules/**,.clinerules/**,README.md} (do one glob search).
Guidelines (read more at https://aka.ms/vscode-instructions-docs):
If .github/copilot-instructions.md exists, merge intelligently - preserve valuable content while updating outdated sections
Write concise, actionable instructions (~20-50 lines) using markdown structure
Include specific examples from the codebase when describing patterns
Avoid generic advice ("write tests", "handle errors") - focus on THIS project's specific approaches
Document only discoverable patterns, not aspirational practices
Reference key files/directories that exemplify important patterns
Update .github/copilot-instructions.md for the user, then ask for feedback on any unclear or incomplete sections to iterate.
需要注意:这个文件会在多轮对话中被不断迭代和补充,相当于 Copilot 在本项目中的“长期记忆”。
建议每隔几轮对话,回顾并适度修订其中内容,删除已过时的约定,补充新的共识,避免误导后续会话。
3.2 .github/instructions/*.instructions.md
在项目根目录下创建 .github/instructions/ 文件夹,可以为不同技术栈或文件类型编写更细粒度的指令文件。
例如,针对 Spring Boot 项目创建专用说明文档(示例如下)。更多模板可参考 awesome-copilot 项目:https://github.com/github/awesome-copilot/。
---
description: 'Guidelines for building Spring Boot base applications'
applyTo: '**/*.java, **/*.kt'
---
# Spring Boot Development
## General Instructions
- Make only high confidence suggestions when reviewing code changes.
- Write code with good maintainability practices, including comments on why certain design decisions were made.
- Handle edge cases and write clear exception handling.
- For libraries or external dependencies, mention their usage and purpose in comments.
## Spring Boot Instructions
### Dependency Injection
- Use constructor injection for all required dependencies.
- Declare dependency fields as `private final`.
### Configuration
- Use YAML files (`application.yml`) for externalized configuration.
- Environment Profiles: Use Spring profiles for different environments (dev, test, prod)
- Configuration Properties: Use @ConfigurationProperties for type-safe configuration binding
- Secrets Management: Externalize secrets using environment variables or secret management systems
### Code Organization
- Package Structure: Organize by feature/domain rather than by layer
- Separation of Concerns: Keep controllers thin, services focused, and repositories simple
- Utility Classes: Make utility classes final with private constructors
### Service Layer
- Place business logic in `@Service`-annotated classes.
- Services should be stateless and testable.
- Inject repositories via the constructor.
- Service method signatures should use domain IDs or DTOs, not expose repository entities directly unless necessary.
### Logging
- Use SLF4J for all logging (`private static final Logger logger = LoggerFactory.getLogger(MyClass.class);`).
- Do not use concrete implementations (Logback, Log4j2) or `System.out.println()` directly.
- Use parameterized logging: `logger.info("User {} logged in", userId);`.
### Security & Input Handling
- Use parameterized queries | Always use Spring Data JPA or `NamedParameterJdbcTemplate` to prevent SQL injection.
- Validate request bodies and parameters using JSR-380 (`@NotNull`, `@Size`, etc.) annotations and `BindingResult`
## Build and Verification
- After adding or modifying code, verify the project continues to build successfully.
- If the project uses Maven, run `mvn clean package`.
- If the project uses Gradle, run `./gradlew build` (or `gradlew.bat build` on Windows).
- Ensure all tests pass as part of the build.
## Useful Commands
| Gradle Command | Maven Command | Description |
|:--------------------------|:----------------------------------|:----------------------------------------------|
| `./gradlew bootRun` |`./mvnw spring-boot:run` | Run the application. |
| `./gradlew build` |`./mvnw package` | Build the application. |
| `./gradlew test` |`./mvnw test` | Run tests. |
| `./gradlew bootJar` |`./mvnw spring-boot:repackage` | Package the application as a JAR. |
| `./gradlew bootBuildImage`|`./mvnw spring-boot:build-image` | Package the application as a container image. |
4. 最终实践:三层规则协同
当 .cursor/rules/*.mdc、.github/copilot-instructions.md 以及 .github/instructions/*.instructions.md 三层规则配置完成后,你会发现:
- 打开 Copilot Chat 发起新会话时,模型会自动引用这些自定义指令;
- Cursor 在补全代码时,也会自动遵守对应目录下的
.mdc约束; - 不同 AI 工具在同一项目中逐渐“说同一种话”,减少理解成本和架构破坏风险。
实践建议是:
- 在
.instructions.md中放置通用的编程与工程实践指令(跨项目可复用); - 在
.github/copilot-instructions.md中记录本仓库特有的架构决策、工作流与约定; - 在
.cursor/rules/下用.mdc精确约束不同层次与路径的代码形态。
工具选择上的推荐是:
- 如果主要使用 Cursor 编辑器,优先在项目根目录下维护
.cursor/rules/*.mdc规则文件,通过globs精细约束各个目录与角色; - 如果主要使用 VS Code + Copilot,则推荐组合使用
.github/copilot-instructions.md与.github/instructions/*.instructions.md:前者负责项目级“总说明书”,后者针对特定技术栈与文件类型补充细粒度约定。
通过这三层规则与不同工具的合理搭配,可以显著提升 Vibe Coding 体验,让 AI 更自然地融入你的编码风格与项目架构。