Merged PR 600: [ライセンスアラート改善]リトライ対応
## 概要 [Task3025: [ライセンスアラート改善]リトライ対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3025) ライセンスアラート処理にリトライ処理を追加しました。 メールの多重送信を防ぐために、送信成功したメールについてはredisに保存し、送信時にキャッシュをチェックする処理を入れました。 ## レビューポイント 処理の流れが妥当か。 redisに保存するキー、値は適切か。 if文のネストが相当深くなってしまったが、改善できるポイントはあるか。 ## UIの変更 なし ## 動作確認状況 ローカルで動作確認済み。(テスト用コードで無理やりエラーを発生させての確認) ## 補足 なし
This commit is contained in:
parent
94f34a0fde
commit
4399a61f2b
@ -11,5 +11,10 @@
|
||||
"extensionBundle": {
|
||||
"id": "Microsoft.Azure.Functions.ExtensionBundle",
|
||||
"version": "[4.*, 5.0.0)"
|
||||
},
|
||||
"retry": {
|
||||
"strategy": "fixedDelay",
|
||||
"maxRetryCount": 3,
|
||||
"delayInterval": "00:00:10"
|
||||
}
|
||||
}
|
||||
@ -5,8 +5,8 @@ import { AdB2cResponse, AdB2cUser } from "./types/types";
|
||||
import { error } from "console";
|
||||
import { makeADB2CKey, restoreAdB2cID } from "../common/cache";
|
||||
import { promisify } from "util";
|
||||
import { createRedisClient } from "../redis/redis";
|
||||
import { InvocationContext } from "@azure/functions";
|
||||
import { RedisClient } from "redis";
|
||||
|
||||
export class Adb2cTooManyRequestsError extends Error {}
|
||||
|
||||
@ -23,16 +23,24 @@ export class AdB2cService {
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
const credential = new ClientSecretCredential(
|
||||
process.env.ADB2C_TENANT_ID,
|
||||
process.env.ADB2C_CLIENT_ID,
|
||||
process.env.ADB2C_CLIENT_SECRET
|
||||
);
|
||||
const authProvider = new TokenCredentialAuthenticationProvider(credential, {
|
||||
scopes: ["https://graph.microsoft.com/.default"],
|
||||
});
|
||||
try {
|
||||
const credential = new ClientSecretCredential(
|
||||
process.env.ADB2C_TENANT_ID,
|
||||
process.env.ADB2C_CLIENT_ID,
|
||||
process.env.ADB2C_CLIENT_SECRET
|
||||
);
|
||||
const authProvider = new TokenCredentialAuthenticationProvider(
|
||||
credential,
|
||||
{
|
||||
scopes: ["https://graph.microsoft.com/.default"],
|
||||
}
|
||||
);
|
||||
|
||||
this.graphClient = Client.initWithMiddleware({ authProvider });
|
||||
this.graphClient = Client.initWithMiddleware({ authProvider });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,9 +50,9 @@ export class AdB2cService {
|
||||
*/
|
||||
async getUsers(
|
||||
context: InvocationContext,
|
||||
redisClient: RedisClient,
|
||||
externalIds: string[]
|
||||
): Promise<AdB2cUser[] | undefined> {
|
||||
const redisClient = createRedisClient();
|
||||
): Promise<AdB2cUser[]> {
|
||||
try {
|
||||
const b2cUsers: AdB2cUser[] = [];
|
||||
const keys = externalIds.map((externalId) => makeADB2CKey(externalId));
|
||||
@ -123,7 +131,7 @@ export class AdB2cService {
|
||||
|
||||
return [...cachedUsers, ...b2cUsers];
|
||||
} else {
|
||||
return undefined;
|
||||
return b2cUsers;
|
||||
}
|
||||
} catch (e) {
|
||||
const { statusCode } = e;
|
||||
@ -132,7 +140,6 @@ export class AdB2cService {
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
redisClient.quit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +1,5 @@
|
||||
export const ADB2C_PREFIX = "adb2c-external-id:"
|
||||
export const ADB2C_PREFIX = "adb2c-external-id:";
|
||||
export const SEND_COMPLETE_PREFIX = "send-complete-id:";
|
||||
export const MAIL_U103 = "[U103]";
|
||||
export const MAIL_U104 = "[U104]";
|
||||
export const DONE = "Done"; // メール送信成功時にredisにキャッシュする値
|
||||
|
||||
25
dictation_function/src/common/cache/index.ts
vendored
25
dictation_function/src/common/cache/index.ts
vendored
@ -1,4 +1,4 @@
|
||||
import { ADB2C_PREFIX } from './constants';
|
||||
import { ADB2C_PREFIX, SEND_COMPLETE_PREFIX } from "./constants";
|
||||
|
||||
/**
|
||||
* ADB2Cのユーザー格納用のキーを生成する
|
||||
@ -6,8 +6,8 @@ import { ADB2C_PREFIX } from './constants';
|
||||
* @returns キャッシュのキー
|
||||
*/
|
||||
export const makeADB2CKey = (externalId: string): string => {
|
||||
return `${ADB2C_PREFIX}${externalId}`;
|
||||
}
|
||||
return `${ADB2C_PREFIX}${externalId}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* ADB2Cのユーザー格納用のキーから外部ユーザーIDを取得する
|
||||
@ -15,5 +15,20 @@ export const makeADB2CKey = (externalId: string): string => {
|
||||
* @returns 外部ユーザーID
|
||||
*/
|
||||
export const restoreAdB2cID = (key: string): string => {
|
||||
return key.replace(ADB2C_PREFIX, '');
|
||||
}
|
||||
return key.replace(ADB2C_PREFIX, "");
|
||||
};
|
||||
|
||||
/**
|
||||
* ライセンスアラートメール送信履歴格納用のキーを生成する
|
||||
* @param formattedDate 当日の日付(YYYY:MM:DD)
|
||||
* @param externalId 外部ユーザーID
|
||||
* @param mail メール種別
|
||||
* @returns キャッシュのキー
|
||||
*/
|
||||
export const makeSendCompKey = (
|
||||
formattedDate: string,
|
||||
externalId: string,
|
||||
mail: string
|
||||
): string => {
|
||||
return `${SEND_COMPLETE_PREFIX}${formattedDate}${mail}${externalId}`;
|
||||
};
|
||||
|
||||
@ -19,36 +19,166 @@ import { createMailContentOfLicenseExpiringSoon } from "../sendgrid/mailContents
|
||||
import { AdB2cService } from "../adb2c/adb2c";
|
||||
import { SendGridService } from "../sendgrid/sendgrid";
|
||||
import { getMailFrom } from "../common/getEnv/getEnv";
|
||||
import { createRedisClient } from "../redis/redis";
|
||||
import { RedisClient } from "redis";
|
||||
import { promisify } from "util";
|
||||
import { makeSendCompKey } from "../common/cache";
|
||||
import {
|
||||
MAIL_U103,
|
||||
MAIL_U104,
|
||||
SEND_COMPLETE_PREFIX,
|
||||
DONE,
|
||||
} from "../common/cache/constants";
|
||||
|
||||
export async function licenseAlertProcessing(
|
||||
context: InvocationContext,
|
||||
datasource: DataSource,
|
||||
redisClient: RedisClient,
|
||||
sendgrid: SendGridService,
|
||||
adb2c: AdB2cService
|
||||
) {
|
||||
context.log("[IN]licenseAlertProcessing");
|
||||
const mailFrom = getMailFrom();
|
||||
const accountRepository = datasource.getRepository(Account);
|
||||
try {
|
||||
context.log("[IN]licenseAlertProcessing");
|
||||
|
||||
// 第五のアカウントを取得
|
||||
const accounts = await accountRepository.find({
|
||||
where: {
|
||||
tier: TIERS.TIER5,
|
||||
},
|
||||
relations: {
|
||||
primaryAdminUser: true,
|
||||
secondaryAdminUser: true,
|
||||
},
|
||||
});
|
||||
// redisのキー用
|
||||
const currentDate = new DateWithZeroTime();
|
||||
const formattedDate = `${currentDate.getFullYear()}-${(
|
||||
currentDate.getMonth() + 1
|
||||
).toString()}-${currentDate.getDate().toString()}`;
|
||||
const keysAsync = promisify(redisClient.keys).bind(redisClient);
|
||||
|
||||
const licenseRepository = datasource.getRepository(License);
|
||||
const currentDate = new DateWithZeroTime();
|
||||
const expiringSoonDate = new ExpirationThresholdDate(currentDate.getTime());
|
||||
const currentDateWithZeroTime = new DateWithZeroTime();
|
||||
const currentDateWithDayEndTime = new DateWithDayEndTime();
|
||||
const sendTargetAccounts = [] as accountInfo[];
|
||||
// メール送信対象のアカウント情報を取得
|
||||
const sendTargetAccounts = await getAlertMailTargetAccount(
|
||||
context,
|
||||
datasource
|
||||
);
|
||||
|
||||
const counts = async () => {
|
||||
// adb2cからメールアドレスを取得し、上記で取得したアカウントにマージする
|
||||
const sendTargetAccountsMargedAdb2c = await createAccountInfo(
|
||||
context,
|
||||
redisClient,
|
||||
adb2c,
|
||||
sendTargetAccounts
|
||||
);
|
||||
|
||||
// メール送信
|
||||
await sendAlertMail(
|
||||
context,
|
||||
redisClient,
|
||||
sendgrid,
|
||||
sendTargetAccountsMargedAdb2c,
|
||||
formattedDate
|
||||
);
|
||||
|
||||
// 最後まで処理が正常に通ったら、redisに保存した送信情報を削除する
|
||||
try {
|
||||
const delAsync = promisify(redisClient.del).bind(redisClient);
|
||||
|
||||
const keys = await keysAsync(`${SEND_COMPLETE_PREFIX}${formattedDate}*`);
|
||||
console.log(`delete terget:${keys}`);
|
||||
if (keys.length > 0) {
|
||||
const delResult = await delAsync(...keys);
|
||||
console.log(`delete number:${delResult}`);
|
||||
}
|
||||
} catch (e) {
|
||||
context.log("redis delete failed");
|
||||
throw e;
|
||||
}
|
||||
} catch (e) {
|
||||
throw e;
|
||||
} finally {
|
||||
context.log("[OUT]licenseAlertProcessing");
|
||||
}
|
||||
}
|
||||
|
||||
export async function licenseAlert(
|
||||
myTimer: Timer,
|
||||
context: InvocationContext
|
||||
): Promise<void> {
|
||||
context.log("[IN]licenseAlert");
|
||||
|
||||
dotenv.config({ path: ".env" });
|
||||
dotenv.config({ path: ".env.local", override: true });
|
||||
let datasource: DataSource;
|
||||
try {
|
||||
datasource = new DataSource({
|
||||
type: "mysql",
|
||||
host: process.env.DB_HOST,
|
||||
port: Number(process.env.DB_PORT),
|
||||
username: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
entities: [User, Account, License],
|
||||
});
|
||||
await datasource.initialize();
|
||||
} catch (e) {
|
||||
context.log("database initialize failed.");
|
||||
context.error(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
let redisClient: RedisClient;
|
||||
try {
|
||||
// redis接続
|
||||
redisClient = createRedisClient();
|
||||
} catch (e) {
|
||||
context.log("redis client create failed.");
|
||||
context.error(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
try {
|
||||
const adb2c = new AdB2cService();
|
||||
const sendgrid = new SendGridService();
|
||||
await licenseAlertProcessing(
|
||||
context,
|
||||
datasource,
|
||||
redisClient,
|
||||
sendgrid,
|
||||
adb2c
|
||||
);
|
||||
} catch (e) {
|
||||
context.log("licenseAlertProcessing failed.");
|
||||
context.error(e);
|
||||
throw e;
|
||||
} finally {
|
||||
await datasource.destroy();
|
||||
redisClient.quit;
|
||||
context.log("[OUT]licenseAlert");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* アラートメールを送信する対象のアカウントを取得する
|
||||
* @param context
|
||||
* @param datasource
|
||||
* @returns accountInfo[] メール送信対象のアカウント情報
|
||||
*/
|
||||
async function getAlertMailTargetAccount(
|
||||
context: InvocationContext,
|
||||
datasource: DataSource
|
||||
): Promise<accountInfo[]> {
|
||||
try {
|
||||
context.log("[IN]getAlertMailTargetAccount");
|
||||
const currentDate = new DateWithZeroTime();
|
||||
const expiringSoonDate = new ExpirationThresholdDate(currentDate.getTime());
|
||||
const currentDateWithZeroTime = new DateWithZeroTime();
|
||||
const currentDateWithDayEndTime = new DateWithDayEndTime();
|
||||
|
||||
// 第五のアカウントを取得
|
||||
const accountRepository = datasource.getRepository(Account);
|
||||
const accounts = await accountRepository.find({
|
||||
where: {
|
||||
tier: TIERS.TIER5,
|
||||
},
|
||||
relations: {
|
||||
primaryAdminUser: true,
|
||||
secondaryAdminUser: true,
|
||||
},
|
||||
});
|
||||
|
||||
const sendTargetAccounts = [] as accountInfo[];
|
||||
const licenseRepository = datasource.getRepository(License);
|
||||
for (const account of accounts) {
|
||||
// 有効期限がしきい値より未来または未設定で、割り当て可能なライセンス数の取得を行う
|
||||
const allocatableLicenseWithMargin = await licenseRepository.count({
|
||||
@ -109,6 +239,7 @@ export async function licenseAlertProcessing(
|
||||
let primaryAdminExternalId: string | undefined;
|
||||
let secondaryAdminExternalId: string | undefined;
|
||||
let parentCompanyName: string | undefined;
|
||||
|
||||
if (shortage !== 0 || userCount !== 0) {
|
||||
primaryAdminExternalId = account.primaryAdminUser
|
||||
? account.primaryAdminUser.external_id
|
||||
@ -143,12 +274,33 @@ export async function licenseAlertProcessing(
|
||||
secondaryAdminEmail: undefined,
|
||||
});
|
||||
}
|
||||
};
|
||||
await counts();
|
||||
return sendTargetAccounts;
|
||||
} catch (e) {
|
||||
context.error(e);
|
||||
context.log("getAlertMailTargetAccount failed.");
|
||||
throw e;
|
||||
} finally {
|
||||
context.log("[OUT]getAlertMailTargetAccount");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Azure AD B2Cからユーザ情報を取得し、アカウント情報を作成する
|
||||
* @param context
|
||||
* @param redisClient
|
||||
* @param adb2c
|
||||
* @param sendTargetAccounts RDBから取得したアカウント情報
|
||||
* @returns accountInfo[] メール送信対象のアカウント情報
|
||||
*/
|
||||
async function createAccountInfo(
|
||||
context: InvocationContext,
|
||||
redisClient: RedisClient,
|
||||
adb2c: AdB2cService,
|
||||
sendTargetAccounts: accountInfo[]
|
||||
): Promise<accountInfo[]> {
|
||||
// ADB2Cからユーザーを取得する用の外部ID配列を作成
|
||||
const externalIds = [] as string[];
|
||||
sendTargetAccounts.map((x) => {
|
||||
sendTargetAccounts.forEach((x) => {
|
||||
if (x.primaryAdminExternalId) {
|
||||
externalIds.push(x.primaryAdminExternalId);
|
||||
}
|
||||
@ -156,11 +308,10 @@ export async function licenseAlertProcessing(
|
||||
externalIds.push(x.secondaryAdminExternalId);
|
||||
}
|
||||
});
|
||||
const adb2cUsers = await adb2c.getUsers(context, externalIds);
|
||||
if (!adb2cUsers) {
|
||||
const adb2cUsers = await adb2c.getUsers(context, redisClient, externalIds);
|
||||
if (adb2cUsers.length === 0) {
|
||||
context.log("Target user not found");
|
||||
context.log("[OUT]licenseAlertProcessing");
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
// ADB2Cから取得したメールアドレスをRDBから取得した情報にマージ
|
||||
sendTargetAccounts.map((info) => {
|
||||
@ -188,17 +339,54 @@ export async function licenseAlertProcessing(
|
||||
}
|
||||
}
|
||||
});
|
||||
return sendTargetAccounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* アラートメールを送信する
|
||||
* @param context
|
||||
* @param redisClient
|
||||
* @param sendgrid
|
||||
* @param sendTargetAccounts メール送信対象のアカウント情報
|
||||
* @param formattedDate redisのキーに使用する日付
|
||||
* @returns ユーザ情報
|
||||
*/
|
||||
async function sendAlertMail(
|
||||
context: InvocationContext,
|
||||
redisClient: RedisClient,
|
||||
sendgrid: SendGridService,
|
||||
sendTargetAccounts: accountInfo[],
|
||||
formattedDate: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
context.log("[IN]sendAlertMail");
|
||||
|
||||
// redis用
|
||||
const getAsync = promisify(redisClient.get).bind(redisClient);
|
||||
const setexAsync = promisify(redisClient.setex).bind(redisClient);
|
||||
const ttl = process.env.ADB2C_CACHE_TTL;
|
||||
const mailFrom = getMailFrom();
|
||||
|
||||
const sendMail = async () => {
|
||||
for (const targetAccount of sendTargetAccounts) {
|
||||
// プライマリ管理者が入っているかチェック
|
||||
// 入っていない場合は、アラートメールを送信する必要が無いため、何も処理をせず次のループへ
|
||||
if (targetAccount.primaryAdminExternalId) {
|
||||
// メール送信
|
||||
// strictNullChecks対応
|
||||
if (targetAccount.primaryAdminEmail) {
|
||||
// ライセンス不足メール
|
||||
if (targetAccount.shortage !== 0) {
|
||||
if (!targetAccount.primaryAdminEmail) {
|
||||
continue;
|
||||
}
|
||||
// ライセンス不足メール
|
||||
if (targetAccount.shortage !== 0) {
|
||||
// redisに送信履歴がない場合のみ送信する
|
||||
const mailResult = await getAsync(
|
||||
makeSendCompKey(
|
||||
formattedDate,
|
||||
targetAccount.primaryAdminExternalId,
|
||||
MAIL_U103
|
||||
)
|
||||
);
|
||||
if (mailResult !== DONE) {
|
||||
const { subject, text, html } =
|
||||
await createMailContentOfLicenseShortage(
|
||||
targetAccount.companyName,
|
||||
@ -217,45 +405,107 @@ export async function licenseAlertProcessing(
|
||||
context.log(
|
||||
`Shortage mail send success. mail to :${targetAccount.primaryAdminEmail}`
|
||||
);
|
||||
} catch {
|
||||
// 送信成功時、成功履歴をredisに保存
|
||||
try {
|
||||
const key = makeSendCompKey(
|
||||
formattedDate,
|
||||
targetAccount.primaryAdminExternalId,
|
||||
MAIL_U103
|
||||
);
|
||||
await setexAsync(key, ttl, DONE);
|
||||
context.log(
|
||||
"setex Result:",
|
||||
`key:${key},ttl:${ttl},value:Done`
|
||||
);
|
||||
} catch (e) {
|
||||
context.error(e);
|
||||
context.log(
|
||||
"setex failed.",
|
||||
`target: ${targetAccount.primaryAdminEmail}`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
context.error(e);
|
||||
context.log(
|
||||
`Shortage mail send failed. mail to :${targetAccount.primaryAdminEmail}`
|
||||
);
|
||||
}
|
||||
|
||||
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
|
||||
if (targetAccount.secondaryAdminEmail) {
|
||||
// ライセンス不足メール
|
||||
if (targetAccount.shortage !== 0) {
|
||||
const { subject, text, html } =
|
||||
await createMailContentOfLicenseShortage(
|
||||
targetAccount.companyName,
|
||||
targetAccount.shortage,
|
||||
targetAccount.parentCompanyName
|
||||
);
|
||||
// メールを送信
|
||||
try {
|
||||
await sendgrid.sendMail(
|
||||
targetAccount.secondaryAdminEmail,
|
||||
mailFrom,
|
||||
subject,
|
||||
text,
|
||||
html
|
||||
);
|
||||
context.log(
|
||||
`Shortage mail send success. mail to :${targetAccount.secondaryAdminEmail}`
|
||||
);
|
||||
} catch {
|
||||
context.log(
|
||||
`Shortage mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
|
||||
);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// ライセンス失効警告メール
|
||||
if (targetAccount.userCountOfLicenseExpiringSoon !== 0) {
|
||||
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
|
||||
if (
|
||||
targetAccount.secondaryAdminEmail &&
|
||||
targetAccount.secondaryAdminExternalId
|
||||
) {
|
||||
// redisに送信履歴がない場合のみ送信する
|
||||
const mailResult = await getAsync(
|
||||
makeSendCompKey(
|
||||
formattedDate,
|
||||
targetAccount.secondaryAdminExternalId,
|
||||
MAIL_U103
|
||||
)
|
||||
);
|
||||
if (mailResult !== DONE) {
|
||||
const { subject, text, html } =
|
||||
await createMailContentOfLicenseShortage(
|
||||
targetAccount.companyName,
|
||||
targetAccount.shortage,
|
||||
targetAccount.parentCompanyName
|
||||
);
|
||||
// メールを送信
|
||||
try {
|
||||
await sendgrid.sendMail(
|
||||
targetAccount.secondaryAdminEmail,
|
||||
mailFrom,
|
||||
subject,
|
||||
text,
|
||||
html
|
||||
);
|
||||
context.log(
|
||||
`Shortage mail send success. mail to :${targetAccount.secondaryAdminEmail}`
|
||||
);
|
||||
// 送信成功時、成功履歴をredisに保存
|
||||
try {
|
||||
const key = makeSendCompKey(
|
||||
formattedDate,
|
||||
targetAccount.secondaryAdminExternalId,
|
||||
MAIL_U103
|
||||
);
|
||||
await setexAsync(key, ttl, DONE);
|
||||
context.log(
|
||||
"setex Result:",
|
||||
`key:${key},ttl:${ttl},value:Done`
|
||||
);
|
||||
} catch (e) {
|
||||
context.error(e);
|
||||
context.log(
|
||||
"setex failed.",
|
||||
`target: ${targetAccount.secondaryAdminEmail}`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
context.error(e);
|
||||
context.log(
|
||||
`Shortage mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ライセンス失効警告メール
|
||||
if (targetAccount.userCountOfLicenseExpiringSoon !== 0) {
|
||||
// redisに送信履歴がない場合のみ送信する
|
||||
const mailResult = await getAsync(
|
||||
makeSendCompKey(
|
||||
formattedDate,
|
||||
targetAccount.primaryAdminExternalId,
|
||||
MAIL_U104
|
||||
)
|
||||
);
|
||||
if (mailResult !== DONE) {
|
||||
const { subject, text, html } =
|
||||
await createMailContentOfLicenseExpiringSoon(
|
||||
targetAccount.companyName,
|
||||
@ -274,80 +524,99 @@ export async function licenseAlertProcessing(
|
||||
context.log(
|
||||
`Expiring soon mail send success. mail to :${targetAccount.primaryAdminEmail}`
|
||||
);
|
||||
} catch {
|
||||
// 送信成功時、成功履歴をredisに保存
|
||||
try {
|
||||
const key = makeSendCompKey(
|
||||
formattedDate,
|
||||
targetAccount.primaryAdminExternalId,
|
||||
MAIL_U104
|
||||
);
|
||||
await setexAsync(key, ttl, DONE);
|
||||
context.log(
|
||||
"setex Result:",
|
||||
`key:${key},ttl:${ttl},value:Done`
|
||||
);
|
||||
} catch (e) {
|
||||
context.error(e);
|
||||
context.log(
|
||||
"setex failed.",
|
||||
`target: ${targetAccount.primaryAdminEmail}`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
context.error(e);
|
||||
context.log(
|
||||
`Expiring soon mail send failed. mail to :${targetAccount.primaryAdminEmail}`
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
|
||||
if (targetAccount.secondaryAdminEmail) {
|
||||
// ライセンス不足メール
|
||||
if (targetAccount.shortage !== 0) {
|
||||
const { subject, text, html } =
|
||||
await createMailContentOfLicenseExpiringSoon(
|
||||
targetAccount.companyName,
|
||||
targetAccount.userCountOfLicenseExpiringSoon,
|
||||
targetAccount.parentCompanyName
|
||||
);
|
||||
// メールを送信
|
||||
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
|
||||
if (
|
||||
targetAccount.secondaryAdminEmail &&
|
||||
targetAccount.secondaryAdminExternalId
|
||||
) {
|
||||
// redisに送信履歴がない場合のみ送信する
|
||||
const mailResult = makeSendCompKey(
|
||||
formattedDate,
|
||||
targetAccount.secondaryAdminExternalId,
|
||||
MAIL_U104
|
||||
);
|
||||
if (mailResult !== DONE) {
|
||||
const { subject, text, html } =
|
||||
await createMailContentOfLicenseExpiringSoon(
|
||||
targetAccount.companyName,
|
||||
targetAccount.userCountOfLicenseExpiringSoon,
|
||||
targetAccount.parentCompanyName
|
||||
);
|
||||
// メールを送信
|
||||
try {
|
||||
await sendgrid.sendMail(
|
||||
targetAccount.secondaryAdminEmail,
|
||||
mailFrom,
|
||||
subject,
|
||||
text,
|
||||
html
|
||||
);
|
||||
context.log(
|
||||
`Expiring soon mail send success. mail to :${targetAccount.secondaryAdminEmail}`
|
||||
);
|
||||
try {
|
||||
await sendgrid.sendMail(
|
||||
targetAccount.secondaryAdminEmail,
|
||||
mailFrom,
|
||||
subject,
|
||||
text,
|
||||
html
|
||||
const key = makeSendCompKey(
|
||||
formattedDate,
|
||||
targetAccount.secondaryAdminExternalId,
|
||||
MAIL_U104
|
||||
);
|
||||
await setexAsync(key, ttl, DONE);
|
||||
context.log(
|
||||
`Expiring soon mail send success. mail to :${targetAccount.secondaryAdminEmail}`
|
||||
"setex Result:",
|
||||
`key:${key},ttl:${ttl},value:Done`
|
||||
);
|
||||
} catch {
|
||||
} catch (e) {
|
||||
context.error(e);
|
||||
context.log(
|
||||
`Expiring soon mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
|
||||
"setex failed.",
|
||||
`target: ${targetAccount.secondaryAdminEmail}`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
context.error(e);
|
||||
context.log(
|
||||
`Expiring soon mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
await sendMail();
|
||||
|
||||
context.log("[OUT]licenseAlertProcessing");
|
||||
}
|
||||
|
||||
export async function licenseAlert(
|
||||
myTimer: Timer,
|
||||
context: InvocationContext
|
||||
): Promise<void> {
|
||||
context.log("[IN]licenseAlert");
|
||||
|
||||
dotenv.config({ path: ".env" });
|
||||
dotenv.config({ path: ".env.local", override: true });
|
||||
const datasource = new DataSource({
|
||||
type: "mysql",
|
||||
host: process.env.DB_HOST,
|
||||
port: Number(process.env.DB_PORT),
|
||||
username: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
entities: [User, Account, License],
|
||||
});
|
||||
await datasource.initialize();
|
||||
|
||||
const adb2c = new AdB2cService();
|
||||
const sendgrid = new SendGridService();
|
||||
try {
|
||||
await licenseAlertProcessing(context, datasource, sendgrid, adb2c);
|
||||
} catch (e) {
|
||||
context.log("licenseAlertProcessing failed");
|
||||
context.error(e);
|
||||
context.log("sendAlertMail failed.");
|
||||
throw e;
|
||||
} finally {
|
||||
await datasource.destroy();
|
||||
context.log("[OUT]licenseAlert");
|
||||
context.log("[OUT]sendAlertMail");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
import { app, InvocationContext, Timer } from "@azure/functions";
|
||||
import * as dotenv from "dotenv";
|
||||
import { promisify } from "util";
|
||||
import { createRedisClient } from "../redis/redis";
|
||||
|
||||
export async function redisTimerTest(
|
||||
myTimer: Timer,
|
||||
context: InvocationContext
|
||||
): Promise<void> {
|
||||
context.log("---Timer function processed request.");
|
||||
|
||||
dotenv.config({ path: ".env" });
|
||||
dotenv.config({ path: ".env.local", override: true });
|
||||
|
||||
const redisClient = createRedisClient();
|
||||
const setAsync = promisify(redisClient.set).bind(redisClient);
|
||||
const getAsync = promisify(redisClient.get).bind(redisClient);
|
||||
|
||||
await setAsync("foo", "bar");
|
||||
const value = await getAsync("foo");
|
||||
context.log(`value=${value}`); // returns 'bar'
|
||||
|
||||
await redisClient.quit;
|
||||
}
|
||||
|
||||
app.timer("redisTimerTest", {
|
||||
schedule: "*/30 * * * * *",
|
||||
handler: redisTimerTest,
|
||||
});
|
||||
@ -20,12 +20,34 @@ export const createRedisClient = (): RedisClient => {
|
||||
host: host,
|
||||
port: port,
|
||||
password: password,
|
||||
retry_strategy: (options) => {
|
||||
if (options.attempt <= 3) {
|
||||
console.log(
|
||||
`Retrying connection to Redis. Attempt ${options.attempt}`
|
||||
);
|
||||
return 10000; // ミリ秒単位でのリトライまでの間隔
|
||||
} else {
|
||||
console.log("Exceeded maximum number of connection attempts.");
|
||||
return undefined; // リトライを終了
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
client = createClient({
|
||||
url: `rediss://${host}:${port}`,
|
||||
password: password,
|
||||
tls: {},
|
||||
retry_strategy: (options) => {
|
||||
if (options.attempt <= 3) {
|
||||
console.log(
|
||||
`Retrying connection to Redis. Attempt ${options.attempt}`
|
||||
);
|
||||
return 10000; // ミリ秒単位でのリトライまでの間隔
|
||||
} else {
|
||||
console.log("Exceeded maximum number of connection attempts.");
|
||||
return undefined; // リトライを終了
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,8 @@ import { ADB2C_SIGN_IN_TYPE } from "../constants";
|
||||
import { SendGridService } from "../sendgrid/sendgrid";
|
||||
import { AdB2cService } from "../adb2c/adb2c";
|
||||
import { InvocationContext } from "@azure/functions";
|
||||
import { RedisClient } from "redis";
|
||||
import { createRedisClient } from "../redis/redis";
|
||||
|
||||
describe("licenseAlert", () => {
|
||||
dotenv.config({ path: ".env" });
|
||||
@ -40,6 +42,7 @@ describe("licenseAlert", () => {
|
||||
const context = new InvocationContext();
|
||||
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
||||
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
||||
const redisClient = createRedisClient();
|
||||
// 呼び出し回数でテスト成否を判定
|
||||
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
||||
|
||||
@ -63,8 +66,15 @@ describe("licenseAlert", () => {
|
||||
null
|
||||
);
|
||||
|
||||
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
|
||||
await licenseAlertProcessing(
|
||||
context,
|
||||
source,
|
||||
redisClient,
|
||||
sendgridMock,
|
||||
adb2cMock
|
||||
);
|
||||
expect(spySend.mock.calls).toHaveLength(1);
|
||||
redisClient.quit;
|
||||
});
|
||||
|
||||
it("ライセンス在庫不足メール、ライセンス失効警告メールが送信されること", async () => {
|
||||
@ -72,6 +82,7 @@ describe("licenseAlert", () => {
|
||||
const context = new InvocationContext();
|
||||
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
||||
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
||||
const redisClient = createRedisClient();
|
||||
|
||||
// 呼び出し回数でテスト成否を判定
|
||||
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
||||
@ -96,8 +107,15 @@ describe("licenseAlert", () => {
|
||||
null
|
||||
);
|
||||
|
||||
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
|
||||
await licenseAlertProcessing(
|
||||
context,
|
||||
source,
|
||||
redisClient,
|
||||
sendgridMock,
|
||||
adb2cMock
|
||||
);
|
||||
expect(spySend.mock.calls).toHaveLength(2);
|
||||
redisClient.quit;
|
||||
});
|
||||
|
||||
it("在庫があるため、ライセンス在庫不足メールが送信されないこと", async () => {
|
||||
@ -105,6 +123,7 @@ describe("licenseAlert", () => {
|
||||
const context = new InvocationContext();
|
||||
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
||||
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
||||
const redisClient = createRedisClient();
|
||||
|
||||
// 呼び出し回数でテスト成否を判定
|
||||
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
||||
@ -142,8 +161,15 @@ describe("licenseAlert", () => {
|
||||
null
|
||||
);
|
||||
|
||||
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
|
||||
await licenseAlertProcessing(
|
||||
context,
|
||||
source,
|
||||
redisClient,
|
||||
sendgridMock,
|
||||
adb2cMock
|
||||
);
|
||||
expect(spySend.mock.calls).toHaveLength(0);
|
||||
redisClient.quit;
|
||||
});
|
||||
|
||||
it("AutoRenewがtureのため、ライセンス失効警告メールが送信されないこと", async () => {
|
||||
@ -151,6 +177,7 @@ describe("licenseAlert", () => {
|
||||
const context = new InvocationContext();
|
||||
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
||||
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
||||
const redisClient = createRedisClient();
|
||||
|
||||
// 呼び出し回数でテスト成否を判定
|
||||
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
||||
@ -175,8 +202,15 @@ describe("licenseAlert", () => {
|
||||
null
|
||||
);
|
||||
|
||||
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
|
||||
await licenseAlertProcessing(
|
||||
context,
|
||||
source,
|
||||
redisClient,
|
||||
sendgridMock,
|
||||
adb2cMock
|
||||
);
|
||||
expect(spySend.mock.calls).toHaveLength(1);
|
||||
redisClient.quit;
|
||||
});
|
||||
});
|
||||
|
||||
@ -211,6 +245,7 @@ export class AdB2cServiceMock {
|
||||
*/
|
||||
async getUsers(
|
||||
context: InvocationContext,
|
||||
redisClient: RedisClient,
|
||||
externalIds: string[]
|
||||
): Promise<AdB2cUser[]> {
|
||||
const AdB2cMockUsers: AdB2cUser[] = [
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user