2.自定義@Excel註解實現數據Excel形式導入導出

来源:https://www.cnblogs.com/kzf-99/archive/2023/04/07/17296402.html
-Advertisement-
Play Games

前言 這幾天在學習如何使用自定義註解實現Excel格式數據導入導出,參考的還是若依框架裡面的代碼,由於是初學,所以照貓畫虎唄,但是難受的是需要複製並根據自己項目修改作者自定義的工具類以及導入這些工具類的依賴包。由於吃了這個苦,我決定把這個艱辛的CV操作通過一張邏輯圖來表達,方便我以後復用。下麵證實開 ...


前言

  這幾天在學習如何使用自定義註解實現Excel格式數據導入導出,參考的還是若依框架裡面的代碼,由於是初學,所以照貓畫虎唄,但是難受的是需要複製並根據自己項目修改作者自定義的工具類以及導入這些工具類的依賴包。由於吃了這個苦,我決定把這個艱辛的CV操作通過一張邏輯圖來表達,方便我以後復用。下麵證實開始介紹這個功能的實現,但是由於對項目中的只是很不瞭解,我這裡簡單實現,並簡單講解,深層次的代碼我會給出,後續會繼續運用講解。整篇博客分為兩個部分,一部分是數據的導出,一部分是數據的導入。本文項目鏈接:WomPlus: 結合若依項目對原始工單項目內容進行增強 (gitee.com)

1.所需要的依賴

  在進行項目前,我們需要導入依賴才能引用具體的功能,因此第一步就是導入依賴了,這裡除了Excel需要的依賴,一些工具類的依賴也需要導入,為什麼呢?因為作者的項目寫的很細緻,比如在咋們的java中自帶有StringUtils工具類,但是作者細緻地自己寫了一個,可見其基礎功能之深呀!

<!--Excel工具類依賴-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.2</version>
</dependency>

<!--常用工具類依賴-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>

<!--io常用工具依賴-->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

<!--解決@ConfigurationProperties(prefix = "wo")的
Springboot Configuration Annotation Processor not found in classPath問題依賴-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
所需依賴

2.修改配置文件

  引入依賴是第一步,修改配置文件就是我們的第二步了,在這裡修改配置文件是因為在該功能的數據導入,作者是在自己自定義的RuoYiConfig中通過

@ConfigurationProperties(prefix = "wo")註解獲取配置文件中profile屬性值,即我們上傳下載文件的地址。我這裡使用的是properties為尾碼的文件,配置文件為yaml的請根據自己文件類型修改
//添加自己的項目配置類WoConfig需要配置的項目名和導出Excel形式數據下載路徑
#項目相關配置
wo.name=WO
wo.profile=G:/Desktop/base_study/project/myself_springboot_project/wom-plus/uploadPath

3.數據以Excel格式導出的Controller層

@PostMapping("/export")
@ResponseBody
public AjaxResult export(@RequestParam(value = "name", required = false) String username){
    List<SysUser> list = userDetailsService.getUserListByUsername(username);
    ExcelUtil<SysUser> util = new ExcelUtil<>(SysUser.class);
    return util.exportExcel(list, "用戶數據");
}
  在UserController的export()方法中,首先傳入一個Class.class作為ExcelUtils類的參數來new一個ExcelUtils對象util,然後傳遞一個Class的list對象和表名稱到util的exportExcel()方法中返回Class的Excel信息。

3.1 ExcelUtils的有參構造

//1.傳入需要Excel導出類的Class.class,返回一個ExcelUtil<Class>對象
public ExcelUtil(Class<T> clazz)
{
    this.clazz = clazz;
}

  這裡就是傳入一個Class類來創建該類的Excel對象,本篇博客圍繞著SysUser類實現該功能的,因此這裡傳入SysUser.class

3.2 exportExcel(List<T>list, String sheetName)

public AjaxResult exportExcel(List<T> list, String sheetName)
{
    return exportExcel(list, sheetName, StringUtils.EMPTY);
}

  在方法中,調用exportExcel(List<SysUser>list, String sheetName, String title)封裝Excel格式數據導出

3.3 exportExcel(List<T> list, String sheetName, String title)

public AjaxResult exportExcel(List<T> list, String sheetName, String title)
{
    this.init(list, sheetName, title, Type.EXPORT);
    return exportExcel();
}

  本文方法首先初始化要創建的Excel表格,然後返回exportExcel()方法來講要導出的數據寫進前面的profile路徑中

3.3.1 init(list, sheetName, title, Type.EXPORT)

public void init(List<T> list, String sheetName, String title, Type type)
{
    if (list == null)
    {
        list = new ArrayList<T>();
    }
    this.list = list;
    this.sheetName = sheetName;
    this.type = type;
    this.title = title;
    //根據要導出Excel的實體類的欄位創建欄位Class類所需要的Excel欄位
    createExcelField();
    createWorkbook();//創建一個工作薄
    createTitle();//創建Excel第一行標題
    createSubHead();//創建對象子列表名稱
}

3.3.2 exportExcel()

public AjaxResult exportExcel()
{
    OutputStream out = null;
    try
    {
        writeSheet();//寫入數據到Sheet
        String filename = encodingFilename(sheetName);//編輯文件名
        //getAbsoluteFile(filename)根據文件名稱獲取下載路徑
        //創建一個輸出流
        out = new FileOutputStream(getAbsoluteFile(filename));
        wb.write(out);//寫入Excel信息到該路徑
        return AjaxResult.success(filename);//返回導出成功信息
    }
    catch (Exception e)
    {
        log.error("導出Excel異常{}", e.getMessage());
        throw new UtilException("導出Excel失敗,請聯繫網站管理員!");
    }
    finally
    {
        IOUtils.closeQuietly(wb);//關閉工作薄對象輸出流
        IOUtils.closeQuietly(out);//關閉輸出流
    }
}
導出資料庫數據到指定路徑

3.4 @Excel和@Excels註解

package com.ku.wo.framework.aspectj.lang.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.math.BigDecimal;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import com.ku.wo.common.utils.poi.ExcelHandlerAdapter;

/**
 * 自定義導出Excel數據註解
 * 
 * @author ruoyi
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)//作用在什麼地方
public @interface Excel
{
    /**
     * 導出時在excel中排序
     */
    public int sort() default Integer.MAX_VALUE;

    /**
     * 導出到Excel中的名字.
     */
    public String name() default "";

    /**
     * 日期格式, 如: yyyy-MM-dd
     */
    public String dateFormat() default "";

    /**
     * 如果是字典類型,請設置字典的type值 (如: sys_user_sex)
     */
    public String dictType() default "";

    /**
     * 讀取內容轉表達式 (如: 0=男,1=女,2=未知)
     */
    public String readConverterExp() default "";

    /**
     * 分隔符,讀取字元串組內容
     */
    public String separator() default ",";

    /**
     * BigDecimal 精度 預設:-1(預設不開啟BigDecimal格式化)
     */
    public int scale() default -1;

    /**
     * BigDecimal 舍入規則 預設:BigDecimal.ROUND_HALF_EVEN
     */
    public int roundingMode() default BigDecimal.ROUND_HALF_EVEN;

    /**
     * 導出時在excel中每個列的高度 單位為字元
     */
    public double height() default 14;

    /**
     * 導出時在excel中每個列的寬 單位為字元
     */
    public double width() default 16;

    /**
     * 文字尾碼,如% 90 變成90%
     */
    public String suffix() default "";

    /**
     * 當值為空時,欄位的預設值
     */
    public String defaultValue() default "";

    /**
     * 提示信息
     */
    public String prompt() default "";

    /**
     * 設置只能選擇不能輸入的列內容.
     */
    public String[] combo() default {};

    /**
     * 是否需要縱向合併單元格,應對需求:含有list集合單元格)
     */
    public boolean needMerge() default false;

    /**
     * 是否導出數據,應對需求:有時我們需要導出一份模板,這是標題需要但內容需要用戶手工填寫.
     */
    public boolean isExport() default true;

    /**
     * 另一個類中的屬性名稱,支持多級獲取,以小數點隔開
     */
    public String targetAttr() default "";

    /**
     * 是否自動統計數據,在最後追加一行統計數據總和
     */
    public boolean isStatistics() default false;

    /**
     * 導出類型(0數字 1字元串 2圖片)
     */
    public ColumnType cellType() default ColumnType.STRING;

    /**
     * 導出列頭背景顏色
     */
    public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT;

    /**
     * 導出列頭字體顏色
     */
    public IndexedColors headerColor() default IndexedColors.WHITE;

    /**
     * 導出單元格背景顏色
     */
    public IndexedColors backgroundColor() default IndexedColors.WHITE;

    /**
     * 導出單元格字體顏色
     */
    public IndexedColors color() default IndexedColors.BLACK;

    /**
     * 導出欄位對齊方式
     */
    public HorizontalAlignment align() default HorizontalAlignment.CENTER;

    /**
     * 自定義數據處理器
     */
    public Class<?> handler() default ExcelHandlerAdapter.class;

    /**
     * 自定義數據處理器參數
     */
    public String[] args() default {};

    /**
     * 欄位類型(0:導出導入;1:僅導出;2:僅導入)
     */
    Type type() default Type.ALL;

    public enum Type
    {
        ALL(0), EXPORT(1), IMPORT(2);
        private final int value;

        Type(int value)
        {
            this.value = value;
        }

        public int value()
        {
            return this.value;
        }
    }

    public enum ColumnType
    {
        NUMERIC(0), STRING(1), IMAGE(2);
        private final int value;

        ColumnType(int value)
        {
            this.value = value;
        }

        public int value()
        {
            return this.value;
        }
    }
}
//@Excels
package com.ku.wo.framework.aspectj.lang.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Excel註解集
 * 
 * @author ruoyi
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Excels
{
    Excel[] value();
}
@Excel和@Excels

3.5 運行結果

4.數據以Excel格式導出的Controller層

@PostMapping("/importData")
    @ResponseBody
    public AjaxResult importData(MultipartFile file) throws Exception {
        ExcelUtil<SysUser> util = new ExcelUtil<>(SysUser.class);
        List<SysUser> userList = util.importExcel(file.getInputStream());
        String message = userDetailsService.importUser(userList);
        return AjaxResult.success(message);
    }
  在UserController的importData()方法中,首先傳入一個Class.class作為ExcelUtils類的參數來new一個ExcelUtils對象util,然後傳入一個要導入的Excel形式的表格數據作為utils.importData()函數的輸入流is,importData()方法根據is返回Excel表格數據中的Class類的list對象,接著傳入一個Class的list對象和一個關於是否支持更新資料庫已有數據的布爾變數到importUser()方法中,該方法根據這兩個參數來返回插入成功或失敗的信息message,最後傳入message到AjaxResult.success()方法返回導入數據成功消息。

4.1 importExcel(InputStream)

public List<T> importExcel(InputStream is) throws Exception
{
    return importExcel(is, 0);
}

public List<T> importExcel(InputStream is, int titleNum) throws Exception
{
    return importExcel(StringUtils.EMPTY, is, titleNum);
}

//核心實現代碼
public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception
{
    this.type = Type.IMPORT;
    this.wb = WorkbookFactory.create(is);
    List<T> list = new ArrayList<T>();
    // 如果指定sheet名,則取指定sheet中的內容 否則預設指向第1個sheet
    Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0);
    if (sheet == null)
    {
        throw new IOException("文件sheet不存在");
    }
    boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook);
    Map<String, PictureData> pictures;
    if (isXSSFWorkbook)
    {
        pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb);
    }
    else
    {
        pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb);
    }
    // 獲取最後一個非空行的行下標,比如總行數為n,則返回的為n-1
    int rows = sheet.getLastRowNum();

    if (rows > 0)
    {
        // 定義一個map用於存放excel列的序號和field.
        Map<String, Integer> cellMap = new HashMap<String, Integer>();
        // 獲取表頭
        Row heard = sheet.getRow(titleNum);
        for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++)
        {
            Cell cell = heard.getCell(i);
            if (StringUtils.isNotNull(cell))
            {
                String value = this.getCellValue(heard, i).toString();
                cellMap.put(value, i);
            }
            else
            {
                cellMap.put(null, i);
            }
        }
        // 有數據時才處理 得到類的所有field.
        List<Object[]> fields = this.getFields();
        Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>();
        for (Object[] objects : fields)
        {
            Excel attr = (Excel) objects[1];
            Integer column = cellMap.get(attr.name());
            if (column != null)
            {
                fieldsMap.put(column, objects);
            }
        }
        for (int i = titleNum + 1; i <= rows; i++)
        {
            // 從第2行開始取數據,預設第一行是表頭.
            Row row = sheet.getRow(i);
            // 判斷當前行是否是空行
            if (isRowEmpty(row))
            {
                continue;
            }
            T entity = null;
            for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet())
            {
                Object val = this.getCellValue(row, entry.getKey());

                // 如果不存在實例則新建.
                entity = (entity == null ? clazz.newInstance() : entity);
                // 從map中得到對應列的field.
                Field field = (Field) entry.getValue()[0];
                Excel attr = (Excel) entry.getValue()[1];
                // 取得類型,並根據對象類型設置值.
                Class<?> fieldType = field.getType();
                if (String.class == fieldType)
                {
                    String s = Convert.toStr(val);
                    if (StringUtils.endsWith(s, ".0"))
                    {
                        val = StringUtils.substringBefore(s, ".0");
                    }
                    else
                    {
                        String dateFormat = field.getAnnotation(Excel.class).dateFormat();
                        if (StringUtils.isNotEmpty(dateFormat))
                        {
                            val = parseDateToStr(dateFormat, val);
                        }
                        else
                        {
                            val = Convert.toStr(val);
                        }
                    }
                }
                else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val)))
                {
                    val = Convert.toInt(val);
                }
                else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val)))
                {
                    val = Convert.toLong(val);
                }
                else if (Double.TYPE == fieldType || Double.class == fieldType)
                {
                    val = Convert.toDouble(val);
                }
                else if (Float.TYPE == fieldType || Float.class == fieldType)
                {
                    val = Convert.toFloat(val);
                }
                else if (BigDecimal.class == fieldType)
                {
                    val = Convert.toBigDecimal(val);
                }
                else if (Date.class == fieldType)
                {
                    if (val instanceof String)
                    {
                        val = DateUtils.parseDate(val);
                    }
                    else if (val instanceof Double)
                    {
                        val = DateUtil.getJavaDate((Double) val);
                    }
                }
                else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
                {
                    val = Convert.toBool(val, false);
                }
                if (StringUtils.isNotNull(fieldType))
                {
                    String propertyName = field.getName();
                    if (StringUtils.isNotEmpty(attr.targetAttr()))
                    {
                        propertyName = field.getName() + "." + attr.targetAttr();
                    }
                    else if (StringUtils.isNotEmpty(attr.readConverterExp()))
                    {
                        val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
                    }
                    else if (StringUtils.isNotEmpty(attr.dictType()))
                    {
                        val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());
                    }
                    else if (!attr.handler().equals(ExcelHandlerAdapter.class))
                    {
                        val = dataFormatHandlerAdapter(val, attr);
                    }
                    else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures))
                    {
                        PictureData image = pictures.get(row.getRowNum() + "_" + entry.getKey());
                        if (image == null)
                        {
                            val = "";
                        }
                        else
                        {
                            byte[] data = image.getData();
                            val = FileUtils.writeImportBytes(data);
                        }
                    }
                    ReflectUtils.invokeSetter(entity, propertyName, val);
                }
            }
            list.add(entity);
        }
    }
    return list;
}
importExcel

  該方法主要是通過傳入的文件獲取Excel表格中的數據,這裡是真正導入數據的核心,我暫時還沒想清楚。

4.2 importUser(InputStream)

String importUser(List<SysUser> userList);
@Override
    public String importUser(List<SysUser> userList) {
        //此處的isNull判斷的是一個對象,此時我傳入的是一個用戶list對象
        if (StringUtils.isNull(userList) || userList.size() == 0){
            throw new ServiceException("導入用戶數據不能為空!");
        }
        int successNum = 0;
        //StringBuilder字元拼接工具類,是一個可變類不安全,StringBuffer是一個不可變的安全字元拼接工具類
        StringBuilder successMsg = new StringBuilder();
        for (SysUser user: userList) {
            //因為前面驗證了該list對象不為空,故直接插入
            userMapper.insertUser(user);
            successNum++;
            successMsg.append("<br/>" + successNum + "、賬號 " + user.getUsername() + " 導入成功");
        }
        successMsg.insert(0, "恭喜你,數據已經全部導入成功!共" + successNum + "條,數據如下:");
        return successMsg.toString();
    }
Excel表格數據插入到對應資料庫的數據表

  該方法實現將Excel中獲取的數據導入到對應資料庫的數據表中,並返回導入成功的響應信息。

4.3 運行結果

5.參考項目連鏈接

RuoYi:

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

-Advertisement-
Play Games
更多相關文章
  • ps 命令速查備忘清單 Linux我們提供了一個名為 ps 的實用程式,用於查看與系統上的進程相關的信息,它是 Process Status 的縮寫這份 ps 命令備忘清單的快速參考列表,包含常用選項和示例。入門,為開發人員分享快速參考備忘單。 開發速查表大綱 入門 語法 示例 查看系統上的每個進程 ...
  • Perl語言線上運行編譯,是一款可線上編程編輯器,在編輯器上輸入Perl語言代碼,點擊運行,可線上編譯運行Perl語言,Perl語言代碼線上運行調試,Perl語言線上編譯,可快速線上測試您的Perl語言代碼,線上編譯Perl語言代碼發現是否存在錯誤,如果代碼測試通過,將會輸出編譯後的結果。 該線上工 ...
  • 簡介 ChatGPT Java版SDK開源地址:https://github.com/Grt1228/chatgpt-java ,目前收穫將近1000個star。 有bug歡迎朋友們指出,互相學習,所有咨詢全部免費。 最新版:1.0.10 <dependency> <groupId>com.unfb ...
  • 本文分享自天翼雲開發者社區@《Springfox與SpringDoc——swagger如何選擇(SpringDoc入門)》,作者: 才開始學技術的小白 0.引言 之前寫過一篇關於swagger(實際上是springfox)的使用指南(https://www.ctyun.cn/developer/ar ...
  • 1. execute 和 submit 的區別 前面說了還需要介紹多線程中使用 execute 和 submit 的區別(這兩個方法都是線程池 ThreadPoolExecutor 的方法)。 1.1 方法來源不同 execute 方法是線程池的頂層介面 Executor 定義的,在 ThreadP ...
  • 數字營銷,也就是網路營銷,即線上推廣你的產品或服務。 其核心的是:在正確的客戶面前線上推廣正確的產品 Right Customer Right Product Online 一、關於市場研究 網路營銷中,最重要的第一步是市場研究(Marketing Research)。 包括以下兩個方面: 誰是目標 ...
  • Python的反射機制可以動態獲取對象信息以及動態調用對象,本文介紹如何獲取對象中的函數註釋信息以及參數信息。 定義一個Person類: class Person(): def talk(self, name, age, height=None): """talk function :return: ...
  • 原文:blog.csdn.net/h254931252/article/details/109257998 在實際工作中,重處理是一個非常常見的場景,比如: 發送消息失敗。 調用遠程服務失敗。 爭搶鎖失敗。 這些錯誤可能是因為網路波動造成的,等待過後重處理就能成功。通常來說,會用try/catch, ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...