zoukankan      html  css  js  c++  java
  • MySQL 存储过程 经常使用语法

    MySQL 存储过程是从 MySQL 5.0 開始添加的新功能。存储过程的长处有一箩筐。只是最基本的还是运行效率和SQL 代码封装。特别是 SQL 代码封装功能,假设没有存储过程,在外部程序訪问数据库时(比如 PHP),要组织非常多 SQL 语句。特别是业务逻辑复杂的时候,一大堆的 SQL 和条件夹杂在 PHP 代码中,让人不寒而栗。如今有了 MySQL 存储过程,业务逻辑能够封装存储过程中,这样不仅easy维护,并且运行效率也高。
    一、MySQL 创建存储过程
    “pr_add” 是个简单的 MySQL 存储过程,这个存储过程有两个 int 类型的输入參数 “a”、“b”,返回这两个參数的和。
    drop procedure if exists pr_add;
    -- 计算两个数之和
    create procedure pr_add
    (
       a int,
       b int
    )
    begin
       declare c int;
       if a is null then
          set a = 0;
       end if;
       if b is null then
          set b = 0;
       end if;
       set c = a + b;
       select c as sum;
       /*
       return c;- 不能在 MySQL 存储过程中使用。return 仅仅能出如今函数中。
      /
    end;
    二、调用 MySQL 存储过程
    call pr_add(10, 20);
    运行 MySQL 存储过程,存储过程參数为 MySQL 用户变量。
    set @a = 10;
    set @b = 20;
    call pr_add(@a, @b);
    三、MySQL 存储过程特点
    创建 MySQL 存储过程的简单语法为:
    create procedure 存储过程名字()
    (
       [in|out|inout] 參数 datatype
    )
    begin
       MySQL 语句;
    end;
    MySQL 存储过程參数假设不显式指定“in”、“out”、“inout”,则默觉得“in”。习惯上,对于是“in” 的參数,我们都不会显式指定。
    1. MySQL 存储过程名字后面的“()”是必须的,即使没有一个參数,也须要“()”
    2. MySQL 存储过程參数,不能在參数名称前加“@”,如:“@a int”。以下的创建存储过程语法在 MySQL 中是错误的(在 SQL Server 中是正确的)。 MySQL 存储过程中的变量,不须要在变量名字前加“@”,尽管 MySQL client用户变量要加个“@”。
    create procedure pr_add
    (
       @a int,- 错误
       b int   - 正确
    )
    3. MySQL 存储过程的參数不能指定默认值。
    4. MySQL 存储过程不须要在 procedure body 前面加 “as”。而 SQL Server 存储过程必须加 “as” keyword。
    create procedure pr_add
    (
       a int,
       b int
    )
    as             - 错误,MySQL 不须要 “as”
    begin
       mysql statement ...;
    end;
    5. 假设 MySQL 存储过程中包括多条 MySQL 语句,则须要 begin end keyword。
    create procedure pr_add
    (
       a int,
       b int
    )
    begin
       mysql statement 1 ...;
       mysql statement 2 ...;
    end;
    6. MySQL 存储过程中的每条语句的末尾,都要加上分号 “;”
       ...
       declare c int;
       if a is null then
          set a = 0;
       end if;
       ...
    end;
    7. MySQL 存储过程中的凝视。
       /*
         这是个
         多行 MySQL 凝视。
      /
       declare c int;    - 这是单行 MySQL 凝视 (注意- 后至少要有一个空格)
       if a is null then 这也是个单行 MySQL 凝视
          set a = 0;
       end if;
       ...
    end;
    8. 不能在 MySQL 存储过程中使用 “return” keyword。
       set c = a + b;
       select c as sum;
       /*
       return c;- 不能在 MySQL 存储过程中使用。return 仅仅能出如今函数中。
      /
    end;
    9. 调用 MySQL 存储过程时候,须要在过程名字后面加“()”,即使没有一个參数,也须要“()”
    call pr_no_param();
    10. 由于 MySQL 存储过程參数没有默认值,所以在调用 MySQL 存储过程时候,不能省略參数。能够用 null 来替代。
    call pr_add(10, null);

     
    1,前提
    须要MySQL 5

    2,Hello World
    MySQL存储过程之Hello World
    Java代码 复制代码
    1. DELIMITER $$   
    2.   
    3. DROP PROCEDURE IF EXISTS HelloWorld$$   
    4. CREATE PROCEDURE HelloWorld()   
    5. BEGIN   
    6.     SELECT "Hello World!";   
    7. END$$   
    8.   
    9. DELIMITER ;  
    DELIMITER $$
    
    DROP PROCEDURE IF EXISTS HelloWorld$$
    CREATE PROCEDURE HelloWorld()
    BEGIN
        SELECT "Hello World!";
    END$$
    
    DELIMITER ;
    


    3,变量
    使用DECLARE来声明,DEFAULT赋默认值,SET赋值
    Java代码 复制代码
    1. DECLARE counter INT DEFAULT 0;   
    2. SET counter = counter+1;  
    DECLARE counter INT DEFAULT 0;
    SET counter = counter+1;
    


    4,參数
    IN为默认类型,值必须在调用时指定,值不能返回(值传递)
    OUT值能够返回(指针传递)
    INOUT值必须在调用时指定,值能够返回
    Java代码 复制代码
    1. CREATE PROCEDURE test(a INT, OUT b FLOAT, INOUT c INT)  
    CREATE PROCEDURE test(a INT, OUT b FLOAT, INOUT c INT)
    


    5,条件推断
    IF THENELSEIFELSEEND IF
    Java代码 复制代码
    1. DELIMITER $$   
    2.   
    3. DROP PROCEDURE IF EXISTS discounted_price$$   
    4. CREATE PROCEDURE discunted_price(normal_price NUMERIC(82), OUT discount_price NUMERIC(82))   
    5. BEGIN   
    6.     IF (normal_price > 500) THEN   
    7.         SET discount_price = normal_price * .8;   
    8.     ELSEIF (normal_price > 100) THEN   
    9.         SET discount_price = normal_price * .9;   
    10.     ELSE   
    11.         SET discount_price = normal_price;   
    12.     END IF;   
    13. END$$   
    14.   
    15. DELIMITER ;  
    DELIMITER $$
    
    DROP PROCEDURE IF EXISTS discounted_price$$
    CREATE PROCEDURE discunted_price(normal_price NUMERIC(8, 2), OUT discount_price NUMERIC(8, 2))
    BEGIN
        IF (normal_price > 500) THEN
            SET discount_price = normal_price * .8;
        ELSEIF (normal_price > 100) THEN
            SET discount_price = normal_price * .9;
        ELSE
            SET discount_price = normal_price;
        END IF;
    END$$
    
    DELIMITER ;
    


    6,循环
    LOOPEND LOOP
    Java代码 复制代码
    1. DELIMITER $$   
    2.   
    3. DROP PROCEDURE IF EXISTS simple_loop$$   
    4.   
    5. CREATE PROCEDURE simple_loop(OUT counter INT)   
    6. BEGIN   
    7.     SET counter = 0;   
    8.     my_simple_loop: LOOP   
    9.         SET counter = counter+1;   
    10.         IF counter = 10 THEN   
    11.             LEAVE my_simple_loop;   
    12.         END IF;   
    13.     END LOOP my_simple_loop;   
    14. END$$   
    15.   
    16. DELIMITER ;  
    DELIMITER $$
    
    DROP PROCEDURE IF EXISTS simple_loop$$
    
    CREATE PROCEDURE simple_loop(OUT counter INT)
    BEGIN
        SET counter = 0;
        my_simple_loop: LOOP
            SET counter = counter+1;
            IF counter = 10 THEN
                LEAVE my_simple_loop;
            END IF;
        END LOOP my_simple_loop;
    END$$
    
    DELIMITER ;
    

    WHILE DOEND WHILE
    Java代码 复制代码
    1. DELIMITER $$   
    2.   
    3. DROP PROCEDURE IF EXISTS simple_while$$   
    4.   
    5. CREATE PROCEDURE simple_while(OUT counter INT)   
    6. BEGIN   
    7.     SET counter = 0;   
    8.     WHILE counter != 10 DO   
    9.         SET counter = counter+1;   
    10.     END WHILE;   
    11. END$$   
    12.   
    13. DELIMITER ;  
    DELIMITER $$
    
    DROP PROCEDURE IF EXISTS simple_while$$
    
    CREATE PROCEDURE simple_while(OUT counter INT)
    BEGIN
        SET counter = 0;
        WHILE counter != 10 DO
            SET counter = counter+1;
        END WHILE;
    END$$
    
    DELIMITER ;
    

    REPEATUNTILL
    Java代码 复制代码
    1. DELIMITER $$   
    2.   
    3. DROP PROCEDURE IF EXISTS simple_repeat$$   
    4.   
    5. CREATE PROCEDURE simple_repeat(OUT counter INT)   
    6. BEGIN   
    7.     SET counter = 0;   
    8.     REPEAT   
    9.         SET counter = counter+1;   
    10.     UNTIL counter = 10 END REPEAT;   
    11. END$$   
    12.   
    13. DELIMITER ;  
    DELIMITER $$
    
    DROP PROCEDURE IF EXISTS simple_repeat$$
    
    CREATE PROCEDURE simple_repeat(OUT counter INT)
    BEGIN
        SET counter = 0;
        REPEAT
            SET counter = counter+1;
        UNTIL counter = 10 END REPEAT;
    END$$
    
    DELIMITER ;
    


    7,异常处理
    假设用cursor获取SELECT语句返回的全部结果集时应该定义NOT FOUND error handler来防止存储程序提前终结
    假设SQL语句可能返回constraint violation等错误时应该创建一个handler来防止程序终结

    8,数据库交互
    INTO用于存储单行记录的查询结果
    Java代码 复制代码
    1. DECLARE total_sales NUMERIC(82);   
    2. SELECT SUM(sale_value) INTO total_sales FROM sales WHERE customer_id=in_customer_id;  
    DECLARE total_sales NUMERIC(8, 2);
    SELECT SUM(sale_value) INTO total_sales FROM sales WHERE customer_id=in_customer_id;
    


    CURSOR用于处理多行记录的查询结果
    Java代码 复制代码
    1. DELIMITER $$   
    2.   
    3. DROP PROCEDURE IF EXITS cursor_example$$   
    4. CREATE PROCEDURE cursor_example()   
    5.     READS SQL DATA   
    6. BEGIN   
    7.     DECLARE l_employee_id INT;   
    8.     DECLARE l_salary NUMERIC(8,2);   
    9.     DECLARE l_department_id INT;   
    10.     DECLARE done INT DEFAULT 0;   
    11.     DECLARE cur1 CURSOR FOR SELECT employee_id, salary, department_id FROM employees;   
    12.     DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;   
    13.   
    14.     OPEN cur1;   
    15.     emp_loop: LOOP   
    16.         FETCH cur1 INTO l_employee_id, l_salary, l_department_id;   
    17.         IF done=1 THEN   
    18.             LEAVE emp_loop;   
    19.         END IF;   
    20.     END LOOP emp_loop;   
    21.     CLOSE cur1;   
    22. END$$   
    23. DELIMITER ;  
    DELIMITER $$
    
    DROP PROCEDURE IF EXITS cursor_example$$
    CREATE PROCEDURE cursor_example()
        READS SQL DATA
    BEGIN
        DECLARE l_employee_id INT;
        DECLARE l_salary NUMERIC(8,2);
        DECLARE l_department_id INT;
        DECLARE done INT DEFAULT 0;
        DECLARE cur1 CURSOR FOR SELECT employee_id, salary, department_id FROM employees;
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;
    
        OPEN cur1;
        emp_loop: LOOP
            FETCH cur1 INTO l_employee_id, l_salary, l_department_id;
            IF done=1 THEN
                LEAVE emp_loop;
            END IF;
        END LOOP emp_loop;
        CLOSE cur1;
    END$$
    DELIMITER ;
    


    unbounded SELECT语句用于存储过程返回结果集
    Java代码 复制代码
    1. DELIMITER $$   
    2. DROP PROCEDURE IF EXISTS sp_emps_in_dept$$   
    3. CREATE PROCEDURE sp_emps_in_dept(in_employee_id INT)   
    4. BEGIN   
    5.     SELECT employee_id, surname, firstname, address1, address2, zipcode, date_of_birth FROM employees WHERE department_id=in_employee_id;   
    6. END$$   
    7.   
    8. DELIMITER ;  
    DELIMITER $$
    DROP PROCEDURE IF EXISTS sp_emps_in_dept$$
    CREATE PROCEDURE sp_emps_in_dept(in_employee_id INT)
    BEGIN
        SELECT employee_id, surname, firstname, address1, address2, zipcode, date_of_birth FROM employees WHERE department_id=in_employee_id;
    END$$
    
    DELIMITER ;
    


    UPDATE、INSERT、DELETE、CREATE TABLE等非查询语句也能够嵌入存储过程里
    Java代码 复制代码
    1. DELIMITER $$   
    2.   
    3. DROP PROCEDURE IF EXITS sp_update_salary$$   
    4. CREATE PROCEDURE sp_update_salary(in_employee_id INT, in_new_salary NUMERIC(8,2))   
    5. BEGIN   
    6.     IF in_new_salary < 5000 OR in_new_salary > 500000 THEN   
    7.         SELECT "Illegal salary: salary must be between $5000 and $500, 000";   
    8.     ELSE   
    9.         UPDATE employees SET salary=in_new_salary WHERE employee_id=in_employee_id;   
    10.     END IF:   
    11. END$$   
    12.   
    13. DELIMITER ;  
    DELIMITER $$
    
    DROP PROCEDURE IF EXITS sp_update_salary$$
    CREATE PROCEDURE sp_update_salary(in_employee_id INT, in_new_salary NUMERIC(8,2))
    BEGIN
        IF in_new_salary < 5000 OR in_new_salary > 500000 THEN
            SELECT "Illegal salary: salary must be between $5000 and $500, 000";
        ELSE
            UPDATE employees SET salary=in_new_salary WHERE employee_id=in_employee_id;
        END IF:
    END$$
    
    DELIMITER ;
    


    9,使用CALL调用存储程序
    Java代码 复制代码
    1. DELIMITER $$   
    2.   
    3. DROP PROCEDURE IF EXISTS call_example$$   
    4. CREATE PROCEDURE call_example(employee_id INT, employee_type VARCHAR(20))   
    5.     NO SQL   
    6. BEGIN   
    7.     DECLARE l_bonus_amount NUMERIC(8,2);   
    8.   
    9.     IF employee_type='MANAGER' THEN   
    10.         CALL calc_manager_bonus(employee_id, l_bonus_amount);   
    11.     ELSE   
    12.         CALL calc_minion_bonus(employee_id, l_bonus_amount);   
    13.     END IF;   
    14.     CALL grant_bonus(employee_id, l_bonus_amount);   
    15. END$$   
    16. DELIMITER ;  
    DELIMITER $$
    
    DROP PROCEDURE IF EXISTS call_example$$
    CREATE PROCEDURE call_example(employee_id INT, employee_type VARCHAR(20))
        NO SQL
    BEGIN
        DECLARE l_bonus_amount NUMERIC(8,2);
    
        IF employee_type='MANAGER' THEN
            CALL calc_manager_bonus(employee_id, l_bonus_amount);
        ELSE
            CALL calc_minion_bonus(employee_id, l_bonus_amount);
        END IF;
        CALL grant_bonus(employee_id, l_bonus_amount);
    END$$
    DELIMITER ;
    


    10,一个复杂的样例
    Java代码 复制代码
    1. CREATE PROCEDURE putting_it_all_together(in_department_id INT)   
    2.     MODIFIES SQL DATA   
    3. BEGIN   
    4.     DECLARE l_employee_id INT;   
    5.     DECLARE l_salary NUMERIC(8,2);   
    6.     DECLARE l_department_id INT;   
    7.     DECLARE l_new_salary NUMERIC(8,2);   
    8.     DECLARE done INT DEFAULT 0;   
    9.   
    10.     DECLARE cur1 CURSOR FOR   
    11.         SELECT employee_id, salary, department_id   
    12.         FROM employees   
    13.         WHERE department_id=in_department_id;   
    14.   
    15.     DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;   
    16.   
    17.     CREATE TEMPORARY TABLE IF NOT EXISTS emp_raises   
    18.         (employee_id INT, department_id INT, new_salary NUMERIC(8,2));   
    19.   
    20.     OPEN cur1;   
    21.     emp_loop: LOOP   
    22.         FETCH cur1 INTO l_employee_id, l_salary, l_department_id;   
    23.         IF done=1 THEN    /* No more rows */  
    24.             LEAVE emp_loop;   
    25.         END IF;   
    26.         CALL new_salary(1_employee_id, l_new_salary); /* Get new salary */  
    27.         IF (l_new_salary <> l_salary) THEN  /* Salary changed */  
    28.             UPDATE employees   
    29.                 SET salary=l_new_salary   
    30.             WHERE employee_id=l_employee_id;   
    31.             /* Keep track of changed salaries */  
    32.             INSERT INTO emp_raises(employee_id, department_id, new_salary)   
    33.                 VALUES (l_employee_id, l_department_id, l_new_salary);   
    34.         END IF:   
    35.     END LOOP emp_loop;   
    36.     CLOSE cur1;   
    37.     /* Print out the changed salaries */  
    38.     SELECT employee_id, department_id, new_salary from emp_raises   
    39.         ORDER BY employee_id;   
    40. END;  
    CREATE PROCEDURE putting_it_all_together(in_department_id INT)
        MODIFIES SQL DATA
    BEGIN
        DECLARE l_employee_id INT;
        DECLARE l_salary NUMERIC(8,2);
        DECLARE l_department_id INT;
        DECLARE l_new_salary NUMERIC(8,2);
        DECLARE done INT DEFAULT 0;
    
        DECLARE cur1 CURSOR FOR
            SELECT employee_id, salary, department_id
            FROM employees
            WHERE department_id=in_department_id;
    
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;
    
        CREATE TEMPORARY TABLE IF NOT EXISTS emp_raises
            (employee_id INT, department_id INT, new_salary NUMERIC(8,2));
    
        OPEN cur1;
        emp_loop: LOOP
            FETCH cur1 INTO l_employee_id, l_salary, l_department_id;
            IF done=1 THEN    /* No more rows */
                LEAVE emp_loop;
            END IF;
            CALL new_salary(1_employee_id, l_new_salary); /* Get new salary */
            IF (l_new_salary <> l_salary) THEN  /* Salary changed */
                UPDATE employees
                    SET salary=l_new_salary
                WHERE employee_id=l_employee_id;
                /* Keep track of changed salaries */
                INSERT INTO emp_raises(employee_id, department_id, new_salary)
                    VALUES (l_employee_id, l_department_id, l_new_salary);
            END IF:
        END LOOP emp_loop;
        CLOSE cur1;
        /* Print out the changed salaries */
        SELECT employee_id, department_id, new_salary from emp_raises
            ORDER BY employee_id;
    END;
    


    11,存储方法
    存储方法与存储过程的差别
    1,存储方法的參数列表仅仅同意IN类型的參数,并且不是必需也不同意指定INkeyword
    2,存储方法返回一个单一的值,值的类型在存储方法的头部定义
    3,存储方法能够在SQL语句内部调用
    4,存储方法不能返回结果集
    语法:
    Java代码 复制代码
    1. CREATE   
    2.     [DEFINER = { user | CURRENT_USER }]   
    3.     PROCEDURE sp_name ([proc_parameter[,...]])   
    4.     [characteristic ...] routine_body   
    5.   
    6. CREATE   
    7.     [DEFINER = { user | CURRENT_USER }]   
    8.     FUNCTION sp_name ([func_parameter[,...]])   
    9.     RETURNS type   
    10.     [characteristic ...] routine_body   
    11.        
    12. proc_parameter:   
    13.     [ IN | OUT | INOUT ] param_name type   
    14.        
    15. func_parameter:   
    16.     param_name type   
    17.   
    18. type:   
    19.     Any valid MySQL data type   
    20.   
    21. characteristic:   
    22.     LANGUAGE SQL   
    23.   | [NOT] DETERMINISTIC   
    24.   | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }   
    25.   | SQL SECURITY { DEFINER | INVOKER }   
    26.   | COMMENT 'string'  
    27.   
    28. routine_body:   
    29.     Valid SQL procedure statement  
    CREATE
        [DEFINER = { user | CURRENT_USER }]
        PROCEDURE sp_name ([proc_parameter[,...]])
        [characteristic ...] routine_body
    
    CREATE
        [DEFINER = { user | CURRENT_USER }]
        FUNCTION sp_name ([func_parameter[,...]])
        RETURNS type
        [characteristic ...] routine_body
        
    proc_parameter:
        [ IN | OUT | INOUT ] param_name type
        
    func_parameter:
        param_name type
    
    type:
        Any valid MySQL data type
    
    characteristic:
        LANGUAGE SQL
      | [NOT] DETERMINISTIC
      | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
      | SQL SECURITY { DEFINER | INVOKER }
      | COMMENT 'string'
    
    routine_body:
        Valid SQL procedure statement
    

    各參数说明见CREATE PROCEDURE and CREATE FUNCTION Syntax
    样例:
    Java代码 复制代码
    1. DELIMITER $$   
    2.   
    3. DROP FUNCTION IF EXISTS f_discount_price$$   
    4. CREATE FUNCTION f_discount_price   
    5.     (normal_price NUMERIC(8,2))   
    6.     RETURNS NUMERIC(8,2)   
    7.     DETERMINISTIC   
    8. BEGIN   
    9.     DECLARE discount_price NUMERIC(8,2);   
    10.   
    11.     IF (normal_price > 500) THEN   
    12.         SET discount_price = normal_price * .8;   
    13.     ELSEIF (normal_price >100) THEN   
    14.         SET discount_price = normal_price * .9;   
    15.     ELSE   
    16.         SET discount_price = normal_price;   
    17.     END IF;   
    18.   
    19.     RETURN(discount_price);   
    20. END$$   
    21.   
    22. DELIMITER ;  
    DELIMITER $$
    
    DROP FUNCTION IF EXISTS f_discount_price$$
    CREATE FUNCTION f_discount_price
        (normal_price NUMERIC(8,2))
        RETURNS NUMERIC(8,2)
        DETERMINISTIC
    BEGIN
        DECLARE discount_price NUMERIC(8,2);
    
        IF (normal_price > 500) THEN
            SET discount_price = normal_price * .8;
        ELSEIF (normal_price >100) THEN
            SET discount_price = normal_price * .9;
        ELSE
            SET discount_price = normal_price;
        END IF;
    
        RETURN(discount_price);
    END$$
    
    DELIMITER ;
    


    12,触发器
    触发器在INSERT、UPDATE或DELETE等DML语句改动数据库表时触发
    触发器的典型应用场景是重要的业务逻辑、提高性能、监控表的改动等
    触发器能够在DML语句运行前或后触发
    Java代码 复制代码
    1. DELIMITER $$   
    2.   
    3. DROP TRIGGER sales_trigger$$   
    4. CREATE TRIGGER sales_trigger   
    5.     BEFORE INSERT ON sales   
    6.     FOR EACH ROW   
    7. BEGIN   
    8.     IF NEW.sale_value > 500 THEN   
    9.         SET NEW.free_shipping = 'Y';   
    10.     ELSE   
    11.         SET NEW.free_shipping = 'N';   
    12.     END IF;   
    13.   
    14.     IF NEW.sale_value > 1000 THEN   
    15.         SET NEW.discount = NEW.sale_value * .15;   
    16.     ELSE   
    17.         SET NEW.discount = 0;   
    18.     END IF;   
    19. END$$   
    20.   
    21. DELIMITER ;  

    转自:http://fyb613.blog.163.com/blog/static/325460922010044417672/

  • 相关阅读:
    【React Native】某个页面禁用物理返回键
    【React Native】DeviceEventEmitter监听通知及带参数传值
    转载【React Native代码】手写验证码倒计时组件
    【React Native】 中设置 APP 名称、应用图标、为安卓添加启动图
    【React Native错误集】* What went wrong: Execution failed for task ':app:installDebug'.
    【React Native错误集】Import fails with "Failed to execute 'ImportScripts' on 'WorkerGlobalScope'"
    【React Native错误集】Android error “Could not get BatchedBridge, make sure your bundle is packaged properly” on start of app
    「React Native笔记」在React的 setState 中操作数组和对象的多种方法(合集)
    【React Native】Error: Attribute application@allowBackup value=(false) from AndroidManifest.xml
    坚果云如何使用二次验证码/谷歌身份验证器/两步验证/虚拟MFA?
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/4314361.html
Copyright © 2011-2022 走看看