倉儲模式Repository的選擇與設計

来源:http://www.cnblogs.com/Zhang-Xiang/archive/2017/11/15/7839540.html
-Advertisement-
Play Games

首次接觸倉儲的概念來自Eric Evans 的經典著作《領域驅動設計-軟體核心複雜性應對之道》,但書中沒有具體實現。如何實現倉儲模式,在我這幾年的使用過程中也積累了一些具體的實施經驗。根據項目的大小、可維護性、可擴展性,以及併發我們可以做以下幾種設計; 1、項目小,擴展性差 public inter... ...


首次接觸倉儲的概念來自Eric Evans 的經典著作《領域驅動設計-軟體核心複雜性應對之道》,但書中沒有具體實現。如何實現倉儲模式,在我這幾年的使用過程中也積累了一些具體的實施經驗。根據項目的大小、可維護性、可擴展性,以及併發我們可以做以下幾種設計;

 

1、項目小,擴展性差

public interface IRepository<T> where T : class,new()
    {
        /// <summary>
        /// 創建對象
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        T Create(T model);

        /// <summary>
        /// 更新對象
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        T Update(T model);

        /// <summary>
        /// 根據對象全局唯一標識檢索對象
        /// </summary>
        /// <param name="guid"></param>
        /// <returns></returns>
        T Retrieve(Guid guid);


        /// <summary>
        /// 根據條件表達式檢索對象
        /// </summary>
        /// <param name="expression">條件表達式</param>
        /// <returns></returns>
        /// <exception cref = "ArgumentNullException" > source 為 null</exception>
        T Retrieve(Expression<Func<T, bool>> expression);

        /// <summary>
        /// 根據對象全局唯一標識刪除對象
        /// </summary>
        /// <param name="guid">對象全局唯一標識</param>
        /// <returns>刪除的對象數量</returns>
        int Delete(Guid guid);

        /// <summary>
        /// 根據對象全局唯一標識集合刪除對象集合
        /// </summary>
        /// <param name="guids">全局唯一標識集合</param>
        /// <returns>刪除的對象數量</returns>
        int BatchDelete(IList<Guid> guids);

        List<T> GetAll();

        List<T> GetAll(Expression<Func<T, bool>> expression, Expression<Func<T, dynamic>> sortPredicate, SortOrder sortOrder, int skip, int take, out int total);
    }
 
IRepository介面包含了CRUD操作,如果在業務中還需要擴展,只需在IRepository介面中添加即可。
public class RepositoryImpl<T> : IRepository<T> where T : class, new()
    {
        protected readonly string ConnectionString;

        protected RepositoryImpl(ISqlHelp sqlHelp)
        {
            ConnectionString = sqlHelp.SQLConnectionString();
        }

        public int BatchDelete(IList<Guid> guids)
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                foreach (var item in guids)
                {
                    var model = dbcontext.Set<T>().Find(item);
                    dbcontext.Entry(model).State = EntityState.Deleted;
                }
                return dbcontext.SaveChanges();
            }
        }

        public T Create(T model)
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                dbcontext.Entry(model).State = EntityState.Added;
                var createRowCount = dbcontext.SaveChanges();
                return createRowCount > 0 ? model : null;
            }
        }

        /// <summary>
        /// 刪除模型
        /// </summary>
        /// <param name="guid">指定的全局標識</param>
        /// <returns>刪除數量</returns>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public int Delete(Guid guid)
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                var model = dbcontext.Set<T>().Find(guid);
                if (model == null) throw new ArgumentOutOfRangeException(nameof(guid));
                dbcontext.Entry(model).State = EntityState.Deleted;
                return dbcontext.SaveChanges();
            }
        }

        public List<T> GetAll()
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                return dbcontext.Set<T>().Where(q => true).ToList();
            }
        }

        public List<T> GetAll(Expression<Func<T, bool>> expression, Expression<Func<T, dynamic>> sortPredicate, SortOrder sortOrder, int skip, int take, out int total)
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                total = dbcontext.Set<T>().Where(expression).Count();
                switch (sortOrder)
                {
                    case SortOrder.Ascending:
                        return dbcontext.Set<T>().Where(expression).OrderBy(sortPredicate).Skip(skip).Take(take).ToList();

                    case SortOrder.Descending:
                        return dbcontext.Set<T>().Where(expression).OrderByDescending(sortPredicate).Skip(skip).Take(take).ToList();

                }
                throw new InvalidOperationException("基於分頁功能的查詢必須指定排序欄位和排序順序。");
            }
        }

        /// <summary>
        /// 返回序列中的第一個元素
        /// </summary>
        /// <param name="expression">查詢表達式</param>
        /// <returns>T</returns>
        /// <exception cref="ArgumentNullException">source 為 null</exception>
        public T Retrieve(Expression<Func<T, bool>> expression)
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                return dbcontext.Set<T>().FirstOrDefault(expression);
            }
        }

        public T Retrieve(Guid guid)
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                return dbcontext.Set<T>().Find(guid);
            }
        }

        public T Update(T model)
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                dbcontext.Entry(model).State = EntityState.Modified;
                var updateRowAcount = dbcontext.SaveChanges();
                return updateRowAcount > 0 ? model : null;
            }
        }
    }
 
RepositoryImpl為IRepository介面的實現。其中ISqlHelp介面包含獲取資料庫鏈接字元串的功能,DbContext為EntityFramework類庫。
 
public sealed class UserServer
    {
        private readonly IRepository<User> _userRepository;

        public UserServer(IRepository<User> userRepository)
        {
            _userRepository = userRepository;
        }

        public void CreateUser()
        {
            var user = new User();
            _userRepository.Create(user);
        }
    }

 

這是最簡單的倉儲使用方式,優點是簡單、快速,缺點是擴展性差且違反開放-關閉原則(Open-Close Principle)。但如果項目小且項目生存周期短可選擇此模式進行快速搭建。


 

2、項目大,可擴展性好,不對併發做處理。

因為項目要求高擴展性,每次修改都對IRepository修改也違反軟體設計原則。這裡IRepository介面不變,但是RepositoryImpl做如下修改:

public class RepositoryImpl<T> : IRepository<T> where T : class, new()
    {
        protected readonly string ConnectionString;

        protected RepositoryImpl(ISqlHelp sqlHelp)
        {
            ConnectionString = sqlHelp.SQLConnectionString();
        }

        public virtual int BatchDelete(IList<Guid> guids)
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                foreach (var item in guids)
                {
                    var model = dbcontext.Set<T>().Find(item);
                    dbcontext.Entry(model).State = EntityState.Deleted;
                }
                return dbcontext.SaveChanges();
            }
        }

        public virtual T Create(T model)
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                dbcontext.Entry(model).State = EntityState.Added;
                var createRowCount = dbcontext.SaveChanges();
                return createRowCount > 0 ? model : null;
            }
        }

        /// <summary>
        /// 刪除模型
        /// </summary>
        /// <param name="guid">指定的全局標識</param>
        /// <returns>刪除數量</returns>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public virtual int Delete(Guid guid)
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                var model = dbcontext.Set<T>().Find(guid);
                if (model == null) throw new ArgumentOutOfRangeException(nameof(guid));
                dbcontext.Entry(model).State = EntityState.Deleted;
                return dbcontext.SaveChanges();
            }
        }

        public virtual List<T> GetAll()
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                return dbcontext.Set<T>().Where(q => true).ToList();
            }
        }

        public virtual List<T> GetAll(Expression<Func<T, bool>> expression, Expression<Func<T, dynamic>> sortPredicate, SortOrder sortOrder, int skip, int take, out int total)
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                total = dbcontext.Set<T>().Where(expression).Count();
                switch (sortOrder)
                {
                    case SortOrder.Ascending:
                        return dbcontext.Set<T>().Where(expression).OrderBy(sortPredicate).Skip(skip).Take(take).ToList();

                    case SortOrder.Descending:
                        return dbcontext.Set<T>().Where(expression).OrderByDescending(sortPredicate).Skip(skip).Take(take).ToList();

                }
                throw new InvalidOperationException("基於分頁功能的查詢必須指定排序欄位和排序順序。");
            }
        }

        /// <summary>
        /// 返回序列中的第一個元素
        /// </summary>
        /// <param name="expression">查詢表達式</param>
        /// <returns>T</returns>
        /// <exception cref="ArgumentNullException">source 為 null</exception>
        public virtual T Retrieve(Expression<Func<T, bool>> expression)
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                return dbcontext.Set<T>().FirstOrDefault(expression);
            }
        }

        public virtual T Retrieve(Guid guid)
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                return dbcontext.Set<T>().Find(guid);
            }
        }

        public virtual T Update(T model)
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                dbcontext.Entry(model).State = EntityState.Modified;
                var updateRowAcount = dbcontext.SaveChanges();
                return updateRowAcount > 0 ? model : null;
            }
        }
    }
}

 

即在每個方法實現上加上了virtual關鍵字使方法可以重載。在示例1中業務使用User對象的倉儲方式為IRepository<User>,如果業務需要針對User對象集合做批量修改,這時就必須去修改IRepository和RepositoryImpl,所以這裡將添加介面IUserRepository,

    /// <summary>
    /// 用戶倉儲介面
    /// </summary>
    public interface IUserRepository:IRepository<User>
    {
        /// <summary>
        /// 批量修改用戶生日
        /// </summary>
        void BatchUpdateUserBirthday();
    }

 

UserRepositoryImpl實現為

public sealed class UserRepositoryImpl: RepositoryImpl<User>,IUserRepository
    {
        public UserRepositoryImpl(ISqlHelp sqlHelp) : base(sqlHelp)
        {

        }

        public void BatchUpdateUserBirthday()
        {
            using (var dbcontext = new DbContext(ConnectionString))
            {
                var usersFromDb = dbcontext.Set<User>().Where(q => q.Name.Equals("zhang"));
                foreach (var item in usersFromDb)
                {
                    item.Name = "wang";
                    dbcontext.Entry(item).State = EntityState.Modified;
                }
                dbcontext.SaveChanges();
            }
        }
    }

 

這裡不對代碼的實現合理性做討論,只是為了說明倉儲模式的設計。

而在業務層中的使用如下:

public sealed class UserServer
    {
        private readonly IUserRepository _userRepository;

        public UserServer(IUserRepository userRepository)
        {
            _userRepository = userRepository;
        }

        public void CreateUser()
        {
            var user = new User();
            _userRepository.Create(user);
        }

        public void BatchUpdateBirthDay()
        {
            _userRepository.BatchUpdateUserBirthday();
        }

 

此倉儲模式在實際使用中稍顯複雜,每添加一個實體,需要添加對應的介面和實現兩個文件,但是這裡的一點複雜度換來代碼的高擴展性和維護性是值得的。

 

3、項目龐大,擴展性高,有併發處理需求

因為項目涉及高併發,採用倉儲模式+工作單元模式的設計,使用工作單元的原因是可以提高資料庫寫操作負載,並且在倉儲模式中可以根據不同的資料庫鏈接字元串讀不同的庫。

對於併發的,可以分為多線程、並行處理、非同步編程、響應式編程。(引用:《Concurrency in C# Cookbook》—Author,Stephen Cleary)

在倉儲中我會使用非同步編程實現併發。

 

倉儲介面如下:

public interface IRepository<T> where T:class,IEntity,new ()
    {
        /// <summary>
        /// 根據條件表達式獲取集合
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        Task<List<T>> FindByAsync(Expression<Func<T, bool>> predicate);

        IQueryable<T> FindQueryableByAsync(Expression<Func<T, bool>> predicate);

        /// <summary>
        /// 根據對象全局唯一標識檢索對象
        /// </summary>
        /// <param name="ID"></param>
        /// <returns></returns>
        Task<T> RetrieveAsync(Guid ID);

        /// <summary>
        /// 根據條件表達式檢索對象
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        Task<T> RetrieveAsync(Expression<Func<T, bool>> predicate);

        /// <summary>
        /// 獲取所有數據
        /// </summary>
        /// <returns></returns>
        Task<List<T>> GetAllAsync();

        /// <summary>
        /// 獲取所有數據
        /// </summary>
        /// <returns></returns>
        List<T> GetAll();

        /// <summary>
        /// 根據條件表示分頁獲取數據集合
        /// </summary>
        /// <param name="predicate">斷言表達式</param>
        /// <param name="sortPredicate">排序斷言</param>
        /// <param name="sortOrder">排序方式</param>
        /// <param name="skip">跳過序列中指定數量的元素,然後返回剩餘的元素</param>
        /// <param name="take">從序列的開頭返回指定數量的連續元素</param>
        /// <returns>item1:數據集合;item2:數據總數</returns>
        Task<Tuple<List<T>,int>> GetAllAsync(Expression<Func<T, bool>> predicate, Expression<Func<T, dynamic>> sortPredicate, SortOrder sortOrder, int skip, int take);
    }

 

工作單元介面如下:

/// <summary>
    /// Unit Of Work Pattern
    /// </summary>
    public interface IUnitOfWork : IDisposable
    {
        DbContext DbContext { get; set; }

        /// <summary>
        /// 提交所有更改
        /// </summary>
        Task CommitAsync();
        
        #region Methods
        /// <summary>
        /// 將指定的聚合根標註為“新建”狀態。
        /// </summary>
        /// <typeparam name="T">需要標註狀態的聚合根類型。</typeparam>
        /// <param name="obj">需要標註狀態的聚合根。</param>
        void RegisterNew<T>(T obj)
            where T : class, IEntity;
        /// <summary>
        /// 將指定的聚合根標註為“更改”狀態。
        /// </summary>
        /// <typeparam name="T">需要標註狀態的聚合根類型。</typeparam>
        /// <param name="obj">需要標註狀態的聚合根。</param>
        void RegisterModified<T>(T obj)
            where T : class;
        /// <summary>
        /// 將指定的聚合根標註為“刪除”狀態。
        /// </summary>
        /// <typeparam name="T">需要標註狀態的聚合根類型。</typeparam>
        /// <param name="obj">需要標註狀態的聚合根。</param>
        void RegisterDeleted<T>(T obj)
            where T : class;
        #endregion
    }
 
倉儲實現如下:
public class RepositoryImpl<T> : IRepository<T> where T : class, IEntity, new()
    {
        protected readonly DbContext Context;

        protected RepositoryImpl(IContextHelper contextHelper)
        {
            Context = contextHelper.DbContext;
        }

        public virtual async Task<List<T>> FindByAsync(Expression<Func<T, bool>> predicate)
        {
            return await Context.Set<T>().Where(predicate).ToListAsync();
        }

        public virtual IQueryable<T> FindQueryableByAsync(Expression<Func<T, bool>> predicate)
        {
            return Context.Set<T>().Where(predicate);
        }

        public virtual async Task<List<T>> GetAllAsync()
        {
            return await Context.Set<T>().ToListAsync();
        }

        public List<T> GetAll()
        {
            return Context.Set<T>().ToList();
        }

        public virtual async Task<Tuple<List<T>, int>> GetAllAsync(Expression<Func<T, bool>> predicate,
            Expression<Func<T, dynamic>> sortPredicate, SortOrder sortOrder, int skip, int take)
        {
            var result = Context.Set<T>().Where(predicate);
            var total = result.Count();
            switch (sortOrder)
            {

                case SortOrder.Ascending:
                    var resultAscPaged = await
                        Context.Set<T>().Where(predicate).OrderBy(sortPredicate).Skip(skip).Take(take).ToListAsync();
                    return new Tuple<List<T>, int>(resultAscPaged, total);


                case SortOrder.Descending:
                    var resultDescPaged = await
                        Context.Set<T>().Where(predicate)
                            .OrderByDescending(sortPredicate)
                            .Skip(skip)
                            .Take(take).ToListAsync();
                    return new Tuple<List<T>, int>(resultDescPaged, total);
            }
            throw new InvalidOperationException("基於分頁功能的查詢必須指定排序欄位和排序順序。");
        }

        public virtual async Task<T> RetrieveAsync(Expression<Func<T, bool>> predicate)
        {
            return await Context.Set<T>().FirstOrDefaultAsync(predicate);
        }

        public virtual async Task<T> RetrieveAsync(Guid id)
        {
            return await Context.Set<T>().FindAsync(id);
        }
    }

 

工作單元實現如下:

public class UnitOfWork : IUnitOfWork
    {
        public DbContext DbContext { get; set; }
        public UnitOfWork(IContextHelper contextHelp)
        {
            DbContext = contextHelp.DbContext;
        }

        /// <summary>
        /// Saves all pending changes
        /// </summary>
        /// <returns>The number of objects in an Added, Modified, or Deleted state</returns>
        public virtual async Task CommitAsync()
        {
            // Save changes with the default options
            try
            {
                await DbContext.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                ex.Entries.Single().Reload();
            }

        }

        /// <summary>
        /// Disposes the current object
        /// </summary>
        public virtual void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Disposes all external resources.
        /// </summary>
        /// <param name="disposing">The dispose indicator.</param>
        private void Dispose(bool disposing)
        {
            if (!disposing) return;
            if (DbContext == null) return;

            DbContext.Dispose();
            DbContext = null;
        }

        public virtual void RegisterNew<TEntity>(TEntity obj) where TEntity : class, IEntity
        {
            DbContext.Set<TEntity>().Add(obj);
        }

        public virtual void RegisterModified<TEntity>(TEntity obj) where TEntity : class
        {
            DbContext.Entry(obj).State = EntityState.Modified;
        }

        public virtual void RegisterDeleted<TEntity>(TEntity obj) where TEntity : class
        {
            DbContext.Entry(obj).State = EntityState.Deleted;
        }

    }

 

在業務層中的使用同2。


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

-Advertisement-
Play Games
更多相關文章
  • 這是我在一個面試初級工程師的時候遇到的一個問題,覺得在項目中都沒怎麼遇到. 一.編寫一個Student對象 二.編寫測試類主要用到得時Collections這個輔助工具 1.Collections.sort方法支持對對象進組排序 2.使用方法:只要實現Comparator方法. 3.實現目的:主要是 ...
  • 要生成對象並通過名稱空間註入屬性的類 代碼如下: XML配置文件寫法如下: 生成對象及屬性值調用方法,代碼如下: ...
  • 作者:NiceCui 本文謝絕轉載,如需轉載需徵得作者本人同意,謝謝。 本文鏈接:http://www.cnblogs.com/NiceCui/p/7835122.html 郵箱:[email protected] 日期:2017-11-14 22:26 將枚舉作為參數傳遞在複雜的服務調用中也是很常 ...
  • 鍵盤操作 功能 Alt + / 語句或變數名自動補全 Ctrl + Shift + F 語句格式化 Ctrl + / 單行註釋(或取消單行註釋) Ctrl + Shift + / 多行註釋 Ctrl + Shift + \ 取消多行註釋 Ctrl + Shift + o 快捷導包 Alt + 上下箭 ...
  • Given a positive integer N, your task is to calculate the sum of the positive integers less than N which are not coprime to N. A is said to be coprime ...
  • 記錄下麵試裡面遇到的一些java盲區,一方面掃描自己的知識盲區,一方面也可以給後面面試的朋友一些警示,以免面試的時候出現不知道的尷尬情況。 提出問題:父類靜態屬性,父類屬性,父類構造方法,子類靜態屬性, 子類屬性,子類構造方法的初始化順序? 提出猜想:父類靜態屬性=> 父類屬性=> 父類構造方法= ...
  • 靜態方法和實例方法的區別主要體現在兩個方面: 在外部調用靜態方法時,可以使用"類名.方法名"的方式,也可以使用"對象名.方法名"的方式。而實例方法只有後面這種方式。也就是說,調用靜態方法可以無需創建對象。 靜態方法在訪問本類的成員時,只允許訪問靜態成員(即靜態成員變數和靜態方法),而不允許訪問實例成 ...
  • 有一個模式可以幫助你的對象知悉現況,不會錯過該對象感興趣的事,對象甚至在運行時可以決定是否要繼續被通知,如果一個對象狀態的改變需要通知很多對這個對象關註的一系列對象,就可以使用觀察者模式 。觀察者模式也是JDK中使用最多的一個設計模式,而我們本章討論的就是它。 那麼首先,我們先來看一看此模式的定義: ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...