使用 Spring Framework 時常犯的十大錯誤

来源:https://www.cnblogs.com/springforall/archive/2019/07/29/11261736.html
-Advertisement-
Play Games

Spring 可以說是最流行的 Java 框架之一,也是一隻需要馴服的強大野獸。雖然它的基本概念相當容易掌握,但成為一名強大的 Spring 開發者仍需要很多時間和努力。 在本文中,我們將介紹 Spring 中一些常見的錯誤,特別是面向 Web 應用程式和 Spring Boot。正如 Spring ...


Spring 可以說是最流行的 Java 框架之一,也是一隻需要馴服的強大野獸。雖然它的基本概念相當容易掌握,但成為一名強大的 Spring 開發者仍需要很多時間和努力。

在本文中,我們將介紹 Spring 中一些常見的錯誤,特別是面向 Web 應用程式和 Spring Boot。正如 Spring Boot 官網 所說,Spring Boot 對應該如何構建 Production-Ready 的應用保持著 相當固執的觀點,本文將嘗試模仿這種觀點,並提供一些技巧的概述,這些技巧將很好地融入標準 Spring Boot 的 web 應用程式開發中去。

如果你對 Spring Boot 還不是很熟悉,但仍想嘗試下接下來提到的一些內容,我也為本文創建了一個 GitHub 倉庫。如果你在閱讀過程中感到困惑,我建議把代碼 clone 下來,併在本地電腦上使用這些代碼。

1. 常見錯誤一:太過關註底層

我們正在解決這個常見錯誤,是因為 “非我所創” 綜合症在軟體開發領域很是常見。癥狀包括經常重寫一些常見的代碼,很多開發人員都有這種癥狀。

雖然理解特定庫的內部結構及其實現,在很大程度上是好的並且很有必要的(也可以是一個很好的學習過程),但作為軟體工程師,不斷地處理相同的底層實現細節對個人的開發生涯是有害的。像 Spring 這種抽象框架的存在是有原因的,它將你從重覆地手工勞作中解放出來,並允許你專註於更高層次的細節 —— 領域對象和業務邏輯。

因此,接受抽象。下次面對特定問題時,首先進行快速搜索,確定解決該問題的庫是否已被集成到 Spring 中;現在,你可能找到一個合適的現成解決方案。比如,一個很有用的庫,在本文的其他部分,我將在示例中使用 Project Lombok 註解。Lombok 被用作樣板代碼生成器,希望懶惰的開發人員在熟悉這個庫時不會遇到問題。舉個例子,看看使用 Lombok 的 “標準 Java Bean” 是什麼樣子的:

@Getter
@Setter
@NoArgsConstructor
public class Bean implements Serializable {
    int firstBeanProperty;
    String secondBeanProperty;
}

如你所想,上述代碼被編譯為:

public class Bean implements Serializable {
    private int firstBeanProperty;
    private String secondBeanProperty;

    public int getFirstBeanProperty() {
        return this.firstBeanProperty;
    }

    public String getSecondBeanProperty() {
        return this.secondBeanProperty;
    }

    public void setFirstBeanProperty(int firstBeanProperty) {
        this.firstBeanProperty = firstBeanProperty;
    }

    public void setSecondBeanProperty(String secondBeanProperty) {
        this.secondBeanProperty = secondBeanProperty;
    }

    public Bean() {
    }
}

但是,請註意,如果你打算在 IDE 中使用 Lombok,很可能需要安裝一個插件,可在 此處 找到 Intellij IDEA 版本的插件。

2. 常見錯誤二:內部結構 “泄露”

公開你的內部結構,從來都不是一個好主意,因為它在服務設計中造成了不靈活性,從而促進了不好的編碼實踐。“泄露” 的內部機製表現為使資料庫結構可以從某些 API 端點訪問。例如,下麵的 POJO(“Plain Old Java Object”)類表示資料庫中的一個表:

@Entity
@NoArgsConstructor
@Getter
public class TopTalentEntity {

    @Id
    @GeneratedValue
    private Integer id;

    @Column
    private String name;

    public TopTalentEntity(String name) {
        this.name = name;
    }

}

假設,存在一個端點,他需要訪問 TopTalentEntity 數據。返回 TopTalentEntity 實例可能很誘人,但更靈活的解決方案是創建一個新的類來表示 API 端點上的 TopTalentEntity 數據。

@AllArgsConstructor
@NoArgsConstructor
@Getter
public class TopTalentData {
    private String name;
}

這樣,對資料庫後端進行更改將不需要在服務層進行任何額外的更改。考慮下,在 TopTalentEntity 中添加一個 “password” 欄位來存儲資料庫中用戶密碼的 Hash 值 —— 如果沒有 TopTalentData 之類的連接器,忘記更改服務前端,將會意外地暴露一些不必要的秘密信息。

3. 常見錯誤三:缺乏關註點分離

隨著程式規模的增長,逐漸地,代碼組織成為一個越來越重要的問題。諷刺的是,大多數好的軟體工程原則開始在規模上崩潰 —— 特別是在沒有太多考慮程式體繫結構設計的情況下。開發人員最常犯的一個錯誤就是混淆代碼關註點,這很容易做到!

通常,打破 關註點分離 的是將新功能簡單地 “倒” 在現有類中。當然,這是一個很好的短期解決方案(對於初學者來說,它需要更少的輸入),但它也不可避免地會在將來成為一個問題,無論是在測試期間、維護期間還是介於兩者之間。考慮下下麵的控制器,它將從資料庫返回 TopTalentData

@RestController
public class TopTalentController {

    private final TopTalentRepository topTalentRepository;

    @RequestMapping("/toptal/get")
    public List<TopTalentData> getTopTalent() {
        return topTalentRepository.findAll()
                .stream()
                .map(this::entityToData)
                .collect(Collectors.toList());
    }

    private TopTalentData entityToData(TopTalentEntity topTalentEntity) {
        return new TopTalentData(topTalentEntity.getName());
    }

}

起初,這段代碼似乎沒什麼特別的問題;它提供了一個從 TopTalentEntity 實例檢索出來的 TopTalentData的 List。然而,仔細觀察下,我們可以看到 TopTalentController 實際上在此做了些事情;也就是說,它將請求映射到特定端點,從資料庫檢索數據,並將從 TopTalentRepository 接收的實體轉換為另一種格式。一個“更乾凈” 的解決方案是將這些關註點分離到他們自己的類中。看起來可能是這個樣子的:

@RestController
@RequestMapping("/toptal")
@AllArgsConstructor
public class TopTalentController {

    private final TopTalentService topTalentService;

    @RequestMapping("/get")
    public List<TopTalentData> getTopTalent() {
        return topTalentService.getTopTalent();
    }
}

@AllArgsConstructor
@Service
public class TopTalentService {

    private final TopTalentRepository topTalentRepository;
    private final TopTalentEntityConverter topTalentEntityConverter;

    public List<TopTalentData> getTopTalent() {
        return topTalentRepository.findAll()
                .stream()
                .map(topTalentEntityConverter::toResponse)
                .collect(Collectors.toList());
    }
}

@Component
public class TopTalentEntityConverter {
    public TopTalentData toResponse(TopTalentEntity topTalentEntity) {
        return new TopTalentData(topTalentEntity.getName());
    }
}

這種層次結構的另一個優點是,它允許我們通過檢查類名來確定將功能駐留在何處。此外,在測試期間,如果需要,我們可以很容易地用模擬實現來替換任何類。

4. 常見錯誤四:缺乏異常處理或處理不當

一致性的主題並非是 Spring(或 Java)所獨有的,但仍然是處理 Spring 項目時需要考慮的一個重要方面。雖然編碼風格可能存在爭議(通常團隊或整個公司內部已達成一致),但擁有一個共同的標準最終會極大地提高生產力。對多人團隊尤為如此;一致性允許交流發生,而不需要花費很多資源在手把手交接上,也不需要就不同類的職責提供冗長的解釋。

考慮一個包含各種配置文件、服務和控制器的 Spring 項目。在命名時保持語義上的一致性,可以創建一個易於搜索的結構,任何新的開發人員都可以按照自己的方式管理代碼;例如,將 Config 尾碼添加到配置類,服務層以 Service 結尾,以及控制器用 Controller 結尾。

與一致性主題密切相關,伺服器端的錯誤處理值得特別強調。如果你曾經不得不處理編寫很差的 API 的異常響應,那你可能知道原因 —— 正確解析異常會是一件痛苦的事情,而確定這些異常最初發生的原因則更為痛苦。

作為一名 API 開發者,理想情況下你希望覆蓋所有面向用戶的端點,並將他們轉換為常見的錯誤格式。這通常意味著有一個通用的錯誤代碼和描述,而不是逃避解決問題:a) 返回一個 “500 Internal Server Error”信息。b) 直接返回異常的堆棧信息給用戶。(實際上,這些都應該不惜一切代價地去避免,因為除了客戶端難以處理以外,它還暴露了你的內部信息)。

例如,常見錯誤響應格式可能長這樣:

@Value
public class ErrorResponse {

    private Integer errorCode;
    private String errorMessage;

}

與此類似的事情在大多數流行的 API 中也經常遇到,由於可以容易且系統地記錄,效果往往很不錯。將異常轉換為這種格式可以通過向方法提供 @ExceptionHandler 註解來完成(註解案例可見於第六章)。

5. 常見錯誤五:多線程處理不當

不管是桌面應用還是 Web 應用,無論是 Spring 還是 No Spring,多線程都是很難破解的。由並行執行程式所引起的問題是令人毛骨悚然且難以捉摸的,而且常常難以調試 —— 實際上,由於問題的本質,一旦你意識到你正在處理一個並行執行問題,你可能就不得不完全放棄調試器了,並 “手動” 檢查代碼,直到找到根本上的錯誤原因。不幸的是,這類問題並沒有千篇一律的解決方案;根據具體場景來評估情況,然後從你認為最好的角度來解決問題。

當然,理想情況下,你也希望完全避免多線程錯誤。同樣,不存在那種一刀切的方法,但這有一些調試和防止多線程錯誤的實際考慮因素:

5.1. 避免全局狀態

首先,牢記 “全局狀態” 問題。如果你正創建一個多線程應用,那麼應該密切關註任何可能全局修改的內容,如果可能的話,將他們全部刪掉。如果某個全局變數有必須保持可修改的原因,請仔細使用 synchronization,並對程式性能進行跟蹤,以確定沒有因為新引入的等待時間而導致系統性能降低。

5.2. 避免可變性

這點直接來自於 函數式編程,並且適用於 OOP,聲明應該避免類和狀態的改變。簡而言之,這意味著放棄 setter 方法,併在所有模型類上擁有私有的 final 欄位。它們的值唯一發生變化的時間是在構造期間。這樣,你可以確定不會出現爭用問題,且訪問對象屬性將始終提供正確的值。

5.3. 記錄關鍵數據

評估你的程式可能會在何處發生異常,並預先記錄所有關鍵數據。如果發生錯誤,你將很高興可以得到信息說明收到了哪些請求,並可更好地瞭解你的應用程式為什麼會出現錯誤。需要再次註意的是,日誌記錄引入了額外的文件 I/O,可能會嚴重影響應用的性能,因此請不要濫用日誌。

5.4. 復用現存實現

每當你需要創建自己的線程時(例如:向不同的服務發出非同步請求),復用現有的安全實現來代替創建自己的解決方案。這在很大程度上意味著要使用 ExecutorServices 和 Java 8 簡潔的函數式 CompletableFutures 來創建線程。Spring 還允許通過 DeferredResult 類來進行非同步請求處理。

6. 常見錯誤六:不使用基於註解的驗證

假設我們之前的 TopTalent 服務需要一個端點來添加新的 TopTalent。此外,假設基於某些原因,每個新名詞都需要為 10 個字元長度。執行此操作的一種方法可能如下:

@RequestMapping("/put")
public void addTopTalent(@RequestBody TopTalentData topTalentData) {
    boolean nameNonExistentOrHasInvalidLength =
            Optional.ofNullable(topTalentData)
         .map(TopTalentData::getName)
   .map(name -> name.length() == 10)
   .orElse(true);

    if (nameNonExistentOrInvalidLength) {
        // throw some exception
    }

    topTalentService.addTopTalent(topTalentData);
}

然而,上面的方法(除了構造很差以外)並不是一個真正 “乾凈” 的解決辦法。我們正檢查不止一種類型的有效性(即 TopTalentData 不得為空,TopTalentData.name 不得為空,且 TopTalentData.name 為 10 個字元長度),以及在數據無效時拋出異常。

通過在 Spring 中集成 Hibernate validator,數據校驗可以更乾凈地進行。讓我們首先重構 addTopTalent 方法來支持驗證:

@RequestMapping("/put")
public void addTopTalent(@Valid @NotNull @RequestBody TopTalentData topTalentData) {
    topTalentService.addTopTalent(topTalentData);
}

@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleInvalidTopTalentDataException(MethodArgumentNotValidException methodArgumentNotValidException) {
    // handle validation exception
}

此外,我們還必須指出我們想要在 TopTalentData 類中驗證什麼屬性:

public class TopTalentData {
    @Length(min = 10, max = 10)
    @NotNull
    private String name;
}

現在,Spring 將在調用方法之前攔截其請求並對參數進行驗證 —— 無需使用額外的手工測試。

另一種實現相同功能的方法是創建我們自己的註解。雖然你通常只在需要超出 Hibernate的內置約束集 時才使用自定義註解,本例中,我們假設 @Length 不存在。你可以創建兩個額外的類來驗證字元串長度,一個用於驗證,一個用於對屬性進行註解:

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = { MyAnnotationValidator.class })
public @interface MyAnnotation {

    String message() default "String length does not match expected";

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

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

    int value();

}

@Component
public class MyAnnotationValidator implements ConstraintValidator<MyAnnotation, String> {

    private int expectedLength;

    @Override
    public void initialize(MyAnnotation myAnnotation) {
        this.expectedLength = myAnnotation.value();
    }

    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        return s == null || s.length() == this.expectedLength;
    }
}

請註意,這些情況下,關註點分離的最佳實踐要求在屬性為 null 時,將其標記為有效(isValid 方法中的 s == null),如果這是屬性的附加要求,則使用 @NotNull 註解。

public class TopTalentData {
    @MyAnnotation(value = 10)
    @NotNull
    private String name;
}

7. 常見錯誤七:(依舊)使用基於xml的配置

雖然之前版本的 Spring 需要 XML,但如今大部分配置均可通過 Java 代碼或註解來完成;XML 配置只是作為附加的不必要的樣板代碼。

本文(及其附帶的 GitHub 倉庫)均使用註解來配置 Spring,Spring 知道應該連接哪些 Bean,因為待掃描的頂級包目錄已在 @SpringBootApplication 複合註解中做了聲明,如下所示:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

複合註解(可通過 Spring 文檔 瞭解更多信息)只是向 Spring 提示應該掃描哪些包來檢索 Bean。在我們的案例中,這意味著這個頂級包 (co.kukurin)將用於檢索:

  • @Component (TopTalentConverterMyAnnotationValidator)
  • @RestController (TopTalentController)
  • @Repository (TopTalentRepository)
  • @Service (TopTalentService) 類

如果我們有任何額外的 @Configuration 註解類,它們也會檢查基於 Java 的配置。

8. 常見錯誤八:忽略 profile

在服務端開發中,經常遇到的一個問題是區分不同的配置類型,通常是生產配置和開發配置。在每次從測試切換到部署應用程式時,不要手動替換各種配置項,更有效的方法是使用 profile。

考慮這麼一種情況:你正在使用記憶體資料庫進行本地開發,而在生產環境中使用 MySQL 資料庫。本質上,這意味著你需要使用不同的 URL 和 (希望如此) 不同的憑證來訪問這兩者。讓我們看看可以如何做到這兩個不同的配置文件:

8.1. APPLICATION.YAML 文件

# set default profile to 'dev'
spring.profiles.active: dev

# production database details
spring.datasource.url: 'jdbc:mysql://localhost:3306/toptal'
spring.datasource.username: root
spring.datasource.password:

8.2. APPLICATION-DEV.YAML 文件

spring.datasource.url: 'jdbc:h2:mem:'
spring.datasource.platform: h2

假設你不希望在修改代碼時意外地對生產資料庫進行任何操作,因此將預設配置文件設為 dev 是很有意義的。然後,在伺服器上,你可以通過提供 -Dspring.profiles.active=prod 參數給 JVM 來手動覆蓋配置文件。另外,還可將操作系統的環境變數設置為所需的預設 profile。

9. 常見錯誤九:無法接受依賴項註入

正確使用 Spring 的依賴註入意味著允許其通過掃描所有必須的配置類來將所有對象連接在一起;這對於解耦關係非常有用,也使測試變得更為容易,而不是通過類之間的緊耦合來做這樣的事情:

public class TopTalentController {

    private final TopTalentService topTalentService;

    public TopTalentController() {
        this.topTalentService = new TopTalentService();
    }
}

我們讓 Spring 為我們做連接:

public class TopTalentController {

    private final TopTalentService topTalentService;

    public TopTalentController(TopTalentService topTalentService) {
        this.topTalentService = topTalentService;
    }
}

Misko Hevery 的 Google talk 深入解釋了依賴註入的 “為什麼”,所以,讓我們看看它在實踐中是如何使用的。在關註點分離(常見錯誤 #3)一節中,我們創建了一個服務和控制器類。假設我們想在 TopTalentService行為正確的前提下測試控制器。我們可以通過提供一個單獨的配置類來插入一個模擬對象來代替實際的服務實現:

@Configuration
public class SampleUnitTestConfig {
    @Bean
    public TopTalentService topTalentService() {
        TopTalentService topTalentService = Mockito.mock(TopTalentService.class);
        Mockito.when(topTalentService.getTopTalent()).thenReturn(
                Stream.of("Mary", "Joel").map(TopTalentData::new).collect(Collectors.toList()));
        return topTalentService;
    }
}

然後,我們可以通過告訴 Spring 使用 SampleUnitTestConfig 作為它的配置類來註入模擬對象:

@ContextConfiguration(classes = { SampleUnitTestConfig.class })

之後,我們就可以使用上下文配置將 Bean 註入到單元測試中。

10. 常見錯誤十:缺乏測試,或測試不當

儘管單元測試的概念已經存在很長時間了,但很多開發人員似乎要麼 “忘記” 做這件事(特別是如果它不是 “必需” 的時候),要麼只是在事後把它添加進來。這顯然是不可取的,因為測試不僅應該驗證代碼的正確性,還應該作為程式在不同場景下應如何表現的文檔。

在測試 Web 服務時,很少只進行 “純” 單元測試,因為通過 HTTP 進行通信通常需要調用 Spring 的 DispatcherServlet,並查看當收到一個實際的 HttpServletRequest 時會發生什麼(使它成為一個 “集成” 測試,處理驗證、序列化等)。REST Assured,一個用於簡化測試REST服務的 Java DSL,在 MockMVC 之上,已經被證明提供了一個非常優雅的解決方案。考慮以下帶有依賴項註入的代碼片段:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {
        Application.class,
        SampleUnitTestConfig.class
})
public class RestAssuredTestDemonstration {

    @Autowired
    private TopTalentController topTalentController;

    @Test
    public void shouldGetMaryAndJoel() throws Exception {
        // given
        MockMvcRequestSpecification givenRestAssuredSpecification = RestAssuredMockMvc.given()
                .standaloneSetup(topTalentController);

        // when
        MockMvcResponse response = givenRestAssuredSpecification.when().get("/toptal/get");

        // then
        response.then().statusCode(200);
        response.then().body("name", hasItems("Mary", "Joel"));
    }

}

SampleUnitTestConfig 類將 TopTalentService 的模擬實現連接到 TopTalentController 中,而所有的其他類都是通過掃描應用類所在包的下級包目錄來推斷出的標準配置。RestAssuredMockMvc 只是用來設置一個輕量級環境,並向 /toptal/get 端點發送一個 GET 請求。

11. 成為 Spring 大師

Spring 是一個功能強大的框架,很容易上手,但需要一些投入和時間才可以完全掌握。長遠來看,花時間熟悉框架肯定會提高你的生產力,並最終助你寫出更乾凈的代碼,成為更好的開發人員。

想尋找更多資源,Spring In Action 是一本涵蓋了很多 Spring 核心主題的優秀實戰書籍。

原文:https://www.toptal.com/spring/top-10-most-common-spring-framework-mistakes

作者:Toni Kukurin

譯者:萬想


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

-Advertisement-
Play Games
更多相關文章
  • 在進入正題之前,說一些廢話,談談對於我的前一篇文章被移出博客園首頁的想法。不談我對於其他首頁文章的看法,光從我自身找找原因。下麵分析下可能的原因: 1. 篇幅太短:我覺得篇幅不能決定文章的質量,要說清楚一個問題,肯定字數越少越好 2. 代碼過多,文字太少:Talk is cheap. Show me ...
  • 對於重覆的代碼,如果是重覆的字元串,我們會想到提出一個變數。如果是重覆的代碼塊,我們會想到提取出一個方法。 但如果這重覆的代碼塊中有一處或幾處是會變化的,那麼就沒那麼容易提取出一個方法。說起來有點抽象,下麵看一個例子。 一、分頁查詢 寫過CRUD的同學肯定寫過很多分頁查詢,分頁查詢的主要步驟是先校驗 ...
  • 舉個慄子 問題描述 模擬訪問資料庫“新增用戶”和“得到用戶”,用戶類假設只有 ID和Name 兩個欄位。 簡單實現 User SqlServerUser 測試 測試結果 存在問題 如果需要連接別的資料庫,那麼這個寫法無法擴展,下麵使用 工廠方法模式 實現 工廠方法模式實現 IUser SqlServ ...
  • [toc] 前言 Composite設計模式,將物體組合成一個樹結構,讓單個對象和組合對象使用起來都一樣,組合對象負責將實際的操作分發給每一個組件。 這篇博文分析了安卓的View相關的類,它們可以說是用了Composite設計模式。其中分析View的measure,layout,draw是如何從組合 ...
  • 零基礎java開發工程師視頻教程全套,基礎+進階+項目實戰(152G) 共130天,152G 下載地址 ...
  • 周末閑來無事,看到隔壁家的老王在和隔壁家的媳婦玩24點,就進屋看了看。發現老王是真不行啊,那不行,這也不行。 就連個24點都玩不過他媳婦,給他媳婦氣的,啥都不能滿足,這不能,那也不能。 我坐下來和他媳婦玩了兩把,那都是無出其右,把把贏! 我要走的時候,他媳婦還輓留我多玩幾把,有意思。 為了能... ...
  • 書評: 感謝作者和譯者,很好的手把手的一個新手編程體驗書,消除編程物質恐懼感,線上看的liam huang翻譯的版,不確定看的是第幾版,有一些加分題沒有做,第五十題黑手黨外星人飛船做起來有點壓力,準備轉去codeacademy / think python和a byte of python在進行一些 ...
  • 前段時間自己使用 redis 開發的時候,搞了一個 docker ,然後直接開放連接沒有密碼,其實一開始我就知道會被黑產掃到然後給我種馬,但是把因為也是測試服務,其實也沒怎麼上心,於是就放任自由了,結果第二天果然收到了一份新鮮的木馬。然後簡單對其入侵做了一個分析,結果發現沒有能攻擊成功,但是既然木馬 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...