分佈式應用框架 Dapr

微服務架構已成為構建雲原生應用程序的標準,微服務架構提供了令人信服的好處,包括可伸縮性,鬆散的服務耦合和獨立部署,但是這種方法的成本很高,需要了解和熟練掌握分佈式系統。為了使用所有開發人員能夠使用任何語言和任何框架輕鬆地構建便攜式微服務應用程序,無論是開發新項目還是遷移現有代碼

Dapr 介紹

Github: 

Dapr是一種可移植的,事件驅動的,無服務器運行時,用於構建跨雲和邊緣的分佈式應用程序。

Distributed Application Runtime. An event-driven, portable runtime for building microservices on cloud and edge.

其中提到了多語言和多開發者框架,我認為這是他選擇的通過通信共享信息,即 HTTPGRPC 支持多語言等特性。微軟想通過這個設定一個構建微服務應用的規則。從根本上確立你開發的每一個應用的獨立性。賦能每個開發者,為了使Dapr對於不同的語言更加方便,它還包括針對Go,Java,JavaScript,.NET和Python的語言特定的SDK。這些SDK通過類型化的語言API(而不是調用http / gRPC API)公開了Dapr構建塊中的功能,例如保存狀態,發布事件或創建actor。這使開發人員可以使用他們選擇的語言編寫無狀態和有狀態功能以及參与者的組合。並且由於這些SDK共享Dapr運行時,您甚至可以獲得跨語言的actor和功能支持!

Dapr還可以與任何開發人員框架集成。例如,在Dapr .NET SDK中, Core集成,該集成帶來了可響應其他服務的發布/訂閱事件的狀態路由控制器, Core成為構建微服務Web應用程序的更好框架。

 

不過需要注意的是Dapr目前正處於Alpha階段, 今天剛發布了0.2版本。在v1.0穩定版本發布之前,建議不要用於生產環境。

 

下面進行一個 QuickStart

環境

  1. Install (微服務已經離不開容器化了)
  2. Install
  3. Install

在Windows 上通過Powershell 安裝:

powershell -Command "iwr -useb https://raw.githubusercontent.com/dapr/cli/master/install/install.ps1 | iex"
然後把 c:\dapr 添加到環境變量 PATH
運行dapr命令,檢查輸出是否正常

C:\workshop\Github\dotnet-sdk>dapr –help

         __
     ____/ /___ _____  _____
    / __  / __ ‘/ __ \/ ___/
   / /_/ / /_/ / /_/ / /   
   \__,_/\__,_/ .___/_/
               /_/

======================================================
A serverless runtime for hyperscale, distributed systems

Usage:
   dapr [command]

Available Commands:
   help        Help about any command
   init        Setup dapr in Kubernetes or Standalone modes
   list        List all dapr instances
   publish     publish an event to multiple consumers
   run         Launches dapr and your app side by side
   send        invoke a dapr app with an optional payload
   stop        Stops a running dapr instance and its associated app
   uninstall   removes a dapr installation

Flags:
   -h, –help      help for dapr
       –version   version for dapr

Use “dapr [command] –help” for more information about a command.

 

執行初始化(會啟動 docker 容器)
dapr init 
Making the jump to hyperspace... 
Downloading binaries and setting up components 
Success! Dapr is up and running
下載.NET SDk代碼
 ,裏面有.NET Core的多個示例代碼:
示例 描述
Demonstrates creating virtual actors that encapsulate code and state. Also see docs in this repo for a tutorial.
Demonstrates ASP.NET Core integration with Dapr by create Controllers and Routes.

The gRPC client sample shows how to make Dapr calls to publish events, save state, get state and delete state using a gRPC client.


我們一起來看下ASP.NET Core的Demo;

例子中主 我們使用 Dapr 的交互。Dapr通過 Runtime

  • 提供 Dapr API 給多語言調用。
  • 提供 狀態管理 By state stores

 

/// <summary>
  /// Sample showing Dapr integration with controller.
  /// </summary>
  [ApiController]
  public class SampleController : ControllerBase
  {
      /// <summary>
      /// Gets the account information as specified by the id.
      /// </summary>
      /// <param name=”account”>Account information for the id from Dapr state store.</param>
      /// <returns>Account information.</returns>
      [HttpGet(“{account}”)]
      public ActionResult<Account> Get(StateEntry<Account> account)
      {
          if (account.Value is null)
          {
              return this.NotFound();
          }

         return account.Value;
      }

 

     /// <summary>
      /// Method for depositing to account as psecified in transaction.
      /// </summary>
      /// <param name=”transaction”>Transaction info.</param>
      /// <param name=”stateClient”>State client to interact with dapr runtime.</param>
      /// <returns>A <see cref=”Task{TResult}”/> representing the result of the asynchronous operation.</returns>
      [Topic(“deposit”)]
      [HttpPost(“deposit”)]
      public async Task<ActionResult<Account>> Deposit(Transaction transaction, [FromServices] StateClient stateClient)
      {
          var state = await stateClient.GetStateEntryAsync<Account>(transaction.Id);
          state.Value ??= new Account() { Id = transaction.Id, };
          state.Value.Balance += transaction.Amount;
          await state.SaveAsync();
          return state.Value;
      }

 

     /// <summary>
      /// Method for withdrawing from account as specified in transaction.
      /// </summary>
      /// <param name=”transaction”>Transaction info.</param>
      /// <param name=”stateClient”>State client to interact with dapr runtime.</param>
      /// <returns>A <see cref=”Task{TResult}”/> representing the result of the asynchronous operation.</returns>
      [Topic(“withdraw”)]
      [HttpPost(“withdraw”)]
      public async Task<ActionResult<Account>> Withdraw(Transaction transaction, [FromServices] StateClient stateClient)
      {
          var state = await stateClient.GetStateEntryAsync<Account>(transaction.Id);

         if (state.Value == null)
          {
              return this.NotFound();
          }

         state.Value.Balance -= transaction.Amount;
          await state.SaveAsync();
          return state.Value;
      }
  }

 

這裏重點是狀態存儲,即將 state 通過 StateClient 存儲在 Dapr 中,我們通過狀態轉移在 Dapr 里實現了 stateless。
 
Dapr 運行.NET 應用程序

演示Dapr的服務調用,在終端中切換到項目目錄,然後使用dapr啟動應用

 

C:\workshop\Github\dotnet-sdk\samples\AspNetCore\ControllerSample>dapr run –app-id routing –app-port 5000 dotnet run   
Starting Dapr with id routing. HTTP Port: 61102. gRPC Port: 61103
You’re up and running! Both Dapr and your app logs will appear here.

 

注意: 以上dapr run命令,通過app-id指定了應用的ID,通過app-port指定了應用的端口(webapi默認使用5000作為http端口),後跟dotnet run命名啟動當前項目。可參考Dapr文檔

 

後台運行的 CLI 命令,這裡是前台打印的日誌, 注意到 .NET App 在指定的 5000 端口運行,同時還有狀態存儲的 redis6379 端口運行

 

== DAPR == time=”2019-11-16T18:33:22+08:00″ level=info msg=”starting Dapr Runtime — version 0.2.0 — commit c75b11
1-dirty”
     == DAPR == time=”2019-11-16T18:33:22+08:00″ level=info msg=”log level set to: info”
== DAPR == time=”2019-11-16T18:33:22+08:00″ level=info msg=”standalone mode configured”
== DAPR == time=”2019-11-16T18:33:22+08:00″ level=info msg=”dapr id: routing”
== DAPR == time=”2019-11-16T18:33:22+08:00″ level=info msg=”loaded component messagebus (pubsub.redis)”       
     == DAPR == time=”2019-11-16T18:33:22+08:00″ level=info msg=”loaded component statestore (state.redis)”
     == DAPR == time=”2019-11-16T18:33:22+08:00″ level=info msg=”application protocol: http. waiting on port 5000″
     == APP == info: Microsoft.Hosting.Lifetime[0]
     == APP ==       Now listening on:
== APP == info: Microsoft.Hosting.Lifetime[0]
== APP ==       Application started. Press Ctrl+C to shut down.
     == APP == info: Microsoft.Hosting.Lifetime[0]
== APP ==       Hosting environment: Development
== APP == info: Microsoft.Hosting.Lifetime[0]
== APP ==       Content root path: C:\workshop\Github\dotnet-sdk\samples\AspNetCore\ControllerSample
     == DAPR == time=”2019-11-16T18:33:31+08:00″ level=info msg=”application discovered on port 5000″
     == DAPR == 2019-11-16 18:33:32.029764 I | redis: connecting to localhost:6379
     == DAPR == 2019-11-16 18:33:32.036316 I | redis: connected to localhost:6379 (localAddr: [::1]:61164, remAddr:
[::1]:6379)
     == DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”actor runtime started. actor idle timeout: 1h0m0s.
actor scan interval: 30s”
     == DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”actors: starting connection attempt to placement se
rvice at localhost:6050″
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”http server is running on port 61102″
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”gRPC server is running on port 61103″
     == DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”local service entry announced”
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”dapr initialized. Status: Running. Init Elapsed 917
6.5164ms”
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”actors: established connection to placement service
  at localhost:6050″
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”actors: placement order received: lock”
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”actors: placement order received: update”
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”actors: placement tables updated”
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”actors: placement order received: unlock”
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[100]
== APP ==       Start processing HTTP request GET
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[100]
== APP ==       Sending HTTP request GET
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[101]
== APP ==       Received HTTP response after 2228.2998000000002ms – OK    
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[101]      
== APP ==       End processing HTTP request after 2257.3405000000002ms – OK
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[100]
== APP ==       Start processing HTTP request POST
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[100]
== APP ==       Sending HTTP request POST
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[101]
     == APP ==       Received HTTP response after 67.46000000000001ms – Created
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[101]
== APP ==       End processing HTTP request after 68.0343ms – Created
     == APP == info: System.Net.Http.HttpClient.state.LogicalHandler[100]
     == APP ==       Start processing HTTP request GET
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[100]
== APP ==       Sending HTTP request GET
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[101]
== APP ==       Received HTTP response after 5.8247ms – OK
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[101]
== APP ==       End processing HTTP request after 6.268400000000001ms – OK
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[100]
== APP ==       Start processing HTTP request POST
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[100]
== APP ==       Sending HTTP request POST
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[101]
== APP ==       Received HTTP response after 4.5181000000000004ms – Created
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[101]
== APP ==       End processing HTTP request after 4.6208ms – Created
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[100]
== APP ==       Start processing HTTP request GET
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[100]
== APP ==       Sending HTTP request GET
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[101]
== APP ==       Received HTTP response after 20.2967ms – OK
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[101]
== APP ==       End processing HTTP request after 20.691100000000002ms – OK

 

 

為了同時實現可移植性和與現有代碼的輕鬆集成,Dapr通過http或gRPC提供了標準API。Dapr端口可從Dapr啟動日誌中獲取,如以下日誌表示Dapr公開的HTTP端口為61102(通過Dapr也可使用gRPC方式進行服務調用)

 

== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”http server is running on port 61102″
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”gRPC server is running on port 61103″

 

我們可通過以下地址來調用示例方法,根據Dapr服務調用API規範,其代理調用規則為:

POST/GET/PUT/DELETE http://localhost:<Dapr端口>/v1.0/invoke/<id>/method/<method-name>

直接調用:GET http://localhost:5000/17

通過Dapr服務調用: GET 
 
注意: Dapr的服務調用是有dapr sidecar來實現的,在被調用的服務中無需注入任何與dapr相關的代碼。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

C# web項目中sql數據庫轉sqlite數據庫

最近做了一個小網站,用到了一個使用sql server 2005的.net cms系統,但是現在我所買虛擬主機的服務商,不給虛擬主機提供sql server服務了,那就轉數據庫吧,轉啥好呢,思來想去,access?剛入行時候用了很久,簡單夠用,不過實在提不起興趣了,sqlite?嗯…還沒用過,只是簡單看過介紹,聽說性能還不錯,那就試試吧,等等,不知道虛擬主機支持不支持?!百度!然而一大堆沒啥用處的提問和回答,也許可能大概是我搜索的關鍵詞不對,懶得管了,年齡大了,沒有那個勁兒了,實踐出真理,先上手試試驗證一下吧,說干就干

先查查怎麼在本地創建和管理數據庫,然後選擇使用了SQLiteStudio這個軟件,然後新建個test數據庫->隨便插條數據->然後在vs創建個test web項目->數據庫文件扔進去->新建個頁面,查下數據显示到頁面->本地運行,ok!,發布,->上傳虛擬主機,懷着稍稍激動的心情,打開網址,ok!完全可以!

這麼簡單嗎?nonono,運行之前還是有點小小的障礙的:

首先項目需要用到System.Data.SQLite.dll,到sqlite官網下一個吧,

然後添加引用,引用之後,還需要連接字符串,搜索(ss)! 嗯,和access很像,附個例子:

<add name="ConnectionString" connectionString="Data Source=|DataDirectory|testdb.db;Version=3;Pooling=true;FailIfMissing=false" providerName="System.Data.SQLite" />

這樣就可以運行了!具體的參數還有不少,ss一下,根據需求自己設置。

然後開始改cms吧,首先是轉數據庫,一看錶,我尼瑪,好多表,自己一個一個建嗎?想想都想打消折騰的念頭了,有沒有什麼工具可以藉助呢?ss一下,還真有!

SQL server To SQLite DB Convert 這是一位叫liron.levi老外寫的,項目地址:https://www.codeproject.com/Articles/26932/Convert-SQL-Server-DB-to-SQLite-DB

簡直神器,界面如下

 作者還給出了源代碼,在源代碼中可以看到數據類型對應的轉換

        /// <summary>
        /// Used when creating the CREATE TABLE DDL. Creates a single row
        /// for the specified column.
        /// </summary>
        /// <param name="col">The column schema</param>
        /// <returns>A single column line to be inserted into the general CREATE TABLE DDL statement</returns>
        private static string BuildColumnStatement(ColumnSchema col, TableSchema ts, ref bool pkey)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("\t[" + col.ColumnName + "]\t");

            // Special treatment for IDENTITY columns
            if (col.IsIdentity)
            {
                if (ts.PrimaryKey.Count == 1 && (col.ColumnType == "tinyint" || col.ColumnType == "int" || col.ColumnType == "smallint" ||
                    col.ColumnType == "bigint" || col.ColumnType == "integer"))
                {
                    sb.Append("integer PRIMARY KEY AUTOINCREMENT");
                    pkey = true;
                }
                else
                    sb.Append("integer");
            }
            else
            {
                if (col.ColumnType == "int")
                    sb.Append("integer");
                else
                {
                    sb.Append(col.ColumnType);
                }
                if (col.Length > 0)
                    sb.Append("(" + col.Length + ")");
            }
            if (!col.IsNullable)
                sb.Append(" NOT NULL");

            if (col.IsCaseSensitivite.HasValue && !col.IsCaseSensitivite.Value)
                sb.Append(" COLLATE NOCASE");

            string defval = StripParens(col.DefaultValue);
            defval = DiscardNational(defval);
            _log.Debug("DEFAULT VALUE BEFORE [" + col.DefaultValue + "] AFTER [" + defval + "]");
            if (defval != string.Empty && defval.ToUpper().Contains("GETDATE"))
            {
                _log.Debug("converted SQL Server GETDATE() to CURRENT_TIMESTAMP for column [" + col.ColumnName + "]");
                sb.Append(" DEFAULT (CURRENT_TIMESTAMP)");
            }
            else if (defval != string.Empty && IsValidDefaultValue(defval))
                sb.Append(" DEFAULT " + defval);

            return sb.ToString();
        }

View Code

然後就用這個工具把cms的sql server數據庫轉換成sqlite數據庫文件,直接扔進cms項目中。

這樣就行了嗎?當然還有工作要做,原來項目中操作sql server的類庫也要換成sqlite的,sql語句也要做相應的轉換,下面就挑一些重點的說一說:

先說數據類型吧:

由於在工具的源代碼中,主鍵不管什麼類型的int轉成sqlite都用的integer,有需求的可以自己改代碼,不過最好要熟悉sqlite的數據類型。下面列出我在項目中所遇到的主要類型轉換

sql server c# sqlite c#
int new SqlParameter(“@id”, SqlDbType.Int,4)  integer new SQLiteParameter(“@id”, DbType.Int64,8)
nvarchar new SqlParameter(“@id”, SqlDbType.NVarChar,50) nvarchar new SQLiteParameter(“@id”, DbType.String,50)
decimal new SqlParameter(“@id”, SqlDbType.Decimal) numeric new SQLiteParameter(“@id”, DbType.Decimal)
tinyint new SqlParameter(“@id”, SqlDbType.TinyInt,1) smallint new SQLiteParameter(“@id”, DbType.Int16,1)
ntext new SqlParameter(“@id”, SqlDbType.NText) text new SQLiteParameter(“@id”, DbType.String)
datetime new SqlParameter(“@id”, SqlDbType.DateTime) datetime new SQLiteParameter(“@id”, DbType.DateTime)
Image new SqlParameter(“@fs”, SqlDbType.Image) Binary new SQLiteParameter(“@fs”, DbType.Binary)

 

代碼數據類型轉換之後,就剩調試修改sql語句了,下面列出我在項目中遇到的比較主要的轉換

1、top語句,在sqlite中要使用limit,和mysql差不多,例如

  sql:select top 10 * from table_1

  sqlite:select * from table_1 limit 10

2、在插入一條數據后,要獲取最新的id

  sql:select @@IDENTITY;

  sqlite:select LAST_INSERT_ROWID();

3、計算時間差

  sql:where datediff(d,field_time,getdate())>=0

  sqlite:where JULIANDAY(datetime('now','localtime'))-JULIANDAY(field_time)>=0

4、分頁

  sql:2005以上一般使用ROW_NUMBER()  

    select * from ( select row_number() over(order by id) as rows,* ) as t where rows between PageIndex*PageSize and PageIndex*PageSize+PageSize

  sqlite:用 limit  offset

    select * from table_1 limit PageSize offset (PageIndex- 1) * PageSize 

5、最讓人抓狂的是修改表部分的差異

  這一部分單獨再寫一篇吧

時間不早了,來自老年人的嘆息…..

第二天了,附上修改表的文章鏈接:

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

農業和環保引發對立 德國兩派人馬街頭拉鋸

摘錄自2020年01月19日中央通訊社德國報導

德國農民與環保人士對壘加深,農民不滿新環保措施加重成本負擔,環保人士則抗議農業推升氣候變遷危機,兩邊的對立讓政治人物左右為難,也使得德國年度最大農業展蒙上陰影。

柏林一年一度「綠色週」貿易博覽會昨天(18日)開幕,德國農民選在當天展開一場名為「土地創造聯繫」(Land schafft Verbindung, LSV)活動,將數千輛牽引機開上全國各地城市街頭,藉此抗議政府施行更嚴格的農業法規。

德國最近推行的種種農業改革,包括在食品引進「動物福利標籤」、限制農藥使用以保護昆蟲等,都讓農民大喊吃不消,說政府新環保措施引發的成本都是農民在扛。他們也擔心新法規將導致收入大減,走上街頭據理力爭,高舉標語吶喊「別忘了是農民餵飽你們」、「沒有農場、沒有食物、沒有未來」。

農民昨天集體抗議後,今天輪到環保人士走上街頭。主辦單位說有約2萬7000名環保人士齊聚柏林地標布蘭登堡大門(Brandenburg Gate)附近,抗議他們口中的農業損害效應,並要求改變德國現行的務農方法。這場集會由環保團體「我們受夠了」(We are fed up)發起。氣候環保人士指稱近期的農業改革做得還不夠,現場另一邊站著抗議的農民,國會議員則被夾在中間。

農民和環保倡議者之間的鴻溝近幾年來有加深趨勢,這與學生帶領的「週五為未來而戰」(Fridays for Future)罷課抗暖化行動有關。環保人士今天的抗議行動自2011年以來年年登場,這個由綠色團體發起的活動要求全面性改革,以支持小農和對環境友善的農業。他們在網路上說:「農業加深暖化危機和社會衝突,我們必須加以阻止。」

農民組成的LSV抗議團體則自稱是相對於「我們受夠了」團體的反制力量,有憤怒的養蜂人將蜂蜜傾倒在政治人物大門前,也有生氣的農民走上街頭阻撓交通。農業部長克勒克納(Julia Kloeckner)16日呼籲對立的兩方要有妥協的胸襟。

綠黨共同領袖哈柏克(Robert Habeck)警告環保人士勿把氣候暖化歸咎在農民頭上,但也堅持說,拒絕改革並非社會前進之道,「減少對氣候和動物的保障,絕對不會是正確解答」。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

泰國曼谷空品差 民眾抱怨政府無作為

摘錄自2020年1月21日公視報導

泰國曼谷近年來每到冬天,空氣品質就拉警報。曼谷的天空再度為霧霾籠罩,機車騎士紛紛戴上口罩,家庭主婦出門也不例外。

根據曼谷官方監測的數據,曼谷部分地區中午時段PM2.5濃度,飆升到每立方公尺 95微克,幾乎是安全標準的兩倍。官員表示這主要是受到逆溫現象的影響,也就是暖空氣將冷空氣困在地面附近,導致汽機車廢氣滯留且散不出去。泰國政府雖然已經開始管控汽車廢氣,必要時還派出無人機灑水,但霧鎖曼谷的情況依然每年上演。最新民調顯示,曼谷有八成一的民眾,認為政府改善空污沒有效率。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

南投搬家前需注意的眉眉角角,別等搬了再說!

亞銀:借鏡碳信用建構「藍色信用」 以市場機制籌措海洋基金

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

日本風災損害輻射檢查設備 營養午餐難保食安

文:宋瑞文(媽媽監督核電廠聯盟特約撰述)

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

南投搬家前需注意的眉眉角角,別等搬了再說!

每天3分鐘操作系統修鍊秘籍(14):IO操作和DMA、RDMA

I/O操作和DMA、RDMA

用戶進程想要執行IO操作時(例如想要讀磁盤數據、向磁盤寫數據、讀鍵盤的輸入等等),由於用戶進程工作在用戶模式下,它沒有執行這些操作的權限,只能通過發起對應的系統調用請求操作系統幫忙完成這些操作。這裏因為系統調用產生中斷將陷入到內核,進行一次上下文切換操作。

內核進程幫忙執行IO操作時,由於IO操作相比於CPU來說是極慢的操作,CPU不應該等待在這個過程中,而是切換到其它進程上去執行其它任務。這裏再次涉及到一次上下文切換:從內核態回到用戶態的其它進程。

DMA要求硬件的支持,需要在硬件中集成一個小型的“CPU”,比如現在的机械硬盤、固態硬盤、網卡等硬件都帶有DMA功能,這樣操作系統要執行IO操作時,直接將相關指令發送給這些DMA硬件,DMA處理器負責IO操作,而操作系統這時可以放棄CPU,讓CPU去執行其它進程。例如對於讀磁盤文件時,操作系統將相關指令以及數據應寫在哪個內存地址發送給DMA硬件后,由DMA硬件去讀寫數據到指定內存地址,當IO操作完成后,DMA硬件通過總線發送一個硬件中斷給CPU,於是陷入到內核態(這裏涉及了一次上下文切換),內核就知道了IO已經完成,於是將Kernel Buffer數據拷貝到用戶進程的IO Buffer,並準備調度用戶進程(再次上下文切換)。

假如不使用DMA硬件的話,那麼IO操作過程中,操作系統將多次參与,負責將硬件數據讀入或讀出內存,操作系統參与意味着要陷入到內核態,並且獲取CPU控制權,這也意味着要進行大量的上下文切換以及佔用大量CPU資源。

而使用DMA后,只有4次必要的上下文切換,且IO操作的過程中完全不需要消耗CPU資源。

除了DMA,還有更高級的RDMA(Remote Direct Memory Access)機制,它需要操作系統和硬件的支持,還需要編寫RDMA方式的代碼。

前面介紹緩衝空間時提到過,一般情況下,每個用戶進程要讀、寫數據,都會經過兩個必要的緩衝層:內核空間的Kernel Buffer、用戶空間的IO Buffer。例如讀文件數據時,先將數據拷貝到內核的緩衝空間(page cache),然後陷入內核,內核將該緩衝空間數據拷貝到用戶空間的緩衝空間(IO Buffer),當調度到用戶進程時,用戶進程從自己的緩衝空間讀取數據。

DMA機制並沒有繞過這兩個緩衝層,但使用RDMA機制,程序可以直接繞過Kernel Buffer,內核發現是RDMA操作后,直接告訴RDMA硬件將讀取的數據(寫操作也一樣)寫入到用戶空間的IO Buffer,而不需要先拷貝到Kernel Buffer,再拷貝到IO Buffer。雖然RDMA機制相比DMA不會減少上下文切換次數,但是它減少了內存數據拷貝的過程,相當於是使用了O_DIRECT標記的直接IO技術。

DMA和RDMA兩種技術對比如圖:RDMA一般實現在網卡上,但出於方便理解,下圖直接使用磁盤來描述

像這種繞過內核功能的技術,通常稱為內核旁路(Kernel Bypass),RDMA技術內核旁路的是一種,還有像TOE也是內核旁路的一種。

雖然RDMA比較優秀,但是它需要硬件、操作系統和代碼的同時支持,對編程而言是一個比較大的衝擊,所以目前使用的非常少。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

vue學習筆記(五)條件渲染和列表渲染

前言

在眾多的編程語言中,我們的基礎語法總是少不了一些專業語法,比如像定義變量,條件語句,for循環,數組,函數等等,vue.js這個優秀的前端框架中也有同樣的語法,我們換一個名詞,將條件語句改成專業詞彙叫做條件渲染,循環語句改成專業詞彙叫做列表渲染,這樣比較舒服一點。

本章目標

  • 學會條件渲染的使用

  • 學會可復用的key的使用

  • 學會列表渲染的使用

條件渲染

1.v-if的使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<div id="app01">
    <span v-if="type==='A'">成績為A</span>
</div>
<script src="../js/vue.js"></script>
<script>
    let vm=new Vue({
        el:'#app01',
        data:{
            type:'A'
        },
        methods:{
            
        },
        watch:{
            
        },
        computed:{
            
        }
    })
</script>
</body>
</html>

結果:成績為A

v-if判斷條件是否相等,就像if一樣,如果相等,那麼值就會true,與之對應的還有v-else,v-else-if

2.v-else的使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<div id="app01">
    <span v-if="type==='A'">成績為A</span>
    <span v-else>成績為B</span>
</div>
<script src="../js/vue.js"></script>
<script>
    let vm=new Vue({
        el:'#app01',    
        data:{
            type:'B'
        },
        methods:{
            
        },
        watch:{
            
        },
        computed:{
            
        }
    })
</script>
</body>
</html>

結果:成績為B

小練習

我們做一個小練習,鞏固一下v-if和v-else的使用,需求如下:點擊一個按鈕時,按鈕上的文字變為显示,再次點擊時按鈕上的文字變為隱藏,當按鈕上的文字显示隱藏時,显示紅色,按鈕上的文字變為显示時显示藍色

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style type="text/css">
            .box{
                width: 100px;
                height: 100px;
            }
            .red{
                background: red;
            }
            .blue{
                background: blue;
            }
        </style>
    </head>
    <body>
        <div id="app">
            <button @click="handleClick">{{text}}</button>
            <div v-if="show" class="box red"></div>
            <div v-else class="blue box"></div>
        </div>
        <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            let vm=new Vue({
                el:'#app',
                data:{
                    show:true,
                    text:'隱藏'
                },
                methods:{
                    handleClick(){
                        this.show=!this.show;
                        this.text=this.show?'隱藏':'显示'
                    }
                }
            })
            
        </script>
    </body>
</html>

結果

 

3.v-else-if的使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<div id="app01">
    <input type="text" v-model="type"/>
    <div v-if="type==='A'">成績為A</div>
    <div v-else-if="type==='B'">成績為B</div>
    <div v-else-if="type==='C'">成績為C</div>
    <div v-else>不及格</div>
</div>
<script src="../js/vue.js"></script>
<script>
    let vm=new Vue({
        el:'#app01',    
        data:{
            type:''
        },
        methods:{
            
        },
        watch:{
            
        },
        computed:{
            
        }
    })
</script>
</body>
</html>

結果:

4.v-show

說起這個v-show,其實和v-if有與曲同工的妙處,但是又有不同的地方,我們來看下示例你就秒懂了

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style type="text/css">
            .box{
                width: 100px;
                height: 100px;
            }
            .red{
                background: red;
            }
            .blue{
                background: blue;
            }
        </style>
    </head>
    <body>
        <div id="app">
            <div v-show="show" class="box red"></div>
            <button @click="handleClick()">{{text}}</button>
        </div>
        <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            let vm=new Vue({
                el:'#app',
                data:{
                    show:true,
                    text:'隱藏',
                },
                methods:{
                    handleClick(){
                        this.show=!this.show;
                        this.text=this.show?'隱藏':'显示'
                    }
                },
                computed:{
                    
                }
            })
        </script>
    </body>
</html>

當按鈕變為显示的時候,背景顏色消失,這裏就不截圖了,有興趣的小夥伴可以自己去嘗試,既然v-if可以幫我們實現元素的显示和隱藏,那我們還需要v-show干什麼呢?不妨看下接下來的實例。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style type="text/css">
            .box{
                width: 100px;
                height: 100px;
            }
            .red{
                background: red;
            }
            .blue{
                background: blue;
            }
        </style>
    </head>
    <body>
        <div id="app">
            <div v-show="show" class="box red"></div>
            <div class="box blue" v-if="show"></div>
            <button @click="handleClick()">{{text}}</button>
        </div>
        <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            let vm=new Vue({
                el:'#app',
                data:{
                    show:true,
                    text:'隱藏',
                },
                methods:{
                    handleClick(){
                        this.show=!this.show;
                        this.text=this.show?'隱藏':'显示'
                    }
                },
                computed:{
                    
                }
            })
        </script>
    </body>
</html>

 當我們點擊按鈕的時候

 

現在結果已經出來了,使用v-show的dom元素,dom元素只是簡單的切換display屬性,而v-if會將dom元素移除,當我們再次點擊時,v-if又會重新渲染元素,可想而知如果頻繁的切換的話,那麼有多麼的耗費性能,因此我總結了如下幾點

  • 頻繁的切換显示/隱藏要使用v-show

  • 只判斷一次時,使用v-if

5.減少dom的生成

我們都知道js操作dom元素是非常消耗性能的,但是我們需要盡量的避免這個問題,vue中為我們提供了一個template標籤,這個標籤叫做模板(至於什麼叫做模板,後期的博客會講到),我們先看一個示例

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style type="text/css">
            .box{
                width: 100px;
                height: 100px;
            }
            .red{
                background: red;
            }
            .blue{
                background: blue;
            }
        </style>
    </head>
    <body>
        <div id="app">
            <div v-if="show">
                <div class="box red"></div>
                <div class="box blue"></div>
            </div>
        </div>
        <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            let vm=new Vue({
                el:'#app',
                data:{
                    show:true,
                },
                methods:{
                    
                },
                computed:{
                    
                }
            })
        </script>
    </body>
</html>

 我們想讓圖上的那個div消失,不想為了管理同一組元素而多生成一個節點,這樣是非常消耗性能的,我們將div標籤變成template標籤

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style type="text/css">
            .box{
                width: 100px;
                height: 100px;
            }
            .red{
                background: red;
            }
            .blue{
                background: blue;
            }
        </style>
    </head>
    <body>
        <div id="app">
            <div v-if="show">
                <div class="box red"></div>
                <div class="box blue"></div>
            </div>
            <template v-if="show">
                <div class="box red"></div>
                <div class="box blue"></div>
            </template>
        </div>
        <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            let vm=new Vue({
                el:'#app',
                data:{
                    show:true,
                },
                methods:{
                    
                },
                computed:{
                    
                }
            })
        </script>
    </body>
</html>

View Code

 現在我有心中萌生了一個想法,v-if可以使用template,那麼v-show是否可以使用呢?

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style type="text/css">
            .box{
                width: 100px;
                height: 100px;
            }
            .red{
                background: red;
            }
            .blue{
                background: blue;
            }
        </style>
    </head>
    <body>
        <div id="app">
            <template v-if="show">
                <div class="box red"></div>
                <div class="box blue"></div>
            </template>
            <template v-show="show">
                <div class="box red"></div>
                <div class="box blue"></div>
            </template>
            <button @click="handleClick()">{{text}}</button>
        </div>
        <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            let vm=new Vue({
                el:'#app',
                data:{
                    show:true,
                    text:'隱藏',
                },
                methods:{
                    handleClick(){
                        this.show=!this.show;
                        this.text=this.show?'隱藏':'显示'
                    }
                },
                computed:{
                    
                }
            })
        </script>
    </body>
</html>

View Code

 

 答案是v-if可以使用template,而v-show不能使用template

vue中用key管理可復用的元素

Vue 會盡可能高效地渲染元素,通常會復用已有元素而不是從頭開始渲染。這麼做除了使 Vue 變得非常快之外,還有其它一些好處。例如,如果你允許用戶在不同的登錄方式之間切換。

示例一:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<div id="app01">
    <template v-if="type==='username'">
        <label>用戶名</label>
        <input type="text" placeholder="請輸入您的賬號" />
    </template>
    <template v-else>
        <label>郵箱</label>
        <input type="text" placeholder="請輸入您的郵箱" />
    </template>
    <p>
        <a href=""@click.prevent="type='username'">用戶名登錄</a>|
        <a href=""@click.prevent="type='email'">郵箱登錄</a>
    </p>
</div>
<script src="../js/vue.js"></script>
<script>
    let vm=new Vue({
        el:'#app01',    
        data:{
            isShow:true,
            type:'username'
        },
        methods:{
            
        },
        watch:{
            
        },
        computed:{
            
        }
    })
</script>
</body>
</html>

結果:

 

 

當我們在用戶名登錄和郵箱切換的時候,我們發現我們輸入的內容始終保持,為什麼呢?總的來說,因為兩個模板使用了相同的元素,input不會被替換掉——僅僅是替換了它的 placeholder屬性,這樣也不總是符合實際需求,所以 Vue 為你提供了一種方式來表達這兩個元素是完全獨立的,不要復用它們,只需添加一個具有唯一值的key屬性即可。

示例二:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<div id="app01">
    <template v-if="type==='username'">
        <label>用戶名:</label>
        <input type="text" placeholder="請輸入您的用戶名"  key='usename'/>
    </template>
    <template v-else>
        <label>郵箱:</label>
        <input type="text" placeholder="請輸入您的郵箱"  key='email'/>
    </template>
    <p>
        <a href=""@click.prevent="type='username'">用戶名登錄</a>|
        <a href=""@click.prevent="type='email'">郵箱登錄</a>
    </p>
</div>
<script src="../js/vue.js"></script>
<script>
    let vm=new Vue({
        el:'#app01',    
        data:{
            isShow:true,
            type:'username'
        },
        methods:{
            
        },
        watch:{
            
        },
        computed:{
            
        }
    })
</script>
</body>
</html>

結果:

 

現在我們點擊切換的時候,輸入框都會重新渲染,當然我們的<label>標籤依舊的高效的復用,因為它沒有添加key。

列表渲染

我們用v-for指令根據一組數組的選項列表進行渲染,v-for指令需要以item in items的形式的特殊語法,items是原數據數組並且item是元素迭代的別名

1.v-for的基本使用

語法:(item,index) in|of items

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>v-for的基本使用</title>
    </head>
    <body>
        <div id="app">
            <ul>
                <li v-for="(item) in arr">{{item}}</li>
            </ul>
        </div>
        <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            let vm=new Vue({
                el:'#app',
                data:{
                    arr:['apple','banana','pear']
                },
                methods:{
                    
                },
                computed:{
                    
                }
            })
        </script>
    </body>
</html>

結果:

當然v-for中也可以帶第二個參數index

2.v-for中帶索引

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>v-for的基本使用</title>
    </head>
    <body>
        <div id="app">
            <ul>
                <li v-for="(item,index) in arr">{{item}}--{{index}}</li>
            </ul>
        </div>
        <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            let vm=new Vue({
                el:'#app',
                data:{
                    arr:['apple','banana','pear']
                },
                methods:{
                    
                },
                computed:{
                    
                }
            })
        </script>
    </body>
</html>

View Code

3.v-for迭代字符串

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>v-for的基本使用</title>
    </head>
    <body>
        <div id="app">
            <ul>
                <li v-for="(item,index) in arr">{{item}}--{{index}}</li>
            </ul>
            <ul>
                <li v-for="item in 'helloworld'">{{item}}</li>
            </ul>
        </div>
        <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            let vm=new Vue({
                el:'#app',
                data:{
                    arr:['apple','banana','pear']
                },
                methods:{
                    
                },
                computed:{
                    
                }
            })
        </script>
    </body>
</html>

View Code

4.v-for迭代對象

語法:(value,key,index) of | in items

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>v-for迭代對象</title>
    </head>
    <body>
        <div id="app">
            <ul>
                <li v-for="(value,key,index) of obj">{{value}}-{{key}}-{{index}}</li>
            </ul>
        </div>
        <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            let vm=new Vue({
                el:'#app',
                data:{
                    obj:{
                        name:'kk',
                        age:18,
                        sex:'male'
                    }
                },
                methods:{
                    
                },
                computed:{
                    
                }
            })
        </script>
    </body>
</html>

結果:

5.v-for迭代整數

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>v-for迭代對象</title>
    </head>
    <body>
        <div id="app">
            <ul>
                <li v-for="(value,key,index) of obj">{{value}}-{{key}}-{{index}}</li>
            </ul>
            <ul>
                <li v-for="item in 10">{{item}}</li>
            </ul>
        </div>
        <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            let vm=new Vue({
                el:'#app',
                data:{
                    obj:{
                        name:'kk',
                        age:18,
                        sex:'male'
                    }
                },
                methods:{
                    
                },
                computed:{
                    
                }
            })
        </script>
    </body>
</html>

結果:

注意:但我們迭代整數的時候,item從1開始而不是從0開始

總結

在本章內容中,我們一共學習了三個知識點,分別是條件渲染的使用(v-if,v-else,v-else-if),管理可復用的key,列表渲染(v-for的基本使用等等),本章的內容也多但是在實際應用上非常廣泛,畢竟這些是非常基礎的語法,基礎不牢,地動山搖,學習任何東西都需要自己一步一個腳印走出來。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

南投搬家前需注意的眉眉角角,別等搬了再說!

窮人可用便宜電 巴西22歲女發明新方式發電

摘錄自2020年1月29日民視綜合報導

在巴西,一名年僅22歲的學生,想到了一個新穎卻又簡單的方式來發電。把石墨倒出來、塗在紙上,再把這些石墨紙剪成一塊塊的小正方形,20塊疊一起,用來捕捉大氣中水分子的能量,就這樣只須紙張、石墨和水氣,簡單幾種原料就能發電。

22歲的莫蕾拉是巴西聖馬利亞聯邦大學的學生,「此(裝置)捕捉的電力和電化學無關,而是和大氣中的離子有關,所以這是為什麼這個電池具永續性又對環境友善。」

除了環保,要讓50、60顆LED小燈泡發光的材料費,大概只須5分美元、約合新台幣1.5元,非常便宜。莫蕾拉認為,雖然目前她的發明還在初始階段,但以後有望擴大規模、幫助較貧困地區的人民。

莫蕾拉的指導教授則表示,雖然他們並不認為這種發電方式能取代其他能源,但現在有任何具永續性、可以再生的能源,都是大家所追求的。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

老虎「從後追上」一把壓地上!他「裝死不動」倖存

摘錄自2020年1月30日ETtoday報導

位於印度中部的馬哈拉施特拉邦(Maharashtra)25日發生老虎攻擊人的事件,一名當地村民被老虎追趕後壓制在地上,為了脫困,他先是假裝自己已經死亡,一動也不動動的躺在地上,旁邊的村民則是不斷地朝著老虎丟擲石頭以示恐嚇;最後老虎似乎受到驚嚇轉身逃跑,而該名村民也平安無事的躲過了一劫。

報導中指出,這起混亂一共造成至少三人受傷,老虎最後則是似乎受到驚嚇,頭也不回地轉身跑走,被壓在地上的男子幸運地存活下來,沒有受到太大的傷害。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

南投搬家前需注意的眉眉角角,別等搬了再說!