閱讀561 返回首頁    go 京東網上商城


利用JSON-schema校驗請求報文,封裝轉換錯誤信息,提示前台

JSON-chema的語法就不講述了,可自行查閱相關文檔。

需求場景:前台請求接口的報文,為防止被非法攔截,需要後台再校驗一遍報文合法性,之前都是在java代碼中,用java代碼來判斷,查閱資料找到了json-schema-validate,一些基本簡單的校驗可以交給它來完成。

但是校驗的具體哪個字段的提示信息不能以中文提示返回給前台,所以自己寫了一個解析 校驗結果的方法,來將檢驗結果錯誤信息以中文形式提示給前台。不多說,上代碼



/***
 * 讀取定義的schema文件 
 */
@Slf4j
public class ReadJsonFromFileUtil {

    /***
     * 將文件中的json串解析成jsonNode
     * 現在未將結果緩存,會有效率問題
     * @param fileName 文件名稱
     * @return
     */
    public static JsonNode readJSONfile(String fileName) {
        //無法使用Cacheable 注解,JsonNode 未實現序列號接口。暫時用此方法
        Object cacheValue = SingletonUtil.getInstance().get(BusinessConstant.SINGLETON_KEY_JSONNODE + fileName);
        if (cacheValue != null) {
            return (JsonNode) cacheValue;
        }
        JsonNode instance = null;
        try {
            //            in = ReadJsonFromFileUtil.class.getClassLoader().getResourceAsStream("json/" + fileName + ".json");
            //            instance = new JsonNodeReader().fromInputStream(in);
            //
            instance = JsonLoader.fromResource("/json/" + fileName + ".json");
            SingletonUtil.getInstance().set(BusinessConstant.SINGLETON_KEY_JSONNODE + fileName, instance);
        } catch (Exception e) {
            log.error("[ReadJsonFromFileUtil] readJSONfile Exception!!! \n ", e);
        }
        return instance;
    }

}

我的jsonschema配置文件 放在了/src/main/resources/json目錄下



@Slf4j
public class BaseJsonValidateUtil {

    private final static JsonSchemaFactory factory = JsonSchemaFactory.byDefault();

    /***
     * 根據定義的json schema 校驗json格式
     * @param jsonFileName schema 文件名稱
     * @param params 傳遞的json 串
     */
    public static ProcessingReport ValidateJsonFormat(String jsonFileName, JSONObject params) {
        log.info("recevie validate schema is {}, \n request params  is \n {} ", jsonFileName, params);
        JsonNode schema = ReadJsonFromFileUtil.readJSONfile(jsonFileName);

        JsonNode data = convertJsonToNode(params);
        Preconditions.checkNotNull(schema, "未定義數據模板");
        Preconditions.checkNotNull(data, "缺少配置信息");

        ProcessingReport report = factory.getValidator().validateUnchecked(schema, data);

        //如果定義了empty_message 或者error_message 的話,將這些信息以異常的方式拋給前端
        convertMessage(report, schema);

        if (log.isDebugEnabled()) {
            log.debug("JsonSchema validate result {} \n", report);
        }

        Preconditions.checkArgument(report.isSuccess(), "請求數據格式非法");

        return report;
    }

    /***
     * 將json轉換未 JsonNode
     * @param paramsJson
     * @return
     */
    private static JsonNode convertJsonToNode(JSONObject paramsJson) {
        JsonNode actualObj = null;
        try {
            ObjectMapper mapper = new ObjectMapper();
            actualObj = mapper.readTree(paramsJson.toJSONString());
        } catch (IOException e) {
            log.error("convertJsonToNode Exception!!! \n {}", e);
            return actualObj;
        }
        return actualObj;

    }

    /***
     *根據 report裏麵的錯誤字段,找到schema對應字段定義的中文提示,顯示都前端
     * @param report 校驗json 的結果,裏麵包含錯誤字段,錯誤信息。
     * @param schema 原始的schema文件。主要用來讀取empty_message,error_message中文信息
     */
    private static void convertMessage(ProcessingReport report, JsonNode schema) {
        Iterator<ProcessingMessage> iter = report.iterator();
        ProcessingMessage processingMessage = null;
        //保存校驗失敗字段的信息
        JsonNode schemaErrorFieldJson = null;
        //原始校驗返回的信息
        JsonNode validateResult = null;
        while (iter.hasNext()) {
            processingMessage = iter.next();
            validateResult = processingMessage.asJson();
            //keyword表示 一定是不符合schema規範
            JsonNode keywordNode = validateResult.get("keyword");
            if (null != keywordNode) {
                //說明json validate 失敗

                schemaErrorFieldJson = findErrorField(schema, validateResult);
                //keyword 如果是require說明缺少必填字段,取schema中 字段對應的empty_message
                if ("required".equalsIgnoreCase(keyword)) {
                    //如果是require,找到哪個字段缺少了
                    JsonNode missingNode = null;
                    if (null == schemaErrorFieldJson) {
                        missingNode = validateResult.get("missing");
                        schemaErrorFieldJson = schema.get("properties").get(missingNode.get(0).textValue());
                    }

                    if (null != schemaErrorFieldJson.get("empty_message")) {
                        log.error("validate field 【requeied】[{}] fail.", JSON.toJSON(schemaErrorFieldJson));
                        Preconditions.checkArgument(false, schemaErrorFieldJson.get("empty_message").textValue());
                    }
                } else {
                    //非必填校驗失敗。說明是格式驗證失敗。取schema中 字段對應的error_message
                    if (null != schemaErrorFieldJson.get("error_message")) {
                        log.error("validate field 【validate】 [{}] fail.", JSON.toJSON(schemaErrorFieldJson));
                        Preconditions.checkArgument(false, schemaErrorFieldJson.get("error_message").textValue());
                    }

                }
            }
        }
    }

    /***
     * 根據校驗結果的 schema pointer 中的url遞歸尋找JsonNode
     * @param schema
     * @param validateResult
     * @return
     */
    private static JsonNode findErrorField(JsonNode schema, JsonNode validateResult) {
        //取到的數據是
        String[] split = validateResult.get("schema").get("pointer").textValue().split("/");
        JsonNode tempNode = null;
        if (!ArrayUtils.isEmpty(split)) {
            for (int i = 1; i < split.length; i++) {
                if (i == 1) {
                    tempNode = read(schema, validateResult, split[i]);
                } else {
                    tempNode = read(tempNode, validateResult, split[i]);
                }

            }
        }
        return tempNode;
    }

    private static JsonNode read(JsonNode jsonNode, JsonNode validateResult, String fieldName) {
        return jsonNode.get(fieldName);
    }
}


所以功能都在ValidateJsonFormat 方法中完成

第一步:讀取指定的schema文件

JsonNode schema = ReadJsonFromFileUtil.readJSONfile(jsonFileName);


第二步:將請求的json轉換成JsonNode

JsonNode data = convertJsonToNode(params);


第三步:調用json-schema校驗方法,校驗是否合法

ProcessingReport report = factory.getValidator().validateUnchecked(schema, data);


第四步:將校驗返回的結果解析,並將定義的empty_message,或者error_message 提示給前台

PS:這兩個字段是自定義字段。非json-schema標準語法字段。所以檢驗結果中,會提示語法錯誤,但不影響我們使用

empty_message: 表示沒有此字段,觸發了required校驗,會取此字段信息提示到前台

error_message: 表示不符合定義的校驗規則(enum,maxlength,minlength,pattern等等),會取此字段中的中文提示

如果這兩個字段為定義,則會觸發

Preconditions.checkArgument(report.isSuccess(), "請求數據格式非法");


schema樣例


{
  "type": "object",
  "description": "測試接口",
  "properties": {
    "productCode": { "type": "string", "minLength": 1 },
    "birthday": { "type": "string",  "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" ,"empty_message":"請選擇出生日期","error_message":"出生日期格式不正確"},
    "gender": { "type": "string", "enum": ["M", "F"],"empty_message":"請選擇性別","error_message":"性別輸入非法" },
    "period": { "type": "string", "enum": ["20", "30","999"],"empty_message":"請選擇期間","error_message":"期間輸入非法" },
    "buyCount": { "type": "number", "multipleOf": 1,"minimum": 5,"maximum":10,"exclusiveMinimum":false,"exclusiveMaximum": false ,"empty_message":"請選擇購買份數","error_message":"購買份數輸入非法"},
    "amount": { "type": "string","empty_message":"請輸入金額"}
  },
  "required": [
    "productCode",
    "birthday",
    "gender",
    "period",
    "buyCount",
    "amount"
  ]
}

請求json樣例

{

"productCode":"TEST",
"birthday":"2010-02-09",
"gender": "M",
"period": "20",
"buyCount":5,
"amount":"50000"

}

controller


 @RequestMapping(value = "/check", method = RequestMethod.POST)
    @ResponseBody
    public JSONObject check(@RequestBody JSONObject params) {
        JSONObject result = BusinessCommonResponseUtil.createSuc(null);
        try {
            ProcessingReport schemaValidateReport = BaseJsonValidateUtil.ValidateJsonFormat("schema_rial", params);
            log.info("response ={}", result);
            return result;
        } catch (IllegalArgumentException argException) {
            log.error("check IllegalArgumentException!!! \n ", argException);
            return BusinessCommonResponseUtil.createFail(argException.getMessage());
        } catch (Exception e) {
            log.error(check Exception!!! \n ", e);
            return BusinessCommonResponseUtil.createFail("係統異常");
        }
    }
controller返回的json報文格式根據業務而定



convertMessage方法說明: 此方法無法將 type: array類型的元素的錯誤信息以中文提示給前台。


@Slf4j 注解屬於是lombok注解,編譯器需要安裝此插件。其餘錯誤引入相關依賴即可

如果有什麼好的想法,歡迎交流。






最後更新:2017-09-14 13:32:59

  上一篇:go  使用systemd管理Yii2(或其他)隊列服務,實現故障重啟、開機自啟動等功能
  下一篇:go  開發手機直播App:如何在真旺雲配置七牛直播計費【完結篇】