1. <i id="s6b2k"><small id="s6b2k"></small></i>
    <b id="s6b2k"><bdo id="s6b2k"></bdo></b>
  2. <wbr id="s6b2k"></wbr>

    通過lms.samples熟悉lms微服務框架的使用詳解_其它綜合

    來源:腳本之家  責任編輯:小易  

    經過一段時間的開發與測試,終于發布了Lms框架的第一個正式版本(1.0.0版本),并給出了lms框架的樣例項目lms.samples。本文通過對lms.samples的介紹,簡述如何通過lms框架快速的構建一個微服務的業務框架,并進行應用開發。

    lms.samples項目基本介紹

    lms.sample項目由三個獨立的微服務應用模塊組成:account、stock、order和一個網關項目gateway構成。

    業務應用模塊

    每個獨立的微服務應用采用模塊化設計,主要由如下幾部分組成:

      主機(Host): 主要用于托管微服務應用本身,主機通過引用應用服務項目(應用接口的實現),托管微服務應用,通過托管應用服務,在主機啟動的過程中,向服務注冊中心注冊服務路由。 應用接口層(Application.Contracts): 用于定義應用服務接口,通過應用接口,該微服務模塊與其他微服務模塊或是網關進行rpc通信的能力。在該項目中,除了定義應用服務接口之前,一般還定義與該應用接口相關的DTO對象。應用接口除了被該微服務應用項目引用,并實現應用服務之前,還可以被網關或是其他微服務模塊引用。網關或是其他微服務項目通過應用接口生成的代理與該微服務模塊通過rpc進行通信。 應用服務層(Application): 應用服務是該微服務定義的應用接口的實現。應用服務與DDD傳統分層架構的應用層的概念一致。主要負責外部通信與領域層之間的協調。一般地,應用服務進行業務流程控制,但是不包含業務邏輯的實現。 領域層(Domain): 負責表達業務概念,業務狀態信息以及業務規則,是該微服務模塊的業務核心。一般地,在該層可以定義聚合根、實體、領域服務等對象。 領域共享層(Domain.Shared): 該層用于定義與領域對象相關的模型、實體等相關類型。不包含任何業務實現,可以被其他微服務引用。 數據訪問(DataAccess)層: 該層一般用于封裝數據訪問相關的對象。例如:倉庫對象、 SqlHelper、或是ORM相關的類型等。在lms.samples中,通過efcore實現數據的讀寫操作。

    服務聚合與網關

    lms框架不允許服務外部與微服務主機直接通信,應用請求必須通過http請求到達網關,網關通過lms提供的中間件解析到服務條目,并通過rpc與集群內部的微服務進行通信。所以,如果服務需要與集群外部進行通信,那么,開發者定義的網關必須要引用各個微服務模塊的應用接口層;以及必須要使用lms相關的中間件。

    開發環境

      .net版本: 5.0.101 lms版本: 1.0.0 IDE: (1) visual studio 最新版 (2) Rider(推薦)

    主機與應用托管

    主機的創建步驟

    通過lms框架創建一個業務模塊非常方便,只需要通過如下4個步驟,就可以輕松的創建一個lms應用業務模塊。

    1.創建項目

    創建控制臺應用(Console Application)項目,并且引用Silky.Lms.NormHost包。

    dotnet add package Silky.Lms.NormHost --version 1.0.0

    2.應用程序入口與主機構建

    main方法中,通用.net的主機Host構建并注冊lms微服務。在注冊lms微服務時,需要指定lms啟動的依賴模塊。

    一般地,如果開發者不需要額外依賴其他模塊,也無需在應用啟動或停止時執行方法,那么您可以直接指定NormHostModule模塊。

     public class Program
        {
            public static async Task Main(string[] args)
            {
                await CreateHostBuilder(args).Build().RunAsync();
            }
    
            private static IHostBuilder CreateHostBuilder(string[] args)
            {
                return Host.CreateDefaultBuilder(args)
                        .RegisterLmsServices<NormHostModule>()
                    ;
            }
        }

    3.配置文件

    lms框架支持yml或是json格式作為配置文件。通過appsettings.yml對lms框架進行統一配置,通過appsettings.${Environment}.yml對不同環境變量下的配置項進行設置。

    開發者如果直接通過項目的方式啟動應用,那么可以通過Properties/launchSettings.jsonenvironmentVariables.DOTNET_ENVIRONMENT環境變量。如果通過docker-compose的方式啟動應用,那么可以通過.env設置DOTNET_ENVIRONMENT環境變量。

    為保證配置文件有效,開發者需要顯式的將配置文件拷貝到項目生成目錄下。

    4.引用應用服務層和數據訪問層

    一般地,主機項目需要引用該微服務模塊的應用服務層和數據訪問層。只有主機引用應用服務層,主機在啟動時,才會生成服務條目的路由,并且將服務路由注冊到服務注冊中心。

    一個典型的主機項目文件如下所示:

    <Project Sdk="Microsoft.NET.Sdk">
    
        <PropertyGroup>
            <OutputType>Exe</OutputType>
            <TargetFramework>net5.0</TargetFramework>
        </PropertyGroup>
    
        <ItemGroup>
          <PackageReference Include="Silky.Lms.NormHost" Version="$(LmsVersion)" />
        </ItemGroup>
    
        <ItemGroup>
          <None Update="appsettings.yml">
            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
          </None>
          <None Update="appsettings.Production.yml">
            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
          </None>
          <None Update="appsettings.Development.yml">
            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
          </None>
        </ItemGroup>
    
        <ItemGroup>
          <ProjectReference Include="..\Lms.Account.Application\Lms.Account.Application.csproj" />
          <ProjectReference Include="..\Lms.Account.EntityFrameworkCore\Lms.Account.EntityFrameworkCore.csproj" />
        </ItemGroup>
    </Project>
    

    配置

    一般地,一個微服務模塊的主機必須要配置:服務注冊中心、分布式鎖鏈接、分布式緩存地址、集群rpc通信token、數據庫鏈接地址等。

    如果使用docker-compose來啟動和調試應用的話,那么,rpc配置節點下的的host和port可以缺省,因為生成的每個容器的都有自己的地址和端口號。

    如果直接通過項目的方式啟動和調試應用的話,那么,必須要配置rpc節點下的port,每個微服務模塊的主機應用有自己的端口號。

    lms框架的必要配置如下所示:

    rpc:
      host: 0.0.0.0
      rpcPort: 2201
      token: ypjdYOzNd4FwENJiEARMLWwK0v7QUHPW
    registrycenter:
      connectionStrings: 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183;127.0.0.1:2184,127.0.0.1:2185,127.0.0.1:2186 # 使用分號;來區分不同的服務注冊中心
      registryCenterType: Zookeeper
    distributedCache:
      redis:
        isEnabled: true 
        configuration: 127.0.0.1:6379,defaultDatabase=0
    lock:
      lockRedisConnection: 127.0.0.1:6379,defaultDatabase=1
    connectionStrings:
        default: server=127.0.0.1;port=3306;database=account;uid=root;pwd=qwe!P4ss;

    應用接口

    應用接口定義

    一般地,在應用接口層開發者需要安裝Silky.Lms.Rpc包。如果該微服務模塊還涉及到分布式事務,那么還需要安裝Silky.Lms.Transaction.Tcc,當然,您也可以選擇在應用接口層安裝Silky.Lms.Transaction包,在應用服務層安裝Silky.Lms.Transaction.Tcc包。

      開發者只需要在應用接口通過ServiceRouteAttribute特性對應用接口進行直接即可。 Lms約定應用接口應當以IXxxAppService命名,這樣,服務條目生成的路由則會以api/xxx形式生成。當然這并不是強制的。 每個應用接口的方法都對應著一個服務條目,服務條目的Id為: 方法的完全限定名 + 參數名 您可以在應用接口層對方法的緩存、路由、服務治理、分布式事務進行相關配置。該部分內容請參考官方文檔 網關或是其他模塊的微服務項目需要引用服務應用接口項目或是通過nuget的方式安裝服務應用接口生成的包。 [Governance(ProhibitExtranet = true)]可以標識一個方法禁止與集群外部進行通信,通過網關也不會生成swagger文檔。 應用接口方法生成的WebApi支持restful API風格。Lms支持通過方法的約定命名生成對應http方法請求的WebApi。您當然開發者也可以通過HttpMethodAttribute特性對某個方法進行注解。

    一個典型的應用接口的定義

    /// <summary>
        /// 賬號服務
        /// </summary>
        [ServiceRoute]
        public interface IAccountAppService
        {
            /// <summary>
            /// 新增賬號
            /// </summary>
            /// <param name="input">賬號信息</param>
            /// <returns></returns>
            Task<GetAccountOutput> Create(CreateAccountInput input);
    
            /// <summary>
            /// 通過賬號名稱獲取賬號
            /// </summary>
            /// <param name="name">賬號名稱</param>
            /// <returns></returns>
            [GetCachingIntercept("Account:Name:{0}")]
            [HttpGet("{name:string}")]
            Task<GetAccountOutput> GetAccountByName([CacheKey(0)] string name);
    
            /// <summary>
            /// 通過Id獲取賬號信息
            /// </summary>
            /// <param name="id">賬號Id</param>
            /// <returns></returns>
            [GetCachingIntercept("Account:Id:{0}")]
            [HttpGet("{id:long}")]
            Task<GetAccountOutput> GetAccountById([CacheKey(0)] long id);
    
            /// <summary>
            /// 更新賬號信息
            /// </summary>
            /// <param name="input"></param>
            /// <returns></returns>
            [UpdateCachingIntercept( "Account:Id:{0}")]
            Task<GetAccountOutput> Update(UpdateAccountInput input);
    
            /// <summary>
            /// 刪除賬號信息
            /// </summary>
            /// <param name="id">賬號Id</param>
            /// <returns></returns>
            [RemoveCachingIntercept("GetAccountOutput","Account:Id:{0}")]
            [HttpDelete("{id:long}")]
            Task Delete([CacheKey(0)]long id);
    
            /// <summary>
            /// 訂單扣款
            /// </summary>
            /// <param name="input"></param>
            /// <returns></returns>
            [Governance(ProhibitExtranet = true)]
            [RemoveCachingIntercept("GetAccountOutput","Account:Id:{0}")]
            [Transaction]
            Task<long?> DeductBalance(DeductBalanceInput input);
        }

    應用服務--應用接口的實現

      應用服務層只需要引用應用服務接口層以及領域服務層,并實現應用接口相關的方法。 確保該微服務模塊的主機引用了該模塊的應用服務層,這樣主機才能夠托管該應用本身。 應用服務層可以通過引用其他微服務模塊的應用接口層項目(或是安裝nuget包,取決于開發團隊的項目管理方法),與其他微服務模塊進行rpc通信。 應用服務層需要依賴領域服務,通過調用領域服務的相關接口,實現該模塊的核心業務邏輯。 DTO到實體對象或是實體對DTO對象的映射關系可以在該層指定映射關系。

    一個典型的應用服務的實現如下所示:

    public class AccountAppService : IAccountAppService
        {
            private readonly IAccountDomainService _accountDomainService;
    
            public AccountAppService(IAccountDomainService accountDomainService)
            {
                _accountDomainService = accountDomainService;
            }
    
            public async Task<GetAccountOutput> Create(CreateAccountInput input)
            {
                var account = input.MapTo<Domain.Accounts.Account>();
                account = await _accountDomainService.Create(account);
                return account.MapTo<GetAccountOutput>();
            }
    
            public async Task<GetAccountOutput> GetAccountByName(string name)
            {
                var account = await _accountDomainService.GetAccountByName(name);
                return account.MapTo<GetAccountOutput>();
            }
    
            public async Task<GetAccountOutput> GetAccountById(long id)
            {
                var account = await _accountDomainService.GetAccountById(id);
                return account.MapTo<GetAccountOutput>();
            }
    
            public async Task<GetAccountOutput> Update(UpdateAccountInput input)
            {
                var account = await _accountDomainService.Update(input);
                return account.MapTo<GetAccountOutput>();
            }
    
            public Task Delete(long id)
            {
                return _accountDomainService.Delete(id);
            }
    
            [TccTransaction(ConfirmMethod = "DeductBalanceConfirm", CancelMethod = "DeductBalanceCancel")]
            public async Task<long?> DeductBalance(DeductBalanceInput input)
            {
                var account = await _accountDomainService.GetAccountById(input.AccountId);
                if (input.OrderBalance > account.Balance)
                {
                    throw new BusinessException("賬號余額不足");
                }
                return await _accountDomainService.DeductBalance(input, TccMethodType.Try);
            }
    
            public Task DeductBalanceConfirm(DeductBalanceInput input)
            {
                return _accountDomainService.DeductBalance(input, TccMethodType.Confirm);
            }
    
            public Task DeductBalanceCancel(DeductBalanceInput input)
            {
                return _accountDomainService.DeductBalance(input, TccMethodType.Cancel);
            }
        }

    領域層--微服務的核心業務實現

      領域層是該微服務模塊核心業務處理的模塊,一般用于定于聚合根、實體、領域服務、倉儲等業務對象。 領域層引用該微服務模塊的應用接口層,方便使用dto對象。 領域層可以通過引用其他微服務模塊的應用接口層項目(或是安裝nuget包,取決于開發團隊的項目管理方法),與其他微服務模塊進行rpc通信。 領域服務必須要直接或間接繼承ITransientDependency接口,這樣,該領域服務才會被注入到ioc容器。 lms.samples 項目使用TanvirArjel.EFCore.GenericRepository包實現數據的讀寫操作。

    一個典型的領域服務的實現如下所示:

    public class AccountDomainService : IAccountDomainService
        {
            private readonly IRepository _repository;
            private readonly IDistributedCache<GetAccountOutput, string> _accountCache;
    
            public AccountDomainService(IRepository repository,
                IDistributedCache<GetAccountOutput, string> accountCache)
            {
                _repository = repository;
                _accountCache = accountCache;
            }
    
            public async Task<Account> Create(Account account)
            {
                var exsitAccountCount = await _repository.GetCountAsync<Account>(p => p.Name == account.Name);
                if (exsitAccountCount > 0)
                {
                    throw new BusinessException($"已經存在{account.Name}名稱的賬號");
                }
    
                exsitAccountCount = await _repository.GetCountAsync<Account>(p => p.Email == account.Email);
                if (exsitAccountCount > 0)
                {
                    throw new BusinessException($"已經存在{account.Email}Email的賬號");
                }
    
                await _repository.InsertAsync<Account>(account);
                return account;
            }
    
            public async Task<Account> GetAccountByName(string name)
            {
                var accountEntry = _repository.GetQueryable<Account>().FirstOrDefault(p => p.Name == name);
                if (accountEntry == null)
                {
                    throw new BusinessException($"不存在名稱為{name}的賬號");
                }
    
                return accountEntry;
            }
    
            public async Task<Account> GetAccountById(long id)
            {
                var accountEntry = _repository.GetQueryable<Account>().FirstOrDefault(p => p.Id == id);
                if (accountEntry == null)
                {
                    throw new BusinessException($"不存在Id為{id}的賬號");
                }
    
                return accountEntry;
            }
    
            public async Task<Account> Update(UpdateAccountInput input)
            {
                var account = await GetAccountById(input.Id);
                if (!account.Email.Equals(input.Email))
                {
                    var exsitAccountCount = await _repository.GetCountAsync<Account>(p => p.Email == input.Email);
                    if (exsitAccountCount > 0)
                    {
                        throw new BusinessException($"系統中已經存在Email為{input.Email}的賬號");
                    }
                }
    
                if (!account.Name.Equals(input.Name))
                {
                    var exsitAccountCount = await _repository.GetCountAsync<Account>(p => p.Name == input.Name);
                    if (exsitAccountCount > 0)
                    {
                        throw new BusinessException($"系統中已經存在Name為{input.Name}的賬號");
                    }
                }
    
                await _accountCache.RemoveAsync($"Account:Name:{account.Name}");
                account = input.MapTo(account);
                await _repository.UpdateAsync(account);
                return account;
            }
    
            public async Task Delete(long id)
            {
                var account = await GetAccountById(id);
                await _accountCache.RemoveAsync($"Account:Name:{account.Name}");
                await _repository.DeleteAsync(account);
            }
    
            public async Task<long?> DeductBalance(DeductBalanceInput input, TccMethodType tccMethodType)
            {
                var account = await GetAccountById(input.AccountId);
                var trans = await _repository.BeginTransactionAsync();
                BalanceRecord balanceRecord = null;
                switch (tccMethodType)
                {
                    case TccMethodType.Try:
                        account.Balance -= input.OrderBalance;
                        account.LockBalance += input.OrderBalance;
                        balanceRecord = new BalanceRecord()
                        {
                            OrderBalance = input.OrderBalance,
                            OrderId = input.OrderId,
                            PayStatus = PayStatus.NoPay
                        };
                        await _repository.InsertAsync(balanceRecord);
                        RpcContext.GetContext().SetAttachment("balanceRecordId",balanceRecord.Id);
                        break;
                    case TccMethodType.Confirm:
                        account.LockBalance -= input.OrderBalance;
                        var balanceRecordId1 = RpcContext.GetContext().GetAttachment("orderBalanceId")?.To<long>();
                        if (balanceRecordId1.HasValue)
                        {
                            balanceRecord = await _repository.GetByIdAsync<BalanceRecord>(balanceRecordId1.Value);
                            balanceRecord.PayStatus = PayStatus.Payed;
                            await _repository.UpdateAsync(balanceRecord);
                        }
                        break;
                    case TccMethodType.Cancel:
                        account.Balance += input.OrderBalance;
                        account.LockBalance -= input.OrderBalance;
                        var balanceRecordId2 = RpcContext.GetContext().GetAttachment("orderBalanceId")?.To<long>();
                        if (balanceRecordId2.HasValue)
                        {
                            balanceRecord = await _repository.GetByIdAsync<BalanceRecord>(balanceRecordId2.Value);
                            balanceRecord.PayStatus = PayStatus.Cancel;
                            await _repository.UpdateAsync(balanceRecord);
                        }
                        break;
                }
    
               
                await _repository.UpdateAsync(account);
                await trans.CommitAsync();
                await _accountCache.RemoveAsync($"Account:Name:{account.Name}");
                return balanceRecord?.Id;
            }
        }

    數據訪問(EntityFrameworkCore)--通過efcore實現數據讀寫

    lms.samples項目使用orm框架efcore進行數據讀寫。 lms提供了IConfigureService,通過繼承該接口即可使用IServiceCollection的實例指定數據上下文對象和注冊倉庫服務。
    public class EfCoreConfigureService : IConfigureService
        {
            public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
            {
                services.AddDbContext<OrderDbContext>(opt =>
                        opt.UseMySql(configuration.GetConnectionString("Default"),
                            ServerVersion.AutoDetect(configuration.GetConnectionString("Default"))))
                    .AddGenericRepository<OrderDbContext>(ServiceLifetime.Transient)
                    ;
            }
    
            public int Order { get; } = 1;
        }

    3.主機項目需要顯式的引用該項目,只有這樣,該項目的ConfigureServices才會被調用。

    4.數據遷移,請參考

    應用啟動與調試

    獲取源碼

    1.使用git 克隆lms項目源代碼,lms.samples存放在samples目錄下

    # github
    git clone https://github.com/liuhll/lms.git
    
    # gitee
    git clone https://gitee.com/liuhll2/lms.git

    必要的前提

      服務注冊中心zookeeper 緩存服務redis mysql數據庫

    如果您電腦已經安裝了docker以及docker-compose命令,那么您只需要進入samples\docker-compose\infrastr目錄下,打開命令行工作,執行如下命令就可以自動安裝zookeeperredismysql等服務:

    docker-compose -f .\docker-compose.mysql.yml -f .\docker-compose.redis.yml -f .\docker-compose.zookeeper.yml up -d

    數據庫遷移

    需要分別進入到各個微服務模塊下的EntityFrameworkCore項目(例如:),執行如下命令:

    dotnet ef database update

    例如: 需要遷移account模塊的數據庫如下所示:

    order模塊和stock模塊與account模塊一致,在服務運行前都需要通過數據庫遷移命令生成相關數據庫。

      數據庫遷移指定數據庫連接地址默認指定的是appsettings.Development.yml中配置的,您可以通過修改該配置文件中的connectionStrings.default配置項來指定自己的數據庫服務地址。 如果沒有dotnet ef命令,則需要通過dotnet tool install --global dotnet-ef安裝ef工具,請[參考] (https://docs.microsoft.com/zh-cn/ef/core/get-started/overview/install)

    以項目的方式啟動和調試

    使用visual studio作為開發工具

    進入到samples目錄下,使用visual studio打開lms.samples.sln解決方案,將項目設置為多啟動項目,并將網關和各個模塊的微服務主機設置為啟動項目,如下圖:

    設置完成后直接啟動即可。

    使用rider作為開發工具進入到samples目錄下,使用rider打開lms.samples.sln解決方案,打開各個微服務模塊下的Properties/launchSettings.json,點擊圖中綠色的箭頭即可啟動項目。

    啟動網關項目后,可以看到應用接口的服務條目生成的swagger api文檔 http://localhost:5000/swagger

    默認的環境變量為: Development,如果需要修改環境變量的話,可以通過Properties/launchSettings.json下的environmentVariables節點修改相關環境變量,請參考在 ASP.NET Core 中使用多個環境

    數據庫連接、服務注冊中心地址、以及redis緩存地址和分布式鎖連接等配置項可以通過修改appsettings.Development.yml配置項自定義指定。

    以docker-compose的方式啟動和調試

    進入到samples目錄下,使用visual studio打開lms.samples.dockercompose.sln解決方案,將docker-compose設置為啟動項目,即可啟動和調式。

    應用啟動成功后,打開: http://127.0.0.1/swagger,即可看到swagger api文檔

    以docker-compose的方式啟動和調試,則指定的環境變量為:ContainerDev

    數據庫連接、服務注冊中心地址、以及redis緩存地址和分布式鎖連接等配置項可以通過修改appsettings.ContainerDev.yml配置項自定義指定,配置的服務連接地址不允許為: 127.0.0.1或是localhost

    測試和調式

    服務啟動成功后,您可以通過寫入/api/account-post接口和/api/product-post新增賬號和產品,然后通過/api/order-post接口進行測試和調式。

    開源地址

    github: https://github.com/liuhll/lms

    gitee: https://gitee.com/liuhll2/lms

    到此這篇關于通過lms.samples熟悉lms微服務框架的使用的文章就介紹到這了,更多相關lms微服務框架內容請搜索真格學網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持真格學網!

    您可能感興趣的文章:詳解Java 微服務架構詳解用Spring Boot Admin來監控我們的微服務Intellij IDEA中啟動多個微服務(開啟Run Dashboard管理)Idea springboot如何實現批量啟動微服務詳解IDEA啟動多個微服務的配置方法新手學習微服務SpringCloud項目架構搭建方法

  3. 本文相關:
  4. antd通過 filterdropdown 自定義按某天時間搜索功能
  5. 最適合人工智能開發的5種編程語言 附人工智能入門書籍
  6. 滑動驗證碼的設計與理解
  7. 獻給寫作者的 markdown 新手指南
  8. eventstore文件存儲設計詳解
  9. 網址(url)支持的最大長度是多少?最大支持多少個字符?
  10. 使用百度云加速后網站打開速度慢、廣告不顯示的解決方法
  11. scala項目構建工具sbt和intellij idea環境配置詳解
  12. oauth 2.0 概念及授權流程梳理
  13. qqwry.dat的數據結構圖文解釋
  14. 如何使用Spring Boot/Spring Cloud 實現微服務應用
  15. LMS是什么?
  16. 如何使用Spring Boot/Spring Cloud 實現微服務應用
  17. 手機增值業務:Brew / LMS / UTK是什么意思
  18. 網站首頁網頁制作腳本下載服務器操作系統網站運營平面設計媒體動畫電腦基礎硬件教程網絡安全javascriptasp.netphp編程ajax相關正則表達式asp編程jsp編程編程10000問css/htmlflex腳本加解密web2.0xml/rss網頁編輯器相關技巧安全相關網頁播放器其它綜合dart首頁詳解java 微服務架構詳解用spring boot admin來監控我們的微服務intellij idea中啟動多個微服務(開啟run dashboard管理)idea springboot如何實現批量啟動微服務詳解idea啟動多個微服務的配置方法新手學習微服務springcloud項目架構搭建方法antd通過 filterdropdown 自定義按某天時間搜索功能最適合人工智能開發的5種編程語言 附人工智能入門書籍滑動驗證碼的設計與理解獻給寫作者的 markdown 新手指南eventstore文件存儲設計詳解網址(url)支持的最大長度是多少?最大支持多少個字符?使用百度云加速后網站打開速度慢、廣告不顯示的解決方法scala項目構建工具sbt和intellij idea環境配置詳解oauth 2.0 概念及授權流程梳理qqwry.dat的數據結構圖文解釋最新idea2021注冊碼永久激活(激活intellij idea2020永久破解,親測最新idea2020激活碼超詳細教程(設idea激活碼最新獲取方法(idea20關于最新idea2020.2.1,2.2,3以上刪除svn三種方法delsvn(windows+最新intellij idea 2020.2永久激intellij idea 2020最新注冊碼(親c/s和b/s兩種架構的概念、區別和分享4個最受歡迎的大數據可視化工具比特幣上的數獨游戲合約的實現代碼微信公眾平臺開發——群發信息關注程序員健康:程序最需要注意的幾件事微信支付--簽名錯誤問題的解決方法分別使用vue和android實現長按券碼復制功lambda 表達式導致 arthas 無法 redefine詳細解析webpack是怎么運行的最新idea2020激活碼超詳細教程(設置插件倉微信小程序(應用號)組件詳細介紹
    免責聲明 - 關于我們 - 聯系我們 - 廣告聯系 - 友情鏈接 - 幫助中心 - 頻道導航
    Copyright © 2017 www.yu113.com All Rights Reserved
    战天txt全集下载