This guide documents the coding conventions, patterns, and best practices used throughout the InterviewGuide codebase. Following these conventions ensures consistency and maintainability.
Java Coding Conventions
Lombok Annotations
The project heavily uses Lombok to reduce boilerplate code. The most common annotations are:
Generates a constructor with required arguments (final fields and fields marked with @NonNull). @ Service
@ RequiredArgsConstructor
public class ResumeUploadService {
private final ResumeParseService parseService ;
private final FileStorageService storageService ;
private final ResumePersistenceService persistenceService ;
// Constructor automatically generated by Lombok
// Enables constructor-based dependency injection
}
Benefits :
Eliminates constructor boilerplate
Works seamlessly with Spring’s constructor injection
Makes dependencies explicit and immutable
Automatically generates a logger field named log. @ Slf4j
@ RestController
@ RequiredArgsConstructor
public class ResumeController {
@ PostMapping ( "/api/resumes/upload" )
public Result < Map < String , Object >> uploadAndAnalyze (@ RequestParam ( "file" ) MultipartFile file ) {
log . info ( "收到简历上传请求: {}, 大小: {} bytes" , file . getOriginalFilename (), file . getSize ());
// ...
}
}
Equivalent to :private static final Logger log = LoggerFactory . getLogger ( ResumeController . class );
Automatically generates getter and setter methods. @ Getter
public class BusinessException extends RuntimeException {
private final Integer code ;
private final String message ;
// Lombok generates getCode() and getMessage()
}
Generates a constructor with all fields as parameters. @ Getter
@ AllArgsConstructor
public enum ErrorCode {
SUCCESS ( 200 , "成功" ),
RESUME_NOT_FOUND ( 2001 , "简历不存在" ),
RESUME_PARSE_FAILED ( 2002 , "简历解析失败" );
private final Integer code ;
private final String message ;
}
MapStruct for DTO Mapping
The project uses MapStruct for type-safe, compile-time object mapping between entities and DTOs.
Mapper Interface
Service Usage
@ Mapper ( componentModel = "spring" , unmappedTargetPolicy = ReportingPolicy . IGNORE )
public interface ResumeMapper {
/**
* Entity to DTO conversion with field mapping
*/
@ Mapping ( target = "filename" , source = "originalFilename" )
@ Mapping ( target = "analyses" , ignore = true )
@ Mapping ( target = "interviews" , ignore = true )
ResumeDetailDTO toDetailDTOBasic ( ResumeEntity entity );
/**
* DTO to Entity conversion with ignored fields
*/
@ Mapping ( target = "id" , ignore = true )
@ Mapping ( target = "resume" , ignore = true )
@ Mapping ( target = "analyzedAt" , ignore = true )
ResumeAnalysisEntity toAnalysisEntity ( ResumeAnalysisResponse response );
/**
* Custom mapping with null handling
*/
@ Named ( "nullToZero" )
default int nullToZero ( Integer value ) {
return value != null ? value : 0 ;
}
@ Mapping ( target = "contentScore" , source = "contentScore" , qualifiedByName = "nullToZero" )
ResumeAnalysisResponse . ScoreDetail toScoreDetail ( ResumeAnalysisEntity entity );
}
Key Conventions :
Use componentModel = "spring" to make mappers Spring beans
Use unmappedTargetPolicy = ReportingPolicy.IGNORE for flexible mapping
Explicitly mark ignored fields with @Mapping(target = "field", ignore = true)
Use @Named methods for custom conversion logic
Handle JSON fields manually in service layer (MapStruct doesn’t handle JSON serialization)
Spring Framework Conventions
Controllers
Services
Repositories
Use @RestController for REST API endpoints:@ Slf4j
@ RestController
@ RequiredArgsConstructor
public class ResumeController {
private final ResumeUploadService uploadService ;
private final ResumeHistoryService historyService ;
@ PostMapping ( value = "/api/resumes/upload" , consumes = MediaType . MULTIPART_FORM_DATA_VALUE )
@ RateLimit ( dimensions = { RateLimit . Dimension . GLOBAL , RateLimit . Dimension . IP }, count = 5 )
public Result < Map < String , Object >> uploadAndAnalyze (@ RequestParam ( "file" ) MultipartFile file ) {
Map < String , Object > result = uploadService . uploadAndAnalyze (file);
return Result . success (result);
}
@ GetMapping ( "/api/resumes" )
public Result < List < ResumeListItemDTO >> getAllResumes () {
List < ResumeListItemDTO > resumes = historyService . getAllResumes ();
return Result . success (resumes);
}
}
Conventions :
Use @RestController (combines @Controller + @ResponseBody)
Use @RequiredArgsConstructor for dependency injection
Use @Slf4j for logging
Wrap all responses in Result<T>
Use descriptive method names
Add rate limiting with @RateLimit where appropriate
Use @Service for business logic:@ Slf4j
@ Service
@ RequiredArgsConstructor
public class ResumeUploadService {
private final ResumeParseService parseService ;
private final FileStorageService storageService ;
private final ResumePersistenceService persistenceService ;
/**
* 上传并分析简历(异步)
*
* @param file 简历文件
* @return 上传结果(分析将异步进行)
*/
public Map < String , Object > uploadAndAnalyze ( MultipartFile file ) {
// 1. 验证文件
fileValidationService . validateFile (file, MAX_FILE_SIZE, "简历" );
// 2. 解析简历
String resumeText = parseService . parseResume (file);
// 3. 保存到存储
String fileKey = storageService . uploadResume (file);
// 4. 保存到数据库
ResumeEntity savedResume = persistenceService . saveResume (file, resumeText, fileKey);
// 5. 发送异步分析任务
analyzeStreamProducer . sendAnalyzeTask ( savedResume . getId (), resumeText);
return buildResponse (savedResume);
}
}
Conventions :
Use @Service annotation
Follow single responsibility principle (one service per concern)
Use descriptive method names that reflect business operations
Add comprehensive Javadoc comments
Keep methods focused and short
Delegate infrastructure concerns to infrastructure layer
Use Spring Data JPA for database access:@ Repository
public interface ResumeRepository extends JpaRepository < ResumeEntity , Long > {
/**
* Find resume by SHA-256 hash (for duplicate detection)
*/
Optional < ResumeEntity > findByFileHash ( String fileHash );
/**
* Find all resumes ordered by upload time descending
*/
List < ResumeEntity > findAllByOrderByUploadedAtDesc ();
/**
* Count resumes uploaded within a time range
*/
@ Query ( "SELECT COUNT(r) FROM ResumeEntity r WHERE r.uploadedAt BETWEEN :start AND :end" )
Long countByUploadedAtBetween (@ Param ( "start" ) LocalDateTime start , @ Param ( "end" ) LocalDateTime end );
}
Conventions :
Use @Repository annotation (optional with Spring Data JPA)
Extend JpaRepository<Entity, ID>
Use Spring Data JPA method naming conventions
Use @Query for complex queries
Return Optional<T> for single results that may not exist
Exception Handling
The project uses a centralized exception handling strategy with custom exceptions and error codes.
BusinessException
ErrorCode Enum
GlobalExceptionHandler
Usage in Service
@ Getter
public class BusinessException extends RuntimeException {
private final Integer code ;
private final String message ;
public BusinessException ( ErrorCode errorCode ) {
super ( errorCode . getMessage ());
this . code = errorCode . getCode ();
this . message = errorCode . getMessage ();
}
public BusinessException ( ErrorCode errorCode , String message ) {
super (message);
this . code = errorCode . getCode ();
this . message = message;
}
}
Error Code Convention :
1xxx: General errors (400, 401, 403, 404, 500)
2xxx: Resume module errors
3xxx: Interview module errors
4xxx: Storage module errors
5xxx: Export module errors
6xxx: Knowledge base module errors
7xxx: AI service errors
8xxx: Rate limiting errors
Result Wrapper Pattern
All API endpoints return a standardized response format using the Result<T> wrapper.
Result Class
Controller Usage
Success Response
Error Response
@ Getter
public class Result < T > {
private final Integer code ;
private final String message ;
private final T data ;
private Result ( Integer code , String message , T data ) {
this . code = code;
this . message = message;
this . data = data;
}
// ========== 成功响应 ==========
public static < T > Result < T > success () {
return new Result <>( 200 , "success" , null );
}
public static < T > Result < T > success ( T data ) {
return new Result <>( 200 , "success" , data);
}
public static < T > Result < T > success ( String message , T data ) {
return new Result <>( 200 , message, data);
}
// ========== 失败响应 ==========
public static < T > Result < T > error ( String message ) {
return new Result <>( 500 , message, null );
}
public static < T > Result < T > error ( Integer code , String message ) {
return new Result <>(code, message, null );
}
public static < T > Result < T > error ( ErrorCode errorCode ) {
return new Result <>( errorCode . getCode (), errorCode . getMessage (), null );
}
}
Naming Conventions
Package Naming
Use lowercase, single words when possible
Use descriptive names: modules.resume, infrastructure.export
Group by feature/module, not by layer (prefer modules.resume.service over service.resume)
Class Naming
Controllers : {Module}Controller (e.g., ResumeController, InterviewController)
Services : {Module}{Action}Service (e.g., ResumeUploadService, ResumeParseService)
Repositories : {Entity}Repository (e.g., ResumeRepository, InterviewSessionRepository)
Entities : {Domain}Entity (e.g., ResumeEntity, InterviewSessionEntity)
DTOs : {Domain}DTO or {Domain}{Purpose}DTO (e.g., ResumeDetailDTO, ResumeListItemDTO)
Mappers : {Module}Mapper (e.g., ResumeMapper, InterviewMapper)
Exceptions : {Description}Exception (e.g., BusinessException, RateLimitExceededException)
Method Naming
Use descriptive, action-oriented names
Controllers : uploadAndAnalyze(), getResumeDetail(), deleteResume()
Services : parseResume(), analyzeResume(), saveAnalysis()
Repositories : Follow Spring Data JPA conventions (findById(), findByFileHash(), deleteById())
Variable Naming
Use camelCase for variables and parameters
Use descriptive names: resumeText, fileKey, analysisResult
Avoid single-letter variables except in loops
Use final for immutable variables when using Lombok’s @RequiredArgsConstructor
Documentation Standards
Add Javadoc comments for:
All public classes
All public methods (especially in services)
Complex private methods
Enum values
/**
* 简历上传服务
* 处理简历上传、解析的业务逻辑
* AI 分析改为异步处理,通过 Redis Stream 实现
*/
@ Slf4j
@ Service
@ RequiredArgsConstructor
public class ResumeUploadService {
/**
* 上传并分析简历(异步)
*
* @param file 简历文件
* @return 上传结果(分析将异步进行)
*/
public Map < String , Object > uploadAndAnalyze ( MultipartFile file ) {
// ...
}
}
Use inline comments sparingly (code should be self-documenting)
Add comments for complex business logic
Explain why , not what
Use numbered steps for multi-step processes
public Map < String, Object > uploadAndAnalyze ( MultipartFile file) {
// 1. 验证文件
fileValidationService . validateFile (file, MAX_FILE_SIZE, "简历" );
// 2. 验证文件类型
String contentType = parseService . detectContentType (file);
validateContentType (contentType);
// 3. 检查简历是否已存在(去重)
Optional < ResumeEntity > existingResume = persistenceService . findExistingResume (file);
if ( existingResume . isPresent ()) {
return handleDuplicateResume ( existingResume . get ());
}
// 4-8. Process and return result...
}
Testing Conventions
Testing is currently minimal in the codebase. Future contributions should include comprehensive unit and integration tests.
Recommended practices :
Use JUnit 5 for unit tests
Use @SpringBootTest for integration tests
Use Mockito for mocking dependencies
Follow naming convention: {ClassName}Test (e.g., ResumeUploadServiceTest)
Use descriptive test method names: uploadAndAnalyze_WhenValidFile_ShouldReturnSuccess()
Frontend Conventions
TypeScript
Use strict type checking
Define interfaces for all API request/response types
Use TypeScript features (union types, generics, type guards)
// types/resume.ts
export interface ResumeListItem {
id : number ;
filename : string ;
fileSize : number ;
uploadedAt : string ;
latestScore : number | null ;
lastAnalyzedAt : string | null ;
interviewCount : number ;
}
export interface ResumeDetail extends ResumeListItem {
analyses : AnalysisHistory [];
interviews : InterviewSummary [];
}
React Components
Use functional components with hooks
Use TypeScript for props
Keep components small and focused
Extract reusable logic into custom hooks
interface ResumeCardProps {
resume : ResumeListItem ;
onDelete : ( id : number ) => void ;
onView : ( id : number ) => void ;
}
export const ResumeCard : React . FC < ResumeCardProps > = ({ resume , onDelete , onView }) => {
return (
< div className = "border rounded-lg p-4" >
< h3 className = "font-semibold" > { resume . filename } </ h3 >
< p className = "text-sm text-gray-600" > Score: { resume . latestScore ?? 'N/A' } </ p >
< div className = "mt-4 flex gap-2" >
< button onClick = { () => onView ( resume . id ) } > View </ button >
< button onClick = { () => onDelete ( resume . id ) } > Delete </ button >
</ div >
</ div >
);
};
API Client
Centralize API calls in src/api/ directory
Use axios for HTTP requests
Handle errors consistently
// api/resume.ts
import axios from 'axios' ;
import type { ResumeListItem , ResumeDetail } from '@/types/resume' ;
const API_BASE = import . meta . env . VITE_API_BASE_URL || 'http://localhost:8080' ;
export const resumeApi = {
uploadResume : async ( file : File ) => {
const formData = new FormData ();
formData . append ( 'file' , file );
const response = await axios . post ( ` ${ API_BASE } /api/resumes/upload` , formData );
return response . data ;
},
getResumeList : async () : Promise < ResumeListItem []> => {
const response = await axios . get ( ` ${ API_BASE } /api/resumes` );
return response . data . data ;
},
getResumeDetail : async ( id : number ) : Promise < ResumeDetail > => {
const response = await axios . get ( ` ${ API_BASE } /api/resumes/ ${ id } /detail` );
return response . data . data ;
},
};
Related Pages
Project Structure Understand the codebase organization
Building the Project Build and run commands
Backend Services Deep dive into service implementations
Redis Streams Learn about async processing patterns