ある記事では、SpringBootでカスタムバリデーターとエラーメッセージの国際化構成を実装する方法を説明しています。
コードエイプストーン
この記事では、例を使用して、Springbootでバリデーターをカスタマイズする方法と国際化されたエラーメッセージを返す方法を説明します。この記事のコードは直接コピーしないでください。大きな問題が発生する可能性があります。最初にサスペンスを残してください、読者と友人は何が悪いのか見ることができますか?
プロジェクトの初期化
springbootの公式Webサイトからテンプレートを直接ダウンロードし、例のGreetingControllerを介して実装ロジックを直接追加します。
@RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
@RequestMapping("/greeting")
public Response<Greeting> greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
if (!"tangleithu".equals(name)) {
throw new BadRequestException("user.notFound");
}
return Response.ok(new Greeting(counter.incrementAndGet(), String.format(template, name)));
}
}
上記のコードは、公式のスプリングガイドデモから直接派生したものです。少し変更します。通常の状況では、正しい結果が返されます。
# curl "localhost:8080/greeting?name=tangleithu&lang=en"
{
"code": 0,
"data": {
"content": "Hello, tangleithu!",
"id": 9
},
"message": "success"
}
国際需要
背の高いプロジェクトとして、間違いなく海外のユーザーがいるので、国際的な構成が必要です。次に、ビジネスロジックをシミュレートします。入力パラメータに上記のnameパラメータなどの検証関数があるとすると、「tangleithu」と等しくない場合、エラーが直接返されます。同時に、返すエラーメッセージは国際化する必要があります。つまり、返される結果は言語環境によって異なります。たとえば、中国語:「ユーザーは存在しません。」対応する英語:「ユーザーは存在しません。」、対応するドイツ語は...、忘れてください、私はしません。
写真を使用して、異なる国や地域(異なる言語)のユーザーが同じビジネスシナリオで同じエラーの原因に遭遇し、異なる翻訳を使用することが望ましい効果であることを表現します。たとえば、パラメータの検証が失敗した場合、Httpステータスコードは400を返し、エラーの理由を通知する必要があります。特定のサービス実装では、特定のエラーメッセージを返す必要がある場合があります。このように、それは統一された方法で簡単に管理することができます。
注:実際のビジネスシナリオでは、バックエンドはエラーコードのみを返す場合があり、特定の表示はキーに従ってフロントエンドによって変換されます。ただし、一部のより柔軟なシナリオ(一部のアプリ実装スキームなど)では、エラーメッセージがバックエンドインターフェイスから直接返される可能性があります。この記事では、単純なケースを使用してプロセス全体を説明します。
統一されたエラー処理
SpringでAOPを使用し、ControllerAdviceを使用してこのBadRequestExceptionを均一にインターセプトします。他の例外についても同じことが言えます。例外情報の統合処理はセキュリティリスクを引き起こすのは簡単ではありません(特定のSQLエラーを直接公開するバックグラウンドでの例外のため、以前に大規模なWebサイトに遭遇し、機密性の高いテーブルが多数あります)構造。情報)。例えば:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BadRequestException.class)
@ResponseBody
public ResponseEntity handle(HttpServletRequest request, BadRequestException e){
String i18message = getI18nMessage(e.getKey(), request);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Response.error(e.getCode(), i18message));
}
}
国際構成
特定のエラーメッセージの翻訳は、対応する言語のリソースファイルで直接構成できます。キーを使用して、この特定の例外情報のエラーコードをマークし、リソースファイルでさまざまな言語を使用して、返される特定のエラーメッセージを定義できます。たとえば、この記事の例では、中国語と英語が追加されています。対応するディレクトリ構造は次のとおりです。
現時点では、リクエストのソースが中国語か英語かに応じて、GlobalExceptionHandlerで対応するエラーメッセージを返すだけで済みます。
private String getI18nMessage(String key, HttpServletRequest request) {
try {
return messageSource.getMessage(key, null, LanguaggeUtils.currentLocale(request));
} catch (Exception e) {
// log
return key;
}
}
リクエストソースから言語情報を取得する方法はたくさんあります。たとえば、リクエストヘッダーからAccept-Lanuageを取得できます。通常、次の図に示すように、ブラウザはユーザーの設定に従ってこのリクエストヘッダーを取得します。
または、langなどのいくつかのパラメーターを明示的に定義することもできます。この記事では詳しく説明しません。次のように、langパラメーターを使用して定義してみましょう。
public class LanguaggeUtils {
public static Locale currentLocale(HttpServletRequest request) {
// 从 RequestHeader 等等获取相应的语言信息
// 简单起见,直接从 queryParams 中取, 只模拟中英文
String locale = request.getParameter("lang");
if ("zh".equalsIgnoreCase(locale)) {
return Locale.CHINA;
} else {
return Locale.ENGLISH;
}
}
}
このようにして、トールでの「国際化された」パラメータの戻りは、数行の簡単なコードで実現できます。次のように効果を試してください。
#curl "localhost:8080/greeting?name=tanglei&lang=en"
{
"code": 400,
"data": null,
"message": "User does not exist."
}
#curl "localhost:8080/greeting?name=tanglei&lang=zh"
{
"code": 400,
"data": null,
"message": "没找到用户呢。"
}
BeanValidator
実際、Formのようなパラメータ検証のためのより簡単な方法があります。これは、SpringBootに付属するValidationフレームワークの助けを借りて、この記事で使用されているこのバージョンの対応する実装はjakarta.validation-apiです。実際、Bean Validationには対応する標準があり、特定の実装が異なる場合があります。標準に興味のある方は、ここをクリックしてJSR#380 Bean Validation2.0をご覧ください。
この記事のデモに戻って、ビジネスロジックでユーザーフォームを渡し、age、name、paramの3つのパラメーターを受け取る必要があるとします。そして、入力を確認してください。その中で、問題を説明するためだけに、paramには特定の意味はありません。
public class UserForm {
@Min(value = 0, message = "validate.userform.age")
@Max(value = 120, message = "validate.userform.age")
private int age;
@NotNull(message = "validate.userform.name.notEmpty")
private String name;
@CustomParam(message = "validate.userform.param.custom")
private String param;
...
}
@RequestMapping("/user")
public Response<Greeting> createUser(@Valid @RequestBody UserForm userForm) {
return Response.ok(new Greeting(counter.incrementAndGet(), String.format(template, userForm.getName())));
}
コードは上記のとおりです。上記の例では、@ Min、@ Max、@ NotNullなどの非常に単純な制約のみを使用しています。意味は名前で確認できます。その他の制約ルールについては、対応するソースコードjavax.validation.constraints.xxxを直接確認できます。たとえば、一般的な電子メールやその他の形式の検証があります。
デフォルトでは、対応する制約に違反した後、デフォルトの出力は、このリクエストを使用するなど、より冗長になります。curl-H "Content-Type:application / json" -d "{}" "localhost:8080 / user"、対応する出力次のように:
{
"error": "Bad Request",
"errors": [
{
"arguments": [
{
"arguments": null,
"code": "name",
"codes": [
"userForm.name",
"name"
],
"defaultMessage": "name"
}
],
"bindingFailure": false,
"code": "NotBlank",
"codes": [
"NotBlank.userForm.name",
"NotBlank.name",
"NotBlank.java.lang.String",
"NotBlank"
],
"defaultMessage": "must not be blank",
"field": "name",
"objectName": "userForm",
"rejectedValue": null
}
],
"message": "Validation failed for object='userForm'. Error count: 1",
"path": "/user",
"status": 400,
"timestamp": "2020-05-10T08:44:12.952+0000"
}
ひょうたんに従ってひょうたんを描きましょう。デバッグ時に、前のGlobalExceptionHandlerにスローされた特定の例外を追加してから、デフォルトの動作を変更します。
@ExceptionHandler(BindException.class)
@ResponseBody
public ResponseEntity handle(HttpServletRequest request, BindException e){
String key = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
String i18message = getI18nMessage(key, request);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Response.error(400, i18message));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public ResponseEntity handle(HttpServletRequest request, MethodArgumentNotValidException e){
String key = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
String i18message = getI18nMessage(key, request);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Response.error(400, i18message));
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
public ResponseEntity handle(HttpServletRequest request, ConstraintViolationException e){
String key = e.getConstraintViolations().iterator().next().getMessage();
String i18message = getI18nMessage(key, request);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Response.error(400, i18message));
}
改善後、カスタムハンドラーを追加した後、返される情報構造は一貫性があり、統一されたフロントエンド処理を容易にします。また、非常に簡潔です。
{
"code": 400,
"data": null,
"message": "validate.userform.name.notEmpty"
}
前に説明したi18nによるパラメータ設定と組み合わせると、検証が失敗した場合、エラー情報は対応する国際化されたリソースファイルによって一律に設定されることがわかります。
カスタムバリデーター
組み込みの条件を満たせない場合は、上記のCustomParamなどのカスタムバリデーターを実装したいと考えています。どうやるか フォームに対応する際の参照検証を容易にするために注釈が必要です。具体的な実装は次のとおりです。
/**
* @author tanglei
* @date 2020/5/10
*/
@Documented
@Constraint(validatedBy = CustomValidator.class)
@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomParam {
String message() default "name.tanglei.www.validator.CustomArray.defaultMessage";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default { };
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
@interface List {
CustomParam[] value();
}
}
上記の@Constraint(validatedBy = CustomValidator.class)に関連付けられている特定のバリデーター実装クラスも必要です。この記事は単なるデモであるため、特定のパラメーターの検証には実際の論理的な意味はありません。以下では、入力パラメーターが「tanglei」と同じであり、検証に合格することを前提としています。そうでない場合、ユーザーはエラーを入力するよう求められます。
public class CustomValidator implements ConstraintValidator<CustomParam, String> {
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if (null == s || s.isEmpty()) {
return true;
}
if (s.equals("tanglei")) {
return true;
} else {
error(constraintValidatorContext, "Invalid params: " + s);
return false;
}
}
@Override
public void initialize(CustomParam constraintAnnotation) {
}
private static void error(ConstraintValidatorContext context, String message) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
}
}
効果を見てください。入力は検証に合格しませんでした。また、入力パラメーター「xx」が条件を満たしていないことを示すプロンプトが表示されます。
完璧に感じますか?
注:上には比較的隠れたセキュリティホールがありますので、注意してください。
注:上には比較的隠れたセキュリティホールがありますので、注意してください。
注:上には比較的隠れたセキュリティホールがありますので、注意してください。
重要なことは3回言われています。一般的に、この記事のアイデアは学ぶ価値がありますが(対応するコードについてはgithubを参照)、完全にコピーしないように注意する必要があります。上記のセキュリティの脆弱性は非常に深刻です。ヒントを教えてください。つまり、CustomValidatorの特定の実装で、友人はそれについて知っていますか?議論するメッセージを残すことを歓迎します。