// 錯誤:Spring MVC 當作表單參數綁定,JSON 解析失敗
@PostMapping("/login")
public Result login(User user) { ... }
// 正確:明確告知從請求體讀取 JSON
@PostMapping("/login")
public Result login(@RequestBody User user) { ... }
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
public class User {
private String username;
private String password;
public User() {} // 必須
// 必須有 Getter/Setter 或 public 欄位
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
// ...
}
// jQuery AJAX
$.ajax({
url: '/login',
type: 'POST',
contentType: 'application/json;charset=UTF-8', // 關鍵
data: JSON.stringify({username: 'admin', password: '123'}),
success: function(res) { ... }
});
// 或原生 fetch
fetch('/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username: 'admin', password: '123'})
});
// 錯誤:不能同時有 @RequestBody 和其他參數綁定註解
@PostMapping("/login")
public Result login(@RequestBody User user, @RequestParam String captcha) { ... }
// 正確:captcha 放入 User 物件,或用 @RequestHeader/@CookieValue
public class LoginForm {
private String username;
private String password;
private String captcha;
// getter/setter
}
@PostMapping("/login")
public Result login(@RequestBody LoginForm form) { ... }
<script>
function doLogin() {
var data = {
username: $('#username').val(),
password: $('#password').val()
};
$.ajax({
url: '${pageContext.request.contextPath}/api/login',
type: 'POST',
contentType: 'application/json;charset=UTF-8',
data: JSON.stringify(data),
dataType: 'json',
success: function(resp) {
if (resp.code === 200) location.href = '/index';
else alert(resp.msg);
}
});
}
</script>
@RestController
@RequestMapping("/api")
public class AuthController {
@Autowired UserService userService;
@PostMapping("/login")
public Result login(@RequestBody @Valid LoginDTO dto, BindingResult binding) {
if (binding.hasErrors()) {
return Result.error(binding.getFieldError().getDefaultMessage());
}
return userService.login(dto);
}
}
@Data // Lombok 自動生成 getter/setter/toString
public class LoginDTO {
@NotBlank(message = "用戶名不能為空")
private String username;
@NotBlank(message = "密碼不能為空")
@Size(min = 6, max = 20, message = "密碼長度 6-20 位")
private String password;
}
@Service
public class UserService {
@Autowired UserMapper userMapper;
public Result login(LoginDTO dto) {
User user = userMapper.findByUsername(dto.getUsername());
if (user == null) return Result.error("用戶不存在");
if (!passwordEncoder.matches(dto.getPassword(), user.getPassword()))
return Result.error("密碼錯誤");
return Result.success(user);
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
.timeZone(TimeZone.getTimeZone("GMT+8"))
.serializationInclusion(JsonInclude.Include.NON_NULL);
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
}
| 現象 | 原因 | 修正 |
|---|---|---|
| 所有欄位為 null | 缺少 @RequestBody | 加上 @RequestBody |
| 欄位值為 "username" 字串 | JSON 解析失敗,表單綁定將鍵名當值 | 檢查 Content-Type、Jackson 依賴、DTO 結構 |
| 415 Unsupported Media Type | Content-Type 不匹配 | 前端加 contentType: 'application/json' |
| 日期反序列化失敗 | 格式不匹配 | 配置 ObjectMapper 日期格式 |
遵循「前端 JSON.stringify + Content-Type: application/json → 後端 @RequestBody + Jackson + DTO」標準鏈路,並善用 `@Valid` 驗證、全域異常處理,即可構建健壯的 JSON API 介面。