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": {
|
"extensionBundle": {
|
||||||
"id": "Microsoft.Azure.Functions.ExtensionBundle",
|
"id": "Microsoft.Azure.Functions.ExtensionBundle",
|
||||||
"version": "[4.*, 5.0.0)"
|
"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 { error } from "console";
|
||||||
import { makeADB2CKey, restoreAdB2cID } from "../common/cache";
|
import { makeADB2CKey, restoreAdB2cID } from "../common/cache";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
import { createRedisClient } from "../redis/redis";
|
|
||||||
import { InvocationContext } from "@azure/functions";
|
import { InvocationContext } from "@azure/functions";
|
||||||
|
import { RedisClient } from "redis";
|
||||||
|
|
||||||
export class Adb2cTooManyRequestsError extends Error {}
|
export class Adb2cTooManyRequestsError extends Error {}
|
||||||
|
|
||||||
@ -23,16 +23,24 @@ export class AdB2cService {
|
|||||||
) {
|
) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
const credential = new ClientSecretCredential(
|
const credential = new ClientSecretCredential(
|
||||||
process.env.ADB2C_TENANT_ID,
|
process.env.ADB2C_TENANT_ID,
|
||||||
process.env.ADB2C_CLIENT_ID,
|
process.env.ADB2C_CLIENT_ID,
|
||||||
process.env.ADB2C_CLIENT_SECRET
|
process.env.ADB2C_CLIENT_SECRET
|
||||||
);
|
);
|
||||||
const authProvider = new TokenCredentialAuthenticationProvider(credential, {
|
const authProvider = new TokenCredentialAuthenticationProvider(
|
||||||
|
credential,
|
||||||
|
{
|
||||||
scopes: ["https://graph.microsoft.com/.default"],
|
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(
|
async getUsers(
|
||||||
context: InvocationContext,
|
context: InvocationContext,
|
||||||
|
redisClient: RedisClient,
|
||||||
externalIds: string[]
|
externalIds: string[]
|
||||||
): Promise<AdB2cUser[] | undefined> {
|
): Promise<AdB2cUser[]> {
|
||||||
const redisClient = createRedisClient();
|
|
||||||
try {
|
try {
|
||||||
const b2cUsers: AdB2cUser[] = [];
|
const b2cUsers: AdB2cUser[] = [];
|
||||||
const keys = externalIds.map((externalId) => makeADB2CKey(externalId));
|
const keys = externalIds.map((externalId) => makeADB2CKey(externalId));
|
||||||
@ -123,7 +131,7 @@ export class AdB2cService {
|
|||||||
|
|
||||||
return [...cachedUsers, ...b2cUsers];
|
return [...cachedUsers, ...b2cUsers];
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return b2cUsers;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const { statusCode } = e;
|
const { statusCode } = e;
|
||||||
@ -132,7 +140,6 @@ export class AdB2cService {
|
|||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} 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にキャッシュする値
|
||||||
|
|||||||
23
dictation_function/src/common/cache/index.ts
vendored
23
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のユーザー格納用のキーを生成する
|
* ADB2Cのユーザー格納用のキーを生成する
|
||||||
@ -7,7 +7,7 @@ import { ADB2C_PREFIX } from './constants';
|
|||||||
*/
|
*/
|
||||||
export const makeADB2CKey = (externalId: string): string => {
|
export const makeADB2CKey = (externalId: string): string => {
|
||||||
return `${ADB2C_PREFIX}${externalId}`;
|
return `${ADB2C_PREFIX}${externalId}`;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ADB2Cのユーザー格納用のキーから外部ユーザーIDを取得する
|
* ADB2Cのユーザー格納用のキーから外部ユーザーIDを取得する
|
||||||
@ -15,5 +15,20 @@ export const makeADB2CKey = (externalId: string): string => {
|
|||||||
* @returns 外部ユーザーID
|
* @returns 外部ユーザーID
|
||||||
*/
|
*/
|
||||||
export const restoreAdB2cID = (key: string): string => {
|
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,18 +19,154 @@ import { createMailContentOfLicenseExpiringSoon } from "../sendgrid/mailContents
|
|||||||
import { AdB2cService } from "../adb2c/adb2c";
|
import { AdB2cService } from "../adb2c/adb2c";
|
||||||
import { SendGridService } from "../sendgrid/sendgrid";
|
import { SendGridService } from "../sendgrid/sendgrid";
|
||||||
import { getMailFrom } from "../common/getEnv/getEnv";
|
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(
|
export async function licenseAlertProcessing(
|
||||||
context: InvocationContext,
|
context: InvocationContext,
|
||||||
datasource: DataSource,
|
datasource: DataSource,
|
||||||
|
redisClient: RedisClient,
|
||||||
sendgrid: SendGridService,
|
sendgrid: SendGridService,
|
||||||
adb2c: AdB2cService
|
adb2c: AdB2cService
|
||||||
) {
|
) {
|
||||||
|
try {
|
||||||
context.log("[IN]licenseAlertProcessing");
|
context.log("[IN]licenseAlertProcessing");
|
||||||
const mailFrom = getMailFrom();
|
|
||||||
const accountRepository = datasource.getRepository(Account);
|
// redisのキー用
|
||||||
|
const currentDate = new DateWithZeroTime();
|
||||||
|
const formattedDate = `${currentDate.getFullYear()}-${(
|
||||||
|
currentDate.getMonth() + 1
|
||||||
|
).toString()}-${currentDate.getDate().toString()}`;
|
||||||
|
const keysAsync = promisify(redisClient.keys).bind(redisClient);
|
||||||
|
|
||||||
|
// メール送信対象のアカウント情報を取得
|
||||||
|
const sendTargetAccounts = await getAlertMailTargetAccount(
|
||||||
|
context,
|
||||||
|
datasource
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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({
|
const accounts = await accountRepository.find({
|
||||||
where: {
|
where: {
|
||||||
tier: TIERS.TIER5,
|
tier: TIERS.TIER5,
|
||||||
@ -41,14 +177,8 @@ export async function licenseAlertProcessing(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
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 = [] as accountInfo[];
|
||||||
|
const licenseRepository = datasource.getRepository(License);
|
||||||
const counts = async () => {
|
|
||||||
for (const account of accounts) {
|
for (const account of accounts) {
|
||||||
// 有効期限がしきい値より未来または未設定で、割り当て可能なライセンス数の取得を行う
|
// 有効期限がしきい値より未来または未設定で、割り当て可能なライセンス数の取得を行う
|
||||||
const allocatableLicenseWithMargin = await licenseRepository.count({
|
const allocatableLicenseWithMargin = await licenseRepository.count({
|
||||||
@ -109,6 +239,7 @@ export async function licenseAlertProcessing(
|
|||||||
let primaryAdminExternalId: string | undefined;
|
let primaryAdminExternalId: string | undefined;
|
||||||
let secondaryAdminExternalId: string | undefined;
|
let secondaryAdminExternalId: string | undefined;
|
||||||
let parentCompanyName: string | undefined;
|
let parentCompanyName: string | undefined;
|
||||||
|
|
||||||
if (shortage !== 0 || userCount !== 0) {
|
if (shortage !== 0 || userCount !== 0) {
|
||||||
primaryAdminExternalId = account.primaryAdminUser
|
primaryAdminExternalId = account.primaryAdminUser
|
||||||
? account.primaryAdminUser.external_id
|
? account.primaryAdminUser.external_id
|
||||||
@ -143,12 +274,33 @@ export async function licenseAlertProcessing(
|
|||||||
secondaryAdminEmail: undefined,
|
secondaryAdminEmail: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
return sendTargetAccounts;
|
||||||
await counts();
|
} 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配列を作成
|
// ADB2Cからユーザーを取得する用の外部ID配列を作成
|
||||||
const externalIds = [] as string[];
|
const externalIds = [] as string[];
|
||||||
sendTargetAccounts.map((x) => {
|
sendTargetAccounts.forEach((x) => {
|
||||||
if (x.primaryAdminExternalId) {
|
if (x.primaryAdminExternalId) {
|
||||||
externalIds.push(x.primaryAdminExternalId);
|
externalIds.push(x.primaryAdminExternalId);
|
||||||
}
|
}
|
||||||
@ -156,11 +308,10 @@ export async function licenseAlertProcessing(
|
|||||||
externalIds.push(x.secondaryAdminExternalId);
|
externalIds.push(x.secondaryAdminExternalId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const adb2cUsers = await adb2c.getUsers(context, externalIds);
|
const adb2cUsers = await adb2c.getUsers(context, redisClient, externalIds);
|
||||||
if (!adb2cUsers) {
|
if (adb2cUsers.length === 0) {
|
||||||
context.log("Target user not found");
|
context.log("Target user not found");
|
||||||
context.log("[OUT]licenseAlertProcessing");
|
return [];
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// ADB2Cから取得したメールアドレスをRDBから取得した情報にマージ
|
// ADB2Cから取得したメールアドレスをRDBから取得した情報にマージ
|
||||||
sendTargetAccounts.map((info) => {
|
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) {
|
for (const targetAccount of sendTargetAccounts) {
|
||||||
// プライマリ管理者が入っているかチェック
|
// プライマリ管理者が入っているかチェック
|
||||||
// 入っていない場合は、アラートメールを送信する必要が無いため、何も処理をせず次のループへ
|
// 入っていない場合は、アラートメールを送信する必要が無いため、何も処理をせず次のループへ
|
||||||
if (targetAccount.primaryAdminExternalId) {
|
if (targetAccount.primaryAdminExternalId) {
|
||||||
// メール送信
|
// メール送信
|
||||||
// strictNullChecks対応
|
// strictNullChecks対応
|
||||||
if (targetAccount.primaryAdminEmail) {
|
if (!targetAccount.primaryAdminEmail) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// ライセンス不足メール
|
// ライセンス不足メール
|
||||||
if (targetAccount.shortage !== 0) {
|
if (targetAccount.shortage !== 0) {
|
||||||
|
// redisに送信履歴がない場合のみ送信する
|
||||||
|
const mailResult = await getAsync(
|
||||||
|
makeSendCompKey(
|
||||||
|
formattedDate,
|
||||||
|
targetAccount.primaryAdminExternalId,
|
||||||
|
MAIL_U103
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (mailResult !== DONE) {
|
||||||
const { subject, text, html } =
|
const { subject, text, html } =
|
||||||
await createMailContentOfLicenseShortage(
|
await createMailContentOfLicenseShortage(
|
||||||
targetAccount.companyName,
|
targetAccount.companyName,
|
||||||
@ -217,16 +405,48 @@ export async function licenseAlertProcessing(
|
|||||||
context.log(
|
context.log(
|
||||||
`Shortage mail send success. mail to :${targetAccount.primaryAdminEmail}`
|
`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(
|
context.log(
|
||||||
`Shortage mail send failed. mail to :${targetAccount.primaryAdminEmail}`
|
`Shortage mail send failed. mail to :${targetAccount.primaryAdminEmail}`
|
||||||
);
|
);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
|
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
|
||||||
if (targetAccount.secondaryAdminEmail) {
|
if (
|
||||||
// ライセンス不足メール
|
targetAccount.secondaryAdminEmail &&
|
||||||
if (targetAccount.shortage !== 0) {
|
targetAccount.secondaryAdminExternalId
|
||||||
|
) {
|
||||||
|
// redisに送信履歴がない場合のみ送信する
|
||||||
|
const mailResult = await getAsync(
|
||||||
|
makeSendCompKey(
|
||||||
|
formattedDate,
|
||||||
|
targetAccount.secondaryAdminExternalId,
|
||||||
|
MAIL_U103
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (mailResult !== DONE) {
|
||||||
const { subject, text, html } =
|
const { subject, text, html } =
|
||||||
await createMailContentOfLicenseShortage(
|
await createMailContentOfLicenseShortage(
|
||||||
targetAccount.companyName,
|
targetAccount.companyName,
|
||||||
@ -245,10 +465,31 @@ export async function licenseAlertProcessing(
|
|||||||
context.log(
|
context.log(
|
||||||
`Shortage mail send success. mail to :${targetAccount.secondaryAdminEmail}`
|
`Shortage mail send success. mail to :${targetAccount.secondaryAdminEmail}`
|
||||||
);
|
);
|
||||||
} catch {
|
// 送信成功時、成功履歴を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(
|
context.log(
|
||||||
`Shortage mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
|
`Shortage mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
|
||||||
);
|
);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -256,6 +497,15 @@ export async function licenseAlertProcessing(
|
|||||||
|
|
||||||
// ライセンス失効警告メール
|
// ライセンス失効警告メール
|
||||||
if (targetAccount.userCountOfLicenseExpiringSoon !== 0) {
|
if (targetAccount.userCountOfLicenseExpiringSoon !== 0) {
|
||||||
|
// redisに送信履歴がない場合のみ送信する
|
||||||
|
const mailResult = await getAsync(
|
||||||
|
makeSendCompKey(
|
||||||
|
formattedDate,
|
||||||
|
targetAccount.primaryAdminExternalId,
|
||||||
|
MAIL_U104
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (mailResult !== DONE) {
|
||||||
const { subject, text, html } =
|
const { subject, text, html } =
|
||||||
await createMailContentOfLicenseExpiringSoon(
|
await createMailContentOfLicenseExpiringSoon(
|
||||||
targetAccount.companyName,
|
targetAccount.companyName,
|
||||||
@ -274,16 +524,46 @@ export async function licenseAlertProcessing(
|
|||||||
context.log(
|
context.log(
|
||||||
`Expiring soon mail send success. mail to :${targetAccount.primaryAdminEmail}`
|
`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(
|
context.log(
|
||||||
`Expiring soon mail send failed. mail to :${targetAccount.primaryAdminEmail}`
|
`Expiring soon mail send failed. mail to :${targetAccount.primaryAdminEmail}`
|
||||||
);
|
);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
|
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
|
||||||
if (targetAccount.secondaryAdminEmail) {
|
if (
|
||||||
// ライセンス不足メール
|
targetAccount.secondaryAdminEmail &&
|
||||||
if (targetAccount.shortage !== 0) {
|
targetAccount.secondaryAdminExternalId
|
||||||
|
) {
|
||||||
|
// redisに送信履歴がない場合のみ送信する
|
||||||
|
const mailResult = makeSendCompKey(
|
||||||
|
formattedDate,
|
||||||
|
targetAccount.secondaryAdminExternalId,
|
||||||
|
MAIL_U104
|
||||||
|
);
|
||||||
|
if (mailResult !== DONE) {
|
||||||
const { subject, text, html } =
|
const { subject, text, html } =
|
||||||
await createMailContentOfLicenseExpiringSoon(
|
await createMailContentOfLicenseExpiringSoon(
|
||||||
targetAccount.companyName,
|
targetAccount.companyName,
|
||||||
@ -302,52 +582,41 @@ export async function licenseAlertProcessing(
|
|||||||
context.log(
|
context.log(
|
||||||
`Expiring soon mail send success. mail to :${targetAccount.secondaryAdminEmail}`
|
`Expiring soon mail send success. mail to :${targetAccount.secondaryAdminEmail}`
|
||||||
);
|
);
|
||||||
} catch {
|
try {
|
||||||
|
const key = makeSendCompKey(
|
||||||
|
formattedDate,
|
||||||
|
targetAccount.secondaryAdminExternalId,
|
||||||
|
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.secondaryAdminEmail}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
context.error(e);
|
||||||
context.log(
|
context.log(
|
||||||
`Expiring soon mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
|
`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) {
|
} catch (e) {
|
||||||
context.log("licenseAlertProcessing failed");
|
context.log("sendAlertMail failed.");
|
||||||
context.error(e);
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
await datasource.destroy();
|
context.log("[OUT]sendAlertMail");
|
||||||
context.log("[OUT]licenseAlert");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
host: host,
|
||||||
port: port,
|
port: port,
|
||||||
password: password,
|
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 {
|
} else {
|
||||||
client = createClient({
|
client = createClient({
|
||||||
url: `rediss://${host}:${port}`,
|
url: `rediss://${host}:${port}`,
|
||||||
password: password,
|
password: password,
|
||||||
tls: {},
|
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 { SendGridService } from "../sendgrid/sendgrid";
|
||||||
import { AdB2cService } from "../adb2c/adb2c";
|
import { AdB2cService } from "../adb2c/adb2c";
|
||||||
import { InvocationContext } from "@azure/functions";
|
import { InvocationContext } from "@azure/functions";
|
||||||
|
import { RedisClient } from "redis";
|
||||||
|
import { createRedisClient } from "../redis/redis";
|
||||||
|
|
||||||
describe("licenseAlert", () => {
|
describe("licenseAlert", () => {
|
||||||
dotenv.config({ path: ".env" });
|
dotenv.config({ path: ".env" });
|
||||||
@ -40,6 +42,7 @@ describe("licenseAlert", () => {
|
|||||||
const context = new InvocationContext();
|
const context = new InvocationContext();
|
||||||
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
||||||
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
||||||
|
const redisClient = createRedisClient();
|
||||||
// 呼び出し回数でテスト成否を判定
|
// 呼び出し回数でテスト成否を判定
|
||||||
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
||||||
|
|
||||||
@ -63,8 +66,15 @@ describe("licenseAlert", () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
|
await licenseAlertProcessing(
|
||||||
|
context,
|
||||||
|
source,
|
||||||
|
redisClient,
|
||||||
|
sendgridMock,
|
||||||
|
adb2cMock
|
||||||
|
);
|
||||||
expect(spySend.mock.calls).toHaveLength(1);
|
expect(spySend.mock.calls).toHaveLength(1);
|
||||||
|
redisClient.quit;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ライセンス在庫不足メール、ライセンス失効警告メールが送信されること", async () => {
|
it("ライセンス在庫不足メール、ライセンス失効警告メールが送信されること", async () => {
|
||||||
@ -72,6 +82,7 @@ describe("licenseAlert", () => {
|
|||||||
const context = new InvocationContext();
|
const context = new InvocationContext();
|
||||||
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
||||||
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
||||||
|
const redisClient = createRedisClient();
|
||||||
|
|
||||||
// 呼び出し回数でテスト成否を判定
|
// 呼び出し回数でテスト成否を判定
|
||||||
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
||||||
@ -96,8 +107,15 @@ describe("licenseAlert", () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
|
await licenseAlertProcessing(
|
||||||
|
context,
|
||||||
|
source,
|
||||||
|
redisClient,
|
||||||
|
sendgridMock,
|
||||||
|
adb2cMock
|
||||||
|
);
|
||||||
expect(spySend.mock.calls).toHaveLength(2);
|
expect(spySend.mock.calls).toHaveLength(2);
|
||||||
|
redisClient.quit;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("在庫があるため、ライセンス在庫不足メールが送信されないこと", async () => {
|
it("在庫があるため、ライセンス在庫不足メールが送信されないこと", async () => {
|
||||||
@ -105,6 +123,7 @@ describe("licenseAlert", () => {
|
|||||||
const context = new InvocationContext();
|
const context = new InvocationContext();
|
||||||
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
||||||
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
||||||
|
const redisClient = createRedisClient();
|
||||||
|
|
||||||
// 呼び出し回数でテスト成否を判定
|
// 呼び出し回数でテスト成否を判定
|
||||||
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
||||||
@ -142,8 +161,15 @@ describe("licenseAlert", () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
|
await licenseAlertProcessing(
|
||||||
|
context,
|
||||||
|
source,
|
||||||
|
redisClient,
|
||||||
|
sendgridMock,
|
||||||
|
adb2cMock
|
||||||
|
);
|
||||||
expect(spySend.mock.calls).toHaveLength(0);
|
expect(spySend.mock.calls).toHaveLength(0);
|
||||||
|
redisClient.quit;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("AutoRenewがtureのため、ライセンス失効警告メールが送信されないこと", async () => {
|
it("AutoRenewがtureのため、ライセンス失効警告メールが送信されないこと", async () => {
|
||||||
@ -151,6 +177,7 @@ describe("licenseAlert", () => {
|
|||||||
const context = new InvocationContext();
|
const context = new InvocationContext();
|
||||||
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
||||||
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
||||||
|
const redisClient = createRedisClient();
|
||||||
|
|
||||||
// 呼び出し回数でテスト成否を判定
|
// 呼び出し回数でテスト成否を判定
|
||||||
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
||||||
@ -175,8 +202,15 @@ describe("licenseAlert", () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
|
await licenseAlertProcessing(
|
||||||
|
context,
|
||||||
|
source,
|
||||||
|
redisClient,
|
||||||
|
sendgridMock,
|
||||||
|
adb2cMock
|
||||||
|
);
|
||||||
expect(spySend.mock.calls).toHaveLength(1);
|
expect(spySend.mock.calls).toHaveLength(1);
|
||||||
|
redisClient.quit;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -211,6 +245,7 @@ export class AdB2cServiceMock {
|
|||||||
*/
|
*/
|
||||||
async getUsers(
|
async getUsers(
|
||||||
context: InvocationContext,
|
context: InvocationContext,
|
||||||
|
redisClient: RedisClient,
|
||||||
externalIds: string[]
|
externalIds: string[]
|
||||||
): Promise<AdB2cUser[]> {
|
): Promise<AdB2cUser[]> {
|
||||||
const AdB2cMockUsers: AdB2cUser[] = [
|
const AdB2cMockUsers: AdB2cUser[] = [
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user