主頁 > 後端開發 > PHP代碼篇(九)PHP介面開發如何使用JWT進行驗證身份

PHP代碼篇(九)PHP介面開發如何使用JWT進行驗證身份

2021-10-15 17:15:39 後端開發

  • 前言

    事情是這樣的,在我進入目前公司的時候,因為公司是一家創業公司,所以在我進去的時候,里面的開發配置就是web前端,ui設計,加我PHP后臺各一個,接手的是一個公益小程式,業務倒是不怎么復雜,負責人說這個專案是之前委托外包公司開發的,用的是uniapp開發的小程式,基于ThinkPHP6.0做的介面開發,和一個CatchAdmin開發的管理后臺,公司主要業務這個上面都已經開發完了,但是由于微信小程式的一些功能的限制,我們想把目前的這個專案轉移到微信公眾號上面,就是改成H5開發的模式,

  • 八月的雨

    當時負責人,說前端可能需要重寫,因為之前是小程式,現在是H5嘛,前端是一個剛服役退伍的年輕小伙子,看了下說,用uniapp開發,到時候打包成H5就可以了,基本問題不大,于是就卡卡的開始寫靜態頁面了,說一周寫完靜態頁面,

    到后臺這邊,就有兩個選擇了,因為不管怎么變,最侄訓是做介面開發,所以有兩個選擇,要么在原來外包公司開發的api上修改,做二開,這樣應該基本改下登陸注冊方式,和部分業務邏輯即可;聽取負責人的態度和想法目前對外包公司開發的api專案主要有如下不滿點:

      1、說這個外包公司開發的專案,里面有很多業務邏輯和現在他們想要不一樣;

      2、有些功能業務,實際上還沒開發完,或者有些開發完了,還沒測驗,有很多問題;

      3、對這個專案的完成度,負責人,自己也不是完全清楚;

    根據負責人的描述,再結合自己這幾天看專案源代碼,發現外包寫的代碼確實比較差,看出來,雖然是用的tp框架,但是寫的基本上非常趕,有些介面,基本上是寫完,就沒有具體測驗,還有最主要的就是,基本所有的業務代碼邏輯承載都在一個controller里面,非常不適合后期的迭代維護,所以第二種選擇就是,直接進行重寫,根據負責人對與專案的業務要求,直接重新設計資料庫,重寫api介面,

  • 八月中下旬的選擇

    當時我的想法,當然是重寫是最好,原因如下:

      1、這個原有專案真實邏輯沒有開發人員真實了解(就是我來的時候,都沒有對接人);

      2、這個專案本身業務邏輯比較單一,而且二期改版有十分大;

      3、如果二開,我既要去熟悉舊有代碼,還要修改,對于新功能的開發,還需要切合舊有邏輯;

    想到如上,確實還是直接重寫最好,這樣,一個是代碼的標準,質量,邏輯都在自己的掌握之中,也便于后期的作業,

  • 我承認自己有賭的成分

    重寫的原因說來,一條一條的,清晰完整,但是在做的時候,因為之前一般進入公司的,去的時候,公司專案基本上已經上線了,我們主要是做維護,和調優;自己主導寫一個完整的專案,還沒有;而且之前大多是做前后端不分離的方式,像這種做介面開發,前后端分離的方式,也有點欠缺理解,

    于是入職這一周時間,我一直在重復一個流程:

      1、想一個自己能說的通的開發方案,在腦子里過幾遍;

      2、如果感覺沒問題,就下載相應框架原始碼,搭建demo,開始按方案寫代碼邏輯,在寫的程序中,如果出現方案外的問題,就想辦法解決;

      3、如果方案外的問題實在,無力解決,就只能推倒當前方案;

    最后又回到流程1,這樣我記得有很多次了,當時感覺已經很奔潰了,有點后悔,因為我目前的問題主要是對于這個介面驗證JWT不是特理解,雖然現在寫的時候,已經明白,但是當時是真的,有點暈(今天是10月14日,入職是8月12日),這里說后悔是什么意思呢,因為在上一家公司做的時候,我們有做過一個新專案,就是前后端分離的模式,前端用的是vue,后端純介面開發,介面驗證用的就是jwt,但是當時基礎部分是另外一個同事寫的,我只負責寫業務端,像登錄注冊,驗證,板塊設計都是他做的,記得當時是有在看他驗證這塊是怎么寫的,有不懂的也有當面問過,但是還是沒有具體弄明白,后來想著如果后面需要自己處理這塊,再說,我承認當時確實有賭的成份,現在果然賭輸了,

    人生很多事,就是這樣,有的時候,不知道珍惜,到后來需要自己獨自面對的時候,就要多勞力了,經歷了上面的不斷的試錯和嘗試,時間在一天一天過去,但是我這邊進展,還是一直沒有,每次專案進度開會,明顯感覺到,像是就我這邊進度緩慢,但是有一點可以確認的是,每次在自己的演示中,雖然方案行不通,回到了最初點,但是感覺對于jwt的理解和架構的設計,有了一些實際的理解,我有一種感覺,應該快出來了,果然在一個現在已經忘記了時間上,出來了,

  • 言歸正傳

  PHP通過jwt實作介面驗證,設計思路如下:

    1、先定義controller控制器基類Base.php,作用是繼承改類的,都需要進行token驗證;

    2、在定義一個前端api基類IndexBase.php,作為一個中間層,里面存放驗證后token里面的用戶資訊

    3、書寫PHP實作jwt基類PhpJwt.php,改類主要是獲取token,和驗證token;

  上面三者的關系是,IndexBase.php繼承Base.php繼承PhpJwt.php,

  前端與后端驗證邏輯如下:

    1、前端請求介面,后臺驗證token;

    2、沒有token,給出提示,前端輸入賬號密碼(微信公眾號類,直接通過非靜默授權,獲取用戶openid),進行驗證用戶資訊,驗證通過后,將用戶uid加載到jwt載荷中,生成token,一個驗證toekn(過期時間比較短),一個重繪token(過期時間較長,用于避免每次段時間內,用戶重復登錄),

    3、前端通過登錄拿到兩個token后,存起來,然后在請求需要驗證的介面時,在header頭部加入引數authorization:用戶toekn;來進行驗證,后臺通過token來決議出當前請求用戶的資訊,

  • 核心代碼

    1、PhpJwt.php

<?php
/**
 * PHP實作jwt
 * 
 */
namespace lib;

class PhpJwt {
    //頭部
    private static $header = array(
        'alg'=>'HS256', //生成signature的演算法
        'typ'=>'JWT'  //型別
    );
 
    //使用HMAC生成資訊摘要時所使用的密鑰 md5('jjgw2021')
    private static $key = '99dc2d62ab85bcd9185f3e9324db5567';
    //md5('jjgw2021admin')
    private static $admin_key = '12d44e568140bf62d84d9cb3e20b1103';

    //請求jwt 過期時間 2小時(上線后改為10分鐘)
    private static $request_expect = 3600;

    //重繪jwt 過期時間 24小時
    private static $refresh_expect = 86400;

    private static $admin_request_expect = 1800;

    private static $admin_refresh_expect = 7200;
    //判斷是否后端token
    private static $is_admin = 'is_admin';


    /*** 獲取jwt token
     * @param array $payload jwt載荷  格式如下非必須
     * [
     * 'iss'=>'jwt_admin', //該JWT的簽發者
     * 'iat'=>time(), //簽發時間
     * 'exp'=>time()+7200, //過期時間
     * 'nbf'=>time()+60, //該時間之前不接收處理該Token
     * 'sub'=>'www.admin.com', //面向的用戶
     * 'jti'=>md5(uniqid('JWT').time()) //該Token唯一標識
     * ]
     * @param int $refresh 是否重繪token 1是
     * @param int $is_admin 是否后臺呼叫 1是 0 admin
     * @return string
     */
    public static function getToken(array $payload, $refresh = 0, $is_admin = 0)
    {
        $exp = $refresh ? ($is_admin ? self::$admin_refresh_expect : self::$refresh_expect) : ($is_admin ? self::$admin_request_expect : self::$request_expect);
        $load = [
            'iat' => time(),
            'exp' => time() + $exp,
            'jti' => md5(uniqid('JWT').time())
        ];
        $payload = array_merge($load, $payload);
        $key = ($is_admin == 1 ? self::$admin_key : self::$key);
        $base64header = self::base64UrlEncode(json_encode(self::$header,JSON_UNESCAPED_UNICODE));
        $base64payload = self::base64UrlEncode(json_encode($payload,JSON_UNESCAPED_UNICODE));
        $token = $base64header.'.'.$base64payload.'.'.self::signature($base64header.'.'.$base64payload,$key,self::$header['alg']);
        return $token;
    }
 
 
  /**
   * 驗證token是否有效,默認驗證exp,nbf,iat時間
   * @param string $Token 需要驗證的token
   * @return array
   */
  public static function verifyToken($Token)
  {
        $tokens = explode('.', $Token);
        if (count($tokens) != 3){
            return [
                'code' => 100,
                'msg' => '驗證失敗'
            ];
        }
    
        list($base64header, $base64payload, $sign) = $tokens;
    
        //獲取jwt演算法
        $base64decodeheader = json_decode(self::base64UrlDecode($base64header), JSON_OBJECT_AS_ARRAY);
        if (empty($base64decodeheader['alg'])){
            return [
                'code' => 100,
                'msg' => '驗證失敗'
            ];
        }
        $payload = json_decode(self::base64UrlDecode($base64payload), JSON_OBJECT_AS_ARRAY);
        $key = !empty($payload[self::$is_admin]) ? self::$admin_key : self::$key;
        //簽名驗證
        if (self::signature($base64header . '.' . $base64payload, $key, $base64decodeheader['alg']) !== $sign){
            return [
                'code' => 100,
                'msg' => '簽名驗證失敗'
            ];
        }
    
        //簽發時間大于當前服務器時間驗證失敗
        if (isset($payload['iat']) && $payload['iat'] > time()) {
            return [
                'code' => 100,
                'msg' => '簽發時間大于當前服務器時間,驗證失敗'
            ];
        }
    
        //過期時間小宇當前服務器時間驗證失敗
        if (isset($payload['exp']) && $payload['exp'] < time()) {
            return [
                'code' => 200,
                'msg' => '已過期'
            ];
        }
    
        //該nbf時間之前不接收處理該Token
        if (isset($payload['nbf']) && $payload['nbf'] > time()) {
            return [
                'code' => 100,
                'msg' => '驗證失敗'
            ];
        }
    
        return [
            'code' => 0,
            'msg' => '驗證成功',
            'data' =>$payload
        ];
  }
 
 
 
 
  /**
   * base64UrlEncode  https://jwt.io/ 中base64UrlEncode編碼實作
   * @param string $input 需要編碼的字串
   * @return string
   */
  private static function base64UrlEncode($input)
  {
        return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
  }
 
  /**
   * base64UrlEncode https://jwt.io/ 中base64UrlEncode解碼實作
   * @param string $input 需要解碼的字串
   * @return bool|string
   */
  private static function base64UrlDecode($input)
  {
        $remainder = strlen($input) % 4;
        if ($remainder) {
            $addlen = 4 - $remainder;
            $input .= str_repeat('=', $addlen);
        }
        return base64_decode(strtr($input, '-_', '+/'));
  }
 
  /**
   * HMACSHA256簽名  https://jwt.io/ 中HMACSHA256簽名實作
   * @param string $input 為base64UrlEncode(header).".".base64UrlEncode(payload)
   * @param string $key
   * @param string $alg  演算法方式
   * @return mixed
   */
  private static function signature($input, $key, $alg )
  {
        $alg_config = array(
            'HS256'=>'sha256'
        );
        return self::base64UrlEncode(hash_hmac($alg_config[$alg], $input, $key,true));
  }
}
 

    2、Base.php

<?php
/*
 * @Fun: 控制器基類
 * @User: JessieK
 * @Date: 2021-08-19 18:06:47
 */
namespace app\controller;

use app\BaseController;
use think\facade\Request;
use lib\PhpJwt;

class Base extends BaseController
{
    //會員uid
    protected $uid;
    //會員unionid
    protected $unionid;
    //會員openid
    protected $openid;
    //jwt
    protected $payload;

    public function __construct()
    {
        //更新中
        // $this->apiResult(-100, '網站正在火速更新中,請稍后---');
        // $controller = strtolower(Request::controller());
        $action = strtolower(Request::action());
        if(!in_array($action, ['wechatlogin', 'index', 'coc', 'arealist', 'uploadimg', 'uploadimgstring'])){
            //驗證token
            $this->checkToken();
        }
        if(!in_array($action, ['wechatlogin', 'index', 'coc', 'arealist', 'uploadimg', 'getjwt', 'uploadimgstring'])){
            //驗證sign
            // $this->verifySign();
        }
    }

    /**
     * 驗證token
     */
    public function checkToken()
    {
        $token = empty(Request::header()['authorization']) ? '' : Request::header()['authorization'];
        if(!$token){
            $this->apiResult(-100, 'Authorization不能為空');
        }
        $get_payload = PhpJwt::verifyToken($token);
        switch($get_payload['code']){
            case 100:
                $this->apiResult(-100, 'token驗證失敗');
                break;
            case 200:
                $this->apiResult(1000, 'token已過期');
        }
        $this->uid = $get_payload['data']['uid'];
        $this->unionid = $get_payload['data']['unionid'];
        $this->openid = $get_payload['data']['openid'];
        $this->payload = $get_payload['data'];
    }

    /**
     * @name: 驗證簽名
     * @param {*}
     * @return {*}
     */    
    public function verifySign()
    {
        $token = Request::header()['authorization'];
        list($base64header, $base64payload, $jwtsign) = explode('.', $token);
        $params = Request::post();
        if(empty($params['sign'])){
            $this->apiResult(-100 ,'sign不能為空');
        }
        if(empty($params['timestamp'])){
            $this->apiResult(-100, 'timestamp不能為空');
        }
        //10分鐘有效 毫秒級
        if (time() * 1000 - $params['timestamp'] > 600000) {
            // $this->apiResult(-100, '請求過期');
        }
        $request_sign = $params['sign'];
        //對關聯陣列按照鍵名進行升序排序
        unset($params['sign']);
        ksort($params);
        $param_str = '';
        foreach ($params as $k => $v) {
            $v = is_array($v) ? json_encode($v, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE) : $v;
            $param_str .= $k.$v;
        }
        $restr = $param_str.$jwtsign;
        $sign = md5($restr);
        if (strtolower($request_sign) != strtolower($sign)) {
            $this->apiResult(-100, '簽名驗證失敗');
        }
   
    }

    public function apiResult($code, $msg, $data = [])
    {
        $result = [
            'code' => $code,
            'msg' => $msg,
            'data' => $data,
        ];
        exit(json_encode($result, JSON_UNESCAPED_UNICODE));
    }
}

    3、IndexBase.php

<?php
/*
 * @Fun: 前臺api基類
 * @User: JessieK
 * @Date: 2021-08-19 18:06:47
 */
namespace app\controller;

use app\model\Member;
use think\facade\Request;
class IndexBase extends Base
{
    //會員資訊
    protected $member_info;

    public function __construct()
    {
        parent::__construct();
        if(!$this->openid){
            $this->apiResult(-100, '缺少引數', ['msg' => 'openid為空']);
        }
        $memberModel = new Member();
        $member_info = $memberModel->getUserInfoGather($this->openid);
        if(!$member_info){
            $this->apiResult(1001, '會員資訊不存在');
        }
        //鎖粉操作
        $from_uid = Request::get('from_uid');
        if(empty($member_info['from_uid']) && !empty($from_uid)){
            $memberModel->setFromUid($member_info['uid'], $from_uid, Request::url(true));
        }
        // addlog('errorlog/request/', 'pro', '請求url='.json_encode(request()->get(), JSON_UNESCAPED_UNICODE));
        $this->member_info = $member_info;
    }
}
  • 最后書寫業務代碼

    1、對于需要驗證token的,只需要繼承IndexBase.php即可,基類里面直接對前端傳過來的token進行驗證是否合法,

    2、用戶在登錄后,獲取到請求token,進行介面驗證;請求token過期后,不用重新登錄,用戶用重繪token重繪,獲取到新的請求token,既可以重新獲取驗證,拿到用戶資訊,避免頻繁登錄,

    3、上述代碼,還附帶verfySign簽名,這個主要是可以配合token進行一起使用,jwt實作驗證用戶身份,簽名實作介面請求是否合法,大致邏輯,在每次請求介面時帶上簽名和時間戳,具體簽名邏輯可看上述代碼,

 


-----END

影子是一個會撒謊的精靈,它在虛空中流浪和等待被發現之間;在存在與不存在之間....

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/315870.html

標籤:PHP

上一篇:Hibernate和MyBatis的區別

下一篇:Python代碼閱讀(第15篇):串列求交集

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more