2023年5月22日 星期一

AdonisJs時間戳怎麼老是早八小時

在AdonisJs裏面時間物件預設是使用luxon的DateTime,但使用上你會發現時間會跟現實差時區的八小時(小編人在台灣GM+8),解決辦法就是在環境變數上加上TZ變數(注意不是TimeZone),

// .env
TZ=Asia/Taipei

只需要這樣,程式碼跟設定都無需做任何改動,你就會發現created_at跟updated_at都回到正常時間了。
但是!沒錯別這麼快走,上面說到只有created_at跟updated_at,可能底層有針對這兩個變數做特別處理,所以會特別順利。
如果你有自行設置其他datetime欄位或timestamp欄位,例如小編昨天新增了一個sent_at欄位,結果就不會如預期,還是差八小時,那這問題到底在哪呢?
經過思考後我想起,在db的設置上似乎也有timezone的設置,雖然不知道有沒有關係但就姑且一試吧。

mysql: {
client: "mysql",
connection: {
host: Env.get("MYSQL_HOST"),
port: Env.get("MYSQL_PORT"),
user: Env.get("MYSQL_USER"),
password: Env.get("MYSQL_PASSWORD", ""),
database: Env.get("MYSQL_DB_NAME"),
        // 加入 timezone,取自.env TZ
timezone: Env.get("TZ"),
},
// ...略
},

再來測試一下,竟然過了!太棒了下班!這幾天遇到的雷都還不算很大啊~

AdonisJs報錯Incorrect string value: '\xF0\xA6\x9F\x8C,\xE9...' for column 'table' at row 1

小編在進行文字儲存時,透過orm進行操作,最終指令如下

insert into goal_details
(created_at, domain_code, goal_id, is_achieved, level, reason, remark,
smart_goals,
sub_domain_code, updated_at)
values
('2023-05-22 15:18:38', 'DOMAINS.NUTRITION', 103, false, 1, NULL, NULL,
'如工作時外出午餐, 會到粉麵店叫低脂肉(牛筋, 牛𦟌,魚旦)+米粉/米線',
'SUB_DOMAINS.NUTRITION.REDUCE_FAT', '2023-05-22 15:18:38')

然後報錯:Incorrect string value:......

經過反覆測試發現問題在“𦟌”這個字上面,只要移除就沒事了。但想當然客戶不會接受這無理的需求,而且未來會不會有其他字無法存取不知道,所以只好再想辦法。

小編此時資料庫編碼為utf8mb4,理論上不應該呀,遵循chatGPT指示,嘗試把collation從utf8mb4_0900_ai_ci改成utf8mb4_unicode_520_ci,但問題還是沒有解決。

在苦無對策之際,我嘗試著把上面這句sql直接透過Navicat直接執行,竟然通過了!!所以問題並不在db,那就是在AdonisJs框架了,然後我想了一下,好像之前遇到要寫入表情符號時也遇過相同的錯誤訊息,最初db連線資訊預設是utf8mb3,那這樣似乎就說得通了,所以把config/database.ts的連線資訊加入charset,把它改成跟db一樣utf8mb4即可

mysql: {
client: 'mysql',
connection: {
host: Env.get('MYSQL_HOST'),
port: Env.get('MYSQL_PORT'),
user: Env.get('MYSQL_USER'),
password: Env.get('MYSQL_PASSWORD', ''),
database: Env.get('MYSQL_DB_NAME'),
        // 加入charset
charset: 'utf8mb4',
},
// ...略
},


再回去跑一下程式,發現過了。那就打完收工拉!!

2023年4月27日 星期四

AdonisJs自動注入2

前言

上之介紹過adonisjs/fold的inject,讀過inject的源碼可以知道inject會把宣告的class直接注入進目前class的inject內的instance,而且是static,然後底層透過Route呼叫controller在把instance一一實現出來。那這樣問題來了,如果不是透過Route想要實現自動注入該如何處理。

例如Seeder中偶爾會寫一些腳本,但最好的方法是呼叫service執行相對應的function讓程式走已經經過測試的流程,才不會出現漏改或資料不完整等資料問題。那seeder並非透過route來呼叫,所以無法使用inject,在此我開發了一個套件供大家簡單使用

// install commands
yarn add @lu7766lu7766/adonis-utils
node ace invoke @lu7766lu7766/adonis-utils

// seeders
import BaseSeeder from '@ioc:Adonis/Lucid/Seeder'
import { InjectService } from '@ioc:Adonis/Utils'

export default class ProdDataChange0428 extends BaseSeeder {
@InjectService() // @InjectService(SurveyService)
service: SurveyService

public async run() {
console.log(this.service)
}
}

套件是開源的,源碼也很簡單,下面給大家展示一下。這也不限於只能使用在adonisjs上,甚至在其他專案也可以使用

export function InjectService(injectClass?: any) {
return function (target: Object, propKey?: string) {
if (propKey) {
const targetClass = injectClass ?? (Reflect.getMetadata("design:type", target, propKey) || [])
const params = Reflect.getMetadata("design:paramtypes", targetClass) || []
const service = new targetClass(...params)

Object.defineProperty(target, propKey, {
value: service,
writable: false,
})
}
}
}

後記

有些人喜歡用抽象或介面去管理程式碼,所以設計成參數可以設定要注入什麼,不輸入則自動注入類別,增加一點彈性。

5/23更新

我發現上面的源碼無法把注入的service,建構子裡的物件一並注入,簡單說他就是只有一層,如果要用到第二層的東西就掛了,所以我改用遞回方式去注入物件,下載到舊版的小夥伴記得去更版

2023年4月20日 星期四

git刪除(遠端)分支

因為VSCode沒有提供這個指令,而且也不常用到,所以我老是忘記,在此組個紀錄

git push [REMOTE_NAME] :[BRANCH_NAME]

想不到是用push吧,記得在要刪除的分支前面加個冒號!

順便講一下刪除本地分支的語法,因為VSCode有,所以其實也不用特別記,只是想講

git branch -d [BRANCH_NAME]


當MySQL遇上死鎖ER_LOCK_WAIT_TIMEOUT

當MySQL資料庫處理時遇到ER_LOCK_WAIT_TIMEOUT時,代表該表有交易還沒結束掉,要查詢到底是哪一筆交易沒結束掉可以使用以下SQL,DB_NAME請替換成自己的資料庫名稱

SELECT CONCAT('KILL ', ID, ';')
FROM INFORMATION_SCHEMA.PROCESSLIST
WHERE DB = [DB_NAME] AND TIME >= 10;

之後可以得到該執行序的id,然後直接下SQL,上面這個語法直接幫你把要下的SQL組合起來了,下完就海闊天空了~

KILL [ID];

AdonisJs踩雷第二發Computed

故事是這樣的,我Model有一個欄位用來儲存json字串,然後訂了一個computed欄位自動分析json,所以我很直覺地把分析完的結果拿來修改,再轉為json字串存回去,結果搞了半天內容卻一點變化也沒有。

殊不知computed是不能修改的,底層大概率是用Object.freeze之類的把物件凍結了吧。之後有空再來研究,今天先做個紀錄。

AdonisJs踩雷的一天, Migration completed, but unable to release database lock...

如果在Migration時遇到如標題所示的錯誤訊息,網路上找也只能找到這則相關的資訊,https://github.com/adonisjs/core/discussions/1897

只能說我看完當下完全看不懂他在說些什麼。

經過反覆的測試,才漸漸理解,原來發生的原因是query出錯,更正確的說法是程式報錯,至於錯在哪裡並不會告訴你,可能跟底層跑migration的方式有關,所以比較理想的解法是盡可能讓migration的sql簡單不要報錯,至於那些寫在defer裡面的query跟scripts,請抽離並改用seeder去觸發(這時就會看到錯在哪了,可以順便debug)是比較理想的做法。

另外在跑腳本可能會因為執行時間太長而中止,所以請減少sql的數量,盡可能一次取回所有資料,能把update delete insert塞進一個sql裡面就盡可能塞,減少使用關聯,才能確保腳本跑得完