js加密後臺加密解密以及驗證碼

来源:http://www.cnblogs.com/heavenTang/archive/2017/08/08/7306447.html
-Advertisement-
Play Games

該文檔為轉載內容: 加密解密 1 前端js加密概述 2 前後端加密解密 21 引用的js加密庫 22 js加密解密 23 Java端加密解密PKCS5Padding與js的Pkcs7一致 驗證碼 1 概述 2 驗證碼生成器 3 控制器使用驗證碼 如 CodeController 應用 1 login ...


該文檔為轉載內容:

加密解密
1 前端js加密概述
2 前後端加密解密
21 引用的js加密庫
22 js加密解密
23 Java端加密解密PKCS5Padding與js的Pkcs7一致
驗證碼
1 概述
2 驗證碼生成器
3 控制器使用驗證碼 如 CodeController
應用
1 loginhtml
2 Controller
4 實現思路

  1. 加密解密
    1.1 前端js加密概述

對系統安全性要求比較高,那麼需要選擇https協議來傳輸數據。當然很多情況下一般的web網站,如果安全要求不是很高的話,用http協議就可以了。在這種情況下,密碼的明文傳輸顯然是不合適的,因為如果請求在傳輸過程中被截了,就可以直接拿明文密碼登錄網站了。
HTTPS(443)在HTTP(80)的基礎上加入了SSL(Secure Sockets Layer 安全套接層)協議,SSL依靠證書來驗證伺服器的身份,併為瀏覽器和伺服器之間的通信加密。傳輸前用公鑰加密,伺服器端用私鑰解密。

對於使用http協議的web前端的加密,只能防君子不能防小人。前端是完全暴露的,包括你的加密演算法。
知道了加密演算法,密碼都是可以破解的,只是時間問題。請看知乎上的一篇文章:對抗拖庫

所以加密是為了增加破解的時間成本,如果破解需要花費的時間讓人難以接受,這也就達到了目的。

而為了保證資料庫中存儲的密碼更安全,則需要在後端用多種單向(非對稱)加密手段混合進行加密存儲。

前端加密後端又需要解密,所以需要對稱加密演算法,即前端使用 encrypted = encrypt(password+key),後端使用 password = decrypt(encrypted +key) ,前端只傳輸密碼與key加密後的字元串encrypted ,這樣即使請求被攔截了,也知道了加密演算法,但是由於缺少key所以很難破解出明文密碼。所以這個key很關鍵。而這個key是由後端控制生成與銷毀的,用完即失效,所以即使可以模擬用加密後的密碼來發請求模擬登錄,但是key已經失效了,後端還是驗證不過的。

註意,如果本地環境本就是不安全的,key被知道了,那就瞬間就可以用解密演算法破解出密碼了。這裡只是假設傳輸的過程中被截獲的情形。所以前端加密是防不了小人的。如果真要防,可以將加密演算法的js文件進行壓縮加密,不斷更新的手段來使js文件難以獲取,讓黑客難以獲取加密演算法。變態的google就是這麼乾的,自己實現一個js虛擬機,通過不斷更新加密混淆js文件讓加密演算法難以獲取。這樣黑客不知道加密演算法就無法破解了。

常用的對稱加密演算法有DES、3DES(TripleDES)、AES、RC2、RC4、RC5和Blowfis。可以參考:常用加密演算法的Java實現總結

這裡採用js端與java端互通的AES加密演算法。

1.2 前後端加密解密

1.2.1 引用的js加密庫

Cryptojs下載

1
2
3
1.2.2 js加密解密

var data = "888888";
var srcs = CryptoJS.enc.Utf8.parse(data);
var key = CryptoJS.enc.Utf8.parse('o7H8uIM2O5qv65l2');//Latin1 w8m31+Yy/Nw6thPsMpO5fg==
function Encrypt(word){

var srcs = CryptoJS.enc.Utf8.parse(word);  
var encrypted = CryptoJS.AES.encrypt(srcs, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});  
return encrypted.toString();  

}
function Decrypt(word){

var decrypt = CryptoJS.AES.decrypt(word, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});  
return CryptoJS.enc.Utf8.stringify(decrypt).toString();  

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
這裡key是頁面載入的時候由伺服器端生成的,用隱藏域保存。

1.2.3 Java端加密解密(PKCS5Padding與js的Pkcs7一致)

package com.jykj.demo.util;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

import sun.misc.BASE64Decoder;

public class EncryptUtil {
private static final String KEY = "abcdefgabcdefg12";
private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding";
public static String base64Encode(byte[] bytes){
return Base64.encodeBase64String(bytes);
}
public static byte[] base64Decode(String base64Code) throws Exception{
return new BASE64Decoder().decodeBuffer(base64Code);
}
public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"));

    return cipher.doFinal(content.getBytes("utf-8"));  
}  
public static String aesEncrypt(String content, String encryptKey) throws Exception {  
    return base64Encode(aesEncryptToBytes(content, encryptKey));  
}  
public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {  
    KeyGenerator kgen = KeyGenerator.getInstance("AES");  
    kgen.init(128);  

    Cipher cipher = Cipher.getInstance(ALGORITHMSTR);  
    cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES"));  
    byte[] decryptBytes = cipher.doFinal(encryptBytes);  

    return new String(decryptBytes);  
}  
public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception {  
    return aesDecryptByBytes(base64Decode(encryptStr), decryptKey);  
}  


/**
 * 測試
 * 
 */
public static void main(String[] args) throws Exception {

    String content = "Test String麽麽噠";  //0gqIDaFNAAmwvv3tKsFOFf9P9m/6MWlmtB8SspgxqpWKYnELb/lXkyXm7P4sMf3e
    System.out.println("加密前:" + content);  

    System.out.println("加密密鑰和解密密鑰:" + KEY);  

    String encrypt = aesEncrypt(content, KEY);  
    System.out.println(encrypt.length()+":加密後:" + encrypt);  

    String decrypt = aesDecrypt(encrypt, KEY);  
    System.out.println("解密後:" + decrypt);  
}

}

  1. 驗證碼
    2.1 概述

驗證碼是用來區分人機的操作。
驗證碼劃代的標準是人機識別過程中基於對人類知識的應用。
第一代:標準驗證碼
這一代驗證碼是即是我們常見的圖形驗證碼、語音驗證碼,基於機器難以處理複雜的電腦視覺及語音識別問題,而人類卻可以輕鬆的識別來區分人類及機器。這一代驗證碼初步利用了人類知識容易解答,而電腦難以解答的機制進行人機判斷。

第二代:創新驗證碼
第二代驗證碼是基於第一代驗證碼的核心思想(通過人類知識可以解答,而電腦難以解答的問題進行人機判斷)而產生的創新的交互優化型驗證碼。第二代驗證碼基於第一代驗證碼的核心原理--“人機之間知識的差異”,拓展出大量創新型驗證碼。

第三代:無知識型驗證碼
第三代驗證碼最大的特點是不再基於知識進行人機判斷,而是基於人類固有的生物特征以及操作的環境信息綜合決策,來判斷是人類還是機器。無知識型驗證碼最大特點即無需人類思考,從而不會打斷用戶操作,進而提供更好的用戶體驗。
如Google的新版ReCaptcha、阿裡巴巴的滑動驗證。參考知乎 關於驗證碼

2.2 驗證碼生成器

package com.jykj.demo.util;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;

import javax.imageio.ImageIO;

/**

  • 驗證碼生成器
  • */
    public class ValidateCode {
    // 圖片的寬度。
    private int width = 160;
    // 圖片的高度。
    private int height = 28;
    // 驗證碼字元個數
    private int codeCount = 4;
    // 驗證碼干擾線數
    private int lineCount = 150;
    // 驗證碼
    private String code = null;
    // 驗證碼圖片Buffer
    private BufferedImage buffImg = null;

    private char[] codeSequence = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R',
    'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9' };

    public ValidateCode() {
    this.createCode();
    }

    /**
    • @param width
    •       圖片寬
    • @param height
    •       圖片高
      */
      public ValidateCode(int width, int height) {
      this.width = width;
      this.height = height;
      this.createCode();
      }
    /**
    • @param width
    •       圖片寬
    • @param height
    •       圖片高
    • @param codeCount
    •       字元個數
    • @param lineCount
    •       干擾線條數
      */
      public ValidateCode(int width, int height, int codeCount, int lineCount) {
      this.width = width;
      this.height = height;
      this.codeCount = codeCount;
      this.lineCount = lineCount;
      this.createCode();
      }

    public void createCode() {
    int x = 0, fontHeight = 0, codeY = 0;
    int red = 0, green = 0, blue = 0;

    x = width / (codeCount + 2);// 每個字元的寬度
    fontHeight = height - 2;// 字體的高度
    codeY = height - 4;
    
    // 圖像buffer
    buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    Graphics2D g = buffImg.createGraphics();
    // 生成隨機數
    Random random = new Random();
    // 將圖像填充為白色
    g.setColor(Color.WHITE);
    g.fillRect(0, 0, width, height);
    // 創建字體
    Font font = new Font("Fixedsys", Font.BOLD, fontHeight);
    g.setFont(font);
    //干擾線
    for (int i = 0; i < lineCount; i++) {
        int xs = random.nextInt(width);
        int ys = random.nextInt(height);
        int xe = xs + random.nextInt(width / 8);
        int ye = ys + random.nextInt(height / 8);
        red = random.nextInt(255);
        green = random.nextInt(255);
        blue = random.nextInt(255);
        g.setColor(new Color(red, green, blue));
        g.drawLine(xs, ys, xe, ye);
    }
    
    // randomCode記錄隨機產生的驗證碼
    StringBuffer randomCode = new StringBuffer();
    // 隨機產生codeCount個字元的驗證碼。
    for (int i = 0; i < codeCount; i++) {
        String strRand = String.valueOf(codeSequence[random.nextInt(codeSequence.length)]);
        // 產生隨機的顏色值,讓輸出的每個字元的顏色值都將不同。
        red = random.nextInt(255);
        green = random.nextInt(255);
        blue = random.nextInt(255);
        g.setColor(new Color(red, green, blue));
        g.drawString(strRand, (i + 1) * x, codeY);
        // 將產生的四個隨機數組合在一起。
        randomCode.append(strRand);
    }
    // 將四位數字的驗證碼保存到Session中。
    code = randomCode.toString();

    }

    public void write(String path) throws IOException {
    OutputStream sos = new FileOutputStream(path);
    this.write(sos);
    }

    public void write(OutputStream sos) throws IOException {
    ImageIO.write(buffImg, "png", sos);
    sos.close();
    }

    public BufferedImage getBuffImg() {
    return buffImg;
    }

    public String getCode() {
    return code;
    }
    }

2.3 控制器使用驗證碼 如 CodeController

@RequestMapping("/getCode.do")
public void getCode(HttpServletRequest reqeust, HttpServletResponse response) throws IOException {

    response.setContentType("image/jpeg");
    // 禁止圖像緩存。
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);

    HttpSession session = reqeust.getSession();

    ValidateCode vCode = new ValidateCode(100, 28, 4, 100);
    session.setAttribute(Helper.SESSION_CHECKCODE, vCode.getCode());
    vCode.write(response.getOutputStream());
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

  1. 應用
    實現功能:前端AES加密傳輸後端解密以及n次輸入驗證不通過後需要驗證碼
    有了上面的基礎,實現起來應該不難了。

3.1 login.html









<#if Session.login_failure_count?? && (Session.login_failure_count <=0) >





</#if>













3.2 Controller

在請求登錄頁面時需要後端生成一個隨機的16位字元串的key,用於前後端加密解密用,該key在登錄成功後銷毀,存儲在session中。

@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(){
//生成login_token
session.setAttribute(Helper.SESSION_LOGIN_TOKEN,RandomUtil.generateString(16));//登錄令牌,用於密碼加密的key,16位長度
if(session.getAttribute(Helper.SESSION_USER) == null){
return "login";
}
else
return "redirect:/";
}
1
2
3
4
5
6
7
8
9
10
接下來是提交form表單的請求

@RequestMapping(value = "/signIn", method = RequestMethod.POST,produces = "text/html;charset=UTF-8")
@ResponseBody
public String signIn(String username,String password,boolean remember,String checkCode) throws AuthorizationException{
System.out.println(username+","+password+","+remember+","+checkCode);
Object token = session.getAttribute(Helper.SESSION_LOGIN_TOKEN);//原始令牌

    if(token==null) return JSON.toJSONString(new Result(false,"timeout"));//登錄成功後token失效,則頁面失效,客戶端需要重定向到主界面
    Object countObj = session.getAttribute(Helper.SESSION_LOGIN_FAILURE_COUNT);
    int count = countObj==null?ConfigInfo.login_failure_count:Integer.parseInt(countObj.toString());
    System.out.println("剩餘次數:"+count);
    //驗證碼邏輯
    if(count<=0){//需要驗證碼
        Object oldCode = session.getAttribute(Helper.SESSION_CHECKCODE);
        if(checkCode==null||oldCode==null){//該登錄界面沒有驗證碼欄位,但是已經消耗掉了剩餘次數,說明該頁面是過期頁面,需要重新登錄
            return JSON.toJSONString(new Result(false,"timeout"));//客戶端需要重定向到主界面
        }
        if(checkCode.trim().isEmpty()) return JSON.toJSONString(new Result(false,"請輸入驗證碼"));
        if(oldCode.toString().equalsIgnoreCase(checkCode)){
            //驗證通過,可信客戶端,給兩次剩餘次數
            count=2;
            session.setAttribute(Helper.SESSION_LOGIN_FAILURE_COUNT,2);
        }else{
            return JSON.toJSONString(new Result(false,"codeError"));//驗證碼不正確,客戶端需要刷新驗證碼
        }
    }
    //解密
    try {
        password = EncryptUtil.aesDecrypt(password,token.toString());//解密後
        System.out.println("Decrypt:"+password);
    } catch (Exception e) {
        e.printStackTrace();
        return JSON.toJSONString(new Result(false,"timeout"));//客戶端需要重定向到主界面
    }
    //登錄校驗
    String key = RandomUtil.generateString(16);//重新生成登錄令牌,任何登錄失敗的操作都需要更新登錄令牌
    ViewSysUser user =  sysUserService.selectUserPwd(username,password);
    if(user == null){
        session.setAttribute(Helper.SESSION_LOGIN_TOKEN,key);
        session.setAttribute(Helper.SESSION_LOGIN_FAILURE_COUNT,--count);//剩餘次數-1
        if(count<=0) return JSON.toJSONString(new Result(false,"checkCode"));//客戶端需要重定向到登錄界面將驗證碼顯示出來
        return JSON.toJSONString(new Result(false,"用戶名或密碼錯誤!",key));
    }else{
        if(user.getUserid()!=ConfigInfo.admin_id && !user.getuStatus().equals(ConfigInfo.user_status_normal)) {
            session.setAttribute(Helper.SESSION_LOGIN_TOKEN,key);
            return JSON.toJSONString(new Result(false,"登錄失敗,該賬號已被禁止使用!",key));
        }
        //登錄成功
        session.removeAttribute(Helper.SESSION_LOGIN_TOKEN);
        loginUser = user;
        session.setAttribute(Helper.SESSION_USER,loginUser);
        sysEventService.insertEventLog(Helper.logTypeSecurity,username+" 登錄系統");
        return JSON.toJSONString(new Result(true,"登錄成功!"));
    }
}

下麵是生成隨機數的工具類,很簡單

package com.jykj.demo.util;

import java.util.Random;

public class RandomUtil {
public static final String ALLCHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static final String LETTERCHAR = "abcdefghijkllmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static final String NUMBERCHAR = "0123456789";

/** 
 * 返回一個定長的隨機字元串(只包含大小寫字母、數字) 
 *  
 * @param length 
 *            隨機字元串長度 
 * @return 隨機字元串 
 */  
public static String generateString(int length) {  
    StringBuffer sb = new StringBuffer();  
    Random random = new Random();  
    for (int i = 0; i < length; i++) {  
        sb.append(ALLCHAR.charAt(random.nextInt(ALLCHAR.length())));  
    }  
    return sb.toString();  
}  

/** 
 * 返回一個定長的隨機純字母字元串(只包含大小寫字母) 
 *  
 * @param length 
 *            隨機字元串長度 
 * @return 隨機字元串 
 */  
public static String generateMixString(int length) {  
    StringBuffer sb = new StringBuffer();  
    Random random = new Random();  
    for (int i = 0; i < length; i++) {  
        sb.append(LETTERCHAR.charAt(random.nextInt(LETTERCHAR.length())));  
    }  
    return sb.toString();  
}  

/** 
 * 返回一個定長的隨機純大寫字母字元串(只包含大小寫字母) 
 *  
 * @param length 
 *            隨機字元串長度 
 * @return 隨機字元串 
 */  
public static String generateLowerString(int length) {  
    return generateMixString(length).toLowerCase();  
}  

/** 
 * 返回一個定長的隨機純小寫字母字元串(只包含大小寫字母) 
 *  
 * @param length 
 *            隨機字元串長度 
 * @return 隨機字元串 
 */  
public static String generateUpperString(int length) {  
    return generateMixString(length).toUpperCase();  
}  

/** 
 * 生成一個定長的純0字元串 
 *  
 * @param length 
 *            字元串長度 
 * @return 純0字元串 
 */  
public static String generateZeroString(int length) {  
    StringBuffer sb = new StringBuffer();  
    for (int i = 0; i < length; i++) {  
        sb.append('0');  
    }  
    return sb.toString();  
}  

/** 
 * 根據數字生成一個定長的字元串,長度不夠前面補0 
 *  
 * @param num 
 *            數字 
 * @param fixdlenth 
 *            字元串長度 
 * @return 定長的字元串 
 */  
public static String toFixdLengthString(long num, int fixdlenth) {  
    StringBuffer sb = new StringBuffer();  
    String strNum = String.valueOf(num);  
    if (fixdlenth - strNum.length() >= 0) {  
        sb.append(generateZeroString(fixdlenth - strNum.length()));  
    } else {  
        throw new RuntimeException("將數字" + num + "轉化為長度為" + fixdlenth  
                + "的字元串發生異常!");  
    }  
    sb.append(strNum);  
    return sb.toString();  
}  

/** 
 * 每次生成的len位數都不相同 
 *  
 * @param param 
 * @return 定長的數字 
 */  
public static int getNotSimple(int[] param, int len) {  
    Random rand = new Random();  
    for (int i = param.length; i > 1; i--) {  
        int index = rand.nextInt(i);  
        int tmp = param[index];  
        param[index] = param[i - 1];  
        param[i - 1] = tmp;  
    }  
    int result = 0;  
    for (int i = 0; i < len; i++) {  
        result = result * 10 + param[i];  
    }  
    return result;  
}  

}

3.4 實現思路

現在淘寶登錄界面採用的是 無知識型驗證碼,只需要拖動滑塊來判斷是否是機器還是人,如果拖滑塊驗證失敗,會彈出驗證碼輸入或點擊選擇的框,來進行二次驗證。若驗證成功後,連續5次輸入錯誤的用戶名或密碼,則又會彈出驗證碼來需要繼續驗證。也就是說有一個風險分析系統,如果滿足一定的條件(如連續多次輸入錯誤等)則需要加強驗證。風險分析系統要綜合多種因素如ip,用戶信息等等。Google更簡單,通過點擊覆選框(I’m not a robot)就通過驗證。

剛開始時是不需要驗證碼的,用session來存儲剩餘次數,當連續5次驗證都失敗後,該計數遞減為0,則後臺判斷該客戶端不是很可信,需要驗證碼來加強驗證,重新刷新登錄界面(可以重定向實現)把驗證碼輸入框載入出來。客戶端需要同時提交賬號密碼以及驗證碼到後臺驗證,若驗證碼通過驗證,重新將次數複位(或自定義設置),表示該客戶端暫時可信下次提交登錄時可以不需要驗證碼。

對於密碼的加密傳輸與後端解密,key的生成與銷毀的控制很關鍵。當載入登錄頁面時,由後臺生成一個key給該頁面,並保存到隱藏域中,同時該key也是存在session中。前端js用AES加密演算法將密碼和key混合加密,提交給後臺,後臺用相應的解密演算法還原出原始密碼,然後該原始密碼用存儲時使用的多重混合加密演算法進行加密與資料庫中的密碼匹配驗證。當驗證成功後,移除session中的key。而驗證失敗後的邏輯很關鍵,驗證失敗後,需要重新生成一個key給客戶端,所以客戶端通過返回的信息將key賦值到那個隱藏域欄位中,這樣達到刷新key的目的。

總體來說,驗證碼的邏輯會有點複雜。驗證碼的驗證最好放到後臺來驗證,如果放到前臺就需要用一個隱藏域欄位來存這個驗證碼,這樣的話機器也可以獲取到,那機器就不用識別圖片就可以驗證了,這樣驗證碼就失去了作用。


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

-Advertisement-
Play Games
更多相關文章
  • 前言 很多同學想對CAP的機制以及用法等想有一個詳細的瞭解,所以花了將近兩周時間寫了這份中文的CAP文檔,對 CAP 還不知道的同學可以先看一下 "這篇文章" 。 本文檔為 CAP 文獻(Wiki),本文獻同時提供中文和英文版本,英文版本目前還在翻譯中,會放到Github Wiki 中。 目錄 前言 ...
  • 本例演示在母版頁中的銨鈕事件去獲取某些子頁的內容。在母版本中,有一個銨鈕事件。當瀏覽某些子頁時,用戶點一點母版頁的銨鈕,能去獲取當前頁的內容。 子頁很多,但我們並不是每一個子頁的內容均要去獲取,而且每個子頁的內容不一樣。因此你需要讓父頁的事件知道,哪些子頁是需要獲取的。說白了,這就是平時所說的介面。 ...
  • 【申明】:本人.NET Core小白、Linux小白、MySql小白、nginx小白。而今天要說是讓你精通Linux ... 的開機與關機、nginx安裝與部署、Core的Hello World ...等。 首先,入門文章園子裡面已經很多了。這裡再做個整理和備忘。您也可以根據目錄挑著看。(親測多次可 ...
  • Tag Helpers 提供了在視圖中更改和增強現有HTML元素的功能。將它們添加到視圖中,會經過Razor模板引擎處理並創建一個HTML,之後再返回給瀏覽器。 ...
  • 最近在看《編程珠璣》,挺有意思,驚嘆於作者思維的巧妙。閑來無事,就想著開了博客練練手。內容都是從書上的,我就用C#簡單實現了一下。鄙人只是剛出道的小小程式猿,第一次寫博客,實在寫不出太優秀的代碼,望看到的人多多見諒哈。。有錯誤或者意見啥的隨便提,也是我學習的機會。 書上的內容大致是:給定一個英語詞典 ...
  • 1 那就從簡單的標簽說起吧!1.x中常用的標簽只有4中html、bean、logic、tiles 2 3 而struts2.0里的標簽卻沒有分類,只用在jsp頭文件加上 4 5 6 就能使用struts2.0的標簽庫 7 8 9 下麵就介紹每個標簽的具體應用實例說明:按字母排列 10 11 12 A... ...
  • (1) dictionary 用 method item()可以同時得到key和value (2) 用emurate輪詢List可以同時得到索引和值(3) 假如需要同時輪詢兩個或多個序列,可以使用zip() (4) 如果需要反向輪詢,可以用reversed ...
  • 首先瞭解線程的狀態轉換圖: 在Java中一個類要當做線程來使用有兩種方法: 1)繼承Thread類,並重寫run函數 2)實現Runable介面,並重寫run函數 Java是單繼承的,但某些情況下一個類可能已經繼承了某個父類,則不能再繼承Thread類創建線程,只能用第二種。 下麵是針對同一問題“編 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...