主頁 > 後端開發 > 訂單及其狀態機的設計實作

訂單及其狀態機的設計實作

2022-05-30 17:29:26 後端開發

 狀態機簡介:

 

狀態機是有限狀態自動機的簡稱,是現實事物運行規則抽象而成的一個數學模型,【規則的抽象】

有限狀態機一般都有以下特點:

(1)可以用狀態來描述事物,并且任一時刻,事物總是處于一種狀態;

(2)事物擁有的狀態總數是有限的;

(3)通過觸發事物的某些行為,可以導致事物從一種狀態過渡到另一種狀態;

(4)事物狀態變化是有規則的,A狀態可以變換到B,B可以變換到C,A卻不一定能變換到C;

(5)同一種行為,可以將事物從多種狀態變成同種狀態,但是不能從同種狀態變成多種狀態,

 

狀態機這種描述客觀世界的方式就是將事物抽象成若干狀態,然后所有的事件和規則導致事物在這些狀態中游走,最終使得事物“自圓其說”,

很多通信協議的開發都必須用到狀態機;一個健壯的狀態機可以讓你的程式,不論發生何種突發事件都不會突然進入一個不可預知的程式分支,

 

  • 狀態機示例:

 

 

 

四大概念:

狀態(state)

一個狀態機至少要包含兩個狀態,

分為:現態(源狀態)、次態(目標狀態)

狀態可以理解為一種結果,一種穩態形式,沒有擾動會保持不變的,

 

狀態命名形式:

1.副詞+動詞;例如:待審批、待支付、待識訓

這種命名方式體現了:狀態機就是事件觸發狀態不斷遷徙的本質,表達一種待觸發的感覺,

2.動詞+結果;例如:審批完成、支付完成

3.已+動詞形式;例如:已發貨、已付款

以上兩種命名方式體現了:狀態是一種結果或者穩態的本質,表達了一種已完成的感覺,

 

角色很多的時候,為了表示清晰,可以加上角色名:例如:待財務審批、主管已批準 

命名考慮從用戶好理解角度出發,

 

事件(event)

or

觸發條件

又稱為“條件”,就是某個操作動作的觸發條件或者口令,當一個條件滿足時,就會觸發一個動作,或者執行一次狀態遷徙

這個事件可以是外部呼叫、監聽到訊息、或者各種定時到期等觸發的事件,

對于燈泡,“打開開關”就是一個事件,

條件命名形式:動詞+結果;例如:支付成功、下單時間>5分鐘

動作(action)

事件發生以后要執行動作,例如:事件=“打開開關指令”,動作=“開燈”,一般就對應一個函式,

條件滿足后執行動作,動作執行完畢后,可以遷移到新的狀態,也可以仍舊保持原狀態,

動作不是必需的,當條件滿足后,也可以不執行任何動作,直接遷移到新狀態,

那么如何區分“動作”和“狀態”?

“動作”是不穩定的,即使沒有條件的觸發,“動作”一旦執行完畢就結束了;

而“狀態”是相對穩定的,如果沒有外部條件的觸發,一個狀態會一直持續下去,

 變換(transition)

即從一個狀態變化到另外一個狀態

例如:“開燈程序”就是一個變化

 

狀態機其他表達方式:

 

 

狀態機的設計:

 

資訊系統中有很多狀態機,例如:業務訂單的狀態, 

狀態機的設計存在的問題:什么是狀態?到底有多少個狀態?要細分到什么程度?

  資訊系統是現實世界一種抽象和描述,而業務領域中那些已經發生的事件就是事實資訊系統就是將這些事實以資訊的形式存盤到資料庫中,即:資訊就是一組事實

資訊系統就是存盤這些事實,對這些事實進行管理與追蹤,進而起到提供提高作業效率的作用,

資訊系統就是記錄已經發生的事實,資訊系統中的狀態機基本和事實匹配,即:標識某個事實的完成度,

業務系統,根據實際業務,具體會有哪些發生的事實需要記錄,基本這些事實就至少對應一個狀態,需要記錄的事實就是一種穩態,一種結果,

例如:【待支付】->【已支付】->【已識訓】->【已評價】

這些都是系統需要記錄的已發生的客觀事實,而這些事實就對應了狀態,而發生這些事實的事件就對應了觸發狀態機的轉換的事件,

根據自己的業務實際進行分析,并畫出狀態圖即可,

 

狀態機實作方式:狀態模式

 

下面使經典的自動販賣機例子來說明狀態模式的用法,狀態圖如下:

 

 

 

 

 

 分析一個這個狀態圖:

a、包含4個狀態(我們使用4個int型常量來表示)
b、包含3個暴露在外的方法(投幣、退幣、轉動手柄、(發貨動作是內部方法,售賣機未對外提供方法,售賣機自動呼叫))
c、我們需要處理每個狀態下,用戶都可以觸發這三個動作,

 

我們可以做沒有意義的事情,在【未投幣】狀態,試著退幣,或者同時投幣兩枚,此時機器會提示我們不能這么做,


實作邏輯:

    任何一個可能的動作,我們都要檢查,看看我們所處的狀態和動作是否合適,

/**
 * 自動售歡訓
 * 
 * 
 */
public class VendingMachine
{
 
    /**
     * 已投幣
     */
    private final static int HAS_MONEY = 0;
    /**
     * 未投幣
     */
    private final static int NO_MONEY = 1;
    /**
     * 售出商品
     */
    private final static int SOLD = 2;
    /**
     * 商品售罄
     */
    private final static int SOLD_OUT = 3;
    /**
     * 商品數量
     */
    private int count = 0;
    
         // 當前狀態,開機模式是沒錢
    private int currentStatus = NO_MONEY;
 
        // 開機設定商品數量,初始化狀態
    public VendingMachine(int count)
    {
        this.count = count;
        if (count > 0)
        {
            currentStatus = NO_MONEY;
        }
    }
 
    /**
     * 投入硬幣,任何狀態用戶都可能投幣
     */
    public void insertMoney()
    {
        switch (currentStatus)
        {
        case NO_MONEY:
            currentStatus = HAS_MONEY;
            System.out.println("成功投入硬幣");
            break;
        case HAS_MONEY:
            System.out.println("已經有硬幣,無需投幣");
            break;
        case SOLD:
            System.out.println("請稍等...");
            break;
        case SOLD_OUT:
            System.out.println("商品已經售罄,請勿投幣");
            break;
 
    }
    }
 
    /**
     * 退幣,任何狀態用戶都可能退幣
     */
    public void backMoney()
    {
        switch (currentStatus)
        {
        case NO_MONEY:
            System.out.println("您未投入硬幣");
            break;
        case HAS_MONEY:
            currentStatus = NO_MONEY;
            System.out.println("退幣成功");
            break;
        case SOLD:
            System.out.println("您已經買了糖果...");
            break;
        case SOLD_OUT:
            System.out.println("您未投幣...");
            break;
        }
    }
 
    /**
     * 轉動手柄購買,任何狀態用戶都可能轉動手柄
     */
    public void turnCrank()
    {
        switch (currentStatus)
        {
        case NO_MONEY:
            System.out.println("請先投入硬幣");
            break;
        case HAS_MONEY:
            System.out.println("正在出商品....");
            currentStatus = SOLD;
            dispense();
            break;
        case SOLD:
            System.out.println("連續轉動也沒用...");
            break;
        case SOLD_OUT:
            System.out.println("商品已經售罄");
            break;
 
    }
    }
 
    /**
     * 發放商品
     */
    private void dispense()
    {
 
    switch (currentStatus)
    {
    case NO_MONEY:
    case HAS_MONEY:
    case SOLD_OUT:
        throw new IllegalStateException("非法的狀態...");
    case SOLD:
        count--;
        System.out.println("發出商品...");
        if (count == 0)
        {
            System.out.println("商品售罄");
            currentStatus = SOLD_OUT;
        } else
        {
            currentStatus = NO_MONEY;
        }
        break;
 
    }
 
    }
}
狀態機使用if-else或switch實作
// 測驗自動售賣機
class Test
{
    public static void main(String[] args)
    {
        VendingMachine machine = new VendingMachine(10);
        machine.insertMoney();
        machine.backMoney();
 
    System.out.println("-----------");
 
    machine.insertMoney();
    machine.turnCrank();
    
    System.out.println("-----------");
    machine.insertMoney();
    machine.insertMoney();
    machine.turnCrank();
    machine.turnCrank();
    machine.backMoney();
    machine.turnCrank();
 
    }
}
測驗自動售賣機

使用if-else/switch的方式實作狀態有如下問題:

  • 沒有遵守【開閉】原則,沒有【封裝變化】,所以沒有彈性,應對需求變更非常吃力,

      例如:現在增加一個狀態,每個方法都需要添加if-else陳述句,

  • 狀態如何轉換看得不是很清楚,隱藏在if-else/switch邏輯中,

 

升級策略:

【封裝變化】,區域化每個狀態的行為,將每個狀態的行為放到各自類中,每個狀態只要實作自己的動作就可以了,

販賣機只要將動作委托給代表當前狀態的狀態物件即可,

 

 

public interface State
{
    /**
     * 放錢
     */
    public void insertMoney();
    /**
     * 退錢
     */
    public void backMoney();
    /**
     * 轉動曲柄
     */
    public void turnCrank();
    /**
     * 出商品
     */
    public void dispense();
}
State
public class NoMoneyState implements State
{
 
    private VendingMachine machine;
 
    public NoMoneyState(VendingMachine machine)
    {
        this.machine = machine;
        
    }
    
    @Override
    public void insertMoney()
    {
        System.out.println("投幣成功");
        machine.setState(machine.getHasMoneyState());
    }
 
    @Override
    public void backMoney()
    {
        System.out.println("您未投幣,想退錢?...");
    }
 
    @Override
    public void turnCrank()
    {
        System.out.println("您未投幣,想拿東西么?...");
    }
 
    @Override
    public void dispense()
    {
        throw new IllegalStateException("非法狀態!");
    }
 
}
NoMoneyState
public class HasMoneyState implements State
{
 
    private VendingMachine machine;
 
    public HasMoneyState(VendingMachine machine)
    {
        this.machine = machine;
    }
 
    @Override
    public void insertMoney()
    {
        System.out.println("您已經投過幣了,無需再投....");
    }
 
    @Override
    public void backMoney()
    {
        System.out.println("退幣成功");
            machine.setState(machine.getNoMoneyState());
    }
 
    @Override
    public void turnCrank()
    {
        System.out.println("你轉動了手柄");
        machine.setState(machine.getSoldState());
    }
 
    @Override
    public void dispense()
    {
        throw new IllegalStateException("非法狀態!");
    }
 
}
    
HasMoneyState
public class SoldOutState implements State
{
 
    private VendingMachine machine;
 
    public SoldOutState(VendingMachine machine)
    {
        this.machine = machine;
    }
 
    @Override
    public void insertMoney()
    {
        System.out.println("投幣失敗,商品已售罄");
    }
 
    @Override
    public void backMoney()
    {
        System.out.println("您未投幣,想退錢么?...");
    }
 
    @Override
    public void turnCrank()
    {
        System.out.println("商品售罄,轉動手柄也木有用");
    }
 
    @Override
    public void dispense()
    {
        throw new IllegalStateException("非法狀態!");
    }
 
}
SoldOutState
public class SoldState implements State
{
 
    private VendingMachine machine;
 
    public SoldState(VendingMachine machine)
    {
        this.machine = machine;
    }
 
    @Override
    public void insertMoney()
    {
        System.out.println("正在出貨,請勿投幣");
    }
 
    @Override
    public void backMoney()
    {
        System.out.println("正在出貨,沒有可退的錢");
    }
 
    @Override
    public void turnCrank()
    {
        System.out.println("正在出貨,請勿重復轉動手柄");
    }
 
    @Override
    public void dispense()
    {
        machine.releaseBall();
        if (machine.getCount() > 0)
        {
            machine.setState(machine.getNoMoneyState());
        } else
        {
            System.out.println("商品已經售罄");
            machine.setState(machine.getSoldOutState());
        }
    }
}
SoldState
public class VendingMachine
{
    private State noMoneyState;
    private State hasMoneyState;
    private State soldState;
    private State soldOutState;
    private State winnerState ; 


    private int count = 0;
    private State currentState = noMoneyState;
 
    public VendingMachine(int count)
    {
        noMoneyState = new NoMoneyState(this);
        hasMoneyState = new HasMoneyState(this);
        soldState = new SoldState(this);
        soldOutState = new SoldOutState(this);
        winnerState = new WinnerState(this);
 
              if (count > 0)
             {
            this.count = count;
            currentState = noMoneyState;
             }
    }
 
       //將這些動作委托給當前狀態.
    public void insertMoney()
    {
        currentState.insertMoney();
    }
 
    public void backMoney()
    {
        currentState.backMoney();
    }
       
        // 機器不用提供dispense動作,因為這是一個內部動作.用戶不可以直 
        //接要求機器發放糖果.我們在狀態物件的turnCrank()方法中呼叫 
        //dispense方法;

       //dispense無論如何,即使在nomoney狀態也會被執行.
       //讓不合法的情形下,dispense拋出例外處理,
    public void turnCrank()
    {
        currentState.turnCrank();
            currentState.dispense();
    }
 

    public void releaseBall()
    {
        System.out.println("發出一件商品...");
        if (count != 0)
        {
            count -= 1;
        }
    }
 
    public void setState(State state)
    {
        this.currentState = state;
    }
 
    //getter setter omitted ...
 
}
VendingMachine

 

我們之前說過,if-else/switch實作方式沒有彈性,那現在按照這種實作模式,需求變更修改起來會輕松點嗎?

紅色部分標記了我們的需求變更:當用戶每次轉動手柄的時候,有10%的幾率贈送一瓶,

 

實作方式:

 

我們遵守了【開閉】原則,只要新建一個WinnerState的類即可,然后有限的修改has_money的轉向即可,

為什么WinnerState要獨立成一個狀態,其實它和sold狀態一模一樣,我把代碼寫在SoldState中不行嗎?

  • 第一個原因就是上面說的資訊系統的本質就是記錄【事實】,中獎是需要記錄的事實,它應該是一個狀態,
  • 第二個原因:【單一職責】問題,我們一個類的責任是明確的,

   如果sold需求變化不一定影響到winner代碼實作,winner需求變化時,也不一定要修改sold,比如促銷方案結束了,中獎概率變了等,

   如果他們的變化不是一定互相影響到彼此的,那我們就該將他們分離,即是【隔離變化】也是遵守【單一職責】的原則,

 

public class WinnerState implements State
{
 
    private VendingMachine machine;
 
    public WinnerState(VendingMachine machine)
    {
        this.machine = machine;
    }
 
    @Override
    public void insertMoney()
    {
        throw new IllegalStateException("非法狀態");
    }
 
    @Override
    public void backMoney()
    {
        throw new IllegalStateException("非法狀態");
    }
 
    @Override
    public void turnCrank()
    {
        throw new IllegalStateException("非法狀態");
    }
 
    @Override
    public void dispense()
    {
        System.out.println("你中獎了,恭喜你,將得到2件商品");
        machine.releaseBall();
 
    if (machine.getCount() == 0)
    {
        System.out.println("商品已經售罄");
        machine.setState(machine.getSoldOutState());
    } else
    {
        machine.releaseBall();
        if (machine.getCount() > 0)
        {
            machine.setState(machine.getNoMoneyState());
        } else
        {
            System.out.println("商品已經售罄");
            machine.setState(machine.getSoldOutState());
        }
        
    }
 
    }
 
}
WinnerState
public class HasMoneyState implements State
{
 
    private VendingMachine machine;
    private Random random = new Random();
 
    public HasMoneyState(VendingMachine machine)
    {
        this.machine = machine;
    }
 
    @Override
    public void insertMoney()
    {
        System.out.println("您已經投過幣了,無需再投....");
    }
 
    @Override
    public void backMoney()
    {
        System.out.println("退幣成功");
 
    machine.setState(machine.getNoMoneyState());
    }
 
    @Override
    public void turnCrank()
    {
        System.out.println("你轉動了手柄");
        int winner = random.nextInt(10);
        if (winner == 0 && machine.getCount() > 1)
        {
            machine.setState(machine.getWinnerState());
        } else
        {
            machine.setState(machine.getSoldState());
        }
    }
 
    @Override
    public void dispense()
    {
        throw new IllegalStateException("非法狀態!");
    }
 
}
HasMoneyState

 

總結狀態模式: 

狀態模式:允許物件在內部狀態改變時改變它的行為,物件看起來好像修改了他的類,

解釋:

狀態模式將狀態封裝成為獨立的類,并將動作委托到代表當前狀態的物件,
所以行為會隨著內部狀態改變而改變,
我們通過組合簡單參考不同狀態物件來造成類改變的假象.

狀態模式 策略模式

1.行為封裝的n個狀態中,不同狀態不用行為,

2.context的行為委托到不同狀態中,

3.[當前狀態]在n個狀態中游走,context的行為也隨之[當前狀態]的改變而改變,

4.用戶對context的狀態改變渾然不知,

5.客戶不會直接和state互動,只能通過context暴露的方法互動,state轉換是context內部事情,

6.state可以是介面也可以是抽象類,取決于有沒公共功能可以放進抽象類中,抽象類方便,因為可以后續加方法,

可以將重復代碼放入抽象類中,例如:"你已投入25元,不能重復投" 這種通用代碼放入抽象類中,

7.context可以決定狀態流轉,如果這個狀態流轉是固定的,就適合放在context中進行,但是如果狀態流轉是動態的就適合放在狀態中進行,

例如通過商品的剩余數目來決定流向[已售完]或[等待投幣],這個時候放在狀態類中,因為dispense要根據狀態判斷流轉,

這個寫法決定了,有新需求時候,你是改context還是改state類,

8.可以共享所有的state物件,但是需要修改context的時候時候,需要handler中傳入context參考

 

1.context主動指定需要組合的策略物件是哪一個,

2.可以在啟動的時候通過工廠動態指定具體是哪個策略物件,但是沒有在策略物件之間游走,即:只組合了一個策略物件,

3.策略作為繼承之外一種彈性替代方案,因為繼承導致子類繼承不適用的方法,且每個類都要維護,策略模式通過不同物件組合來改變行為,

4.策略模式聚焦的是互換的演算法來創建業務,

 

 

 

狀態機典型應用:訂單狀態控制

 

 

 

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

CREATE TABLE IF NOT EXISTS `tbl_sapo_biz_param` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `create_time` datetime(3) NOT NULL COMMENT '創建時間',
  `biz_type` varchar(255) NOT NULL COMMENT '業務類別(使用時定義)',
  `biz_code` varchar(255) NOT NULL COMMENT '業務資料編碼',
  `para_key` varchar(255) NOT NULL COMMENT '引數key',
  `para_value` varchar(255) NOT NULL COMMENT '引數值',
  `para_desc` varchar(255) NOT NULL COMMENT '引數描述',
  `level` varchar(64) DEFAULT NULL COMMENT '引數級別;ABCD',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_idx_biz_param_biz_type_biz_code_para_key` (`biz_type`,`biz_code`,`para_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='業務引數表';

CREATE TABLE IF NOT EXISTS `tbl_sapo_order` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `order_no` varchar(100) NOT NULL COMMENT '訂單號',
  `create_time` datetime(3) NOT NULL COMMENT '創建時間',
  `last_update_time` datetime(3) DEFAULT NULL COMMENT '最后更新時間',
  `xx_code` varchar(100) NOT NULL COMMENT '業務物體code',
  `status` int(10) unsigned NOT NULL DEFAULT 3 COMMENT '狀態:3-已取消,4-已接單,5-待支付,6-已支付,7-待執行,8-執行中,9-執行完成',
  `cancel_reason` int(10) unsigned DEFAULT NULL COMMENT '訂單取消的原因,1-未支付(支付取消或過期),2-未準備(準備失敗),3-未執行(啟動過期),4-未完成(執行中取消)',
  `refund_count` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '退款次數',
  `expire_time` datetime(3) DEFAULT NULL ON UPDATE current_timestamp(3) COMMENT '失效時間',
  `invoice_flag` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '是否開過發票標識,0-未開發票,1-已開發票',
  `total_fee` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '訂單總價,單位:分',
  `pay_fee` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '最終支付金額(單位:分)',
  `trace_no` varchar(255) DEFAULT NULL COMMENT '日志追蹤標識',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_idx_order_order_no` (`order_no`),
  KEY `idx_order_xx_code` (`xx_code`),
  KEY `idx_order_expire_time` (`expire_time`),
  CONSTRAINT `fk_order_xx_code` FOREIGN KEY (`xx_code`) REFERENCES `tbl_sapo_xx` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='業務訂單';

CREATE TABLE IF NOT EXISTS `tbl_sapo_order_coupon` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `create_time` datetime(3) NOT NULL COMMENT '創建時間',
  `user_coupon_uuid` varchar(100) NOT NULL COMMENT '用戶優惠券uuid,tbl_sapo_user_coupon表uuid',
  `order_id` int(10) unsigned NOT NULL COMMENT '業務訂單id,tbl_sapo_order表id',
  `status` int(10) unsigned NOT NULL DEFAULT 1 COMMENT '狀態:0-無效,1-有效',
  PRIMARY KEY (`id`),
  KEY `idx_order_coupon_user_coupon_uuid` (`user_coupon_uuid`),
  KEY `idx_order_coupon_order_id` (`order_id`),
  CONSTRAINT `fk_order_coupon_order_id` FOREIGN KEY (`order_id`) REFERENCES `tbl_sapo_order` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='訂單優惠券';

CREATE TABLE IF NOT EXISTS `tbl_sapo_order_status_log` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `create_time` datetime(3) NOT NULL COMMENT '創建時間',
  `order_type` int(10) unsigned NOT NULL DEFAULT 4 COMMENT '訂單型別:4-業務訂單,5-支付訂單,6-退款訂單',
  `order_id` int(10) unsigned NOT NULL COMMENT '訂單id',
  `status` int(10) unsigned NOT NULL COMMENT '狀態',
  `remark` varchar(255) DEFAULT NULL COMMENT '操作備注',
  PRIMARY KEY (`id`),
  KEY `idx_order_status_log_order_id` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='訂單狀態記錄';

CREATE TABLE IF NOT EXISTS `tbl_sapo_pay` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '物理主鍵',
  `requester_no` varchar(255) NOT NULL COMMENT '請求流水號',
  `create_time` datetime(3) NOT NULL COMMENT '創建時間',
  `last_update_time` datetime(3) DEFAULT NULL COMMENT '最后更新時間',
  `order_id` int(10) unsigned NOT NULL COMMENT '業務訂單標識,tbl_sapo_order表id',
  `status` int(10) unsigned NOT NULL DEFAULT 4 COMMENT '狀態:3-撤銷,4-申請,5-待支付,6-已支付,7-失敗',
  `expire_time` datetime(3) DEFAULT NULL ON UPDATE current_timestamp(3) COMMENT '失效時間',
  `payment_complete_time` datetime(3) DEFAULT NULL COMMENT '支付完成時間',
  `remark` varchar(255) DEFAULT NULL COMMENT '備注',
  `account_no` varchar(255) NOT NULL COMMENT '統一支付渠道',
  `requester_code` varchar(255) NOT NULL COMMENT '統一支付服務編碼',
  `body` varchar(255) DEFAULT NULL COMMENT '統一支付商品描述',
  `total_fee` int(10) unsigned NOT NULL COMMENT '金額(單位:分)',
  `trade_type` varchar(64) DEFAULT NULL COMMENT '交易型別',
  `pay_info` varchar(1024) DEFAULT NULL COMMENT '支付引數',
  `payment_pay_no` varchar(255) DEFAULT NULL COMMENT '統一支付平臺支付流水號(請求支付后回傳)',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_idx_pay_requester_no` (`requester_no`),
  UNIQUE KEY `uni_idx_pay_payment_pay_no` (`payment_pay_no`),
  KEY `idx_pay_order_id` (`order_id`),
  CONSTRAINT `fk_pay_order_id` FOREIGN KEY (`order_id`) REFERENCES `tbl_sapo_order` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='支付訂單';

CREATE TABLE IF NOT EXISTS `tbl_sapo_refund` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '物理主鍵',
  `requester_refund_no` varchar(255) NOT NULL COMMENT '請求服務退款流水',
  `create_time` datetime(3) NOT NULL COMMENT '創建時間',
  `last_update_time` datetime(3) DEFAULT NULL COMMENT '最后更新時間',
  `order_id` int(10) unsigned NOT NULL COMMENT '業務訂單標識,tbl_sapo_order表id',
  `payment_id` int(10) unsigned NOT NULL COMMENT '支付訂單id,tbl_sapo_payment表id',
  `expire_time` datetime(3) DEFAULT NULL ON UPDATE current_timestamp(3),
  `status` int(10) unsigned NOT NULL DEFAULT 4 COMMENT '狀態:3-撤銷,4-申請,5-退款中,6-已退款,7-失敗',
  `requester_code` varchar(255) NOT NULL COMMENT '服務編碼',
  `requester_pay_no` varchar(255) NOT NULL COMMENT '請求服務支付流水',
  `refund_fee` int(10) unsigned NOT NULL COMMENT '金額(單位:分)',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_idx_refund_requester_refund_no` (`requester_refund_no`),
  KEY `idx_refund_payment_id` (`payment_id`),
  KEY `idx_refund_order_id` (`order_id`),
  KEY `idx_refund_requester_pay_no` (`requester_pay_no`),
  CONSTRAINT `fk_refund_order_id` FOREIGN KEY (`order_id`) REFERENCES `tbl_sapo_order` (`id`),
  CONSTRAINT `fk_refund_payment_id` FOREIGN KEY (`payment_id`) REFERENCES `tbl_sapo_pay` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='退款訂單';

CREATE TABLE IF NOT EXISTS `tbl_sapo_sys_param` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `create_time` datetime(3) NOT NULL COMMENT '創建時間',
  `para_key` varchar(255) NOT NULL COMMENT '配置鍵',
  `para_value` varchar(1024) NOT NULL COMMENT '配置值',
  `para_desc` varchar(255) NOT NULL COMMENT '配置描述',
  `level` varchar(64) NOT NULL DEFAULT 'A' COMMENT '引數等級,ABCD',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_idx_sys_param_para_key` (`para_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系統配置表';

CREATE TABLE IF NOT EXISTS `tbl_sapo_xx` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `code` varchar(100) NOT NULL COMMENT '唯一編碼',
  `create_time` datetime(3) NOT NULL COMMENT '創建時間',
  `last_update_time` datetime(3) DEFAULT NULL COMMENT '最后更新時間',
  `name` varchar(255) NOT NULL COMMENT '名稱',
  `detail` varchar(255) DEFAULT NULL COMMENT '詳情',
  `status` int(10) unsigned NOT NULL DEFAULT 2 COMMENT '狀態:0-無效,1-有效,2-編輯',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_idx_xx_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='業務物體';

/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;
建表陳述句

 

如上圖所示:

一種典型的訂單設計,業務訂單和支付退款訂單組合,他們分別有自己的狀態機,

  • 業務訂單狀態機負責業務邏輯,并和支付退款狀態機聯動,
  • 一切以業務狀態機為主,例如:業務狀態已經【關單】,此時收到支付成功通知,需要進行退款
  • 每個狀態有自己的過期時間,例外訂單的撈取通過過期時間判斷,

 

 

狀態機模式實作訂單狀態機:

日常開發程序中,狀態機模式應用場景之一的就是訂單模型中的狀態控制,但是區別于狀態模式的點有以下幾個:

  • 狀態模式,所有的操作都在記憶體,而訂單狀態機是要落庫的,為了防止訂單的并發操作,更新訂單的時候需要使用樂觀鎖機制,
  • 狀態模式的狀態物件是新建狀態機的時候初始化進去的,在實際開發中,狀態物件要復用,被spring管理,
  • 而訂單狀態機物件對應了一條資料庫中物體的訂單,是要每次從資料庫中查出來的即時新建物件,所以必須將該新建的訂單狀態機物件傳入到狀態物件中,使用狀態物件處理該訂單狀態機物件,

 

以支付訂單為例:

 

 

 

 

 

/*
   Title: PaymentInfo Description:
  支付訂單狀態機 該類不可被spring管理,需要new出來,一個類就對應一條資料庫中支付訂單記錄
本文來自博客園,作者:wanglifeng,轉載請注明原文鏈接:https://www.cnblogs.com/wanglifeng717/p/16214122.html
@author wanglifeng
*/

public class PaymentStateMachine { // 資料庫中當前支付訂單物體 private SapoPayment payment; // 當前狀態 private PaymentState currentState; // 需要更新入庫的支付訂單物體,與payment屬性配合,payment為當前資料庫中訂單物體,用于樂觀鎖的前置內容校驗, private SapoPayment paymentForUpdate; /* 將最新內容(含狀態)更新入庫,并當前狀態機狀態 */ public void updateStateMachine() { // 從Spring容器中獲取操作資料的dao SapoDao dao = SpringUtil.getBean(SapoDao.class); // 更新資料庫,樂觀鎖機制:帶前置內容資料校驗,其中payment為前置內容,paymentForUpdate為要更新的內容,如果更新結果=0,說明該訂單被其他執行緒修改過,拋例外,放棄此次修改, dao.updateSapoPaymentByNull(paymentForUpdate, payment); // 記錄訂單操作流水 dao.insertSapoOrderStatusLog(SapoOrderStatusLog.getInstance().setOrderId(paymentForUpdate.getId()) .setOrderType(SapoOrderStatusLog.ORDER_TYPE_PAYMENT).setStatus(paymentForUpdate.getStatus())); // 更新當前PaymentStateMachine狀態機 this.setPayment(paymentForUpdate); this.setCurrentState(paymentForUpdate.getStatus()); } // 通過條件獲取一個支付訂單PaymentStateMachine物體 public static PaymentStateMachine getInstance(SapoPayment sapoPaymentForQuery) { // 1.從spring容器中獲取dao; SapoDao dao = SpringUtil.getBean(SapoDao.class); // 2.查出該支付訂單 SapoPayment paymentResult = dao.getSapoPayment(sapoPaymentForQuery); // 3.初始化訂單狀態機 PaymentStateMachine paymentStateMachine = new PaymentStateMachine(); paymentStateMachine.setPayment(paymentResult); paymentStateMachine.setCurrentState(paymentResult.getStatus()); paymentStateMachine.setPaymentForUpdate(SapoPayment.getInstance(paymentResult)); return paymentStateMachine; } // 設定當前狀態機的狀態,輸入資料庫中status欄位,映射成對應的狀態類物體, public void setCurrentState(Integer status) { PaymentState currentState = null; // status數字,映射成對應的狀態類物體 if (SapoPayment.STATUS_APPLY.equals(status)) { currentState = SpringUtil.getBean(PaymentStateApply.class); } else if (SapoPayment.STATUS_WAIT_PAY.equals(status)) { currentState = SpringUtil.getBean(PaymentStateWaitPay.class); } else if (SapoPayment.STATUS_PAY_FINISH.equals(status)) { currentState = SpringUtil.getBean(PaymentStatePayFinish.class); } else if (SapoPayment.STATUS_FAIL.equals(status)) { currentState = SpringUtil.getBean(PaymentStateFail.class); } else if (SapoPayment.STATUS_CANCEL.equals(status)) { currentState = SpringUtil.getBean(PaymentStateCancel.class); } else { throw new BusinessException(ResultInfo.SYS_INNER_ERROR.getCode(), "status not in state machine ,status: " + status); } this.currentState = currentState; } // TODO 待實作,申請支付訂單 public void apply() { // 委托給當前狀態執行,將當前訂單狀態機物件傳進去,使用狀態物件處理訂單 currentState.apply(this); } // TODO 待實作,通知支付結果 public void resultNotify() { // 委托給當前狀態執行 currentState.resultNotify(this); } // TODO 同步給當前狀態執行 public void sync() { // 委托給當前狀態執行 currentState.sync(this); } // 取消訂單 public void cancel() { // 委托給當前狀態執行 currentState.cancel(this); } }

 

public  interface PaymentState {

    public void apply(PaymentStateMachine paymentStateMachine);

    public void resultNotify(PaymentStateMachine paymentStateMachine);
    
    public void sync(PaymentStateMachine paymentStateMachine);
    
    public void cancel(PaymentStateMachine paymentStateMachine);

}
PaymentState狀態介面
@Service
public class PaymentStateApply extends BaseLogger implements PaymentState {

    @Autowired
    FmPayClientService fmPayClientService;

    @Autowired
    SapoDao dao;

    @Autowired
    private JacksonComponent jacksonComponent;

    public void apply(PaymentStateMachine paymentStateMachine) {

       

    }

    public void sync(PaymentStateMachine paymentStateMachine) {

    }

    public void resultNotify(PaymentStateMachine paymentStateMachine) {
        // TODO Auto-generated method stub

    }

    public void cancel(PaymentStateMachine paymentStateMachine) {
        
        SapoPayment sapoPaymentForUpdate = paymentStateMachine.getPaymentForUpdate();
        sapoPaymentForUpdate.setStatus(SapoPayment.STATUS_CANCEL);
        sapoPaymentForUpdate.setExpireTime(null);
        
        paymentStateMachine.updateStateMachine();
        
        

    }

}
PaymentStateApply
@Service
public class PaymentStateCancel extends BaseLogger implements PaymentState {

    public void apply(PaymentStateMachine paymentStateMachine) {
        // TODO Auto-generated method stub

    }

    public void resultNotify(PaymentStateMachine paymentStateMachine) {
        // TODO Auto-generated method stub

    }

    public void sync(PaymentStateMachine paymentStateMachine) {
        // TODO Auto-generated method stub

    }

    public void cancel(PaymentStateMachine paymentStateMachine) {
        // TODO Auto-generated method stub

    }

}
PaymentStateCancel
@Service
public class PaymentStateFail extends BaseLogger implements PaymentState {

    public void apply(PaymentStateMachine paymentStateMachine) {
        // TODO Auto-generated method stub

    }

    public void resultNotify(PaymentStateMachine paymentStateMachine) {
        // TODO Auto-generated method stub

    }

    public void sync(PaymentStateMachine paymentStateMachine) {
        // TODO Auto-generated method stub

    }

    public void cancel(PaymentStateMachine paymentStateMachine) {
        throw new BusinessException(ResultInfo.SYS_INNER_ERROR.getCode(), "fail status can not cancel");

    }

}
PaymentStateFail
@Service
public class PaymentStatePayFinish extends BaseLogger implements PaymentState {

    public void apply(PaymentStateMachine paymentStateMachine) {
        // TODO Auto-generated method stub

    }

    public void resultNotify(PaymentStateMachine paymentStateMachine) {
        // TODO Auto-generated method stub

    }

    public void sync(PaymentStateMachine paymentStateMachine) {
        // TODO Auto-generated method stub

    }

    public void cancel(PaymentStateMachine paymentStateMachine) {
        throw new BusinessException(ResultInfo.SYS_INNER_ERROR.getCode(), "payfinish status can not cancel");

    }

}
PaymentStatePayFinish
@Service
public class PaymentStateWaitPay extends BaseLogger implements PaymentState {

    @Autowired
    FmPayClientService fmPayClientService;

    @Autowired
    SapoDao dao;

    @Autowired
    private JacksonComponent jacksonComponent;

    public void payResultNotify() {
        // TODO implement here
    }

    public void apply(PaymentStateMachine paymentStateMachine) {
        throw new BusinessException(ResultInfo.SYS_INNER_ERROR.getCode(),
                "applyPayPlatform not match payment state machine,currentStatus:"
                        + paymentStateMachine.getPayment().getStatus());

    }

    public void sync(PaymentStateMachine paymentStateMachine) {


        // TODO 過期去統一支付查詢

        String payStatus = queryPayResultResponse.getPayStatus();

        // 1:初始化輸入 2:支付中 3:支付成功 4:支付失敗 5:撤銷
        if (QueryPayResultResponse.PAY_STATUS_INIT.equals(payStatus)) {
            throw new BusinessException(ResultInfo.SYS_INNER_ERROR.getCode(),
                    "FMpay queryPay return init status ,we are waitpay");
        }

        if (QueryPayResultResponse.PAY_STATUS_ING.equals(payStatus)) {
            return;
        }

        SapoPayment sapoPaymentForUpdate = paymentStateMachine.getPaymentForUpdate();

        if (QueryPayResultResponse.PAY_STATUS_CANCEL.equals(payStatus)) {
            sapoPaymentForUpdate.setStatus(SapoPayment.STATUS_CANCEL);

        } else if (QueryPayResultResponse.PAY_STATUS_FAIL.equals(payStatus)) {
            sapoPaymentForUpdate.setStatus(SapoPayment.STATUS_FAIL);

        } else if (QueryPayResultResponse.PAY_STATUS_SUCCESS.equals(payStatus)) {
            sapoPaymentForUpdate.setStatus(SapoPayment.STATUS_PAY_FINISH);

        }
        sapoPaymentForUpdate.setExpireTime(null);

        paymentStateMachine.updateStateMachine();

    }

    public void resultNotify(PaymentStateMachine paymentStateMachine) {
        // TODO Auto-generated method stub

    }

    public void cancel(PaymentStateMachine paymentStateMachine) {
        throw new BusinessException(ResultInfo.SYS_INNER_ERROR.getCode(), "wait pay status can not cancel");

    }

}
PaymentStateWaitPay

 

 

 

本文來自博客園,作者:wanglifeng,轉載請注明原文鏈接:https://www.cnblogs.com/wanglifeng717/p/16214122.html

 

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

標籤:其他

上一篇:為什么我不能從Ionic/Angular中的模式回傳資料?

下一篇:Python搜索書名獲取整本資源_筆趣閣

標籤雲
其他(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