跳到主要内容

开发规范

本文档定义了 Personal Blog Backend 项目的强制性编码标准和架构约束。

ArchUnit 守护

项目使用 ArchUnit 进行架构测试,违反规范的代码将无法通过 CI/CD。


🏗️ 模块结构

blog-modules/
├── blog-*-api/ # API 定义层(仅 DTO/VO/Interface)
└── blog-*-service/ # 服务实现层(Controller/Service/Entity)
模块允许禁止
*-apiDTO, VO, Interface, Enum❌ Entity, 业务逻辑
*-serviceController, Service, Entity, Mapper-
blog-application启动类, 全局配置❌ Controller, Service

依赖规则

graph LR
APP["blog-application"] --> SVC["*-service"]
SVC --> API["*-api"]
SVC --> COMMON["blog-common"]
API --> COMMON

SVC -.-x|禁止| SVC2["其他 *-service"]

💻 编码标准

Entity(数据库实体)

@Data
@TableName("sys_user")
public class User {
@TableId(type = IdType.ASSIGN_ID) // 雪花算法
private Long id;

@Version // 乐观锁
private Integer version;

@TableLogic // 逻辑删除
private Integer isDeleted;

@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}
禁止直接返回 Entity

API 响应必须使用 DTO/VO,通过 MapStruct 转换。


DTO(数据传输对象)

@Data
@Schema(description = "用户信息")
public class UserDTO implements Serializable, Identifiable<Long> {
private Long id;

@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20)
private String username;

@Email(message = "邮箱格式不正确")
private String email;
}

必须要求

  • ✅ 实现 SerializableIdentifiable<T>
  • ✅ 使用 @Schema 注解(API 文档)
  • ✅ 使用 JSR-303 验证注解

依赖注入

@Service
@RequiredArgsConstructor // ✅ 构造器注入
@Slf4j
public class UserServiceImpl {
private final UserMapper userMapper; // ✅ final 字段
private final UserConverter converter;

// ❌ 避免 @Autowired 字段注入
}

统一响应

@GetMapping("/{id}")
public Result<UserVO> getUser(@PathVariable Long id) {
return userService.getVoById(id)
.map(Result::success)
.orElseThrow(() -> new BusinessException(SystemErrorCode.NOT_FOUND));
}

所有 Controller 方法必须返回 Result<T>


异常处理

// ✅ 抛出业务异常,由 GlobalExceptionHandler 统一处理
if (user == null) {
throw new BusinessException(SystemErrorCode.USER_NOT_FOUND);
}

// ❌ 禁止在 Controller 中使用 try-catch

安全上下文

// ❌ 错误:用户可伪造
@PostMapping("/articles")
public Result<?> create(@RequestParam Long userId) { }

// ✅ 正确:从 Security Context 获取
@PostMapping("/articles")
public Result<?> create(@RequestBody ArticleDTO dto) {
Long userId = SecurityUtils.getCurrentUserId();
}

🗄️ 数据库变更

禁止手动修改 Schema,必须使用 Flyway:

  1. 创建脚本:blog-application/src/main/resources/db/
  2. 命名格式:V{version}__{description}.sql
-- V1.0.3__add_phone_column.sql
ALTER TABLE sys_user ADD COLUMN phone VARCHAR(20);
CREATE INDEX idx_phone ON sys_user(phone);

🔧 MapStruct 配置

@Mapper(
componentModel = "spring",
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE
)
public interface UserConverter extends BaseConverter<UserDTO, User, UserVO> {
// nullValuePropertyMappingStrategy = IGNORE 是关键配置
// 确保更新时不会用 null 覆盖已有值
}

🧪 测试规范

单元测试

@ExtendWith(MockitoExtension.class)
class UserServiceImplTest {
@Mock private UserMapper userMapper;
@InjectMocks private UserServiceImpl userService;

@Test
void should_returnUser_when_validId() {
// Given - When - Then 模式
}
}

集成测试

@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired private MockMvc mockMvc;
}

命名规范

  • 类名: {TargetClass}Test
  • 方法名: should_expectedBehavior_when_state()

覆盖率目标

层级目标
Service≥ 80%
Controller≥ 70%
关键路径100%

� 延伸阅读