[實作筆記] 自定義 Hexo Helper

前情提要

我的 Blog 目前是使用 Hexo 搭建的,
使用的主題是 landscape
隨著時光推移, Hexo 漸漸從人們的目光淡出,
主功能、文件、翻譯或是外掛、主題等…, 更新速度變慢很多

我想改寫主題當中的某個輔助函數(Helper), 稍微記錄一下作法。

實作

首先是檔案位置,一個是在根目錄底下建立 scripts 資料夾,
或者你正在使用主題(Theme)底下。
以我的例子來說,我會放在 themes/landscape/scripts 底下

參考下面的圖示

1
2
3
4
5
6
7
8
├── scripts
│ ├── index.js
│ └── your_helper_here.js
├── themes
│ └── the_theme_you_are_using
│ └── scripts
│ ├── index.js
│ └── your_helper_here.js

下一步,你需要建立 index.js,並在其入註冊你的函數,

1
2
3
4
//hexo.extend.helper.register(name, function)
hexo.extend.helper.register("helper_name", function () {
return "<div>your helper function </div>";
});

通常 helper 不會是一個簡單的函數,且會有 UI 相關的程式,
有為了方便管理,我會拆檔,再用 require 來引入函數

1
hexo.extend.helper.register("helper_name", require("./helper_file_name"));

最後,在 *.ejs 檔案中使用 helper

1
<%- helper_name() %>

參考

(fin)

[實作筆記] Dotnet Core 實作隨筆

Web/Api 專案統一錯誤處理

首先建立一個新的類別 ErrorHandlingFilter
繼承 ExceptionFilterAttribute 並複寫 OnException 函數

1
2
3
4
5
6
7
public class ErrorHandlingFilter : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
//do your logical here
}
}

在 Program.cs 中注入

1
builder.Services.AddControllers(options => { options.Filters.Add(new ErrorHandlingFilter()); });

專案更改 Root Namespace

預設會使用專案名稱作為整個專案的 Root Namespace,
但是我們可以在 csproj 檔中設定 RootNamespace 讓它與專案名稱有所差異

1
2
3
4
5
6
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>MyNameSpace</RootNamespace>
</PropertyGroup>
</Project>

Nuget

add nuget registry

1
dotnet nuget add source <registry url> -n SourceName -u UserName -p Password --store-password-in-clear-text

pack

1
dotnet pack -o <output folder> -c NUGET --no-dependencies --force  -p:PackageVersion=<version>

push

1
dotnet nuget push -s SourceName <Packed Version>

建立共用的 HttpClient

在 Program.cs 中註冊

1
2
3
4
5
6
7
builder.Services.AddHttpClient("ThirdService", (provider, client) =>
{
var o = provider.GetRequiredService<IOptions<ThirdServiceOptions>>();
client.BaseAddress = new Uri($"{o.Value.Url}/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", o.Value.Token);
});

再透過 HttpClientFactory 具名取用

1
2
3
4
public MyService(IHttpClientFactory httpClientFactory)
{
HttpClient _httpClient = httpClientFactory.CreateClient("ThirdService");
}

最小化 Docker Image

這一種被稱為 Multiple Stage Builds 的 Docker 技巧,
主要的概念是將建置(Build)與部署(Deploy)的步驟分開,
最終只部署出營運所需的映象檔(Image)。

比如說下面的 Docker File 就是將 dotnet 專案作了兩步驟的處理

此外,如果我們更換適當的 Image,
比如說,在 Create Runtime Image Step,
mcr.microsoft.com/dotnet/aspnet:6.0 改為 mcr.microsoft.com/dotnet/aspnet:6.0-alpine
映象檔的 Size 將減少 46.6% (216.11 MB → 115.4 MB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Build Step
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /source
# copy everything else and build app
COPY /src/WebAPI/. ./WebApi/
WORKDIR /source/WebApi
RUN dotnet restore 'WebApi.csproj'
RUN dotnet build 'WebApi.csproj' -c release -o /app --no-restore
RUN dotnet publish 'WebApi.csproj' -c release -o /app --no-restore

# Create Runtime Image Step
FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine
ENV ASPNETCORE_URLS=http://+:5110
WORKDIR /app
COPY --from=build /app ./
EXPOSE 5110
ENTRYPOINT ["dotnet", "WebApi.dll"]

參考

(fin)

[實作筆記] ASP.Net Core Config 的隨筆

提要

  • DI Configuration
  • 組態的層次
  • Options Pattern

DI Configuration

dotnet webapi 專案預設會注入 IConfiguration ,
在專案中的程式建構子放入 IConfiguration 就可以直接取得組態設定

1
2
3
4
5
6
7
8
9
10
11
12
private readonly IConfiguration _config;

public WeatherForecastController(IConfiguration config)
{
_config = config;
}

[HttpGet(Name = "GetWeatherForecast")]
public string Get()
{
return _config["ASPNETCORE_ENVIRONMENT"]!;
}

你可以將組態設定在 appsettings.json 之中
或是透過環境變數,使用環境變數有幾種作法

  • 設定在機器上
  • 設定在 Cloud Service 所提供的組態管理工具之中
  • 使用 launchSettings.json

在開發環境我會使用 launchSettings 好處是可以不需要真的在開發機的環境中設定環境變數,
對同時擁有多個專案的開發者而言,可以有效隔離不同的專案變數,避免彼此影響污染

組態的層次

為了方便管理,我們可以透過組態的層次來加以分門別類。
比如說:

1
2
3
4
5
6
7
8
"Level": {
"First": {
"Key": "1st Value"
},
"Second": {
"Key": "2nd Value"
}
},

一般的 JSON 或 XML 要作到這樣的層次結構都相當的簡單,
而要取用組態的方式如下:

1
var key = _config["Level:First:Key"];

不過我們要注意環境變數並無法使用:字符,也沒有 JSON 或是 XML 所能提供的結構,
這裡要注意可以用 __(雙底線(Double underscore))來建立層次。

1
2
$Level__First__Key
$Level__Second__Key

Options Pattern

Options Pattern 可以提供一種更物件導向的組態配置解決方案,
你只需要兩個步驟,即可實作 Options Pattern

  1. 建立 Option 物件
    我在這裡提供二種不同的方法,後面會再說明

    1
    2
    3
    4
    5
    6
    //方法一 Dictionary
    public class LevelOptions
    {
    public const string Level = "Level";

    public Dictionary<string,string> First { get; set; }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //方法二 Object
    public class LevelOption
    {
    public const string Level = "Level";

    public FirstOptions First { get; set; }
    }

    public class FirstOptions
    {
    public string Key { get; set; } = string.Empty;
    }
  2. 註冊 Option 物件

1
builder.Services.Configure<LevelOptions>(LevelOption.Level)

最後是讀取組態,在方法一(使用 Dictionary)的方法讀取

1
var key = _options.Value.First["Key"];

或是方法二(使用 Object)的方法讀取

1
var key = _options.Value.First.Key;

兩種方法都可以,物件化的方法需要建立多個物件,
但是在取用組態的過程就不會有 hard coding string;
反之 Dictionary 的方法可以少寫很多物件。

對我而言我比較偏向使用 Object 的方法去處理,只要適當的分類,
物件多並不是問題,而且通常一次不會增加太多的組態,可以迭代增量。

順代一提,如果組態檔層次太多,會是另一個困擾 ? 我的建議是,如果組態的深度到達 3 時,
需要考慮這是不是一個壞味道,是不是過度優化了 ?
實務上來說,我會避免建立超過 3 層,也很難會有一定要超過 3 層的情境,
如果你有什麼情境上需要超過 3 層,可以留言給我,我們可以一起討論。

在 Program.cs 使用

如果在 Program.cs 的注入階段,如果要取得組態,
可以用
(new ConfigurationManager()).GetSection("First")["Key"]
或是 Environment.GetEnvironmentVariable("Level__First__Key"); 等方法取得組態。
但是我更推薦 ServiceProvider 的 GetRequiredService 取得 Options

1
2
3
4
5
6
7
8
9
10
//注入在前
builder.Services.Configure<LevelOption>(builder.Configuration.GetSection(LevelOption.Level));
builder.Services.AddHttpClient("OtherService", (provider, client) =>
{
//透過 provider 取得 config
var o = provider.GetRequiredService<IOptions<LevelOptions>>();
client.BaseAddress = new Uri($"{o.Value.Url}/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", o.Value.Token);
});

這樣就可以用一致的方法取得組態了。

參考

(fin)

[實作筆記] Hexo 更新 Google Analytics 4

前情提要

幾天前收到來自 Google Analytics 的這樣一封信

1
2
3
4
5
Google Analytics (分析) 4 即將取代通用 Analytics (分析)

我們即將以新一代的成效評估解決方案 Google Analytics (分析) 4,
取代通用 Analytics (分析)。從 2023 年 7 月 1 日起,通用 Analytics (分析) 資源將停止處理新的命中資料;
如果通用 Analytics (分析) 仍是您常用的工具,建議您採行相應措施,準備好改用 Google Analytics (分析) 4

實作步驟

參考以前我寫的這篇–如何讓 Google Analytics 追踪你的 Hexo Blog
我們知道在 hexo 的佈景主題 landscape 有提供舊版 GA 的設定,
我們只需要修改 theme/layout/_partial/google-analytics.ejs 即可
程式參考如下

1
2
3
4
5
6
7
8
9
10
11
12
13
<% if (theme.google_analytics){ %>
<!-- Google Analytics -->
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=<%= theme.google_analytics %>"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());

gtag('config', '<%= theme.google_analytics %>');
</script>
<!-- End Google Analytics -->
<% } %>

由於通用 GA 在 2023 年後就不再支援,所以我就不特地處理相容,直接切換到 GA4

參考

(fin)

[實作筆記] 簡單的方法 Mock TypeScript Interface

前情提要

我目前在實作一個 React 的專案,並結合 Storybook 來實現 CDD(component driven development),
我很喜歡 CDD 的概念,這裡解決 TDD 對 View 難以趨動開發的痛點,
但是又維持了 Unit Test 的基本精神,進一步結合 Chromatic 可以提供視覺化的檢核頁面,
作為與 PO/PM/UX/UI/DESIGNER 之間的溝通工具非常好用。

問題

我作了一個簡單的 Header 組件,
這通常是網站或 APP 上方的區塊,通常會提供一些額外的資訊或功能,
例如: 登入/登出,而這裡我使用 Firebase 作為實作登入/登出的功能,
作為主要的邏輯判斷,firebase 的 Auth 物件會被傳入

1
2
3
Header = (props: { sighOut: () => void; auth?: Auth }) => {
//...
};

這裡我們先不討論 Auth 的邏輯是否應該與 Header 相依(嗯,這裡看起來是個壞味道),
而是在我們的 Storybook 撰寫測試案例時,應該如何 mock Auth 物件。
我的程式都是用 TypeScript 與 tsx 寫的,
所以如果不符合型別,IDE 會直接報錯,程式跟本無法執行

1
2
3
4
5
6
7
8
const Template: ComponentStory<typeof Header> = () => (
<Header
sighOut={() => {
alert("mocked Signout");
}}
auth={mockAuth}
/>
);

試過的方法

  • jest Mock
  • ts-mockito
  • ts-auto-mock

以上的方法有的成功,有的失敗,最大的問題是在配置與撰寫其實很麻煩,
甚至會有同事說為什麼要用 TypeScript 自找麻煩呢 ?

逐一 mock 所有 interface 的屬性,對於一個像 Auth 這樣複雜的介面實作上相當麻煩,
而且大多 mock 的屬性都用不到

我的想法是,應該是我對 TypeScript 與其生態圈不夠了解,寫起來才會覺得綁手綁腳的,
不能推缷給程式語言,同時 TypeScript 強型別的好處,其實也幫了我不少。
仔細研究後,我找到一個簡單方法如下:

解法

不需要 mock Library , 不需要麻煩的設定或是第三方套件,
最簡單的寫法 as 就可以將物件 mock 成為你想要的 Interface

1
2
3
const mockAuth = {
currentUser: { email: "[email protected]" },
} as Auth;

這樣的寫法除了可以只 mock 必要的屬性外,
如果你物件中包含了不屬於介面應有的屬性,
TypeScript 將會提出警告,非常好用。

(fin)

[實作筆記] 升級現有基於 TypeScript 的 React 17 專案到 React 18

前情提要

升級 React 在官方的文件之中,似乎是一件極為簡單的事,
但是如果你的專案是基於 TypeScript 開發,
在前幾周應該無法順利升級,主要的原因是相對應的 @types 套件並未同時更新,
幸運的是,現在看到文章的你,已經可以順利更新了,請參考以下的步驟。

記錄

安裝 react、react-dom、@types/react、@types/react-dom

1
npm i react@18 react-dom@18 @types/react@18 @types/react-dom@18

更改 ReactDOM.render 的寫法

原本的寫法

1
2
3
4
import React from "react";
import ReactDOM from "react-dom";

ReactDOM.render(<App />, document.getElementById("root"));

後來的寫法

1
2
3
4
5
6
import React from "react";
import ReactDOM from "react-dom/client";

const ele = document.getElementById("root") as Element;
const root = ReactDOM.createRoot(ele);
root.render(<App />);

特別注意要處理 Element 的型別

參考

(fin)

[踩雷筆記] Brave 瀏覽器與本機開發 http

前情提要

Brave 是一款主打安全、注重隱私的瀏覽器,雖然目前仍相當小眾,
但在開發者中有一定使用者。

https 通訊協定現在已經是一個 Web 服務、網站、api 的基本安全配備,但是對於開發者來說,
在本地開發常常僅使用 http 通訊協定就足以作功能性的驗証。

情境

使用 dotnet core 建立了一個 web api 的服務,預設的啟動站台 end point 為 http://localhost:5000
而 Brave 作為預設瀏覽器開啟後,會看到 403 的錯誤頁面。

1
2
3
找不到 localhost
網頁找不到此網址的網頁:http://localhost:5000/
HTTP ERROR 404

解決方法

正常的思路,應該是讓本地開發環境建立起 https 的連線,這裡又會有相關的憑証問題要處理,
應該有蠻多不同的作法,改天我再寫一篇相關的介紹。

短解很簡單,換個瀏覽器就可以了,
如果堅持要用 Brave ,可以至設定 > 防護 > 關閉「升級連線至 HTTPS」的開關即可。
順代一提,當我使用 hexo、React 等工具建立靜態網站時不會有這個問題。

參考

(fin)

[實作筆記] 靜態網站部署整合 GCP (二) --- Load Balancing & Cloud Storage

前請提要

去年有記錄一篇有關 GCP 部署靜態網站時遇到情境與問題,
不過比較偏像流水帳,不同的部署策略都在同一篇文章,另外有一些問題也得到解決,
除了更新原有文章外,這篇的目的是為了將 Load Balancing 與 Cloud Storage 抽出,
並加以潤飾。

概要

  • 在 Cloud Storage 建立一個 Bucket
  • 在 Network Service 建立一個 Load Balancing
  • 設定 Load Balancing 的 Backend 綁定到 Bucket
  • 作為網站,需要允許所有人讀取 Bucket 的內容

必要條件

GCP 平台的帳戶與足夠的權限,並且已建立專案

在 Cloud Storage 建立一個 Bucket

第一步前往Cloud Storage
點擊 +CREATE BUCKET,
在 Choose where to store your data 的區塊,
Location type 有三種

  • multi-region
  • dual-region
  • region

這是地理位置與高可用性的相關設定,越後面的設定成本越便宜,但是可用性也越低。
即使如此 google 仍保証了 SLA: 99.95% 的高可用性。

接下來是 Choose a default storage class for your data 的區塊,有

  • Standard
  • Nearline
  • Coldline
  • Archive

等四種不同的設定,
與檔案的使用頻率有關,對於網站來說建議使用 Standard 。
收費可以參考下表

Standard Nearline Coldline Archive
Storage(per GB-Month) $0.026 $0.01 $0.007 $0.004
retrieval(per GB-Month) Free $0.01 $0.02 $0.05
Class A Operations(per 1000ops) $0.005 $0.01 $0.01 $0.05
Class B Operations(per 1000 ops) $0.0004 $0.001 $0.005 $0.05
SLA 99.95% 99.9% 99.9% 99.9%

接下來是 Choose how to control access to objects 的設定,
不要勾選 Enforce public access prevention on this bucket,
Access control 選擇 Uniform , 這裡的設定是為了避免從 internet 存取 bucket 的資料,
但是我們的目的是放置靜態網站的資料,所以不需設定。

再來是 Choose how to protect object data
這是保護資料的策略,有版本(versioning)與備份(retention)兩種策略,
我們不需要所以選擇 None

按下 Create 以建立 Bucket,
接下來為了讓 web 存取我們選擇 more action(3 個點的 Icon ) > Edit Access
New Principals > 選擇 allUsers > Storage Object Viewer.
接下來可以上傳你的靜態網站的資源了,這裡我們多作一個設定,通常我們要指定網站的首頁為何,
約定成俗是 index.html,一樣 more action(3 個點的 Icon ) > edit website configuration
將 Index (main) page suffix 設定為 index.html(記得 bucket 裡要有這個檔)

Load Balancing 相關設定

接下來設定 Load Balancing,
這只需要設定一次,在目前的實作未涉及 VPC、CDN、Path Rules 等相關議題,
實務上需要的話,請再加上去考量。

  1. GCP > Network Service > Load Balancing
  2. Create Load Balancer
  3. Backend configuration > Create A Backend Bucket
    • 自已取一個 Backend Bucket name
    • Cloud Storage Bucket > Browse > 選取之前所建立的 Bucket
    • 不勾選 Enable Cloud CDN, 相關的設定與應用程式的應用有關, 較為複雜之後再進行處理
  4. Host and path rules > Simple host and path rule
  5. Frontend IP and PORT > Add Frontend IP And Port
    • Protocol > HTTP (正常應使用 HTTPS, 這次為了求快而未作相關設定)
    • Network Service Tier > Premium

可以參考 GCP 的教學與我們實作上的細微差異

  • Create a bucket. → 手動建立只需要處理一次
  • Upload and share your site’s files. → CI 執行
  • Set up a load balancer and SSL certificate. → 手動建立只需要處理一次
  • Connect your load balancer to your bucket. → 手動建立只需要處理一次
  • Point your domain to your load balancer using an A record. → 未處理
  • Test the website.

CI/CD 的相關設定 (已過時)

參考以下部份的 .gitlab-ci.yml 檔
非常簡單,只需要 gsutil rsync -R 將前一個 job 建置的檔案推到 Cloud Storage 即可

1
2
3
4
5
6
7
8
9
10
deploy-job: # This job runs in the deploy stage.
stage: deploy # It only runs when *both* jobs in the test stage complete successfully.
image: google/cloud-sdk
needs:
- job: build-job
artifacts: true
script:
# - gcloud auth list # Show the ACTIVE ACCOUNT *
- gsutil rsync -R build gs://your_bucket_name
- echo "Application successfully deployed."

其它(已過時)

這裡仍有一個未知的設定,gsutil 命令會需要權限才能對 Cloud Storage 寫入,
如何讓 Gitlab-Runner 底下的 Container 擁有指定的 GCP 權限呢 ?
試著下 gcloud auth list 會發現確實有一個可工作的 Account 所以一定有相關的設定要處理,
因為沒有實作到,故不作記錄,但未來再有機會不要忘了這一段的工程。

大部份的工作只需要設定一次,未來只需要 CI 將新版的靜態網站上傳到 Cloud Storage 網站就會更新。

參考

(fin)

[生活筆記] 2022 貓劇感想

第一次看到貓劇是在高中音樂課上,老師所播放了 DVD 的片段,
具體的內容也記不清了——當時的老師常常放影片上課,
比如說用廚具演奏的樂團、貓劇等,——
當初只覺得特別,甚至有點詭異,並沒有什麼感觸。

大學畢業後,等待當兵的時間,不知道在什麼時間點,我再次接觸了貓劇。
這次就陷入狂熱(FEVER),一開始是在網路上找片段看,後來也買了 DVD,
整個部片應該看了不下 30 次,甚至是可以聽著音樂睡覺。
我最喜歡的是看每隻貓咪的互動與細節。從那個時候開始,我就希望可以前往看貓劇的演出。

我已經不確定當時有沒有確認過停演資訊,但是我知道倫敦是貓的首演場地,
所以我心中一直想去倫敦參觀(更多的部份是阿森納)
而目前知道倫敦已經沒有演出了,順代一提,歌詞有偷婊熱刺的部份 XDD

1
2
3
4
Grizabella, The Glamour Cat

She haunted many a low resort
Near the grimy road of Tottenham Court

2018 年我開始學習 Lindy Dance 與聽爵士樂,2020 在課堂上認識了一些朋友有在看音樂劇,
聊天過後,再次激起心中的火花。2021 雖然歷經疫情,但是台灣國內的狀況一直相對穩定,
所以開售當天馬上買了票,在朋友的推薦下,大手筆直接買了衛武營 5800 的票,
結果疫情爆發,就拖到了 2022 年。
雖然票價不便宜,但是這次的體驗真的值得。

好的初體驗,第一次看音樂劇就是史上有名的劇,位置在前排中間的位置;
先說說硬體設施,前排是看不到字幕的,不過貓不是一部需要字幕的戲劇,
特別是我以前看過無數次的前提下,除了 Growltiger 的部份沒有看過外,
其它的部份我都相當的熟悉,加上文本本身是詩集的原因,所以其實並不需要太過依賴字幕,
如果英聽夠好的話更是不需要。

第一排的好處是可以看到細節,這點非常適合貓劇,比起劇情與主要出場貓咪的表演來說,
更重要的是散落在背景的貓咪表情、肢體其實正在無時無刻演出,有時他們的互動會讓我誤以為是演員在聊天,
但是隨著演員的動作,才會發現即時聚光燈不在他們身上,他們也仍然在演出,只要在舞台上就是戲的一部份。
而這個距離也可以看到布景的細節
聲音部份是真的可以聽到人聲,不是透過音響的聲音,可以感覺到人聲的媚力,
這次我的位置聽 Cassandra 聲音真得超清楚(聽說麥克風是藏在他們的頭上,
但是他們會互相撥頭時也不會有聲音,所以收音對我來說還是個迷)

  • 會被演員當 Target Point Out ,被點到時真得很有觸電感
  • 終場 Rum Tum Tugger 會超近

但是也是有缺點的部份,首先是視覺的角度較低,在

  • 衛武營第一排有時會被燈箱(或是風扇、提詞機)或貓檔住
  • 當貓咪圍成圈的時候也會被檔住
  • 側面進場的貓咪要轉頭才知道,沒有機會互動

下次看建議的位子是可以平/俯視舞台的地方,靠走道有機會互動

這次補上更多細節是 DVD 沒有的

  • 三大母貓的舞蹈很多也很好看(dvd 看不到完整的)
    • Demeter >> Munkustrap
    • Bombalurina >> Rum Tum Tugger
    • Jellylorum >> Gus(Growltiger >> Griddlebone)
  • JennyAnyDots 有一大段的 TAP 表演(有點像火焰之舞)在 DVD 是刪減版
  • 群舞時有很多三層次的編排在 DVD 看不出來,太近也會看不清楚
  • Mungojerrie And Rumpelteazer 的排舞有點像馬戲團的表演,不太確定舞蹈風格
  • Growltiger >> Griddlebone 的表演像義大利歌劇
  • Tantomile & Coricopat 通常會率先作出反應
  • Victoria 在 Old Deuteronomy 歌詞中 Victoria’s accession 被提到時會被 Munkustrap Cue 一下這點很可愛
    • 常在 C 位
    • solo 的部份我覺得更像體操或芭蕾
    • 舞會 spotlight 的部份搭配的是 Plato/George/Admetus(名字不一定)
  • Jemima 才是第一個用唱歌的方式回應 Grizabella 的小貓,一直記錯成 Victoria (Victoria 是用肢體接觸),初演是 Sarah Brightman 飾演的角色,這次有一段用中文演唱(但是我其實希望他能唱原文就好…中文讓人出戲)
  • Mistoffelees 是芭蕾的表演,特別是連續轉圈(The Conjuring Turn)
  • 我以為演員會讓我認不出來,但是化裝真的很厲害,就像是有臉譜一樣,跟 DVD 版本沒有什麼差別
  • Grizabella 被趕走的時候讓我想到取消文化與 Lindy Dancer Max 的事件
  • 2019 電影版是邪典加悲劇,我反而希望可以買到 DVD 版本的數位檔
  • 2022 年在台灣有幫 Old Deuteronomy 脫口罩(好像是 Alonzo), 有一種現實的可愛無奈

參考

-https://leticiaschic.com/cats-musical-actor-part1/

(fin)

[實作筆記] SonarCloud with .NET 6.0.x

前情提要

承上篇,在升級了 .NET 6.0.x 後發現,
SonarCloud 的測試含概率不知何時變成 0 ,
相對應的 Code Smell 也沒有作檢查, 加上原本的 github/workflow 並沒特別分門別類,
所以真正的錯誤原因已經不可考, 修復 SonarCloud 的過程中特別撰文記錄.

實作

第一件事是在升級 .NET 6.0 後, 試著在本地環境上執行檢查並上傳到 SonarCloud.

前置條件

本機的執行步驟

  • dotnet sonarscanner begin

    1
    2
    3
    4
    5
    6
    7
    dotnet sonarscanner begin \
    /o:"marsen-github" \
    /k:"Marsen.NetCore.Dojo" \
    /n:"Marsen.NetCore.Dojo" \
    /d:sonar.host.url="https://sonarcloud.io" \
    /d:sonar.cs.opencover.reportsPaths="./test/_/TestResults/_/coverage.opencover.xml" \
    /d:sonar.login="****"

    參數說明
    /o: Organization Key
    /k: Project Key
    /n: Project Name
    /d:sonar.host.url: sonar server url
    /d:sonar.cs.opencover.reportsPaths: OpenCover coverage report
    /d:sonar.login: 登入 sonar server 的 token

  • dotnet build

    1
    dotnet build Marsen.NetCore.Dojo.sln
  • dotnet test

    1
    2
    3
    dotnet test Marsen.NetCore.Dojo.sln \
    --logger trx --collect:"XPlat Code Coverage" \
    -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
  • dotnet sonarscanner end

    1
    dotnet-sonarscanner end /d:sonar.login="****"

Github Workflow 的設定

目前並沒有官方的解決方案 ,
我們使用 SonarScanner for .NET 來執行 workflow ,
這個專案十分簡潔,可以快速看一下內容,了解到怎麼實作一個 workflow ,
因為非官方解決方案,所以我 fork 這個方案避免意外發生.

而在 workflow 中設定的也是使用 fork 的方案
設定參考如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- name: SonarCloud Scan
uses: marsen/[email protected]
with:
# The key of the SonarQube project
sonarProjectKey: Marsen.NetCore.Dojo
# The name of the SonarQube project
sonarProjectName: Marsen.NetCore.Dojo
# The name of the SonarQube Organization
sonarOrganization: marsen-github
# Optional extra command arguments the the SonarScanner 'begin' command
sonarBeginArguments: /d:sonar.cs.opencover.reportsPaths="./test/*/TestResults/*/coverage.opencover.xml"
# Optional. Set to 1 or true to not run 'dotnet test' command
# dotnetDisableTests: true
dotnetTestArguments: Marsen.NetCore.Dojo.Integration.Test.sln --logger trx -p:CoverletOutputFormat="opencover" --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
dotnetBuildArguments: Marsen.NetCore.Dojo.sln

其它

注意到 opencover 的開發者已經不再維護(2021 年 6 月),並建議使用 AltCover.
但是 sonarcloud 並未支援 altcover 的報表, 這裡有相關的 issue 追蹤中.

參考

(fin)