B/S結構系統的會話機制(session)

来源:https://www.cnblogs.com/TheMagicalRainbowSea/archive/2023/04/29/17364555.html
-Advertisement-
Play Games

B/S結構系統的會話機制(session) 每博一文案 你跑得快,22歲有個家,身邊全是贊嘆,你跑得慢,30歲還在路上追求夢想。有的人為了車,房拼了一輩子, 有的人買輛摩托車走遍了大好江山。你想成為怎樣的人,過怎樣的生活,只要你不後悔就行。 並不是所有人都能在早上七點鐘起床的,也別拿一碗飯來衡量一個 ...


B/S結構系統的會話機制(session)

在這裡插入圖片描述

目錄


每博一文案

你跑得快,22歲有個家,身邊全是贊嘆,你跑得慢,30歲還在路上追求夢想。有的人為了車,房拼了一輩子,
有的人買輛摩托車走遍了大好江山。你想成為怎樣的人,過怎樣的生活,只要你不後悔就行。
並不是所有人都能在早上七點鐘起床的,也別拿一碗飯來衡量一個人的胃口的大小。
有的人喜歡狼吞虎咽,有的人喜歡細嚼慢咽,允許別人做,別人允許自己做自己。
一歲有一歲的味道,跟著自己的心就好。不是所有選擇都要做正確的選項的,只要你想,你可以選擇
你喜歡的選項。沿途的花會一直開,以後的路也是,祝你祝我。

1. session 會話機制的概述

在Web應用程式中,我們經常要跟蹤用戶身份。當一個用戶登錄成功後,如果他繼續訪問其他頁面,Web程式如何才能識別出該用戶身份?

因為HTTP協議是一個無狀態協議,即Web應用程式無法區分收到的兩個HTTP請求是否是同一個瀏覽器發出的。為了跟蹤用戶狀態,伺服器可以向瀏覽器分配一個唯一ID,並以Cookie的形式發送到瀏覽器,瀏覽器在後續訪問時總是附帶此Cookie,這樣,伺服器就可以識別用戶身份。

我們把這種基於唯一ID識別用戶身份的機制稱為Session。每個用戶第一次訪問伺服器後,會自動獲得一個Session ID。如果用戶在一段時間內沒有訪問伺服器,那麼Session會自動失效,下次即使帶著上次分配的Session ID訪問,伺服器也認為這是一個新用戶,會分配新的Session ID。

2. 什麼是 session 的會話

會話對應的英語單詞:session

當用戶打開瀏覽器,進行一系列操作,然後最終將瀏覽器關閉,這個整個過程叫做:一次會話。會話在伺服器端也有一個對應的java對象,這個java對象叫做:session

什麼是一次請求:用戶在瀏覽器上點擊了一下,然後到頁面停下來,可以粗略認為是一次請求。請求對應的伺服器端的java對象是:request。 這裡提前透露一點後面的內容: session 對象是用伺服器端生成的,所以這裡是通過 request 請求的方式向伺服器獲取到一個 session 會話對象

  • 一個會話當中包含多次請求(一次會話對應N次請求。)

這裡我們可以列印顯示我們的 session 地址信息

package com.RainbowSea.session;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;
import java.io.PrintWriter;


@WebServlet("/session")
public class TestSessionServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {

        HttpSession session = request.getSession();

        response.setContentType("text/html;charSet=UTF-8");
        PrintWriter out = response.getWriter();

        out.println(" session對象:  " + session);

    }
}

在這裡插入圖片描述

從 session 對象當中存在於: org.apache.catalina.session.StandardSession 的位置。

在Java的servlet 的規範當中,session 對應的類名為: HttpSession(jarkata.servlett.http.HttpSession)
在這裡插入圖片描述

註意:

sessioin 機制屬於 B/S結構的一部分。如果使用php語言開發WEB項目,同樣也是有session這種機制的。session機制實際上是一個規範。然後不同的語言對這種會話機制都有實現。

獲取 sessoin 的對象方法:

// 註意: sessio 是存儲在伺服器端的,所以我們這裡使用的是 request 請求的方式,向伺服器請求獲取到 session 對象 

// 該訪問獲取到 session 對象,如果伺服器端沒有 session 對象會自動創建出  session 對象
 HttpSession session = request.getSession();
        
// 獲取到 session 對象,(參數為 false )表示:如果伺服器當中沒有 session 是不會自動創建的。
HttpSession session1 = request.getSession(false);

3. session 的作用

session對象最主要的作用是:保存會話狀態。(用戶登錄成功了,這是一種登錄成功的狀態,你怎麼把登錄成功的狀態一直保存下來呢?使用session對象可以保留會話狀態。)

那我們為什麼需要session 對象來保存會話狀態呢?

因為HTTP協議是一種無狀態協議。

什麼是無狀態:請求的時候,B和S是連接的,但是請求結束之後,連接就斷了。為什麼要這麼做?HTTP協議為什麼要設計成這樣?因為這樣的無狀態協議,可以降低伺服器的壓力。請求的瞬間是連接的,請求結束之後,連接斷開,這樣伺服器壓力小。

只要B和S斷開了,那麼關閉瀏覽器這個動作,伺服器知道嗎?

因為 HTTP 協議是無狀態的連接的,所以當我們關閉了 瀏覽器的時候,我們的伺服器端是無法接收到瀏覽器被關閉的一個信息的。所以:我們的伺服器自然也就無法知道瀏覽器關閉了。

一個會話對應一個 sessoin 對象,一個 session 對應上一個 ID也就是 (JSESSIONID)

比如:張三打開一個瀏覽器 A,李四打開一個瀏覽器B,訪問伺服器之後,在服務端會生成:

  • 張三專屬的session對象,同時會標記上一個 對應的 ID 信息
  • 李四專屬的session對象 ,同時會標記上一個對應的 ID 信息。
  • 註意了:這兩者之間的 ID信息是不一樣的。

代碼舉例:

package com.RainbowSea.serssion;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/test/session")
public class TestSessionServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {

        // request 和 session 都是在服務端的java對象,都在JVM當中
        // request對象代表一次請求,(一次請求對應一個request對象,再次請求就會對應兩個不同的request對象)
        // session對象代表一次會話,(一次會話對應一個session 對象)
        // 獲取session,如何伺服器當中沒有 session 對象就會自動創建一個,
        HttpSession session = request.getSession();
        // 獲取到伺服器端的 session ,如果沒有不會自動創建 session 對象
        //HttpSession session1 = request.getSession(false);

        //session.setAttribute(); 將數據存儲到 session 會話當中。
        //session.getAttribute() 將數據從 session 會話當中取出




        // 將session 對象響應到瀏覽器端
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.println("會話對象:" + session);



    }
}

結果:

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

什麼表示一個會話

粗略的可以認為一個:當我們打開一個瀏覽器訪問一個A伺服器的時候,如果這個瀏覽器不關閉的情況下,該瀏覽器發送的請求都是向 A伺服器,那麼哪個瀏覽器發送對於這個A伺服器發送的所有的請求都可以理解為是一個 session 會話。

我們也可以再精細一點的再說一下:就是我們在京東網站,A用戶登錄以後,在京東網站當中的,進行查詢商品,購買商品,添加商品購物車,等等,都是屬於該 A用戶專屬的一個 session 的會話,當我們再在京東網站當中,B用戶登錄以後,在京東網站當中的,進行查詢商品,購買商品,添加商品購物車,等等這些是 B用戶請求操作的都是專屬於一個 session 會話。

為什麼不使用request對象保存會話狀態?為什麼不使用ServletContext對象保存會話狀態?

  • request.setAttribute()存數據,request.getAttribute()取數據,ServletContext也有這個方法。request是請求域。ServletContext是應用域。
  • request是一次請求一個對象。
  • ServletContext對象是伺服器啟動的時候創建,伺服器關閉的時候銷毀,這個ServletContext對象只有一個。
  • ServletContext對象的域太大。
  • request請求域(HttpServletRequest)、session會話域(HttpSession)、application應用域(ServletContext)
  • 三個域之間的作用域的大小關係:request (請求域)< session(會話域) < application(應用域) 。

4. session 的實現原理解釋

HttpSession session = request.getSession();

這行代碼很神奇。張三訪問的時候獲取的 session 對象就是張三專屬的。李四訪問的時候獲取的 session 對象就是李四專屬的。

這是如何做到的呢?我們可以舉一個有關於我們實際生活當中的一個例子:

比如: 我們張三,李四都是在同一個大學的班級當中,張三和李四上的都是同一個籃球課(體育課),當他們上課的時候

,他們的體育老師帶來了(一筐籃球)(就是 session ),讓同學們自行挑選好自己的籃球,用於上籃球課。這時候我們的張三認真的挑選到了一個籃球,並且試了試手感,感覺十分的不錯。心裡就有了一點小心思:就是想要,自己每次上籃球課的時候,都可以找到,並拿到這個手感不錯的籃球。怎麼實現這個想法呢?於是,張三同學就在,這個他手中的(手感不錯)籃球上做了一個標記(SESSIONID=xxxxxx)。這個標記只有張三自己知道是乾什麼的,其他同學都不知道。這樣當下次以後的每一節籃球課,張三都可以根據自己所作的這個標記,從眾多籃球當中,找到這個,自己標記到的籃球了。

這個例子當中的: 一筐籃球就可以比作是 : 伺服器的當中的 session 會話對象,而其中的 張三自己在籃球上作的標記就可以比作是: SESSIONID=xxxxxx 是 session 對象的 ID 了。

在這裡插入圖片描述

session 生成的過程:

一個 session 會話對象 對應一個 JSESSIONID=xxxxxx (就是一個標記 session 會話對象的 ID (類似於一個人的身份證信息)是唯一的)。

伺服器當中是有一個類似於 Map 的一個 session 列表。該 session 列表當中存在兩樣東西: key 對應的是 JSESSIONID=xxxxxx (也就是 session 的ID的標記) ,而 value 對應的則是 session 對象。

key (session 的 ID) value ( session 對象)
JSESSIONID=123 session1
JSESSIONID=456 session2

當用戶第一次請求伺服器的時候,伺服器會為該用戶生成一個 session 會話對象,同時伺服器會將該 session 對應 JSESSIONID=123,也就是: sessionID 發送給客戶端。客戶端會接收到伺服器發送過來的 JSESSIONID ,並存儲到 客戶端的緩存(Cookie) 當中。同時需要註意的是: JSESSIONID=xxxxxx 這個是以Cookie的形式保存在瀏覽器的記憶體中的。瀏覽器只要關閉。這個cookie就沒有了。(當然這是預設的情況下,你是可以自定義設置的。關於 Cookie 的內容這裡就不會說明瞭。)

舉例:具體代碼詳細如下:

package com.RainbowSea.session;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;
import java.io.PrintWriter;


@WebServlet("/session")
public class TestSessionServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {

        HttpSession session = request.getSession();

        // 
        response.setContentType("text/html;charSet=UTF-8");
        PrintWriter out = response.getWriter();


        //
        out.println(" session對象:  " + session);

    }
}

如下是我們的瀏覽器(客戶端) 向伺服器 第一次 發送請求(response) 的效果圖:如下:

在這裡插入圖片描述

第二次,我們的瀏覽器(客戶端)向伺服器發送 第二次請求(response) ,因為我們瀏覽器(客戶端)第一次請求的時候,已經將伺服器響應過來的 JSESSIONID 存儲到了,自己客戶端的 Cookie 當中去了。所以,當我們的客戶端再次向上一個伺服器發送請求的時候,這是同屬於同一個會話的,所以我們的客戶端將第一次請求的時候,獲取到的 JSESSIONID 發送給 伺服器,伺服器根據 JSESSIONID 查找session對象。 返回給客戶端,所以兩者之間的 session 對象的地址是一樣的,因為是同屬於同一個會話的。測試效果如下:

在這裡插入圖片描述

註意:我們的瀏覽器是遵循 HTTP 協議的,而 HTTP 協議是 無狀態的,導致我們的伺服器無法知道瀏覽器關閉了,所以我們的 會話銷毀存在一種(延遲銷毀的機制:簡單的說就是,當一個 session 會話,在一定的時間段內沒有,任何的請求發發送了,伺服器就會認為該 sessoin 沒有用了,會自動銷毀該 session 會話對象了),當我們關閉瀏覽器,記憶體消失,Cookie 消失,Cookie 消失了,那存在其中的 JSESSIONID (也就是 sessionID ) 自然也就消失了。而 JSESSIONID 消失了,我們的客戶端也就無法根據該 JSESSIONID 獲取到,訪問到 對應的 session 對象了,當到達一定的時間段後,還是沒有任何客戶端訪問該 Session 會話,伺服器就會自動銷毀該 session 會話對象了。

關閉瀏覽器,重新發送請求,測試效果如下圖所示:

在這裡插入圖片描述

session對象的銷毀:

session 對象是什麼時候銷毀:

瀏覽器關閉的時候,伺服器是不知道的,伺服器無法監測到瀏覽器關閉了(HTTP協議是無狀態協議),所以 session 的銷毀要依靠 session 超時機制,

但也有一種可能,系統提供了 “安全退出”,用戶可以點擊這個按鈕,這樣伺服器就知道你退出了,然後伺服器會自動銷毀 session 對象。

  • 第一種: 手動銷毀
// 銷毀 session 對象的
session.invalidate(); 
  • 第二種:自動銷毀(超時銷毀)

為什麼關閉瀏覽器,會話結束?

關閉瀏覽器之後,瀏覽器中保存的 JSESSIONID (也就是 session 的ID)消失,下次重新打開瀏覽器之後,

瀏覽器緩存中沒有這個 session的ID,自然找不到 伺服器中對應的 session 對象,session 對象找不到,等同於會話結束。(超時銷毀,當一個 session 一段時間內沒有,被訪問了,就會自動被伺服器銷毀,這裡我們的 JSESSIONID 都沒有了,我們就無法找到對應 session 的對象,無法找到 session 對象,就更無法訪問了。)

session 超時銷毀機制的設置的時間點,預設是 Tomcat apache-tomcat-10.0.12\conf\web.xml的 web.xml 配置當中,預設配置為了 30 分鐘

在這裡插入圖片描述

在這裡插入圖片描述

<!-- ==================== Default Session Configuration ================= -->
<!-- You can set the default session timeout (in minutes) for all newly   -->
<!-- created sessions by modifying the value below.                       -->
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-sJQQVVaF-1682775113687)(E:\博客\javaWed博客庫\image-20230424221802452.png)]

當然,這個 session 超時銷毀的時間點,我們也是可以設置的。

我們可以根據自己的需要設置,比如:如果是一個銀行的安全信息的話,可以設置為 1~5 分鐘。如果是一個長久使用的話可以設置為 24 小時,7天等等。根據實際業務需要靈活的設置。

重點:如下是 session 的生成,銷毀的原理圖示:

在這裡插入圖片描述

5. 補充: Cookie禁用了,session還能找到嗎 ?

cookie禁用是什麼意思?伺服器正常發送cookie給瀏覽器,但是瀏覽器不要了。拒收了。並不是伺服器不發了。

如下是: Google Chrome 瀏覽器禁用 Cookie 的設置:
在這裡插入圖片描述

在這裡插入圖片描述

當我們禁用了瀏覽器的 Cookie 設置,再次訪問我們的 Servlet 伺服器的效果如下:

在這裡插入圖片描述

下麵這個是 Firefox火狐瀏覽器的禁用 Cookie 的設置。

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

結論:當瀏覽器禁用了Cookie 緩存功能,伺服器正常發送cookie信息(包括了 JSESSIONID 信息)給瀏覽器,但是瀏覽器不要了。拒收了,並不是伺服器不發了。所以導致的結果就是:客戶端不會發送給伺服器 JSESSIONID信息了,找不到了,每一次請求都會獲取到新的session對象。

問題:cookie禁用了,session機制還能實現嗎?

可以,需要使用 URL 重寫機制。

如下:演示:當我們訪問伺服器時,通過瀏覽器的 檢查功能中的 ——> 網路(NetWork) 當中的第一次請求伺服器,伺服器響應給客戶端的 JSESSIONID 的信息會顯示在其中的:response headers (請求頭當中 )。

在這裡插入圖片描述

將其中的 jsessionid=19D1C99560DCBF84839FA43D58F56E16 拼接到我們訪問的 URL當中,中間使用 ; 分號隔開。如下:需要註意的是,將其中的 JSESSIONID 寫成小寫的: jsessionid

http://127.0.0.1:8080/servlet14/session;jsessionid=F247C2C5CBE489F45383D116224F071B

在這裡插入圖片描述

在這裡插入圖片描述

原理:是雖然我們瀏覽器沒有保存住伺服器響應過來的JSESSIONID信息,但是我們手動將其中的SESSIOND給記住了,並通過地址欄的方式,get的方式發送給了伺服器,伺服器就會幫我們去session列表當中找到該對過的JSESSIONID的
session對象,而不是新建esssion對象了。

URL重寫機制會提高開發者的成本。開發人員在編寫任何請求路徑的時候,後面都要添加一個sessionid,給開髮帶來了很大的難度,很大的成本。所以大部分的網站都是這樣設計的:你要是禁用cookie,你就別用了。

怎麼理解這個: 你要是禁用了 Cookie 緩存機制,你就別用了。就是說,如果你把 Cookie 禁用了一些網站你可能打不開來,或者說無法顯示全部內容信息。當你開始這個設置 禁用Cookie 都會有一些提示的信息給到你的。比如:

在這裡插入圖片描述

。如下當我們把 Firefox火狐瀏覽器的禁用 Cookie 打開,訪問

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

6. 總結一下到目前位置我們所瞭解的域對象:

  • request(對應的類名:HttpServletRequest)請求域(請求級別的)
  • session(對應的類名:HttpSession)會話域(用戶級別的)
  • application(對應的類名:ServletContext)應用域(項目級別的,所有用戶共用的。)
  • 這三個域對象的大小關係:request < session < application
  • 他們三個域對象都有以下三個公共的方法:
    • setAttribute(向域當中綁定數據)
    • getAttribute(從域當中獲取數據)
    • removeAttribute(刪除域當中的數據)
  • 使用原則:儘量使用小的域。

7. oa 項目的優化體驗:使用上 session 會話機制:

閱讀如下內容,大家可以先移步至: Servlet註解的使用,簡化配置 以及,使用模板方法設計模式優化oa項目_ChinaRainbowSea的博客-CSDN博客看看有助於閱讀理解。

session掌握之後,我們怎麼解決oa項目中的登錄問題:就是我們的登錄頁面是一個擺設,當用戶沒有登錄的情況下,可以直接通過在地址欄上輸入 URL 可以訪問到對應的資源信息。

這裡我們可以使用: session 會話機制,讓登錄起作用:就是如果用戶直接通過在地址欄上輸入 URL 可以訪問到對應的資源信息的時候,判斷用戶是否登錄過,如果登錄過,則可以直接訪問。如果沒有登錄過就跳轉到登錄頁面,進行一個正確的登錄成功的操作,才可以訪問。同時設置一個安全退出系統,銷毀 session 對象的按鈕設置。

登錄成功之後,可以將用戶的登錄信息存儲到session當中。也就是說session中如果有用戶的信息就代表用戶登錄成功了。session中沒有用戶信息,表示用戶沒有登錄過。則跳轉到登錄頁面。

優化源碼如下:

首先是登錄頁面的優化:當用戶登錄成功,將用戶的登錄信息存儲到session當中(這裡我們存儲到用戶的用戶名信息。)

核心優化代碼:

// 登錄成功與否
if (success) {

   // 成功,跳轉到用戶列表頁面
   // 這裡使用重定向(沒有資源的共用):重定向需要加/項目名 +

   // 獲取session 對象(這裡的要求是: 必須獲取到 session ,沒有session 也要新建一個 session 對象)
   // 註意:我們下麵的這個會話是不能刪除的,因為上面我們雖然通過 welcome Servlet 進行了一個會話
   // 但是 welcome 當中是當我們cookie 當中存在並且用戶名和密碼正確的時候才會進行一個 session 的
   HttpSession session = request.getSession();  // 伺服器當中沒有 session 會話域自動創建
   session.setAttribute("username", username);  // 將用戶名存儲到 session 會話域當中


   response.sendRedirect(request.getContextPath() + "/dept/list");
   } else {
     // 失敗,跳轉到失敗頁面
     response.sendRedirect(request.getContextPath() + "/error.jsp");
   }

全部的代碼:

package com.RainbowSea.servlet;

import com.RainbowSea.DBUtil.DBUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;


@WebServlet({"/user/login", "/user/exit"})
public class UserServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {

        // 獲取到瀏覽器地址欄上的URL路徑
        String servletPath = request.getServletPath();

        if ("/user/login".equals(servletPath)) {
            doLogin(request, response);
        } else if ("/user/exit".equals(servletPath)) {
            doExit(request, response);
        }


    }

    private void doExit(HttpServletRequest request, HttpServletResponse response) throws IOException {
    }

    protected void doLogin(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {

        // 一個用戶登錄驗證的方式:驗證用戶名和密碼是否正確
        // 獲取用戶名和密碼
        // 前端提交是數據是:username=111&password=fads
        // 註意:post 提交的數據是在請求體當中,而get提交的數據是在請求行當中

        boolean success = false;  // 標識登錄成功

        String username = request.getParameter("username");
        String password = request.getParameter("password");

        String exempt = request.getParameter("exempt");

        // 連接資料庫驗證用戶名和密碼
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            // 1. 獲取連接,註冊驅動
            connection = DBUtil.getConnection();

            // 2. 獲取操作數據對象,預編譯sql語句, ? 占位符不要加,“”,'' 單雙引號,成了字元串了,無法識別成占位符了。
            String sql = "select username,password from t_user where username = ? and password = ?";
            preparedStatement = connection.prepareStatement(sql);

            // 3. 填充占位符,真正執行sql語句
            preparedStatement.setString(1, username);
            preparedStatement.setString(2, password);

            resultSet = preparedStatement.executeQuery();

            // 4. 處理查詢結果集
            // 只有一條結果集
            if (resultSet.next()) {
                // 登錄成功
                success = true;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            // 5. 關閉資源,最後使用的最先關閉,
            DBUtil.close(connection, preparedStatement, resultSet);
        }

        // 登錄成功與否
        if (success) {
            
            // 成功,跳轉到用戶列表頁面
            // 這裡使用重定向(沒有資源的共用):重定向需要加/項目名 +

            // 獲取session 對象(這裡的要求是: 必須獲取到 session ,沒有session 也要新建一個 session 對象)
            // 註意:我們下麵的這個會話是不能刪除的,因為上面我們雖然通過 welcome Servlet 進行了一個會話
            // 但是 welcome 當中是當我們cookie 當中存在並且用戶名和密碼正確的時候才會進行一個 session 的
            HttpSession session = request.getSession();  // 伺服器當中沒有 session 會話域自動創建
            session.setAttribute("username", username);  // 將用戶名存儲到 session 會話域當中


            response.sendRedirect(request.getContextPath() + "/dept/list");
        } else {
            // 失敗,跳轉到失敗頁面
            response.sendRedirect(request.getContextPath() + "/error.jsp");
        }


    }
}

其次是當用戶想要直接通過 URL訪問的時候,判斷用戶是否登錄成功過,登錄成功過可以訪問,沒有登錄成功過無法訪問:

思路是:

我們通過 session 會話機制,判斷用戶是否登錄過,如果用戶沒有登錄就想要訪問 到其信息,不可以,因為我們這裡判斷了一次是否登錄過,只有登錄入過了,才會將中登錄到用戶名為 “username” 的信息存儲到 session 會話當中,如果沒有的話是查詢不到的,返回的是 null。需要註意的一點就是,我們的jsp 當中的內置對象,是會自動創建一個 session 會話對象的(所以就會導致,就算我們沒有登錄成功 ,session 對象也是不為空的,因為JSP創建了 session 對象,我們可以通過JSP 指令禁止 JSP 生成 session 內置對象 <%@page session = false %>,需要所有會被訪問,生成的 Jsp 文件都需要設置該指令。這裡 所謂的禁用了就是,對應的訪問生成的 xxx_jsp.java) 當中不會翻譯生成其中內置的 session 對象),但是因為這裡我們進行了一個 雙重的判斷機制。

if(session != null && session.getAttribute("username") != null)
// 雙重的判斷,一個是 session 會話域要存在,其次是 會話域當中存儲了名為 "username" 的信息,可以用戶登錄的信息可以從 session 找到,如果找不到 ,返回 null ,找到不為 null 。這樣就解決了 JSP 內置session 對象的沒有登錄 session 不為 null 的影響了。

需要註意一點的就是:這裡我們要使用HttpSession session = request.getSession(false)

HttpSession session = request.getSession(false);  // 獲取到伺服器當中的session ,沒有不會創建的,
// session 是用戶登錄成功才創建的,其他情況不要創建 session 會話對象。

核心代碼:


// 可以使用模糊查詢 @WebServlet("/dept/*")
@WebServlet({"/dept/list", "/dept/detail", "/dept/delete", "/dept/save", "/dept/modify"})
public class DeptServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {

        String servletPath = request.getServletPath();  // 獲取到瀏覽器當中的uri

        // 獲取session 這個 session 是不不需要新建的
        // 只是獲取當前session ,獲取不到這返回null,
        HttpSession session = request.getSession(false);  // 獲取到伺服器當中的session ,沒有不會創建的


        /**
         * 說明這裡我們通過 session 會話機制,判斷用戶是否登錄過,如果用戶沒有登錄就想要訪問
         * 到其信息,不可以,因為我們這裡判斷了一次是否登錄過,只有登錄入過了,才會將中登錄到
         * 用戶名為 “username” 的信息存儲到 session 會話當中,如果沒有的話是查詢不到的,返回的是 null
         * 需要註意的一點就是,我們的jsp 當中的內置對象,是會自動創建一個 session 會話對象的,但是
         * 因為這裡我們進行了一個 雙重的判斷機制。註意:需要先將對應的 xx_jsp.java 生成才行。同時
         * 使用 <%@page session = false %> 指令的話,需要所有會被訪問,生成的 Jsp 文件都需要設置。
         *
         *   jakarta.servlet.http.HttpSession session = null;
         *   session = pageContext.getSession();
         */
        if(session != null && session.getAttribute("username") != null) {
            // 雙重的判斷,一個是 session 會話域要存在,其次是 會話域當中存儲了名為 "username" 的信息
            if ("/dept/list".equals(servletPath)) {
                doList(request, response);
            } else if ("/dept/detail".equals(servletPath)) {
                doDetail(request, response);
            } else if ("/dept/delete".equals(servletPath)) {
                doElete(request,response);
            } else if("/dept/save".equals(servletPath)) {
                doSave(request,response);
            } else if("/dept/modify".equals(servletPath)) {
                doModify(request,response);
            }
        } else {
            response.sendRedirect(request.getContextPath());  // 訪問的web 站點的根即可,自動找到的是名為 index.jsp
        }

    }
}

最後就是:用戶點擊安全退出系統,銷毀 session 對象的實現了。

當我們點擊 安全退出,手動將 session 會話對象銷毀了。就需要重新登錄了。只有重新登錄,建立新的登錄成功的 session 會話信息,才能再次通過URL訪問。

核心代碼如下:

session.invalidate();  // 銷毀 session 對象。
/**
* 用戶手動點擊安全退出,銷毀 session 對象
* @param request
 * @param response
* @throws IOException
*/
private void doExit(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 獲取到客戶端發送過來的 sessoin
        HttpSession session = request.getSession();

        if (session != null) {
            // 手動銷毀 session 對象
            // 註意:會話銷毀的了,自然需要重寫登錄了,沒有登錄過,無法進行一個路徑的訪問的
            session.invalidate();
            
            // 跳轉會登錄的頁面
            response.sendRedirect(request.getContextPath());  // 項目名路徑預設就是訪問的index.html 的歡迎頁面
        }
    }

全部具體代碼:

package com.RainbowSea.servlet;

import com.RainbowSea.DBUtil.DBUtil;
import com.RainbowSea.bean.Dept;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

// 可以使用模糊查詢 @WebServlet("/dept/*")
@WebServlet({"/dept/list", "/dept/detail", "/dept/delete", "/dept/save", "/dept/modify"})
public class DeptServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {

        String servletPath = request.getServletPath();  // 獲取到瀏覽器當中的uri

        // 獲取session 這個 session 是不不需要新建的
        // 只是獲取當前session ,獲取不到這返回null,
        HttpSession session = request.getSession(false);  // 獲取到伺服器當中的session ,沒有不會創建的


        /**
         * 說明這裡我們通過 session 會話機制,判斷用戶是否登錄過,如果用戶沒有登錄就想要訪問
         * 到其信息,不可以,因為我們這裡判斷了一次是否登錄過,只有登錄入過了,才會將中登錄到
         * 用戶名為 “username” 的信息存儲到 session 會話當中,如果沒有的話是查詢不到的,返回的是 null
         * 需要註意的一點就是,我們的jsp 當中的內置對象,是會自動創建一個 session 會話對象的,但是
         * 因為這裡我們進行了一個 雙重的判斷機制。註意:需要先將對應的 xx_jsp.java 生成才行。同時
         * 使用 <%@page session = false %> 指令的話,需要所有會被訪問,生成的 Jsp 文件都需要設置。
         *
         *   jakarta.servlet.http.HttpSession session = null;
         *   session = pageContext.getSession();
         */
        if(session != null && session.getAttribute("username") != null) {
            // 雙重的判斷,一個是 session 會話域要存在,其次是 會話域當中存儲了名為 "username" 的信息
            if ("/dept/list".equals(servletPath)) {
                doList(request, response);
            } else if ("/dept/detail".equals(servletPath)) {
                doDetail(request, response);
            } else if ("/dept/delete".equals(servletPath)) {
                doElete(request,response);
            } else if("/dept/save".equals(servletPath)) {
                doSave(request,response);
            } else if("/dept/modify".equals(servletPath)) {
                doModify(request,response);
            }
        } else {
            response.sendRedirect(request.getContextPath());  // 訪問的web 站點的根即可,自動找到的是名為 index.jsp
        }

    }


    /**
     * 修改部門信息
     *
     * @param request
     * @param response
     */
    private void doModify(HttpServletRequest request, HttpServletResponse response) throws IOException {
        request.setCharacterEncoding("UTF-8");  // 設置獲取的的信息的編碼集
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        // 影響資料庫的行數
        int count = 0;


        String deptno = request.getParameter("deptno");
        String dname = request.getParameter("dname");
        String loc = request.getParameter("loc");


        try {
            // 1. 註冊驅動,連接資料庫
            connection = DBUtil.getConnection();

            // 2. 獲取到操作資料庫的對象,預編譯sql語句,sql測試
            String sql = "update dept set dname = ?,loc = ? where depton = ?";
            preparedStatement = connection.prepareStatement(sql);

            // 3. 填充占位符,真正執行sql語句
            // 從下標 1開始
            preparedStatement.setString(1, dname);
            preparedStatement.setString(2, loc);
            preparedStatement.setString(3, deptno);

            count = preparedStatement.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            // 4. 釋放資源,最後使用的優先被釋放
            DBUtil.close(connection, preparedStatement, null);
        }

        if (count == 1) {
            // 更新成功
            // 跳轉到部門列表頁面(部門列表錶面是通過java程式動態生成的,所以還需要再次執行另一個Servlet)
            // 轉發是伺服器內部的操作,“/” 不要加項目名
            // request.getRequestDispatcher("/dept/list/").forward(request,response);

            // 優化使用重定向,自發前端(需要指明項目名)
            response.sendRedirect(request.getContextPath() + "/dept/list");

        }
    }


    /**
     * 保存部門信息
     *
     * @param request
     * @param response
     */
    private void doSave(HttpServletRequest request, HttpServletResponse response) throws IOException {

        request.setCharacterEncoding("UTF-8");

        // 獲取到前端的數據,建議 name 使用複製
        String deptno = request.getParameter("deptno");
        String dname = request.getParameter("dname");
        String loc = request.getParameter("loc");

        // 連接資料庫,添加數據
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        // 影響資料庫的行數
        int count = 0;

        try {
            // 1. 註冊驅動,連接資料庫
            connection = DBUtil.getConnection();

            // 2. 獲取操作資料庫對象,預編譯sql語句,Sql測試
            String sql = "insert into dept(depton,dname,loc) values(?,?,?)";
            preparedStatement = connection.prepareStatement(sql);

            // 3. 填充占位符, 真正執行sql語句,
            // 註意: 占位符的填充是從 1 開始的,基本上資料庫相關的起始下標索引都是從 1下標開始的
            preparedStatement.setString(1, deptno);
            preparedStatement.setString(2, dname);
            preparedStatement.setString(3, loc);

            // 返回影響資料庫的行數
            count = preparedStatement.executeUpdate();

            // 5.釋放資源
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(connection, preparedStatement, null);
        }

        // 保存成功,返回部門列表頁面
        if (count == 1) {
            // 這裡應該使用,重定向
            // 這裡用的轉發,是伺服器內部的,不要加項目名
            //request.getRequestDispatcher("/dept/list/").forward(request, response);

            // 重定向
            response.sendRedirect(request.getContextPath() + "/dept/list");
        }
    }


    /**
     * 通過部門刪除部門
     *
     * @param request
     * @param response
     */
    private void doElete(HttpServletRequest request, HttpServletResponse response) throws IOException {

        request.setCharacterEncoding("UTF-8");  // 設置獲取的的信息的編碼集
        // 獲取到發送數據
        String deptno = request.getParameter("deptno");

         /*
        根據部門編號刪除信息,
        刪除成功,跳轉回原來的部門列表頁面
        刪除失敗,跳轉刪除失敗的頁面
         */

        Connection connection = null;
        PreparedStatement preparedStatement = null;

        // 記錄刪除資料庫的行數
        int count = 0;


        // 連接資料庫進行刪除操作

        try {
            // 1.註冊驅動,連接資料庫
            connection = DBUtil.getConnection();

            // 開啟事務(取消自動提交機制),實現可回滾
            connection.setAutoCommit(false);

            // 2. 預編譯sql語句,sql測試
            String sql = "delete from dept where depton = ?"; // ? 占位符
            preparedStatement = connection.prepareStatement(sql);

            // 3. 填充占位符,真正的執行sql語句
            preparedStatement.setString(1, deptno);
            // 返回影響資料庫的行數
            count = preparedStatement.executeUpdate();
            connection.commit();  // 手動提交數據
        } catch (SQLException e) {
            // 遇到異常回滾
            if (connection != null) {
                try {
                    // 事務的回滾
                    connection.rollback();
                } catch (SQLException ex) {
                    throw new RuntimeException(ex);
                }
            }
            throw new RuntimeException(e);
        } finally {
            // 4. 釋放資源
            // 因為這裡是刪除數據,沒有查詢操作,所以 沒有 ResultSet 可以傳null
            DBUtil.close(connection, preparedStatement, null);
        }

        if (count == 1) {
            // 刪除成功
            // 仍然跳轉到部門列表頁面
            // 部門列表頁面的顯示需要執行另外一個Servlet,怎麼辦,可以使用跳轉,不過這裡最後是使用重定向
            // 註意:轉發是在伺服器間的,所以不要加“項目名” 而是 / + web.xml 映射的路徑即可
            //request.getRequestDispatcher("/dept/list/").forward(request,response);

            // 優化:使用重定向機制 註意: 重定向是自發到前端的地址欄上的,前端所以需要指明項目名
            // 註意: request.getContextPath() 返回的根路徑是,包含了 "/" 的
            response.sendRedirect(request.getContextPath() + "/dept/list");
        }
    }


    /**
     * 通過部門編號,查詢部門的詳情
     *
     * @param request
     * @param response
     */
    private void doDetail(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        request.setCharacterEncoding("UTF-8");  // 設置獲取的的信息的編碼集

        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        // 獲取到部門編號
        String dno = request.getParameter("dno");
        Dept dept = new Dept();

        // 獲取到部門編號,獲取部門信息,將部門信息收集好,然後跳轉到JSP做頁面展示


        try {
            // 2. 連接資料庫,根據部門編號查詢資料庫
            // 1.註冊驅動,連接資料庫
            connection = DBUtil.getConnection();

            // 2. 預編譯SQL語句,sql要測試
            String sql = "select dname,loc from dept where depton = ?";  // ? 占位符
            preparedStatement = connection.prepareStatement(sql);

            // 3. 填充占位符,真正執行sql語句
            preparedStatement.setString(1, dno);
            resultSet = preparedStatement.executeQuery();

            // 4. 處理查詢結果集
            while (resultSet.next()) {
                String dname = resultSet.getString("dname");
                String loc = resultSet.getString("loc");

                // 封裝對象(建議使用咖啡豆,因為只有一個對象)
                dept.setDeptno(dno);
                dept.setDname(dname);
                dept.setLoc(loc);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            // 5. 釋放資源
            DBUtil.close(connection, preparedStatement, resultSet);
        }

        // 這個咖啡豆只有一個,所以不需要袋子,只需要將這個咖啡豆放到request請求域當中,
        // 用於對應的 jsp顯示
        request.setAttribute("dept", dept);
        //String sign = request.getParameter("f");

        /*if("m".equals(sign)) {

            // 轉發:多個請求為一個請求(地址欄不會發生改變)
            // 註意: 該路徑預設是從 web 開始找的 / 表示 web
            // 轉發到修改頁面
            request.getRequestDispatcher("/edit.jsp").forward(request,response);
        } else if("d".equals(sign)) {
            // 跳轉到詳情頁面
            request.getRequestDispatcher("/detail.jsp").forward(request,response);
        }*/

        // 或者優化
        // 註意 無論是轉發還是重定向都是從 “/” 開始的
        // request.getParameter()拿到的是 f=edit,還是f=detail 就是跳轉到的哪個頁面
        //<a href="<%=request.getContextPath()%>/dept/detail?f=edit&dno=<%=dept.getDeptno()%>">修改</a>
        //<a href="<%=request.getContextPath()%>/dept/detail?f=detail&dno=<%=dept.getDeptno()%>">詳情</a>
        String forward = "/" + request.getParameter("f") + ".jsp";
        request.getRequestDispatcher(forward).forward(request, response);
    }


    /**
     * 連接資料庫,查詢所有的部門信息,將部門信息收集好,然後跳轉到JSP頁面展示
     *
     * @param request
     * @param response
     */
    private void doList(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");  // 設置獲取的的信息的編碼集
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        // 創建一個集合List 存儲查詢到的信息
        List<Dept> depts = new ArrayList<Dept>();


        try {
            // 連接資料庫,查詢所有部門:
            // 1. 註冊驅動,獲取連接
            connection = DBUtil.getConnection();
            // 2. 獲取操作資料庫對象,預編譯sql語句
            String sql = "select depton as det,dname,loc from dept"; // 在mysql中測試一下是否正確
            preparedStatement = connection.prepareStatement(sql);

            // 3. 執行sql語句
            resultSet = preparedStatement.executeQuery();

            // 4. 處理查詢結果集
            while (resultSet.next()) {
                String det = resultSet.getString("det");  // 有別名要使用別名
                String dname = resultSet.getString("dname");
                String loc = resultSet.getString("loc");

                Dept dept = new Dept(det, dname, loc);

                // 將部門對象放到List集合當中
                depts.add(dept);

            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {

            // 5. 關閉資源
            DBUtil.close(connection, preparedStatement, resultSet);
        }


        // 查詢到數據,將數據提交給 list.jsp 顯示數據
        // 將集合存儲的數據放到請求域當中,用於其他Servlet 使用 jsp 也是Servelt
        request.setAttribute("depList", depts);

        // 轉發(註意不要重定向),重定向無法共用 request 請求域當中的數據
        // 轉發路徑,/ 預設是從 web 目錄開始找的
        request.getRequestDispatcher("/list.jsp").forward(request, response);
    }
}

用戶界面的優化:顯示 登錄的用戶名:(該用戶名信息,從 存儲到 session 會話對象當中,獲取到的。)

核心代碼如下:

需要註意的點就是:這裡我們使用的是 JSP 內置的 session 對象,所以在這個 JSP頁面當,你不可以把 session 禁用了。

不要設置這個禁用 session 的指令: <%@page session = false %>

在這裡插入圖片描述

優化演示:

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

8. 總結:

  1. session 會話用戶場景:在Web應用程式中,我們經常要跟蹤用戶身份。當一個用戶登錄成功後,如果他繼續訪問其他頁面,Web程式如何才能識別出該用戶身份?
  2. session對象最主要的作用是:保存會話狀態。
  3. 為什麼要保存會話狀態:因為HTTP協議是一種無狀態協議。
    • 無狀態:
      • 優點:這樣伺服器壓力小。
      • 缺點:伺服器無法知道客戶端的狀態(是關閉的狀態,還是開啟的狀態)
  4. 一個 session 會話當中包含多次請求(一次會話對應N次請求。)
  5. session 對象是用伺服器端生成的,所以這裡是通過 request 請求的方式向伺服器獲取到一個 session 會話對象
  6. session 的生成,銷毀,傳遞的原理機制:

在這裡插入圖片描述

  1. 簡單的來說吧 ,session 就是一個標記,通過標記 JSESSIONID 獲取到同一個 session 對象,保證你對應的操作是同一個用戶。
  2. Cookie禁用了,session還能找到嗎 ? 可以,使用 URL重寫機制。
  3. 實現用戶登錄,通過 session 會話機制(保存用戶登錄信息),實現用戶登錄成功,可以通過 URL 直接訪問資源,沒有登錄/登錄失敗,則無法直接通過 URL 訪問資源。

9. 最後:

限於自身水平,其中存在的錯誤,希望大家給予指教,韓信點兵——多多益善,謝謝大家,江湖再見,後悔有期

在這裡插入圖片描述


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

-Advertisement-
Play Games
更多相關文章
  • 書寫識別,網上的大佬們都有輸出。 書寫識別存在的2個問題: 直接拿官網的案例(將 Windows Ink 筆劃識別為文本和形狀 - Windows apps | Microsoft Learn),會發現輸出準確度不高。 另外如果書寫過快,片語識別也是個問題,畢竟無法準確分割字之間的筆跡。 我結合之前 ...
  • .Net Core在調用其他服務時,調用通常使用HttpClient,而HttpClient預設使用HTTP/1.1 。 配置 HttpClient 以使用 HTTP/2 h2 連接 自 .NET Core 3.0 發佈以來, .NET 開發人員可以使用 HttpClient 啟用 HTTP/2 。 ...
  • 本文為大家介紹使用 .NET Core部署到Linux伺服器的方法,通過本文你將瞭解到Linux在虛擬機下的安裝、Xshell,Xftp的使用方法、git在linux下的交互使用以及.net core在linux下的發佈與運行全過程,本文皆在總結了一些經驗與筆記在部署過程中遇到的一些問題,同時分享給... ...
  • 最近在開發用的台式機上啟用了 Windows 的 Hyper-V 虛擬化功能,利用虛擬機運行了一臺 Windows Server 2022 和 一臺 Ubuntu Server,為了方便別的機器直接訪問這兩台虛擬機,所以網路採用了外部網路橋接的模式,讓虛擬機和物理機保持在了同一網段。 為了實現在這一 ...
  • ==資料庫==1、創建資料庫create database [IF NOT EXISTS] 資料庫名; 2、刪除資料庫drop database [IF EXISTS] 資料庫名; 3、切換資料庫select database(); 4、查詢資料庫show databases; —————————— ...
  • 1、四層結構 viewer --> datasources(DataSourceCollection類型) --> datasource --> entities(EntityCollection類型) --> entity 需要學習的方向是:只需要註意每個層與層之間的關係和entity實例如何創建 ...
  • 簡介 模板方法模式(Template Method Pattern)也叫模板模式,是一種行為型模式。它定義了一個抽象公開類,包含基本的演算法骨架,而將一些步驟延遲到子類中,模板方法使得子類可以不改變演算法的結構,只是重定義該演算法的某些特定步驟。不同的子類以不同的方式實現這些抽象方法,從而對剩餘的邏輯有不 ...
  • 轉載請註明 來源:http://www.eword.name/ Author:eword Email:[email protected] 安裝Python 一、查詢是否安裝了Python及安裝路徑 #查看當前Python版本 python --version Python 2.7.16 #查看當前所有 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...