C++ 数据库MySQL 学习笔记(3) - 数据库操作

C++ 数据库MySQL 学习笔记(3) - 数据库操作

视图操作

视图是从一个或多个表中导出来的表,是一种虚拟存在的表。视图就像一个窗口,通过这个窗口可以看到系统专门提供的数据,这样用户可以不看整个数据库表中的数据,而只关心对自己有用的数据。视图可以使用户的操作更方便,而且可以保障数据库系统的安全性。

为什么要使用视图

通过前面章节的知识可以发现,数据库中关于数据的查询有时非常复杂,例如表连接、子查询等,这种查询会让程序员感到非常痛苦,因为它的逻辑太复杂、编写语句比较多,当这种查询需要重复使用时,很难每次都编写正确,从而降低了数据库的实用性。

在具体操作表之前,有时候要求只能操作部分字段,而不是全部字段。例如,在学校里,学生的智商测试结果一般都是保密的,如果因为一时疏忽向查询中多写了关于“智商”的字段,则会让学生的智商显示给所有能够查看该查询结果的人,这时就需要限制使用者操作的字段。

为了提高复杂的SQL语句的复用性和表的操作的安全性,MySQL数据库管理系统提供了视图特性。所谓视图,本质上是一种虚拟表,其内容与真实的表相似,包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储数据值的形式存在,行和列数据来自定义视图的查询所引用的基本表,并且在具体引用视图时动态生成。

视图使程序员只关心感兴趣的某些特定数据和他们所负责的特定任务。这样程序员只能看到视图中所定义的数据,而不是视图所引用表中的数据,从而提高数据库中数据的安全性。

创建视图

虽然视图可以被看成是一种虚拟表,但是其物理上是不存在的,即MySQL并没有专门的位置为视图存储数据。根据视图的概念可以发现其数据来源于查询语句,因此创建视图的基本语法为:

    CREATE[OR REPLACE] VIEW viewname[columnlist]   
    AS SELECT statement                               

其中,CREATE表示创建新的视图;REPLACE表示替换已经创建的视图;viewname为视图的名称;columnlist为属性列;SELECT statement表示SELECT语句;

注意! 创建视图需要登陆用户有相应的权限,

查看权限方法

mysql>  use school;   #选择数据库school                                                                       
mysql>  select user, Select_priv, Create_view_priv FROM mysql.user;#查询数据库用户创建和选择视图权限 

在单表上创建视图

mysql>  use school;   #选择数据库school 
mysql>  alter table student add privacy varchar(64);# 增加私隐列                                               
mysql>  ;#查询数据库用户创建和选择视图权限    
mysql>  CREATE VIEW view_student AS select id, class_id, name from student ;#为学生表创建视图 
mysql>  desc view_student;#查看视图 
mysql>  select * from view_student;   #根据视图进行查询  

在多表上创建视图

    CREATE[OR REPLACE] VIEW viewname[columnlist]   
    AS SELECT statement                               

其中,CREATE表示创建新的视图;REPLACE表示替换已经创建的视图;viewname为视图的名称;columnlist为属性列;SELECT statement表示SELECT语句;与单表上创建视图不同的是,SELECT子句是涉及到多表的联合查询语句。

mysql>  use school;   #选择数据库school 
mysql>  alter table student add privacy varchar(64);# 增加私隐列                                                                                      
mysql>  ;#查询数据库用户创建和选择视图权限    
mysql>  CREATE VIEW view_student_class AS select student.id, student.name, class.name,  class.teacher from class inner join student  on class.id = student.class_id;#为学生表创建视图 
mysql>  desc view_student_class;#查看视图 
mysql>  select * from view_student_class;   #根据视图进行查询  

查看视图

创建完视图后,像表一样,我们经常需要查看视图信息。在MySQL中,有许多可以实现查看视图的语句,如DESCRIBE、SHOW TABLES、SHOW CREATE VIEW。如果要使用这些语句,首先要确保拥有SHOW VIEW的权限。本节将详细讲解查看视图的方法。

使用DESCRIBE | DESC语句查看视图基本信息

前面我们已经详细讲解过使用DESCRIBE语句来查看表的基本定义。因为视图也是一张表,只是这张表比较特殊,是一张虚拟的表,所以同样可以使用DESCRIBE语句来查看视图的基本定义。DESCRIBE语句查看视图的语法如下:

    DESCRIBE | DESC viewname;

在上述语句中,参数viewname表示所要查看设计信息的视图名称。

使用SHOW TABLES语句查看视图基本信息

从MySQL 5.1版本开始,执行SHOW TABLES语句时不仅会显示表的名字,同时也会显示视图的名字。

下面演示通过SHOW TABLES语句查看数据库school中的视图和表的功能,具体SQL语句如下,执行结果如下图所示。

    SHOW TABLES;

在这里插入图片描述

使用show create view/table 语句查看视图创建信息

    SHOW CREATE TABLEVIEW viewname;   

更新视图数据

更新视图是指通过视图来插入(INSERT)、更新(UPDATE)和删除(DELETE)表中的数据。因为视图实质是一个虚拟表,其中没有数据,通过视图更新时都是转换到基本表更新。更新视图时,只能更新权限范围内的数据,超出范围就不能更新了。

mysql>  use school;   #选择数据库school 
mysql>  alter table student add privacy varchar(64);# 增加私隐列                                                                                      
mysql>  ;#查询数据库用户创建和选择视图权限    
mysql>  CREATE VIEW view_student AS select id, class_id, name from student ;#为学生表创建视图 
mysql>  desc view_student;#查看视图 
mysql>  select * from view_student;   #根据视图进行查询  
mysql>  update view_student set name='小花花' where name='小花'; #通过视图更新小花为小花花

不能更新的情况:

  1. 视图中包含SUM()、COUNT()、MAX()和MIN()等函数

  2. 视图中包含UNION、UNION ALL、DISTINCT、GROUP BY和HAVING等关键字

  3. 视图对应的表存在没有默认值的列,而且该列没有包含在视图里

  4. 包含子查询的视图

  5. 其他特殊情况

修改视图

修改视图是指修改数据库中存在的视图,当基本表的某些字段发生变化的时候,可以通过修改视图来保持与基本表的一致性。ALTER语句来修改视图。

使用ALTER语句修改视图

ALTER VIEW viewname[columnlist]   
AS SELECT statement                               

这个语法中的所有关键字和参数除了alter 外,其他都和创建视图是一样的,因此不再赘述。

mysql>  use school;   #选择数据库school 
mysql>  alter table student add privacy varchar(64);# 增加私隐列                                                                                      
mysql>  ;#查询数据库用户创建和选择视图权限    
mysql>  ALTER VIEW view_student_class AS select student.id, student.name, class.name, class.id as class_id, class.teacher from class inner join student  on class.id = student.class_id;#为学生班级表视图增加 class_id 字段 
mysql>  desc view_student_class;#查看视图 
mysql>  select * from view_student_class;   #根据视图进行查询  

删除视图

删除视图是指删除数据库中已存在的视图。删除视图时,只能删除视图的定义,不会删除数据。

在MySQL中,可使用DROP VIEW语句来删除视图,但是用户必须拥有DROP权限。删除视图的语法如下:

    DROP VIEW viewname [viewname]

在上述语句中,参数viewname表示所要删除视图的名称,可同时指定删除多个视图。

mysql>  use school;   #选择数据库school      
mysql>  ;#查询数据库用户创建和选择视图权限    
mysql>  CREATE VIEW view_student_class AS select student.id, student.name, class.name, class.id as class_id, class.teacher from class inner join student  on class.id = student.class_id;#为学生表创建视图 
mysql>  drop view view_student_class;#删除视图 

触发器

触发器(TRIGGER)是由事件来触发某个操作。这些事件包括INSERT语句、UPDATE语句和DELETE语句。当数据库系统执行这些事件时,就会激活触发器执行相应的操作。MySQL从5.0.2版本开始支持触发器。

通过本章的学习,我们将了解触发器的含义和作用、如何创建触发器、查看触发器和删除触发器的方法。同时,可以了解各种事件的触发器的执行情况。

创建触发器

在MySQL中创建触发器通过SQL语句CREATE TRIGGER来实现,其语法形式如下:

       CREATE trigger trigger_name BEFORE|AFTER trigger_EVENT     
       ON TABLE_NAME FOR EACH ROW trigger_STMT               

在上述语句中,参数trigger_name表示要创建的触发器名;

参数BEFORE和AFTER指定了触发器执行的时间,前者在触发器事件之前执行触发器语句,后者在触发器事件之后执行触发器语句;

参数trigger_EVENT表示触发事件,即触发器执行条件,包含DELETE、INSERT和UPDATE语句;参数TABLE_NAME表示触发事件的操作表名;参数FOR EACH ROW表示任何一条记录上的操作满足触发事件都会触发该触发器;

参数trigger_STMT表示激活触发器后被执行的语句。执行语句中如果要引用更新记录中的字段,对于INSERT语句,只有NEW是合法的,表示当前已插入的记录;对于DELETE语句,只有OLD才合法,表示当前删除的记录;而UPDATE语句可以和NEW(更新后)以及OLD(更新前)同时使用

**注意:**不能创建具有相同名字的触发器。另外,对于具有相同触发程序动作时间和事件的给定表,不能有两个触发器。因此,对于有经验的用户,在创建触发器之前,需要查看MySQL中是否已经存在该标识符的触发器和触发器的相关事件。

**【示例10-1】**执行SQL语句CREATE TRIGGER,在数据库school中存在两个表对象:学员表student和班级表 class,创建触发器实现向学员表中插入记录时,就会在插入之后更新班级表中的人数,当我们删除某条学员的记录时,就会在删除后更新班级表中的人数,具体步骤如下:

mysql>  use school;   #选择数据库school                                           
mysql>  CREATE TABLE class (                                                        
  `id` int NOT NULL AUTO_INCREMENT,                                              
  `name` varchar(128) DEFAULT NULL,                                               
  `teacher` varchar(64) DEFAULT NULL,  
  `count`  int DEFAULT 0,                                           
  UNIQUE KEY `id` (`id`)                                                              
);  #创建班级表 class                                                                 
mysql> insert into class values(101, '萌新一班', 'Martin', 0),(102, '萌新二班', 'Rock', 0),(103, '萌新三班', 'Janny', 0);  #创建成绩表 grade                                                 
mysql>  CREATE TABLE `student` (                                                  
  `id` int NOT NULL AUTO_INCREMENT UNIQUE,                                                            
  `name` varchar(64) DEFAULT NULL,                                                
  `class_id` int DEFAULT NULL,                                                      
  `sex` enum('F','M') DEFAULT NULL                                                  
); 
mysql> create trigger tri_insert_student after insert on student for each row update class set count=count+1 where class.id = NEW.class_id;   #创建触发器,新增学员班级人数增1
                                                                               
mysql> insert into student values(1,'小花',101,'M'),(2,'小红',102, 'F'),(3,'小军',102,'F'),(4,'小白',101,'F');  #插入多条记录   
mysql> select count from class  ;  #查询class 表人数  
mysql> create trigger tri_delete_student after delete on student for each row update class set count=count-1 where id = OLD.class_id; #创建触发器,删除学员班级人数减1

触发器包含多条执行语句

       CREATE   trigger trigger_name BEFORE|AFTER trigger_EVENT     
       ON TABLE_NAME FOR EACH ROW                                 
           BEGIN                                                        
            trigger_STMT                                                 
           END                                                           

在上述语句中,比“只有一条执行语句的触发器”语法多出来两个关键字BEGIN和END,在这两个关键字之间是所要执行的多个执行语句的内容,执行语句之间用分号隔开。

在MySQL中,一般情况下用“;”符号作为语句的结束符号,可是在创建触发器时,需要用到“;”符号作为执行语句的结束符号。为了解决该问题,可以使用关键字DELIMITER语句。例如,“DELIMITER ”可以将结束符号设置成“$$”。

mysql>  use school;   #选择数据库school         
mysql>  create table grade(id int UNIQUE AUTO_INCREMENT,  math tinyint unsigned, chinese tinyint unsigned, english tinyint unsigned);       #创建成绩表 grade   
mysql> insert into grade values(1, 80, 87, 91),(2, 72, 64, 89),(3, 54, 69, 87),(4, 78, 79, 89);  #插入多条记录                                      
mysql> DELIMITER $$                                                                                
mysql> create trigger tri_delete_student after delete on student for each row 
        BEGIN                                    
         Delete from grade where id = OLD.id;  #删除成绩表中的记录                                                        
         update class set count=count-1 where id = OLD.class_id; #更新班级表中的记录   
         END;                                    
         $$                                       
         DELIMITER ;                             

查看触发器

SHOW TRIGGERS语句查看触发器

那么如何查看MySQL软件中已经存在的触发器呢?在MySQL软件中查看已经存在的触发器,通过SQL语句SHOW TRIGGERS来实现,其语法形式如下,执行上面的SQL语句,执行结果如图9-10所示。

  SHOW TRIGGERS ;  

通过图9-10的执行结果可以发现,执行完“SHOW TRIGGERS”语句后会显示一个列表,在该列表中会显示出所有触发器的信息。其中,参数Trigger表示触发器的名称;参数Event表示触发器的激发事件;参数Table表示触发器对象触发事件所操作的表;参数Statement表示触发器激活时所执行的语句;参数Timing表示触发器所执行的时间。

在这里插入图片描述

查看系统表triggers实现查看触发器

在MySQL中,在系统数据库information_schema中存在一个存储所有触发器信息的系统表triggers,因此查询该表格的记录也可以实现查看触发器功能。系统表triggers的表结构

mysql>  use information_schema;   #选择数据库information_schema                  
mysql>  select * from triggers;                                                        
mysql>  select * from triggers where trigger_name=’tri_delete_student’; #查询系统表triggers中的触发器           

触发器的删除

在MySQL软件中,可以通过DROP TRIGGER语句或通过工具来删除触发器。

在MySQL中,删除触发器可以通过SQL语句DROP TRIGGER来实现,其语法形式如下:

    DROP TRIGGER trigger_name; 

在上述语句中,参数trigger_name表示所要删除的触发器名称。

存储过程和函数

存储过程和函数是在数据库中定义的一些SQL语句的集合,然后直接调用这些存储过程和函数来执行已经定义好的SQL语句。存储过程和函数可以避免开发人员重复编写相同的SQL语句。而且,存储过程和函数是在MySQL服务器中存储和执行的,可以减少客户器端和服务端的数据传输。

创建存储过程

创建存储过程和函数是指将经常使用的一组SQL语句组合在一起,并将这些SQL语句当作一个整体存储在MySQL服务器中。存储程序可以分为存储过程和函数。在MySQL中创建存储过程使用的语句CREATE PROCEDURE。其语法形式如下:

CREATE PROCEDURE procedure_name([proc_param[,]])    
         routine_body                                     

在上述语句中,参数procedure_name表示所要创建的存储过程名字,参数proc_param表示存储过程的参数,参数routine_body表示存储过程的SQL语句代码,可以用BEGIN…END来标志SQL语句的开始和结束。

提示: 在具体创建存储过程时,存储过程名不能与已经存在的存储过程名重名,实战中推荐存储过程名命名为procedure_xxx或者proc_xxx。

proc_param中每个参数的语法形式如下:

    [IN|OUT|INOUT] param_name type

在上述语句中,每个参数由三部分组成,分别为输入/输出类型、参数名和参数类型。其中,输入/输出类型有三种类型,分别为IN(表示输入类型)、OUT(表示输出类型)、INOUT(表示输入/输出类型)。param_name表示参数名;type表示参数类型,可以是MySQL软件所支持的任意一个数据类型。

mysql>  use school;   #选择数据库school                                             
mysql> DELIMITER $$                                                                                
mysql> create PROCEDURE  proc_delete_student (IN sid int )                                   
        BEGIN 
         declare cid  int ;   #定义变量cid                            
         Select class_id into cid from student where id = sid;    #通过查询语句设置变量                                                                       
         delete from grade where id = sid;  #删除成绩表中的记录  
         delete from student where id = sid;   #删除学生表中的记录                                                    
         update class set count=count-1 where id = cid; #更新班级表中的记录   
        END;                                                                        
        $$                                                                           
         DELIMITER ;                                                                 
mysql>  call proc_delete_student(2);    #调用存储过程                                                        

在存储过程中使用变量

在存储过程和函数中,可以定义和使用变量。用户可以使用关键字DECLARE来定义变量,然后为变量赋值。这些变量的作用范围是在BEGIN…END程序段中。

  1. 定义变量

在MySQL中,可以使用DECLARE关键字来定义变量。定义变量的基本语法如下:

    DECLARE var_name[,] type [DEFAULT value]   

其中,关键字DECLARE是用来声明变量的;参数var_name是变量的名称,可以同时定义多个变量;参数type用来指定变量的类型;DEFAULT value子句将变量默认值设置为value,没有使用DEFAULT子句时,默认值为NULL。

定义变量cid,数据类型为INT型,默认值为10,代码如下:

  DECLARE cid INT DEFAULT 10;    
  1. 为变量赋值

在MySQL中可以使用关键字SET来为变量赋值,SET语句的基本语法如下:

SET var_name=expr[,var_name=expr]

其中,关键字SET用来为变量赋值;参数var_name是变量的名称;参数expr是赋值表达式。一个SET语句可以同时为多个变量赋值,各个变量的赋值语句之间用逗号隔开。

例如,将变量tmp_id赋值为88,代码如下:

SET tmp_id = 88;

在MySQL中,还可以使用SELECT…INTO语句为变量赋值。其基本语法如下:

     SELECT col_name[,] INTO var_name[,]     
     FROM table_name WHERE condition         

其中,参数col_name表示查询的字段名称;参数var_name是变量的名称;参数table_name指表的名称;参数condition指查询条件。

从表student中查询id为4的记录,将该记录的id值赋给变量tmp_id,代码如下:

mysql>  use school;   #选择数据库school                                             
mysql>  drop  PROCEDURE if exists query_student_class_info;                                                                             
mysql> DELIMITER $$                                                                                
mysql> create procedure  query_student_class_info (IN sid int, OUT cname varchar(128), OUT ccount  int)                                   
        BEGIN   
            declare tmp_name varchar(128);
            declare tmp_count int;
            declare tmp_cid  int;
            select class_id into tmp_cid from student where id = sid;         
            select name, count into tmp_name, tmp_count from class where id = tmp_cid;
            set cname = tmp_name, ccount = tmp_count;
         END;    
         $$                                                                           
         DELIMITER ;                                                                 
mysql>  call query_student_class_info(4, @name, @count);    #调用存储过程  
mysql>  select @name, @count;                                                         

光标的使用

查询语句可能查询出多条记录,在存储过程和函数中使用光标来逐条读取查询结果集中的记录。有些书上将光标称为游标。光标的使用包括声明光标、打开光标、使用光标和关闭光标。光标必须声明在处理程序之前,并且声明在变量和条件之后

声明光标

在MySQL中,可以使用DECLARE关键字来声明光标,其基本语法如下:

    DECLARE cursor_name CURSOR         
    FOR select_statement;                  

其中,参数cursor_name表示光标的名称;参数select_statement表示SELECT语句的内容。

**【示例11-2】**下面声明一个名为cur_student的光标,代码如下:

mysql>  use school;   #选择数据库school                                                                                                                         
mysql> DELIMITER $$                                                                                
mysql> create procedure  query_student (IN sid int, OUT cname varchar(128), OUT class_id  int )                                      
        BEGIN                                                  
            DECLARE cur_student CURSOR                     
                FOR SELECT name, class_id FROM  student;    
         END;                                                  
         $$                                                                           
         DELIMITER ;                                                                                        

在上面的示例中,光标的名称为cur_student;SELECT语句部分是从表student中查询出字段name和class_id的值。

打开光标

在MySQL中,使用关键字OPEN来打开光标,其基本语法如下:

OPEN cursor_name;

其中,参数cursor_name表示光标的名称。

下面代码打开一个名为cur_student的光标,代码如下:

OPEN cur_student;

使用光标

在MySQL中,使用关键字FETCH来使用光标,其基本语法如下:

FETCH cursor_name
INTO var_name[,var_name…];

其中,参数cursor_name表示光标的名称;参数var_name表示将光标中的SELECT语句查询出来的信息存入该参数中。var_name必须在声明光标之前就定义好。

**【示例11-3】**下面声明一个名为cur_student的光标,代码如下:

mysql>  use school;   #选择数据库school                                                                                                                         
mysql> DELIMITER $$                                                                                
mysql> create procedure query_student (IN sid int, OUT cname varchar(128), OUT cid int)                                                                                    
        BEGIN                                                                             
            declare tmp_name varchar(128);    #必须定义在声明光标之前                                                                                             
            declare tmp_cid  int;                                                           
            declare  done int default 0;                                                                                                                                                
            declare cur_student CURSOR FOR SELECT name, class_id FROM  student where id = sid;                                                                                      
            declare continue handler for not found set done = 1; #将结束标志绑定到游标上                                                                             
            open  cur_student;                                                             
            select done;                                                                    
            fetch cur_student into tmp_name, tmp_cid;                                        
            select done;                                                                                      
            select tmp_name, tmp_cid;         #打印从光标中获取到的值                                                
            close cur_student;                                                              
            set cname = tmp_name, cid = tmp_cid;                                                                                                                      
         END;                                                                              
mysql>  $$                                                                                 
mysql>  DELIMITER ; 

关闭光标

在MySQL中,使用关键字CLOSE来关闭光标,其基本语法如下:

CLOSE cursor_name;

其中,参数cursor_name表示光标的名称。

例如: 关闭一个名为cur_student的光标,代码如下:

CLOSE cur_student;

在上面的示例中,关闭了这个名称为cur_student的光标。关闭了之后就不能使用FETCH来使用光标了。提示

如果存储过程或函数中执行了SELECT语句,并且SELECT语句会查询出多条记录,这种情况最好使用光标来逐条读取记录,光标必须在处理程序之前且在变量和条件之后声明,而且光标使用完毕后一定要关闭。

流程控制的使用

在存储过程和函数中,可以使用流程控制来控制语句的执行。在MySQL中,可以使用IF语句、CASE语句、LOOP语句、LEAVE语句、ITERATE语句、REPEAT语句和WHILE语句来进行流程控制

if语句

IF语句用来进行条件判断。根据条件执行不同的语句。其语法的基本形式如下:

IF search_condition THEN statement_list       
[ELSEIF search_condition THEN statement_list] ...
[ELSE statement_list]                           
END  IF                                      

参数search_condition表示条件判断语句;参数statement_list表示不同条件的执行语句。

**[示例11-4]**下面是一个IF语句的示例,代码如下:

 IF age>20 THEN SET @count1=@count1+1;  

  ELSEIF age=20 THEN @count2=@count2+1;

  ELSE @count3=@count3+1;        

 END IF;                   

该示例根据age与20的大小关系来执行不同的SET语句。如果age值大于20,将count1的值加1;如果age值等于20,就将count2的值加1;其他情况将count3的值加1。IF语句都需要使用END IF来结束。

mysql>  use school;   #选择数据库school                                                                                                                         
mysql> DELIMITER $$                                                                                
mysql> create procedure proc_test_if (IN input int, OUT output int)
        begin
            if input>20 then set input=input+1;
            elseif input=20 then  set input=input+2;
            else  set input = input+3;
            end if;

            set output = input;
        end; 
mysql>  $$                                                                                 
mysql>  DELIMITER ;                                                                                         

CASE语句

CASE语句可实现比IF语句更复杂的条件判断,其语法的基本形式如下:

CASE case_value                                  
WHEN when_value THEN statement_list            
[ WHEN when_value THEN statement_list ]          
[ELSE statement_list]                               
END CASE                                         

其中,参数case_value表示条件判断的变量;参数when_value表示变量的取值;参数statement_list表示不同when_value值的执行语句

**[示例11-5]**下面是一个CASE语句的示例。代码如下:

CASE level                                    
      WHEN 20 THEN SET attack = attack + 5;  
      WHEN 30 THEN SET attack = attack + 10; 
      WHEN 40 THEN SET attack = attack + 15; 
      ELSE SET attack = attack + 1; 
END CASE            

当级别level值为20时,attack值加5;当级别level值为30时,attack值加10;当级别level值为40时,attack值加15;否则,attack + 1。CASE语句使用END CASE结束。

LOOP 语句

LOOP语句可以使某些特定的语句重复执行,实现一个简单的循环。LOOP语句本身没有停止循环,只有遇到LEAVE语句等才能停止循环。LOOP语句的语句形式如下:

   [begin_label:] LOOP             
   statement_list                  
   END LOOP [end_label]          

其中,参数begin_label和参数end_label分别表示循环开始和结束的标志,这两个标志必须相同,而且都可以省略;参数statement_list表示需要循坏执行的语句。

示例11-6】下面是一个LOOP语句的示例,代码如下:

add_num:LOOP                 
     SET @count = @count + 1; 
END LOOP add_num;            

该示例循环执行count加1的操作。因为没有跳出循环的语句,这个循环成了一个死循环。LOOP循环都以END LOOP结束。

LEAVE语句

LEAVE语句主要用于跳出循环控制,其语法形式如下:

LEAVE label                  

其中,参数label表示循环的标志。

示例11-7】下面是一个LEAVE语句的示例。代码如下:

add_num: LOOP             
SET @count=@count + 1;
Select @count;
IF @count = 100 THEN 
    LEAVE add_num;     
END IF;
END LOOP add_num;         

该示例循环执行count值加1的操作。当count的值等于100时,LEAVE语句跳出循环。

ITERATE 语句

ITERATE语句也是用来跳出循环的语句,但是ITERATE语句是跳出本次循环,然后直接进入下一次循环,ITERATE语句的语法形式如下:

  ITERATE label  

其中,参数label表示循环的标志。

示例11-8】下面是一个ITERATE语句的示例。代码如下:

add_num1:LOOP              
    Set @count = @count +1 
    IF @count=100 THEN     
        LEAVE add_num1       
    ELSE IF MOD(@count, 3) = 0 then
        ITERATE add_num1;   
     Select * from student;    
END LOOP add_num1;       

该示例循环执行count加1的操作,count的值为100时结束循环。如果count的值能够整除3,就跳出本次循环,不再执行下面的SELECT语句。

注意: LEAVE语句和ITERATE语句都用来跳出循环语句,但是两者的功能是不一样的。LEAVE语句是跳出整个循环,然后执行循环后面的程序,和C++ break 相似。ITERATE语句是跳出本次循环,然后进入下一次循环,和C++ continue 相似。使用这两个语句时一定要区分清楚。

REPEAT 语句

REPEAT语句是有条件控制的循环语句。当满足特定条件时,就会跳出循环语句。REPEAT语句的基本语法形式如下:

[begin_label:] REPEAT        
        statement_list;       
     UNTIL search_condition 
END REPEAT [end_label]     

其中,参数statement_list表示循环的执行语句;参数search_condition表示结束循环的条件,满足该条件时循环结束。

示例11-9】下面是一个REPEAT语句的示例。代码如下:

REPEAT                        
     SET @count=@count+1;   
     UNTIL @count=100        
END REPEAT;                   

该示例循环执行count加1的操作,count值为100时结束循环。REPEAT循环都用END REPEAT结束。

WHILE 语句

WHILE语句也是有条件控制的循环语句,但WHILE语句和REPEAT语句是不一样的。WHILE语句是当满足条件时执行循环内的语句。WHILE语句的基本语法形式如下:

[begin_label:] WHILE search_condition DO   
        Statement_list                      
END WHILE [end_label]                     

其中,参数statement_condition表示循环执行的条件,满足该条件时循环执行;参数statement_list表示循环的执行语句。

示例11-10】下面是一个WHILE语句的示例。代码如下:

WHILE @count<100 DO       
    SET @count = @count + 1;
END WHILE;                  

流程控制综合运用

**【示例11-11】**循环访问光标操作,访问光标中的所有记录,代码如下:

mysql>  use school;   #选择数据库school                                                                                                                         
mysql> DELIMITER $$                                                                                
mysql> create procedure query_all_students (IN sid int, OUT cname varchar(128), OUT cid int)                                                                                    
        BEGIN                                                                             
            declare tmp_name varchar(128);    #必须定义在声明光标之前                                                                                             
            declare tmp_cid  int;                                                           
            declare  done int default 0;                                                                                                                                                
            declare cur_student CURSOR FOR SELECT name, class_id FROM  student ;                                                                                      
            declare continue handler for not found set done = 1; #将结束标志绑定到游标上                                                                             
            open  cur_student;                                                             
            read_loop:LOOP      #循环读取                                                                   
                fetch cur_student into tmp_name, tmp_cid;                                                                                                                              
                IF done=1 then                                                              
                    Leave read_loop;                                                                  
                END IF;                                                                     
                select tmp_name, tmp_cid;         #打印从光标中获取到的值                                                                                       
            END LOOP read_loop;                                                                                
            close cur_student;                                                              
            set cname = tmp_name, cid = tmp_cid;                                                                                                                      
         END;                                                                              
mysql>  $$                                                                                 
mysql>  DELIMITER ;                                  

【示例11-12】在学生表中插入一条记录,并返回记录的自增长id

mysql>  use school;   #选择数据库school                                                                                                                         
mysql> DELIMITER $$                                                                                
mysql> create procedure fetch_insert_student_id (IN p_name varchar(128), in p_class_id int, IN p_sex char(1), OUT rid int)                                                                                    
        BEGIN                                                                             
            Insert into student (name, class_id, sex) values(p_name, p_class_id, p_sex);                                                                                
            select last_insert_id() as rid;                                                                                                                     
         END;                                                                              
mysql>  $$                                                                                 
mysql>  DELIMITER ;                                                                                         

查看存储过程

存储过程创建以后,用户可以通过SHOW STATUS语句来查看存储过程的状态,也可以通过SHOW CREATE语句来查看存储过程的定义。用户也可以通过查询information_schema数据库下的Routines表来查看存储过程的信息。本节将详细讲解查看存储过程的状态与定义的方法。

SHOW STATUS语句查看存储过程

在MySQL中,可以通过SHOW STATUS语句。其基本语法形式如下:

SHOW PROCEDURE STATUS  [ like ‘pattern’ ] ;  

其中,参数PROCEDURE表示查询存储过程;参数LIKE 'pattern’用来匹配存储过程的名称。

图11-13的执行结果显示了存储过程的创建时间、修改时间和字符集等信息。

在这里插入图片描述

使用SHOW CREATE语句查看存储过程的定义

在MySQL中,可以通过SHOW CREATE语句查看存储过程的状态,语法形式如下:

SHOW CREATE PROCEDURE proc_name      

其中,参数PROCEDURE表示查询存储过程;参数proc_name表示存储过程的名称。

—【示例11-14】查询名为proc_delete_student的存储过程的状态,代码如下,执行结果如下图所示。

SHOW CREATE PROCEDURE proc_delete_student \G

在这里插入图片描述

从information_schema.Routine表中查看存储过程的信息

存储过程和函数的信息存储在information_schema数据库下的Routines表中。可以通过查询该表的记录来查询存储过程和函数的信息。其基本语法形式如下:

SELECT * FROM information_schema.Routines
               Where ROUTINE_NAME = ‘proc_name’;

其中,字段ROUTINE_NAME是Routines 存储存储过程和函数的列名称;参数proc_name表示存储过程或函数的名称。

—【示例11-15】下面从Routines表中查询名为proc_delete_student的存储过程信息,具体SQL代码如下,执行结果如下图所示。

select routine_definition from information_schema.Routines where routine_name='proc_delete_student';  

在这里插入图片描述

存储过程的删除

在MySQL中删除存储过程通过SQL语句DROP完成:

    DROP PROCEDURE proc_name;            

在上述语句中,关键字DROP PROCEDURE用来表示实现删除存储过程,参数proc_name表示所要删除的存储过程名称。

—【示例11-16】执行SQL语句DROP PROCEDURE,删除存储过程对象proc_delete_student,具体步骤如下:

    DROP PROCEDURE proc_delete_student;

数据库的存储引擎

MySQL中存在多种存储引擎的概念。简而言之,存储引擎就是指表的类型。在具体开发时,为了提高MySQL数据库管理系统的使用效率和灵活性,可以根据实际需要来选择存储引擎。因为存储引擎指定了表的类型,即如何存储和索引数据、是否支持事务等,同时存储引擎也决定了表在计算机中的存储方式。

MySQL支持的存储引擎

用户在选择存储引擎之前,首先需要确定数据库管理系统支持哪些存储引擎。在MySQL数据库管理系统,通过SHOW ENGINES来查看支持的存储引擎,语法如下:

SHOW ENGINES;           

在MySQL中执行SHOW ENGINES的结果如下图12-1所示。

在这里插入图片描述

在创建表时,若没有指定存储引擎,表的存储引擎将为默认的存储引擎。如果需要操作默认存储引擎,首先需要查看默认存储引擎。可以使用下面的SQL语句来查询默认存储引擎,执行结果如图12-2所示。

SHOW VARIABLES LIKE 'default_storage_engine';    

在这里插入图片描述

接下来简单介绍开发中常用的几种常见的存储引擎。

InnoDB存储引擎

InnoDB是MySQL数据库的一种存储引擎。InnoDB给MySQL的表提供了事务、回滚、崩溃修复能力和多版本并发控制的事务安全。MySQL从3.23.34a开始就包含InnoDB存储引擎。InnoDB是MySQL第一个提供外键约束的表引擎,而且InnoDB对事务处理的能力也是MySQL对其他存储引擎所无法与之比拟的。

MySQL 5.6版本之后,除系统数据库之外,默认的存储引擎由MyISAM改为InnoDB,MySQL 8.0版本在原先的基础上将系统数据库的存储引擎也改为了InnoDB。

InnoDB存储引擎中支持自动增长列AUTO_INCREMENT。自动增长列的值不能为空,且值必须唯一。MySQL中规定自增列必须为主键。在插入值时,如果自动增长列不输入值,那么插入的值为自动增长后的值;如果输入的值为0或空(NULL),那么插入的值也为自动增长后的值;如果插入某个确定的值,且该值在前面没有出现过,那么可以直接插入。

InnoDB存储引擎中支持外键(FOREIGN KEY)。外键所在的表为子表,外键所依赖的表为父表。父表中被子表外键关联的字段必须为主键。当删除、更新父表的某条信息时,子表也必须有相应的改变。

InnoDB存储引擎的优势在于提供了良好的事务管理、崩溃修复能力和并发控制;缺点是其读写效率稍差,占用的数据空间相对比较大。

MyISAM存储引擎

MyISAM存储引擎是MySQL中常见的存储引擎,曾是MySQL的默认存储引擎。MyISAM存储引擎是基于ISAM存储引擎发展起来的。MyISAM增加了很多有用的扩展。

MyISAM存储引擎的表存储成3个文件。文件的名字与表名相同,或站名包括frm、MYD和MYI。其中,frm为扩展名的文件存储表的结构;MYD为扩展名的文件存储数据,是MYData的缩写;MYI为扩展名的文件存储索引,是MYIndex的缩写。

基于MyISAM存储引擎的表支持3种存储格式,包括静态型、动态型和压缩型。其中,静态型为MyISAM存储引擎的默认存储格式,其字段是固定长度的;动态型包含变长字段,记录的长度不是固定的;压缩型需要使用myiampack工具创建,占用的磁盘空间较小。

MyISAM存储引擎的优势在于占用空间小,处理速度快;缺点是不支持事务的完整性和并发性。

MEMORY 存储引擎

MEMORY存储引擎是MySQL中一类特殊存储引擎。其使用存储在内存中的内容来创建表,而且所有数据也放在内存中。这些特性都与InnoDB存储引擎、MyISAM存储引擎不同。

每个基于MEMORY存储引擎的表实际对应一个磁盘文件,该文件的文件名与表名相同,类型为frm类型,该文件中只存储表的结构,而其数据文件都是存储在内存中的。这样有利于数据的快速处理,提供整个表的处理效率。值得注意的是,服务器需要有足够的内存来维持MEMORY存储引擎的表的使用。如果不需要使用了,可以释放这些内存,甚至可以删除不需要的表。

MEMORY存储引擎默认使用哈希(HASH)索引。其速度要比使用B型树(BTREE)索引快。如果读者希望使用B型树索引,可以在创建索引时选择使用。

MEMORY表的大小是受到限制的。表的大小主要取决于两个参数,分别是max_rows和max_heap_table_size。其中,max_rows可以在创建表时指定;max_heap_table_size的大小默认为16MB,可以按需要进行扩大。因此,其存在于内存中的特性,这类表的处理速度非常快。但是,其数据易丢失,生命周期短。基于这个缺陷,选择MEMORY存储引擎时需要特别小心。

选择存储引擎

在具体使用MySQL数据库管理系统时,选择一个合适的存储引擎是非常复杂的问题。因为每种存储引擎都有自己的特性、优势和应用场合,所以不能随便选择存储引擎。为了能够正确地选择存储引擎,必须掌握各种存储引擎的特性。

下面从存储引擎的事务安全、存储限制、空间使用、内存使用、插入数据的速度和对外键的支持等角度来比较InnoDB、MyISAM和MEMORY,如表12-3所示。

在这里插入图片描述

C/C++访问MySQL数据库

VS2019配置

第一步:打开mysql的安装目录,默认安装目录如下:C:\Program Files\MySQL\MySQL Server 8.0,确认 lib 目录和include 目录是否存在。

第二步:打开VS2019,新建一个空工程,控制台应用程序即可,注意:解决方案平台选择 X64

在这里插入图片描述

第三步:右击工程名,打开属性页

在这里插入图片描述

第四步:打开VC++目录,在包含目录中,将mysql安装文件中的include文件的路径添加到这里

在这里插入图片描述

**第五步:**打开VC++目录,在库目录中将mysql文件中的lib文件路径添加进来

在这里插入图片描述

第六步:在属性页的链接器中,点击“输入”,将mysql安装文件夹中lib目录下的libmysql.lib文件加到“附加依赖项”中,注意,这里直接把libmysql.lib这个依赖名加进去即可,不要加路径。

在这里插入图片描述

第七步:把mysql安装目录里的lib\libmysql.dll复制到c:\windows\system32下

第八步:编译如下代码,启动mysql 80, 将代码中连接数据库的用户名和密码改成自己的设定,顺利获取到student 表中的结果即表示连接成功!

#include <stdio.h>
#include <mysql.h> // mysql文件
int main(void)
{
	MYSQL mysql;    //数据库句柄
	MYSQL_RES* res; //查询结果集
	MYSQL_ROW row;  //记录结构体

	//初始化数据库
	mysql_init(&mysql);

	//设置字符编码
	mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "gbk");

	//连接数据库
	if (mysql_real_connect(&mysql, "127.0.0.1", "root", "123456qweQWE", "school", 3306, NULL, 0) == NULL) {
		printf("错误原因: %s\n", mysql_error(&mysql));
		printf("连接失败!\n");
		exit(-1);
	}

	//查询数据
	int ret = mysql_query(&mysql, "select * from student;");
	printf("ret: %d\n", ret);

	//获取结果集
	res = mysql_store_result(&mysql);

	//给ROW赋值,判断ROW是否为空,不为空就打印数据。
	while (row = mysql_fetch_row(res))
	{
		printf("%s  ", row[0]);  //打印ID
		printf("%s  ", row[1]);  //打印姓名
		printf("%s  ", row[2]);  //打印班级
		printf("%s  \n", row[3]);//打印性别
	}
	//释放结果集
	mysql_free_result(res);

	//关闭数据库
	mysql_close(&mysql);

	system("pause");
	return 0;
}

实战项目

数据库表设计

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

mysql>  create database box_man;   #创建数据库box_man                                                                                                                        
mysql>                                                                                     
mysql>  create table users(          #创建用户表                                                              
             id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,                              
             username varchar(64) NOT NULL UNIQUE,                                                
             password varchar(32) NOT NULL ,                                                  
             level_id int  default 1                                                              
         );                                                                                  
mysql>  create table levels(         #创建关卡表                                                         
             id int NOT NULL PRIMARY KEY default 1,                                                 
             name varchar(64) NOT NULL UNIQUE,                                                    
             map_row int  NOT NULL,                                                               
             map_column   int NOT NULL,                                                           
             map_data     varchar(4096) NOT NULL,                                        
             next_level_id  int default 0                                                     
         );                                                                                  
mysql>   insert into users values(1000, 'martin', md5('123456qweQWE'), 1);                    
mysql>   insert into levels values(1, '牛刀小试', 9, 12,                                            
 '0,0,0,0,0,0,0,0,0,0,0,0|0,1,0,1,1,1,1,1,1,1,0,0|0,1,4,1,0,2,1,0,2,1,0,0|0,1,0,1,0,1,0,0,1,1,1,0|0,1,0,2,    
0,1,1,4,1,1,1,0|0,1,1,1,0,3,1,1,1,4,1,0|0,1,2,1,1,4,1,1,1,1,1,0|0,1,0,0,1,0,1,1,0,0,1,0|0,0,0,0,0,0,0,0,0,0,  
0,0',0);                                                                                      

推箱子游戏 - 代码优化

登录验证

database.cpp

#include "database.h"
#include <mysql.h>
#include <stdio.h>

#define DB_NAME  "box_man"
#define DB_HOST  "127.0.0.1"
#define DB_PORT  3306
#define DB_USER  "root"
#define DB_USER_PASSWD  "123456qweQWE"

static bool connect_db(MYSQL& mysql);

/***************************************************
 *功能:通过用户名和密码从数据库获取用户信息
 *输入:
 *      user - 用户信息结构体
 *
 *返回值:
 *       获取成功返回true, 失败false
 ***************************************************/
bool fetch_user_info(userinfo& user) {
    MYSQL mysql;
    MYSQL_RES* res; //查询结果集
    MYSQL_ROW row;  //记录结构体
    char sql[256];
    bool ret = false;


    //1.连接到数据库
    if (connect_db(mysql) == false) {
        return false;
    }

    //2.根据用户名和密码获取用户信息(id, level_id)
    snprintf(sql, 256, "select id, level_id from users where username='%s' and password=md5('%s');", user.username.c_str(), user.passwd.c_str());
    ret = mysql_query(&mysql, sql); //成功返回0

    if (ret) {
        printf("数据库查询出错,%s 错误原因: %s\n", sql, mysql_error(&mysql));
        mysql_close(&mysql);
        return false;
    }

    //3.获取结果
    res = mysql_store_result(&mysql);
    row = mysql_fetch_row(res);

    if (row == NULL) {//没有查找到记录
        mysql_free_result(res);
        mysql_close(&mysql);
        return false;
    }

    user.id = atoi(row[0]);
    user.level_id = atoi(row[1]);
    printf("userid: %d  level_id: %d\n", user.id, user.level_id);  //打印ID
 
    //4.返回结果

    //释放结果集
    mysql_free_result(res);

    //关闭数据库
    mysql_close(&mysql);

    return true;
}


bool connect_db(MYSQL& mysql) {

    //1.初始化数据库句柄
    mysql_init(&mysql);

    //2.设置字符编码
    mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "gbk");

    //3.连接数据库
    if (mysql_real_connect(&mysql, DB_HOST, DB_USER, DB_USER_PASSWD, DB_NAME, DB_PORT, NULL, 0) == NULL) {
        printf("数据库连接出错, 错误原因: %s\n", mysql_error(&mysql));
        return false;
    }

    return true;
}

database.h

#pragma once

#include <string>

using namespace std;


//用户信息
typedef struct _userinfo{
	int id;            //用户id
	string username;   //用户名
	string passwd;     //密码
	int level_id;      //关卡id
}userinfo;



bool fetch_user_info(userinfo &user);

boxman.cpp

bool login(userinfo& user) {
	int times = 0;
	bool ret = false;


	do{
		cout << "请输入用户名: ";
		cin >> user.username;

		cout << "请输入密码: ";
		cin >> user.passwd;

		//返回 bool ,成功返回true ,失败返回false .
		ret = fetch_user_info(user);
		times++;

		if (times >= MAX_RETRY_TIMES) {
			break;
		}
		if (ret == false) {
			cout << "登陆失败,请重新输入!" << endl;
		}
	} while (!ret);

	return ret;
}
获取关卡
//database.h
typedef struct _levelinfo {
	int id;            //关卡的id
	string name;       //关卡的名字
	int map_row;       //地图总行数
	int map_column;    //地图总列数
	string  map_data;  //二维地图数据
	int next_level;    //下一关卡的id 
}levelinfo;

bool fetch_level_info(levelinfo &level, int level_id);

//database.cpp
/***************************************************
 *功能:根据关卡id 获取完整的关卡信息(如: 地图,下一关等)
 *输入:
 *      level - 保存关卡信息的结构体变量
 *      level_id - 要获取详细关卡信息的关卡id
 *返回值:
 *       获取成功返回true, 失败false
 ***************************************************/
bool fetch_level_info(levelinfo& level, int level_id) {
    MYSQL mysql;
    MYSQL_RES* res; //查询结果集
    MYSQL_ROW row;  //记录结构体
    char sql[256];
    bool ret = false;


    //1.连接到数据库
    if (connect_db(mysql) == false) {
        return false;
    }

    //2.根据关卡id查询数据库获取关卡地图信息
    snprintf(sql, 256, "select  name, map_row, map_column, map_data, next_level_id from levels where id=%d;", level_id);
    ret = mysql_query(&mysql, sql); //成功返回0

    if (ret) {
        printf("数据库查询出错,%s 错误原因: %s\n", sql, mysql_error(&mysql));
        mysql_close(&mysql);
        return false;
    }

    //3.获取结果
    res = mysql_store_result(&mysql);
    row = mysql_fetch_row(res);

    if (row == NULL) {//没有查找到记录
        mysql_free_result(res);
        mysql_close(&mysql);
        return false;
    }

    level.id = level_id;
    level.name = row[0];
    level.map_row = atoi(row[1]);
    level.map_column = atoi(row[2]);
    level.map_data = row[3];
    level.next_level = atoi(row[5]);

    if(debug) printf("level id: %d  name: %s map row: %d  map column: %d map data: %s next level: %d\n", level.id, level.name.c_str(), level.map_row, level.map_column, level.map_data.c_str(), level.next_level);
    

    //释放结果集
    mysql_free_result(res);

    //关闭数据库
    mysql_close(&mysql);

    return  true;
}
地图适配
//database.h
bool transform_map_db2array(levelinfo &level, int map[LINE][COLUMN]);

//database.cpp
bool transform_map_db2array(levelinfo& level, int map[LINE][COLUMN]) {
    if (level.map_row > LINE || level.map_column > COLUMN) {
        printf("地图超大,请重新设置!\n");
        return false;
    }

    if (level.map_data.length() < 1) {
        printf("地图数据有误,请重新设置!\n");
        return false;
    }

    int start = 0, end = 0;
    int row = 0, column = 0;

    do {
        end = level.map_data.find('|', start);

        if (end < 0) {
            end = level.map_data.length();
        }

        if (start >= end) break;

        string line = level.map_data.substr(start, end - start);
        printf("get line: %s\n", line.c_str());

        //对行地图数据进行解析
        char *next_token = NULL;
        char* item = strtok_s((char*)line.c_str(), ",", &next_token);

        column = 0;

        while (item && column < level.map_column) {
            printf("%s ", item);
            map[row][column] = atoi(item);
            column++;

            item = strtok_s(NULL, ",", &next_token);
        }

        if (column < level.map_column) {
            printf("地图数据解析出错,终止!\n");
            return false;
        }

        printf("\n");
        row++;

        if (row >= level.map_row) {
            break;
        }

        start = end + 1;

    } while (1 == 1);

    if (row < level.map_row) {
        printf("地图行数少于设定, %d(need: %d),终止!\n", row, level.map_row);
        return false;
    }

    return true;
}

//boxman.cpp

//把数据库中的地图数据转换到map 中
	ret = transform_map_db2array(level, map);

下一关跳转
//database.h
bool update_user_level(userinfo& user, int next_level_id);

//database.cpp
bool update_user_level(userinfo& user, int next_level_id) {
    MYSQL mysql;
    MYSQL_RES* res; //查询结果集
    MYSQL_ROW row;  //记录结构体
    char sql[256];
    bool ret = false;


    //1.连接到数据库
    if (connect_db(mysql) == false) {
        return false;
    }

    //2.根据用户id 更新下一关的level_id
    snprintf(sql, 256, "update users set level_id = %d where id=%d;", next_level_id, user.id);

    ret = mysql_query(&mysql, sql);

    if (ret) {
        printf("数据库更新出错,%s 错误原因: %s\n", sql, mysql_error(&mysql));
        mysql_close(&mysql);
        return false;
    }

    //关闭数据库
    mysql_close(&mysql);

    return true;
}

//boxman.cpp

//...............前面省略N行代码....................

void gameNextScene(IMAGE* bg) {
	putimage(0, 0, bg);
	settextcolor(WHITE);
	RECT rec = { 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT };
	settextstyle(20, 0, _T("宋体"));
	drawtext(_T("恭喜您~ \n此关挑战成功,任意键跳转到下一关!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	::system("pause");
	cleardevice();
}

//...............中间省略N行代码....................

if (isGameOver()) {

	if (level.next_level < 1) {
		gameOverScene(&bg_img);
		quit = true;
		break;
	}

	gameNextScene(&bg_img);

	//更新用户下一关的关卡信息
	if (update_user_level(user, level.next_level)) {
		user.level_id = level.next_level;
	}
					
	break;
	//quit = true;
}
完整代码

boxman.cpp

#include <graphics.h>
#include <conio.h>
#include <iostream>
#include <string>
#include "database.h"

using namespace std;

#define SCREEN_WIDTH	800
#define SCREEN_HEIGHT	650
#define PROPS_RATIO		50
#define MAX_RETRY_TIMES	4


#define START_X	100
#define START_Y	100

#define KEY_UP		'w'
#define KEY_DOWN	's'
#define KEY_LEFT	'a'
#define KEY_RIGHT	'd'
#define KEY_QUIT	'q'
#define isValid(pos) pos.x>=0 && pos.x<LINE && pos.y>=0 &&pos.y <COLUMN 


/*int map[9][12] = {
	{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	{0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0},
	{0, 1, 4, 1, 0, 2, 1, 0, 2, 1, 0, 0},
	{0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0},
	{0, 1, 0, 2, 0, 1, 1, 4, 1, 1, 1, 0},
	{0, 1, 1, 1, 0, 3, 1, 1, 1, 4, 1, 0},
	{0, 1, 2, 1, 1, 4, 1, 1, 1, 1, 1, 0},
	{0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0},
	{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
};*/

int map[LINE][COLUMN] = { 0 };

enum _PROPS
{
	WALL,
	FLOOR,
	BOX_DES,
	MAN,
	BOX,
	HIT,
	ALL
};

IMAGE props_img[ALL];

enum _DIRECTION
{
	UP,
	DOWN,
	LEFT,
	RIGHT
};

struct _POS
{
	int x;
	int y;
};

typedef enum _DIRECTION DIRECTION;
typedef enum _PROPS PROPS;
typedef struct _POS POS;
POS man;
bool step_on_DES = false;
bool end_flag = false;

void printWinScreen(IMAGE * bg)
{
	putimage(0, 0, bg);
	settextcolor(WHITE);
	RECT rec = { 0,0,SCREEN_WIDTH,SCREEN_HEIGHT };
	settextstyle(20, 0, _T("Arial"));
	drawtext(_T("You win!!! Press any key to move to the next stage!!!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	system("pause");
	cleardevice();

}

void printOverScreen(IMAGE* bg)
{
	putimage(0, 0, bg);
	settextcolor(WHITE);
	RECT rec = { 0,0,SCREEN_WIDTH,SCREEN_HEIGHT };
	settextstyle(20, 0, _T("Arial"));
	drawtext(_T("You win!!!You finish all the stage for the game!!!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	system("pause");
}


bool winDefine()
{
	for(int i=0; i<LINE; i++)
		for (int j = 0; j < COLUMN; j++)
		{
			if (map[i][j] == BOX_DES)
			{
				return false;
			}
		}
	return true;
}

void update_map_position(POS position, PROPS props)
{
	map[position.x][position.y] = props;
	putimage(START_X + (position.y * PROPS_RATIO), START_Y + (position.x * PROPS_RATIO), &props_img[map[position.x][position.y]]);

}

void map_update(DIRECTION direction)
{

	POS next_pos, next_next_pos;
	next_pos = man;
	next_next_pos = man;
	if (direction == UP)
	{
		next_pos.x = man.x - 1;
		next_next_pos.x = man.x - 2;
	}
	else if (direction == DOWN)
	{
		next_pos.x = man.x + 1;
		next_next_pos.x = man.x + 2;
	}
	else if (direction == LEFT)
	{
		next_pos.y = man.y - 1;
		next_next_pos.y = man.y - 2;
	}
	else if (direction == RIGHT)
	{
		next_pos.y = man.y + 1;
		next_next_pos.y = man.y + 2;
	}

	if (map[next_pos.x][next_pos.y] == FLOOR || map[next_pos.x][next_pos.y] == BOX_DES)
	{
		if (isValid(next_pos) && map[next_pos.x][next_pos.y] == FLOOR)
		{
			update_map_position(next_pos, MAN);
			if (step_on_DES == false)
			{
				update_map_position(man, FLOOR);
			}
			else
			{
				update_map_position(man, BOX_DES);
				step_on_DES = false;
			}

			man = next_pos;
		}
		else if (isValid(next_pos) && map[next_pos.x][next_pos.y] == BOX_DES)
		{
			update_map_position(next_pos, MAN);
			update_map_position(man, FLOOR);
			step_on_DES = true;
			man = next_pos;
		}
	}

	if (isValid(next_pos) && map[next_pos.x][next_pos.y] == BOX)
	{
		if (isValid(next_next_pos) && map[next_next_pos.x][next_next_pos.y] == FLOOR)
		{
			update_map_position(next_next_pos, BOX);
			update_map_position(next_pos, MAN);
			if (step_on_DES == false)
			{
				update_map_position(man, FLOOR);
			}
			else
			{
				update_map_position(man, BOX_DES);
				step_on_DES = false;

			}
			man = next_pos;
		}
		else if (isValid(next_next_pos) && map[next_next_pos.x][next_next_pos.y] == BOX_DES)
		{
			update_map_position(next_next_pos, HIT);
			update_map_position(next_pos, MAN);
			update_map_position(man, FLOOR);
			man = next_pos;
		}

	}
	if (isValid(next_pos) && map[next_pos.x][next_pos.y] == HIT)
	{
		if (isValid(next_next_pos) && map[next_next_pos.x][next_next_pos.y] == FLOOR)
		{
			update_map_position(next_next_pos, BOX);
			update_map_position(next_pos, MAN);
			step_on_DES = true;
			update_map_position(man, FLOOR);
			man = next_pos;
		}

	}

}

bool login(userinfo& user)
{
	int times = 0;
	bool ret = false;
	do {
		cout << "Please enter the username:" << endl;
		cin >> user.username;
		cout << "Please enter the password: " << endl;
		cin >> user.password;
		ret = fetch_user_info(user);
		times++;
		//Return as bool type to determine whether can connect to the userdatabase.
		if (times >= MAX_RETRY_TIMES)
		{
			break;
		}
		if  (ret == false)
		{
			cout << "Failed to login, please retry again." << endl;

		}
	} while (!ret);
	return ret;
}

void init_game_graphic(IMAGE& bg_img)
{
	
	initgraph(SCREEN_WIDTH, SCREEN_HEIGHT);
	loadimage(&bg_img, _T("blackground.bmp"), SCREEN_WIDTH, SCREEN_HEIGHT, true);
	putimage(0, 0, &bg_img);

	loadimage(&props_img[BOX], _T("box.bmp"), PROPS_RATIO, PROPS_RATIO, true);
	loadimage(&props_img[FLOOR], _T("floor.bmp"), PROPS_RATIO, PROPS_RATIO, true);
	loadimage(&props_img[MAN], _T("man.bmp"), PROPS_RATIO, PROPS_RATIO, true);
	loadimage(&props_img[BOX_DES], _T("des.bmp"), PROPS_RATIO, PROPS_RATIO, true);
	loadimage(&props_img[WALL], _T("wall_right.bmp"), PROPS_RATIO, PROPS_RATIO, true);
	loadimage(&props_img[HIT], _T("box.bmp"), PROPS_RATIO, PROPS_RATIO, true);
}

int main()
{
	//User verification 
	userinfo user;
	levelinfo level;
	bool ret = false;
	IMAGE bg_img;
	if (!login(user))
	{
		cout << "Failed to login... Please retry again." << endl;
		system("pause");
		exit(-1);
	}

	else
	{
		cout << "Welcome, "<<user.username<<". You are current at the stage: "<<user.level_id<<". Enjoy your game : )" << endl;
		system("pause");
	}

	//Initialized the stage level for the game
	init_game_graphic(bg_img);

	do
	{
		//Get the stage information from MYSQL
		ret = fetch_level_info(level, user.level_id);

		if (!ret)
		{
			cout << "Failed to get the stage information, please retry" << endl;
			system("pause");
			exit(-1);
		}

		//Convert the map information that query from MYSQL to MAP
		ret = transform_map_db2array(level, map);
		if (!ret)
		{
			cout << "Failed to get the stage information, please retry" << endl;
			system("pause");
			exit(-1);
		}

		char keyboard_input;

		for (int i = 0; i < level.map_row; i++)
		{
			for (int j = 0; j < level.map_column; j++)
			{
				if (map[i][j] == MAN)
				{
					man.x = i;
					man.y = j;
				}
				putimage(START_X + (j * PROPS_RATIO), START_Y + (i * PROPS_RATIO), &props_img[map[i][j]]);
			}
		}

		do
		{

			if (_kbhit)
			{
				keyboard_input = _getch();

				if (keyboard_input == KEY_UP)
				{
					map_update(UP);
				}
				else if (keyboard_input == KEY_DOWN)
				{
					map_update(DOWN);
				}
				else if (keyboard_input == KEY_LEFT)
				{
					map_update(LEFT);
				}
				else if (keyboard_input == KEY_RIGHT)
				{
					map_update(RIGHT);
				}
				else if (keyboard_input == KEY_QUIT)
				{
					end_flag = true;
				}
			}


			if (winDefine())
			{
				if (level.next_level < 1)
				{
					printOverScreen(&bg_img);
					end_flag = true;
					break;
				}
				//Update the next stage information
				printWinScreen(&bg_img);
				if (update_user_level(user, level.next_level))
				{
					user.level_id = level.next_level;
				}
				break;
				//end_flag == true
			}


		} while (end_flag == false);
	} while (end_flag == false);


	system("pause");
	return 0;

}

database.h

#pragma once
#include <iostream>
#include <string>

#define COLUMN	48
#define LINE	48
using namespace std;

typedef struct _userinfo
{
	int id;
	string username;
	string password;
	int level_id;
}userinfo;

typedef struct _levelinfo
{
	int id;
	string name;
 	int map_row;   //Total Map row number
	int map_column; //Total Map column number
	string map_data;
	int next_level;
}levelinfo;

bool fetch_user_info(userinfo &user);
bool fetch_level_info(levelinfo& level,int level_id);
bool transform_map_db2array(levelinfo& level,int map[LINE][COLUMN]);
bool update_user_level(userinfo& user, int next_level_id);

database.cpp

#include "database.h"
#include <mysql.h>
#include <stdio.h>

#define DB_NAME "box_man"
#define DB_HOST "127.0.0.1"
#define DB_PORT 3306
#define DB_USER "root"
#define DB_USER_PASSD "303771Chew!"

static int debug = 1;

static bool connect_db(MYSQL& mysql);

/***************************************
*Function: Use username and password to get the user info from MYSQL
*Input:
*	user - User information structure
* 
* Return:
*	sucessfully get info true, if failed return false;
***************************************/
bool fetch_user_info(userinfo& user)
{
	MYSQL mysql;
	MYSQL_RES* res;  //MYSQL return result
	MYSQL_ROW row;   //MYSQL return result in row
	char sql[256];
	bool ret = false;
	//1. Connect to the database
	if (connect_db(mysql) == false)
	{
		return false;
	}

	//2. According to the username and password, get the userinfo(id, level_id)
	snprintf(sql, 256, "select id, level_id from users where username ='%s' and password = md5('%s');", user.username.c_str(), user.password.c_str());
	//cout << sql << endl;
	ret = mysql_query(&mysql, sql); // This port if success will return 0;

	if (ret)
	{
		cout << "Database query have issue. Reason: " << mysql_error(&mysql) << endl;
		mysql_close(&mysql);
		return false;
	}

	//3. Get the result
	res = mysql_store_result(&mysql);
	row = mysql_fetch_row(res);

	if (row == NULL)
	{
		mysql_free_result(res);
		mysql_close(&mysql);
		return false;
	}
	user.id = atoi(row[0]);
	user.level_id = atoi(row[1]);

	if (debug) cout << "User id: " << user.id << "\t" << "User level id:" << user.level_id << endl;

	//4. Return result
	mysql_free_result(res);
	mysql_close(&mysql);
	return true;

}

bool connect_db(MYSQL& mysql)
{
	//1. Initialized the database MYSQL
	mysql_init(&mysql);

	//2. Setting character encoding
	mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "latin1");

	//3. Connect to the MYSQL database
	if (mysql_real_connect(&mysql, DB_HOST, DB_USER, DB_USER_PASSD, DB_NAME, DB_PORT, NULL, 0) == NULL)
	{
		cout << "Connect failed... Reason:" << mysql_error(&mysql);
		return false;
	}

	return true;
}

/***************************************
*Function: According to the stage id get the completed information for the stage. (Map, next stage...)
*Input:
*	level - level information structure
*	level_id - the level id that we want to get the information
* Return:
*	sucessfully get info true, if failed return false;
***************************************/
bool fetch_level_info(levelinfo& level, int level_id)
{
	MYSQL mysql;
	MYSQL_RES* res;  //MYSQL return result
	MYSQL_ROW row;   //MYSQL return result in row
	char sql[256];
	bool ret = false;
	//1. Connect to the database
	if (connect_db(mysql) == false)
	{
		return false;
	}

	//2.According with the stage id to search for the information of the map
	snprintf(sql, 256, "select name, map_row, map_column, map_data, next_level_id from levels where id=%d;", level_id);
	ret = mysql_query(&mysql, sql); // This port if success will return 0;

	if (ret)
	{
		cout << "Database query have issue. Reason: " << mysql_error(&mysql) << endl;
		mysql_close(&mysql);
		return false;
	}
	//3. Get the result
	res = mysql_store_result(&mysql);
	row = mysql_fetch_row(res);

	if (row == NULL)
	{
		mysql_free_result(res);
		mysql_close(&mysql);
		return false;
	}

	level.id = level_id;
	level.name = row[0];
	level.map_row = atoi(row[1]);
	level.map_column = atoi(row[2]);
	level.map_data = row[3];
	level.next_level = atoi(row[4]);

	if (debug) cout << "The level id is:" << level.id << " The level name is:" << level.name << " The level map row is:" << level.map_row
		<< " The level map column is:" << level.map_column << " The map data is:" << level.map_data << " The level next level is:" << level.next_level << endl;

	//4. Return result
	mysql_free_result(res);
	mysql_close(&mysql);
	return true;
}


bool transform_map_db2array(levelinfo& level, int map[LINE][COLUMN])
{
	if(level.map_row > LINE || level.map_column > COLUMN)
	{
		cout << "The map is too big!" << endl;
		return false;
	}

	if (level.map_data.length() < 1)
	{
		cout << "The map is abnormal, please reset for the map!" << endl;
		return false;
	}

	int start = 0;
	int end = 0;

	int row = 0;
	int column = 0;
	do 
	{
		end = level.map_data.find('|', start);
		if (end < 0)
		{
			end = level.map_data.length();
		}
		if (start >= end) break;
		
		string line = level.map_data.substr(start, end - start);
		cout << line << endl;

		//Seperate the map information from all the ','
		char* next_token = NULL;
		char* item = strtok_s((char*)line.c_str(), ",", &next_token);

		column = 0;
		while (item && column < level.map_column)
		{
			cout << item<<" ";
			map[row][column] = atoi(item);
			column++;

			item = strtok_s(NULL, ",", &next_token);
		}

		if (column < level.map_column)
		{
			cout << "The map information abnormal. Will terminate the program" << endl;
			return false;
		}
		cout << endl;
		row++;

		if (row >= level.map_row)
		{
			break;
		}

		start = end + 1;

	} while (1);

	if (row < level.map_row)
	{
		cout << "The map row is less than the setup. Terminated the program now" << endl;
		return false;
	}
	return true;
}

bool update_user_level(userinfo& user, int next_level_id)
{
	MYSQL mysql;
	MYSQL_RES* res;  //MYSQL return result
	MYSQL_ROW row;   //MYSQL return result in row
	char sql[256];
	bool ret = false;

	//1. Connect to the database
	if (connect_db(mysql) == false)
	{
		return false;
	}

	//2. According to the user id, update the next level_id
	snprintf(sql, 256, "update users set level_id =%d where id=%d;", next_level_id, user.id);

	ret = mysql_query(&mysql, sql);

	if (ret)
	{
		cout << "Database query have issue. Reason: " << mysql_error(&mysql) << endl;
		mysql_close(&mysql);
		return false;
	}

	mysql_close(&mysql);
	return true;
}

实战 - 棋牌游戏数据库开发 (概念)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/759532.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Chrome备份数据

Chrome备份数据 1、 导出谷歌浏览器里的历史记录 参考&#xff1a;https://blog.csdn.net/qq_32824605/article/details/127504219 在资源管理器中找到History文件&#xff0c;文件路径&#xff1a; C:\Users\你的电脑用户名\AppData\Local\Google\Chrome\User Data\Default …

【Linux】Linux系统配置,linux的交互方式

1.Linux系统环境安装 有三种方式 裸机安装或者双系统 -- 不推荐虚拟机安装 --- 不推荐云服务器/安装简单&#xff0c; 维护成本低——推荐&#xff0c; 未来学习效果好 我们借助云服务器 云服务器&#xff08;Elastic Compute Service&#xff0c;ECS&#xff09;的标准定义…

昇思25天学习打卡营第7天|网络构建

昇思25天学习打卡营第7天|网络构建 前言函数式自动微分函数与计算图微分函数与梯度计算Stop GradientAuxiliary data神经网络梯度计算 个人任务打卡&#xff08;读者请忽略&#xff09;个人理解与总结 前言 非常感谢华为昇思大模型平台和CSDN邀请体验昇思大模型&#xff01;从今…

基于SpringBoot的超市进销存系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot框架 工具&#xff1a;MyEclipse、Tomcat 系统展示 首页 首页界面图 个人中心 个人中心…

使用LabVIEW和示波器测试IGBT参数

使用LabVIEW和示波器测试绝缘栅双极型晶体管&#xff08;IGBT&#xff09;参数的综合解决方案。过程包括硬件设置、示波器和其他必要设备的配置&#xff0c;以及开发LabVIEW程序以自动化数据采集、过滤、关键参数计算和结果显示。该方法确保了IGBT测试的准确性、可靠性和高效性…

Python自动化运维 系统基础信息模块

1.系统信息的收集 系统信息的收集&#xff0c;对于服务质量的把控&#xff0c;服务的监控等来说是非常重要的组成部分&#xff0c;甚至是核心的基础支撑部分。我们可以通过大量的核心指标数据&#xff0c;结合对应的检测体系&#xff0c;快速的发现异常现象的苗头&#xff0c;进…

5.How Fast Should You Be When Learning?(你应该用多快的速度学习? (二))

Are you failing to reach an ideal or you dont know what the ideal is? 你是否没有达到理想状态&#xff0c;或者不知道理想状态是什么? A lot of learing involves having a mental representation of what the ideal performance ought to be, a method or approach t…

【JavaEE】多线程代码案例(1)

&#x1f38f;&#x1f38f;&#x1f38f;个人主页&#x1f38f;&#x1f38f;&#x1f38f; &#x1f38f;&#x1f38f;&#x1f38f;JavaEE专栏&#x1f38f;&#x1f38f;&#x1f38f; &#x1f38f;&#x1f38f;&#x1f38f;上一篇文章&#xff1a;多线程&#xff08;2…

维卡币(OneCoin)是投资骗局!中国成维卡币传销重灾区,信徒们醒醒吧!创始人被通缉,生死不明!

维卡币(英文名&#xff1a;OneCoin)是一个隐藏在加密货币外表下的庞氏骗局&#xff0c;因传销诈骗和违法吸金被起诉&#xff0c;受害者遍布全球。它的创始人Ruja Ignatova因欺骗和洗钱被列为通缉嫌疑人&#xff0c;成为全球最大金融诈骗案件之一的逃犯&#xff0c;目前美国政府…

ELK企业级实战

一、Elstic stack在企业的常⻅架构 https://www.bilibili.com/video/BV1x94y1674x/?buvidXY705117E90F73A790429C9CFBD5F70F22168&vd_source939ea718db29535a3847d861e5fe37ef ELK 解决取得问题 痛点1: ⽣产出现故障后&#xff0c;运维需要不停的查看各种不同的⽇志进⾏…

Flutter 入门与实战(十一):底部弹窗ModelBottomSheet详解

这是我参与更文挑战的第6天,活动详情查看: 更文挑战 在实际开发过程中,经常会用到底部弹窗来进行快捷操作,例如选择一个选项,选择下一步操作等等。在 Flutter 中提供了一个 showModelBottomSheet 方法用于弹出底部弹窗,本篇介绍如何使用底部弹窗。 实现效果 最终实现效果…

【使用sudo apt-get出现报错】——无法获得锁 /var/lib/dpkg/lock-open(11:资 源暂时不可用) ,是否有其他进程正占用它?

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、ubuntu中进程正在被占用1. 问题描述2. 原因分析3. 解决 总结 前言 一、ubuntu中进程正在被占用 1. 问题描述 在Ubuntu中&#xff0c;使用终端时输入带有…

50-3 内网信息收集 - 域环境搭建

搭建准备: 在搭建准备阶段,我们需要准备三台 Windows 虚拟机:Windows Server 2012、Windows 7 和 Windows Server 2008。接下来,我们将配置 Windows Server 2012 作为域控制器,而 Windows 7 和 Windows Server 2008 将作为成员机加入域。建议保持这三台虚拟机的内存不超过…

Servlet_Web小结

1.web开发概述 什么是服务器&#xff1f; 解释一&#xff1a;服务器就是一款软件,可以向其发送请求,服务器会做出一个响应. 可以在服务器中部署文件,让他人访问 解释二&#xff1a;也可以把运行服务器软件的计算机也可以称为服务器。 web开发&#xff1a; 指的是从网页中向后…

C++学习全教程(Day2)

一、数组 在程序中为了处理方便,常常需要把具有相同类型的数据对象按有序的形式排列起来&#xff0c;形成“一组”数据&#xff0c;这就是“数组”(array&#xff09; 数组中的数据&#xff0c;在内存中是连续存放的&#xff0c;每个元素占据相同大小的空间&#xff0c;就像排…

redis实战-添加商户缓存

为什么要使用缓存 言简意赅&#xff1a;速度快&#xff0c;好用缓存数据存储于代码中&#xff0c;而代码运行在内存中&#xff0c;内存的读写性能远高于磁盘&#xff0c;缓存可以大大降低用户访问并发量带来的服务器读写压力实际开发中&#xff0c;企业的数据量&#xff0c;少…

网络编程常见问题

1、TCP状态迁移图 2、TCP三次握手过程 2.1、握手流程 1、TCP服务器进程先创建传输控制块TCB&#xff0c;时刻准备接受客户进程的连接请求&#xff0c;此时服务器就进入了LISTEN&#xff08;监听&#xff09;状态&#xff1b; 2、TCP客户进程也是先创建传输控制块TCB&#xff…

RabbitMq教程【精细版一】

一、引言 模块之间的耦合度过高&#xff0c;导致一个模块宕机后&#xff0c;全部功能都不能用了&#xff0c;并且同步通讯的成本过高&#xff0c;用户体验差。 RabbitMQ引言 二、RabbitMQ介绍 MQ全称为Message Queue&#xff0c;消息队列是应用程序和应用程序之间的通信方法。…

如何利用AI生成可视化图表(统计图、流程图、思维导图……)免代码一键绘制图表

由于目前的AI生成图表工具存在以下几个方面的问题&#xff1a; 大多AI图表平台是纯英文&#xff0c;对国内用户来说不够友好&#xff1b;部分平台在生成图表前仍需选择图表类型、配置项&#xff0c;操作繁琐&#xff1b;他们仍需一份规整的数据表格&#xff0c;需要人为对数据…

碧海威L7云路由无线运营版 confirm.php/jumper.php 命令注入漏洞复现(XVE-2024-15716)

0x01 产品简介 碧海威L7网络设备是 北京智慧云巅科技有限公司下的产品,基于国产化ARM硬件平台,采用软硬一体协同设计方案,释放出产品最大效能,具有高性能,高扩展,产品性能强劲,具备万兆吞吐能力,支持上万用户同时在线等高性能。其采用简单清晰的可视化WEB管理界面,支持…