java 解密后為空_java RSA加密解密

該工具類中用到了BASE64,需要借助第三方類庫:javabase64-1.3.1.jar注意:RSA加密明文最大長度117字節,解密要求密文最大長度為128字節,所以在加密和解密的過程中需要分塊進行。

RSA加密對明文的長度是有限制的,如果加密數據過大會拋出如下異常:Exception?in?thread?"main"?javax.crypto.IllegalBlockSizeException:?Data?must?not?be?longer?than?117?bytes

at?com.sun.crypto.provider.RSACipher.a(DashoA13*..)

at?com.sun.crypto.provider.RSACipher.engineDoFinal(DashoA13*..)

at?javax.crypto.Cipher.doFinal(DashoA13*..)

RSAUtils.javapackage?security;

import?java.io.ByteArrayOutputStream;

import?java.security.Key;

import?java.security.KeyFactory;

import?java.security.KeyPair;

import?java.security.KeyPairGenerator;

import?java.security.PrivateKey;

import?java.security.PublicKey;

import?java.security.Signature;

import?java.security.interfaces.RSAPrivateKey;

import?java.security.interfaces.RSAPublicKey;

import?java.security.spec.PKCS8EncodedKeySpec;

import?java.security.spec.X509EncodedKeySpec;

import?java.util.HashMap;

import?java.util.Map;

import?javax.crypto.Cipher;

/**

*?

*?RSA公鑰/私鑰/簽名工具包

*?

*?

*?羅納德·李維斯特(Ron?[R]ivest)、阿迪·薩莫爾(Adi?[S]hamir)和倫納德·阿德曼(Leonard?[A]dleman)

*?

*?

*?字符串格式的密鑰在未在特殊說明情況下都為BASE64編碼格式

*?由于非對稱加密速度極其緩慢,一般文件不使用它來加密而是使用對稱加密,

*?非對稱加密算法可以用來對對稱加密的密鑰加密,這樣保證密鑰的安全也就保證了數據的安全

*?

*

*?@author?IceWee

*?@date?2012-4-26

*?@version?1.0

*/

public?class?RSAUtils?{

/**

*?加密算法RSA

*/

public?static?final?String?KEY_ALGORITHM?=?"RSA";

/**

*?簽名算法

*/

public?static?final?String?SIGNATURE_ALGORITHM?=?"MD5withRSA";

/**

*?獲取公鑰的key

*/

private?static?final?String?PUBLIC_KEY?=?"RSAPublicKey";

/**

*?獲取私鑰的key

*/

private?static?final?String?PRIVATE_KEY?=?"RSAPrivateKey";

/**

*?RSA最大加密明文大小

*/

private?static?final?int?MAX_ENCRYPT_BLOCK?=?117;

/**

*?RSA最大解密密文大小

*/

private?static?final?int?MAX_DECRYPT_BLOCK?=?128;

/**

*?

*?生成密鑰對(公鑰和私鑰)

*?

*

*?@return

*?@throws?Exception

*/

public?static?Map?genKeyPair()?throws?Exception?{

KeyPairGenerator?keyPairGen?=?KeyPairGenerator.getInstance(KEY_ALGORITHM);

keyPairGen.initialize(1024);

KeyPair?keyPair?=?keyPairGen.generateKeyPair();

RSAPublicKey?publicKey?=?(RSAPublicKey)?keyPair.getPublic();

RSAPrivateKey?privateKey?=?(RSAPrivateKey)?keyPair.getPrivate();

Map?keyMap?=?new?HashMap(2);

keyMap.put(PUBLIC_KEY,?publicKey);

keyMap.put(PRIVATE_KEY,?privateKey);

return?keyMap;

}

/**

*?

*?用私鑰對信息生成數字簽名

*?

*

*?@param?data?已加密數據

*?@param?privateKey?私鑰(BASE64編碼)

*

*?@return

*?@throws?Exception

*/

public?static?String?sign(byte[]?data,?String?privateKey)?throws?Exception?{

byte[]?keyBytes?=?Base64Utils.decode(privateKey);

PKCS8EncodedKeySpec?pkcs8KeySpec?=?new?PKCS8EncodedKeySpec(keyBytes);

KeyFactory?keyFactory?=?KeyFactory.getInstance(KEY_ALGORITHM);

PrivateKey?privateK?=?keyFactory.generatePrivate(pkcs8KeySpec);

Signature?signature?=?Signature.getInstance(SIGNATURE_ALGORITHM);

signature.initSign(privateK);

signature.update(data);

return?Base64Utils.encode(signature.sign());

}

/**

*?

*?校驗數字簽名

*?

*

*?@param?data?已加密數據

*?@param?publicKey?公鑰(BASE64編碼)

*?@param?sign?數字簽名

*

*?@return

*?@throws?Exception

*

*/

public?static?boolean?verify(byte[]?data,?String?publicKey,?String?sign)

throws?Exception?{

byte[]?keyBytes?=?Base64Utils.decode(publicKey);

X509EncodedKeySpec?keySpec?=?new?X509EncodedKeySpec(keyBytes);

KeyFactory?keyFactory?=?KeyFactory.getInstance(KEY_ALGORITHM);

PublicKey?publicK?=?keyFactory.generatePublic(keySpec);

Signature?signature?=?Signature.getInstance(SIGNATURE_ALGORITHM);

signature.initVerify(publicK);

signature.update(data);

return?signature.verify(Base64Utils.decode(sign));

}

/**

*?

*?私鑰解密

*?

*

*?@param?encryptedData?已加密數據

*?@param?privateKey?私鑰(BASE64編碼)

*?@return

*?@throws?Exception

*/

public?static?byte[]?decryptByPrivateKey(byte[]?encryptedData,?String?privateKey)

throws?Exception?{

byte[]?keyBytes?=?Base64Utils.decode(privateKey);

PKCS8EncodedKeySpec?pkcs8KeySpec?=?new?PKCS8EncodedKeySpec(keyBytes);

KeyFactory?keyFactory?=?KeyFactory.getInstance(KEY_ALGORITHM);

Key?privateK?=?keyFactory.generatePrivate(pkcs8KeySpec);

Cipher?cipher?=?Cipher.getInstance(keyFactory.getAlgorithm());

cipher.init(Cipher.DECRYPT_MODE,?privateK);

int?inputLen?=?encryptedData.length;

ByteArrayOutputStream?out?=?new?ByteArrayOutputStream();

int?offSet?=?0;

byte[]?cache;

int?i?=?0;

//?對數據分段解密

while?(inputLen?-?offSet?>?0)?{

if?(inputLen?-?offSet?>?MAX_DECRYPT_BLOCK)?{

cache?=?cipher.doFinal(encryptedData,?offSet,?MAX_DECRYPT_BLOCK);

}?else?{

cache?=?cipher.doFinal(encryptedData,?offSet,?inputLen?-?offSet);

}

out.write(cache,?0,?cache.length);

i++;

offSet?=?i?*?MAX_DECRYPT_BLOCK;

}

byte[]?decryptedData?=?out.toByteArray();

out.close();

return?decryptedData;

}

/**

*?

*?公鑰解密

*?

*

*?@param?encryptedData?已加密數據

*?@param?publicKey?公鑰(BASE64編碼)

*?@return

*?@throws?Exception

*/

public?static?byte[]?decryptByPublicKey(byte[]?encryptedData,?String?publicKey)

throws?Exception?{

byte[]?keyBytes?=?Base64Utils.decode(publicKey);

X509EncodedKeySpec?x509KeySpec?=?new?X509EncodedKeySpec(keyBytes);

KeyFactory?keyFactory?=?KeyFactory.getInstance(KEY_ALGORITHM);

Key?publicK?=?keyFactory.generatePublic(x509KeySpec);

Cipher?cipher?=?Cipher.getInstance(keyFactory.getAlgorithm());

cipher.init(Cipher.DECRYPT_MODE,?publicK);

int?inputLen?=?encryptedData.length;

ByteArrayOutputStream?out?=?new?ByteArrayOutputStream();

int?offSet?=?0;

byte[]?cache;

int?i?=?0;

//?對數據分段解密

while?(inputLen?-?offSet?>?0)?{

if?(inputLen?-?offSet?>?MAX_DECRYPT_BLOCK)?{

cache?=?cipher.doFinal(encryptedData,?offSet,?MAX_DECRYPT_BLOCK);

}?else?{

cache?=?cipher.doFinal(encryptedData,?offSet,?inputLen?-?offSet);

}

out.write(cache,?0,?cache.length);

i++;

offSet?=?i?*?MAX_DECRYPT_BLOCK;

}

byte[]?decryptedData?=?out.toByteArray();

out.close();

return?decryptedData;

}

/**

*?

*?公鑰加密

*?

*

*?@param?data?源數據

*?@param?publicKey?公鑰(BASE64編碼)

*?@return

*?@throws?Exception

*/

public?static?byte[]?encryptByPublicKey(byte[]?data,?String?publicKey)

throws?Exception?{

byte[]?keyBytes?=?Base64Utils.decode(publicKey);

X509EncodedKeySpec?x509KeySpec?=?new?X509EncodedKeySpec(keyBytes);

KeyFactory?keyFactory?=?KeyFactory.getInstance(KEY_ALGORITHM);

Key?publicK?=?keyFactory.generatePublic(x509KeySpec);

//?對數據加密

Cipher?cipher?=?Cipher.getInstance(keyFactory.getAlgorithm());

cipher.init(Cipher.ENCRYPT_MODE,?publicK);

int?inputLen?=?data.length;

ByteArrayOutputStream?out?=?new?ByteArrayOutputStream();

int?offSet?=?0;

byte[]?cache;

int?i?=?0;

//?對數據分段加密

while?(inputLen?-?offSet?>?0)?{

if?(inputLen?-?offSet?>?MAX_ENCRYPT_BLOCK)?{

cache?=?cipher.doFinal(data,?offSet,?MAX_ENCRYPT_BLOCK);

}?else?{

cache?=?cipher.doFinal(data,?offSet,?inputLen?-?offSet);

}

out.write(cache,?0,?cache.length);

i++;

offSet?=?i?*?MAX_ENCRYPT_BLOCK;

}

byte[]?encryptedData?=?out.toByteArray();

out.close();

return?encryptedData;

}

/**

*?

*?私鑰加密

*?

*

*?@param?data?源數據

*?@param?privateKey?私鑰(BASE64編碼)

*?@return

*?@throws?Exception

*/

public?static?byte[]?encryptByPrivateKey(byte[]?data,?String?privateKey)

throws?Exception?{

byte[]?keyBytes?=?Base64Utils.decode(privateKey);

PKCS8EncodedKeySpec?pkcs8KeySpec?=?new?PKCS8EncodedKeySpec(keyBytes);

KeyFactory?keyFactory?=?KeyFactory.getInstance(KEY_ALGORITHM);

Key?privateK?=?keyFactory.generatePrivate(pkcs8KeySpec);

Cipher?cipher?=?Cipher.getInstance(keyFactory.getAlgorithm());

cipher.init(Cipher.ENCRYPT_MODE,?privateK);

int?inputLen?=?data.length;

ByteArrayOutputStream?out?=?new?ByteArrayOutputStream();

int?offSet?=?0;

byte[]?cache;

int?i?=?0;

//?對數據分段加密

while?(inputLen?-?offSet?>?0)?{

if?(inputLen?-?offSet?>?MAX_ENCRYPT_BLOCK)?{

cache?=?cipher.doFinal(data,?offSet,?MAX_ENCRYPT_BLOCK);

}?else?{

cache?=?cipher.doFinal(data,?offSet,?inputLen?-?offSet);

}

out.write(cache,?0,?cache.length);

i++;

offSet?=?i?*?MAX_ENCRYPT_BLOCK;

}

byte[]?encryptedData?=?out.toByteArray();

out.close();

return?encryptedData;

}

/**

*?

*?獲取私鑰

*?

*

*?@param?keyMap?密鑰對

*?@return

*?@throws?Exception

*/

public?static?String?getPrivateKey(Map?keyMap)

throws?Exception?{

Key?key?=?(Key)?keyMap.get(PRIVATE_KEY);

return?Base64Utils.encode(key.getEncoded());

}

/**

*?

*?獲取公鑰

*?

*

*?@param?keyMap?密鑰對

*?@return

*?@throws?Exception

*/

public?static?String?getPublicKey(Map?keyMap)

throws?Exception?{

Key?key?=?(Key)?keyMap.get(PUBLIC_KEY);

return?Base64Utils.encode(key.getEncoded());

}

}

Base64Utils.javaimport?java.io.ByteArrayInputStream;

import?java.io.ByteArrayOutputStream;

import?java.io.File;

import?java.io.FileInputStream;

import?java.io.FileOutputStream;

import?java.io.InputStream;

import?java.io.OutputStream;

import?it.sauronsoftware.base64.Base64;

/**

*?

*?BASE64編碼解碼工具包

*?

*?

*?依賴javabase64-1.3.1.jar

*?

*

*?@author?IceWee

*?@date?2012-5-19

*?@version?1.0

*/

public?class?Base64Utils?{

/**

*?文件讀取緩沖區大小

*/

private?static?final?int?CACHE_SIZE?=?1024;

/**

*?

*?BASE64字符串解碼為二進制數據

*?

*

*?@param?base64

*?@return

*?@throws?Exception

*/

public?static?byte[]?decode(String?base64)?throws?Exception?{

return?Base64.decode(base64.getBytes());

}

/**

*?

*?二進制數據編碼為BASE64字符串

*?

*

*?@param?bytes

*?@return

*?@throws?Exception

*/

public?static?String?encode(byte[]?bytes)?throws?Exception?{

return?new?String(Base64.encode(bytes));

}

/**

*?

*?將文件編碼為BASE64字符串

*?

*?

*?大文件慎用,可能會導致內存溢出

*?

*

*?@param?filePath?文件絕對路徑

*?@return

*?@throws?Exception

*/

public?static?String?encodeFile(String?filePath)?throws?Exception?{

byte[]?bytes?=?fileToByte(filePath);

return?encode(bytes);

}

/**

*?

*?BASE64字符串轉回文件

*?

*

*?@param?filePath?文件絕對路徑

*?@param?base64?編碼字符串

*?@throws?Exception

*/

public?static?void?decodeToFile(String?filePath,?String?base64)?throws?Exception?{

byte[]?bytes?=?decode(base64);

byteArrayToFile(bytes,?filePath);

}

/**

*?

*?文件轉換為二進制數組

*?

*

*?@param?filePath?文件路徑

*?@return

*?@throws?Exception

*/

public?static?byte[]?fileToByte(String?filePath)?throws?Exception?{

byte[]?data?=?new?byte[0];

File?file?=?new?File(filePath);

if?(file.exists())?{

FileInputStream?in?=?new?FileInputStream(file);

ByteArrayOutputStream?out?=?new?ByteArrayOutputStream(2048);

byte[]?cache?=?new?byte[CACHE_SIZE];

int?nRead?=?0;

while?((nRead?=?in.read(cache))?!=?-1)?{

out.write(cache,?0,?nRead);

out.flush();

}

out.close();

in.close();

data?=?out.toByteArray();

}

return?data;

}

/**

*?

*?二進制數據寫文件

*?

*

*?@param?bytes?二進制數據

*?@param?filePath?文件生成目錄

*/

public?static?void?byteArrayToFile(byte[]?bytes,?String?filePath)?throws?Exception?{

InputStream?in?=?new?ByteArrayInputStream(bytes);

File?destFile?=?new?File(filePath);

if?(!destFile.getParentFile().exists())?{

destFile.getParentFile().mkdirs();

}

destFile.createNewFile();

OutputStream?out?=?new?FileOutputStream(destFile);

byte[]?cache?=?new?byte[CACHE_SIZE];

int?nRead?=?0;

while?((nRead?=?in.read(cache))?!=?-1)?{

out.write(cache,?0,?nRead);

out.flush();

}

out.close();

in.close();

}

}

RSATester.javaimport?java.util.Map;

public?class?RSATester?{

static?String?publicKey;

static?String?privateKey;

static?{

try?{

Map?keyMap?=?RSAUtils.genKeyPair();

publicKey?=?RSAUtils.getPublicKey(keyMap);

privateKey?=?RSAUtils.getPrivateKey(keyMap);

System.err.println("公鑰:?\n\r"?+?publicKey);

System.err.println("私鑰:?\n\r"?+?privateKey);

}?catch?(Exception?e)?{

e.printStackTrace();

}

}

public?static?void?main(String[]?args)?throws?Exception?{

test();

testSign();

}

static?void?test()?throws?Exception?{

System.err.println("公鑰加密——私鑰解密");

String?source?=?"這是一行沒有任何意義的文字,你看完了等于沒看,不是嗎?";

System.out.println("\r加密前文字:\r\n"?+?source);

byte[]?data?=?source.getBytes();

byte[]?encodedData?=?RSAUtils.encryptByPublicKey(data,?publicKey);

System.out.println("加密后文字:\r\n"?+?new?String(encodedData));

byte[]?decodedData?=?RSAUtils.decryptByPrivateKey(encodedData,?privateKey);

String?target?=?new?String(decodedData);

System.out.println("解密后文字:?\r\n"?+?target);

}

static?void?testSign()?throws?Exception?{

System.err.println("私鑰加密——公鑰解密");

String?source?=?"這是一行測試RSA數字簽名的無意義文字";

System.out.println("原文字:\r\n"?+?source);

byte[]?data?=?source.getBytes();

byte[]?encodedData?=?RSAUtils.encryptByPrivateKey(data,?privateKey);

System.out.println("加密后:\r\n"?+?new?String(encodedData));

byte[]?decodedData?=?RSAUtils.decryptByPublicKey(encodedData,?publicKey);

String?target?=?new?String(decodedData);

System.out.println("解密后:?\r\n"?+?target);

System.err.println("私鑰簽名——公鑰驗證簽名");

String?sign?=?RSAUtils.sign(encodedData,?privateKey);

System.err.println("簽名:\r"?+?sign);

boolean?status?=?RSAUtils.verify(encodedData,?publicKey,?sign);

System.err.println("驗證結果:\r"?+?status);

}

}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/271843.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/271843.shtml
英文地址,請注明出處:http://en.pswp.cn/news/271843.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

程序員效率:職業倦怠的理解

作為程序員保持高效的工作效率最大的挑戰之一就是身體和心理上產生的倦怠。這個是非常常見的,比如剛開始你自己做一個新項目的時候,項目剛開始,我們總是充滿激情、精力旺盛。大多數情況隨著時間的推移,我們的激情慢慢減退&#xf…

OnItemClickListener,OnScrollListener應用

OnItemClickListener:處理視圖中單個條目的點擊事件 OnScrollListener:監聽滾動的變化,可以用于視圖在滾動中加載數據 OnItemClickListener:實現AdapterView.OnItemClickListener接口,別忘記在MainActivity中調用listView.setOnIt…

Mysql 多實例multi_mysqld_multi多實例運行

mysqld_multi多實例運行2016-05-04 TsengYia126.com http://tsengyia.blog.chinaunix.net#################################################################系統環境:RHEL 6.7 [2.6.32-573.el6.x86_64]軟件環境:mysql-server-5.1.73-5.el6_6.x86_64m…

HashMap和ArrayList初始大小和擴容后的大小

創建HashMap對象默認情況下,數組大小為16。 開始擴容的大小原來的數組大小*loadFactor。 擴容后大小是原來的2倍,其中加載因子loadFactor的默認值為0.75,這個參數可以再創建對象時在構造方法中指定。 例如: 16*0.7512&#xf…

程序員效率:如何合理的分解任務

有這樣一句名言:要吃掉一頭大象,每次吃一口。—— 克雷頓艾布拉姆斯(Creighton Abrams)這句名言給我們的啟示就是:看起來非常艱巨的任務,不能一蹴而就,需要進行任務分解,一步一步把它…

linux安裝nagios客戶端

( 安裝到 被監控的機器上)新增用戶和組 useradd nagiosgroupadd nagcmd usermod -a -G nagcmd nagios (如果安裝中報沒有c編譯器,就 yum install gcc) 下載nagios插件 wget http://sourceforge.net/projects/nagiosplug/files/nagiosplug/1.4.15/nagios-plugins-1.4…

鍵值的 key 和 value 允許為null嗎

總結: HashMap對象的key、value值均可為null。HahTable對象的key、value值均不可為null。兩者的的key值均不能重復,若添加key相同的鍵值對,后面的value會自動覆蓋前面的value,但不會報錯。 例子 可以看到添加key相同的鍵值對&am…

程序員:你的業余時間是怎么被浪費掉的?

作為一名程序員如何有效的管理自己的業余時間,會成為和別人拉開差距的重要因素。當然你不能把每天的每一分鐘都充分利用起來。這個目標不可能實現。我們可以做到的是找到自己在哪一方面浪費的時間最多,然后逐漸的去消除它。如果你可以消除掉一到兩個方面…

java的rserve實現_Java 使用 Rserve 實現與 R 的通信

Rserve 是一個基于 TCP/IP 的服務器程序,它允許其他語言調用 R 語言。由于 Rserve 采用 C/S (客戶端/服務器)的調用方式,因此客戶端并不需要鏈接 R 語言庫,客戶端程序與 R 程序可以實現低耦合的目的。為調用 R 語言,需要實現一個與…

對其他組所提建議的回復(第一階段)

序號題目組名 1理財貓非常3加11.圖片的瀏覽功能我們會在第二階段完成2跑什么操running man1.我們會盡快將界面和功能整合 2.物品的分類會在后期進行中逐步完善3水騎士水騎士團隊暫無4鐵大云盤老司機1.我們的項目是可以顯示“注冊成功”的,你的建議... 2.買家賣家的聊…

數據庫文檔生成工具V1.0

??作者主頁:IT技術分享社區 ??作者簡介:大家好,我是IT技術分享社區的博主,從事C#、Java開發九年,對數據庫、C#、Java、前端、運維、電腦技巧等經驗豐富。 ??個人榮譽: 數據庫領域優質創作者🏆&#x…

volatile關鍵字和AtomicInteger

在Java中,線程部分是一個重點,本篇文章說的JUC也是關于線程的。JUC就是java.util .concurrent工具包的簡稱。這是一個處理線程的工具包,JDK 1.5開始出現的。下面一起來看看它怎么使用。 一、volatile關鍵字與內存可見性 1、內存可見性&…

前端:JS幾種常見的排序

??作者主頁:IT技術分享社區 ??作者簡介:大家好,我是IT技術分享社區的博主,從事C#、Java開發九年,對數據庫、C#、Java、前端、運維、電腦技巧等經驗豐富。 ??個人榮譽: 數據庫領域優質創作者🏆&#x…

bzoj3747 [POI2015]Kinoman

線段樹&#xff0c;記錄next[i]下一部與當前電影一樣的位置&#xff0c;然后枚舉區間左端點i&#xff0c;詢問線段樹最大值后刪除i到next[i-1]這段區間的觀影值&#xff0c;且增加next[i]到next[next[i]]-1這段區間的觀影值。 代碼&#xff0c;跑的有點慢 1 #include<cstdio…

java_poi教程.pdf,如何使用POI轉換.DOC / .DOCX為PDF在Java ..?

how to convert ms-document to PDF, is there any example pls sharewith me.. thanks.解決方案If you are requiered to use POI i guess you should take a look at org.apache.poi.hwpf.converterI never tried this, but i guess its worth a try atleast.It seems like y…

在線語音轉文字工具V1.0

在線語音轉文字工具V1.0介紹在線語音轉文字工具V1.0&#xff0c;采用C#開發語音基于Framework4.5開發&#xff0c;主要采用百度語音識別SDK&#xff0c;實現了在線文本轉語音的功能&#xff0c;可以轉換后直接播放。有需要的朋友可以下載學習一下。如果遇到問題的可以留言或者私…

超媒體

“超媒體”是超級媒體的縮寫。超媒體是一種采用非線性網狀結構對塊狀多媒體信息&#xff08;包括文本、圖像、視頻等&#xff09;進行組織和管理的技術。 超媒體在本質上和超文本是一樣的&#xff0c;只不過超文本技術在誕生的初期管理的對象是純文本&#xff0c;所以叫做超文本…

java局部刷新session過期_Ajax局部頁面刷新和History API結合的陷阱

ajax在現代網站已經得到非常普遍地應用&#xff0c;主要的好處大家都知道(異步加載數據&#xff0c;不用刷新整個瀏覽器&#xff0c;更小的數據傳輸尺寸)。對于那些老網站或者老項目來說全盤改造成ajax并不現實&#xff0c;于是就有了“局部頁面刷新”這個解決方案。如果不知道…