基於Python和Xtrbackup的自動化備份與還原實現

来源:https://www.cnblogs.com/wy123/archive/2019/08/01/11279559.html
-Advertisement-
Play Games

xtrabackup是一個MySQL備份還原的常用工具,實際使用過程應該都是shell或者python封裝的自動化腳本,尤其是備份。對還原來說,對於基於完整和增量備份的還原,還原差異備份需要指定增量備份等等一系列容易出錯的手工操作,以及binlog的還原等,如果純手工操作的話非常麻煩。即便是你記性非 ...


xtrabackup是一個MySQL備份還原的常用工具,實際使用過程應該都是shell或者python封裝的自動化腳本,尤其是備份。
對還原來說,對於基於完整和增量備份的還原,還原差異備份需要指定增量備份等等一系列容易出錯的手工操作,以及binlog的還原等,如果純手工操作的話非常麻煩。
即便是你記性非常好,對xtrabackup非常熟悉,純手工操作的話,非常容易出錯,其實也上網找過,還原沒有發現太好用的自動化還原腳本。
於是就自己用Python封裝了xtrabackup備份和還原的過程,可以做到自動化備份,基於時間點的自動化還原等等。

需要對xtrabackup有一定的瞭解,包括流式備份,壓縮備份,Xtrabackup還原,mysqlbinlog還原等等。

 

備份

1,基於xtrabackup的流式壓縮備份。
2,周六/或者任意時間的第一次備份為完整備份,其他時間為基於上一次備份的增量備份。
3,將備份開始時間,結束時間,備份路徑等信息寫入一個日誌文件,方便後續自動化還原的時候解析。

效果如下:不管是什麼時候,第一次必須為完整備份,然後根據上述規則,繼續執行備份的話為基於最新一次備份的增量備份,每備份完成後生成修改備份日誌列表信息。

 實現:

  1 # -*- coding: utf-8 -*-
  2 import os
  3 import time
  4 import datetime
  5 import sys
  6 import socket
  7 import shutil
  8 import logging
  9 
 10 logging.basicConfig(level=logging.INFO
 11                     #handlers={logging.FileHandler(filename='backup_log_info.log', mode='a', encoding='utf-8')}
 12                     )
 13 
 14 
 15 host = "127.0.0.1"
 16 port = "7000"
 17 user = "root"
 18 password = "root"
 19 cnf_file = "/usr/local/mysql57_data/mysql7000/etc/my.cnf"
 20 backup_dir = "/usr/local/backupdata"
 21 backupfilelist = os.path.join(backup_dir,"backupfilelist.log")
 22 backup_keep_days = 15
 23 
 24 #獲取備份類型,周六進行完備,平時增量備份,如果沒有全備,執行完整備份
 25 def get_backup_type():
 26     backup_type = None
 27     if os.path.exists(backupfilelist):
 28         with open(backupfilelist, 'r') as f:
 29             lines = f.readlines()
 30             if(lines):
 31                 last_line = lines[-1] #get last backup name
 32                 if(last_line):
 33                     if(time.localtime().tm_wday==6):
 34                         backup_type = "full"
 35                     else:
 36                         backup_type = "incr"
 37                 else:
 38                     backup_type = "full"
 39             else:
 40                 backup_type = "full"
 41     else:
 42         #full backup when first backup
 43         open(backupfilelist, "a").close()
 44         backup_type = "full"
 45     return backup_type
 46 
 47 #獲取最後一次備份信息
 48 def get_last_backup():
 49     last_backup = None
 50     if os.path.exists(backupfilelist):
 51         with open(backupfilelist, 'r') as f:
 52             lines = f.readlines()
 53             last_line = lines[-1]  # get last backup name
 54             if (last_line):
 55                 last_backup = os.path.join(backup_dir, last_line.split("|")[-1])
 56     return last_backup.replace("\n","")
 57 
 58 
 59 #探測實例埠號
 60 def get_mysqlservice_status():
 61     mysql_stat = 0
 62     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 63     result = s.connect_ex((host, int(port)))
 64     #port os open
 65     if (result == 0):
 66         mysql_stat = 1
 67     return mysql_stat
 68 
 69 #清理過期的歷史備份信息
 70 def clean_expired_file():
 71     for backup_name in os.listdir(backup_dir):
 72         if os.path.isdir(backup_name):
 73             bak_datetime = datetime.datetime.strptime(backup_name.replace("_full","").replace("_incr",""), '%Y%m%d%H%M%S')
 74             if(bak_datetime<datetime.datetime.now() - datetime.timedelta(days=backup_keep_days)):
 75                 shutil.rmtree(os.path.join(backup_dir, backup_name))
 76 
 77 #完整備份
 78 def full_backup(backup_file_name):
 79     os.system("[ ! -d {0}/{1} ] && mkdir -p {0}/{1}".format(backup_dir,backup_file_name))
 80     logfile = os.path.join(backup_dir, "{0}/{1}/backuplog.log".format(backup_dir,backup_file_name))
 81     backup_commond = ''' innobackupex --defaults-file={0} --no-lock {1}/{6}  --user={2} --password={3} --host="{4}" --port={5} --tmpdir={1}/{6} --stream=xbstream --compress  --compress-threads=8 --parallel=4  --extra-lsndir={1}/{6}  > {1}/{6}/{6}.xbstream 2>{7} '''.\
 82                     format(cnf_file,backup_dir,user,password,host,port,backup_file_name,logfile)
 83     execute_result = os.system(backup_commond)
 84     return execute_result
 85 
 86 #增量備份
 87 def incr_backup(backup_file_name):
 88     os.system("[ ! -d {0}/{1} ] && mkdir -p {0}/{1}".format(backup_dir, backup_file_name))
 89     current_backup_dir = "{0}/{1}".format(backup_dir, backup_file_name)
 90     logfile = os.path.join(backup_dir, "{0}/{1}/backuplog.log".format(backup_dir, backup_file_name))
 91     #增量備份基於上一個增量/完整備份
 92     incremental_basedir = get_last_backup()
 93     backup_commond = '''innobackupex --defaults-file={0} --no-lock  {6}  --user={2} --password={3} --host={4} --port={5} --stream=xbstream --tmpdir={6} --compress --compress-threads=8 --parallel=4   --extra-lsndir={6} --incremental --incremental-basedir={7} 2> {8} > {6}/{9}.xbstream '''\
 94                     .format(cnf_file,backup_dir,user,password,host,port,current_backup_dir,incremental_basedir,logfile,backup_file_name)
 95     # print(backup_commond)
 96     execute_result = os.system(backup_commond)
 97     return execute_result
 98 
 99 #刷新binlog,意義不大,原本計劃在完整備份之後執行一個binlog的切換,暫時棄用
100 def flush_log():
101     flush_log_commond = ''' mysql -h${0} -u${1} - p${2} -P${1}  mysql - e"flush logs" '''.format(user,password,host,port)
102     os.system(flush_log_commond)
103 
104 
105 if __name__ == '__main__':
106     mysql_stat = get_mysqlservice_status()
107     backup_type = get_backup_type()
108     if mysql_stat <= 0 :
109         logging.info("mysql instance is inactive,backup exit")
110         sys.exit(1)
111     try:
112         start_time = datetime.datetime.now().strftime('%Y%m%d%_H%M%S')
113         logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')+"--------start backup")
114         #flush_log()
115         backup_file_name = start_time
116         execute_result = None
117         if(backup_type == "full"):
118             backup_file_name = backup_file_name+"_full"
119             logging.info("execute full backup......")
120             execute_result = full_backup(backup_file_name)
121             if (execute_result == 0):
122                 logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------begin cleanup history backup")
123                 logging.info("execute cleanup backup history......")
124                 clean_expired_file()
125                 logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------finsh cleanup history backup")
126         else:
127             backup_file_name = backup_file_name + "_incr"
128             logging.info("execute incr backup......")
129             execute_result = incr_backup(backup_file_name)
130         if(execute_result==0):
131             finish_time = datetime.datetime.now().strftime('%Y%m%d%_H%M%S')
132             backup_info = start_time+"|"+finish_time+"|"+start_time+ "_" + backup_type
133             with open(backupfilelist, 'a+') as f:
134                 f.write(backup_info + '\n')
135             logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')+"--------finish backup")
136         else:
137             logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------xtrabackup failed.please check log")
138     except:
139         raise
140         sys.exit(1)
View Code

 

 

還原

說直白一點,以這裡的基於時間點或者是position的還原,就是一個不斷找文件的過程,
1,首先任何還原,都需要一個創建於還原點前的完整備份。
2,基於上述完整備份,利用還原的時間點與xtrbackup的備份日誌去做對比來獲取所需的增量備份(0個或者1個或者多個)。
3,基於上面兩步找到的(完整+增量)備份,利用最後一個備份的position,用於第一個binlog還原時指定start-position,
   同時利用binlog的最後修改時間與還原的時間點對比,決定使用那些binlog,同時最後一個binlog要指定stop-datime= 還原的時間點

 

1,如何還原時間點的最新的一個完整備份
備份的時候維護一個備份信息,如下,這裡是backfilelist.log,包括備份開始時間,結束時間,備份類型,備份路徑等。
可以根據備份開始時間,找到第一個早於還原時間點的完整備份  

2,如果找到恢復所需要的差異備份
同1,從完整備份開始,依次向後找各個增量備份,直到最後一個早於還原時間點的差異備份,可能有一個或者多個

3,如何找到差異備份之後,需要哪些binlog
基於binlog文件自身的最後修改時間屬性信息,從2中找到的最後一個差異備份的時間,開始向後依次找binlog,可能有一個或者多個 

 

自動還原demo

如下是一個基於時間點來還原資料庫的demo,沒寫入兩條數據,執行一次備份(上述備份會自動區分完整備份或者差異備份)
三次備份之後,繼續寫兩條數據,flush logs,然後繼續分兩次分別寫兩條數據,目的是將數據分散到不同的binlog中,最後刪除全部數據
然後基於刪除數據之前的時間點來自動生成還原資料庫的shell,執行shell即可達到還原資料庫的目的。

如下執行基於時間點的rextrabackup.py文件之後,時間點為"2019-08-01 18:50:59",也就是發生刪除操作的前一個時間點,來生成的還原信息。
其實只需要重定向到一個shell文件中,執行shell文件即可自動化還原,或者直接在python腳本中執行這些命令,即可自動化完成還原操作。
這裡為了顯示,列印了出來。

可以發現,基於時間點的還原,找到的文件是預期的:
1個完整備份,2個增量備份,2個binlog日誌中的一部分數據,
其中binlog日誌還原的start-position成功地銜接到最後一個增量備份的position,同時最後一個binlog日誌的還原停留在指定的時間點。

 

自動生成的shell還原代碼

################uncompress backup file###################
innobackupex --apply-log --redo-only /temp/restoretmp/20190801184134_full
innobackupex --apply-log --redo-only /temp/restoretmp/20190801184134_full --incremental-dir=/temp/restoretmp/20190801184335_inc
innobackupex --apply-log --redo-only /temp/restoretmp/20190801184134_full --incremental-dir=/temp/restoretmp/20190801184518_inc
innobackupex --apply-log /temp/restoretmp/20190801184134_full
################stop mysql service###################
systemctl stop mysqld_7000
####################backup current database file###########################
mv /usr/local/mysql57_data/mysql7000/data /usr/local/mysql57_data/mysql7000/data_20190801185855
mkdir /usr/local/mysql57_data/mysql7000/data
chown -R mysql.mysql /usr/local/mysql57_data/mysql7000/data
################restore backup data###################
innobackupex --defaults-file=/usr/local/mysql57_data/mysql7000/etc/my.cnf --copy-back --rsync /temp/restoretmp/20190801184134_full
chown -R mysql.mysql /usr/local/mysql57_data/mysql7000/data
################stop mysql service###################
systemctl start mysqld_7000
################restore data from binlog###################
cd /usr/local/mysql57_data/mysql7000/log/bin_log
mysqlbinlog mysql_bin_1300.000001  --skip-gtids=true --start-position=982 | mysql mysql -h127.0.0.1 -uroot -proot -P7000
mysqlbinlog mysql_bin_1300.000002  --skip-gtids=true --stop-datetime="2019-08-01 18:50:59" | mysql -h127.0.0.1 -uroot -proot -P7000

日誌信息

 

實現

# -*- coding: utf-8 -*-
import os
import time
import datetime
import sys
import socket
import logging


logging.basicConfig(level=logging.INFO
                    #handlers={logging.FileHandler(filename='restore_log_info.log', mode='a', encoding='utf-8')}
                    )

host = "127.0.0.1"
port = "7000"
user = "root"
password = "root"
instance_name = "mysqld_7000"
stop_at = "2019-08-01 18:50:59"
cnf_file = "/usr/local/mysql57_data/mysql7000/etc/my.cnf"
backup_dir = "/usr/local/backupdata/"
dest_dir = "/temp/restoretmp/"
xtrabackuplog_name = "backuplog.log"
backupfilelist = os.path.join(backup_dir,"backupfilelist.log")


#根據key值,獲取MySQL配置文件中的value
def get_config_value(key):
    value = None
    if not key:
        return value
    if os.path.exists(cnf_file):
        with open(cnf_file, 'r') as f:
            for line in f:
                if (line.split("=")[0]):
                    if(line[0:1]!="#" and line[0:1]!="["):
                        if (key==line.split("=")[0].strip()):
                            value =line.split("=")[1].strip()
    return value


def stop_mysql_service():
    print("################stop mysql service###################")
    print("systemctl stop {}".format(instance_name))

def start_mysql_service():
    print("################stop mysql service###################")
    print("systemctl start {0}".format(instance_name))


#返回備份日誌中的最新的一個早於stop_at時間的完整備份,以及其後面的增量備份
def get_restorefile_list():
    list_backup = []
    list_restore_file = []
    if os.path.exists(backupfilelist):
        with open(backupfilelist, 'r') as f:
            lines = f.readlines()
            for line in lines:
                list_backup.append(line.replace("\n",""))
    if (list_backup):
        for i in range(len(list_backup) - 1, -1, -1):
            list_restore_file.append(list_backup[i])
            backup_name = list_backup[i].split("|")[2]
            if "full" in backup_name:
                full_backup_time = list_backup[i].split("|")[1]
                if(stop_at<full_backup_time):
                    break
                else:
                    list_restore_file = None
    #restore file in the list_restore_log
    list_restore_file.reverse()
    return list_restore_file



#解壓縮需要還原的備份文件,包括一個完整備份以及N個增量備份(N>=0)
def uncompress_backup_file():
    print("################uncompress backup file###################")
    list_restore_backup = get_restorefile_list()


    #如果沒有生成時間早於stop_at的完整備份,無法恢復,退出
    if not list_restore_backup:
        raise("There is no backup that can be restored")
        exit(1)


    for restore_log in list_restore_backup:
        #解壓備份文件
        backup_name = restore_log.split("|")[2]
        backup_path = restore_log.split("|")[2]
        backup_full_name = os.path.join(backup_dir,backup_path,backup_name)
        backup_path = os.path.join(backup_dir,restore_log.split("|")[-1])
        #print('''[ ! -d {0} ] && mkdir -p {0}'''.format(os.path.join(dest_dir,backup_name)))
        os.system('''[ ! -d {0} ] && mkdir -p {0}'''.format(os.path.join(dest_dir,backup_name)))
        #print("xbstream -x < {0}.xbstream -C {1}".format(backup_full_name,os.path.join(dest_dir,backup_name)))
        os.system("xbstream -x < {0}.xbstream -C {1}".format(backup_full_name,os.path.join(dest_dir,backup_name)))
        #print("cd {0}".format(os.path.join(dest_dir,backup_name)))
        os.system("cd {0}".format(os.path.join(dest_dir,backup_name)))
        #print('''for f in `find {0}/ -iname "*\.qp"`; do qpress -dT4 $f  $(dirname $f) && rm -f $f; done '''.format(os.path.join(dest_dir,backup_name)))
        os.system('''for f in `find {0}/ -iname "*\.qp"`; do qpress -dT4 $f  $(dirname $f) && rm -f $f; done'''.format(os.path.join(dest_dir,backup_name)))

        current_backup_begin_time = None
        current_backup_end_time = None
        #比較當前備份的結束時間和stop_at,如果當前備份開始時間小於stop_at並且結束時間大於stop_at,解壓縮備份結束
        with open(os.path.join(dest_dir,backup_name,"xtrabackup_info"), 'r') as f:
            for line in f:
                if line and line.split("=")[0].strip()=="start_time":
                    current_backup_begin_time = line.split("=")[1].strip()
                if line and line.split("=")[0].strip()=="end_time":
                    current_backup_end_time = line.split("=")[1].strip()
        #按照stop_at時間點還原的最後一個資料庫備份,結束從第一個完整備份開始的解壓過程
        if current_backup_begin_time<=stop_at<=current_backup_end_time:
            break

    #返回最後一個備份文件,需要備份文件中的xtrabackup_info,解析出當前備份的end_time,從而確認需要哪些binlog
    return backup_name




#根據返回最後一個備份文件,需要備份文件中的xtrabackup_info,結合stop_at,確認需要還原的binlog文件,以及binlog的position信息
def restore_database_binlog(last_backup_file):
    print("################restore data from binlog###################")
    binlog_dir = get_config_value("log-bin")
    if not (backup_dir):
        binlog_dir = get_config_value("log_bin")
    print("cd {0}".format(os.path.dirname(binlog_dir)))

    last_backup_file =os.path.join(dest_dir,last_backup_file,"xtrabackup_info")
    #parse backuplog.log and get binlog name and position

    backup_position_binlog_file = None
    backup_position = None
    with open(last_backup_file, 'r') as f:
        lines = f.readlines()
        for line in lines:
            if "binlog_pos = filename " in line:
                backup_position_binlog_file = line.replace("binlog_pos = filename ", "").split(",")[0]
                backup_position_binlog_file = backup_position_binlog_file.replace("'", "")
                backup_position = line.replace("binlog_pos = filename ", "").split(",")[1].strip()
                backup_position = backup_position.split(" ")[1].replace("'", "")
                pass
            else:
                continue
        # /usr/local/mysql57_data/mysql8000/log/bin_log/mysql_bin_1300
        binlog_config = get_config_value("log-bin")
        binlog_path = os.path.dirname(binlog_config)
        binlog_files = os.listdir(binlog_path)

        #如果沒有找到binlog,忽略binlog的還原
        if not binlog_files:
            exit(1)

        #對binlog文件排序,按順序遍歷binlog,獲取binlog的最後的修改時間,與stop_at做對比,判斷還原的過程是否需要某個binlogfile
        binlog_files.sort()

        binlog_files_for_restore = []
        # 恢複數據庫的指定時間點
        stop_at_time = datetime.datetime.strptime(stop_at, '%Y-%m-%d %H:%M:%S')
        for binlog in binlog_files:
            if (".index" in binlog or "relay" in binlog):
                continue

            #保留最後一個備份中的binlog,以及其後面的binlog,這部分binlog會在還原的時候用到
            if (int(binlog.split(".")[-1]) >= int(backup_position_binlog_file.split(".")[-1])):
                binlog_files_for_restore.append(binlog)


        binlog_file_count = 0
        #第一個文件,從上最後一個差異備份的position位置開始,最後一個文件,需要stop_at到指定的時間
        for binlog in binlog_files_for_restore:
            if not os.path.isdir(binlog):
                #binlog物理文件的最後修改時間
                binlog_file_updatetime = datetime.datetime.strptime(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(os.stat(binlog_path+"/"+binlog).st_mtime)),'%Y-%m-%d %H:%M:%S')
                #判斷binlog的生成時間,是否大於stop_at,對於修改時間大於stop_at的日誌,需要全部還原,不需要stop_at指定截止點
                if  stop_at_time > binlog_file_updatetime :
                    if (binlog_file_count < 1):
                        if (len(binlog_files_for_restore) == 1):
                            # 找到差異備份之後的第一個binlog,需要根據差異備份的position,來過來第一個binlog文件
                            restore_commond = '''mysqlbinlog {0}  --skip-gtids=true --start-position={1}  --stop-datetime="{2}" | mysql mysql -h{3} -u{4} -p{5} -P{6}''' \
                                .format(binlog, backup_position, stop_at, host, user, password, port)
                            print(restore_commond)
                            binlog_file_count = binlog_file_count + 1
                        else:
                            # 找到差異備份之後的第一個binlog,需要根據差異備份的position,來過來第一個binlog文件
                            restore_commond = '''mysqlbinlog {0}  --skip-gtids=true --start-position={1} | mysql mysql -h{2} -u{3} -p{4} -P{5}''' \
                                .format(binlog, backup_position, host, user, password, port)
                            print(restore_commond)
                            binlog_file_count = binlog_file_count + 1
                    else:
                        # 從第二個文件開始,binlog需要全部還原
                        restore_commond = '''mysqlbinlog {0}  --skip-gtids=true  | mysql mysql -h{1} -u{2} -p{3} -P{4}''' \
                            .format(binlog, host, user, password, port)
                        print(restore_commond)
                        binlog_file_count = binlog_file_count + 1
                else:
                      if (binlog_file_count < 1):
                          restore_commond = '''mysqlbinlog {0}  --skip-gtids=true --start-position={1} --stop-datetime={2} | mysql -h{3} -u{4} -p{5} -P{6}'''.format(binlog, backup_position,stop_at,host,user,password,port)
                          print(restore_commond)
                          binlog_file_count = binlog_file_count + 1
                      else:
                          if (binlog_file_count >= 1):
                              restore_commond = '''mysqlbinlog {0}  --skip-gtids=true --stop-datetime="{1}" | mysql -h{2} -u{3} -p{4} -P{5}'''.format(binlog, stop_at,host,user,password,port)
                              print(restore_commond)
                              binlog_file_count = binlog_file_count + 1
                              break

def apply_log_for_backup():
    list_restore_backup = get_restorefile_list()
    start_flag = 1
    full_backup_path = None

    for current_backup_file in list_restore_backup:
        #解壓備份文件
        current_backup_name = current_backup_file.split("|")[2]
        current_backup_fullname = os.path.join(dest_dir, current_backup_name)
        if(start_flag==1):
            full_backup_path = current_backup_fullname
            start_flag = 0
            print("innobackupex --apply-log --redo-only {0}".format(full_backup_path))
        else:
            print("innobackupex --apply-log --redo-only {0} --incremental-dir={1}".format(full_backup_path,current_backup_fullname))
    #apply_log for full backup at last(remove --read-only parameter)
    print("innobackupex --apply-log {0}".format(full_backup_path))


def restore_backup_data():
    print("####################backup current database file###########################")
    datadir_path = get_config_value("datadir")
    print("mv {0} {1}".format(datadir_path,datadir_path+"_"+ datetime.datetime.now().strftime('%Y%m%d%H%M%S')))
    print("mkdir {0}".format(datadir_path))
    print("chown -R mysql.mysql {0}".format(datadir_path))
    print("################restore backup data###################")
    list_restore_backup = get_restorefile_list()
    full_restore_path= dest_dir + list_restore_backup[0].split("|")[-1].replace(".xbstream
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 我們在裝好了Ubuntu之後,打開瀏覽器,顯示Server not found。 1.點擊網路圖標,顯示如下: 2.進入etc/NetworkManager,找到 Networkmanager.conf文件。 依次輸入指令: sudo su(之後輸入密碼),接著輸入cd / 再輸入cd etc/Ne ...
  • 1、準備 例:兩台192.168.11.10(主),192.168.11.11(從),功能變數名稱www.test1.com 2、主DNS伺服器(192.168.11.10)配置 3、配置正向解析 4、重啟DNS伺服器 5、檢查解析是否成功 6、配置反向解析 7、重啟DNS伺服器 8、檢查解析是否成功 9、 ...
  • Linux網路——查看網路連接情況的命令 摘要:本文主要學習了Linux中用來查看網路連接情況的命令。 hostname命令 hostname命令用於顯示和設置系統的主機名稱,設置只是臨時生效,永久生效需要更改配置文件。 基本語法 修改主機名: 查看系統信息: 選項說明 使用舉例 ping命令 pi ...
  • 前言:無人機和人工智慧現在是非常熱門的話題,將兩者結合起來是一個比較好的創意,本文介紹一種可行的解決方案來實現基於視覺感知的跟蹤無人機。從零開始搭建無人機系統工作量和難度(以及錢)都是非常大的,所以在無人機系統的選擇上,選用正點原子開發的開源演算法無人機Minifly四軸和攝像頭。視覺感知模塊(目標檢 ...
  • 完全零基礎在Linux中安裝 JDK 總體思路:先確定沒有Java程式了 — 然後創建相應路徑文件夾 — 下載JDK — 解壓到當前路徑 — 自定義文件名稱 — 配置環境變數 — 檢查是否安裝成功 第一步 進入到root編程環境 第二步 查看已安裝的Java程式 我有的Java程式如下(這裡因人而異 ...
  • 資料庫MySQL學習筆記 [TOC] 寫在前面 學習鏈接: "資料庫 MySQL 視頻教程全集" MySQL引入 資料庫的好處 1. 持久化數據到本地 2. 可以實現結構化查詢,方便管理 資料庫的相關概念 DB:資料庫(database):存儲數據的“倉庫”,它保存了一系列有組織的數據。 DBMS: ...
  • 某日閑餘時間看到一篇介紹Gridea博客平臺的文章,大概看了一下覺得此平臺還不錯,隨即自己進入Gridea官網瞅了瞅。哇,這搭建過程也太簡單了吧,比Hexo博客搭建要容易很多,而且還有後臺管理客戶端,很適合小白用戶入門。最重要的是不用自己購買功能變數名稱!可以直接與GitHub或Coding配合展示頁面。下 ...
  • 前言: 前面幾篇文章,我們介紹了MySQL的基礎概念及邏輯架構。相信你現在應該有了自己的一套MySQL環境,接下來我們就可以開始練習MySQL了。本文將從MySQL最基礎的語句出發,為你展示出創建及修改不同對象應該使用的標準語句。 1.創建資料庫 創建資料庫的官方標準語法為: 其中{}中的內容為多選 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...