java 攔截、過濾器2

来源:https://www.cnblogs.com/skyice/archive/2023/07/23/17575103.html
-Advertisement-
Play Games

python的環境以及IDE都準備好之後,我們就可以開始Python之旅了。Python的第一個程式通常是列印輸出"Hello, World!",非常簡單。以下是一個示例: ```python print("Hello, World!") ``` # 運行python代碼 首先必須明白python是 ...


一、概述

在SpringMVC中,除了Filter和Interceptor攔截器外,還有對請求Controller的處理,即對請求和響應內容的處理和對請求參數的處理。
image.png

二、ControllerAdvice

@ControllerAdvice本質上同Component一樣,因此也會被當成組件掃描。
其中@ExceptionHandler常用到。即拋出的異常會被統一攔截處理。在項目中對MethodArgumentNotValidException異常攔截處理

@ControllerAdvice
public class GlobalHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result exceptionHandler(MethodArgumentNotValidException e) {
        Result result = new Result(BizExceptionEnum.INVALID_REQ_PARAM.getErrorCode(),
                BizExceptionEnum.INVALID_REQ_PARAM.getErrorMsg());
        logger.error("req params error", e);
        return result;
    }
}
// 上述對MethodArgumentNotValidException異常統一攔截後並統一返回異常

實現原理:

public class DispatcherServlet extends FrameworkServlet {
    // ......
    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        // 處理所有異常
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }
    // ......
}

DispatcherServlet的initHandlerExceptionResolvers(context)方法,方法會取得所有實現了HandlerExceptionResolver介面的bean並保存起來,其中就有一個類型為ExceptionHandlerExceptionResolver的bean,這個bean在應用啟動過程中會獲取所有被@ControllerAdvice註解標註的bean對象做進一步處理,關鍵代碼在這裡

public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
        implements ApplicationContextAware, InitializingBean {
    // ......
    private void initExceptionHandlerAdviceCache() {
        // ......
        List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
        AnnotationAwareOrderComparator.sort(adviceBeans);

        for (ControllerAdviceBean adviceBean : adviceBeans) {
            ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType());
            if (resolver.hasExceptionMappings()) {
                // 找到所有ExceptionHandler標註的方法並保存成一個ExceptionHandlerMethodResolver類型的對象緩存起來
                this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
                if (logger.isInfoEnabled()) {
                    logger.info("Detected @ExceptionHandler methods in " + adviceBean);
                }
            }
            // ......
        }
    }
}

public ExceptionHandlerMethodResolver(Class<?> handlerType) {
    // 查詢當前類中@ExceptionHandler的方法
    for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
        // 獲取方法的異常類型
        for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
            // 添加到緩存中
            addExceptionMapping(exceptionType, method);
        }
    }
}

最後ExceptionHandler被執行過程

// 處理返回結果時,如果異常不為空,則進行異常處理
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                   @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
                                   @Nullable Exception exception) throws Exception {
    boolean errorView = false;
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            // ...
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            // 調用異常Handler處理
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }
    // ...
}

三、RequestBodyAdvice和ResponseBodyAdvice

該類是對入參或者是返回值的處理
RequestBodyAdvice和ResponseBodyAdvice
在ControllerAdvice中,還包含了RequestBodyAdvice,ResponseBodyAdvice

  1. RequestBodyAdvice對請求Body的處理
  2. ResponseBodyAdvice對響應Body的處理

3.1 Spring如何管理

RequestBodyAdvice和ResponseBodyAdvice的實現方式是RequestResponseBodyAdviceChain,其中存儲了xxBodyAdvice的方法,在spring處理請求參數和返回數據是被調用。

class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice<Object> {
  //它持有所有的,記住是所有的advice們
  private final List<Object> requestBodyAdvice = new ArrayList<>(4);
  private final List<Object> responseBodyAdvice = new ArrayList<>(4);

  // 可以看到這是個通用的方法。內來進行區分存儲的   getAdviceByType這個區分方法可以看一下
  // 相容到了ControllerAdviceBean以及beanType本身
  public RequestResponseBodyAdviceChain(@Nullable List<Object> requestResponseBodyAdvice) {
    this.requestBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, RequestBodyAdvice.class));
    this.responseBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, ResponseBodyAdvice.class));
  }

  @Override
  public boolean supports(MethodParameter param, Type type, Class<? extends HttpMessageConverter<?>> converterType) {
    throw new UnsupportedOperationException("Not implemented");
  }
  @Override
  public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
    throw new UnsupportedOperationException("Not implemented");
  }

  // 可以看到最終都是委托給具體的Advice去執行的(supports方法)
  // 特點:符合條件的所有的`Advice`都會順序的、依次的執行
  @Override
  public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
    for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
      if (advice.supports(parameter, targetType, converterType)) {
        request = advice.beforeBodyRead(request, parameter, targetType, converterType);
      }
    }
    return request;
  }
  ... // 其餘方法略。處理邏輯同上順序執行。
  // 最重要的是如下這個getMatchingAdvice()匹配方法


  private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
    // 簡單的說你想要的是Request的還是Response的List呢?
    List<Object> availableAdvice = getAdvice(adviceType);
    if (CollectionUtils.isEmpty(availableAdvice)) {
      return Collections.emptyList();
    }
    List<A> result = new ArrayList<>(availableAdvice.size());
    for (Object advice : availableAdvice) {
      if (advice instanceof ControllerAdviceBean) {
        ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;


        // 這裡面會調用beanTypePredicate.test(beanType)方法
        // 也就是根據basePackages等等判斷此advice是否是否要作用在本類上
        if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {
          continue;
        }
        advice = adviceBean.resolveBean();
      }
      // 當前的advice若是滿足類型要求的,那就添加進去  最終執行切麵操作
      if (adviceType.isAssignableFrom(advice.getClass())) {
        result.add((A) advice);
      }
    }
    return result;
  }
}

我們知道所有的xxxBodyAdvice最終都是通過暴露的RequestResponseBodyAdviceChain來使用的,它內部持有容器內所有的Advice的引用。由於RequestResponseBodyAdviceChain的訪問許可權是default,所以這套機制完全由Spring內部控制。
他唯一設值處是:AbstractMessageConverterMethodArgumentResolver。

AbstractMessageConverterMethodArgumentResolver(一般實際為RequestResponseBodyMethodProcessor):
  // 唯一構造函數,指定所有的advices
  public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters, @Nullable List<Object> requestResponseBodyAdvice) {
    Assert.notEmpty(converters, "'messageConverters' must not be empty");
    this.messageConverters = converters;
    this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
    this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
  }

此構造函數在new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)時候調用,傳進來的requestResponseBodyAdvice就剛好是在初始化RequestMappingHandlerAdapter的時候全局掃描進來的所有的增強器們

3.2 如何使用

請求日誌的列印,用於POST請求的,這裡實現了RequestBodyAdvice用來列印請求參數,也使用了ResponseBodyAdvice列印返回的信息

// 生成日誌信息,並放到request中
@ControllerAdvice
public class RequestBodyAdviceHandler implements RequestBodyAdvice {
    public RequestBodyAdviceHandler() {
    }

    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        Method method = methodParameter.getMethod();
        Class<?> declaringClass = method.getDeclaringClass();
        RestController RestController = (RestController)declaringClass.getAnnotation(RestController.class);
        return RestController != null;
    }

    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        return inputMessage;
    }

    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        this.writeRequestLog(body, inputMessage, parameter, targetType, converterType);
        return body;
    }

    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        this.writeRequestLog(body, inputMessage, parameter, targetType, converterType);
        return body;
    }

    private String toJSONString(Object body, MethodParameter parameter) {
        IgnoreLogBody ignore = (IgnoreLogBody)parameter.getMethodAnnotation(IgnoreLogBody.class);
        if (ignore == null) {
            return JSON.toJSONString(body);
        } else {
            String[] ignoreKey = ignore.ignoreKey();
            return ignoreKey != null && ignoreKey.length != 0 ? JSON.toJSONString(body, new IgnoreLogPropertyFilter(ignore.ignoreKey(), ignore.key()), new SerializerFeature[0]) : JSON.toJSONString(body);
        }
    }

    private void writeRequestLog(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        HttpServletRequest request = RequestHelper.getRequest();
        request.setAttribute("_REQUEST_STARTTIME_", System.currentTimeMillis());
        String requestId = request.getHeader("_REQUEST_ID_");
        if (StringUtils.isEmptyStr(requestId)) {
            requestId = TraceContext.traceId();
        }

        if (StringUtils.isEmptyStr(requestId) || "Ignored_Trace".equals(requestId)) {
            requestId = UUID.randomUUID().toString().replaceAll("-", "");
        }

        request.setAttribute("_REQUEST_ID_", requestId);
        StringBuilder info = new StringBuilder("==>\n");
        info.append("[*=請求requestID=]>: ").append(requestId).append("\n");
        info.append("[==請求地址=======]>: ").append(request.getRequestURL().toString()).append("\n");
        info.append("[==請求方法=======]>: ").append(request.getMethod()).append("\n");
        info.append("[==操作用戶=======]>: ").append(UserInfoContext.getCurrentUserCode()).append("\n");
        info.append("[==客戶IP========]>: ").append(RequestHelper.getClientIP()).append("\n");
        info.append("[==映射方法=======]>: ").append(parameter.getMethod()).append(".").append("\n");
        if (body == null) {
            info.append("[==請求參數=======]>: ").append("該介面未定義參數或參數為空");
        } else if (converterType == FastJsonHttpMessageConverter.class) {
            info.append("[==請求參數=======]>: ").append(this.toJSONString(body, parameter));
        } else {
            info.append("[==請求參數=======]>: ").append(converterType);
        }

        info.append("\n");
        request.setAttribute("_REQUEST_LOG_INFO_", info);
    }
}
@ControllerAdvice
public class ResponseBodyAdviceHandler implements ResponseBodyAdvice<Object> {
    private static final IEventLogger logger = Logtube.getLogger(ResponseBodyAdviceHandler.class.getName());

    public ResponseBodyAdviceHandler() {
    }

    public boolean supports(MethodParameter methodParamter, Class<? extends HttpMessageConverter<?>> converterType) {
        return this.isRestController(methodParamter);
    }

    public Object beforeBodyWrite(Object body, MethodParameter methodParamter, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest req, ServerHttpResponse response) {
        HttpServletRequest request = RequestHelper.getRequest();
        if (request == null) {
            return body;
        } else {
            StringBuilder info = (StringBuilder)request.getAttribute("_REQUEST_LOG_INFO_");
            if (info == null) {
                info = new StringBuilder();
            }

            String requestBodyData = null;
            if (body == null) {
                requestBodyData = null;
            } else if (selectedContentType.includes(MediaType.APPLICATION_JSON)) {
                requestBodyData = JSON.toJSONString(body);
            } else {
                requestBodyData = body.toString();
            }

            Long startTime = (Long)request.getAttribute("_REQUEST_STARTTIME_");
            info.append("[==響應結果=======]>: ").append(requestBodyData == null ? "null" : requestBodyData);
            info.append("\n");
            if (startTime != null) {
                info.append("[==執行耗時=======]>: ").append(System.currentTimeMillis() - startTime).append("ms").append("\n");
            }

            String requestId = (String)request.getAttribute("_REQUEST_ID_");
            logger.info(info.toString());
            return body;
        }
    }

    private boolean isRestController(MethodParameter methodParamter) {
        RestController annotation = (RestController)methodParamter.getDeclaringClass().getAnnotation(RestController.class);
        return annotation != null;
    }
}

最後列印的日誌信息
image.png

四、HandlerMethodReturnValueHandler

4.1 HandlerMethodReturnValueHandler

對返回信息做特殊處理,且只會被調用一次,謹慎處理
預設情況下,spring會調用RequestResponseBodyMethodProcessor來處理返回執行。它實現了HandlerMethodReturnValueHandler的handleReturnValue的方法,
如何選擇returnHandler是在HandlerMethodReturnValueHandlerComposite類做了選擇

	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    	// 選擇返回的handler處理
		HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
		if (handler == null) {
			throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
		}
        // 執行handleReturnValue將數據寫入到reponse流中
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}
// 被@ResponseBody註解的方法,會被執行該類
@Override
public boolean supportsReturnType(MethodParameter returnType) {
    return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
            returnType.hasMethodAnnotation(ResponseBody.class));
}
//將returnValue寫入到流中
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
        throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

    mavContainer.setRequestHandled(true);
    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

    // Try even with null return value. ResponseBodyAdvice could get involved.
    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

知道了HandlerMethodReturnValueHandler是用於返回數據的handler,那麼自己實現這個類用戶封裝自己返回的方法。

4.2 如何使用

一般請求下,返回的數據如下:

{
    "data": {
    },
    "errorCode": "0",
    "errorMsg": "成功",
}

data是其真實數據。在Controller層,需要調用如下

@RestController
@RequestMapping("/stock/depotinventorybalance")
public class DepotInventoryBalanceController {
	@RequestMapping(value = "findById", method = RequestMethod.POST)
	public DepotInventoryBalanceDto findById(@RequestBody DepotInventoryBalanceDto depotInventoryBalance) {
		DepotInventoryBalanceDto  balance = this.depotInventoryBalanceService.findById(depotInventoryBalance);
        return ResponseResultUtil.result(balance);
	}
}

@Data
public class  ResponseResult<T> {
	private T data;
    private String errorCode;
    private String errorMsg; 

}

public static class ResponseResultUtil<T> {
    public static <T> ResponseResult<T> result(T data){
        ResponseResult<T> result = new ResponseResult<>();
        result.setErrorCode( ErrorCode.SUCCESS.getCode());
        result.setErrorMsg(ErrorCode.SUCCESS.getMessage());
        result.setData(data);
        return result;
    }
}

那麼通過註解@AutoResult可以將ResponseResultUtil替換調用,就是在處理HandlerMethodReturnValueHandler時,處理自己handleReturnValue返回數據。但是這裡有一個點,就是實現了自己的類,那麼自己實現的ResponseBodyAdvice,將不會被調用,因為AutoResultReturnValueHandler攔截的請求,會直接返回,不會再調用後續的handler方法。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoResult {
    boolean value() default true;
}

public class AutoResultReturnValueHandler implements HandlerMethodReturnValueHandler {
    private static final IEventLogger logger = Logtube.getLogger(AutoResultReturnValueHandler.class.getName());

    public AutoResultReturnValueHandler() {
    }

    public boolean supportsReturnType(MethodParameter returnType) {
        return this.isRestController(returnType) && this.isAutoResult(returnType);
    }

    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest(HttpServletRequest.class);
        mavContainer.setRequestHandled(true);
        HttpServletResponse response = (HttpServletResponse)webRequest.getNativeResponse(HttpServletResponse.class);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        StringBuilder info = (StringBuilder)request.getAttribute("_REQUEST_LOG_INFO_");
        if (info == null) {
            info = new StringBuilder();
        }

        String requestId = (String)request.getAttribute("_REQUEST_ID_");
        ResponseResult result = new ResponseResult();
        result.setData(returnValue);
        result.setErrorCode( ErrorCode.SUCCESS.getCode());
        result.setErrorMsg(ErrorCode.SUCCESS.getMessage());
        result.setRequestId(requestId);
        String jsonString = JSON.toJSONString(result, new SerializerFeature[]{SerializerFeature.WriteDateUseDateFormat});
        Long startTime = (Long)request.getAttribute("_REQUEST_STARTTIME_");
        info.append("[==響應結果=======]>: ").append(jsonString);
        info.append("\n");
        if (startTime != null) {
            info.append("[==執行耗時=======]>: ").append(System.currentTimeMillis() - startTime).append("ms").append("\n");
        }

        logger.info(info.toString());
        response.getWriter().append(jsonString);
    }

    private boolean isRestController(MethodParameter returnType) {
        RestController annotation = (RestController)returnType.getDeclaringClass().getAnnotation(RestController.class);
        return annotation != null;
    }

    private boolean isAutoResult(MethodParameter returnType) {
        AutoResult methodAnnotation = (AutoResult)returnType.getMethodAnnotation(AutoResult.class);
        if (methodAnnotation != null) {
            return methodAnnotation.value();
        } else {
            AutoResult annotation = (AutoResult)returnType.getDeclaringClass().getAnnotation(AutoResult.class);
            return annotation != null && annotation.value();
        }
    }
}

// 該類下的所有方法都會被攔截
@AutoResult
@RestController
@RequestMapping("/stock/depotinventorybalance")
public class DepotInventoryBalanceController {
	@RequestMapping(value = "findById", method = RequestMethod.POST)
	public DepotInventoryBalanceDto findById(@RequestBody DepotInventoryBalanceDto depotInventoryBalance) {
		return this.depotInventoryBalanceService.findById(depotInventoryBalance);
	}

    // 導出的方法,最後是已文件流的方式返回,
    // 不使用AutoResult返回的結果形式,即不使用自定義Handler類
    @AutoResult(value = false)
    public void export() {
        depotInventoryBalanceService.export();
    }
}



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ...
  • 一、前言 常見的DDD實現架構有很多種,如經典四層架構、六邊形(適配器埠)架構、整潔架構(Clean Architecture)、CQRS架構等。架構無優劣高下之分,只要熟練掌握就都是合適的架構。本文不會逐個去講解這些架構,感興趣的讀者可以自行去瞭解。 本文將帶領大家從日常的三層架構出發,精煉推導 ...
  • ## 介紹 ### 快速概覽 `settings.xml`文件中的 `settings` 元素包含用於定義以各種方式配置Maven執行的值的元素,如`pom.xml`,但不應綁定到任何特定項目或分發給受眾。這些值包括本地倉庫位置、備用遠程倉庫伺服器和身份驗證信息。 `settings.xml`文件可 ...
  • 搭建多Master多Slave模式(同步)集群時的java.lang.NullPointerException異常 一、運行環境等基本描述(問題產生原因是許可權問題,即許可權不夠導致無法啟動broker,甚至broker線程無法通過jps命令查出。下麵闡述分析思路) 1.1)操作系統:Linux 虛擬機 ...
  • 當涉及C++記憶體分區模型時,我們必須理解棧、堆和全局/靜態存儲區的概念。棧用於存儲函數調用和局部變數,堆用於動態記憶體分配,而全局/靜態存儲區用於全局變數和靜態變數。同時,我們還探討了棧幀重用現象,它可能在函數調用時導致局部變數地址重疊。瞭解這些記憶體分區的特點和優化行為,可以幫助我們編寫高效、可靠的C... ...
  • > 原創:扣釘日記(微信公眾號ID:codelogs),歡迎分享,非公眾號轉載保留此聲明。 ### 簡介 我們組有一個流量較大的Java服務,每次發代碼時,服務都會有一小波介面超時,之前簡單分析過,發現這些超時的case僅發生在服務剛啟動時,少量請求會耗時好幾秒,但之後又馬上恢復正常。 ### 問題 ...
  • ## 小程式軟鍵盤&SM2解密方式 轉載請著名出處:[https://www.cnblogs.com/funnyzpc/p/17572445.html](https://www.cnblogs.com/funnyzpc/p/17572445.html) ### SM2基本信息 + 私鑰(primar ...
  • ## 1.等待多線程完成的 CountDownLatch CountDownLatch 允許一個或多個線程等待其他線程完成操作。 假如有這樣一個需求:我們需要解析一個 Excel 里多個 sheet 的數據,此時可以考慮使用多線程,每個線程解析一個 sheet 里的數據,等到所有的 sheet 都解 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...