import moment from 'moment'; import _ from 'lodash'; import axios from 'axios'; import fs from 'fs'; import { promises as fsPromises } from 'fs'; import path from 'path'; import COS from 'cos-nodejs-sdk-v5'; import config from '../../config/index.js'; import { globalCache } from '../../util/GlobalCache.js'; import constantClass from '../../util/constant/index.js'; import { stringUtils } from '../../util/stringClass.js'; import miaoguo from '../../model/miaoguo.js'; import commonModel from '../../model/commonModel.js'; import hanzi from '../../model/hanzi.js'; import { Decrypt, Encrypt } from '../../util/crypto/index.js'; import * as payInfo from '../../model/payInfo.js'; import * as wxusersall from '../../model/wxUsersAll.js'; import WXBizDataCrypt from '../../util/WXBizDataCrypt.js'; async function requestMiaoguoSession(param) { const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${config.wx.miaoguo_appid}&secret=${config.wx.miaoguo_appsecret}&js_code=${param.Code}&grant_type=authorization_code`; try { const res = await axios.get(url, { transformResponse: [(data) => data], }); const htmlString = res.data; const json = JSON.parse(htmlString); if (json && json.openid) { param.OpenID = json.openid; param.sessionKey = json.session_key; if (json.unionid) param.UnionID = json.unionid; return { errcode: 10000 }; } return htmlString; } catch (err) { return { errcode: 101, errStr: err.message || String(err) }; } } async function syncMiaoguoWXUsersAll(userInfo) { let obj = { UnionID: userInfo.UnionID, UserID: userInfo.UserID, ProductID: 166, }; let wxUserList = await wxusersall.GetWXUsersAllInfo(obj); if (!wxUserList || wxUserList.length == 0) { obj = { UserID: userInfo.UserID, ProductID: 166, }; wxUserList = await wxusersall.GetWXUsersAllInfo(obj); if (wxUserList && wxUserList.length > 0) { obj = { UnionID: userInfo.UnionID, ChangYiID: wxUserList[0].ChangYiID, }; await wxusersall.UpdateWXUsersAll(obj); } else { obj = { UnionID: userInfo.UnionID, }; wxUserList = await wxusersall.GetWXUsersAllInfo(obj); if (wxUserList && wxUserList.length > 0) { obj = { UserID166: userInfo.UserID, ChangYiID: wxUserList[0].ChangYiID, }; await wxusersall.UpdateWXUsersAll(obj); } else { obj = { UnionID: userInfo.UnionID, UserID166: userInfo.UserID, }; await wxusersall.AddWXUsersAll(obj); } } } } export async function MiaoguoLogin(ctx) { let param = ctx.request.body || {}; if (param.Introducer) { if (param.Introducer.toString().indexOf("UserID") >= 0) param.Introducer = Number(param.Introducer.replace("UserID=", "")); } if (param.param) { const paramStr = Decrypt(param.param, config.urlSecrets.aes_key, config.urlSecrets.aes_iv); param = JSON.parse(paramStr); } let result = await requestMiaoguoSession(param); if (result.errcode == 10000) { delete param.Code; if (param.sessionKey && param.iv && param.encryptedData) { const pc = new WXBizDataCrypt(config.wx.miaoguo_appid, param.sessionKey); const dataUnionID = pc.decryptData(param.encryptedData, param.iv); param.UnionID = dataUnionID.unionId; } delete param.sessionKey; delete param.iv; delete param.encryptedData; let currentUserID = 0; let userList = {}; if (param.UserFamilyID && param.UserFamilyID > 0) { userList = await miaoguo.GetUsersInfo(param); if (userList && userList.length > 0) currentUserID = userList[0].UserID; const param2 = { UserID: param.UserFamilyID }; userList = await miaoguo.GetUsersInfoByUserID(param2); param.OpenID = userList[0].OpenID; param.UnionID = userList[0].UnionID; } else { userList = await miaoguo.GetUsersInfo(param); if (userList && userList.length > 0) { currentUserID = userList[0].UserID; if (param.UnionID && userList[0].UserID && userList[0].OpenID && (!userList[0].UnionID || userList[0].UnionID == "")) { const paramUnionID = {}; paramUnionID.OpenID = userList[0].OpenID; paramUnionID.UnionID = param.UnionID; await miaoguo.UpdateUsers(paramUnionID); userList[0].UnionID = param.UnionID; } } } if (userList && userList.length > 0) { let isLoseEfficary = 0; const time3 = moment().format('YYYYMMDD'); const time4 = moment(userList[0].ProductServiceTime).format('YYYYMMDD'); if (time3 > time4) { let obj = {}; obj.ParentUserID = userList[0].UserID; await miaoguo.UpdateUserBinding(obj); obj = {}; obj.UserID = userList[0].UserID; await miaoguo.DeleteMiaoguoFamily(obj); const param2 = {}; param2.UserID = currentUserID; userList = await miaoguo.GetUsersInfoByUserID(param2); param.OpenID = userList[0].OpenID; param.UnionID = userList[0].UnionID; isLoseEfficary = 1; } delete param.UserFamilyID; param.LastLoginTime = new Date(); if (!userList[0].IsMember && param.Introducer && userList[0].UserID != param.Introducer) { const param2 = {}; param2.UserID = userList[0].UserID; const wechatUserInfo = await miaoguo.GetWechatServiceInfoByMGUserID(param2); if (wechatUserInfo && wechatUserInfo[0]) { if (!wechatUserInfo[0].IsProbation || wechatUserInfo[0].IsProbation == 0) { const obj2 = {}; obj2.UserID = wechatUserInfo[0].WechatUserID; obj2.Introducer = param.Introducer; await miaoguo.UpdateWechatServiceUsersByUserID(obj2); } else { delete param.Introducer; } } } else { delete param.Introducer; } delete param.UserSource; delete param.SourceID; if (param.NickName == "陌生用户") { delete param.NickName; delete param.AvatarUrl; delete param.Language; delete param.Gender; delete param.City; delete param.Province; delete param.Country; } if (param.NickName == "微信用户") param.NickName = "陌生用户"; if (!userList[0].ShowUserID || userList[0].ShowUserID == "") param.ShowUserID = stringUtils.Random(100, 999).toString() + userList[0].UserID.toString() + stringUtils.Random(100, 999).toString(); if (ctx.query.IsPromotion) { if (!userList[0].PromotionLimitTime) param.PromotionLimitTime = userList[0].ProductServiceTime; } await miaoguo.UpdateUsers(param); userList = await miaoguo.GetUsersInfo(param); if ((!ctx.query.IsPromotion || userList[0].IsPromoter == 0) && userList && userList.length > 0) { delete userList[0].UserTree; delete userList[0].PromotionLimitTime; delete userList[0].WXAccount; delete userList[0].RealName; delete userList[0].IdentityCard; delete userList[0].Phone; } else { if (userList[0] && userList[0].PromotionLimitTime) { const a = moment(userList[0].PromotionLimitTime); const b = moment(); const days = a.diff(b, 'days'); userList[0].PromotionLimitDays = days; userList[0].PromotionLimitTime = moment(userList[0].PromotionLimitTime).format('YYYY.MM.DD'); userList[0].WXServiceCharge = config.WXServiceCharge; userList[0].IndividualIncomeTax = config.IndividualIncomeTax; if (!userList[0].PayeeWechatAccount) userList[0].PayeeWechatAccount = userList[0].NickName; if (!userList[0].WXAccount) userList[0].WXAccount = userList[0].NickName; if (!userList[0].RealName) userList[0].RealName = ""; if (userList[0].Introducer) { const param2 = {}; param2.UserID = userList[0].Introducer; param2.FieldStr = "NickName,AvatarUrl"; const userInfo2 = await miaoguo.GetUsersInfoByUserID(param2); if (userInfo2 && userInfo2[0]) { userList[0].IntroducerNickName = userInfo2[0].NickName; userList[0].IntroducerAvatarUrl = userInfo2[0].AvatarUrl; } } } } userList[0].ProductServiceTime = moment(userList[0].ProductServiceTime).format('YYYY/MM/DD 23:59:59'); if (isLoseEfficary == 1) { userList[0].IsLoseEfficacy = 1; currentUserID = userList[0].UserID; } userList[0].IsNewUser = 0; } else { delete param.UserFamilyID; param.CreateTime = new Date(); param.LastLoginTime = param.CreateTime; param.ProductServiceTime = param.CreateTime; param.PromotionDeliveryTimeIsUp = '2099-12-31 23:59:59'; if (param.NickName == "微信用户") param.NickName = "陌生用户"; await miaoguo.AddUsers(param); userList = await miaoguo.GetUsersInfo(param); userList[0].IsNewUser = 1; if (!userList[0].ShowUserID || userList[0].ShowUserID == "") userList[0].ShowUserID = stringUtils.Random(100, 999).toString() + userList[0].UserID.toString() + stringUtils.Random(100, 999).toString(); const param3 = {}; param3.UserID = userList[0].UserID || 0; param3.CardMaxNumberNew = 10; param3.CardMaxNumberHistory = 40; param3.BasicCardLimit = param3.CardMaxNumberNew + param3.CardMaxNumberHistory; param3.CardMaxNumberNewUrgent = 10; param3.CardMaxNumberHistoryUrgent = 40; param3.CardType = 0; param3.SortTypeIndex = 0; param3.MemoryLevel = 0; param3.ClickType = 0; param3.SecondConfigArray = "0,0,0"; param3.ColorIndexArr = "1,2,3,4"; await miaoguo.AddUserConfig(param3); } if (userList[0].ParentUserID > 0) { const userid = userList[0].UserID; const obj = {}; obj.UserID = userList[0].ParentUserID; userList = await miaoguo.GetUsersInfoByUserID(obj); userList[0].ChildUserID = userid; } userList[0].Subscribe = 0; if (userList[0].UnionID) { await syncMiaoguoWXUsersAll(userList[0]); const obj2 = { UnionID: userList[0].UnionID, }; const wccUserList = await miaoguo.GetWechatServiceUsersInfoByUnionID(obj2); if (wccUserList && wccUserList[0]) { userList[0].Subscribe = wccUserList[0].Subscribe; userList[0].IsProbation = wccUserList[0].IsProbation; if (userList[0].IsProbation == 0 && userList[0].IsPay == 1) { userList[0].IsProbation = 1; } else if (userList[0].IsProbation == 0 && userList[0].UserID < 1000) { const time4 = moment(userList[0].ProductServiceTime).format('YYYYMMDD'); if (time4 > '20190801') { userList[0].IsProbation = 1; } } if (wccUserList[0].TempClassID > 0 || wccUserList[0].TempIsMember > 0) { const obj3 = {}; obj3.UserID = userList[0].UserID; if (wccUserList[0].TempClassID && wccUserList[0].TempClassID > 0) obj3.ClassID = wccUserList[0].TempClassID; if (wccUserList[0].TempIsMember && wccUserList[0].TempIsMember > 0) obj3.IsMember = wccUserList[0].TempIsMember; await miaoguo.UpdateUsersByUserID(obj3); const obj4 = {}; obj4.UserID = wccUserList[0].UserID; if (wccUserList[0].TempClassID && wccUserList[0].TempClassID > 0) obj4.TempClassID = 0; if (wccUserList[0].TempIsMember && wccUserList[0].TempIsMember > 0) obj4.TempIsMember = 0; await miaoguo.UpdateWechatServiceUsersByUserID(obj4); } } } delete userList[0].OpenID; delete userList[0].UnionID; const param2 = {}; param2.ProgramID = 166; param2.Version = param.ProgramVersion; const result3 = await commonModel.GetProductVersionList(param2); if (result3 && result3[0]) { if ((param2.Version == result3[0].Version && result3[0].IsShowPay <= 0) || param2.Version > result3[0].Version) { userList[0].IsShow = result3[0].IsShowPay; } else { userList[0].IsShow = 1; } if (process.env.NODE_ENV != 'development') { const today = moment().format('YYYYMMDD'); if ((userList[0].UserSource == '1001' && userList[0].System == "iOS 10.0.1") || (!userList[0].UserSource && (!userList[0].LastUserSource || userList[0].LastUserSource > 10000)) || ((userList[0].NickName || "").indexOf("dgztest") >= 0)) { userList[0].IsShow = -1; } else if (userList[0].UserID == 28631 && today < "20230526") { userList[0].IsShow = -1; } else if (userList[0].LastUserSource == '1012' && userList[0].IsPay == 0 && userList[0].IsMember == 0 && userList[0].IsApply == 0 && userList[0].IsTryOut == 0 && userList[0].Model == "iPhone XS MAX China-exclusive") { userList[0].IsShow = -1; } } if (userList[0].IsMember === 1) userList[0].IsShow = 1; if (userList[0].IsShow == 1) userList[0].ShowText1 = "分享"; else userList[0].ShowText1 = ""; } if (userList[0].ClassID > 0) { const classList = await miaoguo.GetClassList(userList[0]); if (classList && classList[0]) { userList[0].ClassStartDate = moment(classList[0].StartDate).format('YYYY-MM-DD'); userList[0].ClassEndDate = moment(classList[0].StartDate).add(classList[0].DayNumber - 1, "days").format('YYYY-MM-DD'); userList[0].ClassDayNumber = moment(moment().format("YYYY-MM-DD 00:00:00")).diff(classList[0].StartDate + " 00:00:00", 'days'); userList[0].ClassTotalDayNumber = classList[0].DayNumber; } } if (userList[0].IsShow <= 0 || userList[0].IsPay == 0) { userList[0].HasIntroducer = 1; } else { userList[0].HasIntroducer = 0; } if (userList[0].UserID == 0) { userList[0].IsMaintain = 1; } if (currentUserID > 0) { userList[0].CurrentUserID = currentUserID; userList[0].MainUserID = currentUserID; } result = { errcode: 10000, result: userList[0] }; const log = {}; log.UserID = userList[0].UserID; log.LogID = 374; log.Content = userList[0]; log.Content = JSON.stringify(log.Content); miaoguo.AddMiaoguoLog(log).catch(err => console.error('AddMiaoguoLog failed:', err)); } ctx.body = result; } export async function GetClassList(ctx) { const param = { ClassID: ctx.query.ClassID || 0, Sort: ctx.query.Sort || "desc", Flag: ctx.query.Flag, SqlType: ctx.query.SqlType, }; const list = await miaoguo.GetClassList(param); for (const item of list) { item.EndDate = moment(item.StartDate).add(item.DayNumber, 'days').format('YYYY-MM-DD'); } ctx.body = { errcode: 10000, result: list }; } export async function GetMiaoguoCoupon(ctx) { const param = { UserID: ctx.query.UserID || 0, CouponType: ctx.query.CouponType || 0, }; const list = await miaoguo.GetMiaoguoCoupon(param); if (param.CouponType == 0) { for (const item of list) { const arr = item.Name.split("#"); item.Content = arr; delete item.Name; const today = moment().format("YYYY年MM月DD日"); item.LimitTime = moment(item.LimitTime).format("YYYY年MM月DD日"); if (today > item.LimitTime) item.IsLoseEfficacy = 1; } } ctx.body = { errcode: 10000, result: list }; } export async function GetUserFolderList(ctx) { const param = { UserID: ctx.query.UserID || 0, OrderType: ctx.query.OrderType, IsNewData: ctx.query.IsNewData || 0, }; const url = "GetUserFolderList?UserID=" + param.UserID; let result = globalCache.get(url); if (param.IsNewData == 1 || param.IsNewData == "1") result = 0; if (result === 0) { if (!param.OrderType) { const userConfig = await miaoguo.GetUserConfigData(param); if (userConfig && userConfig.length > 0) param.OrderType = userConfig[0].FolderOrder; param.OrderType = param.OrderType || 'UpdateTime desc'; } const list = await miaoguo.GetUserFolderList(param); const arr = []; for (const item of list) arr.push(item.ID); param.FolderIDStr = arr.join(","); const list2 = await miaoguo.GetUserFolderCount(param); for (const item of list) { for (const countItem of list2) { if (item.ID == countItem.FolderID) item.Total = countItem.Total; } } result = list; if (param.OrderType == "CreateTime desc") result = _.orderBy(result, ['CreateTime'], ['desc']); else if (param.OrderType == "CreateTime asc") result = _.orderBy(result, ['CreateTime'], ['asc']); else if (param.OrderType == "UpdateTime desc") result = _.orderBy(result, ['UpdateTime'], ['desc']); else if (param.OrderType == "UpdateTime asc") result = _.orderBy(result, ['UpdateTime'], ['asc']); else if (param.OrderType == "Total desc") result = _.orderBy(result, ['Total'], ['desc']); else if (param.OrderType == "Total asc") result = _.orderBy(result, ['Total'], ['asc']); else if (param.OrderType == "Name desc") result = _.orderBy(result, ['Name'], ['desc']); else if (param.OrderType == "Name asc") result = _.orderBy(result, ['Name'], ['asc']); globalCache.set(url, result, config.BufferMemoryTimeLowNormal); } ctx.body = { errcode: 10000, result }; } export async function GetUserFolderInfo(ctx) { const param = { UserID: ctx.query.UserID || 0, FolderID: ctx.query.FolderID || 0, }; const url = "GetUserFolderInfo?FolderID=" + param.FolderID; let result = globalCache.get(url); if (result === 0) { const list = await miaoguo.GetUserFolderList(param); for (const item of list) { if (item.ID == param.FolderID) result = item; } globalCache.set(url, result, config.BufferMemoryTimeLowNormal); } ctx.body = { errcode: 10000, result }; } function decryptWebID(value) { return Decrypt(String(value).replace(/@@@/g, '+'), config.urlSecrets.aes_key, config.urlSecrets.aes_iv); } function encryptWebID(value) { return Encrypt(String(value), config.urlSecrets.aes_key, config.urlSecrets.aes_iv); } function decryptWebIDList(list) { if (!list) return []; const source = Array.isArray(list) ? list : String(list).split(','); return source .filter(item => item !== undefined && item !== null && item !== '') .map(item => Number(decryptWebID(item))); } function formatMiaoguoCardTime(card, isDeleteList = false) { if (card.CreateTime) { card.CreateTime = moment(card.CreateTime).format('YYYY-MM-DD HH:mm:ss'); card.CreateTimeStr = stringUtils.GetDateTimeFormat(card.CreateTime); } if (card.UpdateTime) { card.UpdateTime = moment(card.UpdateTime).format('YYYY-MM-DD HH:mm:ss'); card.UpdateTimeStr = stringUtils.GetDateTimeFormat(card.UpdateTime); } if (card.FirstTime) card.FirstTime = moment(card.FirstTime).format('YYYY-MM-DD HH:mm:ss'); if (card.LastTime) { card.LastTimeStr = isDeleteList ? stringUtils.GetDateTimeFormat(card.LastTime) : moment(card.LastTime).format('YYYY-MM-DD'); card.LastTime = moment(card.LastTime).format('YYYY-MM-DD HH:mm:ss'); } if (card.LimitTime) { card.LimitTime = moment(card.LimitTime).format('YYYY-MM-DD HH:mm:ss'); if (!isDeleteList) card.LimitTimeStr = moment(card.LimitTime).format('YYYY-MM-DD'); } if (card.PigeonholeTime) card.PigeonholeTime = moment(card.PigeonholeTime).format('YYYY-MM-DD HH:mm:ss'); return card; } function buildCardListFromRows(rows, folderList = [], isDeleteList = false) { const list = []; let card = null; let content = []; for (let i = 0; i <= rows.length; i++) { if (card != null && (i == rows.length || card.MiaoguoCardID != rows[i].MiaoguoCardID)) { card = formatMiaoguoCardTime(card, isDeleteList); card.Content = content; if (!isDeleteList) delete card.Flag; delete card.MiaoguoContentID; delete card.ContentType; delete card.MiaoguoDeckID; delete card.UserID; if (!isDeleteList) { delete card.HistoryUpdateTime; delete card.HistoryFirstTime; delete card.HistoryLastTime; delete card.HistoryLimitTime; delete card.HistoryIntervalDay; delete card.HistoryRepetitionCoefficient; delete card.HistoryLearnNumber; } if (folderList.length > 0) { const folder = folderList.find(item => item.ID == card.FolderID); if (folder) card.FolderName = folder.Name; } list.push(card); content = []; if (i == rows.length) break; } const item = rows[i]; content.push({ ContentType: item.ContentType, Content: stringUtils.ReplaceAllString(item.Content || '', "''", "'"), }); card = { ...item }; } return list; } async function getCachedMiaoguoCardList(param) { const cacheKey = "GetMiaoguoCard###UserID=" + param.UserID; const cacheValue = globalCache.get(cacheKey); if (cacheValue !== 0) { const list = typeof cacheValue === 'string' ? JSON.parse(cacheValue) : cacheValue; return list.map(item => formatMiaoguoCardTime({ ...item })); } const listID = await miaoguo.GetCardListID(param); const ids = listID.map(item => item.MiaoguoCardID); if (ids.length == 0) return []; const folderList = await miaoguo.GetUserFolderList(param); const rows = await miaoguo.GetCardList({ UserID: param.UserID, IDArr: ids.join(','), OrderType: param.OrderType, }); const list = buildCardListFromRows(rows, folderList); globalCache.set(cacheKey, JSON.stringify(list), config.BufferMemoryTimeLowNormal); return list; } async function getMiaoguoCardListResult(param) { if (param.CardType == 2) param.CardType = "all"; let list = await getCachedMiaoguoCardList(param); let urgentCount = 0; let emptyCount = 0; for (const item of list) { if (item.CardType == 1) urgentCount++; else if (item.CardType == -1) emptyCount++; } const result = { Count: list.length, UrgentCount: urgentCount, EmptyCount: emptyCount, }; if (param.DataType == -1) { result.List = list; return result; } if (param.IsToday == 2) { list = list.filter(item => item.LastTime >= moment().format('YYYY-MM-DD 00:00:00') && item.LimitTime >= moment().add(1, "days").format('YYYY-MM-DD 00:00:00')); result.Count = list.length; param.PageCount = list.length; } else if (param.IsToday == 3) { list = _.orderBy(list, ['LastTime'], ['desc']); let lastDate = ""; const listTemp = []; for (const item of list) { if (item.LastTime < moment().format('YYYY-MM-DD 00:00:00')) { if (lastDate == "") lastDate = moment(item.LastTime).format('YYYY-MM-DD 00:00:00'); if (item.LastTime >= lastDate) listTemp.push(item); } } list = listTemp; result.Count = list.length; param.PageCount = list.length; } else if (param.IsToday == 4) { list = _.orderBy(list, ['LimitTime'], ['desc']) .filter(item => item.LimitTime < moment().format('YYYY-MM-DD 23:59:59') && param.CardType == item.CardType); result.Count = list.length; } else if (param.IsToday == 5 || param.IsToday == 6 || param.IsToday == 7) { list = _.orderBy(list, ['LastTime'], ['desc']); const param3 = { UserID: param.UserID, CreateTime: moment().add(-param.DayNumber, "days").format('YYYY-MM-DD 00:00:00'), DataType: param.IsToday == 5 ? "TimeShort" : param.IsToday == 6 ? "ClickBlue" : "ClickShelve", }; const listID = await miaoguo.GetCardListID(param3); const idSet = new Set(listID.map(item => Number(item.MiaoguoCardID))); list = list.filter(item => idSet.has(Number(item.MiaoguoCardID))); result.Count = list.length; } else { if (param.CardType != "all") { list = list.filter(item => item.CardType === Number(param.CardType)); result.Count = list.length; } if (param.CardType == "all" && param.OrderType.indexOf("LimitTime") >= 0) { list = list.filter(item => item.CardType != -1); result.Count = list.length; } } if (param.IsCollect == 1) { list = list.filter(item => item.IsCollect == 1); result.Count = list.length; } if (param.FolderID) { list = list.filter(item => { if (param.FolderID > 0) return item.FolderID == param.FolderID; if (param.FolderID == -1) return item.FolderID === 0; return true; }); result.Count = list.length; } if (param.Key) { const key = String(param.Key).toLowerCase(); const listTemp = []; for (const item of list) { for (const content of item.Content || []) { if ((content.Content || '').toLowerCase().indexOf(key) >= 0) { listTemp.unshift(item); break; } } } for (let i = 0; i < listTemp.length; i++) { const primary = listTemp[i].Content && listTemp[i].Content[1] ? listTemp[i].Content[1].Content : ""; if (primary && primary.toLowerCase().indexOf(key) >= 0) { const item = listTemp.splice(i, 1); listTemp.unshift(item[0]); } } list = listTemp; result.Count = list.length; } if (param.OrderType == "ac.UpdateTime desc,ac.CreateTime desc") list = _.orderBy(list, ['UpdateTime', 'CreateTime'], ['desc', 'desc']); else if (param.OrderType == "ac.MiaoguoCardID desc") list = _.orderBy(list, ['MiaoguoCardID'], ['desc']); else if (param.OrderType == "ac.MiaoguoCardID asc") list = _.orderBy(list, ['MiaoguoCardID'], ['asc']); else if (param.OrderType == "ac.LearnNumber desc,ac.MiaoguoCardID desc") list = _.orderBy(list, ['LearnNumber', 'MiaoguoCardID'], ['desc', 'desc']); else if (param.OrderType == "ac.LearnNumber,ac.MiaoguoCardID") list = _.orderBy(list, ['LearnNumber', 'MiaoguoCardID'], ['asc', 'asc']); else if (param.OrderType == "ac.LastTime desc") list = _.orderBy(list, ['LastTime'], ['desc']); else if (param.OrderType == "ac.LimitTime desc") list = _.orderBy(list, ['LimitTime'], ['desc']); else if (param.OrderType == "ac.LimitTime asc") list = _.orderBy(list, ['LimitTime'], ['asc']); else if (param.OrderType == "ac.PigeonholeTime asc") list = _.orderBy(list, ['PigeonholeTime'], ['asc']); else if (param.OrderType == "ac.PigeonholeTime desc") list = _.orderBy(list, ['PigeonholeTime'], ['desc']); const arr = []; if (param.IDSelect || param.PrintID) { if (param.PrintID && param.PrintID > 0) { const miaoguoPrint = await miaoguo.GetMiaoguoPrintByID(param); if (miaoguoPrint && miaoguoPrint.length > 0) param.IDSelect = miaoguoPrint[0].CardID; } const selectedIDs = String(param.IDSelect || '').split(','); for (const id of selectedIDs) { const item = list.find(card => String(card.MiaoguoCardID) == String(id)); if (item) arr.push(item); } } else { let index = 0; let canPush = false; const pageIDForCompare = param.Type == "web" && param.PageID > 0 ? encryptWebID(param.PageID) : param.PageID; for (const item of list) { const cardIDForCompare = param.Type == "web" ? encryptWebID(item.MiaoguoCardID) : item.MiaoguoCardID; if (param.PageID == 0 || cardIDForCompare == pageIDForCompare) canPush = true; if (canPush) { if (param.PageID == 0) { arr.push(item); index++; if (index >= param.PageCount) break; } else { if (index > 0) arr.push(item); index++; if (index > param.PageCount) break; } } } } result.List = arr; if (param.Type == "web") { result.List = result.List.map(item => ({ ...item, MiaoguoCardID: encryptWebID(item.MiaoguoCardID), })); if (param.IDSelect) { const miaoguoPrint = await miaoguo.AddMiaoguoPrint({ UserID: param.UserID, CardID: param.IDSelect, }); result.PrintID = miaoguoPrint.insertId; } } return result; } export async function GetMiaoguoCardListWeb(ctx) { const body = ctx.request.body || {}; const param = { ...body, UserID: ctx.query.UserID || "0", PageID: body.PageID ? decryptWebID(body.PageID) : 0, PageCount: ctx.query.PageCount || "10", DataType: body.DataType || "", IsCollect: body.IsCollect == 1 ? 1 : 0, OrderType: body.OrderType || ctx.query.OrderType || "ac.UpdateTime desc", Type: "web", CardType: body.CardType || "2", FolderID: body.FolderID || 0, DayNumber: body.DayNumber || 7, }; if (param.Key) param.PageCount = 500; else if (param.DataType) param.PageCount = 1000; if (body.IDSelect) { const ids = String(body.IDSelect).split(',').map(item => decryptWebID(item)); param.IDSelect = ids.join(','); } const result = await getMiaoguoCardListResult(param); ctx.body = { errcode: 10000, result }; } export async function GetMiaoguoCardList2(ctx) { const param = { UserID: ctx.query.UserID || "0", Key: ctx.query.Key || "", IsToday: ctx.query.IsToday || "0", OrderType: ctx.query.OrderType || "ac.UpdateTime desc,ac.CreateTime desc", PageID: ctx.query.PageID || 0, PageCount: ctx.query.PageCount || 20, IsCollect: ctx.query.IsCollect || 0, Type: ctx.query.Type || "app", DataType: ctx.query.DataType || "", CardType: ctx.query.CardType || "2", IDSelect: ctx.query.IDSelect || "", PrintID: ctx.query.PrintID || 0, FolderID: ctx.query.FolderID || 0, DayNumber: ctx.query.DayNumber || 7, }; const result = await getMiaoguoCardListResult(param); ctx.body = { errcode: 10000, result }; } function clearMiaoguoCardCache(userID) { globalCache.delete("GetMiaoguoCard###UserID=" + userID); } function addMiaoguoLog(log) { miaoguo.AddMiaoguoLog(log).catch(err => console.error('AddMiaoguoLog failed:', err)); } function formatTodayCardTime(item) { item.CreateTime = moment(item.CreateTime).format('YYYY-MM-DD HH:mm:ss'); item.CreateTimeStr = stringUtils.GetDateTimeFormat(item.CreateTime); item.UpdateTime = moment(item.UpdateTime).format('YYYY-MM-DD HH:mm:ss'); item.UpdateTimeStr = stringUtils.GetDateTimeFormat(item.UpdateTime); item.FirstTime = moment(item.FirstTime).format('YYYY-MM-DD HH:mm:ss'); item.LastTime = moment(item.LastTime).format('YYYY-MM-DD HH:mm:ss'); item.LimitTime = moment(item.LimitTime).format('YYYY-MM-DD HH:mm:ss'); item.PigeonholeTime = moment(item.PigeonholeTime).format('YYYY-MM-DD HH:mm:ss'); return item; } function sm2(data, EF) { const result = Math.round(data * EF); return result < 1 ? 1 : result; } function computerMilestone(dayNumber0, dayNumber1, dayNumber2) { return Math.round(100 * (dayNumber0 - dayNumber1) / (dayNumber2 - dayNumber1)) / 100; } export async function GetMiaoguoCardInfo(ctx) { const param = { UserID: ctx.query.UserID || "0", MiaoguoCardID: ctx.query.MiaoguoCardID || "", }; const info = await miaoguo.GetCardInfo(param); if (info && info.length > 0) { const listContent = await miaoguo.GetCardContentList(param.MiaoguoCardID); info[0].Content = listContent; info[0] = formatTodayCardTime(info[0]); } ctx.body = { errcode: 10000, result: info[0] }; } export async function CollectMiaoguoCard(ctx) { const param = { UserID: ctx.query.UserIDSource || "0", UserIDTarget: ctx.query.UserIDTarget || "0", MiaoguoCardID: ctx.query.MiaoguoCardID || "", }; let result = 0; const info = await miaoguo.GetCardInfo(param); if (info && info.length > 0) { const listContent = await miaoguo.GetCardContentList(param.MiaoguoCardID); if (listContent && listContent.length > 1) { param.Content = stringUtils.ReplaceSingleQuotes(listContent[1].Content || ""); let count = await miaoguo.CheckCardInfoExist(param); if (count.length > 0) { if (param.MiaoguoCardID == count[0].MiaoguoCardID) { result = -1; } else { param.Content = stringUtils.ReplaceSingleQuotes(listContent[2]?.Content || ""); param.CardID = count[0].MiaoguoCardID; count = await miaoguo.CheckCardInfoExist(param); if (count.length > 0) { param.Content = stringUtils.ReplaceSingleQuotes(listContent[3]?.Content || ""); param.CardID = count[0].MiaoguoCardID; count = await miaoguo.CheckCardInfoExist(param); result = count.length > 0 ? 0 : 1; } else { result = 1; } } } else { result = 1; } } if (result == 1) { const now = moment().format('YYYY-MM-DD HH:mm:ss'); const insertInfo = await miaoguo.AddCard({ UserID: param.UserIDTarget, CreateTime: now, LimitTime: now, UpdateTime: now, CardType: -1, IsTodayStudy: 0, SourceCardID: param.MiaoguoCardID, }); for (const content of listContent) { await miaoguo.AddCardContent({ MiaoguoCardID: insertInfo.insertId, ContentType: content.ContentType, Content: stringUtils.ReplaceAllString(content.Content || "", "'", "''"), }); } addMiaoguoLog({ UserID: param.UserIDTarget, LogID: 316, Content: JSON.stringify({ ...param, NewMiaoguoCardID: insertInfo.insertId }), }); } } clearMiaoguoCardCache(param.UserIDTarget); ctx.body = { errcode: 10000, result }; } export async function SetGrade(ctx) { const param = { UserID: ctx.query.UserID || "0", Grade: ctx.query.Grade || 0, UpperAndLowerSemesters: ctx.query.UpperAndLowerSemesters || 0, }; await miaoguo.UpdateUsersByUserID(param); const list = await miaoguo.GetCardListID(param); if (list.length == 0) { const folder = { UserID: param.UserID || 0, Name: "样例题卡", FolderType: 1, ColorName: "#FEAF3F", IconName: "cardlist_joy_white_80x80", UpdateTime: moment().format("YYYY-MM-DD HH:mm:ss"), Flag: 0, }; const insertID = await miaoguo.AddFolderInfo(folder); const arrID = []; for (let i = 0; i < 10; i++) { const now = moment().format('YYYY-MM-DD HH:mm:ss'); const insertInfo = await miaoguo.AddCard({ UserID: param.UserID, CreateTime: now, LimitTime: now, UpdateTime: now, IsTodayStudy: 1, FolderID: insertID.insertId, }); arrID.push(insertInfo.insertId); } let grade = param.Grade; if (grade == 0) grade = 1; if (grade == 100) grade = 7; const start = grade * 10 + 1; const end = grade * 10 + 10; let index = 0; for (let i = start; i <= end; i++) { const listContent = await miaoguo.GetCardContentList(i); for (const content of listContent) { await miaoguo.AddCardContent({ MiaoguoCardID: arrID[index], ContentType: content.ContentType, Content: content.Content, }); } index++; } } clearMiaoguoCardCache(param.UserID); setTimeout(() => { const sql = "select c.MiaoguoCardID from MiaoguoCard c left join MiaoguoContent mc on c.MiaoguoCardID=mc.MiaoguoCardID where c.UserID=? and mc.Content is null;"; miaoguo.RunSql([param.UserID], sql).then(list => { for (const item of list) miaoguo.RunSql([item.MiaoguoCardID], "delete from MiaoguoCard where MiaoguoCardID=?;").catch(err => console.error('cleanup empty card failed:', err)); }).catch(err => console.error('cleanup empty cards query failed:', err)); }, 1000 * 60); ctx.body = { errcode: 10000 }; } export async function GetMiaoguoCardToday2(ctx) { const param = { UserID: ctx.query.UserID || 0, }; if (param.UserID == "undefined") param.UserID = 0; let result; if (param.UserID > 0) { const userConfig = await miaoguo.GetUserConfigData(param); if (userConfig && userConfig.length > 0) { param.BasicCardLimit = userConfig[0].BasicCardLimit; param.CardMaxNumberNew = userConfig[0].CardMaxNumberNew; param.CardMaxNumberHistory = userConfig[0].CardMaxNumberHistory; param.SortTypeIndex = userConfig[0].SortTypeIndex; param.CardMaxNumberNewUrgent = userConfig[0].CardMaxNumberNewUrgent; param.CardMaxNumberHistoryUrgent = userConfig[0].CardMaxNumberHistoryUrgent; param.CardTypeConfig = userConfig[0].CardType; } else { Object.assign(param, { TaskSetting: 0, BasicCardLimit: 50, CardMaxNumberNew: 10, CardMaxNumberHistory: 40, CardMaxNumberNewUrgent: 10, CardMaxNumberHistoryUrgent: 40, CardType: 0, SortTypeIndex: 0, MemoryLevel: 0, ClickType: 0, SecondConfigArray: "0,0,0", ColorIndexArr: "1,2,3,4", }); await miaoguo.AddUserConfig(param); param.CardTypeConfig = 0; } const url = ctx.request.url + "&CardType=" + param.CardTypeConfig; result = globalCache.get(url); if (result === 0) { result = {}; const param2 = { UserID: param.UserID, CardType: 0 }; param2.Type = "NewLimitTimeToday"; result.NewLimitTimeToday = (await miaoguo.GetMiaoguoCardTotal(param2))[0].Count; param2.Type = "HistoryLimitTimeToday"; result.HistoryLimitTimeToday = (await miaoguo.GetMiaoguoCardTotal(param2))[0].Count; const todayStart = moment().format('YYYY-MM-DD 00:00:00'); const tomorrowStart = moment().add(1, "days").format('YYYY-MM-DD 00:00:00'); const practiceParam = { UserID: param.UserID, LimitTime: tomorrowStart, LastTime: todayStart, GetCount: true, }; const alreadyPracticeNumber = await miaoguo.GetCardListID(practiceParam); result.AlreadyPracticeNumber = alreadyPracticeNumber[0].Count; if (param.BasicCardLimit > 0) { const historyPracticeParam = { UserID: param.UserID, LimitTime: tomorrowStart, LastTime: todayStart, GetCount: true, CardType: 0, IsNew: 0, }; const alreadyPracticeNumberHistory = await miaoguo.GetCardListID(historyPracticeParam); const alreadyPracticeNumberCountHistory = alreadyPracticeNumberHistory[0].Count; let shouldUpdate = 0; if (result.HistoryLimitTimeToday >= param.BasicCardLimit) { param.CardMaxNumberNew = 0; param.CardMaxNumberHistory = param.BasicCardLimit; shouldUpdate = 1; } else if (result.HistoryLimitTimeToday < param.BasicCardLimit) { param.CardMaxNumberNew = param.BasicCardLimit - result.HistoryLimitTimeToday - alreadyPracticeNumberCountHistory; if (param.CardMaxNumberNew < 0) param.CardMaxNumberNew = 0; param.CardMaxNumberHistory = result.HistoryLimitTimeToday + alreadyPracticeNumberCountHistory; if (param.CardMaxNumberHistory > (param.BasicCardLimit + param.CardMaxNumberNew)) param.CardMaxNumberHistory = param.BasicCardLimit + param.CardMaxNumberNew; shouldUpdate = 1; } if (shouldUpdate) { await miaoguo.UpdateUserConfig({ UserID: param.UserID, CardMaxNumberNew: param.CardMaxNumberNew, CardMaxNumberHistory: param.CardMaxNumberHistory, }); } result.BasicCardLimit = param.BasicCardLimit; } else { result.BasicCardLimit = 0; } const arrID = []; const listNew = []; const listReview = []; const listHistory = []; result.UrgentNewLength = 0; result.UrgentOldLength = 0; result.CommonNewLength = 0; result.CommonOldLength = 0; async function appendTaskList(isUrgent) { let list1 = []; const list2 = []; const list3 = []; param.CardType = isUrgent ? 1 : 0; let history = isUrgent ? param.CardMaxNumberHistoryUrgent : param.CardMaxNumberHistory; if (isUrgent) delete param.CardMaxNumberHistoryUrgent; else delete param.CardMaxNumberHistory; param.LimitTime = tomorrowStart; param.LastTime = todayStart; const countNew = isUrgent ? await miaoguo.GetCardListTodayAlreadyUrgent(param) : await miaoguo.GetCardListTodayAlready(param); if (isUrgent) { param.CardMaxNumberNewUrgent = (param.CardMaxNumberNewUrgent - countNew[0].count) > 0 ? (param.CardMaxNumberNewUrgent - countNew[0].count) : 0; param.SortType = "LimitTime asc,MiaoguoCardID asc"; if (param.CardMaxNumberNewUrgent > 0) list1 = await miaoguo.GetCardListTodayUrgent(param); } else { param.CardMaxNumberNew = (param.CardMaxNumberNew - countNew[0].count) > 0 ? (param.CardMaxNumberNew - countNew[0].count) : 0; if (param.SortTypeIndex == 0) param.SortType = "RAND()"; else if (param.SortTypeIndex == 1) param.SortType = "LimitTime asc,MiaoguoCardID asc"; else if (param.SortTypeIndex == 2) param.SortType = "LimitTime desc,MiaoguoCardID desc"; if (param.CardMaxNumberNew > 0) list1 = await miaoguo.GetCardListToday(param); } const newCount = isUrgent ? Math.min(param.CardMaxNumberNewUrgent || 0, list1.length) : Math.min(param.CardMaxNumberNew || 0, list1.length); list1 = list1.slice(0, newCount).map(item => { item.Number0 = "1m"; item.Number2 = "10m"; item.Number3 = "4d"; arrID.unshift(item.MiaoguoCardID); return formatTodayCardTime(item); }); param.IsReview = true; const reviewList = isUrgent ? await miaoguo.GetCardListTodayUrgent(param) : await miaoguo.GetCardListToday(param); for (const item of reviewList) { item.Number0 = "10m"; item.Number2 = "1d"; item.Number3 = "4d"; list2.push(formatTodayCardTime(item)); arrID.unshift(item.MiaoguoCardID); } delete param.IsReview; delete param.IsTodayStudy; for (const item of reviewList) { if (moment(item.FirstTime).format('YYYY-MM-DD HH:mm:ss') < param.LastTime) history--; } if (history > 0) { let oldCount; if (isUrgent) { param.CardMaxNumberHistoryUrgent = history; oldCount = await miaoguo.GetCardListTodayAlreadyUrgent(param); param.CardMaxNumberHistoryUrgent = (param.CardMaxNumberHistoryUrgent - oldCount[0].count) > 0 ? (param.CardMaxNumberHistoryUrgent - oldCount[0].count) : 0; } else { param.CardMaxNumberHistory = history; oldCount = await miaoguo.GetCardListTodayAlready(param); param.CardMaxNumberHistory = (param.CardMaxNumberHistory - oldCount[0].count) > 0 ? (param.CardMaxNumberHistory - oldCount[0].count) : 0; } const oldLimit = isUrgent ? param.CardMaxNumberHistoryUrgent : param.CardMaxNumberHistory; if (oldLimit > 0) { const oldList = isUrgent ? await miaoguo.GetCardListTodayUrgent(param) : await miaoguo.GetCardListToday(param); const count = Math.min(oldLimit, oldList.length); for (let i = 0; i < count; i++) { const item = oldList[i]; item.Number0 = "10m"; const normal = sm2(item.IntervalDay, getEF(item.RepetitionCoefficient, 4)); item.Number1 = sm2(normal, Number(stringUtils.Random(2, 5)) / 10) + "d"; item.Number2 = normal + "d"; if (item.Number1 == item.Number2) delete item.Number1; let number3 = sm2(normal, item.RepetitionCoefficient); if (normal + 1 > number3) number3 = normal + 1; item.Number3 = number3 + "d"; list3.push(formatTodayCardTime(item)); arrID.unshift(item.MiaoguoCardID); } } } if (isUrgent) { result.UrgentNewLength = list1.length; result.UrgentOldLength = list2.length + list3.length; } else { result.CommonNewLength = list1.length; result.CommonOldLength = list3.length; } for (const item of list1) item.IsUrgentCard = isUrgent ? 1 : 0; for (const item of list2) item.IsUrgentCard = isUrgent ? 1 : 0; for (const item of list3) item.IsUrgentCard = isUrgent ? 1 : 0; listNew.push(...list1); listReview.push(...list2); listHistory.push(...list3); } if (param.CardTypeConfig == 1 || param.CardTypeConfig == 2) await appendTaskList(true); if (param.CardTypeConfig == 0 || param.CardTypeConfig == 2) await appendTaskList(false); if (arrID.length > 0) { const listContent = await miaoguo.GetCardContentList(arrID.join(",")); const contentMap = new Map(); for (const content of listContent) { if (!contentMap.has(content.MiaoguoCardID)) contentMap.set(content.MiaoguoCardID, []); contentMap.get(content.MiaoguoCardID).push({ ContentType: content.ContentType, Content: stringUtils.ReplaceAllString(content.Content || "", "''", "'"), }); } for (const list of [listNew, listReview, listHistory]) { for (const item of list) item.Content = contentMap.get(item.MiaoguoCardID) || []; } // 保证返回列表中每一项的 Content 均为 4 项,否则从列表中剔除,避免前端渲染异常 for (const list of [listNew, listReview, listHistory]) { for (let i = list.length - 1; i >= 0; i--) { if (!Array.isArray(list[i].Content) || list[i].Content.length !== 4) list.splice(i, 1); } } // 同步修正剔除后各项数量统计 result.UrgentNewLength = listNew.filter(item => item.IsUrgentCard === 1).length; result.UrgentOldLength = listReview.filter(item => item.IsUrgentCard === 1).length + listHistory.filter(item => item.IsUrgentCard === 1).length; result.CommonNewLength = listNew.filter(item => item.IsUrgentCard === 0).length; result.CommonOldLength = listHistory.filter(item => item.IsUrgentCard === 0).length; for (const list of [listNew, listReview, listHistory]) { for (const item of list) delete item.IsUrgentCard; } } const dayNumberArr = await miaoguo.GetTaskData(param); let dayNumber = (dayNumberArr || []).length + 1; let isStart = 1; const practiceCount = await miaoguo.GetTaskData({ UserID: param.UserID, CreateTime: todayStart }); if (practiceCount.length > 0 && practiceCount[0].CardCount > 0) { isStart = 0; if (dayNumber > 1) dayNumber--; } const cardNumberArr = await miaoguo.GetCardCount(param); const cardNumber = cardNumberArr && cardNumberArr.length > 0 ? cardNumberArr[0].count : 0; const cardEveryTimeArr = await miaoguo.GetCardEveryTimeAvg({ UserID: param.UserID }); const cardEveryTime = cardEveryTimeArr && cardEveryTimeArr.length > 0 && cardEveryTimeArr[0].avg ? cardEveryTimeArr[0].avg : 20; result.ListNew = listNew; result.ListReview = listReview; result.ListHistory = listHistory; result.PracticeNumber = listNew.length * 2 + listReview.length + listHistory.length; result.PracticeTime = Math.round(result.PracticeNumber * cardEveryTime / 60); if (result.PracticeTime == 0 && result.PracticeNumber > 0) result.PracticeTime = 1; result.DayNumber = dayNumber; result.CardNumber = cardNumber; result.IsStart = isStart; result.Today = moment().format('YYYY-MM-DD'); const cardStudyPlanNumber = await miaoguo.GetStudyPlanCardNumber(param); result.CardNumberCommon = 0; result.CardNumberUrgent = 0; for (const item of cardStudyPlanNumber) { if (item.CardType === 0) result.CardNumberCommon = item.Count; else if (item.CardType === 1) result.CardNumberUrgent = item.Count; } const today = moment().format('YYYYMMDD'); for (const list of [result.ListNew, result.ListReview, result.ListHistory]) { for (const item of list) { const historyUpdateTime = moment(item.HistoryUpdateTime).format('YYYYMMDD'); if (today > historyUpdateTime) { miaoguo.UpdateCard({ MiaoguoCardID: item.MiaoguoCardID, HistoryUpdateTime: moment().format('YYYY-MM-DD 00:00:00'), HistoryFirstTime: item.FirstTime, HistoryLastTime: item.LastTime, HistoryLimitTime: item.LimitTime, HistoryIntervalDay: item.IntervalDay, HistoryRepetitionCoefficient: item.RepetitionCoefficient, HistoryLearnNumber: item.LearnNumber, }).catch(err => console.error('UpdateCard history failed:', err)); } } } const listFolder = await miaoguo.GetUserFolderList(param); result.FolderName = [{ FolderID: 0, FolderName: "" }]; for (const folder of listFolder) result.FolderName.push({ FolderID: folder.ID, FolderName: folder.Name }); globalCache.set(url, result, config.BufferMemoryTimeLower || config.BufferMemoryTimeLowNormal); } } else { result = { IsMaintain: 1 }; } ctx.body = { errcode: 10000, result }; } export async function DeleteMiaoguoCard(ctx) { const param = { MiaoguoCardID: ctx.query.ID, UserID: ctx.query.UserID, Flag: -1, DeleteTime: moment().format("YYYY-MM-DD HH:mm:ss"), }; param.UpdateTime = param.DeleteTime; await miaoguo.UpdateCard(param); addMiaoguoLog({ UserID: ctx.query.UserID, LogID: 299, Content: JSON.stringify(param) }); clearMiaoguoCardCache(ctx.query.UserID); ctx.body = { errcode: 10000 }; } export async function StartMiaoguoTask(ctx) { const param = { UserID: ctx.query.UserID || 0, CurrentUserID: ctx.query.CurrentUserID, }; addMiaoguoLog({ UserID: ctx.query.UserID, LogID: 304, Content: JSON.stringify(param) }); ctx.body = { errcode: 10000 }; } export async function ResetMiaoguoCardTask(ctx) { const param = { UserID: ctx.query.UserID }; const list = await miaoguo.GetCardTodayFinished(param); const arr = []; for (const item of list) { await miaoguo.UpdateCard({ MiaoguoCardID: item.MiaoguoCardID, FirstTime: item.HistoryFirstTime, LastTime: item.HistoryLastTime, LimitTime: item.HistoryLimitTime, IntervalDay: item.HistoryIntervalDay, RepetitionCoefficient: item.HistoryRepetitionCoefficient, LearnNumber: item.HistoryLearnNumber, }); arr.push(item.MiaoguoCardID); } if (arr.length > 0) await miaoguo.ResetTodayFinished({ CardID: arr.join(",") }); await miaoguo.DeleteMiaoguoTask(param); addMiaoguoLog({ UserID: ctx.query.UserID, LogID: 317, Content: JSON.stringify(param) }); clearMiaoguoCardCache(ctx.query.UserID); ctx.body = { errcode: 10000, result: true }; } export async function RemoveFolder(ctx) { const param = { UserID: ctx.query.UserID || 0, MiaoguoCardID: ctx.query.ID || 0, FolderID: ctx.query.FolderID || 0, UpdateTime: moment().format("YYYY-MM-DD HH:mm:ss"), }; await miaoguo.UpdateFolderInfo({ UserID: param.UserID, ID: param.FolderID, UpdateTime: param.UpdateTime, }); param.FolderID = 0; await miaoguo.UpdateCard(param); addMiaoguoLog({ UserID: ctx.query.UserID, LogID: 311, Content: JSON.stringify(param) }); clearMiaoguoCardCache(ctx.query.UserID); ctx.body = { errcode: 10000 }; } export async function UpdateMiaoguoCardType(ctx) { const param = { UserID: ctx.query.UserID || 0, MiaoguoCardID: ctx.query.ID || 0, CardType: ctx.query.CardType || 0, FolderType: ctx.query.FolderType || 0, LimitTime: ctx.query.LimitTime || "", }; if (param.LimitTime === "" || param.LimitTime == "undefined") delete param.LimitTime; await miaoguo.UpdateCardType(param); if (param.FolderType >= 1) { const folderInfo = await miaoguo.GetMiaoguoFolderCount(param); let folderID = 0; if (folderInfo.length > 0) { folderID = folderInfo[0].ID; } else { const insertID = await miaoguo.AddFolderInfo({ UserID: ctx.query.UserID || 0, Name: "练习中不会的题卡", FolderType: 1, ColorName: "#FEAF3F", IconName: "cardlist_joy_white_80x80", UpdateTime: moment().format("YYYY-MM-DD HH:mm:ss"), Flag: 0, }); folderID = insertID.insertId; } await miaoguo.UpdateCard({ MiaoguoCardID: param.MiaoguoCardID, FolderID: folderID }); } addMiaoguoLog({ UserID: ctx.query.UserID, LogID: param.FolderType && param.FolderType >= 1 ? 313 : 301, Content: JSON.stringify(param), }); clearMiaoguoCardCache(ctx.query.UserID); ctx.body = { errcode: 10000 }; } export async function GetMiaoguoTaskTime(ctx) { const param = { UserID: ctx.query.UserID }; param.CreateTime = moment().format('YYYY-MM-DD 00:00:00'); const time = await miaoguo.GetTaskData(param); delete param.CreateTime; const dayNumberArr = await miaoguo.GetTaskData(param); let dayNumber = dayNumberArr.length; if (dayNumber == 0) dayNumber = 1; if (time && time.length > 0) { time[0].DayNumber = dayNumber; if (time[0].CardCount > 0 && time[0].Duration > 0) { param.CreateTime = moment().format('YYYY-MM-DD 00:00:00'); const obj = { UserID: param.UserID, CreateTime: param.CreateTime, CardTask: time[0].CardCount, Duration: time[0].Duration, }; const tasks = await miaoguo.GetUserMiaoguoTask(param); if (tasks && tasks.length > 0) { // 当天任务已存在,dayNumberArr 已包含当天记录,直接用累计练习天数 obj.DayNumber = dayNumberArr.length; await miaoguo.UpdateMiaoguoTask(obj); } else { // 新增当天任务,DayNumber 为已有累计天数 + 1(注意用原始长度,新用户首日应为 1) obj.DayNumber = dayNumberArr.length + 1; await miaoguo.AddMiaoguoTask(obj); await updateMiaoguoMilestoneInfoByUserID(param.UserID); } } if (!time[0].Duration || time[0].Duration < 60) time[0].Duration = 60; time[0].Count = time[0].CardCount; } ctx.body = { errcode: 10000, result: time[0] }; } export async function GetMiaoguoCardTotal(ctx) { const param = { UserID: ctx.query.UserID || 0, CardType: ctx.query.CardType || 0, }; const result = {}; param.Type = "NewAll"; result.NewCardAll = (await miaoguo.GetMiaoguoCardTotal(param))[0].Count; param.Type = "HistoryAll"; result.HistoryCardAll = (await miaoguo.GetMiaoguoCardTotal(param))[0].Count; param.Type = "NewToday"; result.NewToday = (await miaoguo.GetMiaoguoCardTotal(param))[0].Count; param.Type = "NewLimitTimeToday"; result.NewLimitTimeToday = (await miaoguo.GetMiaoguoCardTotal(param))[0].Count; result.NewCardNumber = result.NewLimitTimeToday; param.Type = "HistoryLimitTimeToday"; result.HistoryLimitTimeToday = (await miaoguo.GetMiaoguoCardTotal(param))[0].Count; result.LimitCardNumber = result.HistoryLimitTimeToday; param.Type = "NewAndLimitToday"; result.NewCardNumberAndLimitToday = (await miaoguo.GetMiaoguoCardTotal(param))[0].Count; param.Type = "HistoryAndLimitToday"; result.HistoryCardNumberAndLimitToday = (await miaoguo.GetMiaoguoCardTotal(param))[0].Count; result.LimitCardNumberToday = result.HistoryCardNumberAndLimitToday; ctx.body = { errcode: 10000, result }; } export async function GetMiaoguoPayInfo3(ctx) { const param = { UserID: ctx.query.UserID, PayType: ctx.query.PayType || 1, EndDate: ctx.query.EndDate, Price: ctx.query.Price || 0, DayNumber: ctx.query.DayNumber || 0, }; const price2 = 199; const price2Str = "元 (55折,原价365元)"; const price3 = 258; const price3Str = "元 (7折,原价365元)"; if (param.EndDate && param.EndDate.indexOf("年") > 0) param.EndDate = param.EndDate.replace("年", "-").replace("月", "-").replace("日", ""); let time1 = moment(new Date(param.EndDate)).format('YYYY-MM-DD'); const today = moment().format('YYYY-MM-DD'); if (time1 == "Invalid date" || time1 < today) time1 = today; const userList = await miaoguo.GetUsersInfoByUserID(param); if (userList && userList.length > 0) { const productServiceTime = moment(userList[0].ProductServiceTime).format('YYYY-MM-DD'); if (time1 < productServiceTime) time1 = productServiceTime; } const time2 = moment(time1).add(12, 'months').add(30, 'days').format('YYYY年MM月DD日'); const time5 = moment(time1).format('YYYY年MM月DD日'); const result = {}; if (param.PayType == 2) { result.Text1 = "购买"; result.List1 = [ { Name: "秒过年费", Content: price2 + price2Str }, { Name: "赠送时间", Content: "30天" }, { Name: "有效期", Content: time2 }, ]; result.Text2 = "支付前请注意:\n自支付之日起7天内接受无理由退款,此期限过后不予退款。\n支付即代表您接受本条款。"; result.Text8 = "支付"; result.Text3 = "支付成功"; result.Text4 = ""; result.Text5 = "请点开通"; result.Text6 = ""; result.Text7 = ""; result.PayMoney = price2; } else if (param.PayType == 5) { result.Text1 = "购买"; let price = price3; let priceStr = price3Str; if (userList[0] && userList[0].IsPay == 1) { price = price2; priceStr = price2Str; } result.List1 = [ { Name: "秒过年费", Content: price + priceStr }, { Name: "有效期", Content: time5 }, ]; result.Text2 = "支付前请注意:\n自支付之日起7天内接受无理由退款,此期限过后不予退款。\n支付即代表您接受本条款。"; result.Text8 = "支付"; result.Text3 = "支付成功"; result.Text4 = ""; result.Text5 = "请点开通"; result.Text6 = ""; result.Text7 = ""; result.PayMoney = price; } else if (param.PayType == 6) { const time6 = moment(time1).add(param.DayNumber, 'days').format('YYYY年MM月DD日'); result.Text1 = "购买"; if (param.Price == 0) param.Price = price2; let priceStr = "元"; if (param.Price == price2) priceStr = price2Str; else if (param.Price == price3) priceStr = price3Str; result.List1 = [ { Name: "秒过年费", Content: param.Price + priceStr }, { Name: "有效期", Content: time6 }, ]; result.Text2 = "支付前请注意:\n自支付之日起7天内接受无理由退款,此期限过后不予退款。\n支付即代表您接受本条款。"; result.Text8 = "支付"; result.Text3 = "支付成功"; result.Text4 = ""; result.Text5 = "请点开通"; result.Text6 = ""; result.Text7 = ""; result.PayMoney = param.Price; } else if (param.PayType == 7) { result.Text1 = "购买"; result.List1 = [ { Name: "秒过年费", Content: price2 + price2Str }, { Name: "有效期", Content: time5 }, ]; result.Text2 = "支付前请注意:\n自支付之日起7天内接受无理由退款,此期限过后不予退款。\n支付即代表您接受本条款。"; result.Text8 = "支付"; result.Text3 = "支付成功"; result.Text4 = ""; result.Text5 = "请点开通"; result.Text6 = ""; result.Text7 = ""; result.PayMoney = price2; } ctx.body = { errcode: 10000, result }; } export async function InsertMiaoguoUserLesson(ctx) { const param = { UserID: ctx.query.UserID || 0, LessonID: ctx.query.LessonID || 1, CreateTime: moment().format('YYYY-MM-DD HH:mm:ss'), Content: ctx.query.Content, }; if (param.Content) await miaoguo.AddMiaoguoUserLesson(param); ctx.body = { errcode: 10000 }; } export async function UpdateFolderInfo(ctx) { const param = { UserID: ctx.query.UserID || 0, ID: ctx.query.FolderID || 0, Name: ctx.query.Name || "", Flag: ctx.query.Flag || 0, UpdateTime: moment().format("YYYY-MM-DD HH:mm:ss"), }; if (ctx.query.ColorName) param.ColorName = "#" + ctx.query.ColorName; if (ctx.query.IconName) param.IconName = ctx.query.IconName; if (param.ID > 0) { if (param.Flag == -1) { delete param.Name; delete param.ColorName; delete param.IconName; } await miaoguo.UpdateFolderInfo(param); if (param.Flag == -1) { param.FolderOldID = param.ID; param.FolderID = 0; await miaoguo.UpdateFolderList(param); clearMiaoguoCardCache(ctx.query.UserID); } addMiaoguoLog({ UserID: ctx.query.UserID, LogID: 310, Content: JSON.stringify(param) }); } else { delete param.ID; await miaoguo.AddFolderInfo(param); addMiaoguoLog({ UserID: ctx.query.UserID, LogID: 309, Content: JSON.stringify(param) }); } ctx.body = { errcode: 10000 }; } export async function ShareMiaoguoCard(ctx) { const param = { UserID: ctx.query.UserID || 0, MiaoguoCardID: ctx.query.MiaoguoCardID || 0, IsShare: 1, }; await miaoguo.UpdateCard(param); ctx.body = { errcode: 10000 }; } export async function BuildMiaoguoQRCode(ctx) { const param = { UserID: ctx.query.UserID || 0, PrintID: ctx.query.PrintID || 0, IsWeb: ctx.query.IsWeb || 0, }; try { const tokenUrl = 'https://api.weixin.qq.com/cgi-bin/token?appid=' + config.wx.miaoguo_appid + '&secret=' + config.wx.miaoguo_appsecret + '&grant_type=client_credential'; const tokenRes = await axios.get(tokenUrl, { transformResponse: [(data) => data] }); const tokenJson = typeof tokenRes.data === 'string' ? JSON.parse(tokenRes.data) : tokenRes.data; if (!tokenJson || !tokenJson.access_token) { ctx.body = { errcode: 10000 }; return; } const postJSON = {}; let imageUrl = ""; if (param.PrintID && param.PrintID > 0) { postJSON.width = 280; postJSON.scene = "type=print&P=" + param.PrintID + "&U=" + param.UserID; imageUrl = 'MiaoguoPrint' + param.PrintID + '.png'; } else if (param.IsWeb) { postJSON.width = 280; postJSON.scene = "UserID=" + param.UserID; let userID = Encrypt(param.UserID, config.urlSecrets.aes_key, config.urlSecrets.aes_iv); userID = stringUtils.ReplaceAllString(userID, "/", "___"); userID = stringUtils.ReplaceAllString(userID, "+", "___"); imageUrl = 'MiaoguoUser' + userID + '.png'; miaoguo.UpdateUsersByUserID({ HasACode: 1, UserID: param.UserID }).catch(err => console.error('UpdateUsersByUserID HasACode failed:', err)); } if (imageUrl) { const qrUrl = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=' + tokenJson.access_token; const dir = './public/images/acode'; await fsPromises.mkdir(dir, { recursive: true }); const filePath = path.join(dir, imageUrl); const qrRes = await axios.post(qrUrl, postJSON, { responseType: 'arraybuffer', transformResponse: [(data) => data], }); await fsPromises.writeFile(filePath, Buffer.from(qrRes.data)); await uploadMiaoguoFile(imageUrl, filePath); } } catch (err) { console.error('BuildMiaoguoQRCode failed:', err); } ctx.body = { errcode: 10000 }; } async function updateMiaoguoMilestoneInfoByUserID(userID) { const param = { UserID: userID || 0, FieldStr: "*", OrderType: "desc", }; const list = await miaoguo.GetUserMiaoguoTaskByUserID(param); if (!list || list.length == 0) return; const count = list.length; const arrHasMilestoneID = list.filter(item => item.MilestoneID).map(item => item.MilestoneID); const mileStoneArr = constantClass.GetMilestone(); const maxMileStoneID = mileStoneArr.length; const mArr = []; for (let i = 1; i < maxMileStoneID; i++) { if (!arrHasMilestoneID.includes(i)) mArr.push(i); } const mID = mArr.length > 0 ? mArr[stringUtils.Random(0, mArr.length - 1)] : stringUtils.Random(1, maxMileStoneID); for (const milestone of mileStoneArr) { if (milestone.DayNumber == count) { await miaoguo.UpdateMiaoguoTask({ ID: list[0].ID, MilestoneDayNumber: milestone.DayNumber, MilestoneID: mID, }); break; } } } export async function UpdateMiaoguoMilestoneInfo(ctx) { await updateMiaoguoMilestoneInfoByUserID(ctx.query.UserID || 0); ctx.body = { errcode: 10000 }; } export async function GetHardworkingList(ctx) { const param = { UserID: ctx.query.UserID || 0, OrderType: "desc", }; const cacheKey = "GetHardworkingList?UserID=" + param.UserID; let result = globalCache.get(cacheKey); if (result === 0) { result = {}; param.IsHardworking = true; const dayNumberArr = await miaoguo.GetTaskData(param); result.DayNumber = dayNumberArr[0].Count; if (result.DayNumber > 0) { const mileStoneArr = constantClass.GetMilestone(); for (let i = 0; i < mileStoneArr.length; i++) { if (result.DayNumber < mileStoneArr[i].DayNumber) { result.MilestonePrev = mileStoneArr[i - 1]; result.MilestoneNext = mileStoneArr[i]; result.RemainingDays = mileStoneArr[i].DayNumber - result.DayNumber; result.PastDays = result.DayNumber - mileStoneArr[i - 1].DayNumber; result.Rate = 100 * computerMilestone(result.DayNumber, mileStoneArr[i - 1].DayNumber, mileStoneArr[i].DayNumber) + "%"; break; } } const createTime = moment().format("YYYY-MM") + "-01"; param.FieldStr = "ID,CreateTime,IsShare,HardworkingNumber,MilestoneDayNumber,MilestoneID,AphorismID,ImageID"; result.SevenDays = []; result.CurrentMonth = []; const list = await miaoguo.GetUserMiaoguoTaskByUserID(param); if (list && list.length > 0) { const arrAphorism = await miaoguo.GetAphorism(); for (const item of list) { if (item.AphorismID > 0 || item.MilestoneDayNumber > 0) { const obj = { ID: item.ID, HardworkingNumber: item.HardworkingNumber, OrdinalPostfix: stringUtils.GetOrdinalPostfix(item.HardworkingNumber), }; if (item.MilestoneDayNumber > 0) { for (const milestone of mileStoneArr) { if (milestone.DayNumber == item.MilestoneDayNumber) { obj.Name = milestone.Name; obj.Color2 = milestone.Color2; obj.IsMilestone = 1; break; } } } else { obj.Image = "https://miaguo-1253256735.file.myqcloud.com/web/_share/qmjl_pic_d_" + item.ImageID + ".jpg"; if (item.AphorismID) { for (const aphorism of arrAphorism) { if (aphorism.ID == item.AphorismID) { obj.Content = aphorism.Content; obj.AphorismID = item.AphorismID; break; } } } } obj.CreateTime = moment(item.CreateTime).format("YYYY.MM.DD"); result.SevenDays.push(obj); if (result.SevenDays.length >= 7) break; } } let shareNumber = 0; let shareNumberYear = 0; let shareNumberYearPrev = 0; let shareNumberMonth = 0; for (let i = list.length - 1; i >= 0; i--) { const item = list[i]; item.CreateTime = moment(item.CreateTime).format("YYYY-MM-DD"); if (item.CreateTime >= createTime) { const obj = { ID: item.ID, IsShare: item.IsShare, Day: moment(item.CreateTime).format("D"), HardworkingNumber: item.HardworkingNumber, }; if (item.MilestoneDayNumber > 0) { for (const milestone of mileStoneArr) { if (milestone.DayNumber == item.MilestoneDayNumber) { obj.Name = milestone.Name; obj.Color1 = milestone.Color1; obj.IsMilestone = 1; break; } } } if (item.AphorismID > 0 && item.ImageID) { obj.Image = "https://miaguo-1253256735.file.myqcloud.com/web/_share/qmjl_pic_c_" + item.ImageID + ".jpg"; obj.AphorismID = item.AphorismID; } if (item.IsShare || item.AphorismID > 0 || item.MilestoneDayNumber > 0) shareNumberMonth++; result.CurrentMonth.push(obj); } if (item.IsShare || item.AphorismID > 0 || item.MilestoneDayNumber > 0) { shareNumber++; if (item.CreateTime >= moment().format("YYYY-01-01") && item.IsShare) shareNumberYear++; if (item.CreateTime >= "2022-01-01" && item.CreateTime < "2023-01-01" && item.IsShare) shareNumberYearPrev++; } } result.ShareNumberMonth = shareNumberMonth; result.ShareNumberYear = shareNumberYear; result.ShareNumberYearPrev = shareNumberYearPrev; result.ShareNumberYearRate = Math.round(100 * shareNumberYear / 200); if (result.ShareNumberYearRate > 100) result.ShareNumberYearRate = 100; result.Award = 0; if (result.ShareNumberYearPrev >= 100) result.Award = 30; if (result.ShareNumberYearPrev >= 200) result.Award = 60; result.ShareNumber = shareNumber; result.ShareFirstDay = moment(list[list.length - 1].CreateTime).format("YYYY年MM月DD日"); } } globalCache.set(cacheKey, result, config.BufferMemoryTime); } ctx.body = { errcode: 10000, result }; } export async function GetMilestoneList(ctx) { const param = { UserID: ctx.query.UserID || 0, OrderType: "asc", FieldStr: "*", }; const result = {}; const list = await miaoguo.GetUserMiaoguoTaskByUserID(param); if (list && list.length > 0) { const mileStoneArr = constantClass.GetMilestone(); result.DayNumber = list.length; result.MilestoneFinished = []; let mIndex = 0; let mPosition = ""; for (const item of list) { for (let j = 0; j < mileStoneArr.length; j++) { if (mileStoneArr[j].DayNumber == item.MilestoneDayNumber) { const obj = { ID: item.ID, Name: mileStoneArr[j].Name, DayNumber: item.MilestoneDayNumber, Color: mileStoneArr[j].Color1, Date: moment(item.CreateTime).format("YYYY.MM.DD"), }; if (mileStoneArr[j].DayNumber == 1) { obj.Position = "center"; } else { mPosition = j / 2 == Math.floor(j / 2) ? "left" : "right"; obj.Position = mPosition + ":400rpx;"; } result.MilestoneFinished.push(obj); mIndex = j; break; } } } if (result.MilestoneFinished.length > 0 && result.MilestoneFinished[result.MilestoneFinished.length - 1].Position == "right:400rpx;") { for (const item of result.MilestoneFinished) item.Position = item.Position == "left:400rpx;" ? "right:400rpx;" : "left:400rpx;"; } result.MilestoneRemain = []; for (let j = mIndex + 1; j < mileStoneArr.length; j++) { const item = { ...mileStoneArr[j] }; delete item.Color1; delete item.Color2; result.MilestoneRemain.push(item); } if (result.MilestoneFinished.length > 0 && result.MilestoneRemain.length > 0) { result.RemainRate = computerMilestone(list.length, result.MilestoneFinished[result.MilestoneFinished.length - 1].DayNumber, result.MilestoneRemain[0].DayNumber); result.RemainRateStr = Math.round(100 * result.RemainRate) + "%"; result.MilestoneFinished.push({ ID: list[0].ID, Name: result.MilestoneRemain[0].Name, HardworkingNumber: result.MilestoneRemain[0].DayNumber, Position: "right:425rpx;bottom:-23rpx;", }); result.MilestoneFinished = _.orderBy(result.MilestoneFinished, ['DayNumber'], ["desc"]); } } ctx.body = { errcode: 10000, result }; } export async function GetShareWall(ctx) { const param = { UserID: ctx.query.UserID || 0, PageIndex: ctx.query.PageIndex || 99999, OrderType: "asc", }; const result = {}; let list = await miaoguo.GetUserMiaoguoTaskByUserID(param); if (list && list.length > 0) { list = list.filter(item => item.IsShare == 1 || item.AphorismID > 0 || item.MilestoneDayNumber > 0); result.Total = list.length; result.Menu = []; const len = Math.ceil(list.length / 100); for (let i = len - 1; i >= 0; i--) { const obj = { PageIndex: i * 100, Name: "第" + (i * 100 + 1) + "-" + (i * 100 + 100) + "次", }; if (param.PageIndex == obj.PageIndex) { obj.IsSelected = 1; } else if (param.PageIndex == 99999 && i == len - 1) { obj.IsSelected = 1; param.PageIndex = i * 100; } result.Menu.push(obj); } let index = 0; const mileStoneArr = constantClass.GetMilestone(); result.List = []; for (let i = 0; i < list.length; i++) { if (i >= param.PageIndex) { const item = { ID: list[i].ID, CreateTime: moment(list[i].CreateTime).format("YYYY.MM.DD"), HardworkingNumber: list[i].HardworkingNumber, OrdinalPostfix: stringUtils.GetOrdinalPostfix(list[i].HardworkingNumber), Color2: "#4D4D4D", }; if (list[i].ImageID) item.Image = "https://miaguo-1253256735.file.myqcloud.com/web/_share/qmjl_pic_e_" + list[i].ImageID + ".jpg"; if (list[i].MilestoneDayNumber > 0) { for (const milestone of mileStoneArr) { if (milestone.DayNumber == list[i].MilestoneDayNumber) { item.Name = milestone.Name; item.Color2 = milestone.Color1; item.IsMilestone = 1; break; } } } result.List.push(item); if (++index == 100) break; } } result.List = _.orderBy(result.List, ['CreateTime'], ["desc"]); if (result.List.length > 0) { result.DateStart = moment(new Date(result.List[result.List.length - 1].CreateTime)).format("YYYY年MM月DD日"); result.DateEnd = moment(new Date(result.List[0].CreateTime)).format("YYYY年MM月DD日"); } } ctx.body = { errcode: 10000, result }; } export async function GetShareItemInfo(ctx) { const param = { UserID: ctx.query.UserID || 0, TaskID: ctx.query.TaskID || 0, IsHistory: ctx.query.IsHistory || 0, IsShow: ctx.query.IsShow || 0, }; if (param.TaskID == -1) { let isMilestone = 0; let list = await miaoguo.GetUserMiaoguoTask(param); if (list && list.length > 0) { param.TaskID = list[0].ID; if (list[0].MilestoneDayNumber > 0) isMilestone = 1; if (list[1]) { const param2 = { ID: list[0].ID, DayNumber: list[1].DayNumber + 1, }; if (list[1].HardworkingNumber > 0) { param2.HardworkingNumber = list[1].HardworkingNumber + 1; } else { const param3 = { UserID: param.UserID, TaskID: -1, IsGetHardworkingNumber: 1, }; const hwn = await miaoguo.GetUserMiaoguoTask(param3); if (hwn && hwn.length > 0) param2.HardworkingNumber = hwn[0].HardworkingNumber + 1; } await miaoguo.UpdateMiaoguoTask(param2); } else { await miaoguo.UpdateMiaoguoTask({ ID: list[0].ID, DayNumber: 1, HardworkingNumber: 1, }); } } if (!isMilestone) { const maxAphorismID = config.MaxAphorismID; const maxImageID = config.MaxImageID; const list2 = await miaoguo.GetUserMiaoguoTaskByAphorismIDIsNotNull(param); const aphorismID = []; const imageID = []; for (const item of list2) { aphorismID.push(item.AphorismID); imageID.push(item.ImageID); } const aArr = []; for (let i = 1; i < maxAphorismID; i++) { if (!aphorismID.includes(i)) aArr.push(i); } const aID = aArr.length > 0 ? aArr[stringUtils.Random(0, aArr.length - 1)] : stringUtils.Random(1, maxAphorismID); const iArr = []; for (let i = 1; i < maxImageID; i++) { if (!imageID.includes(i)) iArr.push(i); } const iID = iArr.length > 0 ? iArr[stringUtils.Random(0, iArr.length - 1)] : stringUtils.Random(1, maxImageID); list = await miaoguo.GetUserMiaoguoTask(param); if (list && list[0] && list[0].AphorismID == 0) { await miaoguo.UpdateMiaoguoTask({ UserID: param.UserID, ID: list[0].ID, AphorismID: aID, ImageID: iID, }); } } } let result = {}; if (param.TaskID > 0) { const info = await miaoguo.GetUserMiaoguoTask(param); if (info && info.length > 0) { result = info[0]; result.OrdinalPostfix = stringUtils.GetOrdinalPostfix(result.HardworkingNumber); result.CreateTimeStr1 = moment(result.CreateTime).format("YYYY.MM.DD"); result.CreateTimeStr2 = moment(result.CreateTime).format("YYYY年MM月DD日"); if (!param.IsHistory || param.IsHistory == "0") { param.CreateTime = moment(result.CreateTime).format("YYYY-MM-DD") + " 23:59:59"; const totalData1 = await miaoguo.GetUserMiaoguoPractiseCardTotal(param); result.CardTaskTotal = totalData1[0].Count; const totalData2 = await miaoguo.GetUserMiaoguoTaskTotal(param); result.DurationTotal = Math.round((totalData2[0].DurationTotal || 0) / 60); } delete result.CreateTime; if (result.MilestoneDayNumber) { const mileStoneArr = constantClass.GetMilestone(); let indexMileStone = 0; for (let i = 0; i < mileStoneArr.length; i++) { if (result.MilestoneDayNumber == mileStoneArr[i].DayNumber) { indexMileStone = i + 1; result.Name = mileStoneArr[i].Name.split("\n"); result.Color = mileStoneArr[i].Color2; break; } } result.MilestoneImage = "https://miaguo-1253256735.file.myqcloud.com/web/_milestone/qmjl_milestone_material_" + indexMileStone + ".png"; const arrMilestone = await miaoguo.GetMilestone(); for (const milestone of arrMilestone) { if (result.MilestoneID == milestone.ID) { result.Content1 = result.CreateTimeStr2 + "是我家孩子用「秒过」做复习的第" + result.MilestoneDayNumber; result.Content2 = "天。正在勇敢挑战" + result.CardTaskTotal + "个语数外知识点,专注复习超过"; result.Content3 = result.DurationTotal + "分钟。"; result.Content = []; let str = milestone.Content; do { const str1 = str.substr(0, 28); result.Content.push(str1); str = str.substr(28); } while (str != ""); delete result.AphorismID; delete result.ImageID; break; } } delete result.MilestoneID; } else { const arrAphorism = await miaoguo.GetAphorism(); for (const aphorism of arrAphorism) { if (result.AphorismID == aphorism.ID) { result.Content = aphorism.Content; result.Paraphrase = aphorism.Paraphrase; result.AuthorCn = aphorism.AuthorCn; result.AuthorEn = aphorism.AuthorEn; result.Country = aphorism.Country; result.Title = aphorism.Title; result.BirthAndDeath = aphorism.BirthAndDeath; result.Remark = aphorism.Remark; result.AuthorImage = "https://miaguo-1253256735.file.myqcloud.com/web/_portrait/" + aphorism.Avatar; result.WordImage = "https://miaguo-1253256735.file.myqcloud.com/web/_quotes/" + aphorism.WordImage; delete result.MilestoneDayNumber; delete result.MilestoneID; break; } } if (result.ImageID) result.Image = "https://miaguo-1253256735.file.myqcloud.com/web/_share/qmjl_pic_b_" + result.ImageID + ".jpg"; delete result.ImageID; } result.DurationMinute = Math.round(result.Duration / 60); if (result.DurationMinute == 0) result.DurationMinute = 1; result.ButtonString = param.IsShow > 0 ? "生成群接力图片" : "查看详细信息"; delete result.Flag; } } ctx.body = { errcode: 10000, result }; } export async function GetMiaoguoCardDeleteList2(ctx) { const param = ctx.request.body || {}; param.UserID = ctx.query.UserID || "0"; if (param.PageID && param.PageID != "0" && param.Type == "web") param.PageID = decryptWebID(param.PageID); if (!param.PageCount) param.PageCount = 20; param.PageID = Number(param.PageID); param.DataType = "delete"; const listID = await miaoguo.GetCardListID(param); const ids = listID.map(item => item.MiaoguoCardID); let list = []; if (ids.length > 0) { const rows = await miaoguo.GetCardList({ UserID: param.UserID, IDArr: ids.join(','), OrderType: "ac.UpdateTime desc,ac.CreateTime desc", }); list = buildCardListFromRows(rows, [], true); } const result = { Count: list.length, List: [], }; let index = 0; let canPush = false; for (const item of list) { if (param.PageID == 0 || item.MiaoguoCardID == param.PageID) canPush = true; if (canPush) { if (param.PageID == 0) { result.List.push(item); index++; if (index >= param.PageCount) break; } else { if (index > 0) result.List.push(item); index++; if (index > param.PageCount) break; } } } if (param.Type == "web") { result.List = result.List.map(item => ({ ...item, MiaoguoCardID: encryptWebID(item.MiaoguoCardID), })); } ctx.body = { errcode: 10000, result }; } export async function DeleteMiaoguoCardWeb(ctx) { const param = ctx.request.body || {}; if (param.VisitType && decryptWebID(param.VisitType) == "web") { if (stringUtils.IsArray(param.ID)) { param.MiaoguoCardID = decryptWebIDList(param.ID).join(","); param.IDArr = true; } else { param.MiaoguoCardID = decryptWebID(param.ID); } delete param.VisitType; delete param.ID; if (param.IsRecover == "true") { param.Flag = 0; delete param.IsRecover; param.DeleteTime = null; } else { param.Flag = -1; param.DeleteTime = moment().format("YYYY-MM-DD HH:mm:ss"); } } param.UserID = ctx.query.UserID; param.UpdateTime = moment().format("YYYY-MM-DD HH:mm:ss"); await miaoguo.UpdateCard(param); miaoguo.AddMiaoguoLog({ UserID: ctx.query.UserID, LogID: String(param.MiaoguoCardID || '').indexOf(",") >= 0 ? 305 : 299, Content: JSON.stringify(param), }).catch(err => console.error('AddMiaoguoLog failed:', err)); globalCache.delete("GetMiaoguoCard###UserID=" + param.UserID); ctx.body = { errcode: 10000 }; } async function uploadMiaoguoFile(filename, filepath) { const cos = new COS(config.QCloud); const params = { Bucket: 'miaguo-1253256735', Region: 'ap-guangzhou', Key: filename, Body: fs.createReadStream(filepath), ContentLength: fs.statSync(filepath).size, }; return new Promise((resolve, reject) => { cos.putObject(params, (err, data) => { if (err) { reject(err); } else { fsPromises.unlink(filepath).then(() => resolve(data)).catch(reject); } }); }); } export async function ExportFileWeb(ctx) { const param = ctx.request.body || {}; if (!param.ID || param.ID.length > 30) { ctx.body = { errcode: 1004, errMsg: "上传数据过多!" }; return; } param.CardID = decryptWebIDList(param.ID).join(","); delete param.ID; await miaoguo.UpdateCardExportShare(param); param.UserID = ctx.query.UserID; const insertResult = await miaoguo.InsertExport(param); const exportID = insertResult.insertId; const content = encryptWebID(stringUtils.Random(10000, 99999) + "_" + exportID.toString()); const filename = param.Title + "_" + moment().format("YYYYMMDDHHmmss") + ".mg"; const dir = "./public/images/print"; await fsPromises.mkdir(dir, { recursive: true }); const filepath = path.join(dir, filename); await fsPromises.writeFile(filepath, content); await uploadMiaoguoFile(filename, filepath); miaoguo.AddMiaoguoLog({ UserID: ctx.query.UserID, LogID: 306, Content: JSON.stringify({ ...param, ExportID: exportID }), }).catch(err => console.error('AddMiaoguoLog failed:', err)); ctx.body = "https://miaguo-1253256735.file.myqcloud.com/" + decodeURI(filename); } async function importExportData(exportInfo, userIDTarget) { const folderResult = await miaoguo.AddFolderInfo({ UserID: userIDTarget, Name: exportInfo.Title, ColorName: "#6B727C", IconName: "cardlist_folder_white_66x60", UpdateTime: moment().format("YYYY-MM-DD HH:mm:ss"), ExportID: exportInfo.ID, Flag: 0, }); const folderID = folderResult.insertId; const cardIDs = String(exportInfo.CardID || '').split(',').filter(Boolean); for (const sourceCardID of cardIDs) { const card = { UserID: userIDTarget, CardType: -1, CreateSource: "web", CreateTime: moment().format('YYYY-MM-DD HH:mm:ss'), LimitTime: moment().format('YYYY-MM-DD HH:mm:ss'), UpdateTime: moment().format('YYYY-MM-DD HH:mm:ss'), SourceCardID: sourceCardID, FolderID: folderID, }; const insertInfo = await miaoguo.AddCard(card); const listContent = await miaoguo.GetCardContentList(sourceCardID); for (const content of listContent) { await miaoguo.AddCardContent({ MiaoguoCardID: insertInfo.insertId, ContentType: content.ContentType, Content: content.Content, }); } } } export async function ImportFileWeb(ctx) { const file = ctx.file; const cleanup = async () => { if (file && file.path && fs.existsSync(file.path)) await fsPromises.unlink(file.path); }; if (!file || !file.originalname) { ctx.body = { errcode: 1002, errMsg: "文件上传错误!" }; return; } if (file.originalname.indexOf(".mg") <= 0) { await cleanup(); ctx.body = { errcode: 1001, errMsg: "格式不对!" }; return; } if (!ctx.query.UserID) { await cleanup(); ctx.body = { errcode: 1002, errMsg: "文件上传错误!" }; return; } const userInfo = await miaoguo.GetUsersInfoByUserID({ UserID: ctx.query.UserID }); if (!userInfo || userInfo.length == 0 || moment(userInfo[0].ProductServiceTime).format("YYYYMMDDHHmmss") <= moment().format("YYYYMMDDHHmmss")) { await cleanup(); ctx.body = { errcode: 1002, errMsg: "文件上传错误!" }; return; } try { const data = await fsPromises.readFile(file.path); const firstLine = data.toString().split("\n")[0]; let id = decryptWebID(firstLine); id = id.substr(id.indexOf("_") + 1); const exportInfo = await miaoguo.GetMiaoguoExport({ ID: id }); if (!exportInfo || exportInfo.length == 0) throw new Error("export not found"); await importExportData(exportInfo[0], ctx.query.UserID); globalCache.delete("GetMiaoguoCard###UserID=" + ctx.query.UserID); miaoguo.AddMiaoguoLog({ UserID: ctx.query.UserID, LogID: 318, Content: JSON.stringify(exportInfo[0]), }).catch(err => console.error('AddMiaoguoLog failed:', err)); ctx.body = { errcode: 10000 }; } catch (err) { ctx.body = { errcode: 1003, result: "文件内容错误!" }; } finally { await cleanup(); } } export async function UpdateMiaoguoCardWebType(ctx) { const param = ctx.request.body || {}; if (param.CardType) { const cardIDs = decryptWebIDList(param.List); const param2 = { CardType: param.CardType, UserID: ctx.query.UserID, MiaoguoCardID: cardIDs.join(","), }; await miaoguo.UpdateCardType(param2); if (param.LimitTimeList) { for (let i = 0; i < cardIDs.length; i++) { await miaoguo.UpdateCard({ MiaoguoCardID: cardIDs[i], LimitTime: param.LimitTimeList[i], }); } } miaoguo.AddMiaoguoLog({ UserID: ctx.query.UserID, LogID: param.LimitTimeList ? 307 : (param.CardType == -1 || param.CardType == "-1" ? 301 : 300), Content: JSON.stringify({ ...param, List: cardIDs }), }).catch(err => console.error('AddMiaoguoLog failed:', err)); } globalCache.delete("GetMiaoguoCard###UserID=" + ctx.query.UserID); ctx.body = { errcode: 10000 }; } export async function UpdateUserTagInfo(ctx) { const param = ctx.request.body || {}; const param2 = { UserID: ctx.query.UserID || 0, }; if (param.CommonTags) param2.CommonTags = param.CommonTags; if (param.CommonSymbol) param2.CommonSymbol = param.CommonSymbol; await miaoguo.UpdateUsersByUserID(param2); ctx.body = { errcode: 10000 }; } export async function UpdateFolderInfoWeb(ctx) { const param = ctx.request.body || {}; const folderParam = { UserID: ctx.query.UserID || 0, ID: param.FolderID || 0, Name: param.Name || "", Flag: param.Flag || 0, UpdateTime: moment().format("YYYY-MM-DD HH:mm:ss"), }; if (folderParam.ID > 0) { if (folderParam.Flag == -1) delete folderParam.Name; await miaoguo.UpdateFolderInfo(folderParam); if (folderParam.Flag == -1) { await miaoguo.UpdateFolderList({ UserID: folderParam.UserID, FolderOldID: folderParam.ID, FolderID: 0, }); globalCache.delete("GetMiaoguoCard###UserID=" + ctx.query.UserID); } miaoguo.AddMiaoguoLog({ UserID: ctx.query.UserID, LogID: 310, Content: JSON.stringify(folderParam), }).catch(err => console.error('AddMiaoguoLog failed:', err)); } else { delete folderParam.ID; await miaoguo.AddFolderInfo(folderParam); miaoguo.AddMiaoguoLog({ UserID: ctx.query.UserID, LogID: 309, Content: JSON.stringify(folderParam), }).catch(err => console.error('AddMiaoguoLog failed:', err)); } globalCache.delete("GetUserFolderList?UserID=" + folderParam.UserID); ctx.body = { errcode: 10000 }; } export async function UpdateFolderList(ctx) { const param = ctx.request.body || {}; param.UserID = ctx.query.UserID || 0; param.CardID = decryptWebIDList(param.CardID).join(","); const updateTimeParam = { UpdateTime: moment().format("YYYY-MM-DD HH:mm:ss"), UserID: param.UserID, }; if (param.FolderID > 0) { updateTimeParam.ID = param.FolderID; await miaoguo.UpdateFolderInfo(updateTimeParam); } else { updateTimeParam.CardID = param.CardID; await miaoguo.UpdateFolderInfo(updateTimeParam); } if (!param.FolderID) param.FolderID = 0; await miaoguo.UpdateFolderList(param); globalCache.delete("GetUserFolderList?UserID=" + param.UserID); globalCache.delete("GetMiaoguoCard###UserID=" + ctx.query.UserID); miaoguo.AddMiaoguoLog({ UserID: ctx.query.UserID, LogID: 303, Content: JSON.stringify(param), }).catch(err => console.error('AddMiaoguoLog failed:', err)); ctx.body = { errcode: 10000 }; } export async function UpdateCollectList(ctx) { const param = ctx.request.body || {}; param.UserID = ctx.query.UserID || 0; param.CardID = decryptWebIDList(param.CardID).join(","); if (!param.IsCollect) param.IsCollect = 0; await miaoguo.UpdateCollectList(param); globalCache.delete("GetUserFolderList?UserID=" + param.UserID); globalCache.delete("GetMiaoguoCard###UserID=" + ctx.query.UserID); miaoguo.AddMiaoguoLog({ UserID: ctx.query.UserID, LogID: 302, Content: JSON.stringify(param), }).catch(err => console.error('AddMiaoguoLog failed:', err)); ctx.body = { errcode: 10000 }; } export async function ResetUserCard(ctx) { const param = ctx.request.body || {}; if (param.List) { const cardIDs = decryptWebIDList(param.List); await miaoguo.ResetCard({ UserID: ctx.query.UserID, MiaoguoCardID: cardIDs.join(","), CardType: param.CardType, }); miaoguo.AddMiaoguoLog({ UserID: ctx.query.UserID, LogID: 371, Content: JSON.stringify({ ...param, List: cardIDs }), }).catch(err => console.error('AddMiaoguoLog failed:', err)); } globalCache.delete("GetMiaoguoCard###UserID=" + ctx.query.UserID); ctx.body = { errcode: 10000 }; } export async function ImportExcelData(ctx) { const param = ctx.request.body || {}; param.UserID = ctx.query.UserID || "0"; const folder = { UserID: param.UserID, Name: "D" + moment().format("HHmmss_YYYYMMDD"), ColorName: "#6B727C", IconName: "cardlist_folder_white_66x60", UpdateTime: moment().format("YYYY-MM-DD HH:mm:ss"), ExportID: 0, Flag: 0, }; const insertFolder = await miaoguo.AddFolderInfo(folder); const list = JSON.parse(param.DataList); for (const item of list) { const time = moment().format('YYYY-MM-DD HH:mm:ss'); const insertInfo = await miaoguo.AddCard({ UserID: param.UserID, CardType: -1, CreateSource: "web", CreateTime: time, LimitTime: time, UpdateTime: time, FolderID: insertFolder.insertId, }); const cardID = insertInfo.insertId; await miaoguo.AddCardContent({ MiaoguoCardID: cardID, ContentType: 0, Content: "" }); await miaoguo.AddCardContent({ MiaoguoCardID: cardID, ContentType: 1, Content: stringUtils.ReplaceAllString(item.Question || "", "'", "ˈ"), }); await miaoguo.AddCardContent({ MiaoguoCardID: cardID, ContentType: 2, Content: stringUtils.ReplaceAllString(item.Answer || "", "'", "ˈ"), }); await miaoguo.AddCardContent({ MiaoguoCardID: cardID, ContentType: 3, Content: "" }); } globalCache.delete("GetMiaoguoCard###UserID=" + ctx.query.UserID); miaoguo.AddMiaoguoLog({ UserID: ctx.query.UserID, LogID: 453, Content: JSON.stringify(param.List), }).catch(err => console.error('AddMiaoguoLog failed:', err)); ctx.body = { errcode: 10000 }; } export async function GetAccreditInfo2(ctx) { const param = { Type: ctx.query.Type || 0, }; const result = {}; result.Remind = "为安全运作,本功能不能没有用户名字和头像。请补全资料,并尽量与您的微信名字和头像保持一致。点击项目可直接填充微信信息。"; if (param.Type == 0) { result.Text1 = "编辑用户信息"; result.Text2 = ""; result.Text3 = "您的头像、名字请尽量与微信保持一致,这对一部分功能的安全运行非常重要。同时,这也大大方便客服为您处理新手班、排除故障、帐户、活动奖励发放等需要微信联系的重要事宜。"; result.Text4 = "返回"; } else if (param.Type == "binding") { result.Text1 = "请先核对用户信息"; result.Text2 = "使用「帐号绑定」之前,请先核对头像、名字是否与您的微信保持一致,这对本功能安全运行非常重要。这也大大方便客服在您遇到故障或数据问题时在微信中联络到您,即时了解情况,减少您的损失。\n\n如信息不一致,点击对应项目即可直接填充微信头像或名字,无须手动输入。确认后点「下一步」继续。"; result.Text3 = ""; result.Text4 = "下一步"; } else if (param.Type == "family") { result.Text1 = "请先核对用户信息"; result.Text2 = "使用「家庭用户」之前,请先核对头像、名字是否与您的微信保持一致,这对本功能安全运行非常重要。这也大大方便客服在您遇到故障或数据问题时在微信中联络到您,即时了解情况,减少您的损失。\n\n如信息不一致,点击对应项目即可直接填充微信头像或名字,无须手动输入。确认后点「下一步」继续。"; result.Text3 = ""; result.Text4 = "下一步"; } ctx.body = { errcode: 10000, result }; } export async function GetAccreditInfo(ctx) { const param = { Type: ctx.query.Type || 0, }; const result = {}; if (param.Type == 0) { result.Text1 = "请允许秒过"; result.Text2 = "获得您的头像、昵称\n用于注册登录"; result.Text3 = "非常抱歉,不填写名字会造成后续服务无法进行,如要继续使用秒过,烦劳提供名字。由于微信隐私保护,秒过不能直接获得您的信息,这会造成培训、客服、统计、社群、官网等服务无法运行。此信息仅用于秒过,请放心。"; } else { result.Text1 = "用微信注册秒过"; result.Text2 = "请允许秒过\n获得您的微信头像、昵称"; result.Text3 = "非常抱歉,不填写名字会造成后续服务无法进行,如要继续使用秒过,烦劳提供名字。由于微信隐私保护,秒过不能直接获得您的信息,这会造成培训、客服、统计、社群、官网等服务无法运行。此信息仅用于秒过,请放心。"; } result.Btn1 = "允许"; result.Btn2 = "忽略"; ctx.body = { errcode: 10000, result }; } export async function GetMiaoguoLesson2(ctx) { const arr = constantClass.getLessonList(); ctx.body = { errcode: 10000, result: arr }; } export async function GetMiaoguoNewUserImageList(ctx) { const result = constantClass.getNewUserImageList(); ctx.body = { errcode: 10000, result }; } export async function GetMiaoguoNewUserImageList3126(ctx) { const result = constantClass.getNewUserImageList3136(); ctx.body = { errcode: 10000, result }; } export async function GetMiaoguoTipsList(ctx) { const param = { Version: ctx.query.version || "?v=8", }; const result = constantClass.GetMiaoguoTipsList(); for (const section of result) { for (const item of section.List) { item.Cover = item.Cover + param.Version; for (let i = 0; i < item.Images.length; i++) item.Images[i] = item.Images[i] + param.Version; } } ctx.body = { errcode: 10000, result }; } export async function UpdateUserNickNameAndAvatar(ctx) { const param = ctx.request.body; param.UserID = ctx.query.UserID; if (!param.NickName) param.NickName = "陌生用户"; await miaoguo.UpdateUsersByUserID(param); const log = { UserID: param.UserID, LogID: 315, Content: JSON.stringify(param), }; miaoguo.AddMiaoguoLog(log).catch(err => console.error('AddMiaoguoLog failed:', err)); ctx.body = { errcode: 10000 }; } export async function UpdateUserMemberInfoPost(ctx) { const param = ctx.request.body; param.UserID = ctx.query.UserID || "0"; param.FieldType = ctx.query.FieldType || 0; if (param.FieldType == 1) param.NickNameRemark = param.Content; else if (param.FieldType == 2) param.IsMessage = param.Content; else if (param.FieldType == 3) param.MessageTime = param.Content; if (param.FieldType) { delete param.Content; delete param.FieldType; await miaoguo.UpdateUsersByUserID(param); } ctx.body = { errcode: 10000 }; } export async function UpdateUserMemberInfo(ctx) { const param = { UserID: ctx.query.UserID || 0, WSUUserID: ctx.query.WSUUserID || 0, IsTryOut: ctx.query.IsTryOut, IsMember: ctx.query.IsMember, ClassID: ctx.query.ClassID || 0, GroupID: ctx.query.GroupID || 3, NickNameRemark: ctx.query.NickNameRemark, }; let canUpdate = true; if (ctx.query.NickNameRemark && ctx.query.UserID) { delete param.WSUUserID; delete param.IsMember; delete param.ClassID; delete param.GroupID; delete param.IsTryOut; await miaoguo.UpdateUsersByUserID(param); } else if (ctx.query.GroupID && ctx.query.UserID) { delete param.WSUUserID; delete param.IsMember; delete param.ClassID; delete param.NickNameRemark; delete param.IsTryOut; await miaoguo.UpdateUsersByUserID(param); } else if (ctx.query.IsTryOut && ctx.query.UserID) { delete param.WSUUserID; delete param.IsMember; delete param.GroupID; delete param.NickNameRemark; if (param.IsTryOut == -1) param.IsTryOut = 0; const userInfo = await miaoguo.GetUsersInfoByUserID(param); param.ClassID = userInfo[0].ClassID; const classList = await miaoguo.GetClassList(param); param.ProductServiceTime = moment(classList[0].StartDate).add(classList[0].DayNumber - 1, 'days').format('YYYY-MM-DD 23:59:59'); delete param.Sort; await miaoguo.UpdateUsersByUserID(param); } else if (ctx.query.ParentUserID) { delete param.GroupID; delete param.IsMember; delete param.ClassID; delete param.NickNameRemark; delete param.IsTryOut; param.ParentUserID = ctx.query.ParentUserID; if (param.UserID != param.ParentUserID) { const users = await miaoguo.GetUserBindingList({ UserID: ctx.query.ParentUserID || 0 }); if (users && users.length > 0) canUpdate = false; if (canUpdate) { delete param.WSUUserID; await miaoguo.UpdateUsersByUserID(param); } } } else { delete param.NickNameRemark; delete param.IsTryOut; if (param.IsMember === undefined) delete param.IsMember; delete param.WSUUserID; await miaoguo.UpdateUsersByUserID(param); } const log = { UserID: ctx.query.UserID, LogID: 315, Content: JSON.stringify(param), }; miaoguo.AddMiaoguoLog(log).catch(err => console.error('AddMiaoguoLog failed:', err)); ctx.body = { errcode: 10000 }; } export async function GetUserBindingList(ctx) { const param = { UserID: ctx.query.UserID || 0, }; const userInfo = await miaoguo.GetUserBindingList(param); ctx.body = { errcode: 10000, result: userInfo }; } export async function UpdateUserBinding(ctx) { const param = { ParentUserID: ctx.query.ParentUserID || 0, }; await miaoguo.UpdateUserBinding(param); const log = { UserID: ctx.query.ParentUserID, LogID: 319, Content: JSON.stringify(param), }; miaoguo.AddMiaoguoLog(log).catch(err => console.error('AddMiaoguoLog failed:', err)); ctx.body = { errcode: 10000 }; } export async function GetMiaoguoWXUserInfo(ctx) { const param = { UserID: ctx.query.UserID || 0, FieldStr: ctx.query.FieldStr, GetWechatUserID: ctx.query.GetWechatUserID || 0, }; const userInfo = await miaoguo.GetUsersInfoByUserID(param); if (param.GetWechatUserID == 1) { const wechatUserInfo = await miaoguo.GetWechatServiceInfoByMGUserID(param); if (wechatUserInfo && wechatUserInfo.length > 0) userInfo[0].WechatUserID = wechatUserInfo[0].WechatUserID; } const item = userInfo[0]; if (item && item.IsPromoter > 0) { const a = moment(item.PromotionLimitTime); const b = moment(); item.PromotionLimitDays = a.diff(b, 'days'); item.PromotionLimitTime = moment(item.PromotionLimitTime).format('YYYY.MM.DD'); item.WXServiceCharge = config.WXServiceCharge; item.IndividualIncomeTax = config.IndividualIncomeTax; if (!item.PayeeWechatAccount) item.PayeeWechatAccount = item.NickName; if (!item.WXAccount) item.WXAccount = item.NickName; if (!item.RealName) item.RealName = ""; if (item.Introducer) { const userInfo2 = await miaoguo.GetUsersInfoByUserID({ UserID: item.Introducer, FieldStr: "NickName,AvatarUrl" }); item.IntroducerNickName = userInfo2[0].NickName; item.IntroducerAvatarUrl = userInfo2[0].AvatarUrl; } } if (item) { delete item.OpenID; delete item.UnionID; delete item.Flag; } ctx.body = { errcode: 10000, result: item }; } export async function GetUserCardCount(ctx) { const param = { UserID: ctx.query.UserID || 0, CardType: ctx.query.CardType || 2, IsCollect: ctx.query.IsCollect || 0, OrderType: ctx.query.OrderType || 0, HasHardworking: ctx.query.HasHardworking || 0, }; if (param.UserID == "undefined") param.UserID = 0; if (param.CardType == "undefined") param.CardType = 2; if (param.IsCollect == "undefined") param.IsCollect = 0; const cardNumber = await miaoguo.GetCardCount(param); const result = { CardNumber: cardNumber[0].count, }; if (param.CardType == 2) { param.CardType = 0; const cardNormalNumber = await miaoguo.GetCardCount(param); result.NormalNumber = cardNormalNumber[0].count; param.CardType = 1; const cardUrgentNumber = await miaoguo.GetCardCount(param); result.UrgentNumber = cardUrgentNumber[0].count; if (param.OrderType == 5 || param.OrderType == 6) result.CardNumber = result.NormalNumber + result.UrgentNumber; } if (param.HasHardworking == 1) { const param2 = { FieldStr: "ID,CreateTime,IsShare,HardworkingNumber,MilestoneDayNumber,MilestoneID,AphorismID,ImageID", UserID: param.UserID, OrderType: " desc limit 1", WhereStr: " and (MilestoneDayNumber>0 or AphorismID>0) ", }; const list = await miaoguo.GetUserMiaoguoTaskByUserID(param2); if (list && list.length > 0) { const item = list[0]; delete item.CreateTime; item.OrdinalPostfix = stringUtils.GetOrdinalPostfix(item.HardworkingNumber); if (item.MilestoneDayNumber > 0) { const mileStoneArr = constantClass.GetMilestone(); for (const milestone of mileStoneArr) { if (milestone.DayNumber == item.MilestoneDayNumber) { item.Name = milestone.Name; item.BgColor = milestone.Color2; item.IsMilestone = 1; break; } } } else { item.Image = "https://miaguo-1253256735.file.myqcloud.com/web/_share/qmjl_pic_d_" + item.ImageID + ".jpg"; } result.Hardworking = item; } } ctx.body = { errcode: 10000, result }; } export async function GetMiaoguoLearningRecord(ctx) { const param = { CardID: ctx.query.CardID || 0, }; const list = await miaoguo.GetMiaoguoLearningRecord(param); let total = 0; for (const item of list) { item.CreateTime = moment(item.CreateTime).format('YYYY年MM月DD日 HH:mm'); total += item.Duration; item.Duration = stringUtils.getMinuteSecond(item.Duration, true, true); if (item.Duration && item.Duration.indexOf("分") > 0) item.Duration = item.Duration.replace("分", "分钟"); } const result = {}; result.Total = stringUtils.getMinuteSecond(total, true, true); if (result.Total && result.Total.indexOf("分") > 0) result.Total = result.Total.replace("分", "分钟"); result.List = list; ctx.body = { errcode: 10000, result }; } export async function SendMiaoguoCoupon(ctx) { const param = { UserID: Number(ctx.query.UserID) || 0, CouponType: ctx.query.CouponType, LimitTime: ctx.query.LimitTime, IsUse: 0, Flag: 0, }; param.LimitTime = param.LimitTime.replace("年", "-"); param.LimitTime = param.LimitTime.replace("月", "-"); param.LimitTime = param.LimitTime.replace("日", ""); param.LimitTime = moment(param.LimitTime).format("YYYY-MM-DD 23:59:59"); const list = await miaoguo.GetMiaoguoCoupon(param); if (!list || list.length == 0) { if (param.CouponType == 134) param.CouponNumber = 20; await miaoguo.AddMiaoguoCoupon(param); } ctx.body = { errcode: 10000 }; } export async function UseMiaoguoCoupon(ctx) { const param = { UserID: ctx.query.UserID || 0, CouponType: ctx.query.CouponType || 0, CouponNumber: ctx.query.CouponNumber || 0, IsUse: 1, }; await miaoguo.UpdateMiaoguoCoupon(param); param.CouponNumber = parseInt(param.CouponNumber); if (param.CouponNumber > 0) { const userList = await miaoguo.GetUsersInfoByUserID(param); if (userList && userList.length > 0) { const param2 = { UserID: param.UserID, ProductServiceTime: moment(userList[0].ProductServiceTime).add(param.CouponNumber, "days").format('YYYY-MM-DD 23:59:59'), }; await miaoguo.UpdateUsersByUserID(param2); } } ctx.body = { errcode: 10000 }; } export async function ShareMiaoguoTask(ctx) { const param = { UserID: ctx.query.UserID || 0, CreateTime: ctx.query.CreateTime + " 00:00:00", IsShare: 1, }; if (!param.CreateTime || param.CreateTime == "Invalid date 00:00:00" || param.CreateTime < moment().add(-7, "days").format('YYYY-MM-DD 00:00:00')) param.CreateTime = moment().format('YYYY-MM-DD 00:00:00'); await miaoguo.UpdateMiaoguoTask(param); ctx.body = { errcode: 10000 }; } export async function GetIntroducerUserList(ctx) { const param = { UserID: ctx.query.UserID || 0, }; param.type = "web"; const list0 = await miaoguo.GetIntroducerUserList(param); for (const item of list0) { if (item.NickName && item.NickName.length > 5) item.NickName = item.NickName.substr(0, 5) + "..."; else if (item.NickName === "陌生用户") item.NickName = "用户未授权"; if (item.NickName == "用户未授权" && item.NickName != "陌生用户") { item.AvatarUrl = item.AvatarUrl2; item.NickName = item.NickName2; } if (item.AvatarUrl === "../images/universalpic_face_default_blue_120x120.png") item.AvatarUrl = "../images/universalpic_face_default_gray_114x114.png"; else if (item.AvatarUrl && item.AvatarUrl.indexOf("/132")) item.AvatarUrl = item.AvatarUrl.replace("/132", "/46"); } delete param.type; const list = await miaoguo.GetIntroducerUserList(param); for (const item of list) { if (item.NickName && item.NickName.length > 5) item.NickName = item.NickName.substr(0, 5) + "..."; else if (item.NickName === "陌生用户") item.NickName = "用户未授权"; if (item.AvatarUrl === "../images/universalpic_face_default_blue_120x120.png") item.AvatarUrl = "../images/universalpic_face_default_gray_114x114.png"; else if (item.AvatarUrl && item.AvatarUrl.indexOf("/132")) item.AvatarUrl = item.AvatarUrl.replace("/132", "/46"); item.ProductServiceTime = moment(item.ProductServiceTime).format('YYYY-MM-DD HH:mm:ss'); if (item.PayTime) item.PayTime = moment(item.PayTime).format('YYYY-MM-DD HH:mm:ss'); else item.PayTime = "1900-01-01 00:00:00"; } ctx.body = { errcode: 10000, result: { List1: list0, List2: list } }; } export async function GetMiaoguoShareInfo220(ctx) { const param = { ProgramID: 166, Version: ctx.query.Version, }; const result = [{}]; result[0].Text1 = "推广物料"; result[0].Text2 = "转发小程序"; result[0].Text3 = "成效最佳的方法\n转发含有推广码的小程序到群或朋友"; result[0].BtnStr = "转发"; result[0].OpenType = "share"; result[0].BindTap = ""; const versionList = await commonModel.GetProductVersionList(param); if (versionList && versionList[0] && versionList[0].IsShowPay > 0) { result.push({}); result[1].Text1 = "推广物料"; result[1].Text2 = "勤勉接力图片"; result[1].Text3 = "接力图片自带推广二维码\n图片内容丰富,形式生动,非常适合朋友圈\n一方面让关心您的朋友一起见证孩子的努力\n另一方面潜移默化顺带推广"; result[1].Image = "promotion_materials_qmjl.png?0"; result[1].BtnStr = "前往勤勉接力栏目生成图片"; result[1].BindTap = "gotoShareIndex"; result.push({}); result[2].Text1 = "推广物料"; result[2].Text2 = "海报图片"; result[2].Text3 = "点击效果图,生成含有推广码的海报\n长按图片保存到手机相册"; result[2].Images = [{ icon: "promotion_materials_hb01.png?0", id: "1" }, { icon: "promotion_materials_hb04.png?0", id: "4" }, { icon: "promotion_materials_hb02.png?0", id: "2" }, { icon: "promotion_materials_hb03.png?0", id: "3" }]; result.push({}); result[3].Text1 = "推广物料"; result[3].Text2 = "配图文案"; result[3].Text3 = "复制秒过介绍文案\n可自行编辑加工(内含网页链接)"; result[3].BtnStr = "复制文案"; result[3].OpenType = ""; result[3].BindTap = "buildText"; result.push({}); result[4].Text1 = "推广物料"; result[4].Text2 = "网页链接"; result[4].Text3 = "发布含有推广码的网页\n点击「复制链接」,然后粘贴到文字中"; result[4].BtnStr = "复制链接"; result[4].OpenType = ""; result[4].BindTap = "buildUrl"; } ctx.body = { errcode: 10000, result }; } export async function GetMiaoguoShareInfo(ctx) { const param = { ProgramID: 166, Version: ctx.query.Version, }; const result = [{}]; result[0].Text2 = "转发小程序"; result[0].Text3 = "转发含有推荐人信息的小程序"; result[0].BtnStr = "转发"; result[0].OpenType = "share"; result[0].BindTap = ""; const versionList = await commonModel.GetProductVersionList(param); if (versionList && versionList[0] && versionList[0].IsShowPay > 0) { result[0].Text1 = "方法一"; result.push({}); result[1].Text1 = "方法二"; result[1].Text2 = "发到朋友圈"; result[1].Text3 = "发布含有推荐人信息的图片"; result[1].Text4 = "存图步骤"; result[1].Text5 = "点击生成图片\n长按图片保存到手机相册"; result[1].BtnStr = "生成专门图片"; result[1].OpenType = ""; result[1].BindTap = "buildImage"; } ctx.body = { errcode: 10000, result }; } export async function BuildData(ctx) { const param = { Type: ctx.query.Type || 1, TypeDetail: ctx.query.TypeDetail || 1, }; const result = []; const sleep = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds)); const sendTemplateMessage = async (userID, templateID, paramStr, useProduction = false) => { const baseURL = useProduction ? 'https://www.kylx365.com' : 'http://localhost:' + config.port; await axios.get(baseURL + '/api/SendWXServiceTemplateMessage', { params: { UserID: userID, TemplateID: templateID, ...(paramStr ? { ParamStr: paramStr } : {}) }, }); }; if (param.Type == 1) { const sql = "select u.UserID,u.NickName,count(distinct DATE_FORMAT(r.CreateTime,'%Y%m%d')) as '天数',sum(r.Duration)/60 as '使用分钟数' " + "from MiaoguoWXUsers u " + "inner join MiaoguoCard c on u.UserID=c.UserID " + "inner join MiaoguoLearningRecord r on c.MiaoguoCardID = r.CardID " + "where r.Flag=0 and r.CreateTime>='2019-07-01' and r.CreateTime<'2019-09-01' " + "group by u.UserID having 天数>=5 order by 天数 desc;"; const list = await miaoguo.RunSql([], sql); const limitTime = moment().add(6, "months").format("YYYY-MM-DD"); for (const item of list) { let day = 0; if (item.天数 >= 50) day = 30; else if (item.天数 >= 40) day = 20; else if (item.天数 >= 30) day = 15; else if (item.天数 >= 20) day = 10; else if (item.天数 >= 10) day = 5; else if (item.天数 >= 5) day = 2; const insertSql = "insert into MiaoguoCoupon (CouponType,UserID,LimitTime,IsUse,CouponNumber) values (136," + item.UserID + ",'" + limitTime + "',0," + day + ");"; result.push(insertSql); } } else if (param.Type == 2) { const list = await miaoguo.RunSql([], 'select UserID from MiaoguoWXUsers where IsPay=1 and ClassID=4;'); for (const item of list) { const response = await axios.get('http://localhost:' + config.port + '/api/GetMiaoguoCheckList', { params: { UserID: item.UserID }, }).then(res => res.data); const checklist = response.result; let day = 0; for (let i = 1; i < checklist.List.length; i++) { if (checklist.List[i].Max <= checklist.List[i].Finished) day += 2; } await miaoguo.RunSql([ checklist.UserID, moment().add(6, 'months').format('YYYY-MM-DD'), day, ], 'insert into MiaoguoCoupon (CouponType,UserID,LimitTime,IsUse,CouponNumber) values (134,?,?,0,?);'); await sleep(500); } } else if (param.Type == 3) { const sql = "select w.UserID from MiaoguoWXUsers u inner join WechatServiceWXUsers w on u.UnionID=w.UnionID " + "where u.IsPay=1 and u.UserID not in (" + "select w.Introducer from WechatServiceWXUsers w inner join MiaoguoWXUsers u on w.Introducer=u.UserID " + "where u.IsPay=1 and u.UserID>7 group by w.Introducer) union all " + "select w2.UserID from WechatServiceWXUsers w inner join MiaoguoWXUsers u on w.Introducer=u.UserID " + "inner join WechatServiceWXUsers w2 on w.UnionID=w2.UnionID " + "where u.IsPay=1 and u.UserID>7 group by w.Introducer having count(*)<3;"; const list = await miaoguo.RunSql([], sql); const userIDs = _.uniq(list.map(item => item.UserID).sort()); for (const userID of userIDs) { if (userID === 1) await sendTemplateMessage(userID, 'PopularizationPlan'); await sleep(500); } } else if (param.Type == 4) { let sql = "select wu.UserID,u.NickName,count(distinct DATE_FORMAT(r.CreateTime,'%Y%m%d')) as 'Count',sum(r.Duration)/60 as '使用分钟数',count(distinct c.MiaoguoCardID) from MiaoguoWXUsers u inner join WechatServiceWXUsers wu on u.UnionID=wu.UnionID inner join MiaoguoCard c on u.UserID=c.UserID inner join MiaoguoLearningRecord r on c.MiaoguoCardID = r.CardID where r.CreateTime>='2020-01-01' and r.CreateTime<'2021-01-01' and u.IsPay=1 and u.GroupID=1 group by u.UserID having Count>=100 order by Count desc;"; if (param.TypeDetail == 2) { sql = "select wu.UserID,u.NickName,count(distinct DATE_FORMAT(r.CreateTime,'%Y%m%d')) as 'Count',sum(r.Duration)/60 as '使用分钟数',count(distinct c.MiaoguoCardID) from MiaoguoWXUsers u inner join WechatServiceWXUsers wu on u.UnionID=wu.UnionID inner join MiaoguoCard c on u.UserID=c.UserID inner join MiaoguoLearningRecord r on c.MiaoguoCardID = r.CardID where r.CreateTime>='2020-01-01' and r.CreateTime<'2021-01-01' and u.IsPay=1 and u.GroupID=2 group by u.UserID having Count>=50 order by Count desc;"; } else if (param.TypeDetail == 3) { sql = "select wu.UserID,u.NickName,count(mt.ID) as 'Count' from MiaoguoTask mt inner join MiaoguoWXUsers u on mt.UserID=u.UserID inner join WechatServiceWXUsers wu on u.UnionID=wu.UnionID where mt.IsShare=1 and u.IsPay=1 and u.GroupID=1 and mt.CreateTime>='2020-12-07 00:00:00' and mt.CreateTime<'2021-01-06 00:00:00' group by mt.UserID order by count(mt.ID) desc;"; } else if (param.TypeDetail == 4) { sql = "select wu.UserID,u.NickName,count(mt.ID) as 'Count' from MiaoguoTask mt inner join MiaoguoWXUsers u on mt.UserID=u.UserID inner join WechatServiceWXUsers wu on u.UnionID=wu.UnionID where mt.IsShare=1 and u.IsPay=1 and u.GroupID=2 and mt.CreateTime>='2020-12-07 00:00:00' and mt.CreateTime<'2021-01-06 00:00:00' group by mt.UserID order by count(mt.ID) desc;"; } const list = await miaoguo.RunSql([], sql); /* * 旧系统实际发送语句处于注释状态。以下代码已经转换为 Node.js 24 * 写法;需要恢复该历史奖励时,可解除注释后先用 UserID=1 验证。 for (const item of list) { let messageParam; if (param.TypeDetail == 1) { messageParam = { Title1: '2020年度学习勤勉奖', Title2: '奖励有效期[CouponNumber]天', Title3: '您练习我支持,奖励在2020年持续练习的孩子和家长,祝学业进步!', LimitTime: '2021-07-01', CouponNumber: '30' }; } else if (param.TypeDetail == 2) { messageParam = { Title1: '2020年度学习勤勉奖', Title2: '奖励有效期[CouponNumber]天', Title3: '您练习我支持,奖励在2020年持续练习的孩子和家长,祝学业进步!', LimitTime: '2021-07-01', CouponNumber: '20' }; } else if (param.TypeDetail == 3 || param.TypeDetail == 4) { messageParam = { Title1: '2020年第一学期期末备考30天冲刺奖', Title2: '奖励有效期[CouponNumber]天', Title3: '您练习我支持,奖励在2020年12月07日至2021年01月05日期间勤勉接力的孩子和家长,祝学业进步!', LimitTime: '2021-07-01', CouponNumber: String(item.Count) }; } if (messageParam) { await sendTemplateMessage(item.UserID, 'CouponSendRemind', JSON.stringify(messageParam)); await sleep(1000); } } */ } else if (param.Type == 5) { const sql = 'select u.NickName,w.UserID from MiaoguoWXUsers u inner join MiaoguoWXUsers u2 on u.Introducer=u2.UserID inner join WechatServiceWXUsers w on u2.UnionID=w.UnionID where u.IsPay=1 and u.ClassID=4;'; const list = await miaoguo.RunSql([], sql); for (const item of list) { if (item.UserID == 1) { await sendTemplateMessage(item.UserID, 'IntroducerRemind', JSON.stringify({ NickName: item.NickName })); } await sleep(500); } } else if (param.Type == 6) { const list = await miaoguo.RunSql([], 'select * from WechatServiceWXUsers where Subscribe=1 and IsProbation=0;'); for (const item of list) { await sendTemplateMessage(item.UserID, 'SubscribeNoPayRemind'); await sleep(500); } } else if (param.Type == 7) { const sql = "SELECT mt.UserID,u.NickName,u.AvatarUrl,sum(mt.IsShare) as '天数' FROM kylx365_db.MiaoguoTask mt " + "inner join MiaoguoWXUsers u on mt.UserID=u.UserID " + "where mt.CreateTime>='2019-09-01' and mt.CreateTime<'2019-10-01' and mt.IsShare=1 and u.UserID>7 " + "group by mt.UserID having 天数>=1 order by 天数 desc;"; const list = await miaoguo.RunSql([], sql); const limitTime = moment().add(6, 'months').format('YYYY-MM-DD'); for (const item of list) { result.push("insert into MiaoguoCoupon (CouponType,UserID,LimitTime,IsUse,CouponNumber) values (137," + item.UserID + ",'" + limitTime + "',0," + item.天数 + ");"); } } else if (param.Type == 8) { /* * 旧系统曾按 2024 年任务记录自动筛选名单,后来改成下面的人工确认名单。 * 保留已转换的旧查询,未来需要恢复自动筛选时可直接替换当前 sql。 const taskStatisticsSql = "SELECT mt.UserID,w.UserID as 'WUserID',u.NickName as '小程序昵称',w.NickName as '服务号昵称',u.NickNameRemark as '昵称备注',sum(mt.IsShare) as '群接力天数' " + "FROM kylx365_db.MiaoguoTask mt inner join MiaoguoWXUsers u on mt.UserID=u.UserID " + "inner join WechatServiceWXUsers w on u.UnionID=w.UnionID " + "where mt.CreateTime>='2024-01-01' and mt.CreateTime<='2025-01-01' " + "and mt.IsShare=1 and u.UserID>7 and u.ProductServiceTime>='2025-01-06' " + "group by mt.UserID having 群接力天数>=20 order by 群接力天数 desc;"; */ const sql = "SELECT u.UserID,w.UserID as 'WUserID',u.NickName as '小程序昵称',w.NickName as '服务号昵称',u.NickNameRemark as '昵称备注' " + "FROM MiaoguoWXUsers u inner join WechatServiceWXUsers w on u.UnionID=w.UnionID " + "where u.UserID in " + "(1,699,321,17161,15362,30894,36549,24084,16297,26887,21006,5806,199,31917,39733,34777,6168,29864,27170,27101,13239,33957,39392,40981,39421,30393,23093,22192,12683,24149,41274,12164,41795,41573,1760,15387,25473,33746,3232,31117,15456,23731,3438,41817,17110) " + 'order by u.UserID;'; const list = await miaoguo.RunSql([], sql); for (const item of list) { const day = 20; const messageParam = { Title1: '2025年度有奖勤勉接力', Title2: '赠送[CouponNumber]天', Title3: '一年来辛苦您了,恭喜您和孩子在这不平凡的一年取得不平凡的成就。希望勤勉接力活动能为帮助孩子建立练习习惯起到积极作用,感谢您一直以来的支持。祝全家健康,学业进步,事业昌盛,新年快乐!', LimitTime: '2026-07-11', CouponNumber: String(day), }; await sendTemplateMessage(item.WUserID, 'CouponSendRemind', JSON.stringify(messageParam), true); result.push("insert into MiaoguoCoupon (CouponType,UserID,LimitTime,IsUse,CouponNumber) values (423," + item.UserID + ",'2026-07-11',0," + day + ");"); await sleep(500); } } ctx.body = result.join('\n'); } export async function BuildBookTotal(ctx) { const confirmRun = ctx.query.ConfirmRun == 1; const books = await miaoguo.RunSql( [], "SELECT * FROM HanziBook where Flag=0 and Name2 in ('上学期','下学期') order by Sort;", ); const result = []; for (const book of books) { let wordNum = 0; if (book.Name1 == "语文写字" || book.Name1 == "语文识字" || book.Name1 == "语文词语") { const units = await hanzi.GetHanziUnits({ BookID: book.ID }); for (const unit of units) { if (book.Name1 == "语文词语") { const arr = String(unit.Example || "").split(",").filter(Boolean); wordNum += arr.length; } else { wordNum += String(unit.Example || "").length; } } } else if (book.Name1 == "语文古诗文" || book.Name1 == "语文日积月累") { const poems = await hanzi.GetAncientPoetryList({ BookID: book.ID }); wordNum = poems.length; } else { continue; } result.push({ ID: book.ID, Name: book.Name, WordNum: wordNum }); if (confirmRun) { await miaoguo.RunSql([wordNum, book.ID], "update HanziBook set WordNum=? where ID=?;"); } } ctx.body = { errcode: 10000, result, executed: confirmRun }; } export async function UpdateUserConfigBasicCardLimit(ctx) { const param = { UserID: ctx.query.UserID || 0, BasicCardLimit: ctx.query.BasicCardLimit || 0, CurrentUserID: ctx.query.CurrentUserID || 0, }; const log = { UserID: ctx.query.UserID, LogID: 314, Content: JSON.stringify(param), }; miaoguo.AddMiaoguoLog(log).catch(err => console.error('AddMiaoguoLog failed:', err)); delete param.CurrentUserID; await miaoguo.UpdateUserConfig(param); ctx.body = { errcode: 10000 }; } export async function UploadUserConfig(ctx) { const param = ctx.request.body || {}; param.UserID = Number(ctx.query.UserID || 0); const dataType = ctx.query.Type || "getData"; param.CardType = Number(param.CardType) || 0; param.CardMaxNumberNew = param.CardMaxNumberNew; param.CardMaxNumberHistory = param.CardMaxNumberHistory || 100; param.CardMaxNumberNewUrgent = param.CardMaxNumberNewUrgent; param.CardMaxNumberHistoryUrgent = param.CardMaxNumberHistoryUrgent || 100; param.SortTypeIndex = param.SortTypeIndex || 0; param.MemoryLevel = param.MemoryLevel || 0; param.ClickType = param.ClickType || 0; param.FolderOrder = param.FolderOrder || "UpdateTime desc"; param.IsFolderPractice = param.IsFolderPractice || 0; param.IsAutoSound = param.IsAutoSound; const currentUserID = param.CurrentUserID; delete param.CurrentUserID; if (param.SecondConfigArray) { if (stringUtils.IsArray(param.SecondConfigArray)) param.SecondConfigArray = param.SecondConfigArray.join(","); } else { param.SecondConfigArray = "0,0,0"; } if (param.ColorIndexArr) { if (stringUtils.IsArray(param.ColorIndexArr)) param.ColorIndexArr = param.ColorIndexArr.join(","); } else { param.ColorIndexArr = "1,2,3,4"; } if (!param.IsAutoSound && param.IsAutoSound !== "0" && param.IsAutoSound !== 0) param.IsAutoSound = 1; if (!param.CardMaxNumberNew && param.CardMaxNumberNew != 0) param.CardMaxNumberNew = 10; if (!param.CardMaxNumberNewUrgent && param.CardMaxNumberNewUrgent != 0) param.CardMaxNumberNewUrgent = 10; if (dataType == "getData") { const userList = await miaoguo.GetUserConfigData(param); if (userList[0] && userList[0].SymbolList) userList[0].SymbolList = JSON.parse(userList[0].SymbolList); if (userList.length > 0) ctx.body = { errcode: 10000, result: userList[0] }; else ctx.body = { errcode: 10000, result: param }; return; } const dbParam = { ...param }; if (stringUtils.IsArray(dbParam.SymbolList)) dbParam.SymbolList = JSON.stringify(dbParam.SymbolList); const userList = await miaoguo.GetUserConfigData(param); if (userList.length > 0) await miaoguo.UpdateUserConfig(dbParam); else await miaoguo.AddUserConfig(dbParam); const logParam = { ...param, CurrentUserID: currentUserID }; const log = { UserID: ctx.query.UserID, LogID: 314, Content: JSON.stringify(logParam), }; miaoguo.AddMiaoguoLog(log).catch(err => console.error('AddMiaoguoLog failed:', err)); ctx.body = { errcode: 10000, result: logParam }; } export async function DeleteAllMiaoguoCard(ctx) { const param = { UserID: ctx.query.UserID, Flag: -1, IsAll: 1, }; param.DeleteTime = moment().format("YYYY-MM-DD HH:mm:ss"); param.UpdateTime = param.DeleteTime; await miaoguo.UpdateCard(param); const log = { UserID: ctx.query.UserID, LogID: 454, Content: JSON.stringify(param), }; miaoguo.AddMiaoguoLog(log).catch(err => console.error('AddMiaoguoLog failed:', err)); ctx.body = { errcode: 10000 }; } export async function BuildInitData(ctx) { const param = { UserID: ctx.query.UserID || "0", }; if (Number(param.UserID) > 0) { const count = await miaoguo.GetCardCount(param); if (count[0] && count[0].count == 0) { const cardIDs = []; for (let i = 0; i < 10; i++) { const now = moment().format('YYYY-MM-DD HH:mm:ss'); const insertInfo = await miaoguo.AddCard({ UserID: param.UserID, CreateTime: now, LimitTime: now, IsTodayStudy: 1, }); cardIDs.push(insertInfo.insertId); } for (let i = 0; i < 10; i++) { const listContent = await miaoguo.GetCardContentList(i + 1); for (const contentType of [0, 1, 2, 3]) { await miaoguo.AddCardContent({ MiaoguoCardID: cardIDs[i], ContentType: contentType, Content: listContent[contentType] ? listContent[contentType].Content : "", }); } } } } ctx.body = { errcode: 10000, result: 1 }; } export async function updateMiaoguoTaskData(ctx) { const param = { UserID: ctx.query.UserID || 0, }; let startDate = moment().add(-30, "days").format("YYYY-MM-DD"); let userFilter = ""; if (Number(param.UserID) > 0) { startDate = moment().add(-1200, "days").format("YYYY-MM-DD"); userFilter = " and mc.UserID=" + Number(param.UserID); } const sqlUsers = "select distinct mc.UserID from MiaoguoLearningRecord mlr inner join MiaoguoCard mc on mc.MiaoguoCardID=mlr.CardID where mlr.Flag=0 " + userFilter + " and mlr.CreateTime>=?;"; const list = await miaoguo.RunSql([startDate], sqlUsers); const userIDs = []; for (const item of list) { const sqlDaily = "select DATE_FORMAT(r.CreateTime,'%Y-%m-%d') as CreateTime,count(distinct r.CardID) as Count,sum(r.Duration) as Sum " + "from MiaoguoWXUsers u " + "inner join MiaoguoCard c on u.UserID=c.UserID " + "inner join MiaoguoLearningRecord r on c.MiaoguoCardID = r.CardID " + "where r.Flag=0 and u.UserID=? and r.CreateTime>=? group by CreateTime order by CreateTime;"; const dailyList = await miaoguo.RunSql([item.UserID, startDate], sqlDaily); for (const dayInfo of dailyList) { const createTime = dayInfo.CreateTime + " 00:00:00"; const existing = await miaoguo.RunSql([item.UserID, createTime], "select * from MiaoguoTask where UserID=? and CreateTime=?;"); if (existing && existing.length > 0) { await miaoguo.RunSql( [dayInfo.Count, dayInfo.Sum, item.UserID, createTime], "update MiaoguoTask set CardTask=?,Duration=? where UserID=? and CreateTime=?;", ); } else { await miaoguo.RunSql( [item.UserID, createTime, dayInfo.Count, dayInfo.Sum], "insert into MiaoguoTask (UserID,CreateTime,CardTask,Duration,IsShare,Flag) values (?,?,?,?,0,0);", ); } } userIDs.push(item.UserID); } if (userIDs.length > 0) { const ids = userIDs.map(id => Number(id)).filter(id => Number.isInteger(id) && id > 0).join(','); const resetSql = Number(param.UserID) > 0 ? "update MiaoguoTask set MilestoneID=0,MilestoneDayNumber=0,HardworkingNumber=0 where UserID=?;" : "update MiaoguoTask set MilestoneID=0,MilestoneDayNumber=0,HardworkingNumber=0 where UserID in (" + ids + ");"; await miaoguo.RunSql(Number(param.UserID) > 0 ? [Number(param.UserID)] : [], resetSql); const taskUsers = await miaoguo.RunSql( Number(param.UserID) > 0 ? [Number(param.UserID)] : [], Number(param.UserID) > 0 ? "SELECT distinct UserID FROM MiaoguoTask where UserID=? order by UserID;" : "SELECT distinct UserID FROM MiaoguoTask where UserID in (" + ids + ") order by UserID;", ); const milestone = constantClass.GetMilestone(); for (const user of taskUsers) { const tasks = await miaoguo.RunSql([user.UserID], "SELECT * FROM MiaoguoTask where CardTask>0 and UserID=? order by CreateTime;"); if (!tasks || tasks.length == 0) continue; let milestoneIDs = []; for (let i = 0; i < 40; i++) milestoneIDs.push(i + 1); milestoneIDs = stringUtils.RandomArray(milestoneIDs); let milestoneIndex = 0; let hardworkingIndex = 0; for (let i = 0; i < tasks.length; i++) { const update = { ID: tasks[i].ID, DayNumber: i + 1, }; const milestoneItem = milestone.find(item => item.DayNumber == i + 1); if (milestoneItem) { update.MilestoneDayNumber = milestoneItem.DayNumber; update.MilestoneID = milestoneIDs[milestoneIndex++]; } if (tasks[i].IsShare > 0 || update.MilestoneDayNumber > 0 || tasks[i].AphorismID > 0) update.HardworkingNumber = ++hardworkingIndex; await miaoguo.UpdateMiaoguoTask(update); } } } ctx.body = { errcode: 10000, result: { UserCount: userIDs.length } }; } export async function updateMiaoguoFamily(ctx) { const param = { UserID: ctx.query.UserID || 0, UpdateType: ctx.query.UpdateType || "add", NewUserID: ctx.query.NewUserID || 0, Time: ctx.query.Time, }; let result = 0; const yestoday = moment().add(-10, "minutes").format("YYYY-MM-DD HH:mm:ss"); if (yestoday > param.Time) { result = -1; } else { const list = await miaoguo.GetMiaoguoFamilyByUserID(param); if (list && list.length > 0) { if (param.UpdateType == "delete") { const obj = { ID: list[0].ID, UpdateTime: moment().format("YYYY-MM-DD HH:mm:ss"), Flag: -1, }; await miaoguo.UpdateMiaoguoFamily(obj); miaoguo.AddMiaoguoLog({ UserID: ctx.query.UserID, LogID: 372, Content: JSON.stringify(param) }).catch(err => console.error('AddMiaoguoLog failed:', err)); } else if (param.UpdateType == "add") { if (list[0].UserID1 == param.NewUserID) result = -2; if (list[0].UserID2 == param.NewUserID) result = -2; if (list[0].UserID3 == param.NewUserID) result = -2; if (list[0].UserID1 > 0 && list[0].UserID2 > 0 && list[0].UserID3 > 0) result = -3; if (result == 0) { const obj = { ID: list[0].ID, UpdateTime: moment().format("YYYY-MM-DD HH:mm:ss"), }; if (!list[0].UserID1 || list[0].UserID1 == "") obj.UserID1 = param.NewUserID; else if (!list[0].UserID2 || list[0].UserID2 == "") obj.UserID2 = param.NewUserID; else if (!list[0].UserID3 || list[0].UserID3 == "") obj.UserID3 = param.NewUserID; await miaoguo.UpdateMiaoguoFamily(obj); miaoguo.AddMiaoguoLog({ UserID: ctx.query.UserID, LogID: 373, Content: JSON.stringify(param) }).catch(err => console.error('AddMiaoguoLog failed:', err)); } } } else if (param.UpdateType == "add") { const obj = { UpdateTime: moment().format("YYYY-MM-DD HH:mm:ss"), UserID1: param.UserID, UserID2: param.NewUserID, Flag: 0, }; await miaoguo.AddMiaoguoFamily(obj); miaoguo.AddMiaoguoLog({ UserID: ctx.query.UserID, LogID: 373, Content: JSON.stringify(param) }).catch(err => console.error('AddMiaoguoLog failed:', err)); } } ctx.body = { errcode: 10000, result }; } export async function GetMiaoguoCheckList(ctx) { const param = { UserID: ctx.query.UserID || 0, FieldStr: 'UserID,IsMember,NickName,AvatarUrl', }; const result = {}; result.List = [ {}, { Title: '加入秒过学习班群(微信群)', Max: 1 }, { Title: '收集孩子的错题', Max: 1 }, { Title: '第一课', Remark: "概念课:学习法与记忆规律(约30分钟)", Max: 50, Finished: 0 }, { Title: '第二课', Remark: "操作课:实战制作合格的题卡(约20分钟)", Max: 50, Finished: 0 }, { Title: '第三课', Remark: "操作课:陪伴孩子完成学习任务(约20分钟)", Max: 50, Finished: 0 }, { Title: '第四课', Remark: "精要解析(约10分钟)", Max: 50, Finished: 0 }, { Title: '添加30张题卡', Max: 30 }, { Title: '带领孩子完成20分钟学习任务', Max: 1200 }, { Title: '分享题卡到班群3次', Max: 3 }, { Title: '勤勉接力到班群3天', Max: 3 }, ]; const lessonCount = [0, 28, 7, 6, 14]; const userInfo = await miaoguo.GetUsersInfoByUserID(param); if (userInfo && userInfo.length > 0) { result.UserID = userInfo[0].UserID; result.NickName = userInfo[0].NickName; result.AvatarUrl = userInfo[0].AvatarUrl; if (userInfo[0].IsMember == 1) { result.List[1].Finished = 1; result.List[2].Finished = 1; } const lesson = await miaoguo.GetUserMiaoguoLesson(param); if (lesson && lesson.length > 0) { const lessonCount2 = ["", "", "", "", ""]; for (const item of lesson) lessonCount2[item.LessonID] += "," + item.Content; for (let i = 1; i < lessonCount2.length; i++) { if (lessonCount2[i].length > 0) { lessonCount2[i] = _.uniq(lessonCount2[i].substr(1).split(",")); result.List[i + 2].Finished = Math.round(100 * lessonCount2[i].length / lessonCount[i]); } else { result.List[i + 2].Finished = 0; } } } param.CheckList = 1; const cardNumber = await miaoguo.GetCardCount(param); result.List[7].Finished = 0; if (cardNumber && cardNumber.length > 0) result.List[7].Finished = cardNumber[0].count; delete param.CheckList; param.CreateTime = "2019-03-01"; const taskData = await miaoguo.GetTaskData(param); result.List[8].Finished = 0; if (taskData && taskData.length > 0 && taskData[0].Duration) result.List[8].Finished = taskData[0].Duration; result.List[8].FinishedStr = stringUtils.getMinuteSecond(result.List[8].Finished); const cardShare = await miaoguo.GetUserMiaoguoCardShare(param); result.List[9].Finished = 0; if (cardShare && cardShare.length > 0) result.List[9].Finished = cardShare.length; const taskShare = await miaoguo.GetUserMiaoguoTaskShare(param); result.List[10].Finished = 0; if (taskShare && taskShare.length > 0) result.List[10].Finished = taskShare.length; } let finishCount = 0; for (let i = 1; i < result.List.length; i++) { if (result.List[i].Finished >= result.List[i].Max) finishCount++; } result.FinishedCount = Math.round(100 * finishCount / (result.List.length - 1)); if (result.FinishedCount == 100) await miaoguo.UpdateUsersByUserID({ UserID: param.UserID, IsFinishTask: 1 }); ctx.body = { errcode: 10000, result }; } export async function GetAnnualReport(ctx) { const param = { UserID: ctx.query.UserID || 0, TimeStart: ctx.query.Time || "2024-01-01 00:00:00", TimeEnd: ctx.query.Time || "2024-12-31 23:59:59", }; const result = {}; let sql = `select u.UserID,count(distinct DATE_FORMAT(r.CreateTime,'%Y%m%d')) as 'DayNumber' from MiaoguoWXUsers u inner join MiaoguoCard c on u.UserID=c.UserID inner join MiaoguoLearningRecord r on c.MiaoguoCardID = r.CardID where r.Flag=0 and r.CreateTime>='${param.TimeStart}' and r.CreateTime<='${param.TimeEnd}' and IsPay=1 group by u.UserID order by DayNumber desc;`; let result1 = await miaoguo.RunSql({}, sql); if (result1 && result1.length > 0) { let index = 0; for (let i = 0; i < result1.length; i++) { if (result1[i].UserID == param.UserID) { index = i; break; } } result.Hard = Math.round(10000 * (result1.length - index) / result1.length) / 100 + "%"; } sql = `SELECT m.UserID,m.CreateTime,Count(distinct DATE_FORMAT(mlr.CreateTime,'%Y-%m-%d')) as 'DayNum',Count(mlr.ID) as 'RememberNum',Sum(mlr.Duration) as 'PracticeTime' FROM MiaoguoWXUsers m left join MiaoguoCard mc on m.UserID=mc.UserID left join MiaoguoLearningRecord mlr on mc.MiaoguoCardID=mlr.CardID where mlr.Flag=0 and m.UserID=${param.UserID} and mlr.CreateTime>='${param.TimeStart}' and mlr.CreateTime<='${param.TimeEnd}';`; result1 = await miaoguo.RunSql({}, sql); if (result1 && result1.length > 0) { result.StartDate = moment(result1[0].CreateTime).format("YYYY年MM月DD日"); if (result.StartDate < moment(param.TimeStart).format("YYYY年MM月DD日")) result.StartDate = moment(param.TimeStart).format("YYYY年MM月DD日"); result.EndDate = moment(param.TimeEnd).format("YYYY年MM月DD日"); result.DayNum = result1[0].DayNum; result.RememberNum = result1[0].RememberNum; result.PracticeTime = stringUtils.getMinuteSecond(result1[0].PracticeTime, true); } const shareInfo = await miaoguo.GetUserMiaoguoTaskShare(param); result.ShareNum = 0; if (shareInfo && shareInfo.length > 0) result.ShareNum = shareInfo.length; sql = `SELECT DATE_FORMAT(mlr.CreateTime,'%m月') as MonthName,Sum(mlr.Duration)/60 as 'PracticeTime' FROM MiaoguoCard mc left join MiaoguoLearningRecord mlr on mc.MiaoguoCardID=mlr.CardID where mlr.Flag=0 and mc.UserID=${param.UserID} and DATE_FORMAT(mlr.CreateTime,'%Y-%m') is not null and mlr.CreateTime>='${param.TimeStart}' and mlr.CreateTime<='${param.TimeEnd}' group by DATE_FORMAT(mlr.CreateTime,'%Y-%m');`; result1 = await miaoguo.RunSql({}, sql); if (result1 && result1.length > 0) { for (const item of result1) item.PracticeTime = Math.round(item.PracticeTime); result.PracticeMonth = result1; } sql = `SELECT DATE_FORMAT(mc.CreateTime,'%Y%m') as MonthName,Count(mc.MiaoguoCardID) as 'CardNum' FROM MiaoguoCard mc where mc.UserID=${param.UserID} and mc.CreateTime>='${param.TimeStart}' and mc.CreateTime<='${param.TimeEnd}' and mc.Flag=0 group by DATE_FORMAT(mc.CreateTime,'%Y-%m');`; result1 = await miaoguo.RunSql({}, sql); if (result1 && result1.length > 0) { result.CardNull = ""; const monthStart = Number(result1[0].MonthName); const monthEnd = Number(moment(param.TimeEnd).format("YYYYMM")); for (let j = monthStart; j <= monthEnd; j++) { let hasMonth = false; for (const item of result1) { if (j == Number(item.MonthName)) { hasMonth = true; break; } } if (!hasMonth) result.CardNull += Number(j.toString().substr(4)) + " "; } result.CardNull = stringUtils.Trim(result.CardNull); if (!result.CardNull) result.CardNull = "每月都有"; } sql = `Select Count(*) as CardNum from MiaoguoCard where UserID=${param.UserID} and Flag=0;`; result1 = await miaoguo.RunSql({}, sql); if (result1 && result1.length > 0) result.CardNum = result1[0].CardNum; sql = `Select Count(*) as NewCardNum from MiaoguoCard where UserID=${param.UserID} and Flag=0 and CreateTime>='${param.TimeStart}' and CreateTime<='${param.TimeEnd}';`; result1 = await miaoguo.RunSql({}, sql); if (result1 && result1.length > 0) result.NewCardNum = result1[0].NewCardNum; sql = `SELECT BtnNumber,Count(mlr.ID) as 'MemoryEvaluation' FROM MiaoguoCard mc left join MiaoguoLearningRecord mlr on mc.MiaoguoCardID=mlr.CardID where mlr.Flag=0 and mc.UserID=${param.UserID} and mlr.BtnNumber>=0 and mlr.BtnNumber<>2 and mlr.CreateTime>='${param.TimeStart}' and mlr.CreateTime<='${param.TimeEnd}' group by BtnNumber;`; result1 = await miaoguo.RunSql({}, sql); if (result1 && result1.length > 0) { let memoryEvaluationTotal = 0; for (const item of result1) memoryEvaluationTotal += item.MemoryEvaluation; const obj = {}; for (const item of result1) { const rate = Math.round(100 * item.MemoryEvaluation / memoryEvaluationTotal) + "%"; switch (item.BtnNumber) { case 0: obj.Shelve = rate; break; case 1: obj.Red = rate; break; case 3: obj.Yellow = rate; break; case 4: obj.Green = rate; break; case 5: obj.Blue = rate; break; } } if (!obj.Shelve) obj.Shelve = "0%"; if (!obj.Red) obj.Red = "0%"; if (!obj.Yellow) obj.Yellow = "0%"; if (!obj.Green) obj.Green = "0%"; if (!obj.Blue) obj.Blue = "0%"; result.MemoryEvaluation = obj; } ctx.body = { errcode: 10000, result }; } export async function GetAwardInfo(ctx) { const param = { UserID: ctx.query.UserID || 0, Time: ctx.query.Time || "", }; if (!param.Time) param.Time = moment().add(-7, "days").format("YYYY-MM-DD HH:mm:ss"); const result = {}; const info = await miaoguo.GetAwardInfo(param); const num1 = info[0].Count; param.IsWebchat = true; const info1 = await miaoguo.GetAwardInfo(param); const num2 = info1[0].Count; delete param.IsWebchat; param.IsBill = true; result.AwardDayNumber = 0; const info2 = await miaoguo.GetAwardInfo(param); if (info2 && info2.length > 0 && info2[0].BillTime) { result.BillTime = moment(info2[0].BillTime).format("YYYY年MM月DD日"); result.AwardDayNumber = Number(info2[0].Count); } result.List = [ { Name: "好友报名", PersonNum: Number(num2), Content: "奖励5天/人", Total: Number(num2) * 5 + "天" }, { Name: "好友续费", PersonNum: Number(num1), Content: "奖励115天/人", Total: Number(num1) * 115 + "天" }, ]; result.Time = moment(param.Time).format("YYYY年MM月DD日"); result.Remark = "报名人数含续费人数(流程为先报名后续费),在续费奖励的120天里,包含了报名奖励的5天。为方便向您说明,表格以单项形式列支,所以续费奖励独立显示为115天。"; result.Total = Number(num2) * 5 + Number(num1) * 115; ctx.body = { errcode: 10000, result }; } export async function GetApprovalUserList(ctx) { const param = { ClassID: ctx.query.ClassID || 0, }; const url = "GetApprovalUserList"; let result = globalCache.get(url); if (result === 0) { result = []; let arr = [8, 9, 10, 11, 12, 13, 105, 141, 204, 206, 209, 216, 222, 229, 238, 248, 252, 402, 526, 568, 571, 787, 1355, 1356, 6137, 8055, 12363]; const list2 = await payInfo.GetMiaoguoPayInfoList(); for (const item of list2) arr.push(item.UserID); arr = _.concat(arr, 348, 364, 456, 597, 699, 735, 1025, 1018, 1009, 1021, 1017); const list3 = await payInfo.GetMiaoguoPayInfoList({ webtype: "web" }); for (const item of list3) arr.push(item.UserID); arr.sort(); arr = _.union(arr); let list = await miaoguo.GetApprovalUserList(param); for (const item of list) { for (const userID of arr) { if (userID == item.UserID) { item.CreateTime = moment(item.CreateTime).format('YY.MM.DD HH:mm'); item.LastLoginTime = moment(item.LastLoginTime).format('YY.MM.DD HH:mm'); if (item.IsProbation == 1) { const time1 = moment().format("YYYYMMDDHHmm"); const time2 = moment(item.LimitTime).add(48, "hours").format("YYYYMMDDHHmm"); if (time1 > time2) item.CssBg = "background-color: #D32F2F;"; if (item.ProbationPayTime) item.ProbationPayTime = moment(item.ProbationPayTime).format('YY.MM.DD HH:mm'); else { if (item.UserID > 199) item.ProbationPayTime = "*"; else item.ProbationPayTime = "#"; } } else { item.ProbationPayTime = ""; } if (!item.Subscribe) item.Subscribe = ""; else item.Subscribe = moment(item.SubscribeTime).format('YY.MM.DD HH:mm'); if (item.IsMember == 1) { item.ButtonOn = true; item.CssBg = "background-color: #e2ffd3;"; } else { item.ButtonOn = false; item.CssBg = "background-color: #fff;"; } if (!item.PayTime) item.PayTime = ""; else item.PayTime = moment(item.PayTime).format('YY.MM.DD HH:mm'); if (item.NickName != item.WSUNickName) item.NickName += '[' + item.WSUNickName + ']'; if (item.NickNameRemark) item.NickName += '「' + item.NickNameRemark + '」'; item.GroupID = stringUtils.getGroupName(item.GroupID); if (item.Introducer) { if (item.IntroducerClassID == 1) item.IntroducerClassID = "1~5"; item.IntroducerGroupID = stringUtils.getGroupName(item.IntroducerGroupID); item.Introducer = item.Introducer + "(" + item.IntroducerClassID + "班 " + item.IntroducerGroupID + ")"; } result.push(item); break; } } } param.type = "NoneMiaoguoData"; list = await miaoguo.GetApprovalUserList(param); for (const item of list) { const obj = {}; obj.UserID = ""; obj.WSUUserID = item.UserID; obj.NickName = "[" + item.NickName + "]"; obj.WSUAvatarUrl = item.AvatarUrl; obj.CreateTime = ""; obj.LastLoginTime = ""; if (!item.Subscribe) obj.Subscribe = ""; else obj.Subscribe = moment(item.SubscribeTime).format('YY.MM.DD HH:mm'); if (item.IsProbation == 1) { if (item.ProbationPayTime) obj.ProbationPayTime = moment(item.ProbationPayTime).format('YY.MM.DD HH:mm'); else obj.ProbationPayTime = ""; } else { obj.ProbationPayTime = ""; } obj.ClassID = item.TempClassID; obj.IsMember = item.TempIsMember; obj.PayTime = ""; if (obj.IsMember == 1) { obj.ButtonOn = true; obj.CssBg = "background-color: #e2ffd3;"; } else { obj.ButtonOn = false; obj.CssBg = "background-color: #fff;"; } if (item.IntroducerUserID) obj.Introducer = item.IntroducerNickName + "(" + item.IntroducerUserID + ")"; delete obj.IntroducerUserID; delete obj.IntroducerNickName; result.unshift(obj); } globalCache.set(url, result, config.BufferMemoryTimeLower); } const arr = []; for (const item of result) { if (item.ClassID == param.ClassID || param.ClassID == "All") { if (item.ClassID == 0 && item.IsMember == 1 && item.Subscribe == 0) { // Keep old behavior: skip this row. } else { arr.push(item); } } } ctx.body = { errcode: 10000, result: arr }; } export async function GetMiaoguoTestIndex(ctx) { const param = { UserID: ctx.query.UserID || 0, }; const resultAll = {}; const userInfo = await miaoguo.GetUsersInfoByUserID(param); if (userInfo && userInfo.length) { const param2 = { UserID: param.UserID, Grade: userInfo[0].Grade, UpperAndLowerSemesters: userInfo[0].UpperAndLowerSemesters, }; const list = await miaoguo.GetMiaoguoTestIndex(param2); const result = { List1: [], List2: [], List3: [], List4: [], }; for (const item of list) { if (item.Sort2 == 1) { item.Name1 = item.Name1.replace("语文", ""); item.Name1 = item.Name1.replace("拼音基础发音", "拼音基础"); item.Name1 = item.Name1.replace("拼音发音组合", "拼音组合"); delete item.BookImageName; } item.KnowledgeImageName = "pic_" + item.KnowledgeImageName; if (item.Sort2 == 1) result.List1.push(item); else if (item.Sort2 == 2) result.List2.push(item); else if (item.Sort2 == 3) result.List3.push(item); else if (item.Sort2 == 4) result.List4.push(item); } if (userInfo[0].Grade >= 7) { const temp = result.List2[1]; result.List2[1] = result.List2[0]; result.List2[0] = temp; } const arrTemp = []; let arrTemp2 = []; for (let k = 0; k < result.List4.length; k++) { if (k > 0 && k % 3 == 0) { arrTemp.push(arrTemp2); arrTemp2 = []; } arrTemp2.push(result.List4[k]); } if (arrTemp2.length > 0) arrTemp.push(arrTemp2); result.List4 = arrTemp; result.Grade = stringUtils.GetGrade2(param2.Grade); result.UpperAndLowerSemesters = userInfo[0].UpperAndLowerSemesters; resultAll.List1 = result; const reportParam = { UserID: param.UserID, BookID: 0, }; resultAll.List2 = await miaoguo.GetMiaoguoTestExamineReport2(reportParam); for (const item of resultAll.List2) { item.TestTime = moment(item.TestTime).format("YYYY年MM月DD日 HH:mm"); if (item.JSONData) { const arr = JSON.parse(item.JSONData); item.RightNum = arr[2].length; item.WrongNum = arr[0].length + arr[1].length; delete item.JSONData; } else { item.TestTime += " 已封存"; } } } ctx.body = { errcode: 10000, result: resultAll }; } export async function GetMiaoguoTestLibrary(ctx) { const url = "GetMiaoguoTestLibrary"; let result = globalCache.get(url); if (result === 0) { result = await miaoguo.GetMiaoguoBook(); for (const item of result) { let str = "cn"; if (item.Category == "English") str = "en"; item.BookImageName = "picZs_" + str + "_" + item.BookImageName + "_cov"; } globalCache.set(url, result, config.BufferMemoryTimeHigher); } ctx.body = { errcode: 10000, result }; } export async function GetMiaoguoTestPackage(ctx) { const param = { UserID: ctx.query.UserID || 0, Category: ctx.query.Category || "", LibraryName1: ctx.query.LibraryName1 || "", LibraryName2: ctx.query.LibraryName2 || "", }; const url = "GetMiaoguoTestPackage?Category=" + param.Category + "&LibraryName1=" + param.LibraryName1 + "&LibraryName2=" + param.LibraryName2; let result = globalCache.get(url); if (result === 0) { result = await miaoguo.GetMiaoguoPackage(param); for (const item of result) { item.TestFunction = JSON.parse(item.TestFunction); let strCategory = "en"; if (item.Category == "语文") strCategory = "cn"; item.KnowledgeImageName = "picZs_" + strCategory + "_" + item.BookImageName + "_" + item.KnowledgeImageName; } globalCache.set(url, result, config.BufferMemoryTimeHigh); } if (param.UserID) { const list = await miaoguo.GetMiaoguoTestExamineReport(param); for (const item of result) { for (const report of list) { if (item.BookName == report.BookName) { for (const fn of item.TestFunction) { if (fn.N == "read") { if (report.ReadNum > item.UnitNum) report.ReadNum = item.UnitNum; fn.C = Math.round(100 * report.ReadNum / item.UnitNum); } else if (fn.N == "write") { if (report.WriteNum > item.UnitNum) report.WriteNum = item.UnitNum; fn.C = Math.round(100 * report.WriteNum / item.UnitNum); } } } } } } ctx.body = { errcode: 10000, result }; } export function buildCambridgeCategoryUnits(wordList, testFunction) { const units = []; for (let start = 0; start < wordList.length; start += 10) { const words = wordList.slice(start, start + 10).map(item => item.Word); const id = units.length + 1; units.push({ ID: id, UnitName: "Lesson " + id, ListStr: words.join("、"), Length: words.length, TestFunction: testFunction, }); } return units; } export async function GetMiaoguoTestUnit(ctx) { const param = { UserID: ctx.query.UserID || 0, BookID: ctx.query.BookID || "0", }; const url = "GetMiaoguoTestUnit?BookID=" + param.BookID; let result = globalCache.get(url); if (result === 0) { const info = await miaoguo.GetMiaoguoPackage(param); info[0].TestFunction = JSON.parse(info[0].TestFunction); let strCategory = "en"; if (info[0].Category == "语文") strCategory = "cn"; info[0].KnowledgeImageName = "picZs_" + strCategory + "_" + info[0].BookImageName + "_" + info[0].KnowledgeImageName; if (info[0].Category == "语文") { if (info[0].BookIDOld == 43) { result = constantClass.arrPinyinList; for (const item of result) { item.UnitName = item.LessonName; if (item.List && item.List.length > 0) { item.ListStr = item.List.join("、"); item.Length = item.List.length; } else { item.ListStr = ""; } item.TestFunction = info[0].TestFunction; delete item.List; } } else if (info[0].BookIDOld == 44) { result = constantClass.getPinyinSoundList; for (const item of result) { item.UnitName = item.Name; item.ListStr = item.ListStr; item.TestFunction = info[0].TestFunction; } } else if (info[0].BookIDOld == 86) { result = await hanzi.GetAncientChineseProseSentence(); for (const item of result) { item.ID = item.UnitID; item.UnitName = item.Unit; item.ListStr = ""; item.Length = item.Count; item.TestFunction = info[0].TestFunction; delete item.UnitID; delete item.Count; delete item.Unit; } } else if (info[0].BookIDOld == 73) { result = await hanzi.GetAncientChineseProse({ IsList: true }); for (const item of result) { item.ID = item.UnitID; item.UnitName = item.Word; item.ListStr = ""; item.Length = item.Count; item.TestFunction = info[0].TestFunction; delete item.UnitID; delete item.Count; delete item.Word; } } else if (info[0].BookName == "古诗文" || info[0].BookName == "日积月累") { result = await hanzi.GetAncientPoetryList({ BookID: info[0].BookIDOld }); const item = { ID: info[0].BookIDOld, UnitName: info[0].BookName, List: [], }; for (const poetry of result) { item.List.push(poetry.Title); item.TestFunction = info[0].TestFunction; } item.ListStr = item.List.join(","); item.Length = item.List.length; result = [item]; } else { result = await hanzi.GetHanziUnits({ BookID: info[0].BookIDOld }); for (const item of result) { if (item.Example && item.Example.indexOf(",") > 0) item.List = item.Example.split(","); else item.List = item.Example.split(""); item.ListStr = item.List.join("、"); item.UnitName = item.Name; item.Length = item.List.length; delete item.List; item.TestFunction = info[0].TestFunction; } } } else { let temp = null; let item = { List: [] }; const arr = []; if (info[0].BookIDOld == 100) { item.ID = 1001; item.UnitName = "英文字母"; item.ListStr = "A、B、C、D、E、F、G、H、I、J、K、L、M、N、O、P、Q、R、S、T、U、V、W、X、Y、Z"; item.List = item.ListStr.split("、"); item.Length = 26; item.TestFunction = info[0].TestFunction; arr.push(item); } else if (info[0].BookIDOld == 101) { result = [{ "ID": 1, "Name": "单元音", "Words": ["/i:/", "/ɪ/", "/æ/", "/e/", "/ɜ:/", "/ə/", "/a:/", "/ʌ/", "/ɔ:/", "/ɔ/", "/u:/", "/u/"] }, { "ID": 2, "Name": "双元音", "Words": ["/ei/", "/aɪ/", "/au/", "/əʊ/", "/ɔɪ/", "/ɪə/", "/ɛə/", "/uə/"] }, { "ID": 3, "Name": "清辅音", "Words": ["/p/", "/t/", "/k/", "/f/", "/θ/", "/s/", "/ts/", "/tr/", "/ʃ/", "/tʃ/", "/h/"] }, { "ID": 4, "Name": "浊辅音", "Words": ["/b/", "/d/", "/g/", "/r/", "/v/", "/ð/", "/z/", "/dz/", "/dr/", "/ʒ/", "/dʒ/", "/m/", "/n/", "/l/", "/ŋ/", "/j/", "/w/"] }]; for (const unit of result) { unit.UnitName = unit.Name; unit.ListStr = unit.Words.join("、"); unit.Length = unit.Words.length; unit.TestFunction = info[0].TestFunction; delete unit.Words; } arr.push(...result); } else if (info[0].BookIDOld >= 111 && info[0].BookIDOld <= 119) { const engList = await miaoguo.GetWordFrequencyList({ Limit: 3000 }); const arrTemp = []; for (let i = info[0].StartID; i < info[0].StartID + info[0].WordNum; i++) { if (i / 10 == Math.floor(i / 10)) { arrTemp.push({ ID: Math.floor(i / 10) + 1, UnitName: "单元" + (Math.floor(i / 10) + 1), Words: [], }); } arrTemp[arrTemp.length - 1].Words.push(engList[i].Word); } for (const unit of arrTemp) { unit.ListStr = unit.Words.join("、"); unit.Length = unit.Words.length; unit.TestFunction = info[0].TestFunction; delete unit.Words; } arr.push(...arrTemp); } else if (["剑桥KET", "剑桥PET", "剑桥FCE"].includes(info[0].LibraryName1)) { let sourceBookID = 133; if (info[0].LibraryName1 == "剑桥PET") sourceBookID = 134; else if (info[0].LibraryName1 == "剑桥FCE") sourceBookID = 136; result = await hanzi.GetEnglishUnitData({ BookID: sourceBookID, LessonID: info[0].BookIDOld, }); arr.push(...buildCambridgeCategoryUnits(result, info[0].TestFunction)); } else { const param2 = { BookID: info[0].BookIDOld }; result = await hanzi.GetEnglishUnitData(param2); let unitNameStart = "Lesson "; if (param2.BookID > 190 && param2.BookID < 207) unitNameStart = "Unit "; if (param2.BookID >= 207 && param2.BookID <= 211) unitNameStart = ""; for (let i = 0; i < result.length; i++) { if (temp && temp.LessonID != result[i].LessonID) { item.ID = temp.LessonID; item.UnitName = unitNameStart + temp.LessonID; if (param2.BookID >= 207 && param2.BookID <= 211) item.UnitName = temp.LessonName; item.ListStr = item.List.join("、"); item.Length = item.List.length; item.TestFunction = info[0].TestFunction; delete item.List; arr.push(item); item = { List: [] }; } item.List.push(result[i].Word); temp = result[i]; } if (result.length > 0) { item.ID = result[result.length - 1].LessonID; item.UnitName = unitNameStart + result[result.length - 1].LessonID; if (param2.BookID >= 207 && param2.BookID <= 211) item.UnitName = result[result.length - 1].LessonName; item.ListStr = item.List.join("、"); item.Length = item.List.length; item.TestFunction = info[0].TestFunction; delete item.List; arr.push(item); } } result = arr; } result = { BookInfo: info[0], List1: result, }; globalCache.set(url, result, config.BufferMemoryTimeHigh); } result.List2 = []; if (param.UserID) { param.IsNum = 1; const list = await miaoguo.GetMiaoguoTestExamineReport2(param); if (list && list.length > 0) { for (const unit of result.List1) { for (const report of list) { if (unit.UnitName == report.UnitName) { if (report.IsTestRead == 1) unit.IsTestRead = 1; if (report.IsBuildRead == 1) unit.IsBuildRead = 1; if (report.IsTestWrite == 1) unit.IsTestWrite = 1; if (report.IsBuildWrite == 1) unit.IsBuildWrite = 1; } } } } param.IsNum = 0; result.List2 = await miaoguo.GetMiaoguoTestExamineReport2(param); for (const item of result.List2) { item.TestTime = moment(item.TestTime).format("YYYY年MM月DD日 HH:mm"); if (item.JSONData) { const arr = JSON.parse(item.JSONData); item.RightNum = arr[2].length; item.WrongNum = arr[0].length + arr[1].length; delete item.JSONData; } else { item.TestTime += " 已封存"; } } } ctx.body = { errcode: 10000, result }; } export async function InsertTestReport2(ctx) { const param = ctx.request.body || {}; param.UserID = ctx.query.UserID || 0; param.TestTime = moment().format("YYYY-MM-DD HH:mm:ss"); const list = await miaoguo.GetMiaoguoTestReport(param); if (list && list.length > 0) { param.ID = list[0].ID; await miaoguo.UpdateMiaoguoTestReport(param); } else { await miaoguo.AddMiaoguoTestReport(param); } ctx.body = { errcode: 10000 }; } export async function GetMiaoguoTestReportByID(ctx) { const param = { ID: ctx.query.ID || 0, UserID: ctx.query.UserID || 0, }; let result = null; const info = await miaoguo.GetMiaoguoTestExamineReport2(param); if (info && info.length > 0) { result = info[0]; if (result.IsTestRead) result.TestType = "read"; else if (result.IsTestWrite) result.TestType = "write"; result.List = JSON.parse(result.JSONData); delete result.JSONData; result.TestTime = moment(result.TestTime).format("YYYY-MM-DD HH:mm:ss"); } ctx.body = { errcode: 10000, result }; } export async function InsertTestReport(ctx) { const param = ctx.request.body || {}; param.UserID = ctx.query.UserID || 0; if (param.ID) { const param2 = { ID: param.ID, UserID: param.UserID, TestRightStr: param.TestRightStr, TestWrongStr: param.TestWrongStr, TestSkipStr: param.TestSkipStr, UpdateTime: moment().format("YYYY-MM-DD HH:mm:ss"), }; if (param.TestTotal) param2.TestTotal = param.TestTotal; if (param.IsFinished == 1) param2.IsFinished = 1; if (param.IsFinished === 0) param2.IsFinished = 0; if (param.TestExistStr) param2.TestExistStr = param.TestExistStr; if (param.BookID) param2.BookID = param.BookID; await miaoguo.UpdateTestReport(param2); } else { param.UpdateTime = moment().format("YYYY-MM-DD HH:mm:ss"); const result = await miaoguo.InsertTestReport(param); param.ID = result.insertId; } ctx.body = { errcode: 10000, result: param.ID }; } export async function GetTestGradeArr(ctx) { const bufferMemoryUrl = "GetTestGradeArr"; let result = globalCache.get(bufferMemoryUrl); if (result === 0) { const arr = []; let param = { Type: "TestMenuMain" }; let listName = await hanzi.GetHanziBooks(param); param.Type = "TestMenu"; let list = await hanzi.GetHanziBooks(param); for (const nameItem of listName) { const obj = { Name: nameItem.Category, Name2: nameItem.Category, Type: "CHN", Select: "", List: [], }; for (const item of list) { if (nameItem.Category == item.Category) { obj.List.push({ ID: item.ID, Type: item.HanziType, Name: item.Name1, Name2: item.Name2, Finish: 0, Total: item.Total, Image: item.Image, }); } } arr.push(obj); } param = { FieldType: "Name1" }; listName = await miaoguo.GetWordBooks(param); list = await miaoguo.GetWordBooks(null); for (const nameItem of listName) { const obj = { Name: nameItem.Category, Name2: nameItem.Category, Type: "ENG", Select: "", List: [], }; for (const item of list) { if (nameItem.Category == item.Category) { obj.List.push({ ID: item.ID, Type: "Word", Name: item.Name, Name2: item.Name2, Finish: 0, Total: item.Total, Image: item.Image, }); } } arr.push(obj); } result = arr; if (result) globalCache.set(bufferMemoryUrl, result, config.BufferMemoryTimeHigher); } ctx.body = { errcode: 10000, result }; } export async function GetTestReportList(ctx) { const param = { UserID: ctx.query.UserID || 0, }; const list = await miaoguo.GetTestReportList(param); for (const item of list) { item.CreateTime = moment(item.CreateTime).format('MM月DD日'); item.UpdateTime = moment(item.UpdateTime).format('MM月DD日'); item.Name = item.Name.split("#"); if (item.wName1 && item.wFlag >= 0) { item.Name[0] = item.wName1 + " " + item.wName2 + " " + item.wName3; delete item.wName1; delete item.wName2; delete item.wName3; delete item.wFlag; } if (item.hName1 && item.hFlag >= 0) { item.Name[0] = item.hName1 + " " + item.hName2 + " " + item.hName3; delete item.hName1; delete item.hName2; delete item.hName3; delete item.hFlag; } } ctx.body = { errcode: 10000, result: list }; } export async function GetTestAncientPoetryList(ctx) { const param = { UserID: ctx.query.UserID || 0, BookID: ctx.query.BookID || 0, }; const list = await hanzi.GetAncientPoetryList(param); const result = []; for (const item of list) { const peomContent = JSON.parse(item.PeomContent); let str2 = item.Dynasty + " " + item.Author + "\n\n"; if (param.BookID >= 74 && item.Category == 1) str2 = "\n\n"; for (const content of peomContent) { if (stringUtils.IsArray(content)) { for (const line of content) str2 += line + "\n"; } else { str2 += content + "\n"; } } let str3 = ""; if (item.Translation) { const translation = JSON.parse(item.Translation); for (const content of translation) { if (stringUtils.IsArray(content)) { for (const line of content) str3 += line; } else { str3 += content; } } } const obj = { ID: item.ID, Word: item.Title, Content: [], }; obj.Content.push({ ContentType: 0, Content: item.Tag }); let str4 = ""; if (param.BookID >= 74 && item.Category == 1) str4 = item.Title; else str4 = "《" + item.Title + "》"; if (item.ImgUrl) str4 += "\n\n[图]" + item.ImgUrl + "[/图]"; obj.Content.push({ ContentType: 1, Content: str4 }); obj.Content.push({ ContentType: 2, Content: str2 }); obj.Content.push({ ContentType: 3, Content: str3 }); result.push(obj); } ctx.body = { errcode: 10000, result }; } export async function GetTestEnglishWords(ctx) { const param = { UserID: ctx.query.UserID || 0, Words: ctx.query.Words || "", BookID: ctx.query.BookID || 0, LessonID: ctx.query.LessonID || 0, TestType: ctx.query.TestType || "read", }; let list = []; if (param.BookID && ((Number(param.BookID) > 120 && Number(param.BookID) < 169) || (Number(param.BookID) >= 184 && Number(param.BookID) <= 186) || (Number(param.BookID) > 190 && Number(param.BookID) <= 211))) { list = await miaoguo.GetTestEnglishNewConceptWords(param); for (const item of list) { let question = ""; let answer = ""; let tags = ""; let soundmark = ""; const sound = item.Word.replace("ˈ", "%27"); let readString = "https://sp0.baidu.com/-rM1hT4a2gU2pMbgoY3K/gettts?lan=uk&text=" + sound + "&spd=3&source=alading"; if (item.Word == "our" || item.Word == "stomachache") readString = readString.replace("lan=uk", "lan=en"); else if (item.Word == "Mr=mister") readString = readString.replace("text=Mr=mister", "text=mister"); else if (item.Word == "(a) round the clock") readString = readString.replace("text=(a) round the clock", "text=a round the clock"); else if (item.Word == "in favo(u)r of") readString = readString.replace("text=in favo(u)r of", "text=in favor of"); if (param.TestType == "read") { if ((param.BookID > 140 && param.BookID < 149) || (param.BookID > 160 && param.BookID < 165)) { question = "[特]" + item.Word + "[/特]"; question += "\n\n\n\n" + item.ExampleSentence; if (!item.Soundmark || item.Soundmark == "") item.Soundmark = "发音"; soundmark = "[读 src='" + readString + "']" + item.Soundmark + "[/读]\n"; answer = soundmark + "\n\n"; answer += item.Translate; tags = "发音,释义"; item.FontSize = 48; } else { question = item.Word; if (!item.Soundmark || item.Soundmark == "") item.Soundmark = "发音"; soundmark = "[读 src='" + readString + "']" + item.Soundmark + "[/读]\n"; answer = soundmark + "\n\n"; if (param.BookID > 190 && param.BookID <= 211) answer += item.Translate; else answer += "[光]" + item.Translate + "[/光]"; tags = "发音,释义"; item.FontSize = 108; if (param.BookID >= 199 && param.BookID < 207) item.FontSize = 54; } } else if (param.TestType == "write") { if ((param.BookID > 140 && param.BookID < 149) || (param.BookID > 160 && param.BookID < 165)) { soundmark = "[读 src='" + readString + "']发音[/读]\n"; question = soundmark + "\n\n"; question += item.Translate; answer = "[特]" + item.Word + "[/特]"; answer += "\n\n\n\n" + item.ExampleSentence; tags = "默写单词"; item.FontSize = 48; } else { soundmark = "[读 src='" + readString + "']发音[/读]\n"; question = soundmark + "\n\n"; question += item.Translate; if (param.BookID == 135 || param.BookID == 184 || param.BookID == 185 || param.BookID == 186) tags = "默写词组"; if (param.BookID >= 199 && param.BookID < 207) tags = "默写句子"; else tags = "默写单词"; answer = "[特]" + item.Word + "[/特]"; if (param.BookID >= 199 && param.BookID < 207) answer = item.Word; item.FontSize = 48; } } item.ReadString = readString; item.Content = [ { ContentType: 0, Content: tags }, { ContentType: 1, Content: question }, { ContentType: 2, Content: answer }, { ContentType: 3, Content: "" }, ]; delete item.Soundmark; delete item.Translate; } } else if (param.BookID && Number(param.BookID) > 100 && Number(param.BookID) < 110) { list = constantClass.GetPhonetic(); const arr = []; for (const item of list) { if (item.LessonID == param.LessonID) arr.push(item); } list = arr; } else if ((Number(param.BookID) >= 169 && Number(param.BookID) <= 183) || (Number(param.BookID) >= 212 && Number(param.BookID) <= 217)) { if (Number(param.BookID) >= 169 && Number(param.BookID) <= 172) param.Type = "GaoKao"; else if (Number(param.BookID) >= 173 && Number(param.BookID) <= 177) param.Type = "GET4"; else if (Number(param.BookID) >= 178 && Number(param.BookID) <= 183) param.Type = "GET6"; else if (Number(param.BookID) >= 212 && Number(param.BookID) <= 217) param.Type = "GET6"; list = await miaoguo.GetTestEnglishWords(param); const listTextbook = await miaoguo.GetTestEnglishWords({ ...param, Type: "textbook", Words: list.map(item => item.Word), }); for (const item of list) { if (!item.JSONString) { const soundFallback = item.Word.replace("ˈ", "%27"); const readStringFallback = "https://sp0.baidu.com/-rM1hT4a2gU2pMbgoY3K/gettts?lan=uk&text=" + soundFallback + "&spd=3&source=alading"; if (param.TestType == "write") { item.Content = [ { ContentType: 0, Content: "默写单词" }, { ContentType: 1, Content: "[读 src='" + readStringFallback + "']发音[/读]\n\n[光]" + item.Translate + "[/光]" }, { ContentType: 2, Content: "[特]" + item.Word + "[/特]" }, { ContentType: 3, Content: "" }, ]; item.FontSize = 48; } else { item.Content = [ { ContentType: 0, Content: "发音,释义" }, { ContentType: 1, Content: item.Word }, { ContentType: 2, Content: "[读 src='" + readStringFallback + "']" + (item.Soundmark || "发音") + "[/读]\n\n[光]" + item.Translate + "[/光]" }, { ContentType: 3, Content: "" }, ]; item.FontSize = 108; } item.ReadString = readStringFallback; continue; } const json = JSON.parse(item.JSONString); item.Data = json.ENG; let question = ""; let answer = ""; let tags = ""; let soundmark = ""; let paraphrase = ""; if (item.Data.Soundmark && item.Data.Soundmark.EngSound && item.Data.Soundmark.Eng) soundmark = "[读 src='" + item.Data.Soundmark.EngSound + "']" + item.Data.Soundmark.Eng + "[/读]\n \n"; if (item.Data.Paraphrase && item.Data.Paraphrase.length > 0) { for (const p of item.Data.Paraphrase) { paraphrase += p.PartOfSpeech; paraphrase += " " + p.ParaphraseList.join(" ") + "\n\n"; } } let paraphraseBasic = ""; let paraphraseBasicEng = ""; if (listTextbook && listTextbook.length > 0) { for (const textBook of listTextbook) { if (textBook.Word == item.Data.Word) paraphraseBasicEng = textBook.Translate; } } paraphraseBasic = item.Translate; if (param.Words.substr(0, 8) == "\"A\",\"B\",") { question = item.Data.Word + " " + item.Data.Word.toString().toLowerCase(); answer = soundmark.substr(0, soundmark.length - 3); answer += "[图]" + json.CHN.BiShunArr2[0] + "[/图]"; answer += "\n[图]" + json.CHN.BiShunArr2[1] + "[/图]"; tags = "发音,书写"; item.FontSize = 108; } else if (param.TestType == "read") { question = item.Data.Word; answer = soundmark; if (paraphraseBasic) { answer += item.Data.Word + " 的基本中文释义\n\n"; answer += "[光]" + paraphraseBasic + "[/光]\n\n"; } if (paraphraseBasicEng) { answer += item.Data.Word + " 的基本英文释义\n\n"; answer += "[光]" + paraphraseBasicEng + "[/光]\n\n"; } if (Number(param.BookID) >= 169 && Number(param.BookID) <= 172) { answer += item.Data.Word + " 的更多中文释义\n\n"; answer += paraphrase; } tags = "发音,释义"; item.FontSize = 108; } else if (param.TestType == "write") { if (item.Data.Soundmark && item.Data.Soundmark.Eng) soundmark = soundmark.replace(item.Data.Soundmark.Eng, "发音"); else soundmark = ""; question = soundmark; if (paraphraseBasic) question += "[光]" + paraphraseBasic + "[/光]\n\n"; if (paraphraseBasicEng) question += "[光]" + paraphraseBasicEng + "[/光]\n\n"; if (Number(param.BookID) >= 169 && Number(param.BookID) <= 172) { paraphrase = stringUtils.ReplaceAllString(paraphrase, item.Data.Word, "[光]~[/光]"); question += paraphrase; } answer = "[特]" + item.Data.Word + "[/特]"; tags = "默写单词"; item.FontSize = 48; } item.Word = item.Data.Word; if (item.Data.Soundmark && item.Data.Soundmark.EngSound) item.ReadString = item.Data.Soundmark.EngSound; else item.ReadString = ""; item.Content = [ { ContentType: 0, Content: tags }, { ContentType: 1, Content: question }, { ContentType: 2, Content: answer }, { ContentType: 3, Content: "" }, ]; delete item.JSONString; delete item.Data; } } else { param.Words = param.Words.split(","); param.Words = JSON.stringify(param.Words); param.Words = param.Words.substr(1, param.Words.length - 2); list = await miaoguo.GetTestEnglishWords(param); param.Type = "textbook"; const listTextbook = await miaoguo.GetTestEnglishWords(param); for (const item of list) { const json = JSON.parse(item.JSONString); item.Data = json.ENG; let question = ""; let answer = ""; let tags = ""; let soundmark = ""; let paraphrase = ""; if (item.Data.Soundmark && item.Data.Soundmark.EngSound && item.Data.Soundmark.Eng) soundmark = "[读 src='" + item.Data.Soundmark.EngSound + "']" + item.Data.Soundmark.Eng + "[/读]\n \n"; if (item.Data.Paraphrase && item.Data.Paraphrase.length > 0) { for (const p of item.Data.Paraphrase) { paraphrase += p.PartOfSpeech; paraphrase += " " + p.ParaphraseList.join(" ") + "\n\n"; } } let paraphraseBasic = ""; let paraphraseBasicEng = ""; let exampleSentence = ""; if (listTextbook && listTextbook.length > 0) { for (const textBook of listTextbook) { if (textBook.Word == item.Data.Word && textBook.BookID < 160) paraphraseBasic = textBook.Translate; if (textBook.Word == item.Data.Word && textBook.BookID > 160) { paraphraseBasicEng = textBook.Translate; exampleSentence = textBook.ExampleSentence; } } } if (param.Words.substr(0, 8) == "\"A\",\"B\",") { question = item.Data.Word + " " + item.Data.Word.toString().toLowerCase(); answer = soundmark.substr(0, soundmark.length - 3); answer += "[图]" + json.CHN.BiShunArr2[0] + "[/图]"; answer += "\n[图]" + json.CHN.BiShunArr2[1] + "[/图]"; tags = "发音,书写"; item.FontSize = 108; } else if (param.TestType == "read") { question = item.Data.Word; answer = soundmark; if (paraphraseBasic) { answer += item.Data.Word + " 的基本中文释义\n\n"; answer += "[光]" + paraphraseBasic + "[/光]\n\n"; } if (paraphraseBasicEng) { answer += item.Data.Word + " 的基本英文释义\n\n"; answer += "[光]" + paraphraseBasicEng + "[/光]\n\n"; } answer += item.Data.Word + " 的更多中文释义\n\n"; answer += paraphrase; tags = "发音,释义"; item.FontSize = 108; } else if (param.TestType == "write") { if (item.Data.Soundmark && item.Data.Soundmark.Eng) soundmark = soundmark.replace(item.Data.Soundmark.Eng, "发音"); else soundmark = ""; question = soundmark; if (paraphraseBasic) question += "[光]" + paraphraseBasic + "[/光]\n\n"; if (paraphraseBasicEng) question += "[光]" + paraphraseBasicEng + "[/光]\n\n"; paraphrase = stringUtils.ReplaceAllString(paraphrase, item.Data.Word, "[光]~[/光]"); question += paraphrase; answer = "[特]" + item.Data.Word + "[/特]"; tags = "默写单词"; item.FontSize = 48; } item.Word = item.Data.Word; if (item.Data.Soundmark && item.Data.Soundmark.EngSound) item.ReadString = item.Data.Soundmark.EngSound; else item.ReadString = ""; item.Content = [ { ContentType: 0, Content: tags }, { ContentType: 1, Content: question }, { ContentType: 2, Content: answer }, { ContentType: 3, Content: "" }, ]; delete item.JSONString; delete item.Data; if (param.TestType == "read" && param.Words.substr(0, 8) != "\"A\",\"B\",") { if (exampleSentence || (json.ENG.ExamplesSentences && json.ENG.ExamplesSentences.length > 0)) { const temp = json.ENG.ExamplesSentences; item.Content[2].Content = item.Content[2].Content + "\n例句:"; let index = 0; if (exampleSentence) item.Content[2].Content = item.Content[2].Content + "\n\n(" + (++index) + ") " + exampleSentence + "\n\n"; const len = temp.length > 5 ? 5 : temp.length; for (let j = 0; j < len; j++) { const num = "(" + (++index) + ")"; item.Content[2].Content = item.Content[2].Content + "\n\n" + num + " " + temp[j][0] + "\n" + temp[j][1]; } } } } } ctx.body = { errcode: 10000, result: list }; } export async function GetMiaoguoTestExamine(ctx) { const param = { BookID: ctx.query.BookID || 0, UnitName: ctx.query.UnitName || "", UnitID: ctx.query.UnitID || 0, TestType: ctx.query.TestType || "read", IsBuild: ctx.query.IsBuild || false, }; const cacheKey = "GetMiaoguoTestExamine?BookID=" + param.BookID + "&UnitName=" + param.UnitName + "&UnitID=" + param.UnitID + "&TestType=" + param.TestType + "&IsBuild=" + param.IsBuild; let result = globalCache.get(cacheKey); if (result === 0) { const info = await miaoguo.GetMiaoguoPackage(param); param.Category = info[0].Category; param.BookID = info[0].BookIDOld; if (param.Category == "语文") { if (param.BookID == "43" || param.BookID == "44") { const list = await fetchLocalResult('/api/GetPinyinUnitWords?TestType=' + param.TestType + '&BookID=' + param.BookID + '&UnitID=' + param.UnitID + '&Word=' + param.UnitName); const arr = []; for (const item of list) { const obj = {}; if (param.IsBuild) obj.Content = item.Content; obj.ContentNew = changeStringToView(item.Content); obj.TagWidth = 230; obj.FontSize = 108; obj.Word = item.Word; obj.ReadString = item.ReadString; arr.push(obj); } result = arr; } else if (param.UnitName == "古诗文" || param.UnitName == "日积月累") { const list = await fetchLocalResult('/api/GetTestAncientPoetryList?BookID=' + param.BookID); const arr = []; for (const item of list) { const obj = {}; if (param.IsBuild) obj.Content = item.Content; obj.ContentNew = changeStringToView(item.Content); obj.TagWidth = 306; if (param.BookID >= "74") obj.TagWidth = 520; if (item.Word.length > 8) obj.FontSize = 48; else obj.FontSize = 64; obj.Word = item.Word; arr.push(obj); } result = arr; } else { const unitInfo = await hanzi.GetHanziUnitInfo(param); if (unitInfo && unitInfo.length > 0 && unitInfo[0].UnitType == 2) { const words = await hanzi.GetHanziUnitByID(param); if (words[0] && words[0].Example) { param.Words = words[0].Example.split(",").join("','"); result = await hanzi.GetMiaoguoLiteracyWords(param); let temp = ""; const arrTemp = []; for (const item of result) { if (temp != item.Word && item.JSONString) { const json = JSON.parse(item.JSONString); const obj = { ID: arrTemp.length + 1 }; let qustion; let answer; let tags; if (param.TestType == "read") { qustion = item.Word; answer = "[读 src='" + item.Word + "']" + json.CHN.PinYin[0].pinyin + "[/读]\n\n"; answer += json.CHN.PinYin[0].explain; tags = "怎么念"; obj.FontSize = 108; } else { qustion = "[读 src='" + item.Word + "']" + json.CHN.PinYin[0].pinyin + "[/读]\n"; answer = item.Word + "\n\n"; if (json.CHN.KaitiArr) { for (const kaiti of json.CHN.KaitiArr) answer += "[图 w='650' h='650']" + kaiti + "[/图]\n"; } answer += json.CHN.PinYin[0].explain; tags = "怎么写"; } obj.Word = item.Word; obj.ReadString = item.Word; obj.Content = [ { ContentType: 0, Content: tags }, { ContentType: 1, Content: qustion }, { ContentType: 2, Content: answer }, { ContentType: 3, Content: "" }, ]; obj.ContentNew = changeStringToView(obj.Content); obj.TagWidth = 136; if (!param.IsBuild) delete obj.Content; arrTemp.push(obj); } temp = item.Word; } result = arrTemp; } } else if (unitInfo && unitInfo.length > 0 && unitInfo[0].UnitType == 4) { const words = await hanzi.GetHanziUnitByID(param); const arrAncientList = await hanzi.GetAncientChineseProse({ IsList: 0 }); result = []; if (words[0] && words[0].Name) { for (const ancient of arrAncientList) { if (ancient.Word == words[0].Name) { let qustion = ancient.ExampleSentence; qustion = stringUtils.ReplaceAllString(qustion, ancient.Word, "[光]" + ancient.Word + "[/光]"); const readString = "“" + ancient.Word + "”字在这里的意思是" + ancient.Definitions; let answer = "[读 src='" + readString + "']" + ancient.Definitions + "[/读]\n\n"; if (ancient.ExampleDefinitions.indexOf("译文") < 0) answer += "译文:"; answer += ancient.ExampleDefinitions; const obj = { Word: "【" + ancient.Word + "】" + ancient.Definitions, ReadString: readString, Content: [ { ContentType: 0, Content: "文言文字释义" }, { ContentType: 1, Content: qustion }, { ContentType: 2, Content: answer }, { ContentType: 3, Content: "" }, ], TagWidth: 270, }; obj.ContentNew = changeStringToView(obj.Content); if (!param.IsBuild) delete obj.Content; result.push(obj); } } } } else if (unitInfo && unitInfo.length > 0 && unitInfo[0].UnitType == 3) { const words = await hanzi.GetHanziUnitByID(param); const resultList = await hanzi.GetAncientChineseProseSentence({ Unit: words[0].Name }); result = []; if (words[0] && words[0].Name) { for (const item of resultList) { let qustion; let answer; let tags; if (param.TestType == "read") { qustion = item.Question; answer = "[光]" + item.Sentence + "[/光]\n\n"; answer += "含义\n• " + item.Paraphrase + "\n\n"; if (item.Explain) answer += "详解\n• " + item.Explain + "\n\n"; if (item.Idiom) answer += "句中包含的成语\n• " + item.Idiom + "\n\n"; if (item.SentenceFragment) answer += "原文片段\n• " + item.SentenceFragment + "\n\n"; if (item.Source) { answer += "出自\n• 《" + item.Source + "》 "; if (item.Author) answer += item.Author + " "; if (item.Dynasty) answer += "(" + item.Dynasty + ")"; answer += "\n\n"; } tags = "接下文"; } else { qustion = item.Paraphrase; answer = "[光]" + item.Sentence + "[/光]\n\n"; if (item.Explain) answer += "详解\n• " + item.Explain + "\n\n"; if (item.Idiom) answer += "句中包含的成语\n• " + item.Idiom + "\n\n"; if (item.SentenceFragment) answer += "原文片段\n• " + item.SentenceFragment + "\n\n"; if (item.Source) { answer += "出自\n• 《" + item.Source + "》 "; if (item.Author) answer += item.Author + " "; if (item.Dynasty) answer += "(" + item.Dynasty + ")"; answer += "\n\n"; } tags = "说古文"; } const obj = { Word: item.Sentence, Content: [ { ContentType: 0, Content: tags }, { ContentType: 1, Content: qustion }, { ContentType: 2, Content: answer }, { ContentType: 3, Content: "" }, ], TagWidth: 136, }; obj.ContentNew = changeStringToView(obj.Content); if (!param.IsBuild) delete obj.Content; result.push(obj); } } } else { if ((param.UnitID > 93 && param.UnitID <= 358) || (param.UnitID >= 561)) { const words = await hanzi.GetHanziUnitByID(param); param.Words = words[0].Example.split("").join("','"); result = await hanzi.GetHanziUnitWords(param); } else if (param.UnitID <= 93) { result = await hanzi.GetHanziUnitWords(param); } for (const item of result) { if (item.Pinyin) { item.Pinyin = item.Pinyin.split(",")[0]; item.CombineWords2 = stringUtils.ReplaceAllString(item.CombineWords, item.Name, item.Pinyin); if (item.CombineWords.length == 2 && item.CombineWords[0] == item.CombineWords[1]) { switch (item.CombineWords) { case "妈妈": item.CombineWords2 = "māma"; break; case "爸爸": item.CombineWords2 = "bàba"; break; case "叔叔": item.CombineWords2 = "shūshu"; break; case "弟弟": item.CombineWords2 = "dìdi"; break; case "爷爷": item.CombineWords2 = "yéye"; break; case "哥哥": item.CombineWords2 = "gēge"; break; case "玲玲": item.CombineWords2 = "língling"; break; } } if (item.CombineWords.length == 2) { switch (item.CombineWords) { case "倔强": item.CombineWords2 = "jué强"; item.Pinyin = "jué"; break; case "搂住": item.CombineWords2 = "lǒu住"; item.Pinyin = "lǒu"; break; } } } } result = getWordJSON(param, result); } } } else if (param.Category == "English") { let unitsid = ""; let words = ""; const bookid = param.BookID; if (bookid > 100 && bookid < 110 && param.UnitName) unitsid = Number(param.UnitName); else if (bookid >= 111 && bookid <= 119) { const engList = await miaoguo.GetWordFrequencyList({ Limit: 3000 }); const idStart = (Number(param.UnitName.replace("单元", "")) - 1) * 10; words = "&Words="; for (let j = idStart; j < idStart + 10; j++) words += engList[j].Word + ","; words = words.substr(0, words.length - 1); } else if ((bookid > 120 && bookid <= 124) || (bookid >= 169 && bookid <= 186)) unitsid = Number(param.UnitName.replace("Lesson ", "")); else if (bookid > 130 && bookid <= 168 && param.UnitName) unitsid = Number(param.UnitName); else if (bookid == 100) { param.TestType = "read"; words = "&Words=A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z"; } else if (info[0].LibraryName1.indexOf("剑桥") >= 0 && info[0].LibraryName2 == "分类词汇") { unitsid = param.UnitID; param.UnitID = param.BookID; param.BookID = 133; if (info[0].LibraryName1 == "剑桥PET") param.BookID = 134; else if (info[0].LibraryName1 == "剑桥FCE") param.BookID = 136; } else if (bookid > 190 && bookid <= 211) unitsid = param.UnitID; const list = await fetchLocalResult('/api/GetTestEnglishWords?TestType=' + param.TestType + '&BookID=' + param.BookID + '&LessonID=' + param.UnitID + words); const arr = []; for (let i = 0; i < list.length; i++) { const obj = {}; if (param.IsBuild) obj.Content = list[i].Content; if (list[i].ExampleSentence && list[i].ExampleSentence.substring(0, 1) == "{") { const arrTemp = ["\n\n例句:"]; const jsonSentence = JSON.parse(list[i].ExampleSentence); const arrSentence = jsonSentence.Sentences; const count = arrSentence.length > 2 ? 2 : arrSentence.length; for (let k = 0; k < count; k++) { let sentence1 = arrSentence[k].Sentence; const word2 = list[i].Word; sentence1 = stringUtils.ReplaceAllString(sentence1, word2, "[线]" + word2 + "[/线]"); if (sentence1.substring(0, word2.length).toLowerCase() == word2.toLowerCase()) { const word1 = sentence1.substring(0, word2.length); sentence1 = sentence1.replace(word1, "[线]" + word1 + "[/线]"); } arrTemp.push(sentence1); arrTemp.push(arrSentence[k].Translate); arrTemp.push("\n"); } list[i].Content[2].Content += arrTemp.join("\n"); } obj.ContentNew = changeStringToView(list[i].Content); obj.Word = list[i].Word; obj.ReadString = list[i].ReadString; if (param.TestType == "read") { obj.TagWidth = 212; obj.FontSize = 108; if ((param.BookID >= 140 && param.BookID <= 149) || (param.BookID >= 160 && param.BookID <= 164)) obj.FontSize = 48; else if ((param.BookID == 135) || (param.BookID >= 184 && param.BookID <= 186)) obj.FontSize = 72; if (obj.Word.length > 10) obj.FontSize = 84; if (param.BookID >= 199 && param.BookID < 207) obj.FontSize = 54; } else if (param.TestType == "write") { obj.TagWidth = 186; obj.FontSize = 42; } if (info[0].LibraryName1.indexOf("剑桥") >= 0 && info[0].LibraryName2 == "分类词汇") { if (i >= (unitsid - 1) * 10 && i < unitsid * 10) arr.push(obj); } else { arr.push(obj); } } result = arr; } globalCache.set(cacheKey, result, config.BufferMemoryTimeHigh); } ctx.body = { errcode: 10000, result }; } export async function AddMiaoguoCard(ctx) { const param = ctx.request.body || {}; let createSource = "app"; if (param.VisitType) { createSource = Decrypt(param.VisitType, config.urlSecrets.aes_key, config.urlSecrets.aes_iv); if (createSource == "web") { param.ID = Decrypt(param.ID, config.urlSecrets.aes_key, config.urlSecrets.aes_iv); delete param.VisitType; ctx.query.Type = param.Type; delete param.Type; } } param.UserID = ctx.query.UserID; const field1 = param.Field1 || ""; const field2 = param.Field2 || ""; const field3 = param.Field3 || ""; let tags = ""; if (param.Tags && param.Tags.length > 0) tags = stringUtils.IsArray(param.Tags) ? param.Tags.join(",") : param.Tags; delete param.Field1; delete param.Field2; delete param.Field3; delete param.Tags; let isExist = false; if (ctx.query.Type == "IsExist") { const param2 = { UserID: ctx.query.UserID, FolderID: param.FolderID, Content: stringUtils.ReplaceAllString(field1, "'", "''"), IsTest: 1, }; param2.Content = stringUtils.ReplaceAllString(param2.Content, "\n", ""); const miaoguoCardID = await miaoguo.GetCardIDByContent(param2); if (miaoguoCardID && miaoguoCardID.length > 0) { param2.MiaoguoCardID = miaoguoCardID[0].MiaoguoCardID; param2.Type = "TestReset"; await miaoguo.ResetCard(param2); globalCache.delete("GetMiaoguoCard###UserID=" + param.UserID); isExist = true; } } if (!isExist && (ctx.query.Type == "edit" || ctx.query.Type == "edit1" || ctx.query.Type == "edit2" || ctx.query.Type == "edit3")) { param.MiaoguoCardID = param.ID; delete param.ID; let obj = { MiaoguoCardID: param.MiaoguoCardID, ContentType: 1, Content: updateSoundError(field1, createSource), }; await miaoguo.UpdateCardContent(obj); obj.ContentType = 2; obj.Content = updateSoundError(field2, createSource); await miaoguo.UpdateCardContent(obj); obj.ContentType = 3; obj.Content = updateSoundError(field3, createSource); await miaoguo.UpdateCardContent(obj); if (tags || tags.length == 0) { obj.ContentType = 0; obj.Content = tags; await miaoguo.UpdateCardContent(obj); } if (param.IsTodayStudy || param.LimitTime) { obj = { MiaoguoCardID: param.MiaoguoCardID, IsTodayStudy: param.IsTodayStudy, IsCollect: param.IsCollect || 0, FolderID: param.FolderID || 0, FontSize: param.FontSize || 48, }; if (param.LimitTime) { if (param.LimitTime.indexOf("/") >= 0) param.LimitTime = stringUtils.ReplaceAllString(param.LimitTime.toString(), "/", "-"); obj.LimitTime = moment(param.LimitTime).format('YYYY-MM-DD HH:mm:ss'); } if (param.IsTodayStudy == 1) obj.LimitTime = moment().format('YYYY-MM-DD HH:mm:ss'); obj.UpdateTime = moment().format('YYYY-MM-DD HH:mm:ss'); const cardInfo = await miaoguo.GetCardInfo(param); await miaoguo.UpdateCard(obj); const param2 = { UserID: param.UserID, MiaoguoCardID: param.MiaoguoCardID, CardType: param.CardType || 0, LimitTime: obj.LimitTime, }; if (param2.LimitTime === "" || param2.LimitTime == "undefined") delete param2.LimitTime; else if (cardInfo && cardInfo.length > 0) { const limittime = moment(cardInfo[0].LimitTime).format('YYYY-MM-DD HH:mm:ss'); if (param2.LimitTime == limittime) delete param2.LimitTime; } if (cardInfo && cardInfo.length > 0 && param2.CardType != cardInfo[0].CardType) await miaoguo.UpdateCardType(param2); miaoguo.AddMiaoguoLog({ UserID: ctx.query.UserID, LogID: 298, Content: JSON.stringify({ Content1: obj, Content2: param2 }), }).catch(err => console.error('AddMiaoguoLog failed:', err)); } } else if (!isExist) { delete param.ID; param.CreateTime = moment().format('YYYY-MM-DD HH:mm:ss'); if (!param.LimitTime) param.LimitTime = param.CreateTime; param.UpdateTime = param.CreateTime; param.CreateSource = createSource; let canSave = true; if (field1 && (field1.indexOf("]undefined[/图]") > 0 || field1.indexOf("][/图]") > 0 || field2.indexOf("]undefined[/图]") > 0 || field2.indexOf("][/图]") > 0 || field3.indexOf("]undefined[/图]") > 0 || field3.indexOf("][/图]") > 0)) canSave = false; if (canSave) { const insertResult = await miaoguo.AddCard(param); const obj = { MiaoguoCardID: insertResult.insertId, ContentType: 0, Content: tags, }; await miaoguo.AddCardContent(obj); obj.ContentType = 1; obj.Content = updateSoundError(field1, createSource); await miaoguo.AddCardContent(obj); obj.ContentType = 2; obj.Content = updateSoundError(field2, createSource); await miaoguo.AddCardContent(obj); obj.ContentType = 3; obj.Content = updateSoundError(field3, createSource); await miaoguo.AddCardContent(obj); miaoguo.AddMiaoguoLog({ UserID: ctx.query.UserID, LogID: 297, Content: JSON.stringify(param), }).catch(err => console.error('AddMiaoguoLog failed:', err)); } } globalCache.delete("GetMiaoguoCard###UserID=" + param.UserID); ctx.body = { errcode: 10000 }; function updateSoundError(content, source) { if (source == "web" && content) { const arr = constantClass.GetSoundErrorArr(); let str = content; while (str.indexOf("[读 src=") >= 0) { str = str.substr(str.indexOf("[读 src=") + 8); const sound = str.substr(0, str.indexOf("'")); for (const item of arr) { if (sound.indexOf(item.Word) >= 0 && sound.indexOf(item.Sound) < 0) { const sound2 = sound.replace(item.Word, item.Sound); content = content.replace("[读 src='" + sound, "[读 src=''" + sound2); break; } } } } return content; } } export async function BuildMiaoguoTestCard(ctx) { const param = ctx.request.body || {}; param.UserID = ctx.query.UserID || 0; if (param.FolderID == -1) { const arrColor = constantClass.getFolderColor; const arrFolderIcon = constantClass.getFolderIcon; const numColor = stringUtils.Random(0, arrColor.length - 1); const numIcon = stringUtils.Random(0, arrFolderIcon.length - 1); const obj2 = { UserID: param.UserID || 0, Name: param.UnitName + "_" + moment().format("MMDDHHmm"), FolderType: 0, ColorName: arrColor[numColor].Color, IconName: arrFolderIcon[numIcon], UpdateTime: moment().format("YYYY-MM-DD HH:mm:ss"), Flag: 0, }; const insertID = await miaoguo.AddFolderInfo(obj2); param.FolderID = insertID.insertId; } else if (param.FolderID > 0) { await miaoguo.UpdateFolderInfo({ ID: param.FolderID, UserID: param.UserID, UpdateTime: moment().format("YYYY-MM-DD HH:mm:ss"), }); } const list = await fetchLocalResult("/api/GetMiaoguoTestExamine?BookID=" + param.BookID + "&UnitName=" + param.UnitName + "&UnitID=" + param.UnitID + "&TestType=" + param.TestType + "&IsBuild=1"); if (param.WordList.length > 0) { for (let i = 0; i < param.WordList.length; i++) { if (param.WordList[i] && param.WordList[i].Word) param.WordList[i] = param.WordList[i].Word; } } const arr = []; for (const word of param.WordList) { for (const item of list) { if (word == item.Word) { const obj = { Field1: item.Content[1].Content, Field2: item.Content[2].Content, Field3: item.Content[3] && item.Content[3].Content ? item.Content[3].Content : "", Tags: item.Content[0].Content, ID: 0, FolderID: param.FolderID, LimitTime: moment().format("YYYY-MM-DD HH:mm:ss"), FontSize: item.FontSize, }; if (!stringUtils.CheckIsArray(obj.Tags)) obj.Tags = obj.Tags.split(","); arr.push(obj); break; } } } for (const postJSON of arr) await fetchLocalPost("/api/AddMiaoguoCard?Type=IsExist&UserID=" + param.UserID, postJSON); ctx.body = { errcode: 10000 }; } export async function BuildMiaoguoCardByYJBDC(ctx) { const param = ctx.request.body || {}; param.UserID = ctx.query.UserID || 0; let folderID = 0; const folderInfo = await miaoguo.GetMiaoguoFolderCount({ UserID: param.UserID, FolderType: 2 }); if (!folderInfo || folderInfo.length == 0) { const insertID = await miaoguo.AddFolderInfo({ UserID: ctx.query.UserID || 0, Name: "语境背单词", FolderType: 2, ColorName: "#7C63DC", IconName: "cardlist_paper_white_48x62", UpdateTime: moment().format("YYYY-MM-DD HH:mm:ss"), Flag: 0, }); folderID = insertID.insertId; } else if (folderInfo.length > 0) { folderID = folderInfo[0].ID; } const arrWords = param.Words.split(","); for (let i = 0; i < arrWords.length; i++) arrWords[i] = "'" + arrWords[i].replace(/'/g, "''") + "'"; const list = await miaoguo.RunSql(null, "select * from Words where BookID=110 and Word in (" + arrWords.join(",") + ");"); const arr = []; for (const item of list) { const question = "[特]" + item.Word + "[/特]"; let answer = ""; if (item.Soundmark) answer = "[读 src='https://sp0.baidu.com/-rM1hT4a2gU2pMbgoY3K/gettts?lan=uk&text=" + item.Word + "&spd=3&source=alading']" + item.Soundmark + "[/读]\n \n"; answer += "[光]" + item.Translate + "[/光]\n \n"; if (item.ExampleSentence) { answer += "例句:\n"; const json = JSON.parse(item.ExampleSentence); const sentences = json.Sentences; const count = sentences.length > 3 ? 3 : sentences.length; for (let j = 0; j < count; j++) { answer += "(" + (j + 1) + ")" + sentences[j].Sentence + "\n"; answer += sentences[j].Translate + "\n\n"; } } arr.push({ Field1: question, Field2: answer, Field3: "", Tags: ["语境", "单词"], ID: 0, FolderID: folderID, LimitTime: moment().format("YYYY-MM-DD HH:mm:ss"), }); } for (const postJSON of arr) await fetchLocalPost("/api/AddMiaoguoCard?Type=IsExist&UserID=" + param.UserID, postJSON); ctx.body = { errcode: 10000 }; } export async function UpdateMiaoguoCardTodayAll(ctx) { const paramBody = ctx.request.body || {}; let currentTime = moment().format('YYYY-MM-DD HH:mm:ss'); const logInfo = await miaoguo.GetMiaoguoLogInfo({ UserID: ctx.query.UserID, LogID: 304 }); if (logInfo && logInfo.length > 0) { const today = moment().format('YYYY-MM-DD'); const startDate = moment(logInfo[0].CreateTime).format('YYYY-MM-DD'); if (startDate < today) currentTime = startDate + " 23:59:59"; } for (const item of paramBody.List || []) { const param = { MiaoguoCardID: item.MiaoguoCardID, UserID: ctx.query.UserID, IntervalTime: item.IntervalTime || "1d", BtnNumber: item.BtnNumber || 4, FontSize: item.FontSize || 48, Duration: item.Duration || 0, LearningType: item.LearningType || 0, IsUndo: item.IsUndo || 0, LastTime: item.LastTime || currentTime, }; if (param.LastTime > currentTime) param.LastTime = currentTime; if (param.Duration == "undefined") param.Duration = 0; let info = await miaoguo.GetCardInfo(param); if (info && info.length > 0) { info = info[0]; if (param.BtnNumber != 1 && param.BtnNumber != -1) { info.LastTime = param.LastTime; info.FirstTime = moment(info.FirstTime).format('YYYY-MM-DD HH:mm:ss'); if (info.FirstTime < "1900-01-02 00:00:00") info.FirstTime = info.LastTime; info.LearnNumber = info.LearnNumber + 1; let btnNumber = param.BtnNumber; if (param.LearningType == 0 && btnNumber < 4) btnNumber = 4; if (info.RepetitionCoefficient > 1.3) info.RepetitionCoefficient = getEF(info.RepetitionCoefficient, btnNumber); else info.RepetitionCoefficient = 1.5; } if (param.BtnNumber == 1) { info.LastTime = param.LastTime; const days = Number(param.IntervalTime.replace("d", "")); info.LimitTime = moment().add(days, "days").format('YYYY-MM-DD HH:mm:ss'); } else if (param.IntervalTime) { const intervalTime = param.IntervalTime; if (intervalTime.indexOf("m") > 0) { const minute = Number(intervalTime.replace("m", "")); info.LimitTime = moment().add(minute, "minute").format('YYYY-MM-DD HH:mm:ss'); info.IntervalDay = 0; } else if (intervalTime.indexOf("d") > 0) { const days = Number(intervalTime.replace("d", "")); info.LimitTime = moment().add(days, "days").format('YYYY-MM-DD HH:mm:ss'); info.IntervalDay = days; } } if (param.FontSize) info.FontSize = param.FontSize; if (param.BtnNumber == -1) { info.IsTodayStudy = 1; info.LimitTime = moment().format('YYYY-MM-DD HH:mm:ss'); info.IntervalDay = 0; } else if (info.IsTodayStudy == 1 && param.BtnNumber > 0) { info.IsTodayStudy = 0; } await miaoguo.UpdateCard(info); const obj = { CardID: param.MiaoguoCardID, CardType: info.CardType, CreateTime: param.LastTime, LearningType: param.LearningType, BtnNumber: Number(param.BtnNumber), RepetitionCoefficient: info.RepetitionCoefficient, }; if (param.IntervalTime) { if (param.BtnNumber == 1) obj.IntervalTime = "1d"; else obj.IntervalTime = param.IntervalTime; } if (param.Duration) { obj.Duration = param.Duration; if (param.Duration > 1800) obj.Duration = 1800; } const learningRecord = await miaoguo.GetLearningRecordInfoNew(obj); if (!learningRecord || learningRecord.length == 0) await miaoguo.AddLearningRecord(obj); } } ctx.body = { errcode: 10000, result: 1 }; miaoguo.AddMiaoguoLog({ UserID: ctx.query.UserID, LogID: 312, Content: JSON.stringify(paramBody), }).catch(err => console.error('AddMiaoguoLog failed:', err)); globalCache.delete("GetMiaoguoCard###UserID=" + ctx.query.UserID); } export async function UpdateMiaoguoCardInfo(ctx) { const param = { UserID: ctx.query.UserID || "0", MiaoguoCardID: ctx.query.MiaoguoCardID || "0", IsCollect: ctx.query.IsCollect, }; const info = await miaoguo.GetCardInfo(param); if (info && info.length > 0) { const obj = { MiaoguoCardID: param.MiaoguoCardID, }; if (param.IsCollect) obj.IsCollect = param.IsCollect; await miaoguo.UpdateCard(obj); const log = { UserID: param.UserID, LogID: 298, Content: JSON.stringify(obj), }; miaoguo.AddMiaoguoLog(log).catch(err => console.error('AddMiaoguoLog failed:', err)); } ctx.body = { errcode: 10000 }; } export async function UpdateCardImageUrl(ctx) { const param = { MiaoguoCardID: ctx.query.MiaoguoCardID, }; const list = await miaoguo.GetCardContentList(param.MiaoguoCardID); if (list && list.length > 0) { for (let i = 1; i < list.length; i++) { let temp = list[i].Content; if (!temp) continue; let changed = false; const startTime = Date.now(); while (temp.indexOf("[图") >= 0) { temp = temp.substr(temp.indexOf("[图 url='")); const oldUrl = temp.substring(temp.indexOf("[图 url='") + 8, temp.indexOf("']")); const newUrl = temp.substring(temp.indexOf("']") + 2, temp.indexOf("[/图]")); if (newUrl) { changed = true; list[i].Content = list[i].Content.replace(newUrl, ""); list[i].Content = list[i].Content.replace(oldUrl, newUrl); } temp = temp.substr(temp.indexOf("[/图]") + 4); try { stringUtils.loopBreaker(startTime); } catch { break; } } if (changed) { await miaoguo.UpdateCardContent({ MiaoguoCardID: param.MiaoguoCardID, ContentType: list[i].ContentType, Content: list[i].Content, }); } } } ctx.body = { errcode: 10000 }; } export async function GetBiShunByKaitiUrl(ctx) { const param = ctx.request.body || {}; let result = param.Url; const arr = globalCache.get("HanziWordsAll"); if (arr !== 0) { for (const item of arr) { if (param.Url === item.KaitiUrl && param.DataType == "bishun") { result = item.BiShunUrl; break; } else if (param.Url === item.BiShunUrl && param.DataType == "kaiti") { result = item.KaitiUrl; break; } } } ctx.body = { errcode: 10000, result }; } async function fetchLocalResult(path) { const response = await fetch('http://localhost:' + config.port + encodeURI(path)); const json = await response.json(); return json.result; } async function fetchLocalPost(path, body) { const response = await fetch('http://localhost:' + config.port + encodeURI(path), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body), }); return await response.json(); } function changeStringToView(field) { const result = { Field: [[]], Images: [], }; for (let j = 0; j < field.length; j++) { if (j == 0) { if (field[j].ContentType == 0 && field[j].Content && field[j].Content.length > 0) result.Tags = field[j].Content.toString().split(","); else result.Tags = []; } else { if (field[j].ContentType == j && field[j].Content && field[j].Content.length > 0) { const arrResult = []; let str = field[j].Content.toString(); str = decryptUrl(str); if (str.indexOf("[读") > 0) str = str.replace(/\[读/g, "\n[读"); str = str.replace(/\[图/g, "\n[图"); if (str.indexOf("[音") > 0) str = str.replace(/\[音/g, "\n[音"); str = str.replace(/\[\/读\]/g, "[/读]\n"); str = str.replace(/\[\/图\]/g, "[/图]\n"); str = str.replace(/\[\/音\]/g, "[/音]\n"); str = str.replace(/\n\n\n/g, "\n\n"); const arr = str.split("\n"); for (let k = 0; k < arr.length; k++) { if (arr[k].indexOf("[图") >= 0 && arr[k].indexOf("[/图]") > 0) { const obj = { Type: "image" }; obj.ContentServer = arr[k].substring(arr[k].indexOf("[图") + 3, arr[k].indexOf("[/图]")); const w = 650; if (obj.ContentServer.indexOf("w='") >= 0 && obj.ContentServer.indexOf("h='") >= 0) { obj.Width = obj.ContentServer.substring(obj.ContentServer.indexOf("w='") + 3, obj.ContentServer.indexOf("h='") - 2); obj.Height = obj.ContentServer.substring(obj.ContentServer.indexOf("h='") + 3, obj.ContentServer.indexOf("']")); obj.Height = Math.round((w * Number(obj.Height)) / Number(obj.Width)); obj.Width = w; } else { obj.Width = ""; obj.Height = ""; } obj.ContentServer = obj.ContentServer.substring(obj.ContentServer.indexOf("]") + 1); obj.Content = obj.ContentServer; if (obj.Content.lastIndexOf("fm=58") >= 0) obj.IsShowBishun = true; else if (obj.Content.indexOf("hanyu-word-gif") > 0) obj.IsShowKaiti = true; result.Images.push(obj.Content); arrResult.push(obj); } else if (arr[k].indexOf("[特]") >= 0 && arr[k].indexOf("[/特]") > 0) { arrResult.push({ Type: "bigword", Content: arr[k].substring(arr[k].indexOf("[特]") + 3, arr[k].indexOf("[/特]")), }); } else if ((arr[k].indexOf("[线]") >= 0 && arr[k].indexOf("[/线]") > 0) || (arr[k].indexOf("[光]") >= 0 && arr[k].indexOf("[/光]") > 0)) { const content = []; let temp = arr[k]; do { const num1 = temp.indexOf("[线]"); const num2 = temp.indexOf("[光]"); if (num1 >= 0 || num2 >= 0) { if ((num1 < num2 && num1 >= 0 && num2 >= 0) || (num1 >= 0 && num2 < 0)) { const temp1 = temp.substring(0, temp.indexOf("[线]")); if (temp1) content.push({ key: "normal", value: temp1 }); else if (temp && temp.indexOf("[线]") < 0) { content.push({ key: "normal", value: temp }); temp = ""; } } else if ((num1 > num2 && num1 >= 0 && num2 >= 0) || (num1 < 0 && num2 >= 0)) { const temp1 = temp.substring(0, temp.indexOf("[光]")); if (temp1) content.push({ key: "normal", value: temp1 }); else if (temp && temp.indexOf("[光]") < 0) { content.push({ key: "normal", value: temp }); temp = ""; } } else { content.push({ key: "normal", value: temp }); temp = ""; } } else { content.push({ key: "normal", value: temp }); temp = ""; } if (temp.length > 0 && (num1 >= 0 || num2 >= 0)) { if ((num1 < num2 && num1 >= 0 && num2 >= 0) || (num1 >= 0 && num2 < 0)) { temp = temp.substr(temp.indexOf("[线]") + 3); const temp1 = temp.substring(0, temp.indexOf("[/线]")); if (temp1) content.push({ key: "line", value: temp1 }); temp = temp.substr(temp.indexOf("[/线]") + 4); } else if ((num1 > num2 && num1 >= 0 && num2 >= 0) || (num1 < 0 && num2 >= 0)) { temp = temp.substr(temp.indexOf("[光]") + 3); const temp1 = temp.substring(0, temp.indexOf("[/光]")); if (temp1) content.push({ key: "highlighter", value: temp1 }); temp = temp.substr(temp.indexOf("[/光]") + 4); } else { temp = ""; } } else { temp = ""; } } while (temp.length > 0); arrResult.push({ Type: "line", Content: content }); } else if (arr[k].indexOf("[读") >= 0 && arr[k].indexOf("[/读]") > 0) { const obj = { Type: "sound" }; if (arr[k].indexOf("[读]") >= 0) { obj.Content = arr[k].substring(arr[k].indexOf("[读]") + 3, arr[k].indexOf("[/读]")); } else { const tempIndex = arr[k].indexOf("']") + 2; obj.Content = arr[k].substring(tempIndex, arr[k].indexOf("[/读]")); obj.SoundMark = arr[k].substring(arr[k].indexOf("src='") + 5, arr[k].indexOf("']")); } arrResult.push(obj); } else if (arr[k].indexOf("[音") >= 0 && arr[k].indexOf("[/音]") > 0) { const obj = { Type: "recorder" }; obj.SoundMark = arr[k].substring(arr[k].indexOf("url='") + 5, arr[k].indexOf("']")); arrResult.push(obj); } else if (arr[k] != "") { arrResult.push({ Type: "normal", Content: arr[k] }); } else if (k > 0 && arr[k] == "") { arrResult.push({ Type: "br", Content: "" }); } arrResult.push({ Type: "return" }); } for (let i = arrResult.length - 1; i >= 0; i--) { if (arrResult[i].Type == "return" || arrResult[i].Type == "br") arrResult.pop(); else break; } result.Field.push(arrResult); } else { result.Field.push([]); } } } return result; } function decryptUrl(str) { if (str && str.constructor == Array) { if (str.length > 0) str = str.join(","); else str = ""; } if (str) { str = str.replace(/#####1#####/g, "baidu.com"); str = str.replace(/#####2#####/g, "iciba.com"); str = str.replace(/#####3#####/g, "https://pinyin.kylx365.com/sounds"); str = str.replace(/#####4#####/g, "https://miaguo-1253256735.file.myqcloud.com"); } return str; } function getPinyinNormal(pinyin) { let result = ""; const arr = constantClass.getPinyinArray(); for (const item of arr) { if (item[1] == pinyin) { result = item[0]; break; } } return result; } function getEF(EF, Q) { if (Q < 3) Q = 3; let result = Math.round(1000 * (EF + (0.1 - (5 - Q) * (0.08 + (5 - Q) * 0.02)))) / 1000; if (result < 1.6) result = 1.6; if (EF < 1.8 && Q == 4) result = 1.8; if (EF < 2 && Q == 5) result = 2; return result; } function getWordJSON(param, words) { const taskList = []; if (words[0].Pinyin) { for (const word of words) { const taskInfo = { FontSize: 144, TagWidth: 136, Content: [ { ContentType: 0, Content: [] }, { ContentType: 1, Content: [] }, { ContentType: 2, Content: [] }, ], }; taskInfo.Word = word.Name; const pinyinNormal = "(" + getPinyinNormal(word.Pinyin) + ")"; const combineWords = stringUtils.ReplaceAllString(word.CombineWords, word.Name, word.Name + pinyinNormal); taskInfo.ReadString = word.Name + pinyinNormal + "," + combineWords + "的" + word.Name + pinyinNormal; if (param.TestType == "read") { taskInfo.Content[0].Content = ["怎么念"]; let question = word.CombineWords; question = stringUtils.ReplaceAllString(question, word.Name, "(" + word.Name + ")"); taskInfo.Content[1].Content = question; taskInfo.Content[2].Content = "[读 src='" + taskInfo.ReadString + "']" + word.Pinyin + "[/读]"; if (word.CombineWords.length > 6) { taskInfo.FontSize = 108; if (word.CombineWords.length > 10) taskInfo.FontSize = 68; } } else if (param.TestType == "write") { taskInfo.Content[0].Content = ["怎么写"]; const question2 = word.CombineWords2; taskInfo.Content[1].Content = "[读 src='" + taskInfo.ReadString + "']" + question2 + "[/读]"; taskInfo.Content[2].Content = word.Name + "[图 w='650' h='650']" + word.KaitiUrl + "[/图]"; if (question2.length > 6) { taskInfo.FontSize = 108; if (question2.length > 10) { taskInfo.FontSize = 68; if (question2.length > 14) taskInfo.FontSize = 48; } } } taskInfo.ContentNew = changeStringToView(taskInfo.Content); if (!param.IsBuild) delete taskInfo.Content; taskList.push(taskInfo); } } return taskList; }