✨ feat(后台): 批量邮件

This commit is contained in:
luying
2023-06-03 20:02:38 +08:00
parent 6e5a8affe9
commit ac877d9561
12 changed files with 435 additions and 308 deletions
@@ -13,7 +13,7 @@ import { taflush } from '../../../services/sdkService';
import { ActivityInRemote } from '../../../domain/activityField/activityField'; import { ActivityInRemote } from '../../../domain/activityField/activityField';
import { pushCurrentTime } from '../../../services/auctionService'; import { pushCurrentTime } from '../../../services/auctionService';
import { SurveyModel, SurveyUpdate } from '../../../db/Survery'; import { SurveyModel, SurveyUpdate } from '../../../db/Survery';
import { getSurvey } from '../../../services/gmService'; import { compaireMailGoods, getSurvey } from '../../../services/gmService';
import { sendMessageToAllWithSuc } from '../../../services/pushService'; import { sendMessageToAllWithSuc } from '../../../services/pushService';
import { sendMailByContent, sendMailsByGmMail } from '../../../services/mailService'; import { sendMailByContent, sendMailsByGmMail } from '../../../services/mailService';
import { saveMemory } from '../../../services/log/memoryLogService'; import { saveMemory } from '../../../services/log/memoryLogService';
@@ -69,13 +69,28 @@ export class GmHandler {
//对接gm后台,下发邮件 //对接gm后台,下发邮件
async sendMail(gmmail: GMMailType, isPass: boolean, uid: number) { async sendMail(gmmail: GMMailType, isPass: boolean, uid: number) {
let id = gmmail._id; let id = gmmail._id;
let { receivers, mailType, status, timeType, startTime, circleHour, circleDay } = gmmail; let { receivers, mailType, status, hasGoods, goods } = gmmail;
if(status != GM_MAIL_STATUS.CREATE) { if(status != GM_MAIL_STATUS.CREATE) {
return resResult(STATUS.GM_MAIL_HAS_SENT); return resResult(STATUS.GM_MAIL_HAS_SENT);
} }
if(isPass) { if(isPass) {
await sendMailsByGmMail([gmmail]); let gmmailArr: GMMailType[] = [];
if(mailType == GM_MAIL_TYPE.SINGLE && hasGoods && (!goods || goods.length == 0)) {
for(let receiver of receivers) {
let index = gmmailArr.findIndex(gmmail => compaireMailGoods(gmmail, receiver));
if(index == -1) {
gmmailArr.push({...gmmail, receivers: [receiver], goods: receiver.rewards, hasGoods: receiver?receiver.rewards.length > 0: false });
} else {
gmmailArr[index].receivers.push(receiver);
}
}
} else {
gmmailArr.push(gmmail);
}
await sendMailsByGmMail(gmmailArr);
} }
await GMMailModel.updateMailById(id, { status: isPass? GM_MAIL_STATUS.PASS: GM_MAIL_STATUS.NOT_PASS, viewAt: new Date(), viewBy: uid }, uid); await GMMailModel.updateMailById(id, { status: isPass? GM_MAIL_STATUS.PASS: GM_MAIL_STATUS.NOT_PASS, viewAt: new Date(), viewBy: uid }, uid);
return resResult(STATUS.SUCCESS); return resResult(STATUS.SUCCESS);
+16 -1
View File
@@ -6,7 +6,7 @@ import { GroupMessageType } from "../db/GroupMessage";
import { pinus } from "pinus"; import { pinus } from "pinus";
import { ServerlistModel, ServerlistType } from "../db/Serverlist"; import { ServerlistModel, ServerlistType } from "../db/Serverlist";
import { sendMailsByGmMail } from "./mailService"; import { sendMailsByGmMail } from "./mailService";
import GMMail, { GMMailModel } from '../db/GMMail'; import GMMail, { GMMailModel, GMMailType, Receiver } from '../db/GMMail';
import { getPastTime, nowSeconds } from "../pubUtils/timeUtil"; import { getPastTime, nowSeconds } from "../pubUtils/timeUtil";
import { CreateServerParam } from "../domain/backEndField/params"; import { CreateServerParam } from "../domain/backEndField/params";
import { SignInData } from "../domain/activityField/signInField"; import { SignInData } from "../domain/activityField/signInField";
@@ -386,3 +386,18 @@ export async function calHiddenData(uid: number) {
} }
await HiddenDataModel.updateHiddenData(getPastTime(), heroes, goods, uid); await HiddenDataModel.updateHiddenData(getPastTime(), heroes, goods, uid);
} }
export function compaireMailGoods(gmmail: GMMailType, receiver: Receiver) {
if(gmmail.hasGoods == false && (!receiver.rewards || receiver.rewards.length)) return true;
let tmpGoods = gmmail.goods||[];
let receiverGoods = receiver.rewards||[];
if(tmpGoods.length != receiverGoods.length) return false;
tmpGoods.sort((a, b) => a.id - b.id);
receiverGoods.sort((a, b) => a.id - b.id);
for(let i = 0; i < tmpGoods.length; i++) {
if(!receiverGoods[i]) return false;
if(tmpGoods[i].id != receiverGoods[i].id || tmpGoods[i].count != receiverGoods[i].count) return false;
}
return true
}
+10
View File
@@ -47,6 +47,16 @@ export default class MailController extends Controller {
public async updateGMMail() { public async updateGMMail() {
const { ctx } = this; const { ctx } = this;
const msg = ctx.request.body; const msg = ctx.request.body;
if(msg.receivers && msg.receivers.length > 0) {
try {
for(let receiver of msg.receivers) {
if(receiver.rewards) receiver.rewards = JSON.parse(receiver.rewards);
}
} catch(e) {
return ctx.body = ctx.service.utils.resResult(STATUS.WRONG_PARMS);
}
}
let params = new UpdateMailParams(msg); let params = new UpdateMailParams(msg);
let check = params.checkParams(); let check = params.checkParams();
if(!check) return ctx.body = ctx.service.utils.resResult(STATUS.WRONG_PARMS); if(!check) return ctx.body = ctx.service.utils.resResult(STATUS.WRONG_PARMS);
+72 -3
View File
@@ -1,4 +1,4 @@
import { Controller } from 'egg'; import { Controller, FileStream } from 'egg';
import { Stream } from 'stream'; import { Stream } from 'stream';
import { STATUS } from '@consts'; import { STATUS } from '@consts';
const fs = require('fs'); const fs = require('fs');
@@ -13,10 +13,14 @@ const folderName = 'hot_update_files';
const hotUpdateAddr = `/root/${folderName}`; const hotUpdateAddr = `/root/${folderName}`;
const publishPath = '/root/hot_update_backup'; const publishPath = '/root/hot_update_backup';
import { exec } from 'child_process' import { exec } from 'child_process'
import { reloadResources } from '@pubUtils/data'; import { gameData, reloadResources } from '@pubUtils/data';
import { decodeArrayStr } from '@pubUtils/util'; import { decodeArrayStr, genCode, parseGoodStr } from '@pubUtils/util';
import { getLocalImgUrl, getLocalQrCodeUrl, getRemoteImgUrl, getRemoteQrCodeUrl } from '@pubUtils/battleUtils'; import { getLocalImgUrl, getLocalQrCodeUrl, getRemoteImgUrl, getRemoteQrCodeUrl } from '@pubUtils/battleUtils';
import { RewardInter } from '@pubUtils/interface';
import { RoleModel } from '@db/Role';
import { isNumber, isString } from 'util';
const sendToWormhole = require('stream-wormhole'); const sendToWormhole = require('stream-wormhole');
const XLSX = require('xlsx');
export default class UploadController extends Controller { export default class UploadController extends Controller {
@@ -328,6 +332,71 @@ public async uploadQrCode() {
return; return;
} }
} }
public async uploadMailExcel() {
const { ctx } = this;
try {
const { ctx } = this;
const stream = await ctx.getFileStream();
let exceldata = await this.streamToExcekData(stream);
let result: { code: string, env: string, serverId: number, roleId: string, roleName: string, rewards: string, isError: boolean, isNameError: boolean, isRewardError: boolean }[] = [];
for(let i = 1; i < exceldata.length; i++) {
let data = exceldata[i];
let newObj = { code: genCode(5), env: ctx.app.config.realEnv, serverId: data.serverId, roleId: data.roleId, roleName: '', rewards: '', isError: true, isNameError: true, isRewardError: true };
if(data.roleId && isString(data.roleId)) {
let role = await RoleModel.findByRoleId(data.roleId, 'roleName serverId');
if(role && role.serverId == data.serverId) {
newObj.roleName = role.roleName;
newObj.serverId = role.serverId;
newObj.isNameError = false;
}
}
if(data.rewards && isString(data.rewards)) {
try {
let rewards: RewardInter[] = parseGoodStr(data.rewards);
let isRewardError = false;
for(let { id, count } of rewards) {
let dicGoods = gameData.goods.get(id);
if(!dicGoods) { isRewardError = true; break; }
if(!isNumber(count) || count <= 0) { isRewardError = true; break; }
}
newObj.rewards = JSON.stringify(rewards);
newObj.isRewardError = isRewardError;
} catch(e) {
console.log('updateMailExcel', e)
}
}
newObj.isError = newObj.isNameError || newObj.isRewardError;
result.push(newObj);
}
ctx.body = ctx.service.utils.resResult(STATUS.SUCCESS, { result });
return;
} catch (e) {
console.error(e)
ctx.body = ctx.service.utils.resResult(STATUS.WRONG_PARMS, { result: [] });
return;
}
}
public async streamToExcekData(stream: FileStream): Promise<{ serverId: number, roleId: string, rewards: string }[]> {
return new Promise((resolve, reject) => {
let exceldata = [];
stream.on('error', reject);
stream.on('data', function(chunk) {
const workbook = XLSX.read(chunk);
for(let sheet in workbook.Sheets) {
if(workbook.Sheets.hasOwnProperty(sheet)) {
exceldata = XLSX.utils.sheet_to_json(workbook.Sheets[sheet]);
break;
}
}
});
stream.on('end', () => resolve(exceldata))
});
}
} }
export function childExec(commond: string) { export function childExec(commond: string) {
+1
View File
@@ -13,6 +13,7 @@ export default (app: Application) => {
router.post('/api/upload/uploadjson', tokenParser, controller.upload.uploadJson); router.post('/api/upload/uploadjson', tokenParser, controller.upload.uploadJson);
router.post('/api/upload/reloadresource', tokenParser, controller.upload.reloadResource); router.post('/api/upload/reloadresource', tokenParser, controller.upload.reloadResource);
router.post('/api/upload/uploadqrcode', tokenParser, controller.upload.uploadQrCode); router.post('/api/upload/uploadqrcode', tokenParser, controller.upload.uploadQrCode);
router.post('/api/upload/uploadmailexcel', tokenParser, controller.upload.uploadMailExcel);
router.post('/api/gmaccount/getgmlist', tokenParser, controller.gmaccount.getGmList); router.post('/api/gmaccount/getgmlist', tokenParser, controller.gmaccount.getGmList);
router.post('/api/gmaccount/createaccount', tokenParser,controller.gmaccount.createGmAccount); router.post('/api/gmaccount/createaccount', tokenParser,controller.gmaccount.createGmAccount);
+1 -1
View File
@@ -23,7 +23,7 @@ export default class Mail extends Service {
role = await RoleModel.findByRoleId(roleId); role = await RoleModel.findByRoleId(roleId);
} }
if(roleName) { if(roleName) {
role = await RoleModel.findByRoleName(roleName); role = await RoleModel.findByRoleName(roleName, serverId);
} }
if(!role || role.serverId != serverId) return this.ctx.service.utils.resResult(STATUS.WRONG_PARMS); if(!role || role.serverId != serverId) return this.ctx.service.utils.resResult(STATUS.WRONG_PARMS);
return this.ctx.service.utils.resResult(STATUS.SUCCESS, { env: this.ctx.app.config.realEnv, serverId, roleId: role.roleId, roleName: role.roleName }) return this.ctx.service.utils.resResult(STATUS.SUCCESS, { env: this.ctx.app.config.realEnv, serverId, roleId: role.roleId, roleName: role.roleName })
+2 -2
View File
@@ -47,9 +47,9 @@ export default (appInfo: EggAppInfo) => {
fileSize: '100mb', fileSize: '100mb',
mode: 'stream', mode: 'stream',
whitelist: [ whitelist: [
'.json', '.ts', '.zip', '.jpg', '.png', '.jpeg', '.mp4', '.gif' '.json', '.ts', '.zip', '.jpg', '.png', '.jpeg', '.mp4', '.gif', '.xls', '.xlsx'
], ],
fileExtensions: ['.json', '.ts', '.zip', '.jpg', '.png', '.jpeg', '.mp4', '.gif'], // 扩展几种上传的文件格式 fileExtensions: ['.json', '.ts', '.zip', '.jpg', '.png', '.jpeg', '.mp4', '.gif', '.xls', '.xlsx'], // 扩展几种上传的文件格式
autoFields: true autoFields: true
}; };
+4 -2
View File
@@ -36,10 +36,12 @@
"egg-cors": "^2.2.3", "egg-cors": "^2.2.3",
"egg-http-proxy-middleware": "^1.0.3", "egg-http-proxy-middleware": "^1.0.3",
"egg-scripts": "^2.6.0", "egg-scripts": "^2.6.0",
"egg-xtransit": "^2.0.0",
"exceljs": "^4.3.0", "exceljs": "^4.3.0",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"formstream": "^1.1.1", "formstream": "^1.1.1",
"moment": "^2.29.0", "moment": "^2.29.0",
"node-xlsx": "^0.23.0",
"pump": "^3.0.0", "pump": "^3.0.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"stream-wormhole": "^1.1.0", "stream-wormhole": "^1.1.0",
@@ -53,12 +55,12 @@
"@types/supertest": "^2.0.0", "@types/supertest": "^2.0.0",
"autod": "^3.0.1", "autod": "^3.0.1",
"autod-egg": "^1.1.0", "autod-egg": "^1.1.0",
"egg-ci": "^1.8.0",
"egg-bin": "^4.11.0", "egg-bin": "^4.11.0",
"egg-ci": "^1.8.0",
"egg-mock": "^3.16.0", "egg-mock": "^3.16.0",
"tslib": "^1.9.0",
"eslint": "^6.7.2", "eslint": "^6.7.2",
"eslint-config-egg": "^8.0.0", "eslint-config-egg": "^8.0.0",
"tslib": "^1.9.0",
"typescript": "^3.0.0" "typescript": "^3.0.0"
}, },
"engines": { "engines": {
+5 -3
View File
@@ -10,7 +10,7 @@ import { GMMail as StategyMail } from './ServerStategy';
import { ServerlistType } from './Serverlist'; import { ServerlistType } from './Serverlist';
import moment = require('moment'); import moment = require('moment');
class Reward { class MailReward {
@prop({ required: true }) @prop({ required: true })
id: number; id: number;
@prop({ required: true }) @prop({ required: true })
@@ -26,6 +26,8 @@ export class Receiver {
roleId?: string; roleId?: string;
@prop({ required: true }) @prop({ required: true })
roleName?: string; roleName?: string;
@prop({ required: true, type: MailReward, _id: false })
rewards?: MailReward[];
} }
export default class GMMail extends BaseModel { export default class GMMail extends BaseModel {
@@ -33,8 +35,8 @@ export default class GMMail extends BaseModel {
@prop({ required: false}) @prop({ required: false})
hasGoods: boolean; // 有效时间,单位小时 hasGoods: boolean; // 有效时间,单位小时
@prop({ required: true, type: Reward, default: [], _id: false }) @prop({ required: true, type: MailReward, default: [], _id: false })
goods: Reward[]; goods: MailReward[];
@prop({ required: true, default: true }) @prop({ required: true, default: true })
timeType: MAIL_TIME_TYPE; // 邮件时间类型 timeType: MAIL_TIME_TYPE; // 邮件时间类型
+2 -2
View File
@@ -404,8 +404,8 @@ export default class Role extends BaseModel {
return role; return role;
} }
public static async findByRoleName(roleName: string, select?: string, getters = false, virtuals = true) { public static async findByRoleName(roleName: string, serverId: number, select?: string, getters = false, virtuals = true) {
const role: RoleType = await RoleModel.findOne({ roleName }).select(select).lean({ getters, virtuals }); const role: RoleType = await RoleModel.findOne({ roleName, serverId }).select(select).lean({ getters, virtuals });
return role; return role;
} }
+8 -2
View File
@@ -20,14 +20,14 @@ export class UpdateMailParams {
content: string; content: string;
sendName: string; sendName: string;
mailType: GM_MAIL_TYPE; // 收件人类型 mailType: GM_MAIL_TYPE; // 收件人类型
receivers: {env: string; serverId: number; roleId: string; roleName: string; }[]; receivers: {env: string; serverId: number; roleId: string; roleName: string; rewards: {id: number; count: number}[] }[];
reason: string; // 原因 reason: string; // 原因
isSp: boolean = false; // 特殊邮件 isSp: boolean = false; // 特殊邮件
isSingle: boolean; isSingle: boolean;
constructor(obj: UpdateMailParams) { constructor(obj: UpdateMailParams) {
this.goods = obj.goods; this.goods = obj.goods;
this.hasGoods = obj.goods?.length > 0; if(obj.goods && obj.goods?.length > 0) this.hasGoods = true;
this.timeType = obj.timeType; this.timeType = obj.timeType;
this.expire = obj.expire; this.expire = obj.expire;
this.startTime = obj.startTime; this.startTime = obj.startTime;
@@ -44,6 +44,12 @@ export class UpdateMailParams {
this.mailType = GM_MAIL_TYPE.SERVER; this.mailType = GM_MAIL_TYPE.SERVER;
} }
this.receivers = obj.receivers; this.receivers = obj.receivers;
if(obj.receivers && obj.receivers.length > 0) {
for(let { rewards } of obj.receivers) {
console.log('#### rewards', rewards)
if(rewards && rewards.length > 0) this.hasGoods = true;
}
}
this.reason = obj.reason; this.reason = obj.reason;
this.isSp = obj.isSp; this.isSp = obj.isSp;
} }
+7
View File
@@ -999,5 +999,12 @@
"name": "上传二维码", "name": "上传二维码",
"module": "server", "module": "server",
"type": "update" "type": "update"
},
{
"id": 144,
"api": "/api/upload/uploadmailexcel",
"name": "上传excel",
"module": "server",
"type": "update"
} }
] ]