JavaFX 集成 Sqlite 和 Hibernate 開發爬蟲應用

来源:https://www.cnblogs.com/yangchaojie/archive/2019/08/06/11312477.html
-Advertisement-
Play Games

目錄 [隱藏] 0.1 前言: 0.2 界面 0.3 Maven 環境 0.4 項目結構 0.5 整合 Hibernate 0.5.1 SQLiteDialect.java 資料庫方言代碼 0.5.2 hibernate.cfg.xml Hibernate配置文件 0.6 項目初始化連接資料庫自動建 ...


目錄 [隱藏]

前言:

在開發 JavaFX 應用總是避免不了數據存儲的,如果僅僅是一個簡單的配置數據,那麼不需要資料庫即可實現,那麼如果要面對幾十萬等大數據量的持久化存儲,那免不了要和資料庫和JDBC框架打交道了。

資料庫該怎麼選呢? 首先考慮我比較熟的 MySql,可是要使用MySql,你就必須要去官網下載MySql的安裝包,還要進行賬號密碼等配置,如果這軟體是面向大眾的,用戶要使用總不能還要先裝資料庫,再看半天安裝教程吧?

這不行,那麼我之前有接觸過兩個嵌入式資料庫,一個是H2,一個就是開發Android 時接觸的Sqlite。

H2 我事先考察了一下,覺得資料並不是很多,遠沒有 Sqlite 使用廣泛,而且 Sqlite 是 Android 官方內置的資料庫,我還去看了 Sqlite 最大數據存儲等測試文章,億級的數據量下還能保持性能,這才放心使用。

界面

Maven 環境

<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>com.jfoenix</groupId>
			<artifactId>jfoenix</artifactId>
			<version>8.0.8</version>
		</dependency>
		<!--sqlite 版本3.7.2 -->
		<dependency>
			<groupId>org.xerial</groupId>
			<artifactId>sqlite-jdbc</artifactId>
			<version>3.7.2</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.21</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.21</version>
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-1.2-api</artifactId>
			<version>2.8.2</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
     <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.11.3</version>
        </dependency>
		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-core</artifactId>
			<version>4.1.21</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/io.datafx/flow -->
		<dependency>
			<groupId>io.datafx</groupId>
			<artifactId>flow</artifactId>
			<version>8.0.1</version>
		</dependency>
		<dependency>
			<groupId>org.controlsfx</groupId>
			<artifactId>controlsfx</artifactId>
			<version>8.40.14</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>5.4.2.Final</version>
		</dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
        </dependency>
		<!-- https://mvnrepository.com/artifact/com.github.inamik.text.tables/inamik-text-tables -->
		<dependency>
			<groupId>com.github.inamik.text.tables</groupId>
			<artifactId>inamik-text-tables</artifactId>
			<version>0.8</version>
		</dependency>

 

項目結構

 

整合 Hibernate

Hibernate 並不支持 Sqlite,但只是缺少一個資料庫方言代碼而已,這個在網上有很多,copy 一份在hibernate配置文件中引入就可以了。

SQLiteDialect.java 資料庫方言代碼

package util;

import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.function.SQLFunctionTemplate;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.dialect.function.VarArgsSQLFunction;
import org.hibernate.type.StandardBasicTypes;

import java.sql.Types;

public class SQLiteDialect extends Dialect {
    public SQLiteDialect() {
        super();
        registerColumnType(Types.BIT, "integer");
        registerColumnType(Types.TINYINT, "tinyint");
        registerColumnType(Types.SMALLINT, "smallint");
        registerColumnType(Types.INTEGER, "integer");
        registerColumnType(Types.BIGINT, "bigint");
        registerColumnType(Types.FLOAT, "float");
        registerColumnType(Types.REAL, "real");
        registerColumnType(Types.DOUBLE, "double");
        registerColumnType(Types.NUMERIC, "numeric");
        registerColumnType(Types.DECIMAL, "decimal");
        registerColumnType(Types.CHAR, "char");
        registerColumnType(Types.VARCHAR, "varchar");
        registerColumnType(Types.LONGVARCHAR, "longvarchar");
        registerColumnType(Types.DATE, "date");
        registerColumnType(Types.TIME, "time");
        registerColumnType(Types.TIMESTAMP, "timestamp");
        registerColumnType(Types.BINARY, "blob");
        registerColumnType(Types.VARBINARY, "blob");
        registerColumnType(Types.LONGVARBINARY, "blob");
        // registerColumnType(Types.NULL, "null");
        registerColumnType(Types.BLOB, "blob");
        registerColumnType(Types.CLOB, "clob");
        registerColumnType(Types.BOOLEAN, "integer");

        registerFunction("concat", new VarArgsSQLFunction(StandardBasicTypes.STRING, "", "||", ""));
        registerFunction("mod", new SQLFunctionTemplate(StandardBasicTypes.INTEGER, "?1 % ?2"));
        registerFunction("substr", new StandardSQLFunction("substr", StandardBasicTypes.STRING));
        registerFunction("substring", new StandardSQLFunction("substr", StandardBasicTypes.STRING));
    }

    public boolean supportsIdentityColumns() {
        return true;
    }

    public boolean hasDataTypeInIdentityColumn() {
        return false;
    }

    public String getIdentityColumnString() {
        return "integer";
    }

    public String getIdentitySelectString() {
        return "select last_insert_rowid()";
    }

    public boolean supportsLimit() {
        return true;
    }

    public String getLimitString(String query, boolean hasOffset) {
        return new StringBuffer(query.length() + 20).append(query).append(hasOffset ? " limit ? offset ?" : " limit ?")
                .toString();
    }

    public boolean supportsTemporaryTables() {
        return true;
    }

    public String getCreateTemporaryTableString() {
        return "create temporary table if not exists";
    }

    public boolean dropTemporaryTableAfterUse() {
        return false;
    }

    public boolean supportsCurrentTimestampSelection() {
        return true;
    }

    public boolean isCurrentTimestampSelectStringCallable() {
        return false;
    }

    public String getCurrentTimestampSelectString() {
        return "select current_timestamp";
    }

    public boolean supportsUnionAll() {
        return true;
    }

    public boolean hasAlterTable() {
        return false;
    }

    public boolean dropConstraints() {
        return false;
    }

    public String getAddColumnString() {
        return "add column";
    }

    public String getForUpdateString() {
        return "";
    }

    public boolean supportsOuterJoinForUpdate() {
        return false;
    }

    public String getDropForeignKeyString() {
        throw new UnsupportedOperationException("No drop foreign key syntax supported by SQLiteDialect");
    }

    public String getAddForeignKeyConstraintString(String constraintName, String[] foreignKey, String referencedTable,
                                                   String[] primaryKey, boolean referencesPrimaryKey) {
        throw new UnsupportedOperationException("No add foreign key syntax supported by SQLiteDialect");
    }

    public String getAddPrimaryKeyConstraintString(String constraintName) {
        throw new UnsupportedOperationException("No add primary key syntax supported by SQLiteDialect");
    }

    public boolean supportsIfExistsBeforeTableName() {
        return true;
    }

    public boolean supportsCascadeDelete() {
        return false;
    }

    @Override
    public boolean bindLimitParametersInReverseOrder() {
        return true;
    }
}

hibernate.cfg.xml Hibernate配置文件

<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">util.SQLiteDialect</property> <!-- 資料庫方言 -->
        <property name="hibernate.connection.driver_class">org.sqlite.JDBC</property><!-- 引用jdbc包 -->
        <property name="hibernate.connection.url">jdbc:sqlite:D:\eclipse_workspace\Letv\src\main\resources\db\letv.db</property> <!-- 資料庫鏈接 -->
        <!--        <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property> <!– 資料庫方言 –>-->
        <!--        <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property><!– 引用jdbc包 –>-->
        <!--                <property name="hibernate.connection.url">jdbc:mysql://127.0.0.1:3306/bbs?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=CTT&nullCatalogMeansCurrent=true</property> <!– 資料庫鏈接 –>-->
        <!--        <property name="hibernate.connection.username">root</property>-->
        <!--        <property name="hibernate.connection.password">1234</property>-->

        <property name="hibernate.format_sql">true</property>
        <property name="hibernate.show_sql">true</property>
        <mapping resource="db/LetvConfigEntity.hbm.xml"/>
        <mapping resource="db/LetvCookieEntity.hbm.xml"/>
        <mapping resource="db/LetvLinkEntity.hbm.xml"/>
        <mapping resource="db/LetvUserEntity.hbm.xml"/>


    </session-factory>

</hibernate-configuration>

這裡的 :<property name=”hibernate.connection.url”>jdbc:sqlite:D:\eclipse_workspace\Letv\src\main\resources\db\letv.db</property>

是絕對路徑,這個是用 idea 自動生成給我改了,可以使用相對路徑在項目根目錄下創建資料庫文件,而 url 只需要 : String url = “jdbc:sqlite:src/main/resources/db/letv.db”;

這樣寫就可以。

項目初始化連接資料庫自動建表:

資料庫那肯定不能沒有表,而表又不可能讓用戶去建,所以只能讓程式代勞,並不難,用 Navicat 打開資料庫建好表,導出 sql 文件,將其中的建表語句提取出來,在項目初次載入時,找到 sqlite 的 db 尾碼的資料庫文件,如果沒有,那麼創建,連接資料庫,執行建表語句。

public class SqliteUtil {

    private static Connection connection;

    public synchronized static Connection getConnection() throws SQLException {
        //如果 當前練
        if (connection == null) {
            try {
                String driverClass = "org.sqlite.JDBC";
                Class.forName(driverClass);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            String url = "jdbc:sqlite:src/main/resources/db/letv.db";
            return connection = DriverManager.getConnection(url);
        } else {
            return connection;
        }
    }
    public static void close() {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        } else {
            throw new NullPointerException("連接未開啟!");
        }
    }

這段代碼中,getConnection() 方法調用後會連接 Sqlite 資料庫,如果沒有,則會創建。

    public static final String DB_USER = "create table letv_user (" +
            "  letv_user_id integer(11) not null," +
            "  letv_user_uid text(11)," +
            "  letv_user_link text(40)," +
            "  primary key (letv_user_id)" +
            ");";

    public static void createDatabases() throws SQLException, IOException {
        Connection connection = getConnection();
        Statement statement = connection.createStatement();
        statement.execute(DB_CONFIG);
        statement.execute(DB_COOKIE);
        statement.execute(DB_LINK);
        statement.execute(DB_USER);
        close();
    }

 

定義建表語句,調用 createDatabases() 則會執行建表語句創建表。

程式初次運行創建資料庫和表

 if (this.getClass().getResource("db/letv.db") == null) {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Loading.fxml"));
            AnchorPane pane = loader.load();
            Scene scene = new Scene(pane, 500, 300);
            PleaseProvideController loadController = loader.getController();
            loadController.setInfo("正在創建資料庫……");
            primaryStage.setScene(scene);
            primaryStage.initStyle(StageStyle.UNDECORATED);
            primaryStage.setAlwaysOnTop(true);
            primaryStage.getIcons().addAll(new Image("file:/resource/logo/logo.ico"));
            primaryStage.show();
            Platform.runLater(() -> {
                try {
                    SqliteUtil.createDatabases();
                    logger.info("資料庫創建成功!");
                    primaryStage.close();
                    start(new Stage());
                } catch (SQLException | IOException e) {
                    e.printStackTrace();
                }
            });
        }

 

創建資料庫需要一些時間,這個時候可以給一個 Loading 界面:

經過測試發現創建資料庫和表時間並不長,所以這一步可以省略,當然如果表多那就看情況了。

 

JFoenix 界面開發

JFoenix 的界面非常好看,Google Material 的設計風格,案例:

 

 

這個 UI 庫是開源的,非常美觀,提供的控制項也很豐富,只是文檔感覺不是很好,但好在可以再官方提供的 Demo 案例查看控制項的使用。

Github 地址 : 
https://github.com/jfoenixadmin/JFoenix

官方文檔 : 
http://www.jfoenix.com/documentation.html

JFoenix 表格 TreeTable

官方案例:

如果覺得 JFoenix 的表格實現代碼要比原生的簡單,那你就錯了,代碼量依舊比較大,而且如果需要對錶的列綁定欄位,欄位不是只讀,就是如果你需要表的欄位可以被編輯操作,那麼相應的綁定的欄位類型必須是 JavaFX 提供的 Property 類型,JavaFX 為這種類型提供了綁定方法,但是如果是使用這種類型去結合 Hibernate 欄位映射,報錯沒跑了。

所以,我只能將用戶映射表的實體類和綁定表的類分開,分為兩個類,一個欄位類型是原生的,另一個欄位類型是 Property 類型。


public class LetvCookieTable extends RecursiveTreeObject<LetvCookieTable> {

    public long cookieId;

    public StringProperty cookieKey;

    public StringProperty cookieValue;

    public LetvCookieTable(long cookieId,String cookieKey, String cookieValue) {
        this.cookieId = cookieId;
        this.cookieKey = new SimpleStringProperty(cookieKey);
        this.cookieValue = new SimpleStringProperty(cookieValue);
    }

這個就是用來綁定表的實體類,再表格界面載入的時候,查詢返回實體類結果集,接著將實體類轉換成 Property 類型的類添加到 ObservableList 中。

欄位綁定

  //column
            JFXTreeTableColumn<LetvCookieTable, String> key = new JFXTreeTableColumn<>("Key");
            key.setCellValueFactory((TreeTableColumn.CellDataFeatures<LetvCookieTable, String> param) -> {
                if (key.validateValue(param)) {
                    return param.getValue().getValue().cookieKey;
                } else {
                    return key.getComputedValue(param);
                }
            });
            JFXTreeTableColumn<LetvCookieTable, String> value = new JFXTreeTableColumn<>("Value");
            value.setCellValueFactory((TreeTableColumn.CellDataFeatures<LetvCookieTable, String> param) -> {
                if (value.validateValue(param)) {
                    return param.getValue().getValue().cookieValue;
                } else {
                    return value.getComputedValue(param);
                }
            });

 

TreeTable 綁定刪除按鈕

現在需要一個刪除的列,提供刪除按鈕,點擊後刪除這一行的數據。

代碼和 TableView 大體上是一樣的,但在取值上有點小差異。

 JFXTreeTableColumn<LetvCookieTable, String> options = new JFXTreeTableColumn<>("options");
            options.setCellFactory(new Callback<TreeTableColumn<LetvCookieTable, String>, TreeTableCell<LetvCookieTable, String>>() {
                @Override
                public TreeTableCell<LetvCookieTable, String> call(TreeTableColumn<LetvCookieTable, String> param) {
                    JFXButton button = new JFXButton();
                    button.setText("刪除");
                    return new TreeTableCell<LetvCookieTable, String>() {
                        JFXButton delBtn = button;

                        @Override
                        protected void updateItem(String item, boolean empty) {
                            super.updateItem(item, empty);
                            if (empty) {
                                setGraphic(null);
                                setText(null);
                            } else {
                                delBtn.setOnMouseClicked(event -> {
                                    Transaction transaction = session.beginTransaction();
                                    logger.info("刪除:" + getIndex());
                                    LetvCookieTable table = getTreeTableView().getTreeItem(getIndex()).getValue();
                                    session.doWork(connection -> {
                                        Statement st;
                                        logger.info("id:" + table.cookieId);
                                        String sql = "delete from letv_cookie where cookie_id = " + table.cookieId;
                                        st = connection.createStatement();
                                        st.executeUpdate(sql);
                                        st.close();
                                    });
                                    NotificationUtil.notification("信息", "刪除成功", "info");
                                    transaction.commit();
                                    observableCookie.remove(getIndex());
                                });
                                setGraphic(button);
                                setText(null);
                            }
                        }
                    };
                }
            });

JavaFX 獲取當前行 :

getTableView().getItems().get(getIndex())

JFoenix :

getTreeTableView().getTreeItem(getIndex()).getValue()

 

TreeTable 可編輯

 key.setCellFactory((TreeTableColumn<LetvCookieTable, String> param) -> new GenericEditableTreeTableCell<>(
                    new TextFieldEditorBuilder()));
            key.setOnEditCommit((TreeTableColumn.CellEditEvent<LetvCookieTable, String> t) -> {
              LetvCookieTable table =  t.getTreeTableView().getTreeItem(t.getTreeTablePosition()
                        .getRow())
                        .getValue();
                table.cookieKey.set(t.getNewValue());
                Transaction transaction = session.beginTransaction();
                Query updateLink = session.createQuery("update db.LetvCookieEntity set cookieKey = :newVal where cookieId=" + table.cookieId);
                updateLink.setParameter("newVal", t.getNewValue());
                updateLink.executeUpdate();
                transaction.commit();
                session.clear();
                NotificationUtil.notification("信息","更新成功!","info");

            });

 

未完待續 ……


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 項目進行微信開發, 認證了一個微信服務號專門用於內部測試,但是內部可能存在多套不同環境(開發dev、測試sit、預發佈uat)等,由於微信限制一個服務號只能配置一個網頁授權功能變數名稱, 又不可能給每個環境單獨配一個服務號,這樣不僅需要成本而且很浪費資源, 所以重點需要解決下麵這個問題: 1、可以自動區分環 ...
  • 函數概念: 函數是用來完成某種特定任務的可重用代碼塊; 函數可以使程式更具模塊化,擁有良好的結構; 函數定義後在程式中可以重覆調用; 函數分為內置函數和自定義函數 考點: 變數的作用域和靜態變數 延伸1,函數的參數及參數的引用傳遞。 延伸2,函數的返回值及引用返回。 延伸3,外部文件的導入。 延伸4 ...
  • [TOC] 原文鏈接: "Qt實現表格樹控制項 自繪樹節點虛線" 一、開心一刻 一程式員第一次上女朋友家她媽板著臉問 :你想娶我女兒,有多少存款? 程式員低了下頭:五百! 她媽更鄙視了:才五百塊,買個廁所都不夠! 程式員忙說:不是人民幣! 她媽:就算是美元,還是不夠買廁所! 程式員:其實是比特幣! 她 ...
  • 後端基於方法的許可權控制 Spirng Security 預設情況下, Spring Security 並不啟用方法級的安全管控. 啟用方法級的管控後, 可以針對不同的方法通過註解設置不同的訪問條件;Spring Security 支持三種方法級註解, 分別是 JSR 205/Secured 註解/p ...
  • 一、生活場景 基於建造者模式,描述軟體開發的流程。 1、代碼實現 2、代碼結構圖 二、建造者模式 1、基礎概念 建造模式是對象的創建模式。建造模式可以將一個產品的內部屬性描述與產品的生產過程分割,從而可以使一個建造過程生成具有不同的內部表象的產品對象。也就是使用一個中介對象封裝一系列的對象交互,使其 ...
  • 一. 數據類型轉換 1.1 自動類型轉換 又叫:隱式類型轉換 概念:數據範圍小的類型能自動轉換成數據範圍大的類型 byte short int long float double 1.2 強制類型轉換 概念:將數據範圍大的類型使用指定格式轉換成數據範圍小的類型 格式:範圍小的數據類型 變數名 = ( ...
  • Ural 1029 Ministry 題解 [TOC] 題意 給定一個$n\times m(1\le n \le10,1\le m \le500)$的矩陣,矩陣中的每個值都是一個小於等於$10^9$的正整數。 現在從第$1$行的任意位置開始,在第$n$行的任意位置結束。每次有$3$種移動選擇(不能移 ...
  • 上班有一周了,聊一聊在大廠上班的一點感受和學到的一些東西。 1.吃飯也有江湖 入職那天去了周圍的一家早餐點,看了一眼價格,不得不說帝都著外面的店真的貴啊,半根油條6塊錢,一碗豆漿5塊錢,一根烤腸5塊錢,隨隨便便一頓早飯就15 20塊了,瑟瑟發抖。。。平時午餐點個外賣的話算上餐盒費和送餐費,一個外賣大 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...