基於SpringBoot搭建應用開發框架(二) —— 登錄認證

来源:https://www.cnblogs.com/chiangchou/archive/2018/11/10/springboot-2.html
-Advertisement-
Play Games

基於 SpringSecurity 實現標準用戶名密碼登錄,基於 SpringSocial 實現QQ登錄,基於 OAuth2 實現認證伺服器。在完成登錄功能的同時,一步步分析 spring security、spring social、oauth 的實現原理,源碼分析等。 ...


零、前言

本文基於《基於SpringBoot搭建應用開發框架(一)——基礎架構》,通過該文,熟悉了SpringBoot的用法,完成了應用框架底層的搭建。

在開始本文之前,底層這塊已經有了很大的調整,主要是SpringBoot由之前的 1.5.9.RELEASE 升級至 2.1.0.RELEASE 版本,其它依賴的三方包基本也都升級到目前最新版了。

其次是整體架構上也做了調整:

  sunny-parent:sunny 項目的頂級父類,sunny-parent 又繼承自 spring-boot-starter-parent ,為所有項目統一 spring 及 springboot 版本。同時,管理項目中將用到的大部分的第三方包,統一管理版本號。

  sunny-starter:項目中開發的組件以 starter 的方式進行集成,按需引入 starter 即可。sunny-starter 下以 module 的形式組織,便於管理、批量打包部署。

    sunny-starter-core:核心包,定義基礎的操作類、異常封裝、工具類等,集成了 mybatis-mapper、druid 數據源、redis 等。

    sunny-starter-captcha:驗證碼封裝。

  sunny-cloud:spring-cloud 系列服務,微服務基礎框架,本篇文章主要集中在 sunny-cloud-security上,其它的以後再說。

    sunny-cloud-security:認證服務和授權服務。

  sunny-admin:管理端服務,業務中心。

  

 

本篇將會一步步完成系統的登錄認證,包括常規的用戶名+密碼登錄、以及社交方式登錄,如QQ、微信授權登錄等,一步步分析 spring-security 及 oauth 相關的源碼。

一、SpringSecurity 簡介

SpringSecurity 是專門針對基於Spring項目的安全框架,充分利用了AOP和Filter來實現安全功能。它提供全面的安全性解決方案,同時在 Web 請求級和方法調用級處理身份確認和授權。他提供了強大的企業安全服務,如:認證授權機制、Web資源訪問控制、業務方法調用訪問控制、領域對象訪問控制Access Control List(ACL)、單點登錄(SSO)等等。

核心功能:認證(你是誰)、授權(你能幹什麼)、攻擊防護(防止偽造身份)。

基本原理:SpringSecurity的核心實質是一個過濾器鏈,即一組Filter,所有的請求都會經過這些過濾器,然後響應返回。每個過濾器都有特定的職責,可通過配置添加、刪除過濾器。過濾器的排序很重要,因為它們之間有依賴關係。有些過濾器也不能刪除,如處在過濾器鏈最後幾環的ExceptionTranslationFilter(處理後者拋出的異常),FilterSecurityInterceptor(最後一環,根據配置決定請求能不能訪問服務)。

二、標準登錄

使用 用戶名+密碼 的方式來登錄,用戶名、密碼存儲在資料庫,並且支持密碼輸入錯誤三次後開啟驗證碼,通過這樣一個過程來熟悉 spring security 的認證流程,掌握 spring security 的原理。

1、基礎環境

① 創建 sunny-cloud-security 模塊,埠號設置為 8010,在sunny-cloud-security模塊引入security支持以及sunny-starter-core:

② 開發一個TestController

 ③ 不做任何配置,啟動系統,然後訪問 localhost:8010/test 時,會自動跳轉到SpringSecurity預設的登錄頁面去進行認證。那這登錄的用戶名和密碼從哪來呢?

啟動項目時,從控制台輸出中可以找到生成的 security 密碼,從 UserDetailsServiceAutoConfiguration 可以得知,使用的是基於記憶體的用戶管理器,預設的用戶名為 user,密碼是隨機生成的UUID。

我們也可以修改預設的用戶名和密碼。

④ 使用 user 和生成的UUID密碼登錄成功後即可訪問 /test 資源,最簡單的一個認證就完成了。

在不做任何配置的情況下,security會把服務內所有資源的訪問都保護起來,需要先進行身份證認證才可訪問, 使用預設的表單登錄或http basic認證方式。

不過這種預設方式肯定無法滿足我們的需求,我們的用戶名和密碼都是存在資料庫的。下麵我們就來看看在 spring boot 中我們如何去配置自己的登錄頁面以及從資料庫獲取用戶數據來完成用戶登錄。

2、自定義登錄頁面

① 首先開發一個登錄頁面,由於頁面中會使用到一些動態數據,決定使用 thymeleaf 模板引擎,只需在 pom 中引入如下依賴,使用預設配置即可,具體有哪些配置可從 ThymeleafProperties 中瞭解到。

② 同時,在 resources 目錄下,建 static 和 templates 兩個目錄,static 目錄用於存放靜態資源,templates 用於存放 thymeleaf 模板頁面,同時配置MVC的靜態資源映射。

   

③ 開發後臺首頁、登錄頁面的跳轉地址,/login 介面用於向登錄頁面傳遞登錄相關的數據,如用戶名、是否啟用驗證碼、錯誤消息等。

 1 package com.lyyzoo.sunny.security.controller;
 2 
 3 import javax.servlet.http.HttpServletResponse;
 4 import javax.servlet.http.HttpSession;
 5 
 6 import org.apache.commons.lang3.StringUtils;
 7 import org.springframework.beans.factory.annotation.Autowired;
 8 import org.springframework.security.web.WebAttributes;
 9 import org.springframework.stereotype.Controller;
10 import org.springframework.ui.Model;
11 import org.springframework.web.bind.annotation.GetMapping;
12 import org.springframework.web.bind.annotation.RequestMapping;
13 import org.springframework.web.bind.annotation.ResponseBody;
14 
15 import com.lyyzoo.sunny.captcha.CaptchaImageHelper;
16 import com.lyyzoo.sunny.core.base.Result;
17 import com.lyyzoo.sunny.core.message.MessageAccessor;
18 import com.lyyzoo.sunny.core.userdetails.CustomUserDetails;
19 import com.lyyzoo.sunny.core.userdetails.DetailsHelper;
20 import com.lyyzoo.sunny.core.util.Results;
21 import com.lyyzoo.sunny.security.constant.SecurityConstants;
22 import com.lyyzoo.sunny.security.domain.entity.User;
23 import com.lyyzoo.sunny.security.domain.service.ConfigService;
24 import com.lyyzoo.sunny.security.domain.service.UserService;
25 
26 /**
27  *
28  * @author bojiangzhou 2018/03/28
29  */
30 @Controller
31 public class SecurityController {
32 
33     private static final String LOGIN_PAGE = "login";
34 
35     private static final String INDEX_PAGE = "index";
36 
37     private static final String FIELD_ERROR_MSG = "errorMsg";
38     private static final String FIELD_ENABLE_CAPTCHA = "enableCaptcha";
39 
40     @Autowired
41     private CaptchaImageHelper captchaImageHelper;
42     @Autowired
43     private UserService userService;
44     @Autowired
45     private ConfigService configService;
46 
47     @RequestMapping("/index")
48     public String index() {
49         return INDEX_PAGE;
50     }
51 
52     @GetMapping("/login")
53     public String login(HttpSession session, Model model) {
54         String errorMsg = (String) session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
55         String username = (String) session.getAttribute(User.FIELD_USERNAME);
56         if (StringUtils.isNotBlank(errorMsg)) {
57             model.addAttribute(FIELD_ERROR_MSG, errorMsg);
58         }
59         if (StringUtils.isNotBlank(username)) {
60             model.addAttribute(User.FIELD_USERNAME, username);
61             User user = userService.getUserByUsername(username);
62             if (user == null) {
63                 model.addAttribute(FIELD_ERROR_MSG, MessageAccessor.getMessage("login.username-or-password.error"));
64             } else {
65                 if (configService.isEnableCaptcha(user.getPasswordErrorTime())) {
66                     model.addAttribute(FIELD_ENABLE_CAPTCHA, true);
67                 }
68             }
69         }
70         session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
71 
72         return LOGIN_PAGE;
73     }
74 
75     @GetMapping("/public/captcha.jpg")
76     public void captcha(HttpServletResponse response) {
77         captchaImageHelper.generateAndWriteCaptchaImage(response, SecurityConstants.SECURITY_KEY);
78     }
79 
80     @GetMapping("/user/self")
81     @ResponseBody
82     public Result test() {
83         CustomUserDetails details = DetailsHelper.getUserDetails();
84 
85         return Results.successWithData(details);
86     }
87 
88 }
View Code

 ④  從 spring boot 官方文檔可以得知,spring security 的核心配置都在 WebSecurityConfigurerAdapter 里,我們只需繼承該適配器覆蓋預設配置即可。首先來看看預設的登錄頁面以及如何配置登錄頁面。

通過 HttpSecurity 配置安全策略,首先開放了允許匿名訪問的地址,除此之外都需要認證,通過 formLogin() 來啟用表單登錄,並配置了預設的登錄頁面,以及登錄成功後的首頁地址。

啟動系統,訪問資源跳轉到自定義的登錄頁面了:

⑤ 那麼預設的登錄頁面是怎麼來的呢,以及做了哪些預設配置?

從 formLogin() 可以看出,啟用表單登錄即啟用了表單登錄的配置 FormLoginConfigurer:

從 FormLoginConfigurer 的構造函數中可以看出,表單登錄用戶名和密碼的參數預設配置為 username 和 password,所以,我們的登錄頁面中需和這兩個參數配置成一樣,當然了,我們也可以在 formLogin() 後自定義這兩個參數。

同時,可以看出開啟了 UsernamePasswordAuthenticationFilter 過濾器,用於 用戶名+密碼 登錄方式的認證,這個之後再說明。

從初始化配置中可以看出,預設創建了 DefaultLoginPageGeneratingFilter 過濾器用於生成預設的登錄頁面,從該過濾器的初始化方法中我們也可以瞭解到一些預設的配置。這個過濾器只有在未配置自定義登錄頁面時才會生效。

3、SpringSecurity基本原理

在進行後面的開發前,先來瞭解下 spring security 的基本原理。

spring security 的核心是過濾器鏈,即一組 Filter。所有服務資源的請求都會經過 spring security 的過濾器鏈,並響應返回。

我們從控制臺中可以找到輸出過濾器鏈的類 DefaultSecurityFilterChain,在現有的配置上,可以看到當前過濾器鏈共有13個過濾器。

每個過濾器主要做什麼可以參考:Spring Security 核心過濾器鏈分析

過濾器鏈的創建是通過 HttpSecurity 的配置而來,實際上,每個 HttpSecurity 的配置都會創建相應的過濾器鏈來處理對應的請求,每個請求都會進入 FilterChainProxy 過濾器,根據請求選擇一個合適的過濾器鏈來處理該請求。

過濾器的順序我們可以從 FilterComparator 中得知,並且可以看出 spring security 預設有25個過濾器(自行查看):

 不難發現,幾乎所有的過濾器都直接或間接繼承自 GenericFilterBean,通過這個基礎過濾器可以看到都有哪些過濾器,通過每個過濾器的名稱我們能大概瞭解到 spring security 為我們提供了哪些功能,要啟用這些功能,只需通過配置加入相應的過濾器即可,比如 oauth 認證。

過濾器鏈中,綠色框出的這類過濾器主要用於用戶認證,這些過濾器會根據當前的請求檢查是否有這個過濾器所需的信息,如果有則進入該過濾器,沒有則不會進入下一個過濾器。

比如這裡,如果是表單登錄,要求必須是[POST /login],則進入 UsernamePasswordAuthenticationFilter 過濾器,使用用戶名和密碼進行認證,不會再進入BasicAuthenticationFilter;

如果使用 http basic 的方式進行認證,要求請求頭必須包含 Authorization,且值以 basic 打頭,則進入 BasicAuthenticationFilter 進行認證。

經過前面的過濾器後,最後會進入到 FilterSecurityInterceptor,這是整個 spring security 過濾器鏈的最後一環,在它身後就是服務的API。

這個過濾器會去根據配置決定當前的請求能不能訪問真正的資源,主要一些實現功能在其父類AbstractSecurityInterceptor中。

[1] 拿到的是許可權配置,會根據這些配置決定訪問的API能否通過。

[2] 當前上下文必須有用戶認證信息 Authentication,就算是匿名訪問也會有相應的過濾器來生成 Authentication。不難發現,不同類型的認證過濾器對應了不同的 Authentication。使用用戶名和密碼登錄時,就會生成 UsernamePasswordAuthenticationToken。

[3] 用戶認證,首先判斷用戶是否已認證通過,認證通過則直接返回 Authentication,否則調用認證器進行認證。認證通過之後將 Authentication 放到 Security 的上下文,這就是為何我們能從 SecurityContextHolder 中取到 Authentication 的源頭。

認證管理器是預設配置的 ProviderManager,ProviderManager 則管理者多個 AuthenticationProvider 認證器 ,認證的時候,只要其中一個認證器認證通過,則標識認證通過。

認證器:表單登錄預設使用 DaoAuthenticationProvider,我們想要實現從資料庫獲取用戶名和密碼就得從這裡入手。

[4] 認證通過後,使用許可權決定管理器 AccessDecisionManager 判斷是否有許可權,管理器則管理者多個 許可權投票器 AccessDecisionVoter,通過投票器來決定是否有許可權訪問資源。因此,我們也可以自定義投票器來判斷用戶是否有許可權訪問某個API。

 

最後,如果未認證通過或沒有許可權,FilterSecurityInterceptor 則拋出相應的異常,異常會被 ExceptionTranslationFilter 捕捉到,進行統一的異常處理分流,比如未登錄時,重定向到登錄頁面;沒有許可權的時候拋出403異常等。

4、用戶認證流程

從 spring security 基本原理的分析中不難發現,用戶的認證過程涉及到三個主要的組件:

AbstractAuthenticationProcessingFilter:它在基於web的認證請求中用於處理包含認證信息的請求,創建一個部分完整的Authentication對象以在鏈中傳遞憑證信息。

AuthenticationManager:它用來校驗用戶的憑證信息,或者會拋出一個特定的異常(校驗失敗的情況)或者完整填充Authentication對象,將會包含了許可權信息。

AuthenticationProvider:它為AuthenticationManager提供憑證校驗。一些AuthenticationProvider的實現基於憑證信息的存儲,如資料庫,來判定憑證信息是否可以被認可。

我們從核心的 AbstractAuthenticationProcessingFilter 入手,來分析下用戶認證的流程。

[1] 可以看到,首先會調用 attemptAuthentication 來獲取認證後的 Authentication。attemptAuthentication 是一個抽象方法,在其子類中實現。

 前面提到過,啟用表單登錄時,就會創建 UsernamePasswordAuthenticationFilter 用於處理表單登錄。後面開發 oauth2 認證的時候則會用到 OAuth2 相關的過濾器。

從 attemptAuthentication 的實現中可以看出,主要是將 username 和 password 封裝到 UsernamePasswordAuthenticationToken。

從當前 UsernamePasswordAuthenticationToken 的構造方法中可以看出,此時的 Authentication 設置了未認證狀態。

 【#】通過 setDetails 可以向 UsernamePasswordAuthenticationToken  中加入 Details 用於後續流程的處理,稍後我會實現AuthenticationDetailsSource 將驗證碼放進去用於後面的認證。

之後,通過 AuthenticationManager 進行認證,實際是 ProviderManager 管理著一些認證器,這些配置都可以通過 setter 方法找到相應配置的位置,這裡就不贅述了。

不難發現,用戶認證器使用的是 AbstractUserDetailsAuthenticationProvider,流程主要涉及到 retrieveUser  和 additionalAuthenticationChecks 兩個抽象方法。

【#】AbstractUserDetailsAuthenticationProvider 預設只有一個實現類 DaoAuthenticationProvider,獲取用戶信息、用戶密碼校驗都是在這個實現類里,因此我們也可以實現自己的 AbstractUserDetailsAuthenticationProvider 來處理相關業務。

【#】從 retrieveUser 中可以發現,主要使用 UserDetailsService 來獲取用戶信息,該介面只有一個方法 loadUserByUsername,我們也會實現該介面來從資料庫獲取用戶信息。如果有複雜的業務邏輯,比如鎖定用戶等,還可以覆蓋 retrieveUser 方法。

 用戶返回成功後,就會通過 PasswordEncoder 來校驗用戶輸入的密碼和資料庫密碼是否匹配。註意資料庫存入的密碼是加密後的密碼,且不可逆。

 用戶、密碼都校驗通過後,就會創建已認證的 Authentication,從此時 UsernamePasswordAuthenticationToken 的構造方法可以看出,構造的是一個已認證的 Authentication。

[2] 如果用戶認證失敗,會調用 AuthenticationFailureHandler 的 onAuthenticationFailure 方法進行認證失敗後的處理,我們也會實現這個介面來做一些失敗後邏輯處理。

[3] 用戶認證成功,將 Authentication 放入 security 上下文,調用 AuthenticationSuccessHandler 做認證成功的一些後續邏輯處理,我們也會實現這個介面。

5、用戶認證代碼實現

通過 spring security 基本原理分析和用戶認證流程分析,我們已經能夠梳理出完成認證需要做哪些工作了。

① 首先設計並創建系統用戶表:

② CustomUserDetails

自定義 UserDetails,根據自己的需求將一些常用的用戶信息封裝到 UserDetails 中,便於快速獲取用戶信息,比如用戶ID、昵稱等。

 1 package com.lyyzoo.sunny.core.userdetails;
 2 
 3 import java.util.Collection;
 4 import java.util.Objects;
 5 
 6 import org.springframework.security.core.GrantedAuthority;
 7 import org.springframework.security.core.userdetails.User;
 8 
 9 
10 /**
11  * 定製的UserDetail對象
12  *
13  * @author bojiangzhou 2018/09/02
14  */
15 public class CustomUserDetails extends User {
16     private static final long serialVersionUID = -4461471539260584625L;
17 
18     private Long userId;
19 
20     private String nickname;
21 
22     private String language;
23 
24     public CustomUserDetails(String username, String password, Long userId, String nickname, String language,
25                              Collection<? extends GrantedAuthority> authorities) {
26         super(username, password, authorities);
27         this.userId = userId;
28         this.nickname = nickname;
29         this.language = language;
30     }
31 
32     public Long getUserId() {
33         return userId;
34     }
35 
36     public void setUserId(Long userId) {
37         this.userId = userId;
38     }
39 
40     public String getNickname() {
41         return nickname;
42     }
43 
44     public void setNickname(String nickname) {
45         this.nickname = nickname;
46     }
47 
48     public String getLanguage() {
49         return language;
50     }
51 
52     public void setLanguage(String language) {
53         this.language = language;
54     }
55 
56     @Override
57     public boolean equals(Object o) {
58         if (this == o) {
59             return true;
60         }
61         if (!(o instanceof CustomUserDetails)) {
62             return false;
63         }
64         if (!super.equals(o)) {
65             return false;
66         }
67 
68         CustomUserDetails that = (CustomUserDetails) o;
69 
70         if (!Objects.equals(userId, that.userId)) {
71             return false;
72         }
73         return false;
74     }
75 
76     @Override
77     public int hashCode() {
78         int result = super.hashCode();
79         result = 31 * result + userId.hashCode();
80         result = 31 * result + nickname.hashCode();
81         result = 31 * result + language.hashCode();
82         return result;
83     }
84 
85 }
View Code

③ CustomUserDetailsService

自定義 UserDetailsService 來從資料庫獲取用戶信息,並將用戶信息封裝到 CustomUserDetails

 1 package com.lyyzoo.sunny.security.core;
 2 
 3 import java.util.ArrayList;
 4 import java.util.Collection;
 5 
 6 import org.springframework.beans.factory.annotation.Autowired;
 7 import org.springframework.security.core.GrantedAuthority;
 8 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 9 import org.springframework.security.core.userdetails.UserDetails;
10 import org.springframework.security.core.userdetails.UserDetailsService;
11 import org.springframework.security.core.userdetails.UsernameNotFoundException;
12 import org.springframework.stereotype.Component;
13 
14 import com.lyyzoo.sunny.core.message.MessageAccessor;
15 import com.lyyzoo.sunny.core.userdetails.CustomUserDetails;
16 import com.lyyzoo.sunny.security.domain.entity.User;
17 import com.lyyzoo.sunny.security.domain.service.UserService;
18 
19 /**
20  * 載入用戶信息實現類
21  *
22  * @author bojiangzhou 2018/03/25
23  */
24 @Component
25 public class CustomUserDetailsService implements UserDetailsService {
26 
27     @Autowired
28     private UserService userService;
29 
30     @Override
31     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
32         User user = userService.getUserByUsername(username);
33         if (user == null) {
34             throw new UsernameNotFoundException(MessageAccessor.getMessage("login.username-or-password.error"));
35         }
36 
37         Collection<GrantedAuthority> authorities = new ArrayList<>();
38         authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
39 
40         return new CustomUserDetails(username, user.getPassword(), user.getId(),
41                 user.getNickname(), user.getLanguage(), authorities);
42     }
43 
44 }
View Code

④ CustomWebAuthenticationDetails

自定義 WebAuthenticationDetails 用於封裝傳入的驗證碼以及緩存的驗證碼,用於後續校驗。

  1 package com.lyyzoo.sunny.security.core;
  2 
  3 import javax.servlet.http.HttpServletRequest;
  4 
  5 import com.lyyzoo.sunny.captcha.CaptchaResult;
  6 import org.springframework.security.web.authentication.WebAuthenticationDetails;
  7 
  8 /**
  9  * 封裝驗證碼
 10  *
 11  * @author bojiangzhou 2018/09/18
 12  */
 13 public class CustomWebAuthenticationDetails extends WebAuthenticationDetails {
 14 
 15     public static final String FIELD_CACHE_CAPTCHA = "cacheCaptcha";
 16 
 17     private String inputCaptcha;
 18     private String cacheCaptcha;
 19 
 20     public CustomWebAuthenticationDetails(HttpServletRequest request) {
 21         super(request);
 22         cacheCaptcha = (String) request.getAttribute(FIELD_CACHE_CAPTCHA);
 23         inputCaptcha = request.getParameter(CaptchaResult.FIELD_CAPTCHA);
 24     }
 25 
 26     public String getInputCaptcha() {
 27         return inputCaptcha;
 28     }
 29 
 30     public String getCacheCaptcha() {
 31         return cacheCaptcha;
 32     }
 33 
 34     @Override
 35     public boolean	   

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

-Advertisement-
Play Games
更多相關文章
  • 前言 學學忘忘 閑來做個筆記 整理下數組常用方法。 Array 數組常用方法 創建數組的基本方式有兩種 1.第一種是使用Array構造函數, var arr = new Array(); 使用構造函數方式的話 var arr = new Array(20),則表示創建了一個lenght值為20的數組 ...
  • 之前一直用 vue 做一些小項目,最近接觸了一個項目是用 react 做前端,雖然本身是做後端開發的,但是前端還是要瞭解一點的。 現在的項目基本上都是前後端分離的,後端就先不提了。前端的框架也是層出不窮,使用最多的就是 angular、vue、react 。angular 是前幾年用的比較多,最近好 ...
  • JavaScript: 知識點回顧篇(十三):DOM -- Console 對象 ...
  • 非常值得推薦的一本書,不僅僅是學習設計模式,也是在推薦你編程能力的鍛煉。簡單易懂的例子,能讓你學到非常多的知識。需要學習的朋友可以通過網盤免費下載pdf版 (先點擊普通下載 再選擇普通用戶就能免費下載了)http://putpan.com/fs/0yiabe3ns2hu69f37/ 《Head Fi ...
  • 介紹 此Refcard提供了Apache Hadoop,這是最流行的軟體框架,可使用簡單的高級編程模型實現大型數據集的分散式存儲和處理。我們將介紹Hadoop最重要的概念,描述其架構,指導您如何開始使用它以及在Hadoop上編寫和執行各種應用程式。 簡而言之,Hadoop是Apache Softwa ...
  • 單表操作: 一、添加 (1)方式一 from mysite.models import * def add(request): book= Book(name="python",price=99,author="python作者") book.sava() return HttpResponse(" ...
  • 前面5章收穫不大,更多的是 中間的部分,如何實際寫出一種高效優美的代碼,如何封裝 類,構建子程式,如何定義好的命名。同重構有很多部分的重疊。 其中感觸最深的一節,軟體工程最首要的核心技術: 控制複雜度!!!控制複雜度!!!需要學習的朋友可以通過網盤免費下載pdf版 (先點擊普通下載 再選擇普通用戶就 ...
  • 查詢銀行賬戶的數量 1.建立一個項目導入jar包(ioc aop dao 連接池 資料庫驅動 ),拷貝容器對應的配置文件到src下 2.在配置文件中開啟組件掃描 3.寫一個DAO介面定義一個查詢方法 4.定義一個JdbcTemplate的成員變數 4.1在類上加@Repository標註 4.2註入 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...