墨西哥穩定電力擺第一 新能源測試喊卡

摘錄自2020年5月4日經濟日報報導

在疫情蔓延下,墨西哥電力系統主管機關宣布,對乾淨能源新計畫的關鍵測試無限期喊卡,另採取措施,以提高全國電力系統的穩定性,但批評者擔心,這項措施將傷害再生能源業者。

 

能源議題
能源轉型
國際新聞
墨西哥
乾淨能源
武漢肺炎
綠電
疫情看氣候與能源
新能源

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

【其他文章推薦】

※教你寫出一流的銷售文案?

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

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※推薦台中搬家公司優質服務,可到府估價

非洲豬瘟迫降! 韓媒爆北韓豬瘟再起 邊境恐成傳播溫床

摘錄自2020年5月5日自由時報報導

北韓在去年5月底爆出非洲豬瘟,9月時更爆出豬瘟疫情全面失控,但之後卻未有通報案例。不過南韓媒體爆料,非洲豬瘟在北韓又再度爆發,尤其是黃海北道、平安南道以及平安北道三處。

由脫北者經營的南韓媒體《每日北韓》報導,平安南道消息人士透露,非洲豬瘟再次席捲北韓,獸醫機構視其為緊急狀態,尤其是黃海北道、平安南道以及平安北道三處的私人豬舍或合作經營農場。

由於北韓當局最近發布《非洲豬瘟防疫指南》,因此有分析指出,北韓非洲豬瘟疫情仍持續。北韓官報也向民眾宣導牲畜疾病防疫對民眾的重要性。

生活環境
國際新聞
北韓
非洲豬瘟
食品安全

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

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

疫情下的奇景!西班牙西北部棕熊出沒 150年來首見

摘錄自2020年5月5日自由時報報導

根據《CNN》報導,位於西班牙西北部加利西亞(Galicia)奧倫塞的「O Invernadeiro」自然保護區,近日透過架設在園內的攝影機拍到一頭年輕公熊活動的畫面,據分析,這頭公熊年紀在3至5歲左右,健康狀況良好。

園方表示,棕熊是西班牙原生物種,從1973年起便被列入野生動物保護名單,過往雖有文獻紀錄常出沒在加利西亞地區,但這次是150年內首度有棕熊被觀測到在加利西亞南部出現,意義非凡。

生態保育
生物多樣性
國際新聞
西班牙
棕熊
動物與大環境變遷
武漢肺炎

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

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

麥肯錫報告:後疫情時代下的氣候變遷

轉載自台大風險社會與政策研究中心;編譯:倪茂庭(風險中心助理研究員)、吳玗恂(風險中心助理研究員)

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

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

STM32串口打印的那些知識

常規打印方法

在STM32的應用中,我們常常對printf進行重定向的方式來把打印信息printf到我們的串口助手。在MDK環境中,我們常常使用MicroLIB+fputc的方式實現串口打印功能,即:

要實現fputc函數的原因是:printf函數依賴於fputc函數,重新實現fputc內部從串口發送數據即可間接地實現printf打印輸出數據到串口。

不知道大家有沒有看過正點原子裸機串口相關的例程,他們的串口例程里不使用MicroLIB,而是使用標準庫+fputc的方式。相關代碼如:

#if 1
#pragma import(__use_no_semihosting)
//標準庫需要的支持函數
struct __FILE
{
    int handle;
};

FILE __stdout;
/**
 * @brief	定義_sys_exit()以避免使用半主機模式
 * @param	void
 * @return  void
 */
void _sys_exit(int x)
{
    x = x;
}

int fputc(int ch, FILE *f)
{
    while((USART1->ISR & 0X40) == 0); //循環發送,直到發送完畢

    USART1->TDR = (u8) ch;
    return ch;
}
#endif

關於這兩種方法的一些說明可以查看Mculover666兄的重定向printf函數到串口輸出的多種方法這篇文章。這篇文章中不僅包含上面的兩種方法,而且也包含着在GCC中使用標準庫重定向printf的方法。

自己實現一個打印函數

以上的幾種方法基本上是改造C庫的printf函數來實現串口打印的功能。其實我們也可以自己實現一個串口打印的功能。

printf本身就是一個變參函數,其原型為:

int printf (const char *__format, ...);

所以,我們要重新封裝的一個串口打印函數自然也應該是一個變參函數。具體實現如下:

1、基於STM32的HAL庫

#define TX_BUF_LEN  256     /* 發送緩衝區容量,根據需要進行調整 */
uint8_t TxBuf[TX_BUF_LEN];  /* 發送緩衝區                       */
void MyPrintf(const char *__format, ...)
{
  va_list ap;
  va_start(ap, __format);
  
  /* 清空發送緩衝區 */
  memset(TxBuf, 0x0, TX_BUF_LEN);
  
  /* 填充發送緩衝區 */
  vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);
  va_end(ap);
  int len = strlen((const char*)TxBuf);
  
  /* 往串口發送數據 */
  HAL_UART_Transmit(&huart1, (uint8_t*)&TxBuf, len, 0xFFFF);
}

因為我們使用printf函數基本不使用其返回值,所以這裏直接用void類型了。自定義變參函數需要用到va_start、va_end等宏,需要包含頭文件stdarg.h。關於變參函數的一些學習可以查看網上的一些博文,如:

https://www.cnblogs.com/wulei0630/p/9444062.html

這裏我們使用的是STM32的HAL庫,其給我們提供HAL_UART_Transmit接口可以直接把整個發送緩衝區的內容給一次性發出去。

2、基於STM32標準庫

若是基於STM32的標準庫,就需要一字節一字節的循環發送出去,具體代碼如:

#define TX_BUF_LEN  256     /* 發送緩衝區容量,根據需要進行調整 */
uint8_t TxBuf[TX_BUF_LEN];  /* 發送緩衝區                       */
void MyPrintf(const char *__format, ...)
{
  va_list ap;
  va_start(ap, __format);
    
  /* 清空發送緩衝區 */
  memset(TxBuf, 0x0, TX_BUF_LEN);
    
  /* 填充發送緩衝區 */
  vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);
  va_end(ap);
  int len = strlen((const char*)TxBuf);
  
  /* 往串口發送數據 */
  for (int i = 0; i < len; i++)
  {
	while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET);    
	USART_SendData(USART1, TxBuf[i]);
  }
}

測試結果:

我們也可以使用我們的MyPrintf函數按照上一篇文章:======的方式封裝一個宏打印函數:

以上就是我們自定義方式實現的一種串口打印函數。

但是,我想說:對於串口打印的使用,我們沒必要自己創建一個打印函數。看到這,是不是有人想要打我了。。。。看了半天,你卻跟我說沒必要用。。。

哈哈,別急,我們不應用在串口打印調試方面,那可以用在其它方面呀。

(1)應用一:

比如最近我在實際應用中:我們的MCU跑的是我們老大自己寫的一個小的操作系統+我們公司自己開發的上位機。我們MCU端與上位機使用的是串口通訊,MCU往上位機發送的數據有兩種類型,一種是HEX格式數據,一種是字符串數據。

但是我們下位機的這兩種數據,在通過串口發送之前都得統一把數據封包交給那個系統通信任務,然後再由通信任務發出去。在這裏,就不能用printf了。老大也針對他的這個系統實現了一個deb_printf函數用於打印調試。

但是,那個函數既複雜又很雞肋,稍微複雜一點的數據就打印不出來了。因此我利用上面的思路給它新封裝了一個打印調試函數,很好用,完美地兼容了老大的那個系統。具體代碼就不分享了,大體代碼、思路如上。

(2)應用二:

我們在使用串口與ESP8266模塊通訊時,可利用類似這樣的方式封裝一個發送數據的函數,這個函數的使用可以像printf一樣簡單。可以以很簡單的方式把數據透傳至服務端,比如我以前的畢設中就有這麼應用:

以上就是本次的分享,如有錯誤,歡迎指出!謝謝

我的個人博客:https://www.lizhengnian.cn/

我的微信公眾號:嵌入式大雜燴

我的CSDN博客:https://blog.csdn.net/zhengnianli

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

【其他文章推薦】

※教你寫出一流的銷售文案?

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

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※推薦台中搬家公司優質服務,可到府估價

ASP.NET Core中間件與HttpModule有何不同

前言

在ASP.NET Core中最大的更改之一是對Http請求管道的更改,在ASP.NET中我們了解HttpHandlerHttpModule但是到現在這些已經被替換為中間件那麼下面我們來看一下他們的不同處。

HttpHandler

Handlers處理基於擴展的特定請求,HttpHandlers作為進行運行,同時做到對ASP.NET響應請求。他是一個實現System.Web.IHttphandler接口的類。任何實現IHttpHandler接口的類都可以作為Http請求處理響應的目標程序。
它提供了對文件特定的擴展名處理傳入請求,
ASP.NET框架提供了一些默認的Http處理程序,最常見的處理程序是處理.aspx文件。下面提供了一些默認的處理程序。

Handler Extension Description
Page Handler .aspx handle normal WebPages
User Control Handler .ascx handle Web user control pages
Web Service Handler .asmx handle Web service pages
Trace Handler trace.axd handle trace functionality

創建一個自定義HttpHandler


public class CustomHttpHandler:IHttpHandler
{
    
    public bool IsReusable
    {
        //指定是否可以重用處理程序
        get {return true;}
    }
    
    public void ProcessRequest(HttpContext context)
    {
        //TODO
        throw new NotImplementedException();
    }
}

在web.config中添加配置項

<!--IIS6或者IIS7經典模式-->  
  <system.web>  
    <httpHandlers>  
      <add name="mycustomhandler" path="*.aspx" verb="*" type="CustomHttpHandler"/>  
    </httpHandlers>  
  </system.web>  
   
<!--IIS7集成模式-->  
  <system.webServer>  
    <handlers>  
       <add name="mycustomhandler" path="*.aspx" verb="*" type="CustomHttpHandler"/>  
    </handlers>  
  </system.webServer>  

異步HttpHandlers

異步的話需要繼承HttpTaskAsyncHandler類,HttpTaskAsyncHandler類實現了IHttpTaskAsyncHandlerIHttpHandler接口

public class CustomHttpHandlerAsync:HttpTaskAsyncHandler
{
    
    public override Task ProcessRequestAsync(HttpContext context)
    {

        throw new NotImplementedException();
    }
}

HttpModule

下面是來自MSDN

Modules are called before and after the handler executes. Modules enable developers to intercept, participate in, or modify each individual request. Modules implement the IHttpModule interface, which is located in the System.Web namespace.

HttpModule類似過濾器,它是一個基於事件的,在應用程序發起到結束的整個生命周期中訪問事件

自定義一個HttpModule

public class CustomModule : IHttpModule
    {
        public void Dispose()
        {
            throw new NotImplementedException();
        }

        public void Init(HttpApplication context)
        {
            context.BeginRequest += new EventHandler(BeginRequest);
            context.EndRequest += new EventHandler(EndRequest);
        }
        void BeginRequest(object sender, EventArgs e)
        {
            ((HttpApplication)sender).Context.Response.Write("請求處理前");
        }

        void EndRequest(object sender, EventArgs e)
        {
            ((HttpApplication)sender).Context.Response.Write("請求處理結束后");
        }
    }


web.config中配置

<!--IIS6或者IIS7經典模式-->  
<system.web>  
    <httpModules>  
      <add name="mycustommodule" type="CustomModule"/>  
    </httpModules>  
  </system.web>  
<!--IIS7集成模式-->  
<system.webServer>  
    <modules>  
      <add name="mycustommodule" type="CustomModule"/>  
    </modules>  
</system.webServer>  

中間件

中間件可以視為集成到Http請求管道中的小型應用程序組件,它是ASP.NET中HttpModule和HttpHandler的結合,它可以處理身份驗證、日誌請求記錄等。

中間件和HttpModule的相似處

中間件和HttpMoudle都是可以處理每個請求,同時可以配置進行返回我們自己的定義。

中間件和httpModule之間的區別

HttpModule 中間件
通過web.config或global.asax配置 在Startup文件中添加中間件
執行順序無法控制,因為模塊順序主要是基於應用程序生命周期事件 可以控制執行內容和執行順序按照添加順序執行。
請求和響應執行順序保持不變 響應中間件順序與請求順序相反
HttpModules可以附件特定應用程序事件的代碼 中間件獨立於這些事件

中間件示例

 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  {
      if (env.IsDevelopment())
      {
          app.UseDeveloperExceptionPage();
      }

      app.UseHttpsRedirection();

      app.UseRouting();

      app.UseAuthorization();

      app.UseEndpoints(endpoints =>
      {
          endpoints.MapControllers();
      });
  }

在如上代碼片段中我們有一些中間件的添加,同時也有中間件的順序。

Reference

How ASP.NET Core 1.0 Middleware is different from HttpModule

https://support.microsoft.com/en-us/help/307985/info-asp-net-http-modules-and-http-handlers-overview

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

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

詳解SpringBoot(2.3)應用製作Docker鏡像(官方方案)

關於《SpringBoot-2.3容器化技術》系列

《SpringBoot-2.3容器化技術》系列,旨在和大家一起學習實踐2.3版本帶來的最新容器化技術,讓咱們的Java應用更加適應容器化環境,在雲計算時代依舊緊跟主流,保持競爭力;

全系列文章分為主題和輔助兩部分,主題部分如下:

  1. 《體驗SpringBoot(2.3)應用製作Docker鏡像(官方方案)》;
  2. 《詳解SpringBoot(2.3)應用製作Docker鏡像(官方方案)》;
  3. 《掌握SpringBoot-2.3的容器探針:基礎篇》;
  4. 《掌握SpringBoot-2.3的容器探針:深入篇》;
  5. 《掌握SpringBoot-2.3的容器探針:實戰篇》;

輔助部分是一些參考資料和備忘總結,如下:

  1. 《SpringBoot-2.3鏡像方案為什麼要做多個layer》;
  2. 《設置非root賬號不用sudo直接執行docker命令》;
  3. 《開發階段,將SpringBoot應用快速部署到K8S》;

本篇簡介

在前文,咱們快速體驗了官方推薦的docker鏡像製作方案,但也產生了幾個疑問:

  1. SpringBoot-2.3版本推薦的鏡像構建方案和舊版本比有什麼不同?
  2. pom.xml中spring-boot-maven-plugin插件新增的參數,到底做了什麼?
  3. Dockerfile中,java -Djarmode=layertools -jar application.jar extract這個操作啥意思?

本篇的目標就是解答上述問題,在尋找答案的過程中不斷補全知識點,提升自己;

關鍵知識點:鏡像layer

前文多次提到的鏡像layer到底是什麼,為什麼會有多層layer?有必要先把這個知識點夯實了,請參考文章《SpringBoot-2.3鏡像方案為什麼要做多個layer》

老版本SpringBoot的官方方案

SpringBoot-2.2.0.RELEASE版本為例,官方文檔(
https://docs.spring.io/spring-boot/docs/2.2.0.RELEASE/reference/pdf/spring-boot-reference.pdf)給出的做法如下:

  1. 將SpringBoot工程編譯構建,在target目錄得到jar;
  2. 在target目錄新建dependency文件夾;
  3. 將jar解壓到dependency文件夾;
  4. 編寫Dockerfile文件,內容如下:
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=target/dependency
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.MyApplication"]
  1. 可見,官方推薦的做法是將整個jar文件解壓,在Dockerfile中多次用COPY命令分別複製,這樣做的好處顯而易見:多個layer,如果鏡像的新版本中只修改了應用代碼,那麼下載鏡像時只會下載/app這個layer,其他部分直接使用本地緩存,這是docker鏡像的常規優化手段;
  2. 上述方案有個小問題:麻煩!!!
  3. 於是2.3.0.RELEASE版本做了些優化,讓事情變得簡單些;

2.3.0.RELEASE版本方案和舊版的區別

2.3.0.RELEASE版本構建Docker的步驟如下:

  1. pom.xml中的spring-boot-maven-plugin插件增加一個配置項;
    2.編譯構建生成jar;
  2. 編寫Dockerfile,裏面用到了多階段構建(multi-stage builds),用工具從jar中提取拆分后,再多次執行COPY命令將拆分后的內容放入鏡像,達到多個layer的目的;

因此,2.3.0.RELEASE版本和舊版本相比有如下變化:

  1. pom.xml中多了個參數;
  2. 構建好jar后,無需自己解壓jar;
  3. Dockefile內容不一樣,舊版是手動解壓jar,再在Dockerfile分別複製,2.3.0.RELEASE是通過java命令從jar中提取出各部分內容

搞清楚了新舊版本的區別,咱們繼續研究下一個問題吧;

pom.xml中spring-boot-maven-plugin插件新增的參數

  1. pring-boot-maven-plugin插件新增參數如下圖所示:

2. 上述參數有啥用?我這邊編譯構建了兩次jar,第一次有上述參數,第二次沒有,將兩次生成的jar解壓后對比,發現用了上述參數后,生成的jar會多出下圖紅框中的兩個文件:

  1. 看看layers.idx文件的內容,如下圖:
  1. 上圖中的內容分別是什麼意思呢?官方已給出了詳細解釋,如下圖紅框:
  1. 綜上所述,layers.idx文件是個清單,裏面記錄了所有要被複制到鏡像中的信息,接下來看看如何使用layers.idx文件,這就涉及到jar包中新增的另一個文件:spring-boot-jarmode-layertools-2.3.0.RELEASE.jar

spring-boot-jarmode-layertools工具

  1. 前面已經介紹過jar中除了layers.idx,還多了個文件:spring-boot-jarmode-layertools-2.3.0.RELEASE.jar ,來看看這個文件的用處;
  2. 進入工程的target目錄,這裏面是編譯后的jar文件(我這裏文件名為dockerlayerdemo-0.0.1-SNAPSHOT.jar),注意此時的spring-boot-maven-plugin插件是帶上了下圖紅框中的參數的:
  1. 執行以下命令:
java -Djarmode=layertools -jar dockerlayerdemo-0.0.1-SNAPSHOT.jar list
  1. 得到結果如下圖所示,是layers.idx文件的內容:
  1. 來看看官方對這個layertools的解釋,list參數的作用上面我們已經體驗過了,重點是紅框中的extract參數,它的作用是從jar中提取構建鏡像所需的內容:
  1. 看到這裏,您是否想到了《體驗SpringBoot(2.3)應用製作Docker鏡像(官方方案)》中Dockerfile的內容,請看下圖的紅框和紅字,是否有種恍然大悟的感覺:jar構建生成清單layers.idx,Dockerfile中根據清單從jar提取文件放入鏡像:

至此,三個問題都已經找到了答案,小結一下:

SpringBoot-2.3.0.RELEASE推薦的鏡像構建方案和舊版本相比有什麼不同

  1. pom.xml中的spring-boot-maven-plugin插件增加一個配置項;
  2. 構建好jar后,舊版本要自己解壓jar,新版不需要;
  3. 新版本的jar中,多了個文件清單layers.idx和鏡像文件處理工具spring-boot-jarmode-layertools-2.3.0.RELEASE.jar
  4. 舊版的Dockefile內容:因為前面解壓好了,所有在Dockerfile里直接複製前面解壓的內容,這裏就有個風險:前一步解壓和當前複製的文件位置要保證一致;
  5. 新版的Dockerfile內容:使用工具spring-boot-jarmode-layertools-2.3.0.RELEASE.jar,根據的layers.idx內容從jar中提取文件,複製到鏡像中;
  6. 新版的Dockerfile中,由於使用了分階段構建,因此從jar提取文件的操作不會保存到鏡像的layer中;

pom.xml中spring-boot-maven-plugin插件新增的參數,到底做了什麼

spring-boot-maven-plugin插件新增的參數,使得編譯構建得到jar中多了兩個文件,如下圖所示:

Dockerfile中,java -Djarmode=layertools -jar application.jar extract這個操作啥意思

  1. java -Djarmode=layertools -jar application.jar extract的作用是從jar中提取文件,這些文件是docker鏡像的一部分;
  2. 上述操作的參數是extract,另外還有兩個參數,官方解釋它們的作用如下:

至此,問題已全部澄清,相信您對SpringBoot-2.3.0.RELEASE官方的鏡像構建方案也足夠了解了,最後是我根據自己的認識畫的流程圖,幫助您快速理解整個構建流程:

歡迎訪問我的GitHub

  • 地址:https://github.com/zq2599/blog_demos
  • 內容:原創文章分類匯總,及配套源碼,涉及Java、Docker、K8S、DevOPS等

歡迎關注我的公眾號:程序員欣宸

https://github.com/zq2599/blog_demos

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

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

小師妹學JavaIO之:文件系統和WatchService

目錄

  • 簡介
  • 監控的痛點
  • WatchService和文件系統
  • WatchSerice的使用和實現本質
  • 總結

簡介

小師妹這次遇到了監控文件變化的問題,F師兄給小師妹介紹了JDK7 nio中引入的WatchService,沒想到又順道普及了一下文件系統的概念,萬萬沒想到。

監控的痛點

小師妹:F師兄最近你有沒有感覺到呼吸有點困難,后領有點涼颼颼的,說話有點不順暢的那種?

沒有啊小師妹,你是不是秋衣穿反了?

小師妹:不是的F師兄,我講的是心裏的感覺,那種莫須有的壓力,還有一絲悸動纏繞在心。

別繞彎子了小師妹,是不是又遇到問題了。

更多精彩內容且看:

  • 區塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續更新
  • Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新
  • Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新
  • java程序員從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程

更多內容請訪問www.flydean.com

小師妹:還是F師兄懂我,這不上次的Properties文件用得非常上手,每次修改Properties文件都要重啟java應用程序,真的是很痛苦。有沒有什麼其他的辦法呢?

辦法當然有,最基礎的辦法就是開一個線程定時去監控屬性文件的最後修改時間,如果修改了就重新加載,這樣不就行了。

小師妹:寫線程啊,這麼麻煩,有沒有什麼更簡單的辦法呢?

就知道你要這樣問,還好我準備的比較充分,今天給你介紹一個JDK7在nio中引入的類WatchService。

WatchService和文件系統

WatchService是JDK7在nio中引入的接口:

監控的服務叫做WatchService,被監控的對象叫做Watchable:

WatchKey register(WatchService watcher,
                      WatchEvent.Kind<?>[] events,
                      WatchEvent.Modifier... modifiers)
        throws IOException;
WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events)
        throws IOException;

Watchable通過register將該對象的WatchEvent註冊到WatchService上。從此只要有WatchEvent發生在Watchable對象上,就會通知WatchService。

WatchEvent有四種類型:

  1. ENTRY_CREATE 目標被創建
  2. ENTRY_DELETE 目標被刪除
  3. ENTRY_MODIFY 目標被修改
  4. OVERFLOW 一個特殊的Event,表示Event被放棄或者丟失

register返回的WatchKey就是監聽到的WatchEvent的集合。

現在來看WatchService的4個方法:

  1. close 關閉watchService
  2. poll 獲取下一個watchKey,如果沒有則返回null
  3. 帶時間參數的poll 在等待的一定時間內獲取下一個watchKey
  4. take 獲取下一個watchKey,如果沒有則一直等待

小師妹:F師兄,那怎麼才能構建一個WatchService呢?

上次文章中說的文件系統,小師妹還記得吧,FileSystem中就有一個獲取WatchService的方法:

public abstract WatchService newWatchService() throws IOException;

我們看下FileSystem的結構圖:

在我的mac系統上,FileSystem可以分為三大類,UnixFileSystem,JrtFileSystem和ZipFileSystem。我猜在windows上面應該還有對應的windows相關的文件系統。小師妹你要是有興趣可以去看一下。

小師妹:UnixFileSystem用來處理Unix下面的文件,ZipFileSystem用來處理zip文件。那JrtFileSystem是用來做什麼的?

哎呀,這就又要扯遠了,為什麼每次問問題都要扯到天邊….

從前當JDK還是9的時候,做了一個非常大的改動叫做模塊化JPMS(Java Platform Module System),這個Jrt就是為了給模塊化系統用的,我們來舉個例子:

public void useJRTFileSystem(){
        String resource = "java/lang/Object.class";
        URL url = ClassLoader.getSystemResource(resource);
        log.info("{}",url);
    }

上面一段代碼我們獲取到了Object這個class的url,我們看下如果是在JDK8中,輸出是什麼:

jar:file:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar!/java/lang/Object.class

輸出結果是jar:file表示這個Object class是放在jar文件中的,後面是jar文件的路徑。

如果是在JDK9之後:

jrt:/java.base/java/lang/Object.class

結果是jrt開頭的,java.base是模塊的名字,後面是Object的路徑。看起來是不是比傳統的jar路徑更加簡潔明了。

有了文件系統,我們就可以在獲取系統默認的文件系統的同時,獲取到相應的WatchService:

WatchService watchService = FileSystems.getDefault().newWatchService();

WatchSerice的使用和實現本質

小師妹:F師兄,WatchSerice是咋實現的呀?這麼神奇,為我們省了這麼多工作。

其實JDK提供了這麼多類的目的就是為了不讓我們重複造輪子,之前跟你講監控文件的最簡單辦法就是開一個獨立的線程來監控文件變化嗎?其實…..WatchService就是這樣做的!

PollingWatchService() {
        // TBD: Make the number of threads configurable
        scheduledExecutor = Executors
            .newSingleThreadScheduledExecutor(new ThreadFactory() {
                 @Override
                 public Thread newThread(Runnable r) {
                     Thread t = new Thread(null, r, "FileSystemWatcher", 0, false);
                     t.setDaemon(true);
                     return t;
                 }});
    }

上面的方法就是生成WatchService的方法,小師妹看到沒有,它的本質就是開啟了一個daemon的線程,用來接收監控任務。

下面看下怎麼把一個文件註冊到WatchService上面:

private void startWatcher(String dirPath, String file) throws IOException {
        WatchService watchService = FileSystems.getDefault().newWatchService();
        Path path = Paths.get(dirPath);
        path.register(watchService, ENTRY_MODIFY);

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            try {
                watchService.close();
            } catch (IOException e) {
                log.error(e.getMessage());
            }
        }));

        WatchKey key = null;
        while (true) {
            try {
                key = watchService.take();
                for (WatchEvent<?> event : key.pollEvents()) {
                    if (event.context().toString().equals(fileName)) {
                        loadConfig(dirPath + file);
                    }
                }
                boolean reset = key.reset();
                if (!reset) {
                    log.info("該文件無法重置");
                    break;
                }
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
    }

上面的關鍵方法就是path.register,其中Path是一個Watchable對象。

然後使用watchService.take來獲取生成的WatchEvent,最後根據WatchEvent來處理文件。

總結

道生一,一生二,二生三,三生萬物。一個簡簡單單的功能其實背後隱藏着…道德經,哦,不對,背後隱藏着道的哲學。

本文的例子https://github.com/ddean2009/learn-java-io-nio

本文作者:flydean程序那些事

本文鏈接:http://www.flydean.com/java-io-file-watchservice/

本文來源:flydean的博客

歡迎關注我的公眾號:程序那些事,更多精彩等着您!

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

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(五)

系列文章

  1. 基於 abp vNext 和 .NET Core 開發博客項目 – 使用 abp cli 搭建項目
  2. 基於 abp vNext 和 .NET Core 開發博客項目 – 給項目瘦身,讓它跑起來
  3. 基於 abp vNext 和 .NET Core 開發博客項目 – 完善與美化,Swagger登場
  4. 基於 abp vNext 和 .NET Core 開發博客項目 – 數據訪問和代碼優先
  5. 基於 abp vNext 和 .NET Core 開發博客項目 – 自定義倉儲之增刪改查
  6. 基於 abp vNext 和 .NET Core 開發博客項目 – 統一規範API,包裝返回模型
  7. 基於 abp vNext 和 .NET Core 開發博客項目 – 再說Swagger,分組、描述、小綠鎖
  8. 基於 abp vNext 和 .NET Core 開發博客項目 – 接入GitHub,用JWT保護你的API
  9. 基於 abp vNext 和 .NET Core 開發博客項目 – 異常處理和日誌記錄
  10. 基於 abp vNext 和 .NET Core 開發博客項目 – 使用Redis緩存數據
  11. 基於 abp vNext 和 .NET Core 開發博客項目 – 集成Hangfire實現定時任務處理
  12. 基於 abp vNext 和 .NET Core 開發博客項目 – 用AutoMapper搞定對象映射
  13. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(一)
  14. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(二)
  15. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(三)
  16. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(一)
  17. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(二)
  18. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(三)
  19. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(四)

上篇文章完成了文章詳情頁數據查詢和清除緩存的功能。

本篇繼續完成分類、標籤、友情鏈接的後台操作接口,還是那句話,這些純CRUD的內容,建議還是自己動手完成比較好,本篇將不再啰嗦,直接貼代碼,以供參考。

分類

添加接口:查詢分類列表QueryCategoriesForAdminAsync()、新增分類InsertCategoryAsync(...)、更新分類UpdateCategoryAsync(...)、刪除分類DeleteCategoryAsync(...)

#region Categories

/// <summary>
/// 查詢分類列表
/// </summary>
/// <returns></returns>
Task<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>> QueryCategoriesForAdminAsync();

/// <summary>
/// 新增分類
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<ServiceResult> InsertCategoryAsync(EditCategoryInput input);

/// <summary>
/// 更新分類
/// </summary>
/// <param name="id"></param>
/// <param name="input"></param>
/// <returns></returns>
Task<ServiceResult> UpdateCategoryAsync(int id, EditCategoryInput input);

/// <summary>
/// 刪除分類
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
Task<ServiceResult> DeleteCategoryAsync(int id);

#endregion Categories

查詢分類列表需要返回的模型類QueryCategoryForAdminDto.cs

//QueryCategoryForAdminDto.cs
namespace Meowv.Blog.Application.Contracts.Blog
{
    public class QueryCategoryForAdminDto : QueryCategoryDto
    {
        /// <summary>
        /// 主鍵
        /// </summary>
        public int Id { get; set; }
    }
}

新增分類和更新分類需要的輸入參數EditCategoryInput.cs,直接繼承CategoryDto即可。

//EditCategoryInput.cs
namespace Meowv.Blog.Application.Contracts.Blog.Params
{
    public class EditCategoryInput : CategoryDto
    {
    }
}

分別實現這幾個接口。

/// <summary>
/// 查詢分類列表
/// </summary>
/// <returns></returns>
public async Task<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>> QueryCategoriesForAdminAsync()
{
    var result = new ServiceResult<IEnumerable<QueryCategoryForAdminDto>>();

    var posts = await _postRepository.GetListAsync();

    var categories = _categoryRepository.GetListAsync().Result.Select(x => new QueryCategoryForAdminDto
    {
        Id = x.Id,
        CategoryName = x.CategoryName,
        DisplayName = x.DisplayName,
        Count = posts.Count(p => p.CategoryId == x.Id)
    });

    result.IsSuccess(categories);
    return result;
}
/// <summary>
/// 新增分類
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<ServiceResult> InsertCategoryAsync(EditCategoryInput input)
{
    var result = new ServiceResult();

    var category = ObjectMapper.Map<EditCategoryInput, Category>(input);
    await _categoryRepository.InsertAsync(category);

    result.IsSuccess(ResponseText.INSERT_SUCCESS);
    return result;
}

這裏需要一條AutoMapper配置,將EditCategoryInput轉換為Category,忽略Id字段。

CreateMap<EditCategoryInput, Category>().ForMember(x => x.Id, opt => opt.Ignore());
/// <summary>
/// 更新分類
/// </summary>
/// <param name="id"></param>
/// <param name="input"></param>
/// <returns></returns>
public async Task<ServiceResult> UpdateCategoryAsync(int id, EditCategoryInput input)
{
    var result = new ServiceResult();

    var category = await _categoryRepository.GetAsync(id);
    category.CategoryName = input.CategoryName;
    category.DisplayName = input.DisplayName;

    await _categoryRepository.UpdateAsync(category);

    result.IsSuccess(ResponseText.UPDATE_SUCCESS);
    return result;
}
/// <summary>
/// 刪除分類
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<ServiceResult> DeleteCategoryAsync(int id)
{
    var result = new ServiceResult();

    var category = await _categoryRepository.FindAsync(id);
    if (null == category)
    {
        result.IsFailed(ResponseText.WHAT_NOT_EXIST.FormatWith("Id", id));
        return result;
    }

    await _categoryRepository.DeleteAsync(id);

    result.IsSuccess(ResponseText.DELETE_SUCCESS);
    return result;
}

BlogController.Admin.cs中添加接口。

#region Categories

/// <summary>
/// 查詢分類列表
/// </summary>
/// <returns></returns>
[HttpGet]
[Authorize]
[Route("admin/categories")]
[ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
public async Task<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>> QueryCategoriesForAdminAsync()
{
    return await _blogService.QueryCategoriesForAdminAsync();
}

/// <summary>
/// 新增分類
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost]
[Authorize]
[Route("category")]
[ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
public async Task<ServiceResult> InsertCategoryAsync([FromBody] EditCategoryInput input)
{
    return await _blogService.InsertCategoryAsync(input);
}

/// <summary>
/// 更新分類
/// </summary>
/// <param name="id"></param>
/// <param name="input"></param>
/// <returns></returns>
[HttpPut]
[Authorize]
[Route("category")]
[ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
public async Task<ServiceResult> UpdateCategoryAsync([Required] int id, [FromBody] EditCategoryInput input)
{
    return await _blogService.UpdateCategoryAsync(id, input);
}

/// <summary>
/// 刪除分類
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpDelete]
[Authorize]
[Route("category")]
[ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
public async Task<ServiceResult> DeleteCategoryAsync([Required] int id)
{
    return await _blogService.DeleteCategoryAsync(id);
}

#endregion Categories

標籤

添加接口:查詢標籤列表QueryTagsForAdminAsync()、新增標籤InsertTagAsync(...)、更新標籤UpdateTagAsync(...)、刪除標籤DeleteTagAsync(...)

#region Tags

/// <summary>
/// 查詢標籤列表
/// </summary>
/// <returns></returns>
Task<ServiceResult<IEnumerable<QueryTagForAdminDto>>> QueryTagsForAdminAsync();

/// <summary>
/// 新增標籤
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<ServiceResult> InsertTagAsync(EditTagInput input);

/// <summary>
/// 更新標籤
/// </summary>
/// <param name="id"></param>
/// <param name="input"></param>
/// <returns></returns>
Task<ServiceResult> UpdateTagAsync(int id, EditTagInput input);

/// <summary>
/// 刪除標籤
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
Task<ServiceResult> DeleteTagAsync(int id);

#endregion Tags

查詢標籤列表需要返回的模型類QueryTagForAdminDto.cs

//QueryTagForAdminDto.cs
namespace Meowv.Blog.Application.Contracts.Blog
{
    public class QueryTagForAdminDto : QueryTagDto
    {
        /// <summary>
        /// 主鍵
        /// </summary>
        public int Id { get; set; }
    }
}

新增標籤和更新標籤需要的輸入參數EditTagInput.cs,直接繼承TagDto即可。

//EditTagInput.cs
namespace Meowv.Blog.Application.Contracts.Blog.Params
{
    public class EditTagInput : TagDto
    {
    }
}

分別實現這幾個接口。

/// <summary>
/// 查詢標籤列表
/// </summary>
/// <returns></returns>
public async Task<ServiceResult<IEnumerable<QueryTagForAdminDto>>> QueryTagsForAdminAsync()
{
    var result = new ServiceResult<IEnumerable<QueryTagForAdminDto>>();

    var post_tags = await _postTagRepository.GetListAsync();

    var tags = _tagRepository.GetListAsync().Result.Select(x => new QueryTagForAdminDto
    {
        Id = x.Id,
        TagName = x.TagName,
        DisplayName = x.DisplayName,
        Count = post_tags.Count(p => p.TagId == x.Id)
    });

    result.IsSuccess(tags);
    return result;
}
/// <summary>
/// 新增標籤
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
public async Task<ServiceResult> InsertTagAsync(EditTagInput input)
{
    var result = new ServiceResult();

    var tag = ObjectMapper.Map<EditTagInput, Tag>(input);
    await _tagRepository.InsertAsync(tag);

    result.IsSuccess(ResponseText.INSERT_SUCCESS);
    return result;
}

這裏需要一條AutoMapper配置,將EditCategoryInput轉換為Tag,忽略Id字段。

CreateMap<EditTagInput, Tag>().ForMember(x => x.Id, opt => opt.Ignore());
/// <summary>
/// 更新標籤
/// </summary>
/// <param name="id"></param>
/// <param name="dto"></param>
/// <returns></returns>
public async Task<ServiceResult> UpdateTagAsync(int id, EditTagInput input)
{
    var result = new ServiceResult();

    var tag = await _tagRepository.GetAsync(id);
    tag.TagName = input.TagName;
    tag.DisplayName = input.DisplayName;

    await _tagRepository.UpdateAsync(tag);

    result.IsSuccess(ResponseText.UPDATE_SUCCESS);
    return result;
}
/// <summary>
/// 刪除標籤
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<ServiceResult> DeleteTagAsync(int id)
{
    var result = new ServiceResult();

    var tag = await _tagRepository.FindAsync(id);
    if (null == tag)
    {
        result.IsFailed(ResponseText.WHAT_NOT_EXIST.FormatWith("Id", id));
        return result;
    }

    await _tagRepository.DeleteAsync(id);
    await _postTagRepository.DeleteAsync(x => x.TagId == id);

    result.IsSuccess(ResponseText.DELETE_SUCCESS);
    return result;
}

BlogController.Admin.cs中添加接口。

#region Tags

/// <summary>
/// 查詢標籤列表
/// </summary>
/// <returns></returns>
[HttpGet]
[Authorize]
[Route("admin/tags")]
[ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
public async Task<ServiceResult<IEnumerable<QueryTagForAdminDto>>> QueryTagsForAdminAsync()
{
    return await _blogService.QueryTagsForAdminAsync();
}

/// <summary>
/// 新增標籤
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost]
[Authorize]
[Route("tag")]
[ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
public async Task<ServiceResult> InsertTagAsync([FromBody] EditTagInput input)
{
    return await _blogService.InsertTagAsync(input);
}

/// <summary>
/// 更新標籤
/// </summary>
/// <param name="id"></param>
/// <param name="input"></param>
/// <returns></returns>
[HttpPut]
[Authorize]
[Route("tag")]
[ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
public async Task<ServiceResult> UpdateTagAsync([Required] int id, [FromBody] EditTagInput input)
{
    return await _blogService.UpdateTagAsync(id, input);
}

/// <summary>
/// 刪除標籤
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpDelete]
[Authorize]
[Route("tag")]
[ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
public async Task<ServiceResult> DeleteTagAsync([Required] int id)
{
    return await _blogService.DeleteTagAsync(id);
}

#endregion Tags

友鏈

添加接口:查詢友鏈列表QueryFriendLinksForAdminAsync()、新增友鏈InsertFriendLinkAsync(...)、更新友鏈UpdateFriendLinkAsync(...)、刪除友鏈DeleteFriendLinkAsync(...)

#region FriendLinks

/// <summary>
/// 查詢友鏈列表
/// </summary>
/// <returns></returns>
Task<ServiceResult<IEnumerable<QueryFriendLinkForAdminDto>>> QueryFriendLinksForAdminAsync();

/// <summary>
/// 新增友鏈
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<ServiceResult> InsertFriendLinkAsync(EditFriendLinkInput input);

/// <summary>
/// 更新友鏈
/// </summary>
/// <param name="id"></param>
/// <param name="input"></param>
/// <returns></returns>
Task<ServiceResult> UpdateFriendLinkAsync(int id, EditFriendLinkInput input);

/// <summary>
/// 刪除友鏈
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
Task<ServiceResult> DeleteFriendLinkAsync(int id);

#endregion FriendLinks

查詢友鏈列表需要返回的模型類QueryFriendLinkForAdminDto.cs

//QueryFriendLinkForAdminDto.cs
namespace Meowv.Blog.Application.Contracts.Blog
{
    public class QueryFriendLinkForAdminDto : FriendLinkDto
    {
        /// <summary>
        /// 主鍵
        /// </summary>
        public int Id { get; set; }
    }
}

新增友鏈和更新友鏈需要的輸入參數EditFriendLinkInput.cs,直接繼承FriendLinkDto即可。

//EditFriendLinkInput .cs
namespace Meowv.Blog.Application.Contracts.Blog.Params
{
    public class EditFriendLinkInput : FriendLinkDto
    {
    }
}

分別實現這幾個接口。

/// <summary>
/// 查詢友鏈列表
/// </summary>
/// <returns></returns>
public async Task<ServiceResult<IEnumerable<QueryFriendLinkForAdminDto>>> QueryFriendLinksForAdminAsync()
{
    var result = new ServiceResult<IEnumerable<QueryFriendLinkForAdminDto>>();

    var friendLinks = await _friendLinksRepository.GetListAsync();

    var dto = ObjectMapper.Map<List<FriendLink>, IEnumerable<QueryFriendLinkForAdminDto>>(friendLinks);

    result.IsSuccess(dto);
    return result;
}
/// <summary>
/// 新增友鏈
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<ServiceResult> InsertFriendLinkAsync(EditFriendLinkInput input)
{
    var result = new ServiceResult();

    var friendLink = ObjectMapper.Map<EditFriendLinkInput, FriendLink>(input);
    await _friendLinksRepository.InsertAsync(friendLink);

    // 執行清除緩存操作
    await _blogCacheService.RemoveAsync(CachePrefix.Blog_FriendLink);

    result.IsSuccess(ResponseText.INSERT_SUCCESS);
    return result;
}
/// <summary>
/// 更新友鏈
/// </summary>
/// <param name="id"></param>
/// <param name="input"></param>
/// <returns></returns>
public async Task<ServiceResult> UpdateFriendLinkAsync(int id, EditFriendLinkInput input)
{
    var result = new ServiceResult();

    var friendLink = await _friendLinksRepository.GetAsync(id);
    friendLink.Title = input.Title;
    friendLink.LinkUrl = input.LinkUrl;

    await _friendLinksRepository.UpdateAsync(friendLink);

    // 執行清除緩存操作
    await _blogCacheService.RemoveAsync(CachePrefix.Blog_FriendLink);

    result.IsSuccess(ResponseText.UPDATE_SUCCESS);
    return result;
}
/// <summary>
/// 刪除友鏈
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<ServiceResult> DeleteFriendLinkAsync(int id)
{
    var result = new ServiceResult();

    var friendLink = await _friendLinksRepository.FindAsync(id);
    if (null == friendLink)
    {
        result.IsFailed(ResponseText.WHAT_NOT_EXIST.FormatWith("Id", id));
        return result;
    }

    await _friendLinksRepository.DeleteAsync(id);

    // 執行清除緩存操作
    await _blogCacheService.RemoveAsync(CachePrefix.Blog_FriendLink);

    result.IsSuccess(ResponseText.DELETE_SUCCESS);
    return result;
}

其中查詢友鏈列表和新增友鏈中有兩條AutoMapper配置。

CreateMap<FriendLink, QueryFriendLinkForAdminDto>();

CreateMap<EditFriendLinkInput, FriendLink>().ForMember(x => x.Id, opt => opt.Ignore());

BlogController.Admin.cs中添加接口。

#region FriendLinks

/// <summary>
/// 查詢友鏈列表
/// </summary>
/// <returns></returns>
[HttpGet]
[Authorize]
[Route("admin/friendlinks")]
[ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
public async Task<ServiceResult<IEnumerable<QueryFriendLinkForAdminDto>>> QueryFriendLinksForAdminAsync()
{
    return await _blogService.QueryFriendLinksForAdminAsync();
}

/// <summary>
/// 新增友鏈
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost]
[Authorize]
[Route("friendlink")]
[ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
public async Task<ServiceResult> InsertFriendLinkAsync([FromBody] EditFriendLinkInput input)
{
    return await _blogService.InsertFriendLinkAsync(input);
}

/// <summary>
/// 更新友鏈
/// </summary>
/// <param name="id"></param>
/// <param name="input"></param>
/// <returns></returns>
[HttpPut]
[Authorize]
[Route("friendlink")]
[ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
public async Task<ServiceResult> UpdateFriendLinkAsync([Required] int id, [FromBody] EditFriendLinkInput input)
{
    return await _blogService.UpdateFriendLinkAsync(id, input);
}

/// <summary>
/// 刪除友鏈
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpDelete]
[Authorize]
[Route("friendlink")]
[ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
public async Task<ServiceResult> DeleteFriendLinkAsync([Required] int id)
{
    return await _blogService.DeleteFriendLinkAsync(id);
}

#endregion

Next

截止本篇,基於 abp vNext 和 .NET Core 開發博客項目 系列的後台API部分便全部開發完成了。

本博客項目系列是我一邊寫代碼一邊記錄后的成果,並不是開發完成后再拿出來寫的,涉及到東西也不是很多,對於新手入門來說應該是夠了的,如果你從中有所收穫請多多轉發分享。

在此,希望大家可以關注一下我的微信公眾號:『阿星Plus』,文章將會首發在公眾號中。

現在有了API,大家可以選擇自己熟悉的方式去開發前端界面,比如目前我博客的線上版本就是用的 ASP.NET Core Web ,感興趣的可以去 release 分支查看。

關於前端部分,看到有人呼籲vue,說實話前端技術不是很厲害,本職主要是後端開發,可能達不到預期效果。

所以我準備入坑 Blazor ,接下來就現學現賣吧,一起學習一起做項目一起進步,加油

開源地址:https://github.com/Meowv/Blog/tree/blog_tutorial

搭配下方課程學習更佳 ↓ ↓ ↓

http://gk.link/a/10iQ7

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

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

手把手帶你入門numpy,從此數據處理不再慌【四】

本文始發於個人公眾號:TechFlow,原創不易,求個關注

今天是numpy專題的第四篇文章,numpy中的數組重塑與三元表達式。

首先我們來看數組重塑,所謂的重塑本質上就是改變數組的shape。在保證數組當中所有元素不變的前提下,變更數組形狀的操作。比如常用的操作主要有兩個,一個是轉置,另外一個是reshape。

轉置與reshape

轉置操作很簡單,它對應線性代數當中的轉置矩陣這個概念,也就是說它的功能就是將一個矩陣進行轉置

轉置矩陣的定義是將一個矩陣的橫行寫為轉置矩陣的縱列,把縱列寫成轉置矩陣的橫行。這個定義的是二維的矩陣,本質上來說,轉置操作其實是將一個矩陣沿着矩陣的大對角線進行翻轉。翻轉之後,顯然這個矩陣的各個維度都會發生變化。

其中二維的矩陣最直觀,一個4 x 3的矩陣,轉置之後得到的是3 x 4的矩陣。如果維度更多呢?如果是3 x 2 x 4的矩陣轉置之後會得到什麼?

很簡單,得到的會是4 x 2 x 3的矩陣。我們都知道,如果我們把一個矩陣各個維度的大小寫在一起,會得到一個元組(tuple),這個元組稱為矩陣的shape,我實在是不知道該怎麼翻譯這個單詞,但是我覺得叫做形狀不太妥當,所以就保留了英文原文。轉置之後,矩陣的shape會整個翻轉。比如(3, 2, 4)會變成(4, 2, 3)。

我們可以來看一個例子,會更加的直觀。首先我們先看最簡單的二維矩陣:

這是隨機出來的一個3 x 4的二維矩陣,在numpy當中,有兩種方式獲取一個矩陣或者是數組的轉置。第一種方式是通過在數組的變量名之後加上.T操作符,第二種方式是調用numpy中的transpose函數,這兩種方式是一樣的。我個人比較傾向於前者,寫起來比較簡單。

我們可以看到轉置之後新的矩陣的第一列其實是原矩陣的第一行,第一行是原矩陣的第一列。可以看成是原矩陣按照從左上角到右下角的一條無形的線翻轉之後的結果。

理解了轉置之後,我們再來看reshape操作。其實我們從這個單詞上也能大概猜到它的意思,reshape也就是再次shape的意思,本意是根據我們想要的shape重新組裝矩陣當中的元素

我們來看一個例子吧,首先,我們通過arange方法來獲取一個一維的數組:

因為是1維的,所以我們去看它的shape也只有一維。假設我們不喜歡這樣的一維數組,而想把它變成3 x 4或者是6 x 2的格式,這時候使用reshape就會很方便。

本質上來說reshape操作其實就是按照順序從矩陣當中獲取元素,然後按照我們制定的shape填充出一個新的矩陣的操作。這個應該不難理解, 它也是非常常用的重塑操作,通過reshape和轉置,我們可以很方便地操作矩陣的大小,根據我們的需要作出改變。

三元表達式

在許多編程語言當中我們經常會用到三元表達式,三元表達式其實本質就是if-else語句,只是我們用特殊的方法將它簡寫。

比如說在C++當中,我們可以把if condition A else B簡寫成:condition ? A : B。Python同樣支持三元表達式,不過對C++的三元表達式做了一些改動,在Python當中三元表達式寫成:A if condition else B。相對來說更加直觀一些,我們經常會在數組初始化的時候用到三元表達式。

比如,我們可能會這樣生成一個數組:

arr = [1 if condition else 0 for _ in range(10)]

我們通過條件來判斷了每一位是1還是0來生成了一個數組,簡化了代碼。在numpy當中同樣繼承了這個用法,我們一樣可以使用三元表達式,不過numpy將它封裝進了where函數當中,我們是通過調用一個方法來實現三元表達式的功能。我們來看下具體的用法,假設我們有兩個數組:

我們還有一個bool型的數組c,我們希望根據c數組選擇從a數組或者是b數組當中獲取數據。我們可以使用where寫成這樣:

在這個例子當中,c數組中的1和0分別表示True和False。當我們調用np.where的時候,numpy會自動根據c數組當中的值去選擇從a數組還是b數組當中獲取數據。相當於我們執行了這麼一段代碼:

[x if c else y for c, x, y in zip(c, a, b)]

雖然兩者的運行結果是一樣的,但是顯然使用循環的方法計算耗時更長,而使用numpy的向量做法運算速度更快。除此之外,numpy的where方法還支持高維的數組,但是循環的方法不行。並且where還有一些更高級的用法,比如說我們傳入的第二個和第三個參數,可以不是數組而是一個標量。比如我們可以指定當c中的元素是True的時候填入1,否則填入-1:

甚至我們還可以將標量和向量結合起來使用:

並且這裏的數組c也可以替換成邏輯運算:

總結

今天的文章主要介紹了Numpy當中的reshape、轉置以及where的用法,這些也是numpy的基礎用法,尤其是轉置、reshape,幾乎是處理數據必用的方法。所以想要從事Python機器學習或者是人工智能的小夥伴,numpy的這些用法是一定要會的。

本文當中介紹的只是numpy的一些固定套路,但其實numpy很多的用法是可以組合的,一些看似平淡無奇的用法組合在一起之後會有神奇的效果。這一點光看書或者是資料是很難窮盡的,所以如果你已經學會了這些api的基本使用,接下來最應該做的是去讀一些大牛的源碼,看看大牛們是如何運用這些工具的,相信一定還會有新的收貨。

文章就到這裏,如果喜歡本文,可以的話,請點個關注

本文使用 mdnice 排版

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

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價