码迷,mamicode.com
首页 > 编程语言 > 详细

java 入参校验

时间:2020-06-05 15:26:45      阅读:58      评论:0      收藏:0      [点我收藏+]

标签:getc   setter   policy   tail   enc   seq   product   context   12px   

项目中经常会涉及到入参校验,下面举个示例做法:

1、入参示例(JSON类型):

{
"sequenceNo":"11111",
"timestamp":"2020-06-05 13:53:02",
"channelId":"1003",
"requestData":{"productCode": "1002","supplierId":"002233"}
}

2、入参接收Vo:

import com.ocft.gamma.spds.trade.common.dto.BaseDto;
import lombok.Getter;
import lombok.Setter;

import javax.validation.constraints.NotNull;

@Getter
@Setter
public class ProductDetailsVo extends BaseDto {

    @NotNull(message ="产品代码不能为空")
    private String productCode;

    private String supplierId;
}

3、controller中的方法:

@RequestMapping(value = "/getProductDetails.do", method = RequestMethod.POST)
    public BaseBodyResponse getProductDetails(@RequestBody BaseBodyRequest request) {
        ProductDetailsVo productDetailsVo = DealValidationJSRUtil.jsonParseAndJSR(request.getRequestData(),ProductDetailsVo.class);
        ProductDetailsDto productDetailsDto = productServiceImpl.getProductDetails(productDetailsVo);
        BaseBodyResponse baseBodyResponse = BaseBodyResponse.success(productDetailsDto);
        return baseBodyResponse;
    }

 

4、校验工具类DealValidationJSRUtil:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.demo.common.enums.MessageEnum;
import com.demo.common.exception.JSRValidationException;
import java.text.MessageFormat;
import java.util.Map;


public class DealValidationJSRUtil {

    private static final int msgLength = 250; // 报文标准长度

    private static final int msgLengthMax = 300; // 最大报文长度

    public static <T> T jsonParseAndJSR(String jsonStr, Class<T> clazz) throws JSONException, JSRValidationException {
        // 1、转换为JSON对象
        T obj = JSON.parseObject(jsonStr, clazz);
        // 2、JSR参数验证
        String code = MessageEnum.ParamsUnValidDetailsAndExplains.getCode(); // 错误码
        DealValidationJSRUtil.validJsrByObj(obj, code);
        return obj;
    }

   
    public static <T> T jsonParseAndJSR(T obj) throws JSONException,
            JSRValidationException {

        // 1、JSR参数验证
        String code = MessageEnum.ParamsUnValidDetailsAndExplains.getCode(); // 错误码
        DealValidationJSRUtil.validJsrByObj(obj, code);

        return obj;
    }

    
    public static <T> T jsonParseAndJsrByTemplate(String jsonStr,
            Class<T> clazz, String templateCode) throws JSONException,
            JSRValidationException {

        // 1、转换为JSON对象
        T obj = JSON.parseObject(jsonStr, clazz);
        // 2、JSR参数验证
        String code = templateCode; // 错误码
        if (MessageEnum.getErrorMessage(code) == null) {
            code = MessageEnum.ParamsUnValidDetailsAndExplains.getCode();
        }
        DealValidationJSRUtil.validJsrByObj(obj, code);
        return obj;
    }

    /**
     * Description: JSR参数验证
     * @param obj
     * @throws JSRValidationException
     */
    private static void validJsrByObj(Object obj, String templateCode) throws JSRValidationException {
        if (obj == null) {
            throw new JSRValidationException(MessageEnum.ParamsDataNotFound);
        }
        // 2-1、参数校验
        Map<String, Object> map = ViolationParamUtils .violationParamToJkkitJa(obj);
        Boolean isValid = (Boolean) map .get(ViolationParamUtils.JKKIT_LA_JSR_CODE);

        // 2-2、如果参数校验不通过,拼装响应报文消息
        if (!isValid) {
            String msgTemplate = MessageEnum.getErrorMessage(templateCode); // 错误信息模板
            String msg = MessageFormat.format(
                    msgTemplate, 
                    map.get(ViolationParamUtils.JKKIT_LA_JSR_PROP), 
                    map.get(ViolationParamUtils.JKKIT_LA_JSR_MSG));
            // 如果错误报文长度超过,获取精简模板
            if (msg.length() > DealValidationJSRUtil.msgLength) {
                if (msg.length() > DealValidationJSRUtil.msgLengthMax) {
                    // 超精简版
                    templateCode = MessageEnum.ParamsUnValid.getCode();
                    msg = MessageEnum.ParamsUnValid.getMessage();
                } else {
                    // 精简版
                    templateCode = MessageEnum.ParamsUnValidDetails.getCode();
                    msg = MessageFormat.format(
                            MessageEnum.ParamsUnValidDetails.getMessage(),
                            map.get(ViolationParamUtils.JKKIT_LA_JSR_PROP),
                            map.get(ViolationParamUtils.JKKIT_LA_JSR_MSG));
                }
            }
            throw new JSRValidationException(templateCode, msg);
        }
    }

}

 

5、工具类ViolationParamUtils:

import com.demo.common.utils.StringUtil;
import lombok.extern.slf4j.Slf4j;

import javax.validation.*;
import java.util.*;


@Slf4j
public class ViolationParamUtils {

    private static final Validator validator;

    private static final String PLACEHOLDER = "####";

    public static final String JKKIT_LA_JSR_CODE = "JKKIT_LA_JSR_CODE";

    public static final String JKKIT_LA_JSR_PROP = "JKKIT_LA_JSR_PROP";

    public static final String JKKIT_LA_JSR_MSG = "JKKIT_LA_JSR_MSG";

    static {
        ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
        validator = vf.getValidator();
    }

    /**
     * Title [原生]通用的参数校验方法 Description 返回注解的message
     * @param object
     * @return List<String> String:错误信息
     * @author MOSHIHONG930
     */
    public static List<String> violationParam(Object object) {
        List<String> validateMessage = new ArrayList<String>();
        Set<ConstraintViolation<Object>> set = validator.validate(object);
        if (set != null && !set.isEmpty()) {
            for (ConstraintViolation<Object> constraintViolation : set) {
                validateMessage.add(constraintViolation.getMessage());
            }
        }
        return validateMessage;
    }

    /**
     * Title [扩展]返回信息带属性名称的参数校验方法 Map Description
     * 
     * @param object
     * @return Map<String, String> KEY 属性名称, Value 错误信息*/
    public static Map<String, String> violationParam2(Object object) {
        Map<String, String> rmap = new HashMap<String, String>();
        Set<ConstraintViolation<Object>> set = validator.validate(object);
        if (set != null && !set.isEmpty()) {
            for (ConstraintViolation<Object> constraintViolation : set) {
                Path prop = constraintViolation.getPropertyPath();
                if (prop == null) {
                    log.info("属性为空!{},msg:{})", object, constraintViolation.getMessage());
                    continue;
                }
                String key = prop.toString(); // 被校验的属性名称
                String val = constraintViolation.getMessage(); // 错误信息
                rmap.put(key, val);
            }
        }
        return rmap;
    }

    /**
     * Title [扩展]返回信息带属性名称的参数校验方法 List Description 1、返回信息格式: 属性名称 + 错误信息。
     * 2、根据“####”占位符来自定义输出错误信息。 (占位符使用方法: 属性:
     * @Min(value=2, message="####不能小于1") private int id; 输出:
     *               当校验不通过时,将输出“id不能小于1” )
     * @param object
     * @return List<String> String:错误信息
     */
    public static List<String> violationParamPro(Object object) {
        List<String> rlist = new ArrayList<String>();
        Set<ConstraintViolation<Object>> set = validator.validate(object);
        if (set != null && !set.isEmpty()) {
            for (ConstraintViolation<Object> constraintViolation : set) {
                Path prop = constraintViolation.getPropertyPath();
                if (prop == null) {
                    // 基本不会出现的场景
                    log.info("属性为空!{},msg:{})", object, constraintViolation.getMessage());
                    continue;
                }
                String key = prop.toString(); // 被校验的属性名称
                String val = constraintViolation.getMessage(); // 错误信息
                // 判断是否存在占位符。存在 则替换。
                boolean flag1 = ViolationParamUtils.existStrByK(val, PLACEHOLDER);
                boolean flag2 = ViolationParamUtils.existStrByK(val, key);
                String msg = "";
                if (flag1 && !flag2) {
                    // 如果占位符存在,属性不存在。 则替换占位符为属性名称
                    msg = val.replaceFirst(PLACEHOLDER, key);
                } else if (!flag1 && !flag2) {
                    // 如果占位符不存在,属性也不存在。 则拼接属性名称在message前
                    msg = key + val;
                } else {
                    // 其他情况。 返回错误信息原文
                    msg = val;
                }
                rlist.add(msg);
            }
        }
        return rlist;
    }

    /**
     * Description 验证k在str是否存在
     */
    private static boolean existStrByK(String str, String k) {
        if (StringUtil.isEmpty(str)|| StringUtil.isEmpty(k)) {
            return false;
        }
        int num = str.indexOf(k);
        if (num >= 0) {
            return true;
        }
        return false;
    }

    /**
     * Title [原生]通用的参数校验方法 Description 返回注解的message
     * @param object
     * @return List<String> String:错误信息
     */
    public static Map<String, Object> violationParamToJkkitJa(Object object) {
        Map<String, Object> msgMap = new HashMap<String, Object>();
        msgMap.put(JKKIT_LA_JSR_CODE, true); // 验证结果
        msgMap.put(JKKIT_LA_JSR_PROP, ""); // 被校验属性[prop1,prop2...]
        msgMap.put(JKKIT_LA_JSR_MSG, ""); // 校验结果[msg1,msg2...]
        Set<ConstraintViolation<Object>> set = validator.validate(object);
        // 验证通过
        if (set == null || set.isEmpty()) {
            return msgMap;
        }
        // 否则,不通过
        msgMap.put(JKKIT_LA_JSR_CODE, false);
        List<String> plist = new ArrayList<String>();
        String props = "";
        String msgs = "";
        for (ConstraintViolation<Object> constraintViolation : set) {
            String prop = StringUtil.toString(constraintViolation.getPropertyPath());
            if (!plist.contains(prop)) {
                plist.add(prop);
                props = props + constraintViolation.getPropertyPath() + ",";
                msgs = msgs + constraintViolation.getMessage() + ",";
            }
        }
        plist = null;

        // 封装被校验属性
        if (StringUtil.isNotBlank(props)) {
            props = props.substring(0, props.length() - 1);
            msgMap.put(JKKIT_LA_JSR_PROP, props);

        }

        // 封装被校验属性结果
        if (StringUtil.isNotBlank(msgs)) {
            msgs = msgs.substring(0, msgs.length() - 1);
            msgMap.put(JKKIT_LA_JSR_MSG, msgs);
        }
        return msgMap;
    }
}

 

6、异常类JSRValidationException:

import com.demo.common.enums.MessageEnum;

public class JSRValidationException extends RuntimeException {

    private static final long serialVersionUID = 8830274988553371796L;
    
    private String responseCode;
    
    private String responseMsg; 
    
    public JSRValidationException(String code, String message) {
        super(message);
        this.responseCode = code;
        this.responseMsg = message;
    }
    
    public JSRValidationException(MessageEnum messageEnum) {
        this.responseCode = messageEnum.getCode();
        this.responseMsg = messageEnum.getMessage();
    }

    public JSRValidationException(String responseMsg) {
        super(responseMsg);
        this.responseMsg = responseMsg;
    }

    public String getResponseMsg() {
        return responseMsg;
    }

    public void setResponseMsg(String responseMsg) {
        this.responseMsg = responseMsg;
    }

    public String getResponseCode() {
        return responseCode;
    }

    public void setResponseCode(String responseCode) {
        this.responseCode = responseCode;
    }
    
}

7、下面是校验注解包里面的相关类(放到同一个文件夹):

  7.1 

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = BigDecimalMinValidator.class)
@Documented
public @interface BigDecimalMin
{
    public String message() default "{java.math.BigDecimal.min.error}";

    public Class<?>[] groups() default {};

    public Class<? extends Payload>[] payload() default {};

    public String value();

    // 是否含自身, 默认否. 即false (0, 0), true [0, 0]
    public boolean containSelf() default false;
}

 7.2

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.math.BigDecimal;


public final class BigDecimalMinValidator implements ConstraintValidator<BigDecimalMin, Object>
{
    private BigDecimal value;
    private String message;

    public void initialize(BigDecimalMin bigDecimalMin) {
        try {
            value = new BigDecimal(bigDecimalMin.value());
        } catch (NumberFormatException nfe) {
            throw new IllegalArgumentException(
                    bigDecimalMin.value() + " 不是 BigDecimal 格式", nfe);
        }
        message = bigDecimalMin.message();
    }

    public boolean isValid(final Object object,
            final ConstraintValidatorContext cvc) {
        if (object == null) {
            return true;
        } else if (object instanceof BigDecimal) {
            BigDecimal bigDecimal = new BigDecimal(object.toString());
            if (value != null && bigDecimal.compareTo(value) < 0) {
                if (BigDecimal.ONE.compareTo(bigDecimal) != 0) {
                    cvc.disableDefaultConstraintViolation();
                    cvc.buildConstraintViolationWithTemplate(message)
                            .addConstraintViolation();
                    return false;
                }
            }
            return true;
        }
        return false;
    }

}

7.3

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = BigDecimalValidator.class)
@Documented
public @interface BigDecimalRange
{
    public String message() default "{java.math.BigDecimal.range.error}";

    public Class<?>[] groups() default {};

    public Class<? extends Payload>[] payload() default {};

    long minPrecision() default Long.MIN_VALUE;

    long maxPrecision() default Long.MAX_VALUE;

    int scale() default 0;

}

7.4

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.math.BigDecimal;

public final class BigDecimalValidator implements ConstraintValidator<BigDecimalRange, Object> {

    private long maxPrecision;
    private long minPrecision;
    private int scale;
    private String message;

    public void initialize(final BigDecimalRange bigDecimalRange) {
        maxPrecision = bigDecimalRange.maxPrecision();
        minPrecision = bigDecimalRange.minPrecision();
        scale = bigDecimalRange.scale();
        message = bigDecimalRange.message();
    }

    public boolean isValid(final Object object,
            final ConstraintValidatorContext cvc) {
        boolean isValid = false;
        if (object == null) {
            // This should be validated by the not null validator.
            isValid = true;
        } else if (object instanceof BigDecimal) {
            BigDecimal bigDecimal = new BigDecimal(object.toString());
            int actualPrecision = bigDecimal.precision();
            int actualScale = bigDecimal.scale();
            isValid = actualPrecision >= minPrecision
                    && actualPrecision <= maxPrecision && actualScale <= scale;
            if (!isValid) {
                cvc.disableDefaultConstraintViolation();
                cvc.buildConstraintViolationWithTemplate(message)
                        .addConstraintViolation();
            }
        }
        return isValid;
    }
}

7.5

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = BigRangeValidator.class)
@Documented
public @interface BigRange
{
    public String message() default "{this field describes bigger}";

    public Class<?>[] groups() default {};

    public Class<? extends Payload>[] payload() default {};

    int value() default 0;
}

7.6

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.math.BigDecimal;

public final class BigRangeValidator implements ConstraintValidator<BigRange, Object>
{
    @SuppressWarnings("unused")
    private int value;
    private String message;

    public void initialize(final BigRange big) {
        value = big.value();
        message = big.message();
    }

    public boolean isValid(final Object object,
            final ConstraintValidatorContext cvc) {
        if (object instanceof BigDecimal) {
            BigDecimal bd = new BigDecimal(object.toString());
            if (bd.compareTo(BigDecimal.ZERO) == -1 || bd.compareTo(BigDecimal.ZERO) == 0) {
                cvc.disableDefaultConstraintViolation();
                cvc.buildConstraintViolationWithTemplate(message)
                        .addConstraintViolation();
                return false;
            }
        }
        return true;
    }
}

7.7

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * Description data部分可以为空
 * 
 */
@Target({ METHOD })
@Retention(RUNTIME)
public @interface DataIsNullValidator {

}

7.8

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = DateTypeFormatValidator.class)
@Documented
public @interface DateTypeFormat
{
    public String message() default "{this dateType format is error}";

    public Class<?>[] groups() default {};

    public Class<? extends Payload>[] payload() default {};

    String dateRegex() default "^\\d{4}(0[1-9]|1[0-2])$";

}

7.9

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;

public class DateTypeFormatValidator implements ConstraintValidator<DateTypeFormat, Object> {

    private String dateRegex;

    private String message;

    public void initialize(final DateTypeFormat dateTypeFormat) {
        dateRegex = dateTypeFormat.dateRegex();
        message = dateTypeFormat.message();
    }

    public boolean isValid(final Object value, final ConstraintValidatorContext context) {
        boolean isValid = false;
        if (value == null) { // This should be validated by the not null
                             // validator.
            isValid = false;
        }
        // 解决findbugs的问题
        if (Pattern.compile(dateRegex) != null && Pattern.compile(dateRegex).matcher(value.toString()).matches()) {
            isValid = true;
        }
        if (!isValid) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
        }
        return isValid;
    }

}

7.10

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * Description 字符串必须是纯数字
 * 
 */
@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = IsNumberStrValidator.class)
@Documented
public @interface IsNumberStr {

    String message() default "字符串必须是纯数字!";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

7.11

import com.ocft.gamma.spds.trade.common.utils.StringUtil;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Description 字符串必须是纯数字
 * 
 */
public class IsNumberStrValidator implements ConstraintValidator<IsNumberStr, Object> {

    private static final String regEx = "[0-9]*";

    public void initialize(IsNumberStr constraintAnnotation) {

    }

    public boolean isValid(Object value, ConstraintValidatorContext context) {
        // 为空时,不验证是否为数字
        if (value == null) {
            return true;
        }
        String str = StringUtil.valueOf(value);
        Pattern pattern = Pattern.compile(regEx);
        Matcher matcher = pattern.matcher(str);
        return matcher.matches();
    }

}

7.12

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * Description 字符串不能包含特殊字符
 */
@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = NotSpecialStrValidator.class)
@Documented
public @interface NotSpecialStr {

    String message() default "字符串不能包含特殊字符!";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

7.13

import com.ocft.gamma.spds.trade.common.utils.StringUtil;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Description 字符串不能包含特殊字符实现
 */
public class NotSpecialStrValidator implements ConstraintValidator<NotSpecialStr, Object> {

    private static final String regEx = "[^`~!@#$%^&*()+=|{}‘:;‘,\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]{1,}";

    public void initialize(NotSpecialStr constraintAnnotation) {

    }

    public boolean isValid(Object value, ConstraintValidatorContext context) {
        // 为空时,不验证
        if (value == null) {
            return true;
        }
        String str = StringUtil.valueOf(value);
        Pattern pattern = Pattern.compile(regEx);
        Matcher matcher = pattern.matcher(str);
        return matcher.matches();
    }
}

 8、请求封装父类BaseBodyRequest:

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class BaseBodyRequest extends BaseDto {

    private static final long serialVersionUID = 941107305584836986L;

    private String sequenceNo;        
    private String timestamp;        
    private String channelId;        
    private String requestData;        
}

 

 

 9、请求封装父类BaseBodyResponse:

import com.ocft.gamma.spds.trade.common.enums.MessageEnum;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class BaseBodyResponse extends BaseDto{
    
    private String responseCode;
    
    private String responseMessage;
    
    private Object responseData;

    public BaseBodyResponse(String code, String message) {
        super();
        this.responseCode = code;
        responseMessage = message;
    }

    public BaseBodyResponse(String code, String message, Object data) {
        super();
        this.responseCode = code;
        responseMessage = message;
        responseData = data;
    }
    
    public BaseBodyResponse(Object data) {
        super();
        this.responseCode = MessageEnum.Success.getCode();
        responseMessage = MessageEnum.Success.getMessage();
        responseData = data;
    }

    public BaseBodyResponse(MessageEnum menum) {
        this.responseCode = MessageEnum.Success.getCode();
        responseMessage = MessageEnum.Success.getMessage();
    }

    /**成功直接返回数据和状态*/
    public static BaseBodyResponse success(Object data){
        return new BaseBodyResponse(data);
    }
    /**成功直接返回数据和状态*/
    public static BaseBodyResponse success(){
        return new BaseBodyResponse(MessageEnum.Success);
    }

    /**失败的时候调用*/
    public static BaseBodyResponse failure(String code, String message){
        return new BaseBodyResponse(code,message);
    }

    /**失败的时候调用*/
    public static BaseBodyResponse failure(MessageEnum messageEnum){
        return new BaseBodyResponse(messageEnum.getCode(),messageEnum.getMessage());
    }
}

 

java 入参校验

标签:getc   setter   policy   tail   enc   seq   product   context   12px   

原文地址:https://www.cnblogs.com/ITDeveloper/p/13049470.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!