unit-test-exception-handler by giuseppe-trisciuoglio/developer-kit
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill unit-test-exception-handler本技能提供了使用 MockMvc 对 @ExceptionHandler 方法和 @ControllerAdvice 类进行单元测试的模式。它涵盖了测试异常到错误响应的转换、HTTP 状态码、错误消息格式化、验证错误处理以及自定义权限评估器,而无需完整的集成测试开销。
在以下情况下使用此技能:
@ControllerAdvice 中的 @ExceptionHandler 方法setControllerAdvice() 来注册异常处理器@ExceptionHandler 方法处理其特定的异常类型广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
@ResponseStatus 断言来验证正确的状态码<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.assertj:assertj-core")
}
// Global exception handler
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleResourceNotFound(ResourceNotFoundException ex) {
return new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
"Resource not found",
ex.getMessage()
);
}
@ExceptionHandler(ValidationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleValidationException(ValidationException ex) {
return new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
"Validation failed",
ex.getMessage()
);
}
}
// Error response DTO
public record ErrorResponse(
int status,
String error,
String message
) {}
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@ExtendWith(MockitoExtension.class)
class GlobalExceptionHandlerTest {
@InjectMocks
private GlobalExceptionHandler exceptionHandler;
private MockMvc mockMvc;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders
.standaloneSetup(new TestController())
.setControllerAdvice(exceptionHandler)
.build();
}
@Test
void shouldReturnNotFoundWhenResourceNotFoundException() throws Exception {
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.status").value(404))
.andExpect(jsonPath("$.error").value("Resource not found"))
.andExpect(jsonPath("$.message").value("User not found"));
}
@Test
void shouldReturnBadRequestWhenValidationException() throws Exception {
mockMvc.perform(post("/api/users")
.contentType("application/json")
.content("{\"name\":\"\"}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.status").value(400))
.andExpect(jsonPath("$.error").value("Validation failed"));
}
}
// Test controller that throws exceptions
@RestController
@RequestMapping("/api")
class TestController {
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
throw new ResourceNotFoundException("User not found");
}
}
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleResourceNotFound(ResourceNotFoundException ex) {
return new ErrorResponse(404, "Not found", ex.getMessage());
}
@ExceptionHandler(DuplicateResourceException.class)
@ResponseStatus(HttpStatus.CONFLICT)
public ErrorResponse handleDuplicateResource(DuplicateResourceException ex) {
return new ErrorResponse(409, "Conflict", ex.getMessage());
}
@ExceptionHandler(UnauthorizedException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ErrorResponse handleUnauthorized(UnauthorizedException ex) {
return new ErrorResponse(401, "Unauthorized", ex.getMessage());
}
@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public ErrorResponse handleAccessDenied(AccessDeniedException ex) {
return new ErrorResponse(403, "Forbidden", ex.getMessage());
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleGenericException(Exception ex) {
return new ErrorResponse(500, "Internal server error", "An unexpected error occurred");
}
}
class MultiExceptionHandlerTest {
private MockMvc mockMvc;
private GlobalExceptionHandler handler;
@BeforeEach
void setUp() {
handler = new GlobalExceptionHandler();
mockMvc = MockMvcBuilders
.standaloneSetup(new TestController())
.setControllerAdvice(handler)
.build();
}
@Test
void shouldReturn404ForNotFound() throws Exception {
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.status").value(404));
}
@Test
void shouldReturn409ForDuplicate() throws Exception {
mockMvc.perform(post("/api/users")
.contentType("application/json")
.content("{\"email\":\"existing@example.com\"}"))
.andExpect(status().isConflict())
.andExpect(jsonPath("$.status").value(409));
}
@Test
void shouldReturn401ForUnauthorized() throws Exception {
mockMvc.perform(get("/api/admin/dashboard"))
.andExpect(status().isUnauthorized())
.andExpect(jsonPath("$.status").value(401));
}
@Test
void shouldReturn403ForAccessDenied() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.status").value(403));
}
@Test
void shouldReturn500ForGenericException() throws Exception {
mockMvc.perform(get("/api/error"))
.andExpect(status().isInternalServerError())
.andExpect(jsonPath("$.status").value(500));
}
}
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BadRequestException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<ErrorDetails> handleBadRequest(BadRequestException ex) {
ErrorDetails details = new ErrorDetails(
System.currentTimeMillis(),
HttpStatus.BAD_REQUEST.value(),
"Bad Request",
ex.getMessage(),
new Date()
);
return new ResponseEntity<>(details, HttpStatus.BAD_REQUEST);
}
}
class ErrorResponseStructureTest {
private MockMvc mockMvc;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders
.standaloneSetup(new TestController())
.setControllerAdvice(new GlobalExceptionHandler())
.build();
}
@Test
void shouldIncludeTimestampInErrorResponse() throws Exception {
mockMvc.perform(post("/api/data")
.contentType("application/json")
.content("{}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.timestamp").exists())
.andExpect(jsonPath("$.status").value(400))
.andExpect(jsonPath("$.error").value("Bad Request"))
.andExpect(jsonPath("$.message").exists())
.andExpect(jsonPath("$.date").exists());
}
@Test
void shouldIncludeAllRequiredErrorFields() throws Exception {
MvcResult result = mockMvc.perform(get("/api/invalid"))
.andExpect(status().isBadRequest())
.andReturn();
String response = result.getResponse().getContentAsString();
assertThat(response).contains("timestamp");
assertThat(response).contains("status");
assertThat(response).contains("error");
assertThat(response).contains("message");
}
}
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ValidationErrorResponse handleValidationException(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return new ValidationErrorResponse(
HttpStatus.BAD_REQUEST.value(),
"Validation failed",
errors
);
}
}
class ValidationExceptionHandlerTest {
private MockMvc mockMvc;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders
.standaloneSetup(new UserController())
.setControllerAdvice(new GlobalExceptionHandler())
.build();
}
@Test
void shouldReturnValidationErrorsForInvalidInput() throws Exception {
mockMvc.perform(post("/api/users")
.contentType("application/json")
.content("{\"name\":\"\",\"age\":-5}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.status").value(400))
.andExpect(jsonPath("$.errors.name").exists())
.andExpect(jsonPath("$.errors.age").exists());
}
@Test
void shouldIncludeErrorMessageForEachField() throws Exception {
mockMvc.perform(post("/api/users")
.contentType("application/json")
.content("{\"name\":\"\",\"email\":\"invalid\"}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errors.name").value("must not be blank"))
.andExpect(jsonPath("$.errors.email").value("must be valid email"));
}
}
@ControllerAdvice
public class GlobalExceptionHandler {
private final MessageService messageService;
private final LoggingService loggingService;
public GlobalExceptionHandler(MessageService messageService, LoggingService loggingService) {
this.messageService = messageService;
this.loggingService = loggingService;
}
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleBusinessException(BusinessException ex, HttpServletRequest request) {
loggingService.logException(ex, request.getRequestURI());
String localizedMessage = messageService.getMessage(ex.getErrorCode());
return new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
"Business error",
localizedMessage
);
}
}
class ExceptionHandlerWithContextTest {
private MockMvc mockMvc;
private GlobalExceptionHandler handler;
private MessageService messageService;
private LoggingService loggingService;
@BeforeEach
void setUp() {
messageService = mock(MessageService.class);
loggingService = mock(LoggingService.class);
handler = new GlobalExceptionHandler(messageService, loggingService);
mockMvc = MockMvcBuilders
.standaloneSetup(new TestController())
.setControllerAdvice(handler)
.build();
}
@Test
void shouldLocalizeErrorMessage() throws Exception {
when(messageService.getMessage("USER_NOT_FOUND"))
.thenReturn("L'utilisateur n'a pas été trouvé");
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value("L'utilisateur n'a pas été trouvé"));
verify(messageService).getMessage("USER_NOT_FOUND");
}
@Test
void shouldLogExceptionOccurrence() throws Exception {
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isBadRequest());
verify(loggingService).logException(any(BusinessException.class), anyString());
}
}
@ControllerAdvice@ControllerAdvice 执行顺序:多个 @ControllerAdvice 处理器可以使用 @Order 注解排序@ResponseStatus 或返回 ResponseEntity,状态码默认为 200@ControllerAdvice 中的 @ExceptionHandler 是全局的;在控制器中是该控制器本地的异常处理器未被调用:确保控制器已注册到 MockMvc 并实际抛出异常。
JsonPath 匹配器不匹配:使用 .andDo(print()) 查看实际的响应结构。
状态码不匹配:验证处理器方法上的 @ResponseStatus 注解。
每周安装数
333
代码仓库
GitHub 星标数
173
首次出现
2026年2月3日
安全审计
安装于
claude-code265
gemini-cli251
opencode250
cursor247
codex245
github-copilot231
This skill provides patterns for unit testing @ExceptionHandler methods and @ControllerAdvice classes using MockMvc. It covers testing exception-to-error-response transformations, HTTP status codes, error message formatting, validation error handling, and custom permission evaluators without full integration test overhead.
Use this skill when:
@ExceptionHandler methods in @ControllerAdvicesetControllerAdvice() when building MockMvc to register exception handlers@ExceptionHandler method handles its specific exception type@ResponseStatus assertions to verify correct status codes<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.assertj:assertj-core")
}
// Global exception handler
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleResourceNotFound(ResourceNotFoundException ex) {
return new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
"Resource not found",
ex.getMessage()
);
}
@ExceptionHandler(ValidationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleValidationException(ValidationException ex) {
return new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
"Validation failed",
ex.getMessage()
);
}
}
// Error response DTO
public record ErrorResponse(
int status,
String error,
String message
) {}
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@ExtendWith(MockitoExtension.class)
class GlobalExceptionHandlerTest {
@InjectMocks
private GlobalExceptionHandler exceptionHandler;
private MockMvc mockMvc;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders
.standaloneSetup(new TestController())
.setControllerAdvice(exceptionHandler)
.build();
}
@Test
void shouldReturnNotFoundWhenResourceNotFoundException() throws Exception {
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.status").value(404))
.andExpect(jsonPath("$.error").value("Resource not found"))
.andExpect(jsonPath("$.message").value("User not found"));
}
@Test
void shouldReturnBadRequestWhenValidationException() throws Exception {
mockMvc.perform(post("/api/users")
.contentType("application/json")
.content("{\"name\":\"\"}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.status").value(400))
.andExpect(jsonPath("$.error").value("Validation failed"));
}
}
// Test controller that throws exceptions
@RestController
@RequestMapping("/api")
class TestController {
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
throw new ResourceNotFoundException("User not found");
}
}
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleResourceNotFound(ResourceNotFoundException ex) {
return new ErrorResponse(404, "Not found", ex.getMessage());
}
@ExceptionHandler(DuplicateResourceException.class)
@ResponseStatus(HttpStatus.CONFLICT)
public ErrorResponse handleDuplicateResource(DuplicateResourceException ex) {
return new ErrorResponse(409, "Conflict", ex.getMessage());
}
@ExceptionHandler(UnauthorizedException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ErrorResponse handleUnauthorized(UnauthorizedException ex) {
return new ErrorResponse(401, "Unauthorized", ex.getMessage());
}
@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public ErrorResponse handleAccessDenied(AccessDeniedException ex) {
return new ErrorResponse(403, "Forbidden", ex.getMessage());
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleGenericException(Exception ex) {
return new ErrorResponse(500, "Internal server error", "An unexpected error occurred");
}
}
class MultiExceptionHandlerTest {
private MockMvc mockMvc;
private GlobalExceptionHandler handler;
@BeforeEach
void setUp() {
handler = new GlobalExceptionHandler();
mockMvc = MockMvcBuilders
.standaloneSetup(new TestController())
.setControllerAdvice(handler)
.build();
}
@Test
void shouldReturn404ForNotFound() throws Exception {
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.status").value(404));
}
@Test
void shouldReturn409ForDuplicate() throws Exception {
mockMvc.perform(post("/api/users")
.contentType("application/json")
.content("{\"email\":\"existing@example.com\"}"))
.andExpect(status().isConflict())
.andExpect(jsonPath("$.status").value(409));
}
@Test
void shouldReturn401ForUnauthorized() throws Exception {
mockMvc.perform(get("/api/admin/dashboard"))
.andExpect(status().isUnauthorized())
.andExpect(jsonPath("$.status").value(401));
}
@Test
void shouldReturn403ForAccessDenied() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.status").value(403));
}
@Test
void shouldReturn500ForGenericException() throws Exception {
mockMvc.perform(get("/api/error"))
.andExpect(status().isInternalServerError())
.andExpect(jsonPath("$.status").value(500));
}
}
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BadRequestException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<ErrorDetails> handleBadRequest(BadRequestException ex) {
ErrorDetails details = new ErrorDetails(
System.currentTimeMillis(),
HttpStatus.BAD_REQUEST.value(),
"Bad Request",
ex.getMessage(),
new Date()
);
return new ResponseEntity<>(details, HttpStatus.BAD_REQUEST);
}
}
class ErrorResponseStructureTest {
private MockMvc mockMvc;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders
.standaloneSetup(new TestController())
.setControllerAdvice(new GlobalExceptionHandler())
.build();
}
@Test
void shouldIncludeTimestampInErrorResponse() throws Exception {
mockMvc.perform(post("/api/data")
.contentType("application/json")
.content("{}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.timestamp").exists())
.andExpect(jsonPath("$.status").value(400))
.andExpect(jsonPath("$.error").value("Bad Request"))
.andExpect(jsonPath("$.message").exists())
.andExpect(jsonPath("$.date").exists());
}
@Test
void shouldIncludeAllRequiredErrorFields() throws Exception {
MvcResult result = mockMvc.perform(get("/api/invalid"))
.andExpect(status().isBadRequest())
.andReturn();
String response = result.getResponse().getContentAsString();
assertThat(response).contains("timestamp");
assertThat(response).contains("status");
assertThat(response).contains("error");
assertThat(response).contains("message");
}
}
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ValidationErrorResponse handleValidationException(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return new ValidationErrorResponse(
HttpStatus.BAD_REQUEST.value(),
"Validation failed",
errors
);
}
}
class ValidationExceptionHandlerTest {
private MockMvc mockMvc;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders
.standaloneSetup(new UserController())
.setControllerAdvice(new GlobalExceptionHandler())
.build();
}
@Test
void shouldReturnValidationErrorsForInvalidInput() throws Exception {
mockMvc.perform(post("/api/users")
.contentType("application/json")
.content("{\"name\":\"\",\"age\":-5}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.status").value(400))
.andExpect(jsonPath("$.errors.name").exists())
.andExpect(jsonPath("$.errors.age").exists());
}
@Test
void shouldIncludeErrorMessageForEachField() throws Exception {
mockMvc.perform(post("/api/users")
.contentType("application/json")
.content("{\"name\":\"\",\"email\":\"invalid\"}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errors.name").value("must not be blank"))
.andExpect(jsonPath("$.errors.email").value("must be valid email"));
}
}
@ControllerAdvice
public class GlobalExceptionHandler {
private final MessageService messageService;
private final LoggingService loggingService;
public GlobalExceptionHandler(MessageService messageService, LoggingService loggingService) {
this.messageService = messageService;
this.loggingService = loggingService;
}
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleBusinessException(BusinessException ex, HttpServletRequest request) {
loggingService.logException(ex, request.getRequestURI());
String localizedMessage = messageService.getMessage(ex.getErrorCode());
return new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
"Business error",
localizedMessage
);
}
}
class ExceptionHandlerWithContextTest {
private MockMvc mockMvc;
private GlobalExceptionHandler handler;
private MessageService messageService;
private LoggingService loggingService;
@BeforeEach
void setUp() {
messageService = mock(MessageService.class);
loggingService = mock(LoggingService.class);
handler = new GlobalExceptionHandler(messageService, loggingService);
mockMvc = MockMvcBuilders
.standaloneSetup(new TestController())
.setControllerAdvice(handler)
.build();
}
@Test
void shouldLocalizeErrorMessage() throws Exception {
when(messageService.getMessage("USER_NOT_FOUND"))
.thenReturn("L'utilisateur n'a pas été trouvé");
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value("L'utilisateur n'a pas été trouvé"));
verify(messageService).getMessage("USER_NOT_FOUND");
}
@Test
void shouldLogExceptionOccurrence() throws Exception {
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isBadRequest());
verify(loggingService).logException(any(BusinessException.class), anyString());
}
}
@ControllerAdvice in MockMvc setup@ControllerAdvice execution order: Multiple @ControllerAdvice handlers can be ordered with @Order annotation@ResponseStatus or returning ResponseEntity, status defaults to 200@ExceptionHandler in @ControllerAdvice is global; in controller it's local to that controllerException handler not invoked : Ensure controller is registered with MockMvc and actually throws the exception.
JsonPath matchers not matching : Use .andDo(print()) to see actual response structure.
Status code mismatch : Verify @ResponseStatus annotation on handler method.
Weekly Installs
333
Repository
GitHub Stars
173
First Seen
Feb 3, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code265
gemini-cli251
opencode250
cursor247
codex245
github-copilot231
Spring Boot工程师技能指南:微服务架构、安全加固与云原生开发实战
2,800 周安装