SpringBoot統(tǒng)一異常和Http響應(yīng)

1.2. Http響應(yīng)內(nèi)容統(tǒng)一封裝

我們在開發(fā)前端后端進(jìn)行交互服務(wù)過程中玫霎,受制于前后端的工作職責(zé)明確,在交互協(xié)議的定義上理解也較為不同妈橄,造成一個項(xiàng)目服務(wù)中重復(fù)定義交互內(nèi)容以及編碼上重復(fù)編寫庶近,不利于項(xiàng)目維護(hù)。所以基于此眷蚓,將后端按照約定請求URL路徑鼻种,并傳入相關(guān)參數(shù),后端服務(wù)器接收請求沙热,進(jìn)行業(yè)務(wù)處理叉钥,返回數(shù)據(jù)給前端,進(jìn)行再次封裝篙贸,供前端以及外部調(diào)用投队。

通常情況下我們后端返回給前端都會采用 JSON 的定義,具體如下:

{
// 返回狀態(tài)碼
code:integer,
// 返回信息描述
message:string,
// 返回值
data:object
}
  • code狀態(tài)碼

在狀態(tài)碼的定義上爵川,在滿足業(yè)務(wù)需求的基礎(chǔ)上蛾洛,避免凌亂,一般業(yè)界同行做法就是參考HTTP請求返回的狀態(tài)碼雁芙。
具體如 百度 - HTTP狀態(tài)碼轧膘。

這里我貼出我將我項(xiàng)目中的常用的羅列出來,供大家參考兔甘。

package xyz.wongs.drunkard.base.message.enums;

/**
 * @ClassName
 * @Description
 * 1000~1999 區(qū)間表示參數(shù)錯誤
 * 2000~2999 區(qū)間表示用戶錯誤
 * 3000~3999 區(qū)間表示接口異常
 * @author WCNGS@QQ.COM
 * @Github <a>https://github.com/rothschil</a>
 * @date 2020/8/2 13:31
 * @Version 1.0.0
 */
public enum ResultCode {

    /** 成功 **/
    SUCCESS(0,"成功"),
    /** 失敗 **/
    FAILURE(-1,"失敗"),

    EXCEPTION(201, "未知異常"),
    RUNTIME_EXCEPTION(202, "運(yùn)行時異常"),
    NULL_POINTER_EXCEPTION(203, "空指針異常"),
    CLASS_CAST_EXCEPTION(204, "類型轉(zhuǎn)換異常"),
    IO_EXCEPTION(205, "IO異常"),
    SYSTEM_EXCEPTION(210, "系統(tǒng)異常"),
    NOT_FOUND(404, "Not Found"),

    /**
     * 1000~1999 區(qū)間表示參數(shù)錯誤
     */
    PARAMS_IS_INVALID(1001,"參數(shù)無效"),
    PARAMS_IS_BANK(1002,"參數(shù)為空"),
    PARAMS_TYPE_BIND_ERROR(1003,"參數(shù)類型錯誤"),
    PARAMS_NOT_COMPLETE(1004,"參數(shù)缺失"),

    /**
     * 2000~2999 區(qū)間表示用戶錯誤
     */
    USER_NOT_LOGGED_IN(2001,"用戶未登錄谎碍,訪問路徑需要驗(yàn)證"),
    USER_NOT_LOGIN_ERROR(2002,"用戶不存在或密碼錯誤"),
    USER_ACCOUNT_FORBIDDEN(2003,"用戶被禁用"),
    USER_NOT_EXIST(2004,"用戶不存在"),
    USER_HAS_EXISTED(2005,"用戶已存在"),
    USER_IS_EXPIRED(2006,"用戶賬號已過期"),
    USER_FIRST_LANDING(2007, "首次登錄"),
    USER_TOKEN_EXPIRED(2008,"Token過期"),
    USER_TOKEN_GENERTATION_FAIL(2009,"生成Token失敗"),
    USER_SIGN_VERIFI_NOT_COMPLIANT(2010,"簽名校驗(yàn)不合規(guī)"),
    USER_PASSWORD_RESET_FAILED(2011, "重置密碼失敗"),
    USER_UNKONWN_INDENTITY(2012, "未知身份"),
    MANY_USER_LOGINS(2111,"多用戶在線"),
    TOO_MANY_PASSWD_ENTER(2112, "密碼輸入次數(shù)過多"),
    VERIFICATION_CODE_INCORECT(2202,"圖形驗(yàn)證碼不正確"),
    VERIFICATION_CODE_FAIL(2203,"圖形驗(yàn)證碼生產(chǎn)失敗"),

    /**
     * 3000~3999 區(qū)間表示接口異常
     */
    API_EXCEPTION(3000, "接口異常"),
    API_NOT_FOUND_EXCEPTION(3002, "接口不存在"),
    API_REQ_MORE_THAN_SET(3003, "接口訪問過于頻繁,請稍后再試"),
    API_IDEMPOTENT_EXCEPTION(3004, "接口不可以重復(fù)提交洞焙,請稍后再試"),
    API_PARAM_EXCEPTION(3005, "參數(shù)異常"),
    API_PARAM_MISSING_EXCEPTION(3006, "缺少參數(shù)"),
    API_METHOD_NOT_SUPPORTED_EXCEPTION(3007, "不支持的Method類型"),
    API_METHOD_PARAM_TYPE_EXCEPTIION(3008, "參數(shù)類型不匹配"),

    ARRAY_EXCEPTION(11001, "數(shù)組異常"),
    ARRAY_OUT_OF_BOUNDS_EXCEPTION(11002, "數(shù)組越界異常"),

    JSON_SERIALIZE_EXCEPTION(30000, "序列化數(shù)據(jù)異常"),
    JSON_DESERIALIZE_EXCEPTION(30001, "反序列化數(shù)據(jù)異常"),

    READ_RESOURSE_EXCEPTION(31002, "讀取資源異常"),
    READ_RESOURSE_NOT_FOUND_EXCEPTION(31003, "資源不存在異常"),

    DATA_EXCEPTION(32004, "數(shù)據(jù)異常"),
    DATA_NOT_FOUND_EXCEPTION(32005, "未找到符合條件的數(shù)據(jù)異常"),
    DATA_CALCULATION_EXCEPTION(32006, "數(shù)據(jù)計(jì)算異常"),
    DATA_COMPRESS_EXCEPTION(32007, "數(shù)據(jù)壓縮異常"),
    DATA_DE_COMPRESS_EXCEPTION(32008, "數(shù)據(jù)解壓縮異常"),
    DATA_PARSE_EXCEPTION(32009, "數(shù)據(jù)轉(zhuǎn)換異常"),

    ENCODING_EXCEPTION(33006, "編碼異常"),
    ENCODING_UNSUPPORTED_EXCEPTION(33006, "編碼不支持異常"),

    DATE_PARSE_EXCEPTION(34001, "日期轉(zhuǎn)換異常"),

    MAILE_SEND_EXCEPTION(35001, "郵件發(fā)送異常");

    /**
     *
     */
    private Integer code;

    /**
     *
     */
    private String msg;

    ResultCode(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

  • message內(nèi)容

這個不解釋啦蟆淀, 就是編碼的文字的意義,說清楚就行澡匪,沒必要太較真熔任,自行腦補(bǔ)。

  • data數(shù)據(jù)

這就是業(yè)務(wù)具體數(shù)據(jù)啦唁情,根據(jù)具體業(yè)務(wù)疑苔,內(nèi)容也不同,這一章節(jié)也沒必要說甸鸟。

這里小結(jié)下惦费,我們除了要有需要定義的內(nèi)容有兩塊:返回的JSON消息體(這里區(qū)分正常響應(yīng)返回兵迅、異常響應(yīng)返回),還需要一套狀態(tài)碼詳細(xì)定義薪贫;再有我們這里做的是WEB恍箭,既然做通用,怎能少攔截器瞧省。

擺脫了繁瑣的文字扯夭,下面開始張羅著貼實(shí)現(xiàn)代碼啦。

1.2.1. 消息體

結(jié)合我們定義的狀態(tài)碼鞍匾,我們返回的消息體主要實(shí)現(xiàn)一個 Serializable勉抓,不要問我為什么。

1.2.1.1. 正常響應(yīng)

package xyz.wongs.drunkard.base.message.response;

import lombok.Data;
import xyz.wongs.drunkard.base.message.enums.ResultCode;

import java.io.Serializable;

/**
 * @ClassName
 * @Description
 * @author WCNGS@QQ.COM
 * @Github <a>https://github.com/rothschil</a>
 * @date 2020/8/2 13:48
 * @Version 1.0.0
 */
@Data
public class Result implements Serializable {
    private static final long serialVersionUID = -4505655308965878999L;

    private Integer code;

    private String message;

    private Object data;

    private Result() {
    }

    public Result(ResultCode resultCode, Object data) {
        this.code = resultCode.getCode();
        this.message = resultCode.getMsg();
        this.data = data;
    }

    private void setResultCode(ResultCode resultCode) {
        this.code = resultCode.getCode();
        this.message = resultCode.getMsg();
    }

    /** 返回成功
     * @Description
     * @param
     * @return xyz.wongs.drunkard.base.message.response.R
     * @throws
     * @date 20/11/13 17:15
     */
    public static Result success() {

        Result result = new Result();
        result.setResultCode(ResultCode.SUCCESS);
        return result;
    }
    /** 返回成功
     * @Description
     * @param
     * @return xyz.wongs.drunkard.base.message.response.R
     * @throws
     * @date 20/11/13 17:15
     */
    public static Result success(Object data) {
        Result result = new Result();
        result.setResultCode(ResultCode.SUCCESS);
        result.setData(data);
        return result;
    }

    /** 返回失敗
     * @Description
     * @param
     * @return xyz.wongs.drunkard.base.message.response.R
     * @throws
     * @date 20/11/13 17:15
     */
    public static Result fail(Integer code, String message) {
        Result result = new Result();
        result.setCode(code);
        result.setMessage(message);
        return result;
    }

    /** 返回失敗
     * @Description
     * @param
     * @return xyz.wongs.drunkard.base.message.response.R
     * @throws
     * @date 20/11/13 17:15
     */
    public static Result fail(ResultCode resultCode) {
        Result result = new Result();
        result.setResultCode(resultCode);
        return result;
    }

}

1.2.1.2. 異常響應(yīng)

package xyz.wongs.drunkard.base.message.response;

import lombok.Data;
import xyz.wongs.drunkard.base.message.enums.ResultCode;

import java.io.Serializable;

/**
 * @author WCNGS@QQ.COM
 * @ClassName ErrorResult
 * @Description 異常錯誤的返回信息實(shí)體
 * @Github <a>https://github.com/rothschil</a>
 * @date 20/11/18 10:42
 * @Version 1.0.0
 */
@Data
public class ErrorResult implements Serializable {

    private static final long serialVersionUID = -4505655308965878999L;

    /**
     * 錯誤編碼
     **/
    private Integer code;
    /**
     * 消息描述
     **/
    private String msg;
    /**
     * 錯誤
     **/
    private String exception;

    public static ErrorResult fail(ResultCode resultCode, Throwable e, String message) {
        ErrorResult errorResult = ErrorResult.fail(resultCode, e);
        errorResult.setMsg(message);
        return errorResult;
    }

    public static ErrorResult fail(ResultCode resultCode, Throwable e) {
        ErrorResult errorResult = new ErrorResult();
        errorResult.setCode(resultCode.getCode());
        errorResult.setMsg(resultCode.getMsg());
        errorResult.setException(e.getClass().getName());
        return errorResult;
    }

    public static ErrorResult fail(Integer code, String message) {
        ErrorResult errorResult = new ErrorResult();
        errorResult.setCode(code);
        errorResult.setMsg(message);
        return errorResult;
    }

}

這樣兩個消息體就寫完啦候学。

1.2.2. 攔截器

我們這里需要做的就是利用攔截器攔截請求,檢查判斷是否此請求返回的值需要包裝纵散。核心就是判斷一個注解annoation是否存在方法或類中梳码。

為了演示的完整,我將代碼貼完整伍掀。

1.2.2.1. Annoation注解

/**
 * @ClassName ResponseResult
 * @Description 
 * @author WCNGS@QQ.COM
 * @Github <a>https://github.com/rothschil</a>
 * @date 20/10/30 21:57
 * @Version 1.0.0
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseResult {
}

1.2.2.2. 攔截器


package xyz.wongs.drunkard.base.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import xyz.wongs.drunkard.base.message.annoation.ResponseResult;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * @author WCNGS@QQ.COM
 * @ClassName ResponseResultInterceptor
 * @Description 請求的攔截器
 * @Github <a>https://github.com/rothschil</a>
 * @date 20/10/30 22:08
 * @Version 1.0.0
 */
@Slf4j
@Component
public class ResponseResultInterceptor implements HandlerInterceptor {

    private static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            final HandlerMethod handlerMethod = (HandlerMethod) handler;
            final Class<?> clazz = handlerMethod.getBeanType();
            final Method method = handlerMethod.getMethod();
            if (clazz.isAnnotationPresent(ResponseResult.class)) {
                request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class));
            } else if (method.isAnnotationPresent(ResponseResult.class)) {
                request.setAttribute(RESPONSE_RESULT_ANN, method.getAnnotation(ResponseResult.class));
            }
        }
        return true;
    }

}

著十幾行代碼的核心處理邏輯掰茶,就是獲取此請求Annoation注解,是否需要返回值包裝蜜笤,并設(shè)置一個屬性標(biāo)記濒蒋,交由下一處理ResponseResultHandler來具體封裝返回值。

細(xì)心的人會發(fā)現(xiàn)這里只處置正常成功的內(nèi)容返回把兔,對于異常的內(nèi)容并未處置沪伙。關(guān)于異常處置我理解統(tǒng)一放在一起來編寫,這樣代碼結(jié)構(gòu)性會更好县好。由此引出下一章節(jié)围橡,全局異常

package xyz.wongs.drunkard.base.handler;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import xyz.wongs.drunkard.base.message.annoation.ResponseResult;
import xyz.wongs.drunkard.base.message.response.Result;

import javax.servlet.http.HttpServletRequest;

/**
 * @ClassName ResponseResultHandler
 * @Description 消息返回體
 * @author WCNGS@QQ.COM
 * @Github <a>https://github.com/rothschil</a>
 * @date 20/11/10 09:28
 * @Version 1.0.0
*/
@Slf4j
@ControllerAdvice(basePackages = "xyz.wongs.drunkard")
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {

    private static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";


    /**
     * @Description 判斷是否要執(zhí)行 beforeBodyWrite 方法缕贡,true為執(zhí)行翁授,false不執(zhí)行,有注解標(biāo)記的時候處理返回值
     * @param returnType
     * @param converterType
     * @return boolean
     * @throws
     * @date 20/11/13 10:50
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        ServletRequestAttributes sra =(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = sra.getRequest();
        ResponseResult responseResult = (ResponseResult)request.getAttribute(RESPONSE_RESULT_ANN);
        return responseResult==null?false:true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectContentType, Class<? extends HttpMessageConverter<?>> selectConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        log.error(" ENTER MSG .... Excu");
        if(body instanceof Result){
            return (Result) body;
        }
        return Result.success(body);
    }
}

1.2.2.3. 全局異常

這里所有的異常都使用到 ErrorResult 類晾咪。

package xyz.wongs.drunkard.base.message.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import xyz.wongs.drunkard.base.message.enums.ResultCode;
import xyz.wongs.drunkard.base.message.response.ErrorResult;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;

/**
 * @author WCNGS@QQ.COM
 * @ClassName GlobalExceptionHandler
 * @Description 全局異常處理Handler
 * @Github <a>https://github.com/rothschil</a>
 * @date 2019/9/23 15:03
 * @Version 1.0.0
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 參數(shù)校驗(yàn)不通過
     *
     * @param ex
     * @return xyz.wongs.drunkard.base.message.response.ErrorResult
     * @throws
     * @author WCNGS@QQ.COM
     * @See
     * @date 2019/9/23 17:53
     * @since
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    @ResponseBody
    public ErrorResult handleConstraintViolationException(ConstraintViolationException ex) {
        log.error("ConstraintViolationException msg:{}", ex.getMessage());
        return ErrorResult.fail(ResultCode.PARAMS_IS_INVALID, ex);
    }


    /**
     * 自定義異常
     *
     * @param request
     * @param ex
     * @return xyz.wongs.drunkard.base.message.response.ErrorResult
     * @throws
     * @author WCNGS@QQ.COM
     * @See
     * @date 2019/9/23 17:53
     * @since
     */
    @org.springframework.web.bind.annotation.ExceptionHandler(DrunkardException.class)
    @ResponseBody
    public ErrorResult handleWeathertopException(HttpServletRequest request, DrunkardException ex) {
        log.error("WeathertopRuntimeException code:{},msg:{}", ex.getCode(), ex.getMessage());
        return ErrorResult.fail(ex.getCode(), ex.getMessage());
    }

    /**
     * @param e
     * @param request
     * @return xyz.wongs.drunkard.base.message.response.ErrorResult
     * @throws
     * @Description 攔截拋出的異常收擦,@ResponseStatus:用來改變響應(yīng)狀態(tài)碼
     * @date 20/11/13 11:14
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Throwable.class)
    public ErrorResult handlerThrowable(Throwable e, HttpServletRequest request) {
        log.error("發(fā)生未知異常!原因是: ", e);
        ErrorResult error = ErrorResult.fail(ResultCode.RUNTIME_EXCEPTION, e);
        return error;
    }

    /**
     * @param e
     * @param request
     * @return xyz.wongs.drunkard.base.message.response.ErrorResult
     * @throws
     * @Description 參數(shù)校驗(yàn)異常
     * @date 20/11/13 11:14
     */
    @ExceptionHandler(BindException.class)
    public ErrorResult handleBindExcpetion(BindException e, HttpServletRequest request) {
        log.error("發(fā)生參數(shù)校驗(yàn)異常谍倦!原因是:", e);
        ErrorResult error = ErrorResult.fail(ResultCode.API_PARAM_EXCEPTION, e, e.getAllErrors().get(0).getDefaultMessage());
        return error;
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
        log.error("發(fā)生參數(shù)校驗(yàn)異常塞赂!原因是:", e);
        ErrorResult error = ErrorResult.fail(ResultCode.API_PARAM_EXCEPTION, e, e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
        return error;
    }
}

1.2.3. 例子

以上雖然將所有代碼貼出,這列為湊完整昼蛀,順道將寫個例子來减途,寫個 Controller

package xyz.wongs.drunkard.war3.web.controller;


import com.github.hiwepy.ip2region.spring.boot.IP2regionTemplate;
import com.github.hiwepy.ip2region.spring.boot.ext.RegionAddress;
import lombok.extern.slf4j.Slf4j;
import org.nutz.plugins.ip2region.DataBlock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import xyz.wongs.drunkard.base.aop.annotion.ApplicationLog;
import xyz.wongs.drunkard.base.message.annoation.ResponseResult;
import xyz.wongs.drunkard.base.message.exception.DrunkardException;
import xyz.wongs.drunkard.war3.limit.RequestLimit;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName IndexController
 * @Description 
 * @author WCNGS@QQ.COM
 * @Github <a>https://github.com/rothschil</a>
 * @date 20/11/18 11:00
 * @Version 1.0.0
*/
@Slf4j
@RestController
@ResponseResult
public class IndexController {

    @RequestLimit(maxCount=3,second=20)
    @ApplicationLog
    @GetMapping("/test")
    public Map<String, Object> test() {
        HashMap<String, Object> data = new HashMap<>(3);
        data.put("info", "測試成功");
        return data;
    }

    @ApplicationLog
    @GetMapping("/fail")
    public Integer error() {
        // 查詢結(jié)果數(shù)
        int res = 0;
        if( res == 0 ) {
            throw new DrunkardException("沒有數(shù)據(jù)");
        }
        return res;
    }

    @Autowired
    IP2regionTemplate template;

    /** 根據(jù)輸入IP地址酣藻,返回解析后的地址
     * @Description
     * @param ip
     * @return xyz.wongs.drunkard.base.message.response.ResponseResult
     * @throws
     * @date 2020/8/17 18:26
     */
    @GetMapping(value = "/convert/{ip}")
    public DataBlock convertDataBlock(@PathVariable String ip){
        DataBlock dataBlock = null;
        try {
            dataBlock = template.binarySearch(ip);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return dataBlock;
    }

    /** 根據(jù)輸入IP地址,返回解析后的地址
     * @Description
     * @param ip
     * @return xyz.wongs.drunkard.base.message.response.ResponseResult
     * @throws
     * @date 2020/8/17 18:26
     */
    @RequestLimit(maxCount=3)
    @GetMapping(value = "/region/{ip}")
    public RegionAddress convert(@PathVariable String ip){
        RegionAddress regionAddress = null;
        try {
            regionAddress = template.getRegionAddress(ip);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return regionAddress;
    }

    @GetMapping(value = "/region/ip={ip}")
    public RegionAddress caseInsensitive(@PathVariable String ip){
        RegionAddress regionAddress = null;
        try {
            regionAddress = template.getRegionAddress(ip);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return regionAddress;
    }

}

訪問 http://localhost:9090/region/ip=109.27.45.12 這是我之前一個例子鳍置,用來解析IP地址辽剧,獲取地域信息的。

正常響應(yīng)
異常響應(yīng)

1.2.4. 源碼地址税产,如果覺得對你有幫助怕轿,請Star

覺得對你有幫助,請Star

Github源碼地址

Gitee源碼地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辟拷,一起剝皮案震驚了整個濱河市撞羽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌衫冻,老刑警劉巖诀紊,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異隅俘,居然都是意外死亡邻奠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門为居,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碌宴,“玉大人,你說我怎么就攤上這事蒙畴》×停” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵膳凝,是天一觀的道長碑隆。 經(jīng)常有香客問我,道長蹬音,這世上最難降的妖魔是什么干跛? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮祟绊,結(jié)果婚禮上楼入,老公的妹妹穿的比我還像新娘。我一直安慰自己牧抽,他們只是感情好嘉熊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布阐肤。 她就那樣靜靜地躺著,像睡著了一般孕惜。 火紅的嫁衣襯著肌膚如雪愧薛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天瞄勾,我揣著相機(jī)與錄音趾疚,去河邊找鬼糙麦。 笑死,一個胖子當(dāng)著我的面吹牛舒裤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播觉吭,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼腾供,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鲜滩?” 一聲冷哼從身側(cè)響起伴鳖,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎徙硅,沒想到半個月后榜聂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嗓蘑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年须肆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桩皿。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡豌汇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泄隔,到底是詐尸還是另有隱情拒贱,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站逻澳,受9級特大地震影響闸天,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜斜做,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一苞氮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧陨享,春花似錦葱淳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至定硝,卻和暖如春皿桑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蔬啡。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工诲侮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人箱蟆。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓沟绪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親空猜。 傳聞我的和親對象是個殘疾皇子绽慈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容