Java使用ANTLR4對Lua腳本語法校驗

文章目錄

  • 什么是ANTLR?
  • 第一個例子
  • ANTLR4 的工作流程
  • Lua腳本語法校驗
    • 準備一個Lua Grammar文件
    • maven配置
    • 新建實體類
    • Lua語法遍歷器
    • 語法錯誤監聽器
    • 單元測試
  • 參考

什么是ANTLR?

https://www.antlr.org/

ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, or translating structured text or binary files. It’s widely used to build languages, tools, and frameworks. From a grammar, ANTLR generates a parser that can build and walk parse trees.

ANTLR(ANother Tool for Language Recognition)是一個強大的解析器生成器,用于讀取、處理、執行或翻譯結構化文本或二進制文件。 它被廣泛用于構建語言、工具和框架。ANTLR 根據語法定義生成解析器,解析器可以構建和遍歷解析樹。

第一個例子

https://github.com/antlr/antlr4/blob/master/doc/getting-started.md#a-first-example

  1. 新建個Hello.g4文件:
// Define a grammar called Hello
grammar Hello;
r  : 'hello' ID ;         // match keyword hello followed by an identifier
ID : [a-z]+ ;             // match lower-case identifiers
WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
  1. 安裝IDEA插件
    ANTLR v4:https://plugins.jetbrains.com/plugin/7358-antlr-v4

  2. 打開ANTLR Preview
    r : 'hello' ID ; // match keyword hello followed by an identifier這行上右鍵,點擊Test Rule r
    輸入hello world,能夠準確識別出ID為word。
    在這里插入圖片描述
    輸入hello World,就不能夠識別出ID為world了。
    在這里插入圖片描述

ANTLR4 的工作流程

  • 詞法分析器 (Lexer) :將字符序列轉換為單詞(Token)的過程。詞法分析器(Lexer)一般是用來供語法解析器(Parser)調用的。
  • 語法解析器 (Parser) :通常作為編譯器或解釋器出現。它的作用是進行語法檢查,并構建由輸入單詞(Token)組成的數據結構(即抽象語法樹)。語法解析器通常使用詞法分析器(Lexer)從輸入字符流中分離出一個個的單詞(Token),并將單詞(Token)流作為其輸入。實際開發中,語法解析器可以手工編寫,也可以使用工具自動生成。
  • 抽象語法樹 (Parse Tree) :是源代碼結構的一種抽象表示,它以樹的形狀表示語言的語法結構。抽象語法樹一般可以用來進行代碼語法的檢查,代碼風格的檢查,代碼的格式化,代碼的高亮,代碼的錯誤提示以及代碼的自動補全等。
    在這里插入圖片描述
    如上左邊的點線流程代表了通過 ANTLR4,將原始的.g4 規則轉化為 Lexer、Parser、Listener 和 Visitor。右邊的虛線流程代表了將原始的輸入流通過 Lexer 轉化為 Tokens,再將 Tokens 通過 Parser 轉化為語法樹,最后通過 Listener 或 Visitor 遍歷 ParseTree 得到最終結果。

Lua腳本語法校驗

準備一個Lua Grammar文件

https://github.com/antlr/grammars-v4/tree/master/lua

/*
BSD LicenseCopyright (c) 2013, Kazunori Sakamoto
Copyright (c) 2016, Alexander Alexeev
All rights reserved.Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:1. Redistributions of source code must retain the above copyrightnotice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyrightnotice, this list of conditions and the following disclaimer in thedocumentation and/or other materials provided with the distribution.
3. Neither the NAME of Rainer Schuster nor the NAMEs of its contributorsmay be used to endorse or promote products derived from this softwarewithout specific prior written permission.THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.This grammar file derived from:Lua 5.3 Reference Manualhttp://www.lua.org/manual/5.3/manual.htmlLua 5.2 Reference Manualhttp://www.lua.org/manual/5.2/manual.htmlLua 5.1 grammar written by Nicolai Mainierohttp://www.antlr3.org/grammar/1178608849736/Lua.gTested by Kazunori Sakamoto with Test suite for Lua 5.2 (http://www.lua.org/tests/5.2/)Tested by Alexander Alexeev with Test suite for Lua 5.3 http://www.lua.org/tests/lua-5.3.2-tests.tar.gz
*/grammar Lua;chunk: block EOF;block: stat* retstat?;stat: ';'| varlist '=' explist| functioncall| label| 'break'| 'goto' NAME| 'do' block 'end'| 'while' exp 'do' block 'end'| 'repeat' block 'until' exp| 'if' exp 'then' block ('elseif' exp 'then' block)* ('else' block)? 'end'| 'for' NAME '=' exp ',' exp (',' exp)? 'do' block 'end'| 'for' namelist 'in' explist 'do' block 'end'| 'function' funcname funcbody| 'local' 'function' NAME funcbody| 'local' attnamelist ('=' explist)?;attnamelist: NAME attrib (',' NAME attrib)*;attrib: ('<' NAME '>')?;retstat: 'return' explist? ';'?;label: '::' NAME '::';funcname: NAME ('.' NAME)* (':' NAME)?;varlist: var_ (',' var_)*;namelist: NAME (',' NAME)*;explist: exp (',' exp)*;exp: 'nil' | 'false' | 'true'| number| string| '...'| functiondef| prefixexp| tableconstructor| <assoc=right> exp operatorPower exp| operatorUnary exp| exp operatorMulDivMod exp| exp operatorAddSub exp| <assoc=right> exp operatorStrcat exp| exp operatorComparison exp| exp operatorAnd exp| exp operatorOr exp| exp operatorBitwise exp;prefixexp: varOrExp nameAndArgs*;functioncall: varOrExp nameAndArgs+;varOrExp: var_ | '(' exp ')';var_: (NAME | '(' exp ')' varSuffix) varSuffix*;varSuffix: nameAndArgs* ('[' exp ']' | '.' NAME);nameAndArgs: (':' NAME)? args;/*
var_: NAME | prefixexp '[' exp ']' | prefixexp '.' NAME;prefixexp: var_ | functioncall | '(' exp ')';functioncall: prefixexp args | prefixexp ':' NAME args;
*/args: '(' explist? ')' | tableconstructor | string;functiondef: 'function' funcbody;funcbody: '(' parlist? ')' block 'end';parlist: namelist (',' '...')? | '...';tableconstructor: '{' fieldlist? '}';fieldlist: field (fieldsep field)* fieldsep?;field: '[' exp ']' '=' exp | NAME '=' exp | exp;fieldsep: ',' | ';';operatorOr: 'or';operatorAnd: 'and';operatorComparison: '<' | '>' | '<=' | '>=' | '~=' | '==';operatorStrcat: '..';operatorAddSub: '+' | '-';operatorMulDivMod: '*' | '/' | '%' | '//';operatorBitwise: '&' | '|' | '~' | '<<' | '>>';operatorUnary: 'not' | '#' | '-' | '~';operatorPower: '^';number: INT | HEX | FLOAT | HEX_FLOAT;string: NORMALSTRING | CHARSTRING | LONGSTRING;// LEXERNAME: [a-zA-Z_][a-zA-Z_0-9]*;NORMALSTRING: '"' ( EscapeSequence | ~('\\'|'"') )* '"';CHARSTRING: '\'' ( EscapeSequence | ~('\''|'\\') )* '\'';LONGSTRING: '[' NESTED_STR ']';fragment
NESTED_STR: '=' NESTED_STR '='| '[' .*? ']';INT: Digit+;HEX: '0' [xX] HexDigit+;FLOAT: Digit+ '.' Digit* ExponentPart?| '.' Digit+ ExponentPart?| Digit+ ExponentPart;HEX_FLOAT: '0' [xX] HexDigit+ '.' HexDigit* HexExponentPart?| '0' [xX] '.' HexDigit+ HexExponentPart?| '0' [xX] HexDigit+ HexExponentPart;fragment
ExponentPart: [eE] [+-]? Digit+;fragment
HexExponentPart: [pP] [+-]? Digit+;fragment
EscapeSequence: '\\' [abfnrtvz"'\\]| '\\' '\r'? '\n'| DecimalEscape| HexEscape| UtfEscape;fragment
DecimalEscape: '\\' Digit| '\\' Digit Digit| '\\' [0-2] Digit Digit;fragment
HexEscape: '\\' 'x' HexDigit HexDigit;fragment
UtfEscape: '\\' 'u{' HexDigit+ '}';fragment
Digit: [0-9];fragment
HexDigit: [0-9a-fA-F];COMMENT: '--[' NESTED_STR ']' -> channel(HIDDEN);LINE_COMMENT: '--'(                                               // --| '[' '='*                                      // --[==| '[' '='* ~('='|'['|'\r'|'\n') ~('\r'|'\n')*   // --[==AA| ~('['|'\r'|'\n') ~('\r'|'\n')*                // --AAA) ('\r\n'|'\r'|'\n'|EOF)-> channel(HIDDEN);WS: [ \t\u000C\r\n]+ -> skip;SHEBANG: '#' '!' ~('\n'|'\r')* -> channel(HIDDEN);

maven配置

使用JDK8的注意:antlr4最高版本為4.9.3,原因如下:
來源:https://github.com/antlr/antlr4/releases/tag/4.10

Increasing minimum java version
Going forward, we are using Java 11 for the source code and the compiled .class files for the ANTLR tool. The Java runtime target, however, and the associated runtime tests use Java 8 (bumping up from Java 7).

<dependencies><dependency><groupId>org.antlr</groupId><artifactId>antlr4-runtime</artifactId><version>${antlr.version}</version></dependency>
</dependencies><build><plugins><plugin><groupId>org.antlr</groupId><artifactId>antlr4-maven-plugin</artifactId><version>${antlr.version}</version><configuration><visitor>true</visitor><listener>true</listener></configuration><executions><execution><goals><goal>antlr4</goal></goals></execution></executions></plugin></plugins>
</build><properties><!--https://mvnrepository.com/artifact/org.antlr/antlr4-runtime--><antlr.version>4.9.3</antlr.version><mojo.version>3.0.0</mojo.version>
</properties>

新建實體類

語法錯誤:每行有什么錯誤。

package com.baeldung.antlr.lua.model;/*** 語法錯誤** @author duhongming* @see* @since 1.0.0*/
public class SyntaxErrorEntry {private Integer lineNum;private String errorInfo;public Integer getLineNum() {return lineNum;}public void setLineNum(Integer lineNum) {this.lineNum = lineNum;}public String getErrorInfo() {return errorInfo;}public void setErrorInfo(String errorInfo) {this.errorInfo = errorInfo;}
}

語法錯誤報告:每行有什么錯誤的集合。

package com.baeldung.antlr.lua.model;import java.util.LinkedList;
import java.util.List;/*** 語法錯誤報告** @author duhongming* @see* @since 1.0.0*/
public class SyntaxErrorReportEntry {private final List<SyntaxErrorEntry> syntaxErrorList = new LinkedList<>();public void addError(int line, int charPositionInLine, Object offendingSymbol, String msg) {SyntaxErrorEntry syntaxErrorEntry = new SyntaxErrorEntry();syntaxErrorEntry.setLineNum(line);syntaxErrorEntry.setErrorInfo(line + "行," + charPositionInLine + "列," + offendingSymbol + "字符處,存在語法錯誤:" + msg);syntaxErrorList.add(syntaxErrorEntry);}public List<SyntaxErrorEntry> getSyntaxErrorReport() {return syntaxErrorList;}
}

Lua語法遍歷器

package com.baeldung.antlr.lua;import com.baeldung.antlr.LuaParser;
import com.baeldung.antlr.LuaVisitor;
import org.antlr.v4.runtime.tree.ErrorNode;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.RuleNode;
import org.antlr.v4.runtime.tree.TerminalNode;/*** Lua語法遍歷器** @author duhongming* @see* @since 1.0.0*/
public class LuaSyntaxVisitor implements LuaVisitor<Object> {
// ctrl+O Override即可
}

語法錯誤監聽器

package com.baeldung.antlr.lua;import com.baeldung.antlr.lua.model.SyntaxErrorReportEntry;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;/*** 語法錯誤監聽器** @author duhongming* @see* @since 1.0.0*/
public class SyntaxErrorListener extends BaseErrorListener {private final SyntaxErrorReportEntry reporter;public SyntaxErrorListener(SyntaxErrorReportEntry reporter) {this.reporter = reporter;}@Overridepublic void syntaxError(Recognizer<?, ?> recognizer,Object offendingSymbol, int line, int charPositionInLine,String msg, RecognitionException e) {this.reporter.addError(line, charPositionInLine, offendingSymbol, msg);}
}

單元測試

package com.baeldung.antlr;import com.baeldung.antlr.lua.LuaSyntaxVisitor;
import com.baeldung.antlr.lua.SyntaxErrorListener;
import com.baeldung.antlr.lua.model.SyntaxErrorEntry;
import com.baeldung.antlr.lua.model.SyntaxErrorReportEntry;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.junit.Test;import java.util.List;import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;public class LuaSyntaxErrorUnitTest {public static List<SyntaxErrorEntry> judgeLuaSyntax(String luaScript) {//新建一個CharStream,讀取數據CharStream charStreams = CharStreams.fromString(luaScript);//包含一個詞法分析器的定義,作用是將輸入的字符序列聚集成詞匯符號。LuaLexer luaLexer = new LuaLexer(charStreams);//新建一個詞法符號的緩沖區,用于存儲詞法分析器生成的詞法符號(Token)CommonTokenStream tokenStream = new CommonTokenStream(luaLexer);//新建一個語法分析器,用于分析詞法符號緩沖區中的詞法符號LuaParser luaParser = new LuaParser(tokenStream);SyntaxErrorReportEntry syntaxErrorReporter = new SyntaxErrorReportEntry();SyntaxErrorListener errorListener = new SyntaxErrorListener(syntaxErrorReporter);luaParser.addErrorListener(errorListener);LuaSyntaxVisitor luaSyntaxVisitor = new LuaSyntaxVisitor();luaSyntaxVisitor.visit(luaParser.chunk());return syntaxErrorReporter.getSyntaxErrorReport();}@Testpublic void testGood() throws Exception {List<SyntaxErrorEntry> errorEntryList = judgeLuaSyntax("if a~=1 then print(1) end");assertThat(errorEntryList.size(), is(0));}@Testpublic void testBad() throws Exception {//新建一個CharStream,讀取數據List<SyntaxErrorEntry> errorEntryList = judgeLuaSyntax("if a!=1 then print(1) end");assertThat(errorEntryList.size(), is(2));}
}

最終目錄情況,及單元測試情況!
在這里插入圖片描述

參考

https://www.baeldung.com/java-antlr
https://juejin.cn/post/7018521754125467661
https://www.nosuchfield.com/2023/08/26/ANTLR4-from-Beginning-to-Practice/
https://blog.csdn.net/qq_37771475/article/details/106387201
https://blog.csdn.net/qq_37771475/article/details/106426327

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/901388.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/901388.shtml
英文地址,請注明出處:http://en.pswp.cn/news/901388.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

觀察者模式(行為模式)

觀察者模式 觀察者模式屬于行為模式&#xff0c;個人理解&#xff1a;和發布訂閱者魔模式是有區別的 細分有兩種&#xff1a;推模式和拉模式兩種&#xff0c;具體區別在于推模式會自帶推送參數&#xff0c;拉模式是在接收通知后要自己獲取更新參數 觀察者模式&#xff08;Obs…

內網滲透 --- 之殺軟工具探測

目錄 內網殺軟探測與應對實戰方案 一、總體思路 二、探測階段——殺軟工具與手法 2.1 進程與服務檢測 2.2 注冊表與文件系統檢測 2.3 Nmap 與 NSE 腳本掃描 三、處理階段——探測到殺軟后的應對措施 3.1 分析評估 3.2 應對策略 四、判斷與驗證——注入 webshell 后如…

(2025親測可用)Chatbox多端一鍵配置Claude/GPT/DeepSeek-網頁端配置

1. 資源準備 API Key&#xff1a;此項配置填寫在一步API官網創建API令牌&#xff0c;一鍵直達API令牌創建頁面創建API令牌步驟請參考API Key的獲取和使用API Host&#xff1a;此項配置填寫https://yibuapi.com/v1查看支持的模型請參考這篇教程模型在線查詢 2. ChatBox網頁版配…

【Pandas】pandas DataFrame keys

Pandas2.2 DataFrame Indexing, iteration 方法描述DataFrame.head([n])用于返回 DataFrame 的前幾行DataFrame.at快速訪問和修改 DataFrame 中單個值的方法DataFrame.iat快速訪問和修改 DataFrame 中單個值的方法DataFrame.loc用于基于標簽&#xff08;行標簽和列標簽&#…

Redis存儲“大數據對象”的常用策略及StackOverflowError錯誤解決方案

Hi&#xff0c;大家好&#xff0c;我是灰小猿&#xff01; 在一些功能的開發中&#xff0c;我們一般會有一些場景需要將得到的數據先暫時的存儲起來&#xff0c;以便后面的接口或業務使用&#xff0c;這種場景我們一般常用的場景就是將數據暫時存儲在緩存中&#xff0c;之后再…

【Python】讀取xyz坐標文件輸出csv文件

Python讀取xyz坐標文件輸出csv文件 import sys import numpy as np import pandas as pd from tqdm import tqdm import cv2 import argparsedef read_xyz(file_path):with open(file_path, "r") as f: # 打開文件data f.readlines() # 讀取文件datas []for …

leetcode 139. Word Break

這道題用動態規劃解決。 class Solution { public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<string> wordSet;for(string& word:wordDict){wordSet.insert(word);}int s_len s.size();//s的下標從1開始起算&#xff0c;dp[j]…

驅動開發硬核特訓 · Day 11(下篇):從 virtio_blk 看虛擬總線驅動模型的真實落地

&#x1f50d; B站相應的視屏教程&#xff1a; &#x1f4cc; 內核&#xff1a;博文視頻 - 總線驅動模型實戰全解析 敬請關注&#xff0c;記得標為原始粉絲。 &#x1f527; 在上篇中&#xff0c;我們已經從理論視角分析了“虛擬總線驅動模型”在 Linux 驅動體系中的獨特定位。…

音視頻轉換器 AV 接口靜電保護方案

方案簡介 音視頻轉換器是將音視頻&#xff08;AV&#xff09;信號轉換成其他格式或信號類型的設備或軟件。 它能夠實現大多數視頻、音頻以及圖像格式之間的轉換&#xff0c;包括但不限于 RMVB、AVI、 MP4、MOV 等常見格式&#xff0c;同時也支持將不同采樣率、位深度、聲道數…

AI agents系列之全從零開始構建

在我們上一篇博客文章中&#xff0c;我們全面介紹了智能代理&#xff0c;討論了它們的特性、組成部分、演變過程、面臨的挑戰以及未來的可能性。 這篇文章&#xff0c;咱們就來聊聊怎么用 Python 從零開始構建一個智能代理。這個智能代理能夠根據用戶輸入做出決策&#xff0c;…

【Python爬蟲】詳細工作流程以及組成部分

目錄 一、Python爬蟲的詳細工作流程 確定起始網頁 發送 HTTP 請求 解析 HTML 處理數據 跟蹤鏈接 遞歸抓取 存儲數據 二、Python爬蟲的組成部分 請求模塊 解析模塊 數據處理模塊 存儲模塊 調度模塊 反爬蟲處理模塊 一、Python爬蟲的詳細工作流程 在進行網絡爬蟲工…

Kotlin 集合過濾全指南:all、any、filter 及高級用法

在 Kotlin 中&#xff0c;集合過濾是數據處理的核心操作之一。無論是簡單的條件篩選&#xff0c;還是復雜的多條件組合&#xff0c;Kotlin 都提供了豐富的 API。本文將詳細介紹 filter、all、any、none 等操作符的用法&#xff0c;并展示如何在實際開發中靈活運用它們。 1. 基礎…

爬蟲:一文掌握 curl-cffi 的詳細使用(支持 TLS/JA3 指紋仿真的 cURL 庫)

更多內容請見: 爬蟲和逆向教程-專欄介紹和目錄 文章目錄 一、curl-cffi 概述1.1 curl-cffi介紹1.2 主要特性1.3 適用場景1.4 使用 curl-cffi 的注意事項1.5 與 requests 和 pycurl 對比1.6 curl-cffi 的安裝二、基本使用2.1 同步請求2.2 異步請求三、高級功能3.1 模擬瀏覽器指…

AllData數據中臺升級發布 | 支持K8S數據平臺2.0版本

&#x1f525;&#x1f525; AllData大數據產品是可定義數據中臺&#xff0c;以數據平臺為底座&#xff0c;以數據中臺為橋梁&#xff0c;以機器學習平臺為中層框架&#xff0c;以大模型應用為上游產品&#xff0c;提供全鏈路數字化解決方案。 ?杭州奧零數據科技官網&#xf…

dnf install openssl失敗的原因和解決辦法

網上有很多編譯OpenSSL源碼(3.x版本)為RPM包的文章&#xff0c;這些文章在安裝RPM包時都是執行rpm -ivh openssl-xxx.rpm --nodeps --force 這個命令能在缺少依賴包的情況下能強行執行安裝 其實根據Centos的文檔&#xff0c;安裝RPM包一般是執行yum install或dnf install。后者…

從入門到進階:React 圖片輪播 Carousel 的奇妙世界!

全文目錄&#xff1a; 開篇語&#x1f590; 前言? 目錄&#x1f3af; 什么是圖片輪播組件&#xff1f;&#x1f528; 初識 React 中的輪播實現示例代碼分析 &#x1f4e6; 基于第三方庫快速實現輪播示例&#xff1a;用 react-slick優勢局限性 &#x1f6e0;? 自己動手實現一個…

2025第十六屆藍橋杯PythonB組部分題解

一、攻擊次數 題目描述 小藍操控三個英雄攻擊敵人&#xff0c;敵人初始血量2025&#xff1a; 第一個英雄每回合固定攻擊5點第二個英雄奇數回合攻擊15點&#xff0c;偶數回合攻擊2點第三個英雄根據回合數除以3的余數攻擊&#xff1a;余1攻2點&#xff0c;余2攻10點&#xff0…

新手寶塔部署thinkphp一步到位

目錄 一、下載對應配置 二、加載數據庫 三、添加FTP? 四、上傳項目到寶塔? 五、添加站點? 六、配置偽靜態 七、其他配置 開啟監控 八、常見錯誤 一、打開寶塔頁面&#xff0c;下載對應配置。 二、加載數據庫 從本地導入數據庫文件 三、添加FTP 四、上傳項目到寶塔…

2025年,HarmonyOS認證學習及考試

HarmonyOS應用開發者認證考試 基礎認證 通過系統化的課程學習&#xff0c;熟練掌握 DevEco Studio&#xff0c;ArkTS&#xff0c;ArkUI&#xff0c;預覽器&#xff0c;模擬器&#xff0c;SDK 等 HarmonyOS 應用開發的關鍵概念&#xff0c;具備基礎的應用開發能力。 高級認證…

3-1 Git分布式版本控制特性探討

Git 的分布式版本控制特性是其核心優勢之一,它使 Git 在版本管理方面具有高度的靈活性、可靠性和高效性。以下從多個方面來理解這一特性: 分布式存儲 在 Git 中,每個開發者的本地機器上都擁有完整的版本庫,包含了項目的所有歷史記錄和元數據。這與集中式版本控制系統(如…