Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
harid
02c30c558a fix
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m9s
2026-07-23 13:53:05 +07:00
22ea54aeb3 fixed getaddrinfo EAI_AGAIN exprofile.bangkok.go.th
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m2s
2026-07-22 22:20:07 +07:00
3 changed files with 140 additions and 19 deletions

View file

@ -43,6 +43,35 @@ class TokenCache {
} }
} }
// network/DNS error codes ที่ถือว่าเป็น transient (ควร retry)
const TRANSIENT_NETWORK_CODES = [
"EAI_AGAIN", // DNS lookup ล้มเหลวชั่วคราว
"ENOTFOUND",
"ECONNRESET",
"ETIMEDOUT",
"ECONNREFUSED",
"EHOSTUNREACH",
"ENETUNREACH",
];
// ตรวจว่า error ควร retry: ครอบ network/DNS (ไม่มี response หรือ code ตรง TRANSIENT) และ HTTP 5xx
function isTransientError(error: any): boolean {
if (!error) return false;
if (!error.response || TRANSIENT_NETWORK_CODES.includes(error.code)) {
return true;
}
return error.response?.status >= 500;
}
// หน่วงเวลา (backoff) ระหว่าง retry — ใช้รูปแบบ setTimeout เดียวกับ keycloak/index.ts
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// config retry สำหรับเรียก Exprofile
const EXPROFILE_MAX_RETRIES = 3; // จากเดิม 2
const EXPROFILE_BACKOFF_BASE_MS = 1000; // backoff = base * 2^retryCount → 1s, 2s
@Route("api/v1/org/ex/retirement") @Route("api/v1/org/ex/retirement")
@Tags("ExRetirement") @Tags("ExRetirement")
@Security("bearerAuth") @Security("bearerAuth")
@ -60,7 +89,7 @@ export class ExRetirementController extends Controller {
}, },
) { ) {
let retryCount = 0; let retryCount = 0;
const maxRetries = 2; const maxRetries = EXPROFILE_MAX_RETRIES;
while (retryCount < maxRetries) { while (retryCount < maxRetries) {
try { try {
@ -92,8 +121,9 @@ export class ExRetirementController extends Controller {
// return res.data; // return res.data;
return new HttpSuccess(res.data.data); return new HttpSuccess(res.data.data);
} catch (error: any) { } catch (error: any) {
if (error.response?.status === 500 && retryCount < maxRetries - 1) { if (isTransientError(error) && retryCount < maxRetries - 1) {
TokenCache.delete(`${clientId}:${clientSecret}`); TokenCache.delete(`${clientId}:${clientSecret}`);
await sleep(EXPROFILE_BACKOFF_BASE_MS * 2 ** retryCount);
retryCount++; retryCount++;
continue; continue;
} }
@ -107,7 +137,7 @@ export class ExRetirementController extends Controller {
@Get("/document/{documentId}") @Get("/document/{documentId}")
async getDocument(@Path("documentId") officerDocumentID: string, @Request() req: any) { async getDocument(@Path("documentId") officerDocumentID: string, @Request() req: any) {
let retryCount = 0; let retryCount = 0;
const maxRetries = 2; const maxRetries = EXPROFILE_MAX_RETRIES;
while (retryCount < maxRetries) { while (retryCount < maxRetries) {
try { try {
const token = await getToken(clientId, clientSecret); const token = await getToken(clientId, clientSecret);
@ -136,8 +166,9 @@ export class ExRetirementController extends Controller {
return; return;
} }
} catch (error: any) { } catch (error: any) {
if (error.response?.status === 500 && retryCount < maxRetries - 1) { if (isTransientError(error) && retryCount < maxRetries - 1) {
TokenCache.delete(`${clientId}:${clientSecret}`); TokenCache.delete(`${clientId}:${clientSecret}`);
await sleep(EXPROFILE_BACKOFF_BASE_MS * 2 ** retryCount);
retryCount++; retryCount++;
continue; continue;
} }
@ -171,8 +202,13 @@ async function getToken(ClientID: string, ClientSecret: string): Promise<string>
TokenCache.set(cacheKey, token); TokenCache.set(cacheKey, token);
return token; return token;
} catch (error) { } catch (error) {
console.error('getToken error:', error); // log แบบกระชับ (ลด log noise จากการ dump object ใหญ่ตามที่เห็นใน log จริง)
return Promise.reject({ message: "Error occurred", error }); console.error(
"getToken error:",
error instanceof Error ? `${error.name}: ${error.message}` : error,
);
// โยน AxiosError ตัวจริงออกไปให้ caller อ่าน code / response / status เพื่อตัดสินใจ retry
throw error;
} }
} }
@ -198,7 +234,7 @@ export async function PostRetireToExprofile(
} }
let retryCount = 0; let retryCount = 0;
const maxRetries = 2; const maxRetries = EXPROFILE_MAX_RETRIES;
while (retryCount < maxRetries) { while (retryCount < maxRetries) {
try { try {
@ -235,8 +271,9 @@ export async function PostRetireToExprofile(
return res.data; return res.data;
} catch (error: any) { } catch (error: any) {
if (error.response?.status === 500 && retryCount < maxRetries - 1) { if (isTransientError(error) && retryCount < maxRetries - 1) {
TokenCache.delete(`${clientId}:${clientSecret}`); TokenCache.delete(`${clientId}:${clientSecret}`);
await sleep(EXPROFILE_BACKOFF_BASE_MS * 2 ** retryCount);
retryCount++; retryCount++;
continue; continue;
} }

View file

@ -11451,11 +11451,10 @@ export class ProfileController extends Controller {
system?: string; system?: string;
}, },
) { ) {
// comment ออกก่อนเพราะยังไม่ได้ใช้ // ค้นหารายชื่อถ้าไม่ส่ง system มาให้ default ตามทะเบียนประวัติ
// // ค้นหารายชื่อถ้าไม่ส่ง system มาให้ default ตามทะเบียนประวัติ let _system: string = "SYS_REGISTRY_OFFICER";
// let _system: string = "SYS_REGISTRY_OFFICER"; if (body.system) _system = body.system;
// if (body.system) _system = body.system; let _data = await new permission().PermissionOrgList(request, _system);
// let _data = await new permission().PermissionOrgList(request, _system);
const findRevision = await this.orgRevisionRepo.findOne({ const findRevision = await this.orgRevisionRepo.findOne({
where: { orgRevisionIsCurrent: true }, where: { orgRevisionIsCurrent: true },
}); });
@ -11500,10 +11499,50 @@ export class ProfileController extends Controller {
.leftJoinAndSelect("current_holders.orgChild2", "orgChild2") .leftJoinAndSelect("current_holders.orgChild2", "orgChild2")
.leftJoinAndSelect("current_holders.orgChild3", "orgChild3") .leftJoinAndSelect("current_holders.orgChild3", "orgChild3")
.leftJoinAndSelect("current_holders.orgChild4", "orgChild4") .leftJoinAndSelect("current_holders.orgChild4", "orgChild4")
.where(body.system ? "profile.isActive = :isActive" : "profile.isDelete = :isDelete", { .where("profile.isActive = :isActive AND profile.isDelete = :isDelete", {
isActive: false, isActive: true,
isDelete: true, isDelete: false,
}) })
.andWhere(
_data.root != undefined && _data.root != null
? _data.root[0] != null
? `current_holders.orgRootId IN (:...root)`
: `current_holders.orgRootId is null`
: "1=1",
{ root: _data.root },
)
.andWhere(
_data.child1 != undefined && _data.child1 != null
? _data.child1[0] != null
? `current_holders.orgChild1Id IN (:...child1)`
: `current_holders.orgChild1Id is null`
: "1=1",
{ child1: _data.child1 },
)
.andWhere(
_data.child2 != undefined && _data.child2 != null
? _data.child2[0] != null
? `current_holders.orgChild2Id IN (:...child2)`
: `current_holders.orgChild2Id is null`
: "1=1",
{ child2: _data.child2 },
)
.andWhere(
_data.child3 != undefined && _data.child3 != null
? _data.child3[0] != null
? `current_holders.orgChild3Id IN (:...child3)`
: `current_holders.orgChild3Id is null`
: "1=1",
{ child3: _data.child3 },
)
.andWhere(
_data.child4 != undefined && _data.child4 != null
? _data.child4[0] != null
? `current_holders.orgChild4Id IN (:...child4)`
: `current_holders.orgChild4Id is null`
: "1=1",
{ child4: _data.child4 },
)
.andWhere( .andWhere(
new Brackets((qb) => { new Brackets((qb) => {
qb.orWhere(body.keyword ? queryLike : "1=1", { keyword: `%${body.keyword}%` }); qb.orWhere(body.keyword ? queryLike : "1=1", { keyword: `%${body.keyword}%` });

View file

@ -6172,6 +6172,7 @@ export class ProfileEmployeeController extends Controller {
*/ */
@Post("search-personal-no-keycloak") @Post("search-personal-no-keycloak")
async getProfileBySearchKeywordNoKeyCloak( async getProfileBySearchKeywordNoKeyCloak(
@Request() request: RequestWithUser,
@Query("page") page: number = 1, @Query("page") page: number = 1,
@Query("pageSize") pageSize: number = 10, @Query("pageSize") pageSize: number = 10,
@Body() @Body()
@ -6181,6 +6182,10 @@ export class ProfileEmployeeController extends Controller {
system?: string; system?: string;
}, },
) { ) {
// ค้นหารายชื่อถ้าไม่ส่ง system มาให้ default ตามทะเบียนประวัติ
let _system: string = "SYS_REGISTRY_EMP";
if (body.system) _system = body.system;
let _data = await new permission().PermissionOrgList(request, _system);
const findRevision = await this.orgRevisionRepo.findOne({ const findRevision = await this.orgRevisionRepo.findOne({
where: { orgRevisionIsCurrent: true }, where: { orgRevisionIsCurrent: true },
}); });
@ -6225,10 +6230,50 @@ export class ProfileEmployeeController extends Controller {
.leftJoinAndSelect("current_holders.orgChild2", "orgChild2") .leftJoinAndSelect("current_holders.orgChild2", "orgChild2")
.leftJoinAndSelect("current_holders.orgChild3", "orgChild3") .leftJoinAndSelect("current_holders.orgChild3", "orgChild3")
.leftJoinAndSelect("current_holders.orgChild4", "orgChild4") .leftJoinAndSelect("current_holders.orgChild4", "orgChild4")
.where(body.system ? "profile.isActive = :isActive" : "profile.isDelete = :isDelete", { .where("profile.isActive = :isActive AND profile.isDelete = :isDelete", {
isActive: false, isActive: true,
isDelete: true, isDelete: false,
}) })
.andWhere(
_data.root != undefined && _data.root != null
? _data.root[0] != null
? `current_holders.orgRootId IN (:...root)`
: `current_holders.orgRootId is null`
: "1=1",
{ root: _data.root },
)
.andWhere(
_data.child1 != undefined && _data.child1 != null
? _data.child1[0] != null
? `current_holders.orgChild1Id IN (:...child1)`
: `current_holders.orgChild1Id is null`
: "1=1",
{ child1: _data.child1 },
)
.andWhere(
_data.child2 != undefined && _data.child2 != null
? _data.child2[0] != null
? `current_holders.orgChild2Id IN (:...child2)`
: `current_holders.orgChild2Id is null`
: "1=1",
{ child2: _data.child2 },
)
.andWhere(
_data.child3 != undefined && _data.child3 != null
? _data.child3[0] != null
? `current_holders.orgChild3Id IN (:...child3)`
: `current_holders.orgChild3Id is null`
: "1=1",
{ child3: _data.child3 },
)
.andWhere(
_data.child4 != undefined && _data.child4 != null
? _data.child4[0] != null
? `current_holders.orgChild4Id IN (:...child4)`
: `current_holders.orgChild4Id is null`
: "1=1",
{ child4: _data.child4 },
)
.andWhere( .andWhere(
new Brackets((qb) => { new Brackets((qb) => {
qb.orWhere(body.keyword ? queryLike : "1=1", { keyword: `%${body.keyword}%` }); qb.orWhere(body.keyword ? queryLike : "1=1", { keyword: `%${body.keyword}%` });