浏览代码

Codex升级Node.js到24.1第二次

chengjie 2 周之前
父节点
当前提交
572f8a7619

+ 33 - 0
package-lock.json

@@ -824,6 +824,14 @@
824 824
         "buffer": "^5.1.0"
825 825
       }
826 826
     },
827
+    "cron-parser": {
828
+      "version": "4.9.0",
829
+      "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz",
830
+      "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==",
831
+      "requires": {
832
+        "luxon": "^3.2.1"
833
+      }
834
+    },
827 835
     "cross-env": {
828 836
       "version": "7.0.3",
829 837
       "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
@@ -1885,6 +1893,11 @@
1885 1893
       "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
1886 1894
       "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="
1887 1895
     },
1896
+    "long-timeout": {
1897
+      "version": "0.1.1",
1898
+      "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz",
1899
+      "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w=="
1900
+    },
1888 1901
     "lru-cache": {
1889 1902
       "version": "7.18.3",
1890 1903
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
@@ -1895,6 +1908,11 @@
1895 1908
       "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz",
1896 1909
       "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg=="
1897 1910
     },
1911
+    "luxon": {
1912
+      "version": "3.7.2",
1913
+      "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
1914
+      "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew=="
1915
+    },
1898 1916
     "make-dir": {
1899 1917
       "version": "3.1.0",
1900 1918
       "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -2032,6 +2050,16 @@
2032 2050
       "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
2033 2051
       "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="
2034 2052
     },
2053
+    "node-schedule": {
2054
+      "version": "2.1.1",
2055
+      "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz",
2056
+      "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==",
2057
+      "requires": {
2058
+        "cron-parser": "^4.2.0",
2059
+        "long-timeout": "0.1.1",
2060
+        "sorted-array-functions": "^1.3.0"
2061
+      }
2062
+    },
2035 2063
     "nth-check": {
2036 2064
       "version": "2.1.1",
2037 2065
       "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@@ -2495,6 +2523,11 @@
2495 2523
         "side-channel-map": "^1.0.1"
2496 2524
       }
2497 2525
     },
2526
+    "sorted-array-functions": {
2527
+      "version": "1.3.0",
2528
+      "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz",
2529
+      "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA=="
2530
+    },
2498 2531
     "source-map-js": {
2499 2532
       "version": "1.2.1",
2500 2533
       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",

+ 3 - 0
package.json

@@ -11,6 +11,8 @@
11 11
     "dev": "./scripts/use-node-version.sh cross-env NODE_ENV=development node src/app.js",
12 12
     "prestart": "node scripts/check-node-version.js",
13 13
     "start": "./scripts/use-node-version.sh cross-env NODE_ENV=production node src/app.js",
14
+    "prebuild": "node scripts/check-node-version.js",
15
+    "build": "./scripts/use-node-version.sh cross-env NODE_ENV=production node src/build.js",
14 16
     "test": "./scripts/use-node-version.sh node",
15 17
     "node": "./scripts/use-node-version.sh node",
16 18
     "check-version": "./scripts/use-node-version.sh node -v"
@@ -42,6 +44,7 @@
42 44
     "moment": "^2.30.1",
43 45
     "multer": "^1.4.5-lts.1",
44 46
     "mysql2": "^3.14.1",
47
+    "node-schedule": "^2.1.1",
45 48
     "pdfkit": "^0.17.1",
46 49
     "request-promise": "^4.2.6",
47 50
     "tencentcloud-sdk-nodejs-ocr": "^4.1.70",

+ 1 - 1
public/mg/main.html

@@ -1284,7 +1284,7 @@
1284 1284
                                         <div></div>
1285 1285
                                     </div>
1286 1286
                                     <div class="panelTitle3 FlexRow" v-if="InfomationResult.ENG.WordFrequency">
1287
-                                        <div class="panelTitle4">该单词大约在 {{InfomationResult.ENG.WordFrequency.Max}} 词频范围内
1287
+                                        <div class="panelTitle4">该单词大约在 {{InfomationResult.ENG.WordFrequency.Max}} 词频范围内<template v-if="InfomationResult.ENG.CEFR_Level">,该单词在<a href="https://www.cambridgeenglish.cn/exams-and-tests/cefr/" target="_blank">CEFR</a>(国际语言标准)中定位是 {{InfomationResult.ENG.CEFR_Level}}</template>
1288 1288
                                         </div>
1289 1289
                                     </div>
1290 1290
                                     <div class="panelItemFrame0 FlexRow">

文件差异内容过多而无法显示
+ 1 - 1
public/mg/miaoguo.js


+ 9 - 1
public/mg/miaoguo_develop.js

@@ -571,7 +571,15 @@ $(document).ready(function () {
571 571
                                                     var obj=[{ContentType:0,Content:""},{ContentType:1,Content:item[0]},{ContentType:2,Content:""},{ContentType:3,Content:""}];
572 572
                                                     var obj2=changeStringToView(obj);
573 573
                                                     var str=obj2.Field[1][0].Content;
574
-                                                    item.push(str);
574
+                                                    if (str && checkIsArray(str))
575
+                                                        item.push(str);
576
+                                                    else {
577
+                                                        let obj3 = {};
578
+                                                        obj3.ChildID=0;
579
+                                                        obj3.key = "normal";
580
+                                                        obj3.value = str;
581
+                                                        item.push([obj3]);
582
+                                                    }
575 583
 
576 584
 
577 585
                                                     item[1]=ReplaceAllString(item[1],"<b>","");

+ 4 - 4
public/mg/mps.html

@@ -587,11 +587,11 @@
587 587
         <div class="title">年份:</div>
588 588
         <div>
589 589
             <select class="select" id="selectScoreYear" >
590
-                <option value="2022">2022</option>
591
-                <option value="2023">2023</option>
592
-                <option value="2024">2024</option>
593
-                <option value="2025">2025</option>
594 590
                 <option value="2026">2026</option>
591
+                <option value="2025">2025</option>
592
+                <option value="2024">2024</option>
593
+                <option value="2023">2023</option>
594
+                <option value="2022">2022</option>
595 595
             </select>
596 596
         </div>
597 597
 

+ 2 - 2
public/mg/mps_error.html

@@ -183,7 +183,7 @@
183 183
         <div class="mainHeight FlexColumn" v-if="ListMain.length>0">
184 184
             <div class="list FlexRow">
185 185
                 <div class="itemHeader item0">编号</div>
186
-                <div class="itemHeader item1">小程序</div>
186
+                <div class="itemHeader item0">小程序</div>
187 187
                 <div class="itemHeader item1">时间</div>
188 188
                 <div class="itemHeader item0">用户</div>
189 189
                 <div class="itemHeader item1">类型</div>
@@ -195,7 +195,7 @@
195 195
             <template v-for="(item, index) in ListMain">
196 196
                 <div :id=" 'tr_'+item.ID " :class="'list list'+item.Flag +' FlexRow'" >
197 197
                     <div class="tdID item0 item2Center" :id=" 'tdID_'+index ">{{item.ID}}</div>
198
-                    <div class="tdID item1">{{item.ProgramName}}</div>
198
+                    <div class="tdID item0">{{item.ProgramName}}</div>
199 199
                     <div class="tdID item1">{{item.CreateTime}}</div>
200 200
                     <div class="tdID item0">{{item.UserID}}</div>
201 201
                     <div class="tdID item1">{{item.FeedbackType}}</div>

+ 1 - 1
public/mg/print.html

@@ -6,7 +6,7 @@
6 6
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
7 7
     <meta name="viewport" content="initial-scale=1, maximum-scale=3, minimum-scale=1, user-scalable=no">
8 8
     <script src="https://kylx365-1253256735.file.myqcloud.com/js/vue.min.js"></script>
9
-    <script src="https://libs.baidu.com/jquery/1.10.2/jquery.min.js"></script>
9
+    <script src="https://kylx365-1253256735.file.myqcloud.com/js/jquery-1.10.2.min.js"></script>
10 10
     <link rel="stylesheet" href="/mg/common.css">
11 11
     <link rel="stylesheet" href="/mg/print.css">
12 12
     <style>@page {

+ 1 - 1
src/api/activity/activityController.js

@@ -192,7 +192,7 @@ export async function PayActivity(ctx) {
192 192
                 import('axios').then(axios => {
193 193
                     axios.default.get(encodeURI(url))
194 194
                         .then(response => {
195
-                            if (response.data) {
195
+                            if (response.data && response.data.errcode === 10000 && response.data.result && response.data.result.status === 'SUCCESS') {
196 196
                                 console.log("send ok:" + response.data.status);
197 197
                                 if (userIDArr.length > 0) {
198 198
                                     const sqlUpdate = "Update WXUsers SET IsRebate=1 where UserID in (" + userIDArr.join(",") + ");";

+ 9 - 0
src/api/common/commonController.js

@@ -318,6 +318,15 @@ export const MsgSecCheck2 = async (ctx) => {
318 318
     ctx.body = result;
319 319
 };
320 320
 
321
+export const MsgSecCheck3 = async (ctx) => {
322
+    const param = ctx.request.body || {};
323
+    if (!param.Content) {
324
+        ctx.body = {};
325
+        return;
326
+    }
327
+    return MsgSecCheck2(ctx);
328
+};
329
+
321 330
 function getAppIDAndSecret(ProgramID){
322 331
     let appid = '', secret = '';
323 332
     if (!ProgramID)

+ 1 - 0
src/api/common/routes.js

@@ -7,6 +7,7 @@ const router = new Router();
7 7
 // 文件上传路由
8 8
 router.post('/api/MiaoguoUploadFile2',uploadMiddleware,commonController.UploadFile);
9 9
 router.post('/api/MsgSecCheck2',commonController.MsgSecCheck2);
10
+router.post('/api/MsgSecCheck3',commonController.MsgSecCheck3);
10 11
 router.get('/api/GetBaiduToken',commonController.GetBaiduTokenDeprecated);
11 12
 router.get('/api/GetBaiduToken20220922',commonController.GetBaiduToken);
12 13
 router.get('/api/Login20221228',commonController.Login20221228);

+ 503 - 2
src/api/index/indexController.js

@@ -10,6 +10,117 @@ import { query } from '../../util/db.js';
10 10
 import DataStatistics from '../../model/dataStatistics.js';
11 11
 import { stringUtils } from '../../util/stringClass.js';
12 12
 import { globalCache } from '../../util/GlobalCache.js';
13
+import miaoguo from '../../model/miaoguo.js';
14
+import enumeration from '../../model/enumeration.js';
15
+
16
+function getEntryDescription(description) {
17
+    if (!description) return description;
18
+    if (description.indexOf("。")) {
19
+        return description.substr(description.indexOf("。") + 1);
20
+    }
21
+    return description;
22
+}
23
+
24
+function getProgramName(sourceID) {
25
+    const names = {
26
+        89: "数学口算练习",
27
+        90: "注意力游戏",
28
+        98: "语文汉语拼音",
29
+        99: "英语自然拼读",
30
+        105: "数学计算题",
31
+        106: "语文识字",
32
+        107: "小学家长教学助手",
33
+        164: "口算星球",
34
+    };
35
+    return names[sourceID] || sourceID;
36
+}
37
+
38
+function getGradeName(grade) {
39
+    if (grade === undefined || grade === null || grade === "") return "";
40
+    const gradeNumber = Number(grade);
41
+    if (gradeNumber >= 1 && gradeNumber < 7) return gradeNumber + "年级";
42
+    if (gradeNumber == 7) return "初一";
43
+    if (gradeNumber == 8) return "初二";
44
+    if (gradeNumber == 9) return "初三";
45
+    if (gradeNumber == 10) return "高一";
46
+    if (gradeNumber == 11) return "高二";
47
+    if (gradeNumber == 12) return "高三";
48
+    if (gradeNumber == 0) return "学龄前或未知";
49
+    if (gradeNumber > 12) return "大学及以上";
50
+    return "";
51
+}
52
+
53
+async function getMiaoguoCheckListResult(userID) {
54
+    const param = {
55
+        UserID: userID,
56
+        FieldStr: "UserID,IsMember,NickName,AvatarUrl",
57
+    };
58
+    const result = {};
59
+    result.List = [
60
+        {},
61
+        { Title: "加入秒过学习班群(微信群)", Max: 1 },
62
+        { Title: "收集孩子的错题", Max: 1 },
63
+        { Title: "第一课", Remark: "概念课:学习法与记忆规律(约30分钟)", Max: 50, Finished: 0 },
64
+        { Title: "第二课", Remark: "操作课:实战制作合格的题卡(约20分钟)", Max: 50, Finished: 0 },
65
+        { Title: "第三课", Remark: "操作课:陪伴孩子完成学习任务(约20分钟)", Max: 50, Finished: 0 },
66
+        { Title: "第四课", Remark: "精要解析(约10分钟)", Max: 50, Finished: 0 },
67
+        { Title: "添加30张题卡", Max: 30 },
68
+        { Title: "带领孩子完成20分钟学习任务", Max: 1200 },
69
+        { Title: "分享题卡到班群3次", Max: 3 },
70
+        { Title: "勤勉接力到班群3天", Max: 3 },
71
+    ];
72
+    const lessonCount = [0, 28, 7, 6, 14];
73
+
74
+    const userInfo = await miaoguo.GetUsersInfoByUserID(param);
75
+    if (userInfo && userInfo.length > 0) {
76
+        result.UserID = userInfo[0].UserID;
77
+        result.NickName = userInfo[0].NickName;
78
+        result.AvatarUrl = userInfo[0].AvatarUrl;
79
+
80
+        if (userInfo[0].IsMember == 1) {
81
+            result.List[1].Finished = 1;
82
+            result.List[2].Finished = 1;
83
+        }
84
+
85
+        const lesson = await miaoguo.GetUserMiaoguoLesson(param);
86
+        if (lesson && lesson.length > 0) {
87
+            const lessonCount2 = ["", "", "", "", ""];
88
+            for (const item of lesson) {
89
+                lessonCount2[item.LessonID] += "," + item.Content;
90
+            }
91
+            for (let i = 1; i < lessonCount2.length; i++) {
92
+                if (lessonCount2[i].length > 0) {
93
+                    const uniqueContent = [...new Set(lessonCount2[i].substr(1).split(","))];
94
+                    result.List[i + 2].Finished = Math.round(100 * uniqueContent.length / lessonCount[i]);
95
+                } else {
96
+                    result.List[i + 2].Finished = 0;
97
+                }
98
+            }
99
+        }
100
+
101
+        const cardNumber = await miaoguo.GetCardCount({ UserID: userID, CheckList: 1 });
102
+        result.List[7].Finished = cardNumber && cardNumber.length > 0 ? cardNumber[0].count : 0;
103
+
104
+        const taskData = await miaoguo.GetTaskData({ UserID: userID, CreateTime: "2019-03-01" });
105
+        result.List[8].Finished = taskData && taskData.length > 0 && taskData[0].Duration ? taskData[0].Duration : 0;
106
+        result.List[8].FinishedStr = stringUtils.getMinuteSecond(result.List[8].Finished);
107
+
108
+        const cardShare = await miaoguo.GetUserMiaoguoCardShare({ UserID: userID });
109
+        result.List[9].Finished = cardShare && cardShare.length > 0 ? cardShare.length : 0;
110
+
111
+        const taskShare = await miaoguo.GetUserMiaoguoTaskShare({ UserID: userID });
112
+        result.List[10].Finished = taskShare && taskShare.length > 0 ? taskShare.length : 0;
113
+    }
114
+
115
+    let finishCount = 0;
116
+    for (let i = 1; i < result.List.length; i++) {
117
+        if (result.List[i].Finished >= result.List[i].Max) {
118
+            finishCount++;
119
+        }
120
+    }
121
+    result.FinishedCount = Math.round(100 * finishCount / (result.List.length - 1));
122
+    return result;
123
+}
13 124
 
14 125
 export async function index(ctx) {
15 126
     ctx.redirect("../../../public/index.html");
@@ -569,9 +680,399 @@ export async function GetMiaoguoUserPayName(ctx) {
569 680
 }
570 681
 
571 682
 export async function GetUserInfoWeb(ctx) {
572
-    ctx.body = { errcode: 10000, result: {}, message: "待迁移 - GetUserInfoWeb 接口较复杂,需单独处理" };
683
+    const param = {
684
+        UserID: ctx.query.UserID || "0",
685
+        IsShortInfo: ctx.query.IsShortInfo || "0",
686
+    };
687
+
688
+    const result = {
689
+        Column0: {},
690
+        Column1: {},
691
+        Column2: {},
692
+    };
693
+
694
+    if (param.UserID > 0) {
695
+        const userInfo = await miaoguo.GetUsersInfoByUserID(param);
696
+        if (userInfo && userInfo[0]) {
697
+            const user = userInfo[0];
698
+            param.UnionID = user.UnionID;
699
+
700
+            result.Column0.类别1 = "小程序基本信息";
701
+            result.Column0.小程序头像 = user.AvatarUrl == "../images/universalpic_face_default_blue_120x120.png" ? "" : user.AvatarUrl;
702
+            result.Column0.小程序用户ID = user.UserID;
703
+            result.Column0.小程序昵称 = user.NickName;
704
+            result.Column0.小程序昵称备注 = user.NickNameRemark;
705
+
706
+            if (param.IsShortInfo === "0") {
707
+                let classID = user.ClassID;
708
+                if (classID == 0) classID = "";
709
+                else if (classID == -1) classID = "待确定";
710
+                else if (classID == 1) classID = "1~5班";
711
+                else classID = classID + "班";
712
+                result.Column0.班级 = classID;
713
+
714
+                result.Column0.是否开通 = user.IsMember == 1 ? "是" : "";
715
+                result.Column0.有效期 = moment(user.ProductServiceTime).format("YYYY-MM-DD");
716
+                result.Column0.是否支付 = user.IsPay == 1 ? "是" : "";
717
+                result.Column0.支付时间 = user.PayTime ? moment(user.PayTime).format("YYYY-MM-DD HH:mm:ss") : "";
718
+
719
+                let groupID = user.GroupID;
720
+                if (user.IsPay == 0) groupID = "";
721
+                else if (groupID > 0) groupID = stringUtils.getGroupName(groupID, true);
722
+                result.Column0.所在群 = groupID;
723
+
724
+                result.Column0.国家 = user.Country || " ";
725
+                result.Column0.省 = user.Province || " ";
726
+                result.Column0.市 = user.City || " ";
727
+                result.Column0.性别 = user.Gender == 1 ? "男" : "女";
728
+                result.Column0.账号创建时间 = moment(user.CreateTime).format("YYYY-MM-DD HH:mm:ss");
729
+
730
+                const enumInfo = await enumeration.GetEnumerationListByName({ Name: user.UserSource });
731
+                result.Column0.创建账号入口 = getEntryDescription(enumInfo[0]?.Description || "");
732
+                if (user.UserSource == "1037") {
733
+                    result.Column0.创建账号入口 += "【" + getProgramName(user.SourceID) + "】";
734
+                }
735
+
736
+                result.Column0.最后登录时间 = moment(user.LastLoginTime).format("YYYY-MM-DD HH:mm:ss");
737
+                const enumInfo2 = await enumeration.GetEnumerationListByName({ Name: user.LastUserSource });
738
+                if (enumInfo2 && enumInfo2[0] && enumInfo2[0].Description) {
739
+                    result.Column0.最后登录方式 = getEntryDescription(enumInfo2[0].Description);
740
+                } else {
741
+                    result.Column0.最后登录方式 = user.LastUserSource;
742
+                }
743
+
744
+                result.Column0.手机品牌 = user.Brand || " ";
745
+                result.Column0.手机型号 = user.Model || " ";
746
+                result.Column0.手机操作系统 = user.System || " ";
747
+                result.Column0.微信版本号 = user.WXVersion || " ";
748
+                result.Column0.小程序库版本 = user.SDKVersion || " ";
749
+                result.Column0.秒过版本 = user.ProgramVersion || " ";
750
+
751
+                result.Column0.主绑用户ID = user.ParentUserID ? user.ParentUserID : "";
752
+                result.Column0.从绑定用户ID = "";
753
+                result.Column0.从绑定用户ID2 = "";
754
+                const bindUser = await miaoguo.GetUserBindingList(param);
755
+                if (bindUser && bindUser.length > 0) {
756
+                    result.Column0.从绑定用户ID = bindUser[0].UserID;
757
+                    if (bindUser.length > 1) result.Column0.从绑定用户ID2 = bindUser[1].UserID;
758
+                }
759
+
760
+                result.Column0.所属年级 = getGradeName(user.Grade);
761
+
762
+                result.Column0.家庭用户ID1 = "";
763
+                result.Column0.家庭用户ID2 = "";
764
+                const familyUser = await miaoguo.GetMiaoguoFamilyByUserID(param);
765
+                if (familyUser && familyUser.length > 0) {
766
+                    const familyArr = [];
767
+                    for (const field of ["UserID1", "UserID2", "UserID3"]) {
768
+                        if (familyUser[0][field] && familyUser[0][field] != param.UserID) {
769
+                            familyArr.push(familyUser[0][field]);
770
+                        }
771
+                    }
772
+                    result.Column0.家庭用户ID1 = familyArr[0];
773
+                    if (familyArr.length > 1) result.Column0.家庭用户ID2 = familyArr[1];
774
+                }
775
+
776
+                result.Column0.类别2 = "推荐人信息";
777
+                result.Column0.推荐人ID = user.Introducer;
778
+
779
+                const userInfo2 = await miaoguo.GetUsersInfoByUserID({
780
+                    UserID: result.Column0.推荐人ID,
781
+                    FieldStr: "NickName,ClassID,GroupID,IsRebate,IsRebateAgent,HasPayAgent,HasPayAgent2,UserTree,PayTime,IsPromoter",
782
+                });
783
+
784
+                if (userInfo2 && userInfo2[0]) {
785
+                    result.Column0.推荐人昵称 = userInfo2[0].NickName;
786
+                    result.Column0.推荐人班级 = userInfo2[0].ClassID + "班";
787
+                    result.Column0.推荐人小组 = stringUtils.getGroupName(userInfo2[0].GroupID, true);
788
+
789
+                    if (user.PayTime) {
790
+                        const today = moment().format("YYYY-MM-DD");
791
+                        const productServiceTime = moment(user.PayTime).add(7, "days").format("YYYY-MM-DD");
792
+                        result.Column0.奖励有无生效 = productServiceTime < today ? "已生效" : "未生效";
793
+                    }
794
+
795
+                    if (userInfo2[0].IsPromoter > 0) {
796
+                        result.Column0.推荐奖励有无提现 = user.HasPayAgent == 1 ? "已提现" : "";
797
+                        if (result.Column0.推荐奖励有无提现 == "") {
798
+                            result.Column0.推荐奖励有无提现 = user.IsRebate == 1 ? "待审核" : "未提现";
799
+                        }
800
+
801
+                        const userInfo3 = await miaoguo.RunSql(
802
+                            [user.Introducer, user.UserID],
803
+                            `Select mar.ApplyTime,mar.CheckTime
804
+                             from MiaoguoAgentPay map
805
+                             inner join MiaoguoAgentRecord mar on map.RecordID=mar.ID
806
+                             where map.Flag=0 and map.Payee=? and map.UserID=?;`,
807
+                        );
808
+                        if (userInfo3 && userInfo3.length > 0) {
809
+                            if (userInfo3[0].ApplyTime) result.Column0.推荐提现申请日期 = moment(userInfo3[0].ApplyTime).format("YYYY-MM-DD");
810
+                            if (userInfo3[0].CheckTime) result.Column0.推荐被审核通过日期 = moment(userInfo3[0].CheckTime).format("YYYY-MM-DD");
811
+                        }
812
+                    } else {
813
+                        result.Column0.推荐奖励有无提现 = "";
814
+                        result.Column0.推荐提现申请日期 = "";
815
+                        result.Column0.推荐被审核通过日期 = "";
816
+                    }
817
+
818
+                    result.Column0.大使ID = "";
819
+                    if (user.UserTree && user.UserTree.indexOf("#6#") >= 0) {
820
+                        result.Column0.大使ID = user.UserTree.substr(3);
821
+                        result.Column0.大使ID = result.Column0.大使ID.substr(0, result.Column0.大使ID.indexOf("#"));
822
+                        if (result.Column0.大使ID == result.Column0.推荐人ID) result.Column0.大使ID = "";
823
+
824
+                        if (result.Column0.大使ID) {
825
+                            const ambassadorInfo = await miaoguo.GetUsersInfoByUserID({
826
+                                UserID: result.Column0.大使ID,
827
+                                FieldStr: "NickName,ClassID,IsRebate,IsRebateAgent,HasPayAgent,HasPayAgent2,UserTree,PayTime",
828
+                            });
829
+                            if (ambassadorInfo && ambassadorInfo[0]) {
830
+                                result.Column0.大使昵称 = ambassadorInfo[0].NickName;
831
+                                result.Column0.大使班级 = ambassadorInfo[0].ClassID + "班";
832
+                                result.Column0.大使奖励有无提现 = user.HasPayAgent2 == 1 ? "已提现" : "";
833
+                                if (result.Column0.大使奖励有无提现 == "") {
834
+                                    result.Column0.大使奖励有无提现 = user.IsRebateAgent == 1 ? "待审核" : "未提现";
835
+                                }
836
+
837
+                                const ambassadorPay = await miaoguo.RunSql(
838
+                                    [result.Column0.大使ID, user.UserID],
839
+                                    `Select mar.ApplyTime,mar.CheckTime
840
+                                     from MiaoguoAgentPay map
841
+                                     inner join MiaoguoAgentRecord mar on map.RecordID=mar.ID
842
+                                     where map.Flag=0 and map.Payee=? and map.UserID=?;`,
843
+                                );
844
+                                if (ambassadorPay && ambassadorPay.length > 0) {
845
+                                    if (ambassadorPay[0].ApplyTime) result.Column0.大使提现申请日期 = moment(ambassadorPay[0].ApplyTime).format("YYYY-MM-DD");
846
+                                    if (ambassadorPay[0].CheckTime) result.Column0.大使被审核通过日期 = moment(ambassadorPay[0].CheckTime).format("YYYY-MM-DD");
847
+                                }
848
+                            }
849
+                        }
850
+                    }
851
+                }
852
+
853
+                const wsUserInfo = await miaoguo.GetWechatServiceUsersInfoByUnionID(param);
854
+                if (wsUserInfo && wsUserInfo.length > 0) {
855
+                    result.Column1.类别3 = "服务号信息";
856
+                    result.Column1.服务号头像 = wsUserInfo[0].AvatarUrl == "../images/universalpic_face_default_blue_120x120.png" ? "" : wsUserInfo[0].AvatarUrl;
857
+                    result.Column1.服务号用户ID = wsUserInfo[0].UserID;
858
+                    result.Column1.服务号昵称 = wsUserInfo[0].NickName;
859
+                    result.Column1.服务号是否关注 = wsUserInfo[0].Subscribe ? "关注" : "取消";
860
+                    result.Column1.是否报名 = wsUserInfo[0].IsProbation == 1 ? "是" : "";
861
+                    result.Column1.报名时间 = moment(wsUserInfo[0].ProbationPayTime).format("YYYY-MM-DD HH:mm:ss");
862
+                    result.Column1.报名方式 = wsUserInfo[0].SourceID == 166 ? "秒过小程序" : "服务号";
863
+                    result.Column1.推荐人ID = wsUserInfo[0].Introducer ? wsUserInfo[0].Introducer : "";
864
+                    result.Column1.服务号国家 = wsUserInfo[0].Country || " ";
865
+                    result.Column1.服务号省 = wsUserInfo[0].Province || " ";
866
+                    result.Column1.服务号市 = wsUserInfo[0].City || " ";
867
+                    result.Column1.服务号性别 = wsUserInfo[0].Gender == 1 ? "男" : "女";
868
+                    result.Column1.服务号创建时间 = moment(wsUserInfo[0].CreateTime).format("YYYY-MM-DD HH:mm:ss");
869
+                }
870
+
871
+                result.Column1.类别4 = "小程序配置信息";
872
+                const userConfigInfo = await miaoguo.GetUserConfigData(param);
873
+                if (userConfigInfo && userConfigInfo[0]) {
874
+                    result.Column1.练习方案 = userConfigInfo[0].CardType == 0 ? "日常" : (userConfigInfo[0].CardType == 1 ? "紧急" : "整体");
875
+                    result.Column1.任务设置中心方案 = userConfigInfo[0].TaskSetting == 1 ? "完整版" : "基础版";
876
+                    result.Column1.任务限量总数 = userConfigInfo[0].BasicCardLimit;
877
+                    result.Column1.日常新题卡上限 = userConfigInfo[0].CardMaxNumberNew;
878
+                    result.Column1.日常旧题卡上限 = userConfigInfo[0].CardMaxNumberHistory;
879
+                    result.Column1.日常选取规则 = userConfigInfo[0].SortTypeIndex == 0 ? "系统规划" : (userConfigInfo[0].SortTypeIndex == 1 ? "最早优先" : "今天优先");
880
+                    result.Column1.紧急新题卡上限 = userConfigInfo[0].CardMaxNumberNewUrgent;
881
+                    result.Column1.紧急旧题卡上限 = userConfigInfo[0].CardMaxNumberHistoryUrgent;
882
+                    result.Column1.记忆评价 = userConfigInfo[0].MemoryLevel == 1 ? "完整模式" : "基础模式";
883
+                    result.Column1.显示答案操作 = userConfigInfo[0].ClickType == 0 ? "专属按钮" : (userConfigInfo[0].ClickType == 1 ? "空白区域" : "专属按钮与空白都可以");
884
+                    result.Column1.主题色 = userConfigInfo[0].ColorIndexArr;
885
+                    result.Column1.符号栏 = userConfigInfo[0].SymbolList ? JSON.parse(userConfigInfo[0].SymbolList).join(",") : "";
886
+                    result.Column1.卡单排序 = userConfigInfo[0].FolderOrder;
887
+                    result.Column1.基于卡单练习 = userConfigInfo[0].IsFolderPractice == 1 ? "是" : "";
888
+                    result.Column1.练习自动播放 = userConfigInfo[0].IsAutoSound == 1 ? "是" : "";
889
+                }
890
+
891
+                result.Column1.类别9 = "作业清单完成情况";
892
+                const checkListInfo = await getMiaoguoCheckListResult(param.UserID);
893
+                result.Column1.可奖励天数 = checkListInfo.FinishedCount / 5;
894
+                if (checkListInfo.FinishedCount > 0) {
895
+                    for (let i = 1; i < checkListInfo.List.length; i++) {
896
+                        result.Column1[checkListInfo.List[i].Title] = checkListInfo.List[i].Finished >= checkListInfo.List[i].Max ? "完成" : (checkListInfo.List[i].Finished + "/" + checkListInfo.List[i].Max);
897
+                    }
898
+                }
899
+            }
900
+
901
+            if (param.IsShortInfo === "0" || param.IsShortInfo === "1") {
902
+                result.Column2.类别5 = "题卡信息";
903
+                result.Column2.题卡管理平台 = "题卡管理平台";
904
+
905
+                const cardCounts = await miaoguo.RunSql(
906
+                    [param.UserID],
907
+                    `select
908
+                        count(*) as Count,
909
+                        sum(case when CardType=1 then 1 else 0 end) as UrgentCount,
910
+                        sum(case when CardType=-1 then 1 else 0 end) as EmptyCount
911
+                     from MiaoguoCard where UserID=? and Flag=0;`,
912
+                );
913
+                const cardInfo = cardCounts[0] || { Count: 0, UrgentCount: 0, EmptyCount: 0 };
914
+                result.Column2.总题卡数 = cardInfo.Count;
915
+
916
+                const today = moment(user.LastLoginTime).format("YYYY-MM-DD 23:59:59");
917
+                const limitCardList = await miaoguo.RunSql(
918
+                    [param.UserID, today],
919
+                    "select CardType,count(*) as Count from MiaoguoCard where UserID=? and Flag=0 and LimitTime<? group by CardType order by CardType;",
920
+                );
921
+                const limitCardListNew = await miaoguo.RunSql(
922
+                    [param.UserID, today],
923
+                    "select CardType,count(*) as Count from MiaoguoCard where UserID=? and Flag=0 and LimitTime<? and FirstTime='1900-01-01 00:00:00' group by CardType order by CardType;",
924
+                );
925
+
926
+                for (const item of limitCardList) {
927
+                    if (item.CardType == 0) {
928
+                        result.Column2.日常题卡数 = cardInfo.Count - cardInfo.UrgentCount - cardInfo.EmptyCount;
929
+                        result.Column2.日常到期题卡数 = item.Count;
930
+                        for (const newItem of limitCardListNew) {
931
+                            if (newItem.CardType == 0) {
932
+                                result.Column2.日常到期_新 = newItem.Count;
933
+                                result.Column2.日常到期_旧 = result.Column2.日常到期题卡数 - newItem.Count;
934
+                            }
935
+                        }
936
+                    } else if (item.CardType == 1) {
937
+                        result.Column2.紧急题卡数 = cardInfo.UrgentCount;
938
+                        result.Column2.紧急到期题卡数 = item.Count;
939
+                        for (const newItem of limitCardListNew) {
940
+                            if (newItem.CardType == 1) {
941
+                                result.Column2.紧急到期_新 = newItem.Count;
942
+                                result.Column2.紧急到期_旧 = result.Column2.紧急到期题卡数 - newItem.Count;
943
+                            }
944
+                        }
945
+                    }
946
+                }
947
+
948
+                result.Column2.归档题卡数 = cardInfo.EmptyCount;
949
+
950
+                const deleteCount = await miaoguo.GetCardListID({
951
+                    UserID: param.UserID,
952
+                    GetCount: 1,
953
+                    DataType: "delete",
954
+                });
955
+                result.Column2.删除题卡数 = deleteCount[0].Count;
956
+
957
+                const dayNumberList = await miaoguo.RunSql(
958
+                    [param.UserID],
959
+                    `SELECT count(distinct DATE_FORMAT(mlr.CreateTime,'%Y%m%d')) as '天数',sum(mlr.Duration) as '总时长'
960
+                     FROM MiaoguoLearningRecord mlr
961
+                     inner join MiaoguoCard mc on mc.MiaoguoCardID=mlr.CardID
962
+                     where mlr.Flag=0 and mc.UserID=? and mlr.Duration>0;`,
963
+                );
964
+                result.Column2.总练习天数 = dayNumberList[0].天数;
965
+                if (param.IsShortInfo === "0") {
966
+                    result.Column2.总练习时长 = stringUtils.getMinuteSecond(dayNumberList[0].总时长, true, false);
967
+                } else {
968
+                    result.Column2.总练习时长 = Math.round(dayNumberList[0].总时长 / 60);
969
+                }
970
+            }
971
+
972
+            if (param.IsShortInfo === "0") {
973
+                result.Column2.类别6 = "创建题卡信息";
974
+                result.Column2.创建题卡列表 = await miaoguo.RunSql(
975
+                    [param.UserID],
976
+                    "SELECT DATE_FORMAT(mc.CreateTime,'%Y-%m-%d') as '时间',count(mc.MiaoguoCardID) as '创建题卡数' from MiaoguoCard mc where mc.UserID=? group by DATE_FORMAT(mc.CreateTime,'%Y-%m-%d') order by DATE_FORMAT(mc.CreateTime,'%Y-%m-%d') desc limit 7;",
977
+                );
978
+
979
+                const cardList2 = await miaoguo.RunSql(
980
+                    [param.UserID],
981
+                    "SELECT DATE_FORMAT(mr.CreateTime,'%Y-%m-%d') as '时间',count(mr.ID) as '练习题卡数',sum(mr.Duration)/60 as '练习分钟' from MiaoguoCard mc inner join MiaoguoLearningRecord mr on mc.MiaoguoCardID=mr.CardID where mc.UserID=? group by DATE_FORMAT(mr.CreateTime,'%Y-%m-%d') order by DATE_FORMAT(mr.CreateTime,'%Y-%m-%d') desc;",
982
+                );
983
+                result.Column2.类别7 = "练习题卡信息 (共" + cardList2.length + "天)";
984
+                result.Column2.练习题卡列表 = cardList2;
985
+
986
+                result.Column2.类别8 = "教程学习情况";
987
+                const lesson = await miaoguo.GetUserMiaoguoLesson(param);
988
+                if (lesson && lesson.length > 0) {
989
+                    const lessonCount1 = ["", "第1课", "第2课", "第3课", "第4课"];
990
+                    const lessonCount2 = ["", "", "", "", ""];
991
+                    for (const item of lesson) {
992
+                        lessonCount2[item.LessonID] += "," + item.Content;
993
+                    }
994
+
995
+                    const tempLesson = [];
996
+                    for (let i = 1; i <= 4; i++) {
997
+                        if (lessonCount2[i].length > 0) {
998
+                            const sortedLesson = [...new Set(lessonCount2[i].substr(1).split(",").map(item => Number(item)))].sort((a, b) => a - b);
999
+                            tempLesson.push({ Title: lessonCount1[i], Content: sortedLesson.join(", ") });
1000
+                        }
1001
+                    }
1002
+                    result.Column2.教程学习列表 = tempLesson;
1003
+                }
1004
+            }
1005
+        }
1006
+    }
1007
+
1008
+    ctx.body = { errcode: 10000, result };
573 1009
 }
574 1010
 
575 1011
 export async function GetStatisticsShareUserPay(ctx) {
576
-    ctx.body = { errcode: 10000, message: "待迁移 - BuildStatisticsShareUserPay" };
1012
+    const sql = `select u.UserID,u.NickName,u.AvatarUrl,u.ClassID,u.Introducer,u.IsPromoter,u.IsRebate,u.IsRebateAgent,u.PromotionDeliveryTimeIsUp,u.PayTime,u.UserTree,cs.StartDate
1013
+        from MiaoguoWXUsers u inner join MiaoguoClassSchedule cs on u.ClassID=cs.ClassID
1014
+        where Left(u.UserTree,2)='#6' and u.IsPay=1 and u.Introducer<>0 order by u.UserTree;`;
1015
+    const list = await miaoguo.RunSql(null, sql);
1016
+    for (let i = 0; i < list.length; i++) {
1017
+        const item = list[i];
1018
+        const treeArr = item.UserTree.split("#");
1019
+        item.Tree = [];
1020
+        item.StartDate = moment(item.StartDate).format("MM-DD");
1021
+        for (let j = 2; j < treeArr.length; j++) {
1022
+            if (treeArr[j] == item.UserID) {
1023
+                for (let k = 2; k < 8; k++) {
1024
+                    if (k == j) {
1025
+                        let name = item.NickName + "(" + item.StartDate + ")";
1026
+                        if (item.IsPromoter == 1) name = "★★" + name;
1027
+                        else if (item.IsPromoter == 2) name = "★" + name;
1028
+                        for (let n = i; n >= 0; n--) {
1029
+                            if (item.Introducer == list[n].UserID) {
1030
+                                const promotionDeliveryTimeIsUp = moment(list[n].PromotionDeliveryTimeIsUp).format("YYYY-MM-DD HH:mm:ss");
1031
+                                const payTime = moment(item.PayTime).format("YYYY-MM-DD HH:mm:ss");
1032
+                                if (item.IsRebate == 0 && payTime < promotionDeliveryTimeIsUp && promotionDeliveryTimeIsUp < "2099-12-31") {
1033
+                                    item.IsNeedTime = 1;
1034
+                                    break;
1035
+                                }
1036
+                            }
1037
+                        }
1038
+                        item.Tree.push({
1039
+                            UserID: item.UserID,
1040
+                            NickName: name,
1041
+                            StartDate: item.StartDate,
1042
+                            IsRebate: item.IsRebate,
1043
+                            IsRebateAgent: item.IsRebateAgent,
1044
+                            AvatarUrl: item.AvatarUrl,
1045
+                            IsNeedTime: item.IsNeedTime,
1046
+                        });
1047
+                    } else {
1048
+                        item.Tree.push(null);
1049
+                    }
1050
+                }
1051
+            }
1052
+        }
1053
+        delete item.NickName;
1054
+        delete item.AvatarUrl;
1055
+        delete item.UserTree;
1056
+    }
1057
+
1058
+    ctx.body = { errcode: 10000, result: list };
1059
+}
1060
+
1061
+export async function BuildStatisticsShareUserPay(ctx) {
1062
+    async function addUserChild(tree, userID) {
1063
+        const list = await miaoguo.RunSql([userID], "select UserID,NickName,AvatarUrl,Introducer from MiaoguoWXUsers where Introducer=? and IsPay=1;");
1064
+        for (const item of list) {
1065
+            const itemTree = tree + "#" + item.UserID;
1066
+            await miaoguo.RunSql([
1067
+                itemTree,
1068
+                Number(config.AgentMoney) * 100,
1069
+                Number(config.AmbassadorMoney) * 100,
1070
+                item.UserID,
1071
+            ], "update MiaoguoWXUsers set UserTree=?,AgentMoney=?,AmbassadorMoney=? where UserID=?;");
1072
+            await addUserChild(itemTree, item.UserID);
1073
+        }
1074
+    }
1075
+
1076
+    await addUserChild("", 0);
1077
+    ctx.body = { errcode: 10000 };
577 1078
 }

+ 1 - 0
src/api/index/routes.js

@@ -49,5 +49,6 @@ router.get('/data/GetStatisticsMPSNew', indexController.GetStatisticsMPSNew);
49 49
 router.get('/data/GetStatisticsWebchatServiceNew', indexController.GetStatisticsWebchatServiceNew);
50 50
 router.get('/api/GetUserInfoWeb', indexController.GetUserInfoWeb);
51 51
 router.get('/api/GetStatisticsShareUserPay', indexController.GetStatisticsShareUserPay);
52
+router.get('/api/BuildStatisticsShareUserPay', indexController.BuildStatisticsShareUserPay);
52 53
 
53 54
 export default router;

+ 536 - 0
src/api/miaoguo/literacyController.js

@@ -419,6 +419,542 @@ export async function GetMiaoguoAISearch2(ctx) {
419 419
     return GetMiaoguoAISearch(ctx);
420 420
 }
421 421
 
422
+function applyLiteracyDefaults(ctx, defaults) {
423
+    const queryObj = ctx.query;
424
+    for (const [key, value] of Object.entries(defaults)) {
425
+        if (queryObj[key] === undefined || queryObj[key] === null || queryObj[key] === '') {
426
+            queryObj[key] = value;
427
+        }
428
+    }
429
+}
430
+
431
+export async function GetMiaoguoAISearchTest(ctx) {
432
+    return GetMiaoguoAISearch(ctx);
433
+}
434
+
435
+export async function GetLiteracyData(ctx) {
436
+    const word = ctx.query.Word || '';
437
+    const htmlString = await axios.get('http://hanyu.baidu.com/s?wd=' + encodeURI(word) + '&ptype=zici', {
438
+        headers: getLegacyDictionaryHeaders(),
439
+    }).then(res => res.data);
440
+    const biShunUrl = getBiShun(htmlString);
441
+    let pinYin;
442
+    let buShouBiHua;
443
+    let combineWords;
444
+    if (word.length == 1) {
445
+        pinYin = getPinyin(htmlString);
446
+        buShouBiHua = getBushouBihua(htmlString);
447
+        pinYin = getExplain(htmlString, pinYin);
448
+        combineWords = getCombineWords(htmlString);
449
+    } else {
450
+        pinYin = getVocabularyPinyin(htmlString);
451
+        buShouBiHua = {};
452
+        pinYin = getExplain(htmlString, pinYin);
453
+        combineWords = {};
454
+    }
455
+    ctx.body = {
456
+        errcode: 10000,
457
+        result: {
458
+            HanZi: word,
459
+            BiShunUrl: biShunUrl,
460
+            PinYin: pinYin,
461
+            BuShou: buShouBiHua.BuShou,
462
+            BiHua: buShouBiHua.BiHua,
463
+            CombineWords: combineWords,
464
+            Antonym: getAntonymSynonym(htmlString, '反'),
465
+            Synonym: getAntonymSynonym(htmlString, '近'),
466
+        },
467
+    };
468
+}
469
+
470
+export async function GetWordData(ctx) {
471
+    const word = ctx.query.Word || '';
472
+    const htmlString = await axios.get('http://www.iciba.com/' + encodeURI(word), {
473
+        headers: getLegacyDictionaryHeaders(),
474
+    }).then(res => res.data);
475
+    let soundmark;
476
+    let paraphrase;
477
+    let deformation;
478
+    if (htmlString.indexOf('<span>英 ') < 0) {
479
+        if (stringUtils.IsChinese(word.substr(0, 1))) {
480
+            paraphrase = getParaphrase(htmlString);
481
+            if (paraphrase.length == 0) paraphrase = getSentenceParaphrase(htmlString);
482
+        } else {
483
+            paraphrase = getSentenceParaphrase(htmlString);
484
+        }
485
+    } else {
486
+        soundmark = getSoundmark(htmlString);
487
+        paraphrase = getParaphrase(htmlString);
488
+        deformation = getDeformation(htmlString);
489
+    }
490
+    ctx.body = { errcode: 10000, result: { Word: word, Soundmark: soundmark, Paraphrase: paraphrase, Deformation: deformation } };
491
+}
492
+
493
+export async function GetYoudaoWordData(ctx) {
494
+    const word = ctx.query.Word || '';
495
+    const htmlString = await axios.get('http://dict.youdao.com/example/' + encodeURI(word) + '/#keyfrom=dict2.top', {
496
+        headers: getLegacyDictionaryHeaders(),
497
+    }).then(res => res.data);
498
+    ctx.body = { errcode: 10000, result: { Word: word, ExamplesSentences: getExamplesSentences(htmlString) } };
499
+}
500
+
501
+function getLegacyDictionaryHeaders() {
502
+    const userAgents = config.UserAgent;
503
+    return { 'User-Agent': userAgents[stringUtils.Random(0, userAgents.length - 1)] };
504
+}
505
+
506
+// Legacy dictionary parsers, preserved from kylx365server.
507
+function getBiShun(data,hasStaticImage) {
508
+    //console.log(data.indexOf("word-header"));
509
+    var result = data.substr(data.indexOf("word-header"));
510
+    //console.log(result.substr(0,20));
511
+    result = result.substr(result.indexOf("header-img"));
512
+    //console.log(result.substr(0,20));
513
+    var imageUrl="";
514
+    if (hasStaticImage){
515
+        imageUrl=result.substr(result.indexOf("background-image: url("));
516
+        imageUrl=imageUrl.substring(22,imageUrl.indexOf(">")-2);
517
+        if (imageUrl.length<10)
518
+            imageUrl="";
519
+    }
520
+    result = result.substr(result.indexOf("word_bishun"));
521
+    //console.log(result.substr(0,20));
522
+    result = result.substr(result.indexOf("data-gif=\"") + 10);
523
+    //console.log(result.substr(10,20));
524
+    result = result.substring(0, result.indexOf(" ") - 1);
525
+    //console.log(result);
526
+
527
+    if (hasStaticImage){
528
+        return [imageUrl,result];
529
+    }
530
+    else
531
+        return result;
532
+}
533
+
534
+function getPinyin(data) {
535
+    var pinyin = [];
536
+    //console.log(data.indexOf("word-header"));
537
+    var result = data.substr(data.indexOf("<div class=\"pronounce\" id=\"pinyin\">"));
538
+    //console.log(result.substr(0,20));
539
+
540
+    var num1, num2;
541
+    var doWhileTime=Date.now();
542
+    do {
543
+        result = result.substr(result.indexOf("<b>") + 3);
544
+        pinyin.push(result.substring(0, result.indexOf("</b>")));
545
+        result = result.substr(result.indexOf("</span>"));
546
+        num1 = result.indexOf("<b>");
547
+        num2 = result.indexOf("</div>");
548
+
549
+        try {
550
+            stringUtils.loopBreaker(doWhileTime);
551
+        }
552
+        catch(err){
553
+            break;
554
+        }
555
+    }
556
+    while (num1 < num2);
557
+
558
+
559
+    return pinyin;
560
+}
561
+
562
+//得到词汇拼音
563
+function getVocabularyPinyin(data) {
564
+    var pinyin = [];
565
+
566
+    var result=data;
567
+
568
+    result=result.substr(result.indexOf("\"title\":\"拼音\""));
569
+    result=result.substr(result.indexOf("\"tag\":\"text\",\"text\":\"")+21);
570
+    result=result.substr(0,result.indexOf("\"}]}]}"));
571
+    pinyin.push(result);
572
+
573
+    console.log(pinyin);
574
+    return pinyin;
575
+}
576
+
577
+//得到部首和笔画
578
+function getBushouBihua(data) {
579
+    var resultObject = {};
580
+    //console.log(data.indexOf("word-header"));
581
+    var result = data.substr(data.indexOf("<li id=\"radical\">"));
582
+    result = result.substr(result.indexOf("<span>") + 6);
583
+    //console.log(result.substr(0,20));
584
+    resultObject.BuShou = result.substring(0, result.indexOf("</span>"));
585
+
586
+    result = data.substr(data.indexOf("<li id=\"stroke_count\">"));
587
+    //console.log(result.substr(0,20));
588
+    result = result.substr(result.indexOf("<span>") + 6);
589
+    resultObject.BiHua = result.substring(0, result.indexOf("</span>"));
590
+    return resultObject;
591
+}
592
+
593
+//得到解释
594
+function getExplain(data, pinyin) {
595
+    var resultObject = [];
596
+
597
+    for (var i = 0; i < pinyin.length; i++) {
598
+        //console.log(pinyin[i]);
599
+        var result = "";
600
+        if (pinyin.length > 1)
601
+            result = data.substr(data.indexOf("<dt class=\"pinyin\">[ " + pinyin[i] + " ]</dt>"));
602
+        else
603
+            result = data.substr(data.indexOf("基本释义"));
604
+
605
+        result = result.substr(result.indexOf("<dd>") + 4);
606
+        var explain = result.substring(0, result.indexOf("</dd>"));
607
+        explain = stringUtils.ReplaceAllString(explain, "  ", "");
608
+        explain = stringUtils.ReplaceAllString(explain, "\n", "");
609
+        var obj = {
610
+            pinyin: pinyin[i],
611
+            explain: explain,
612
+        }
613
+        explain=stringUtils.Trim(explain);
614
+        //console.log("e1:"+explain);
615
+
616
+        if (!explain || explain==""){
617
+            result=data;
618
+            result = result.substr(result.indexOf("id=\"baike-wrapper\""));
619
+            result = result.substr(result.indexOf("<div class=\"tab-content\">"));
620
+            result = result.substr(result.indexOf("<p>"));
621
+            explain=result.substring(0, result.indexOf("</p>")+4);
622
+
623
+            explain = stringUtils.ReplaceAllString(explain, "  ", "");
624
+            explain = stringUtils.ReplaceAllString(explain, "\n", "");
625
+            var href=explain.substring(explain.indexOf("<a "),explain.indexOf("</a>")+4);
626
+            explain = explain.replace(href,"");
627
+            //console.log("e2:"+explain);
628
+            obj.explain=explain;
629
+        }
630
+
631
+        var item = obj.explain;
632
+        item = stringUtils.ReplaceAllString(item, "<p>", "");
633
+        item = stringUtils.ReplaceAllString(item, "<span>", "");
634
+        item = stringUtils.ReplaceAllString(item, "</span>", "");
635
+        item = stringUtils.ReplaceAllString(item, "\r", "");
636
+        item = stringUtils.ReplaceAllString(item, "\n", "");
637
+
638
+        var arr0 = item.split("</p>");
639
+        arr0.pop();
640
+        obj.explain = arr0.join("\n");
641
+        //obj.explain+="</p>";
642
+        resultObject.push(obj);
643
+    }
644
+
645
+    return resultObject;
646
+}
647
+
648
+//得到词语解释
649
+function getExplainCiyu(data, pinyin) {
650
+    var resultObject = [];
651
+
652
+    var obj={};
653
+    obj.pinyin=pinyin;
654
+
655
+    var result=data;
656
+    result=result.substr(result.indexOf("<meta name=\"description\" content=\"")+34);
657
+    result=result.substr(0,result.indexOf("\">"));
658
+    obj.explain=result;
659
+
660
+    resultObject.push(obj);
661
+
662
+    return resultObject;
663
+}
664
+
665
+//得到组词
666
+function getCombineWords(data) {
667
+    var resultObject = [];
668
+    var result = data.substr(data.indexOf("<b class=\"title\" id=\"related_term\">相关组词</b>"));
669
+
670
+    var num1, num2;
671
+    var doWhileTime=Date.now();
672
+    do {
673
+        result = result.substr(result.indexOf("<a href="));
674
+        result = result.substr(result.indexOf(">") + 1);
675
+
676
+        var word = result.substring(0, result.indexOf("</a>"));
677
+        if (word)
678
+            resultObject.push(word);
679
+
680
+        num1 = result.indexOf("<a href=");
681
+        num2 = result.indexOf("</div>");
682
+        try {
683
+            stringUtils.loopBreaker(doWhileTime);
684
+        }
685
+        catch(err){
686
+            break;
687
+        }
688
+    }
689
+    while (num1 < num2);
690
+
691
+    if (resultObject[resultObject.length - 1] == "更多")
692
+        resultObject.splice(resultObject.length - 1, 1);
693
+    return resultObject;
694
+}
695
+
696
+//得到近反义词
697
+function getAntonymSynonym(data, wordType) {
698
+    var resultObject = [];
699
+
700
+    var result=data;
701
+    result=result.substr(result.indexOf(wordType+"义词\",\"isArray\":false"));
702
+    result=result.substr(result.indexOf("\"tag\":\"text\",\"text\":\"")+21);
703
+    result=result.substr(0,result.indexOf("\"}]}]}"));
704
+    resultObject=result.split(" ");
705
+
706
+    return resultObject;
707
+}
708
+
709
+//得到古诗
710
+
711
+function getSoundmark(data,word) {
712
+    var eng, ame,engSound,ameSound;
713
+    if (data.indexOf("<li>英") > 0) {
714
+        var result = data.substr(data.indexOf("<li>英<!-- -->") + 13);
715
+        eng = result.substring(0, result.indexOf("]")+1);
716
+        eng =eng.replace("&#x27;","'");
717
+        if (data.indexOf("<li>美")>=0) {
718
+            result = data.substr(data.indexOf("<li>美<!-- -->") + 13);
719
+            ame = result.substring(0, result.indexOf("]")+1);
720
+            ame =ame.replace("&#x27;","'");
721
+        }
722
+    }
723
+    if (eng && eng.indexOf("ml><html")>=0)
724
+        eng="";
725
+    if (ame && ame.indexOf("ml><html")>=0)
726
+        ame="";
727
+
728
+    var obj = {
729
+        Eng: eng,
730
+        EngSound:"https://sp0.baidu.com/-rM1hT4a2gU2pMbgoY3K/gettts?lan=uk&text="+word+"&spd=3&source=alading",
731
+        AmE: ame,
732
+        AmESound:"https://sp0.baidu.com/-rM1hT4a2gU2pMbgoY3K/gettts?lan=en&text="+word+"&spd=3&source=alading",
733
+    }
734
+    //console.log(result);
735
+    return obj;
736
+}
737
+
738
+//得到中文释义
739
+function getParaphrase(data) {
740
+    //console.log("data:"+data);
741
+    //console.log("result:"+result);
742
+    var num1, num2, paraphrase = [];
743
+    var doWhileTime3=Date.now();
744
+    var css="Mean_part__1Xi6p";
745
+    if (data.indexOf(css)>0){
746
+        do {
747
+            var result = data.substr(data.indexOf(css));
748
+            var doWhileTime2=Date.now();
749
+            do {
750
+                result = result.substr(result.indexOf("<li>"));
751
+                var POS="";
752
+                if (result.indexOf("<li><i>")==0){
753
+                    POS = result.substring(result.indexOf("<li><i>")+7, result.indexOf("</i>"));
754
+                    console.log("POS:"+POS);
755
+                    result = result.substr(result.indexOf("</i>"));
756
+                }
757
+
758
+                result = result.substr(result.indexOf("<div>"));
759
+                var num3, num4, arr = [];
760
+                var doWhileTime=Date.now();
761
+                do {
762
+                    result = result.substr(result.indexOf("<span>"));
763
+                    var str = result.substring(6, result.indexOf("</span>"));
764
+                    //console.log("str:"+str);
765
+                    if (str && str != " ") {
766
+                        str=str.replace("; ","");
767
+                        str=str.replace("<!-- -->","");
768
+                        str=str.replace("<!-- -->","");
769
+                        str=str.replace("<!-- -->","");
770
+                        str=str.replace("&lt;","<");
771
+                        str=str.replace("&gt;",">");
772
+                        arr.push(str);
773
+                        //console.log(str);
774
+                    }
775
+                    result = result.substr(result.indexOf("</span>"));
776
+                    num3 = result.indexOf("<span>");
777
+                    num4 = result.indexOf("</div>");
778
+
779
+                    try {
780
+                        stringUtils.loopBreaker(doWhileTime);
781
+                    }
782
+                    catch(err){
783
+                        break;
784
+                    }
785
+
786
+                }
787
+                while (num3 < num4);
788
+
789
+                var obj = {
790
+                    PartOfSpeech: POS,
791
+                    ParaphraseList: arr,
792
+                }
793
+                //console.log("obj:"+JSON.stringify(obj));
794
+                paraphrase.push(obj);
795
+
796
+                result = result.substr(result.indexOf("</li>")+5);
797
+
798
+                num1 = result.indexOf("</li>");
799
+                num2 = result.indexOf("</ul>");
800
+
801
+                try {
802
+                    stringUtils.loopBreaker(doWhileTime2);
803
+                }
804
+                catch(err){
805
+                    break;
806
+                }
807
+            }
808
+            while (num1 < num2);
809
+            //console.log(result);
810
+            data=result;
811
+
812
+            try {
813
+                stringUtils.loopBreaker(doWhileTime3);
814
+            }
815
+            catch(err){
816
+                break;
817
+            }
818
+        }
819
+        while (result.indexOf(css)>0);
820
+    }
821
+    else if (data.indexOf("Mean_mean__1ydUq")>0){
822
+        var result = data.substr(data.indexOf("Mean_title__2BwLF"));
823
+        result = result.substr(result.indexOf("<p>"));
824
+        var str = result.substring(3, result.indexOf("</p>"));
825
+        console.log("str:"+str);
826
+        var arr=[];
827
+        arr.push(str);
828
+
829
+        var obj = {
830
+            PartOfSpeech: "",
831
+            ParaphraseList: arr,
832
+        }
833
+        if (POS && POS != " ")
834
+            paraphrase.push(obj);
835
+
836
+    }
837
+
838
+
839
+    //console.log(paraphrase);
840
+    return paraphrase;
841
+}
842
+//得到爱词霸英文释义
843
+
844
+function getSentenceParaphrase(data) {
845
+    var result = data.substr(data.indexOf("<div class=\"in-base-top clearfix\" class=\"clearfix\">"));
846
+    //console.log(result);
847
+    result = result.substr(result.indexOf(">") + 1);
848
+    result = result.substr(result.indexOf(">") + 1);
849
+    result = result.substr(result.indexOf(">") + 1);
850
+    result = result.substr(result.indexOf(">") + 1);
851
+    //console.log(result);
852
+    result = result.substr(0, result.indexOf("</div>"));
853
+    if (result.indexOf("<div")>0)
854
+        result="";
855
+    //console.log(result);
856
+    return result;
857
+}
858
+
859
+
860
+//得到变形
861
+function getDeformation(data) {
862
+    var num1, num2, deformation = [];
863
+
864
+    if (data.indexOf("Morphology_morphology")>0) {
865
+
866
+        var result = data.substr(data.indexOf("词态变化"));
867
+        result = result.substr(result.indexOf("Morphology_morphology"));
868
+
869
+        var doWhileTime = Date.now();
870
+        do {
871
+            result = result.substr(result.indexOf("<li>"));
872
+            var key = result.substring(4, result.indexOf("<!-- -->:"));
873
+            result = result.substr(result.indexOf("<span class="));
874
+            result = result.substr(result.indexOf(">"));
875
+            var value = result.substring(1, result.indexOf("</span>"));
876
+            var obj = {
877
+                Key: key,
878
+                Value: value,
879
+            }
880
+            if (value && value != " ")
881
+                deformation.push(obj);
882
+            result = result.substr(result.indexOf("</li>") + 5);
883
+            num1 = result.indexOf("</li>");
884
+            num2 = result.indexOf("</ul>");
885
+
886
+            try {
887
+                stringUtils.loopBreaker(doWhileTime);
888
+            }
889
+            catch (err) {
890
+                break;
891
+            }
892
+
893
+        }
894
+        while (num1 < num2);
895
+    }
896
+    return deformation;
897
+}
898
+
899
+function getExamplesSentences(data) {
900
+    var result=[];
901
+    if (data.indexOf("<div id=\"examples_sentences") > 0) {
902
+        var str = data.substring(data.indexOf("<div id=\"examples_sentences"),data.indexOf("<div id=\"ads"));
903
+        var arr=[];
904
+        do{
905
+            var str1=str.substring(str.indexOf("<li>"),str.indexOf("</li>"));
906
+            var strQ=str1.substring(str1.indexOf("<p>")+3,str1.indexOf("<a class"));
907
+            var arrQ=strQ.split("</span>");
908
+
909
+            for(var i=0;i<arrQ.length;i++){
910
+                if (arrQ[i].indexOf("<span")>=0){
911
+                    arrQ[i]=arrQ[i].replace(arrQ[i].substr(arrQ[i].indexOf("<span"),arrQ[i].indexOf(">")+1),"");
912
+                }
913
+                else
914
+                    arrQ[i]="";
915
+            }
916
+            strQ=arrQ.join("");
917
+            strQ=stringUtils.ReplaceAllString(strQ,"<b>","[线]");
918
+            strQ=stringUtils.ReplaceAllString(strQ,"</b>","[/线]");
919
+
920
+            var strA=str1.substr(str1.indexOf("</p>")+4);
921
+            var strA=strA.substring(strA.indexOf("<p>")+3,strA.indexOf("</p>"));
922
+
923
+            var arrA=strA.split("</span>");
924
+            for(var i=0;i<arrA.length;i++){
925
+                if (arrA[i].indexOf("<span")>=0){
926
+                    arrA[i]=arrA[i].replace(arrA[i].substr(arrA[i].indexOf("<span"),arrA[i].indexOf(">")+1),"");
927
+                }
928
+                else
929
+                    arrA[i]="";
930
+            }
931
+            strA=arrA.join("");
932
+
933
+            arr.push([strQ,strA]);
934
+            str=str.substr(str.indexOf("</li>")+4);
935
+        }
936
+        while(str.indexOf("</li>")>=0);
937
+
938
+    }
939
+
940
+
941
+    //console.log(result);
942
+    return arr;
943
+}
944
+
945
+export async function GetWordFrequency(ctx) {
946
+    const word = ctx.query.Word || "";
947
+    const list = await miaoguo.RunSql([word], "select * from WordFrequency where Word=?;");
948
+    let id = 0;
949
+    if (list && list.length > 0)
950
+        id = list[0].FinallyNum;
951
+    ctx.body = { errcode: 10000, result: getWordFrequency(id) };
952
+}
953
+
954
+export async function buildTemp(ctx) {
955
+    ctx.body = { errcode: 10000 };
956
+}
957
+
422 958
 export async function GetMiaoguoAISearchWeb(ctx) {
423 959
     const param = ctx.request.body || {};
424 960
     param.UserID = ctx.query.UserID || 0;

文件差异内容过多而无法显示
+ 402 - 0
src/api/miaoguo/miaoguoController.js


+ 14 - 0
src/api/miaoguo/routes.js

@@ -6,6 +6,12 @@ import { uploadSingle } from '../../middleware/upload.js';
6 6
 const router = new Router();
7 7
 router.get('/api/GetMiaoguoAISearch',literacyController.GetMiaoguoAISearch);
8 8
 router.get('/api/GetMiaoguoAISearch2',literacyController.GetMiaoguoAISearch2);
9
+router.get('/api/GetMiaoguoAISearchTest',literacyController.GetMiaoguoAISearchTest);
10
+router.get('/api/GetLiteracyData',literacyController.GetLiteracyData);
11
+router.get('/api/GetWordData',literacyController.GetWordData);
12
+router.get('/api/GetWordFrequency',literacyController.GetWordFrequency);
13
+router.get('/api/GetYoudaoWordData',literacyController.GetYoudaoWordData);
14
+router.get('/api/buildTemp',literacyController.buildTemp);
9 15
 router.post('/api/MiaoguoLogin',miaoguoController.MiaoguoLogin);
10 16
 router.get('/api/GetClassList',miaoguoController.GetClassList);
11 17
 router.get('/api/GetMiaoguoCoupon',miaoguoController.GetMiaoguoCoupon);
@@ -33,8 +39,12 @@ router.get('/api/StartMiaoguoTask',miaoguoController.StartMiaoguoTask);
33 39
 router.get('/api/UpdateMiaoguoMilestoneInfo',miaoguoController.UpdateMiaoguoMilestoneInfo);
34 40
 router.get('/api/GetUserFolderList',miaoguoController.GetUserFolderList);
35 41
 router.get('/api/GetUserFolderInfo',miaoguoController.GetUserFolderInfo);
42
+router.get('/api/GetAccreditInfo',miaoguoController.GetAccreditInfo);
36 43
 router.get('/api/GetAccreditInfo2',miaoguoController.GetAccreditInfo2);
44
+router.get('/api/BuildData',miaoguoController.BuildData);
45
+router.get('/api/BuildInitData',miaoguoController.BuildInitData);
37 46
 router.get('/api/GetMiaoguoLesson2',miaoguoController.GetMiaoguoLesson2);
47
+router.get('/api/GetMiaoguoNewUserImageList',miaoguoController.GetMiaoguoNewUserImageList);
38 48
 router.get('/api/GetMiaoguoNewUserImageList3126',miaoguoController.GetMiaoguoNewUserImageList3126);
39 49
 router.get('/api/GetMiaoguoTipsList',miaoguoController.GetMiaoguoTipsList);
40 50
 router.post('/api/UpdateUserNickNameAndAvatar',miaoguoController.UpdateUserNickNameAndAvatar);
@@ -49,10 +59,12 @@ router.get('/api/SendMiaoguoCoupon',miaoguoController.SendMiaoguoCoupon);
49 59
 router.get('/api/UseMiaoguoCoupon',miaoguoController.UseMiaoguoCoupon);
50 60
 router.get('/api/ShareMiaoguoTask',miaoguoController.ShareMiaoguoTask);
51 61
 router.get('/api/GetIntroducerUserList',miaoguoController.GetIntroducerUserList);
62
+router.get('/api/GetMiaoguoShareInfo',miaoguoController.GetMiaoguoShareInfo);
52 63
 router.get('/api/GetMiaoguoShareInfo220',miaoguoController.GetMiaoguoShareInfo220);
53 64
 router.get('/api/UpdateUserConfigBasicCardLimit',miaoguoController.UpdateUserConfigBasicCardLimit);
54 65
 router.post('/api/UploadUserConfig',miaoguoController.UploadUserConfig);
55 66
 router.get('/api/DeleteAllMiaoguoCard',miaoguoController.DeleteAllMiaoguoCard);
67
+router.get('/api/updateMiaoguoTaskData',miaoguoController.updateMiaoguoTaskData);
56 68
 router.get('/api/updateMiaoguoFamily',miaoguoController.updateMiaoguoFamily);
57 69
 router.get('/api/GetMiaoguoCheckList',miaoguoController.GetMiaoguoCheckList);
58 70
 router.get('/api/GetAnnualReport',miaoguoController.GetAnnualReport);
@@ -87,7 +99,9 @@ router.get('/api/GetTestReportList',miaoguoController.GetTestReportList);
87 99
 router.get('/api/GetTestAncientPoetryList',miaoguoController.GetTestAncientPoetryList);
88 100
 router.get('/api/GetTestEnglishWords',miaoguoController.GetTestEnglishWords);
89 101
 router.get('/api/UpdateMiaoguoCardInfo',miaoguoController.UpdateMiaoguoCardInfo);
102
+router.get('/api/UpdateCardImageUrl',miaoguoController.UpdateCardImageUrl);
90 103
 router.post('/api/GetBiShunByKaitiUrl',miaoguoController.GetBiShunByKaitiUrl);
104
+router.get('/api/BuildBookTotal',miaoguoController.BuildBookTotal);
91 105
 router.get('/api/GetLatexList',literacyController.GetLatexList);
92 106
 router.get('/api/GetMiaoguoLiteracyListByID',literacyController.GetMiaoguoLiteracyListByID);
93 107
 router.get('/api/GetMiaoguoLiteracyListByWord',literacyController.GetMiaoguoLiteracyListByWord);

+ 24 - 62
src/api/pay/payController.js

@@ -12,6 +12,7 @@ import { stringUtils } from '../../util/stringClass.js';
12 12
 import config from '../../config/index.js';
13 13
 import { query } from '../../util/db.js';
14 14
 import * as payInfo from '../../model/payInfo.js';
15
+import * as users from '../../model/users.js';
15 16
 
16 17
 const __filename = fileURLToPath(import.meta.url);
17 18
 const __dirname = path.dirname(__filename);
@@ -198,13 +199,29 @@ export async function PayNotify(ctx) {
198 199
         const payResult = await payInfo.GetPayInfoListByOpenIDAndTradeNo(param3);
199 200
 
200 201
         if (payResult && payResult.length > 0) {
202
+            const userUpdate = {
203
+                OpenID: xml.openid,
204
+            };
201 205
             if (payResult[0].PayType == 1) {
202
-                // 打赏 - update user status
203
-                // Note: Users model not migrated yet, placeholder
204
-                console.log('PayType=1 - reward payment');
206
+                userUpdate.Status = 1;
207
+                await users.UpdateUsers(userUpdate);
205 208
             } else if (payResult[0].PayType == 3) {
206
-                // 购买会员
207
-                console.log('PayType=3 - membership purchase');
209
+                userUpdate.IsPay = 1;
210
+                userUpdate.IsMember = 1;
211
+
212
+                const userInfo = await users.GetUsersInfo(userUpdate);
213
+                if (userInfo && userInfo.length > 0) {
214
+                    const currentServiceTime = new Date(userInfo[0].ProductServiceTime).getTime();
215
+                    const now = new Date().getTime();
216
+                    if (currentServiceTime > now) {
217
+                        userUpdate.ProductServiceTime = moment(userInfo[0].ProductServiceTime).add(6, 'months').format('YYYY.MM.DD HH:mm:ss');
218
+                    } else {
219
+                        userUpdate.ProductServiceTime = moment().add(6, 'months').format('YYYY.MM.DD HH:mm:ss');
220
+                    }
221
+                } else {
222
+                    userUpdate.ProductServiceTime = moment().add(6, 'months').format('YYYY-MM-DD HH:mm:ss');
223
+                }
224
+                await users.UpdateUsers(userUpdate);
208 225
             } else if (payResult[0].PayType == 7) {
209 226
                 // 秒过购买
210 227
                 console.log('PayType=7 - miaoguo purchase');
@@ -223,63 +240,8 @@ export async function PayNotify(ctx) {
223 240
  * GET /api/PayUserRebate?UserID=xx&OpenID=xx&Money=xx&PayType=xx&Text=xx
224 241
  */
225 242
 export async function PayUserRebate(ctx) {
226
-    try {
227
-        const param = {
228
-            UserID: ctx.query.UserID,
229
-            OpenID: ctx.query.OpenID,
230
-            Money: ctx.query.Money,
231
-            PayType: ctx.query.PayType,
232
-            Text: ctx.query.Text,
233
-        };
234
-
235
-        if (!param.OpenID) {
236
-            ctx.body = { errcode: 102 };
237
-            return;
238
-        }
239
-
240
-        const money = param.Money;
241
-        const openid = param.OpenID;
242
-        const text = param.Text;
243
-        const xml = getPayUserRebateXML(ctx.req, openid, money, text);
244
-
245
-        console.log('xml:', xml);
246
-        const projectRoot = path.resolve(__dirname, '../../..'); // from src/api/pay/ to project root
247
-
248
-        try {
249
-            const parsedBody = await axios.post('https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers', xml, {
250
-                headers: { 'Content-Type': 'application/xml' },
251
-                httpsAgent: new (await import('https')).Agent({
252
-                    cert: fs.readFileSync(projectRoot + '/cert/apiclient_cert.pem', 'utf8'),
253
-                    key: fs.readFileSync(projectRoot + '/cert/apiclient_key.pem', 'utf8'),
254
-                    requestCert: true,
255
-                })
256
-            }).then(res => res.data);
257
-
258
-            console.log('returnXML:', parsedBody);
259
-            const resultStr = getPayUserReturn(parsedBody, openid);
260
-            if (resultStr.status === 'SUCCESS') {
261
-                const param2 = {
262
-                    TradeNo: resultStr.partner_trade_no,
263
-                    PayType: param.PayType,
264
-                    CreateTime: moment().format('YYYY.MM.DD HH:mm:ss'),
265
-                    UserID: param.UserID,
266
-                    OpenID: param.OpenID,
267
-                    Body: param.Text,
268
-                    Money: param.Money,
269
-                    Status: 1,
270
-                    XMLPay: resultStr.xml,
271
-                };
272
-                await payInfo.AddPaybackInfo(param2);
273
-            }
274
-
275
-            ctx.body = { errcode: 10000, result: resultStr };
276
-        } catch (err) {
277
-            ctx.body = { errcode: 102, errStr: err.message };
278
-        }
279
-    } catch (err) {
280
-        console.error('PayUserRebate error:', err);
281
-        ctx.throw(500, err.message);
282
-    }
243
+    ctx.status = 410;
244
+    ctx.body = { errcode: 10410, errStr: '企业付款接口已废弃' };
283 245
 }
284 246
 
285 247
 /**

文件差异内容过多而无法显示
+ 837 - 123
src/api/pay/payMiaoguoController.js


+ 769 - 11
src/api/pay/productPayController.js

@@ -637,6 +637,112 @@ export async function ProductPayLogin500(ctx) {
637 637
     }
638 638
 }
639 639
 
640
+function getLegacyPayBody(productID, payType, legacyKind) {
641
+    if (payType == 1 || payType == 2) return '';
642
+    if (payType == 3) {
643
+        if (productID == 98) return '语文汉语拼音-购买';
644
+        if (productID == 99) return '英语自然拼读-购买';
645
+        if (productID == 106) return '语文识字-购买';
646
+    }
647
+    if (payType == 4) {
648
+        if (legacyKind === 'pinyin') return '套餐购买';
649
+        if (legacyKind === 'phonics') return '';
650
+        if (productID == 98) return '拼音-套餐购买';
651
+        if (productID == 99) return '自然拼读-套餐购买';
652
+        if (productID == 106) return '识字-套餐购买';
653
+    }
654
+    if (payType == 5) {
655
+        if (legacyKind === 'pinyin') return '拼音兑换卡购买';
656
+        if (legacyKind === 'phonics') return '';
657
+        if (productID == 98) return '拼音-兑换卡';
658
+        if (productID == 99) return '自然拼读-兑换卡';
659
+        if (productID == 106) return '识字-兑换卡';
660
+    }
661
+    if (payType == 6) return '四产品永久购买';
662
+    return '';
663
+}
664
+
665
+async function legacyProductPayLogin(ctx, defaults) {
666
+    const code = ctx.query.code;
667
+    const money = ctx.query.money || defaults.money;
668
+    const payType = ctx.query.payType || 1;
669
+    const productID = Number(ctx.query.productID || defaults.productID);
670
+    const detail = ctx.query.detail || '';
671
+    const appid = getAppId(productID);
672
+    const appsecret = getAppSecret(productID);
673
+    const tradeNo = new Date().getTime() + stringUtils.Random(0, 1000);
674
+
675
+    try {
676
+        const sessionResult = parseMaybeJson(await axios.get(
677
+            'https://api.weixin.qq.com/sns/jscode2session?appid=' + appid + '&secret=' + appsecret
678
+                + '&js_code=' + code + '&grant_type=authorization_code'
679
+        ).then(res => res.data));
680
+        let payDetail = '';
681
+        if (payType == 3) payDetail = '产品购买' + money;
682
+        else if (defaults.legacyKind === 'hanzi' && (payType == 4 || payType == 5 || payType == 6)) payDetail = detail;
683
+        const payParam = {
684
+            appid,
685
+            attach: '',
686
+            body: getLegacyPayBody(productID, payType, defaults.legacyKind),
687
+            detail: payDetail,
688
+            device_info: 'WEB',
689
+            fee_type: 'CNY',
690
+            goods_tag: '',
691
+            limit_pay: 'no_credit',
692
+            mch_id: config.wx.mch_id,
693
+            nonce_str: getRandomStr(productID),
694
+            notify_url: 'https://www.kylx365.com/api/' + defaults.notifyName,
695
+            openid: sessionResult.openid,
696
+            out_trade_no: tradeNo,
697
+            sign_type: 'MD5',
698
+            spbill_create_ip: stringUtils.GetClientIP(ctx.req),
699
+            time_expire: moment().add(10, 'm').format('YYYYMMDDHHmmss'),
700
+            time_start: moment().format('YYYYMMDDHHmmss'),
701
+            total_fee: Math.round(Number(money) * 100),
702
+            trade_type: 'JSAPI',
703
+        };
704
+        payParam.sign = getSign(payParam);
705
+        const xml = stringUtils.ChangeXML(payParam);
706
+        await addPayInfoAll({
707
+            TradeNo: tradeNo,
708
+            PayType: payType,
709
+            BuyType: null,
710
+            CreateTime: new Date(),
711
+            OpenID: sessionResult.openid,
712
+            Body: payParam.body,
713
+            Status: 0,
714
+            Money: payParam.total_fee,
715
+            XMLPre: xml,
716
+            ProductID: productID,
717
+            Remark: null,
718
+        });
719
+        const parsedBody = await axios.post('https://api.mch.weixin.qq.com/pay/unifiedorder', xml, {
720
+            headers: { 'Content-Type': 'text/xml' },
721
+        }).then(res => res.data);
722
+        const result = getReturn(parsedBody, productID);
723
+        result.TradeNo = tradeNo;
724
+        ctx.body = { errcode: 10000, result };
725
+    } catch (err) {
726
+        ctx.body = { errcode: 101, errStr: err.message || err };
727
+    }
728
+}
729
+
730
+export async function PinyinPayLogin(ctx) {
731
+    return legacyProductPayLogin(ctx, { productID: 98, money: 9.9, notifyName: 'PinyinPayNotify', legacyKind: 'pinyin' });
732
+}
733
+
734
+export async function PhonicsPayLogin(ctx) {
735
+    return legacyProductPayLogin(ctx, { productID: 99, money: 19.8, notifyName: 'PhonicsPayNotify', legacyKind: 'phonics' });
736
+}
737
+
738
+export async function HanziPayLogin(ctx) {
739
+    return legacyProductPayLogin(ctx, { productID: 106, money: 9.8, notifyName: 'HanziPayNotify', legacyKind: 'hanzi' });
740
+}
741
+
742
+export async function ProductPayLogin(ctx) {
743
+    return HanziPayLogin(ctx);
744
+}
745
+
640 746
 export async function ProductPayLoginWeb(ctx) {
641 747
     const param = ctx.request.body || {};
642 748
     const code = param.code;
@@ -817,6 +923,37 @@ export async function ProductPayNotify500(ctx) {
817 923
 
818 924
                 await updateMiaoguoCoupon({ UserID: userList[0].UserID, CouponType: 132, IsUse: 1 });
819 925
                 await updateMiaoguoCoupon({ UserID: userList[0].UserID, CouponType: 133, IsUse: 1 });
926
+
927
+                const messageParam = {
928
+                    Price: Math.floor(payResult[0].Money / 100) + '.00',
929
+                };
930
+                if (detailJson && detailJson.PayType) {
931
+                    messageParam.PayType = detailJson.PayType;
932
+                }
933
+                const serviceUsers = await getWechatServiceInfoByMGUserID(userList[0]);
934
+                await axios.get('http://localhost:' + config.port + '/api/SendWXServiceTemplateMessage', {
935
+                    params: {
936
+                        UserID: serviceUsers[0].UserID,
937
+                        TemplateID: 'PayFinished',
938
+                        ParamStr: JSON.stringify(messageParam),
939
+                    },
940
+                });
941
+
942
+                // 保留旧回调中的班级日期修正逻辑和一秒延迟。
943
+                query('SELECT * FROM kylx365_db.MiaoguoClassSchedule where Flag=0 order by ClassID limit 1').then((classInfo) => {
944
+                    const startTime = moment(classInfo[0].StartDate).format('YYYY-MM-DD 00:00:00');
945
+                    const sevenDay = moment(startTime).add(6, 'days').format('YYYY-MM-DD 23:59:59');
946
+                    const oneYear = moment(startTime).add(6, 'days').add(1, 'years').add(30, 'days').format('YYYY-MM-DD 23:59:59');
947
+                    const classID = classInfo[0].ClassID;
948
+                    setTimeout(() => {
949
+                        query("update MiaoguoWXUsers set ProductServiceTime=? where ClassID=? and IsPay=1 and PayTime<?", [oneYear, classID, sevenDay]);
950
+                    }, 1000);
951
+                });
952
+
953
+                await query('update ProductPayInfo p,MiaoguoWXUsers u set p.UserID=u.UserID where p.OpenID=u.OpenID and p.UserID=0 and p.ProductID=166');
954
+                await query("update MiaoguoWXUsers u,(select OpenID,Max(ProductServiceTime) as 'ProductServiceTime' from ProductPayInfo where ProductID=166 and Status=1 and Money>100 group by OpenID) p set u.ProductServiceTime=p.ProductServiceTime where p.OpenID=u.OpenID and (u.ProductServiceTime='0000-00-00 00:00:00' or u.ProductServiceTime<p.ProductServiceTime)");
955
+
956
+                await axios.get('http://localhost:' + config.port + '/api/BuildStatisticsShareUserPay');
820 957
             } else {
821 958
                 param2.IsApply = 1;
822 959
                 await updateMiaoguoUsers(param2);
@@ -842,6 +979,168 @@ export async function ProductPayNotify500(ctx) {
842 979
     ctx.body = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
843 980
 }
844 981
 
982
+export async function PinyinPayNotify(ctx) {
983
+    const xml = ctx.request.body.xml;
984
+    const payEndTime = new Date();
985
+    const userList = await getUsersInfoByOpenID(98, { OpenID: xml.openid });
986
+    const createDay = moment(userList[0].CreateTime).format('YYYYMMDD');
987
+    const payDay = moment(payEndTime).format('YYYYMMDD');
988
+    const today = moment().format('YYYYMMDD');
989
+    const oldServiceDay = moment(userList[0].ProductServiceTime).format('YYYYMMDD');
990
+    let productServiceTime = moment().add(6, 'months').format('YYYY-MM-DD HH:mm:ss');
991
+    let buyType = 110;
992
+    if (createDay == payDay) {
993
+        productServiceTime = moment().add(6, 'months').format('YYYY-MM-DD HH:mm:ss');
994
+        buyType = 112;
995
+    } else if (today < oldServiceDay) {
996
+        productServiceTime = moment(userList[0].ProductServiceTime).add(6, 'months').format('YYYY-MM-DD HH:mm:ss');
997
+    }
998
+    const orderParam = {
999
+        OpenID: xml.openid,
1000
+        TradeNo: xml.out_trade_no,
1001
+        PayEndTime: payEndTime,
1002
+        XMLPay: JSON.stringify(xml),
1003
+        Status: 1,
1004
+        ProductServiceTime: productServiceTime,
1005
+    };
1006
+    await updatePayInfoAll(orderParam);
1007
+    const payResult = await getPayInfoListByOpenIDAndTradeNoAll(orderParam);
1008
+    if (payResult && payResult.length > 0) {
1009
+        if (payResult[0].PayType == 3) {
1010
+            await updateUsersByOpenID(98, { OpenID: xml.openid, IsPay: 1, IsMember: 1, ProductServiceTime: productServiceTime });
1011
+            const buyInfo = await getProductBuyInfoListByProductPayInfoID(payResult[0]);
1012
+            if (!buyInfo || buyInfo.length == 0) {
1013
+                await addProductBuyInfo({ ProductPayInfoID: payResult[0].ID, ProductID: payResult[0].ProductID, Money: payResult[0].Money, BuyType: buyType, Status: 1 });
1014
+            }
1015
+        } else if (payResult[0].PayType == 4) {
1016
+            const buyInfo = await getProductBuyInfoListByProductPayInfoID(payResult[0]);
1017
+            if (!buyInfo || buyInfo.length == 0) {
1018
+                let detail = payResult[0].XMLPre;
1019
+                detail = detail.substring(detail.indexOf('<detail>') + 8, detail.indexOf('</detail>'));
1020
+                const arrDetail = detail.split(',');
1021
+                for (const detailItem of arrDetail) {
1022
+                    const item = detailItem.split(':');
1023
+                    buyType = arrDetail.length == 1 ? 110 : 109;
1024
+                    const status = Number(item[0]) == payResult[0].ProductID ? 1 : 0;
1025
+                    await addProductBuyInfo({ ProductPayInfoID: payResult[0].ID, ProductID: Number(item[0]), Money: Number(item[1]), BuyType: buyType, Status: status });
1026
+                    if (Number(item[0]) == payResult[0].ProductID) {
1027
+                        await updateUsersByOpenID(98, {
1028
+                            OpenID: xml.openid,
1029
+                            IsPay: 1,
1030
+                            IsMember: 1,
1031
+                            ProductServiceTime: arrDetail.length == 1 ? productServiceTime : moment().add(6, 'months').format('YYYY-MM-DD HH:mm:ss'),
1032
+                        });
1033
+                    }
1034
+                }
1035
+            }
1036
+        }
1037
+    }
1038
+    ctx.body = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
1039
+}
1040
+
1041
+export async function PhonicsPayNotify(ctx) {
1042
+    const xml = ctx.request.body.xml;
1043
+    const orderParam = {
1044
+        OpenID: xml.openid,
1045
+        TradeNo: xml.out_trade_no,
1046
+        PayEndTime: new Date(),
1047
+        XMLPay: JSON.stringify(xml),
1048
+        Status: 1,
1049
+    };
1050
+    await updatePayInfoAll(orderParam);
1051
+    const payResult = await getPayInfoListByOpenIDAndTradeNoAll(orderParam);
1052
+    if (payResult && payResult.length > 0) {
1053
+        let productServiceTime;
1054
+        if (payResult[0].PayType == 3) {
1055
+            productServiceTime = FINAL_TIME;
1056
+            await updateUsersByOpenID(99, { OpenID: xml.openid, IsPay: 1, IsMember: 1, ProductServiceTime: productServiceTime });
1057
+        }
1058
+        const buyInfo = await getProductBuyInfoListByProductPayInfoID(payResult[0]);
1059
+        if (!buyInfo || buyInfo.length == 0) {
1060
+            await addProductBuyInfo({
1061
+                ProductPayInfoID: payResult[0].ID,
1062
+                ProductID: payResult[0].ProductID,
1063
+                Money: payResult[0].Money,
1064
+                BuyType: productServiceTime >= FINAL_TIME ? 111 : 110,
1065
+                Status: 1,
1066
+            });
1067
+        }
1068
+    }
1069
+    ctx.body = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
1070
+}
1071
+
1072
+export async function HanziPayNotify(ctx) {
1073
+    const xml = ctx.request.body.xml;
1074
+    const orderKey = { OpenID: xml.openid, TradeNo: xml.out_trade_no };
1075
+    const payResult = await getPayInfoListByOpenIDAndTradeNoAll(orderKey);
1076
+    if (payResult && payResult.length > 0) {
1077
+        const productID = payResult[0].ProductID;
1078
+        const userList = await getUsersInfoByOpenID(productID, orderKey);
1079
+        const payEndTime = new Date();
1080
+        const createDay = moment(userList[0].CreateTime).format('YYYYMMDD');
1081
+        const payDay = moment(payEndTime).format('YYYYMMDD');
1082
+        const today = moment().format('YYYYMMDD');
1083
+        const oldServiceDay = moment(userList[0].ProductServiceTime).format('YYYYMMDD');
1084
+        let productServiceTime = moment().add(6, 'months').format('YYYY-MM-DD HH:mm:ss');
1085
+        let buyType = 110;
1086
+        if (createDay == payDay) {
1087
+            productServiceTime = moment().add(7, 'months').format('YYYY-MM-DD HH:mm:ss');
1088
+            buyType = 112;
1089
+        } else if (today < oldServiceDay) {
1090
+            productServiceTime = moment(userList[0].ProductServiceTime).add(6, 'months').format('YYYY-MM-DD HH:mm:ss');
1091
+        }
1092
+        await updatePayInfoAll({
1093
+            ...orderKey,
1094
+            PayEndTime: payEndTime,
1095
+            XMLPay: JSON.stringify(xml),
1096
+            Status: 1,
1097
+            ProductServiceTime: productServiceTime,
1098
+        });
1099
+
1100
+        if (payResult[0].PayType == 3) {
1101
+            await updateUsersByOpenID(productID, { OpenID: xml.openid, IsPay: 1, IsMember: 1, ProductServiceTime: productServiceTime });
1102
+            const buyInfo = await getProductBuyInfoListByProductPayInfoID(payResult[0]);
1103
+            if (!buyInfo || buyInfo.length == 0) {
1104
+                await addProductBuyInfo({ ProductPayInfoID: payResult[0].ID, ProductID: productID, Money: payResult[0].Money, BuyType: buyType, Status: 1 });
1105
+            }
1106
+        } else if (payResult[0].PayType == 4 || payResult[0].PayType == 6) {
1107
+            const buyInfo = await getProductBuyInfoListByProductPayInfoID(payResult[0]);
1108
+            if (!buyInfo || buyInfo.length == 0) {
1109
+                let detail = payResult[0].XMLPre;
1110
+                detail = detail.substring(detail.indexOf('<detail>') + 8, detail.indexOf('</detail>'));
1111
+                const arrDetail = detail.split(',');
1112
+                for (const detailItem of arrDetail) {
1113
+                    const item = detailItem.split(':');
1114
+                    buyType = payResult[0].PayType == 6 ? 111 : (arrDetail.length == 1 ? 110 : 109);
1115
+                    const itemProductID = Number(item[0]);
1116
+                    await addProductBuyInfo({
1117
+                        ProductPayInfoID: payResult[0].ID,
1118
+                        ProductID: itemProductID,
1119
+                        Money: Number(item[1]),
1120
+                        BuyType: buyType,
1121
+                        Status: itemProductID == productID ? 1 : 0,
1122
+                    });
1123
+                    if (itemProductID == productID) {
1124
+                        await updateUsersByOpenID(productID, {
1125
+                            OpenID: xml.openid,
1126
+                            IsPay: 1,
1127
+                            IsMember: 1,
1128
+                            ProductServiceTime: payResult[0].PayType == 6
1129
+                                ? FINAL_TIME
1130
+                                : (arrDetail.length == 1 ? productServiceTime : moment().add(6, 'months').format('YYYY-MM-DD HH:mm:ss')),
1131
+                        });
1132
+                    }
1133
+                }
1134
+            }
1135
+        }
1136
+    }
1137
+    ctx.body = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
1138
+}
1139
+
1140
+export async function ProductPayNotify(ctx) {
1141
+    return HanziPayNotify(ctx);
1142
+}
1143
+
845 1144
 export async function getAuthorization(ctx) {
846 1145
     const result = getTencentAuthorization(config.QCloud.SecretId, config.QCloud.SecretKey);
847 1146
     ctx.body = { errcode: 10000, result };
@@ -1037,6 +1336,465 @@ export async function SendWXServiceMessage(ctx) {
1037 1336
     }
1038 1337
 }
1039 1338
 
1339
+export async function GetWXServiceUsers(ctx) {
1340
+    try {
1341
+        const accessToken = await getWechatServiceAccessToken();
1342
+        const result = parseMaybeJson(await axios.get(
1343
+            'https://api.weixin.qq.com/cgi-bin/user/get?access_token=' + accessToken
1344
+        ).then(res => res.data));
1345
+        if (!result || !result.data || !Array.isArray(result.data.openid)) {
1346
+            const error = new Error('wechatservice user list failed');
1347
+            error.result = result;
1348
+            throw error;
1349
+        }
1350
+        const openIDs = result.data.openid;
1351
+        const userList = await query('SELECT * FROM WechatServiceWXUsers');
1352
+        const databaseOpenIDs = new Set(userList.map(item => item.OpenID));
1353
+        const subscribedOpenIDs = new Set(openIDs);
1354
+
1355
+        const newOpenIDs = openIDs.filter(openID => !databaseOpenIDs.has(openID));
1356
+        for (const openID of newOpenIDs) {
1357
+            const userResult = parseMaybeJson(await axios.get(
1358
+                'https://api.weixin.qq.com/cgi-bin/user/info?access_token=' + accessToken
1359
+                    + '&openid=' + openID + '&lang=zh_CN'
1360
+            ).then(res => res.data));
1361
+            await addWechatServiceUsers({
1362
+                Subscribe: userResult.subscribe,
1363
+                OpenID: userResult.openid,
1364
+                UnionID: userResult.unionid,
1365
+                NickName: userResult.nickname,
1366
+                Country: userResult.country,
1367
+                Province: userResult.province,
1368
+                City: userResult.city,
1369
+                Gender: userResult.sex,
1370
+                Language: userResult.language,
1371
+                AvatarUrl: userResult.headimgurl,
1372
+                CreateTime: moment(new Date(parseInt(userResult.subscribe_time) * 1000)).format('YYYY-MM-DD HH:mm:ss'),
1373
+                Flag: 0,
1374
+            });
1375
+            await new Promise(resolve => setTimeout(resolve, 400));
1376
+        }
1377
+
1378
+        const unsubscribedOpenIDs = userList
1379
+            .map(item => item.OpenID)
1380
+            .filter(openID => !subscribedOpenIDs.has(openID));
1381
+        for (const openID of unsubscribedOpenIDs) {
1382
+            await updateWechatServiceUsers({ Subscribe: 0, OpenID: openID });
1383
+            await new Promise(resolve => setTimeout(resolve, 400));
1384
+        }
1385
+        ctx.body = { errcode: 10000 };
1386
+    } catch (err) {
1387
+        ctx.body = { errcode: 101, errStr: err.result || err.message || err };
1388
+    }
1389
+}
1390
+
1391
+function parseTemplateParam(paramStr) {
1392
+    if (!paramStr) return {};
1393
+    try {
1394
+        return typeof paramStr === 'string' ? JSON.parse(paramStr) : paramStr;
1395
+    } catch {
1396
+        return {};
1397
+    }
1398
+}
1399
+
1400
+async function buildWXServiceTemplateMessage(templateID, userInfo, paramStr, rawParamStr) {
1401
+    const miniProgram = {
1402
+        appid: config.wx.miaoguo_appid,
1403
+        pagepath: 'pages/index/index?type=userbook',
1404
+    };
1405
+    const nowText = moment().format('YYYY年MM月DD日 HH:mm');
1406
+    switch (templateID) {
1407
+        case 'ClassStart':
1408
+            return {
1409
+                template_id: 'nwzBGhVAaBzFYW9k2NAwjKolcw6Ui-fNDz_3z3iNZWc',
1410
+                miniprogram: miniProgram,
1411
+                data: {
1412
+                    userName: { value: userInfo.NickName },
1413
+                    courseName: { value: '「秒过学习」新手班体验' },
1414
+                    date: { value: moment().format('YYYY-MM-DD') },
1415
+                    remark: { value: '\n体验「秒过」的步骤:\n① 点开通知或在秒过「手册」栏目收看「课程」,有简要版和完整版可供选择。看完简要版即能上手操作。\n② 使用问题请在「秒过班群」@暖暖爸答疑。\n③ 完成作业清单,最高可得新手好礼20天。\n\n欢迎微信咨询班务员「天乐」。' },
1416
+                },
1417
+            };
1418
+        case 'ClassStart2':
1419
+            return {
1420
+                template_id: '5Xokjk_dfg5WBxZWZPfJn8KUwHgMl7RW10Bm6U5Pzbc',
1421
+                miniprogram: miniProgram,
1422
+                data: {
1423
+                    first: { value: '进度提醒' },
1424
+                    keyword1: { value: '「秒过学习」新手班7天体验' },
1425
+                    keyword2: { value: '第2天' },
1426
+                    keyword3: { value: '还剩5天体验时间' },
1427
+                    remark: { value: '\n您报名的「秒过学习」新手班昨天开班了,您体验地还顺利吗?\n\n如您本周没有充足时间,请微信联系班务员「天乐」,可换班次时间。\n\n如您今晚开始体验,步骤如下:\n① 点开通知或在秒过「手册」栏目收看课程,看完简要版就能入门。\n② 使用问题请在「秒过班群」@暖暖爸答疑。\n\n欢迎微信联系班务员「天乐」。' },
1428
+                },
1429
+            };
1430
+        case 'LessonRemind': {
1431
+            const start = moment().format('YYYY年MM月DD日');
1432
+            const end = moment().add(3, 'days').format('YYYY年MM月DD日');
1433
+            return {
1434
+                template_id: '5Xokjk_dfg5WBxZWZPfJn8KUwHgMl7RW10Bm6U5Pzbc',
1435
+                miniprogram: { appid: config.wx.miaoguo_appid, pagepath: `pages/index/index?type=activity&ActivityID=2&EarlyBirdTimeStart=${start}&EarlyBirdTimeEnd=${end}` },
1436
+                data: {
1437
+                    first: { value: '会员优惠活动' },
1438
+                    keyword1: { value: '「秒过学习」新手班7天体验' },
1439
+                    keyword2: { value: '第4天' },
1440
+                    keyword3: { value: '还剩3天体验时间' },
1441
+                    remark: { value: '今天是您报名的「秒过学习」新手班第4天。如您还没体验,请微信联系班务员「天乐」了解快速入门方法。\n\n「新手班限时优惠」今天开始了。现55折199元/年(原365元),还有加送30天有效期等多项福利内容。限时4天,周日结束。\n\n点开通知查阅活动详情,或微信咨询客服「天乐」。' },
1442
+                },
1443
+            };
1444
+        }
1445
+        case 'MembershipDueRemind':
1446
+            if (userInfo.IsPay != 0) return null;
1447
+            return {
1448
+                template_id: 'OA33wcyJlkBRbH8N18D1a5DW5FXolg9rYsNKBgBqr7A',
1449
+                miniprogram: { appid: config.wx.miaoguo_appid, pagepath: `pages/index/index?type=activity&ActivityID=2&EarlyBirdTimeStart=${moment().add(-3, 'days').format('YYYY年MM月DD日')}&EarlyBirdTimeEnd=${moment().format('YYYY年MM月DD日')}` },
1450
+                data: {
1451
+                    first: { value: '秒过新手班即将结业,限时优惠今晚结束' },
1452
+                    keyword1: { value: moment().add(-6, 'days').format('YYYY-MM-DD') },
1453
+                    keyword2: { value: moment().format('YYYY-MM-DD') },
1454
+                    remark: { value: '您报名的「秒过」新手班将于今晚结束。\n\n限时优惠已到最后一天,现55折199元/年(原365元),还有加送30天有效期等多项福利内容。\n\n如您需要重新体验一次,请微信联系班务员「天乐」为您安排。\n\n点开通知查阅活动详情,或微信咨询客服「天乐」。感谢您的支持。' },
1455
+                },
1456
+            };
1457
+
1458
+        /*
1459
+         * 旧系统中以下模板已被注释停用。代码已转换为当前 Node.js 24
1460
+         * 的消息构造格式,保留在这里供将来按业务需要逐项解除注释。
1461
+         *
1462
+        case 'Lesson':
1463
+            return {
1464
+                template_id: 'yQ0d4Tkyq5AhTEfepIjZnA1d5BLMs0N6Vz1Xcef4aCM',
1465
+                miniprogram: miniProgram,
1466
+                data: {
1467
+                    first: { value: '秒过新手培训补充教材发放' },
1468
+                    keyword1: { value: '秒过学习法新手培训班' },
1469
+                    keyword2: { value: moment().format('YYYY-MM-DD') },
1470
+                    keyword3: { value: '第5天' },
1471
+                    keyword4: { value: '详见下文' },
1472
+                    remark: { value: '新手培训时间已过半,您的体验还顺利吗?补充教材——「课外课」6篇已发放。\n\n点开通知或在秒过「手册」栏目里查看。' },
1473
+                },
1474
+            };
1475
+
1476
+        case 'LimitTimePresent':
1477
+            return {
1478
+                template_id: 'yQ0d4Tkyq5AhTEfepIjZnA1d5BLMs0N6Vz1Xcef4aCM',
1479
+                miniprogram: {
1480
+                    appid: config.wx.miaoguo_appid,
1481
+                    pagepath: `pages/index/index?type=activity&ActivityID=2&EarlyBirdTimeStart=${moment().format('YYYY年MM月DD日')}&EarlyBirdTimeEnd=${moment().add(3, 'days').format('YYYY年MM月DD日')}`,
1482
+                },
1483
+                data: {
1484
+                    first: { value: '限时好礼55折优惠' },
1485
+                    keyword1: { value: '秒过学习法新手培训班' },
1486
+                    keyword2: { value: moment().format('YYYY-MM-DD') },
1487
+                    keyword3: { value: '第4天' },
1488
+                    keyword4: { value: '详见下文' },
1489
+                    remark: { value: '续费55折优惠,再送30天,优惠只此4天。\n\n快去领券!点开通知或在秒过「手册」栏目里查看。' },
1490
+                },
1491
+            };
1492
+
1493
+        case 'LimitTimePresendNoUse':
1494
+            if (userInfo.IsPay != 0) return null;
1495
+            return {
1496
+                template_id: 'BU9jbChM6XFP4PLUg_kgH3vowO4y61RqhRiiSuOZNY4',
1497
+                miniprogram: {
1498
+                    appid: config.wx.miaoguo_appid,
1499
+                    pagepath: `pages/index/index?type=activity&ActivityID=2&EarlyBirdTimeStart=${moment().add(-3, 'days').format('YYYY年MM月DD日')}&EarlyBirdTimeEnd=${moment().format('YYYY年MM月DD日')}`,
1500
+                },
1501
+                data: {
1502
+                    first: { value: '您有一张优惠券即将过期' },
1503
+                    keyword1: { value: '限时好礼' },
1504
+                    keyword2: { value: moment().format('YYYY-MM-DD') },
1505
+                    keyword3: { value: '尚未使用' },
1506
+                    remark: { value: '您领取的“限时好礼”优惠券今天到期,请尽快使用。55折最大优惠力度,续费再送30天,到手价每月仅需15元。点开通知或在“用户手册”查看活动详情可续费。\n\n今天是新手包最后一天,明天小程序将停止服务。' },
1507
+                },
1508
+            };
1509
+
1510
+        case 'ClassFinished':
1511
+            return {
1512
+                template_id: 'yQ0d4Tkyq5AhTEfepIjZnA1d5BLMs0N6Vz1Xcef4aCM',
1513
+                miniprogram: { appid: config.wx.miaoguo_appid, pagepath: 'pages/index/index?type=lesson&LessonID=5' },
1514
+                data: {
1515
+                    first: { value: '结业致辞' },
1516
+                    keyword1: { value: '秒过学习法新手培训班' },
1517
+                    keyword2: { value: moment().format('YYYY-MM-DD') },
1518
+                    keyword3: { value: '第7天' },
1519
+                    keyword4: { value: '详见下文' },
1520
+                    remark: { value: '新手培训今天结束。非常荣幸陪伴您度过一段学习时光,为感谢您的参与,主讲人暖暖爸特准备了一篇致辞,不妨一听。祝您和孩子学业有成事业昌隆。再见!\n\n点开通知或在秒过「手册」栏目里查看。' },
1521
+                },
1522
+            };
1523
+
1524
+        case 'ReturnPresent':
1525
+            if (userInfo.IsPay != 0) return null;
1526
+            return {
1527
+                template_id: 'OA33wcyJlkBRbH8N18D1a5DW5FXolg9rYsNKBgBqr7A',
1528
+                miniprogram: miniProgram,
1529
+                data: {
1530
+                    first: { value: '返场好礼进行中,第二次优惠机会' },
1531
+                    keyword1: { value: moment().add(-8, 'days').format('YYYY-MM-DD') },
1532
+                    keyword2: { value: moment().add(-1, 'days').format('YYYY-MM-DD') },
1533
+                    remark: { value: '《秒过学习法》新手包已于昨天结束。想要续搬或其它要求,请与班务员天乐联系。\n\n“返场好礼”进行中——年费7折,到手价258元。\n\n点开通知或在“用户手册”查看。' },
1534
+                },
1535
+            };
1536
+
1537
+        case 'PopularizationPlan':
1538
+            return {
1539
+                template_id: '_vDVcvmJEFxMl2MKWYWUwuFf5KEOFnTf5SiCy4uSyXc',
1540
+                miniprogram: { appid: config.wx.miaoguo_appid, pagepath: 'pages/index/index?type=marketing' },
1541
+                data: {
1542
+                    first: { value: '' },
1543
+                    keyword1: { value: '秒过推广计划' },
1544
+                    keyword2: { value: '详见下文' },
1545
+                    remark: { value: '\n秒过推出了有奖《推广计划》。\n暖暖爸一口气拿出10年有效期邀请大家来瓜分,总共3650天。\n推广中,您的朋友报名或续费都有礼物拿。礼物轻,情意重,天乐衷心感谢大家的厚爱!\n\n点详情打开推广计划', color: '#329468' },
1546
+                },
1547
+            };
1548
+        */
1549
+
1550
+        case 'SharePlan':
1551
+            if (!(userInfo.ClassID > 0)) return null;
1552
+            return {
1553
+                template_id: 'BU9jbChM6XFP4PLUg_kgH3vowO4y61RqhRiiSuOZNY4',
1554
+                url: 'https://mp.weixin.qq.com/s/0StaCd5ePjiIT4Pn2zD7oQ',
1555
+                data: {
1556
+                    first: { value: '免费用N年官方攻略' },
1557
+                    keyword1: { value: '天天接力,顺便推广' },
1558
+                    keyword2: { value: moment().format('YYYY年') },
1559
+                    keyword3: { value: '进行中' },
1560
+                    remark: { value: '秒过正在同时开展「勤勉接力奖」和「秒过推广计划」两项奖励活动。边练习边参与,所有续费用户都有机会轻松获得上不封顶的有效期奖励,很多家长已累计免费时间超过4年了。\n\n怎样拿到N年奖励,点击本通知查看详细攻略。' },
1561
+                },
1562
+            };
1563
+        case 'GroupMustKnow':
1564
+            if (userInfo.IsPay != 1) return null;
1565
+            return {
1566
+                template_id: 'jncKd653-Rs6Ywsbgvfn_eQBwcA19WxcD-UYenOAxYM',
1567
+                miniprogram: { appid: config.wx.miaoguo_appid, pagepath: 'pages/index/index?type=webview&PageID=8' },
1568
+                data: {
1569
+                    first: { value: '秒过学习小组服务通知' },
1570
+                    keyword1: { value: '班务员天乐' },
1571
+                    keyword2: { value: '详见下文' },
1572
+                    remark: { value: '今天班务员将通过微信,邀请秒过续费用户加入专属服务群「秒过学习小组」。接下来,为您介绍小组的相关规则,请务必了解。\n\n点开通知或在秒过「手册」栏目里查看。' },
1573
+                },
1574
+            };
1575
+        case 'NewUserPresentCoupon': {
1576
+            if (userInfo.IsPay != 1) return null;
1577
+            const checklistResponse = await axios.get('http://localhost:' + config.port + '/api/GetMiaoguoCheckList', {
1578
+                params: { UserID: userInfo.UserID },
1579
+            }).then(res => res.data);
1580
+            const checklist = checklistResponse.result;
1581
+            let day = 0;
1582
+            for (let i = 1; i < checklist.List.length; i++) {
1583
+                if (checklist.List[i].Max <= checklist.List[i].Finished) day += 2;
1584
+            }
1585
+            await query('insert into MiaoguoCoupon (CouponType,UserID,LimitTime,IsUse,CouponNumber) values (134,?,?,0,?)', [
1586
+                checklist.UserID,
1587
+                moment().add(6, 'months').format('YYYY-MM-DD'),
1588
+                day,
1589
+            ]);
1590
+            return {
1591
+                template_id: 'ix-Ovx56bTZlG6-WUnHj1lTzQPh1hOGP_0NgJfuEgIU',
1592
+                miniprogram: miniProgram,
1593
+                data: {
1594
+                    first: { value: '你有新的优惠券了' },
1595
+                    keyword1: { value: moment().add(6, 'month').format('YYYY-MM-DD') },
1596
+                    keyword2: { value: '《秒过学习法》新手包' },
1597
+                    remark: { value: '“新手好礼——作业清单奖励”已发放给您。点开通知兑换。如已兑换请忽略本消息。' },
1598
+                },
1599
+            };
1600
+        }
1601
+        case 'PayFinished':
1602
+            {
1603
+            const isFirstPay = paramStr.PayType && (paramStr.PayType == 6 || paramStr.PayType == 7);
1604
+            return {
1605
+                template_id: 'FobvZBQZ_DoV3aAjPoyUax-UwUW5NZhWQE1QiTcCyIQ',
1606
+                miniprogram: miniProgram,
1607
+                data: {
1608
+                    first: { value: isFirstPay ? '检查有效期' : '后续服务说明' },
1609
+                    keyword1: { value: userInfo.NickName },
1610
+                    keyword2: { value: moment().format(isFirstPay ? 'YYYY年MM月DD日 HH:mm' : 'YYYY年MM月DD日') },
1611
+                    keyword3: { value: paramStr.Price },
1612
+                    keyword4: { value: moment(userInfo.ProductServiceTime).format('YYYY年MM月DD日') },
1613
+                    remark: { value: isFirstPay
1614
+                        ? '再次感谢您的支持!点开通知或在“用户手册”查看有效期和会员活动。\n\n重要:会员权益、社群服务、投诉建议、优惠活动等事项都是通过微信和您联系的。请确保您已加班务员天乐的微信(changyibzr)。'
1615
+                        : '感谢您的续费。点开通知或在“用户手册”查看有效期和会员活动。\n\n接下来的服务:\n① 新手包结束后,天乐将邀请您迁往“秒过学习小组”。\n② 秒过有奖推广正在进行——120天奖励等您来拿(上不封顶)。\n\n重要:会员权益、社群服务、投诉建议、优惠活动等事项都是通过微信和您联系的。请确保您已加班务员天乐的微信(changyibzr)。' },
1616
+                },
1617
+            };
1618
+            }
1619
+        case 'IntroducerRemind':
1620
+            return {
1621
+                template_id: 'Fd9ZYBMS3OpS0uw8u13phckVQAFYhcgjMO1mpVwSzho',
1622
+                miniprogram: { appid: config.wx.miaoguo_appid, pagepath: 'pages/index/index?type=marketing' },
1623
+                data: {
1624
+                    first: { value: '小组来新人了!' },
1625
+                    keyword1: { value: userInfo.NickName || '' },
1626
+                    keyword2: { value: moment().add(-1, 'days').format('YYYY-MM-DD') },
1627
+                    remark: { value: `经您推荐\n${paramStr.NickName}已加入“秒过学习小组”。您有伴儿了,今晚@打个招呼吧。` },
1628
+                },
1629
+            };
1630
+        case 'CouponSendRemind':
1631
+            return {
1632
+                template_id: 'StUmtFarUrI3H0-5HbFK_ztxCfc6vLBzd4epehcovnI',
1633
+                miniprogram: miniProgram,
1634
+                data: {
1635
+                    first: { value: '您有新的优惠券了。' },
1636
+                    keyword1: { value: paramStr.Title1 || '' },
1637
+                    keyword2: { value: (paramStr.Title2 || '').replace('[CouponNumber]', paramStr.CouponNumber || '') },
1638
+                    keyword3: { value: paramStr.LimitTime ? moment(paramStr.LimitTime).format('YYYY年MM月DD日') + '截止' : '' },
1639
+                    remark: { value: `${paramStr.Title3}\n\n点击通知后小程序左上角"优惠券"按钮兑换。如已兑换请忽略本消息。` },
1640
+                },
1641
+            };
1642
+        case 'LimitTimeChangeRemind':
1643
+            {
1644
+            const postJSON = {
1645
+                template_id: 'PVncvjny_Ek7KUaZYcL_e-DiSIKuBgS0aQqTmNKqFxE',
1646
+                miniprogram: miniProgram,
1647
+                data: {
1648
+                    first: { value: '' },
1649
+                    keyword1: { value: paramStr.Title1 || '' },
1650
+                    keyword2: { value: moment().format('YYYY年MM月DD日') },
1651
+                    remark: { value: `优惠内容:${paramStr.Title2.replace('[CouponNumber]', paramStr.CouponNumber)}\n兑换结果:您的秒过有效期已从${paramStr.LimitTimeOld} 延长到 ${paramStr.LimitTimeNew}\n\n点击通知核对有效期。` },
1652
+                },
1653
+            };
1654
+            await query('INSERT INTO MiaoguoLogs SET ?', [{
1655
+                UserID: userInfo.UserID,
1656
+                LogID: 424,
1657
+                Content: JSON.stringify(postJSON),
1658
+            }]);
1659
+            return postJSON;
1660
+            }
1661
+        case 'BindUserRemind':
1662
+            return {
1663
+                template_id: 'uTjU60VvAA2t5ZuOeimXbHfGZFr1YOz6fFi0ubgYViY',
1664
+                data: {
1665
+                    first: { value: '您的秒过已被其他用户绑定' },
1666
+                    keyword1: { value: paramStr.NickName || '' },
1667
+                    keyword2: { value: nowText },
1668
+                    remark: { value: '如绑定错误,请立即解绑。期间造成的隐私、数据等问题由个人承担。再次提醒您谨慎使用这项功能。' },
1669
+                },
1670
+            };
1671
+        case 'SubscribeNoPayRemind': {
1672
+            const classInfo = await query('SELECT * FROM kylx365_db.MiaoguoClassSchedule where Flag=0 and StartDate>now() order by StartDate limit 1');
1673
+            const dateStart = moment(classInfo[0].StartDate).format('YYYY年MM月DD日');
1674
+            return {
1675
+                template_id: 'I97cYN52rbJY0VscuHOhbB6J0jBoxuHDu9WL1dLglw4',
1676
+                url: 'https://www.kylx365.com/webpay',
1677
+                data: {
1678
+                    first: { value: `${dateStart},新班上线,等您报名。` },
1679
+                    keyword1: { value: '家长做什么能够迅速提高孩子的成绩?' },
1680
+                    keyword2: { value: '《秒过学习法》专为关心孩子学业的家长带来全新提分利器' },
1681
+                    remark: { value: '从小学到高中,针对学科教育解决错漏问题的学习方法。方法+工具,整套课程均由创始人亲授并全程在线辅导,手把手带您掌握新方法。仅需7天,期间可自由安排学习时间。\n\n点开通知查看新班介绍。如有打搅,请取消关注。' },
1682
+                },
1683
+            };
1684
+        }
1685
+        case 'AddTianleRemind':
1686
+            return {
1687
+                template_id: 'LGxZxJPFRf8Q1oysHio8N_MasH09ibDM2deCvn6djY8',
1688
+                miniprogram: miniProgram,
1689
+                data: {
1690
+                    first: { value: '报名手续未完成' },
1691
+                    keyword1: { value: '「秒过学习」新手班' },
1692
+                    keyword2: { value: userInfo.ProbationPayTime ? moment(userInfo.ProbationPayTime).format('YYYY-MM-DD') : moment().format('YYYY-MM-DD') },
1693
+                    remark: { value: '感谢您报名「秒过」新手班,报名手续还有一步没有做完。\n\n请添加班务员的微信(微信号:changyibzr)\n\n由于开班事宜需要通过微信与您联络,故此烦劳添加班务员微信,否则无法继续为您服务。\n\n如需退班,也请加微信办理退款手续。如不想再收到通知,取关公众号即可。' },
1694
+                },
1695
+            };
1696
+        case 'ClassToInform': {
1697
+            const classInfo = await query('SELECT * FROM MiaoguoClassSchedule where ClassID=?', [userInfo.ClassID || 0]);
1698
+            const dateStart = moment(classInfo[0].StartDate).format('YYYY年MM月DD日');
1699
+            const dateEnd = moment(classInfo[0].StartDate).add(classInfo[0].DayNumber - 1, 'days').format('YYYY年MM月DD日');
1700
+            return {
1701
+                template_id: 'kstJp4B2_QwvyaOBUofHm8sBalIzjcwMgCfgus0OiBQ',
1702
+                miniprogram: miniProgram,
1703
+                data: {
1704
+                    first: { value: '开班通知' },
1705
+                    keyword1: { value: '「秒过学习」新手班体验' },
1706
+                    keyword2: { value: `${dateStart}(开班首日)` },
1707
+                    keyword3: { value: '暖暖爸' },
1708
+                    keyword4: { value: '客服微信 changyibzr' },
1709
+                    remark: { value: `您报名的秒过新手班就要开班了。\n开班:${dateStart}\n结业:${dateEnd}\n共${classInfo[0].DayNumber}天在线答疑时间\n\n接下来\n请加入班群(周一开放答疑服务),班务员「天乐」将通过微信与您直接联系。\n\n结业前请勿取关公众号,以免遗漏进度通知。` },
1710
+                },
1711
+            };
1712
+        }
1713
+        case 'LimitTimeEndAwardSend':
1714
+            return {
1715
+                template_id: 'StUmtFarUrI3H0-5HbFK_ztxCfc6vLBzd4epehcovnI',
1716
+                miniprogram: miniProgram,
1717
+                data: {
1718
+                    first: { value: '推广奖励已发放' },
1719
+                    keyword1: { value: '推广计划奖励' },
1720
+                    keyword2: { value: `已发放${paramStr.DayNumber || ''}天` },
1721
+                    keyword3: { value: moment().format('YYYY年MM月DD日') },
1722
+                    remark: { value: '感谢您的支持。推广计划奖励已发放,您的有效期已增加。推广计划继续有效,后续累计奖励将于下次有效期临近时发放。\n\n点开通知查看有效期。' },
1723
+                },
1724
+            };
1725
+        case 'LimitTimeEndRemind': {
1726
+            const endDate = moment().add(paramStr.RemindDayNumber || 0, 'days').format('YYYY年MM月DD日');
1727
+            return {
1728
+                template_id: 'r90lqajrCCAFWapIu6T0YOB6xX1yEIcBVKfQQchK8bE',
1729
+                miniprogram: { appid: config.wx.miaoguo_appid, pagepath: `pages/index/index?type=paycontinue&EndDate=${endDate}` },
1730
+                data: paramStr.RemindDayNumber == '2' ? {
1731
+                    first: { value: '后天到期' },
1732
+                    keyword1: { value: userInfo.NickName || '' },
1733
+                    keyword2: { value: `${endDate}结束` },
1734
+                    remark: { value: '您的会员有效期就快结束了。非常荣幸能陪伴您和孩子度过一段学习时光。点开通知能直接续费。再次感谢您的支持!' },
1735
+                } : {
1736
+                    first: { value: '今天到期' },
1737
+                    keyword1: { value: userInfo.NickName || '' },
1738
+                    keyword2: { value: `${endDate}结束` },
1739
+                    remark: { value: '您的会员有效期已到期,小程序和各项服务已停用。点开通知可直接续费。感谢您的支持。' },
1740
+                },
1741
+            };
1742
+        }
1743
+        case 'ErrorRemind':
1744
+            return {
1745
+                template_id: 'LGxZxJPFRf8Q1oysHio8N_MasH09ibDM2deCvn6djY8',
1746
+                data: {
1747
+                    first: { value: '报错信息' },
1748
+                    keyword1: { value: rawParamStr },
1749
+                    keyword2: { value: moment().format('YYYY-MM-DD HH:mm:ss') },
1750
+                    remark: { value: rawParamStr },
1751
+                },
1752
+            };
1753
+        case 'PracticeTimingReminder':
1754
+            return {
1755
+                template_id: 'eSwv6P3YZVZU3q0P6EFB3aSKpLx81-4v1DF0giSJ-9k',
1756
+                miniprogram: { appid: config.wx.miaoguo_appid, pagepath: 'pages/index/index' },
1757
+                data: {
1758
+                    first: { value: '练习提醒' },
1759
+                    keyword1: { value: '时间到,来练习吧' },
1760
+                    keyword2: { value: `每天${paramStr.MessageTime || ''}提醒` },
1761
+                    remark: { value: '如要取消本提醒,请打开秒过小程序 ➜ 手册栏目 ➜ 设置(头像右侧) ➜ 练习提醒  ➜ 关闭。或微信联系客服「天乐」,微信号changyibzr。谢谢配合!' },
1762
+                },
1763
+            };
1764
+        default:
1765
+            return null;
1766
+    }
1767
+}
1768
+
1769
+export async function SendWXServiceTemplateMessage(ctx) {
1770
+    const param = {
1771
+        UserID: ctx.query.UserID || 0,
1772
+        TemplateID: ctx.query.TemplateID || 0,
1773
+        ParamStr: ctx.query.ParamStr || '',
1774
+    };
1775
+    try {
1776
+        const accessToken = await getWechatServiceAccessToken();
1777
+        const userList = await getMiaoguoInfoByWCUserID(param);
1778
+        if (!userList || !userList[0] || !userList[0].WCOpenID) {
1779
+            ctx.body = { errcode: 101, errStr: 'user not found' };
1780
+            return;
1781
+        }
1782
+
1783
+        const postJSON = await buildWXServiceTemplateMessage(param.TemplateID, userList[0], parseTemplateParam(param.ParamStr), param.ParamStr);
1784
+        if (!postJSON) {
1785
+            ctx.body = { errcode: 10000 };
1786
+            return;
1787
+        }
1788
+        postJSON.touser = userList[0].WCOpenID;
1789
+
1790
+        const url = 'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=' + accessToken;
1791
+        const result = await axios.post(url, postJSON).then(res => res.data);
1792
+        ctx.body = { errcode: 10000, result };
1793
+    } catch (err) {
1794
+        ctx.body = { errcode: 102, errStr: err.result || err.message || err };
1795
+    }
1796
+}
1797
+
1040 1798
 export async function GetWechatserviceUserNickNameAvatar(ctx) {
1041 1799
     const param = {
1042 1800
         Code: ctx.query.Code,
@@ -1177,35 +1935,35 @@ function getPriceAllJSON() {
1177 1935
     return [
1178 1936
         { ProductNum: '164', Period: [
1179 1937
             { Title1: '6个月(相当于一个学期)', Price: '19.80', Detail: '164:1980', Css: 'panelItem30', Select: true },
1180
-            { Title1: '12个月(相当于一个学年)', Title2: '原价39.6元,立减10.6元,再送"5元续费抵用券"1张', Price: '29.00', Detail: '164:2900' }
1938
+            { Title1: '12个月(相当于一个学年)', Title2: '原价39.6元,立减10.6元,再送“5元续费抵用券”1张', Price: '29.00', Detail: '164:2900' }
1181 1939
         ]},
1182 1940
         { ProductNum: '105', Period: [
1183 1941
             { Title1: '6个月(相当于一个学期)', Price: '19.80', Detail: '105:1980', Css: 'panelItem30', Select: true },
1184
-            { Title1: '12个月(相当于一个学年)', Title2: '原价39.6元,立减10.6元,再送"5元续费抵用券"1张', Price: '29.00', Detail: '105:2900' }
1942
+            { Title1: '12个月(相当于一个学年)', Title2: '原价39.6元,立减10.6元,再送“5元续费抵用券”1张', Price: '29.00', Detail: '105:2900' }
1185 1943
         ]},
1186 1944
         { ProductNum: '106', Period: [
1187 1945
             { Title1: '6个月(相当于一个学期)', Price: '19.80', Detail: '106:1980', Css: 'panelItem30', Select: true },
1188
-            { Title1: '12个月(相当于一个学年)', Title2: '原价39.6元,立减10.6元,再送"5元续费抵用券"1张', Price: '29.00', Detail: '106:2900' }
1946
+            { Title1: '12个月(相当于一个学年)', Title2: '原价39.6元,立减10.6元,再送“5元续费抵用券”1张', Price: '29.00', Detail: '106:2900' }
1189 1947
         ]},
1190 1948
         { ProductNum: '98', Period: [
1191 1949
             { Title1: '6个月(相当于一个学期)', Price: '19.80', Detail: '98:1980', Css: 'panelItem30', Select: true },
1192
-            { Title1: '12个月(相当于一个学年)', Title2: '原价39.6元,立减10.6元,再送"5元续费抵用券"1张', Price: '29.00', Detail: '98:2900' }
1950
+            { Title1: '12个月(相当于一个学年)', Title2: '原价39.6元,立减10.6元,再送“5元续费抵用券”1张', Price: '29.00', Detail: '98:2900' }
1193 1951
         ]},
1194 1952
         { ProductNum: '99', Period: [
1195 1953
             { Title1: '6个月(相当于一个学期)', Price: '19.80', Detail: '99:1980', Css: 'panelItem30', Select: true },
1196
-            { Title1: '12个月(相当于一个学年)', Title2: '原价39.6元,立减10.6元,再送"5元续费抵用券"1张', Price: '29.00', Detail: '99:2900' }
1954
+            { Title1: '12个月(相当于一个学年)', Title2: '原价39.6元,立减10.6元,再送“5元续费抵用券”1张', Price: '29.00', Detail: '99:2900' }
1197 1955
         ]},
1198 1956
         { ProductNum: '106,98', Name: '语文系列', Period: [
1199
-            { Title1: '6个月(相当于一个学期)', Title2: '原价39.6元,立减7.6元,再送"5元续费抵用券"1张', Price: '32.00', Detail: '106:1600,98:1600', Select: true },
1200
-            { Title1: '12个月(相当于一个学年)', Title2: '原价79.2元,立减21.2元,再送"5元续费抵用券"1张', Price: '58.00', Detail: '106:2900,98:2900' }
1957
+            { Title1: '6个月(相当于一个学期)', Title2: '原价39.6元,立减7.6元,再送“5元续费抵用券”1张', Price: '32.00', Detail: '106:1600,98:1600', Select: true },
1958
+            { Title1: '12个月(相当于一个学年)', Title2: '原价79.2元,立减21.2元,再送“5元续费抵用券”1张', Price: '58.00', Detail: '106:2900,98:2900' }
1201 1959
         ]},
1202 1960
         { ProductNum: '164,105', Name: '数学系列', Period: [
1203
-            { Title1: '6个月(相当于一个学期)', Title2: '原价39.6元,立减7.6元,再送"5元续费抵用券"1张', Price: '32.00', Detail: '164:1600,105:1600', Select: true },
1204
-            { Title1: '12个月(相当于一个学年)', Title2: '原价79.2元,立减21.2元,再送"5元续费抵用券"1张', Price: '58.00', Detail: '164:2900,105:2900' }
1961
+            { Title1: '6个月(相当于一个学期)', Title2: '原价39.6元,立减7.6元,再送“5元续费抵用券”1张', Price: '32.00', Detail: '164:1600,105:1600', Select: true },
1962
+            { Title1: '12个月(相当于一个学年)', Title2: '原价79.2元,立减21.2元,再送“5元续费抵用券”1张', Price: '58.00', Detail: '164:2900,105:2900' }
1205 1963
         ]},
1206 1964
         { ProductNum: '164,105,106,98,99', Name: '全套', Period: [
1207
-            { Title1: '6个月(相当于一个学期)', Title2: '原价99元,立减19元,再送"5元续费抵用券"2张', Price: '80.00', Detail: '164:1600,105:1600,106:1600,98:1600,99:1600', Select: true },
1208
-            { Title1: '12个月(相当于一个学年)', Title2: '原价198元,立减53元,再送"5元续费抵用券"2张', Price: '145.00', Detail: '164:2900,105:2900,106:2900,98:2900,99:2900' },
1965
+            { Title1: '6个月(相当于一个学期)', Title2: '原价99元,立减19元,再送“5元续费抵用券”2张', Price: '80.00', Detail: '164:1600,105:1600,106:1600,98:1600,99:1600', Select: true },
1966
+            { Title1: '12个月(相当于一个学年)', Title2: '原价198元,立减53元,再送“5元续费抵用券”2张', Price: '145.00', Detail: '164:2900,105:2900,106:2900,98:2900,99:2900' },
1209 1967
             { Title1: '永久使用', Price: '236.00', Detail: '164:8100,105:8100,106:5000,98:1200,99:1200', Css: 'panelItem30' }
1210 1968
         ]}
1211 1969
     ];

+ 10 - 0
src/api/pay/routes.js

@@ -15,6 +15,14 @@ router.post('/api/PayNotify', payController.PayNotify);
15 15
 router.get('/api/PayUserRebate', payController.PayUserRebate);
16 16
 
17 17
 // productPayController routes (口算500等)
18
+router.get('/api/PinyinPayLogin', productPayController.PinyinPayLogin);
19
+router.post('/api/PinyinPayNotify', productPayController.PinyinPayNotify);
20
+router.get('/api/PhonicsPayLogin', productPayController.PhonicsPayLogin);
21
+router.post('/api/PhonicsPayNotify', productPayController.PhonicsPayNotify);
22
+router.get('/api/HanziPayLogin', productPayController.HanziPayLogin);
23
+router.post('/api/HanziPayNotify', productPayController.HanziPayNotify);
24
+router.get('/api/ProductPayLogin', productPayController.ProductPayLogin);
25
+router.post('/api/ProductPayNotify', productPayController.ProductPayNotify);
18 26
 router.get('/api/ProductPayLogin500', productPayController.ProductPayLogin500);
19 27
 router.post('/api/ProductPayLoginWeb', productPayController.ProductPayLoginWeb);
20 28
 router.post('/api/ProductPayNotify500', productPayController.ProductPayNotify500);
@@ -30,6 +38,8 @@ router.get('/api/ShowWXServiceImageList', productPayController.ShowWXServiceImag
30 38
 router.get('/api/getAuthorization', productPayController.getAuthorization);
31 39
 router.get('/api/GetWechatserviceUserNickNameAvatar', productPayController.GetWechatserviceUserNickNameAvatar);
32 40
 router.get('/api/SendWXServiceMessage', productPayController.SendWXServiceMessage);
41
+router.get('/api/GetWXServiceUsers', productPayController.GetWXServiceUsers);
42
+router.get('/api/SendWXServiceTemplateMessage', productPayController.SendWXServiceTemplateMessage);
33 43
 router.post('/api/WebNotify', productPayController.WebNotify);
34 44
 router.get('/api/ProductPayPriceList500', productPayController.ProductPayPriceList500);
35 45
 router.get('/api/GetUserCouponListByUserID', productPayController.GetUserCouponListByUserID);

+ 8 - 2
src/api/phonics/phonicsController.js

@@ -102,7 +102,7 @@ export async function PhonicsLogin(ctx) {
102 102
                 Version: param.ProgramVersion,
103 103
             };
104 104
             let result3 = await commonModel.GetProductVersionList(param2);
105
-            if (result3) {
105
+            if (result3 && result3.length > 0) {
106 106
                 if ((param2.Version == result3[0].Version && result3[0].IsShowPay <= 0)
107 107
                     || param2.Version > result3[0].Version) {
108 108
                     userList[0].IsShow = result3[0].IsShowPay;
@@ -127,6 +127,9 @@ export async function PhonicsLogin(ctx) {
127 127
                 if (userList[0].IsMember===1)
128 128
                     userList[0].IsShow=1;
129 129
             }
130
+            else {
131
+                userList[0].IsShow = 1;
132
+            }
130 133
         }
131 134
 
132 135
         result = {errcode: 10000, result: userList[0]};
@@ -220,7 +223,7 @@ export async function GetPhonicsRecordData(ctx) {
220 223
             Version: param.Version,
221 224
         };
222 225
         const result3 = await commonModel.GetProductVersionList(param2);
223
-        if (result3) {
226
+        if (result3 && result3.length > 0) {
224 227
             if ((param.Version === result3[0].Version && result3[0].IsShowPay <= 0)
225 228
                 || param.Version > result3[0].Version) {
226 229
                 result[0].IsShow = result3[0].IsShowPay;
@@ -234,6 +237,9 @@ export async function GetPhonicsRecordData(ctx) {
234 237
                 result[0].IsShow = 0;
235 238
             }
236 239
         }
240
+        else {
241
+            result[0].IsShow = 1;
242
+        }
237 243
 
238 244
         ctx.body = {"errcode": 10000, result: result[0]};
239 245
     } else

+ 8 - 2
src/api/pinyin/pinyinController.js

@@ -102,7 +102,7 @@ export async function PinyinLogin(ctx) {
102 102
                 Version: param.ProgramVersion,
103 103
             };
104 104
             let result3 = await commonModel.GetProductVersionList(param2);
105
-            if (result3) {
105
+            if (result3 && result3.length > 0) {
106 106
                 if ((param2.Version == result3[0].Version && result3[0].IsShowPay <= 0)
107 107
                     || param2.Version > result3[0].Version) {
108 108
                     userList[0].IsShow = result3[0].IsShowPay;
@@ -127,6 +127,9 @@ export async function PinyinLogin(ctx) {
127 127
                 if (userList[0].IsMember===1)
128 128
                     userList[0].IsShow=1;
129 129
             }
130
+            else {
131
+                userList[0].IsShow = 1;
132
+            }
130 133
         }
131 134
 
132 135
         result = {errcode: 10000, result: userList[0]};
@@ -215,7 +218,7 @@ export async function GetPinyinRecordData(ctx) {
215 218
             Version: param.Version,
216 219
         };
217 220
         const result3 = await commonModel.GetProductVersionList(param2);
218
-        if (result3) {
221
+        if (result3 && result3.length > 0) {
219 222
             if ((param.Version === result3[0].Version && result3[0].IsShowPay <= 0)
220 223
                 || param.Version > result3[0].Version) {
221 224
                 result[0].IsShow = result3[0].IsShowPay;
@@ -230,6 +233,9 @@ export async function GetPinyinRecordData(ctx) {
230 233
                 result[0].IsShow = 0;
231 234
             }
232 235
         }
236
+        else {
237
+            result[0].IsShow = 1;
238
+        }
233 239
 
234 240
         ctx.body = {"errcode": 10000, result: result[0]};
235 241
     }

+ 0 - 2
src/api/questionTypes/routes.js

@@ -3,10 +3,8 @@ import * as questionTypesController from './questionTypesController.js';
3 3
 
4 4
 const router = new Router();
5 5
 
6
-router.get('/api/GetQuestionTypes', questionTypesController.GetQuestionTypes);
7 6
 router.get('/api/GetQuestionListByQuestionTypeID', questionTypesController.GetQuestionListByQuestionTypeID);
8 7
 router.get('/api/GetQuestionListByQuestionTypeIDArr', questionTypesController.GetQuestionListByQuestionTypeIDArr);
9
-router.get('/api/GetQuestionTypesPrint', questionTypesController.GetQuestionTypesPrint);
10 8
 router.get('/api/MakeQuestionTypeWXACode', questionTypesController.MakeQuestionTypeWXACode);
11 9
 
12 10
 export default router;

+ 132 - 94
src/api/users/userAllController.js

@@ -3,23 +3,88 @@ import _ from 'lodash';
3 3
 import axios from 'axios';
4 4
 import * as wxusersall from '../../model/wxUsersAll.js';
5 5
 import * as users from '../../model/users.js';
6
+import * as payInfo from '../../model/payInfo.js';
7
+import commonModel from '../../model/commonModel.js';
8
+import pinyin from '../../model/pinyin.js';
9
+import phonics from '../../model/phonics.js';
10
+import hanzi from '../../model/hanzi.js';
11
+import mathcalculate from '../../model/mathcalculate.js';
12
+import mathStar from '../../model/mathstar.js';
13
+import parentsHelper from '../../model/parentsHelper.js';
6 14
 import { stringUtils as stringClass } from '../../util/stringClass.js';
7 15
 import config from '../../config/index.js';
8 16
 
9
-// Note: The following model files are NOT yet migrated and are imported as placeholders.
10
-// In the old project, they have direct model/ submodule access. For now, functions that
11
-// depend on these models are marked with comments where the original logic lives.
12
-// - Hanzi (model/Hanzi.js)
13
-// - Phonics (model/Phonics.js)
14
-// - Pinyin (model/Pinyin.js)
15
-// - PayInfo (model/PayInfo.js)
16
-// - MathStar (model/MathStar.js)
17
-// - MathCalculation (model/MathCalculation.js)
18
-// - ParentsHelper (model/ParentsHelper.js)
19
-// - dataUpdateStatus (model/dataUpdateStatus.js)
20
-
21 17
 const FINAL_TIME = '2099-12-31 23:59:59';
22 18
 
19
+function getProductUserModel(productID) {
20
+  switch (Number(productID)) {
21
+    case 89:
22
+      return users;
23
+    case 98:
24
+      return pinyin;
25
+    case 99:
26
+      return phonics;
27
+    case 106:
28
+      return hanzi;
29
+    case 105:
30
+      return mathcalculate;
31
+    case 164:
32
+      return mathStar;
33
+    case 107:
34
+      return parentsHelper;
35
+    default:
36
+      return null;
37
+  }
38
+}
39
+
40
+function getProductName(productID) {
41
+  switch (Number(productID)) {
42
+    case 164:
43
+      return '口算星球';
44
+    case 105:
45
+      return '数学计算题';
46
+    case 106:
47
+      return '语文识字';
48
+    case 98:
49
+      return '语文汉语拼音';
50
+    case 99:
51
+      return '英语自然拼读';
52
+    case 89:
53
+      return '数学口算练习';
54
+    case 107:
55
+      return '小学家长助手';
56
+    default:
57
+      return '';
58
+  }
59
+}
60
+
61
+async function getProductUserInfo(productID, obj) {
62
+  const model = getProductUserModel(productID);
63
+  if (!model || !model.GetUsersInfoByUserID) return [];
64
+  return await model.GetUsersInfoByUserID(obj);
65
+}
66
+
67
+async function updateProductUser(productID, obj) {
68
+  const model = getProductUserModel(productID);
69
+  if (!model || !model.UpdateUsersByUserID) return;
70
+  await model.UpdateUsersByUserID(obj);
71
+}
72
+
73
+async function setProductServiceTimeFromCurrentUser(productID, updateObj, month) {
74
+  const userInfo = await getProductUserInfo(productID, updateObj);
75
+  const currentServiceTime = userInfo && userInfo[0] ? userInfo[0].ProductServiceTime : null;
76
+  const time3 = moment().format('YYYYMMDD');
77
+  const time4 = currentServiceTime ? moment(currentServiceTime).format('YYYYMMDD') : '';
78
+  if (currentServiceTime && time3 < time4) {
79
+    updateObj.ProductServiceTime = moment(currentServiceTime).add(month, 'months').format('YYYY.MM.DD HH:mm:ss');
80
+  } else {
81
+    updateObj.ProductServiceTime = moment().add(month, 'months').format('YYYY.MM.DD HH:mm:ss');
82
+  }
83
+  if (updateObj.ProductServiceTime > FINAL_TIME) {
84
+    updateObj.ProductServiceTime = FINAL_TIME;
85
+  }
86
+}
87
+
23 88
 async function getWxAccessToken(appid, secret) {
24 89
   const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`;
25 90
   const res = await axios.get(url);
@@ -76,13 +141,16 @@ export async function GetWXUsersAllTime(ctx) {
76 141
       list.push({ ID: 89, Name: '数学口算练习', Time: result[0].Time89 });
77 142
     }
78 143
   } else {
79
-    // Product-specific fallback - requires other models not yet migrated
80
-    // For now, return empty list
81
-    // let userInfo = null;
82
-    // const obj = { ID: param.ProductID };
83
-    // switch (Number(param.ProductID)) { ... }
84
-    ctx.body = { errcode: 10000, result: [] };
85
-    return;
144
+    const userInfo = await getProductUserInfo(param.ProductID, param);
145
+    if (userInfo && userInfo[0]) {
146
+      const obj = {
147
+        ID: Number(param.ProductID),
148
+        Name: getProductName(param.ProductID),
149
+        Time: moment(userInfo[0].ProductServiceTime).format('YYYY年MM月DD日'),
150
+      };
151
+      if (obj.Time >= '2099年12月31日') obj.Time = '永久有效';
152
+      list.push(obj);
153
+    }
86 154
   }
87 155
 
88 156
   const arr = [];
@@ -166,13 +234,13 @@ export async function GetWXUsersAllPayInfo(ctx) {
166 234
       }
167 235
     }
168 236
 
169
-    list.sort((a, b) => {
170
-      const tA = moment(a.PayEndTime, 'YYYY年MM月DD日 HH:mm').valueOf();
171
-      const tB = moment(b.PayEndTime, 'YYYY年MM月DD日 HH:mm').valueOf();
172
-      return tB - tA;
173
-    });
237
+    const sortedList = _.sortBy(list, [item => item.PayEndTime]);
238
+    const resultList = [];
239
+    for (let i = sortedList.length - 1; i >= 0; i--) {
240
+      resultList.push(sortedList[i]);
241
+    }
174 242
 
175
-    ctx.body = { errcode: 10000, result: list };
243
+    ctx.body = { errcode: 10000, result: resultList };
176 244
   } else {
177 245
     ctx.body = { errcode: 10000 };
178 246
   }
@@ -211,38 +279,16 @@ export async function UpdateWXUsersAll(ctx) {
211 279
     if (param.BuyType === '108' || param.BuyType === '111') {
212 280
       updateObj.ProductServiceTime = FINAL_TIME;
213 281
     } else if (param.BuyType === '109' || param.BuyType === '110') {
214
-      // Product-specific user info lookup - requires other models not yet migrated
215
-      // For now, use a simpler expiry logic
216
-      const time3 = moment().format('YYYYMMDD');
217
-      // Placeholder - when other models are migrated, replace this with
218
-      // proper product-specific lookup
219
-      const time4 = moment().add(6, 'months').format('YYYYMMDD');
220
-      if (time3 < time4) {
221
-        updateObj.ProductServiceTime = moment().add(6, 'months').format('YYYY.MM.DD HH:mm:ss');
222
-      } else {
223
-        updateObj.ProductServiceTime = moment().add(6, 'months').format('YYYY.MM.DD HH:mm:ss');
224
-      }
225
-      if (updateObj.ProductServiceTime > FINAL_TIME) {
226
-        updateObj.ProductServiceTime = FINAL_TIME;
227
-      }
282
+      await setProductServiceTimeFromCurrentUser(param.CurrentProductID, updateObj, 6);
228 283
     }
229 284
 
230
-    // Update the correct table based on CurrentProductID
231
-    // Requires product-specific model imports (users, pinyin, phonics, etc.)
232
-    // For now, only users is available
233
-    if (Number(param.CurrentProductID) === 89) {
234
-      await users.UpdateUsersByUserID(updateObj);
235
-    }
236
-    // Other product types would route to their respective models:
237
-    // 98 -> pinyin, 99 -> phonics, 106 -> hanzi, 105 -> math, 164 -> mathStar, 107 -> parentsHelper
285
+    await updateProductUser(param.CurrentProductID, updateObj);
238 286
 
239
-    // Update activation status
240 287
     const activateObj = {
241 288
       ID: param.ProductBuyInfoID,
242 289
       Status: 1,
243 290
     };
244
-    // Requires payInfo.UpdateProductBuyInfo - not yet migrated
245
-    // await payInfo.UpdateProductBuyInfo(activateObj);
291
+    await payInfo.UpdateProductBuyInfo(activateObj);
246 292
   }
247 293
 
248 294
   ctx.body = { errcode: 10000 };
@@ -282,8 +328,7 @@ export async function SendRemind(ctx) {
282 328
   if (Number(param.ProgramID) === 89) {
283 329
     dayNumberObj = await users.GetUserDayNumber(param);
284 330
   } else if (Number(param.ProgramID) === 164) {
285
-    // Requires mathStar.GetUserDayNumber - not yet migrated
286
-    dayNumberObj = [{ DayNumber: 0, Max: new Date() }];
331
+    dayNumberObj = await mathStar.GetUserDayNumber(param);
287 332
   }
288 333
 
289 334
   if (dayNumberObj && dayNumberObj.length > 0 &&
@@ -418,36 +463,33 @@ export async function GetWXUsersAllPayInfo500(ctx) {
418 463
       list.push(obj);
419 464
     }
420 465
 
421
-    list.sort((a, b) => {
422
-      const tA = moment(a.PayEndTime, 'YYYY年MM月DD日 HH:mm').valueOf();
423
-      const tB = moment(b.PayEndTime, 'YYYY年MM月DD日 HH:mm').valueOf();
424
-      return tB - tA;
425
-    });
466
+    const sortedList = _.sortBy(list, [item => item.PayEndTime]);
426 467
 
427 468
     const result2 = [];
428
-    for (let i = 0; i < list.length; i++) {
429
-      if (list[i].List.length === 5 && list[i].PayType === 4) {
430
-        list[i].Name = '语数英套装';
431
-        list[i].ImageName = 'product_icon_group_50x50';
432
-      } else if (list[i].List.length === 5 && list[i].PayType === 5) {
433
-        list[i].Name = '唱意教育礼品卡';
434
-        list[i].ImageName = 'product_icon_gift_50x50';
435
-      } else if (list[i].List.length === 2 && list[i].PayType === 4) {
436
-        if (list[i].List[0].ProductID === 106) {
437
-          list[i].Name = '语文套装';
438
-        } else if (list[i].List[0].ProductID === 164) {
439
-          list[i].Name = '数学套装';
469
+    for (let i = sortedList.length - 1; i >= 0; i--) {
470
+      const item = sortedList[i];
471
+      if (item.List.length === 5 && item.PayType === 4) {
472
+        item.Name = '语数英套装';
473
+        item.ImageName = 'product_icon_group_50x50';
474
+      } else if (item.List.length === 5 && item.PayType === 5) {
475
+        item.Name = '唱意教育礼品卡';
476
+        item.ImageName = 'product_icon_gift_50x50';
477
+      } else if (item.List.length === 2 && item.PayType === 4) {
478
+        if (item.List[0].ProductID === 106) {
479
+          item.Name = '语文套装';
480
+        } else if (item.List[0].ProductID === 164) {
481
+          item.Name = '数学套装';
440 482
         }
441
-        list[i].ImageName = 'product_icon_group_50x50';
442
-      } else if (list[i].List.length === 1 && list[i].PayType === 5) {
443
-        list[i].ImageName = 'product_icon_gift_50x50';
444
-        if (list[i].List[0].ProductID === 164) list[i].Name = '口算礼品卡';
445
-        else if (list[i].List[0].ProductID === 105) list[i].Name = '计算题礼品卡';
446
-        else if (list[i].List[0].ProductID === 106) list[i].Name = '识字礼品卡';
447
-        else if (list[i].List[0].ProductID === 98) list[i].Name = '拼音礼品卡';
448
-        else if (list[i].List[0].ProductID === 99) list[i].Name = '自然拼读礼品卡';
483
+        item.ImageName = 'product_icon_group_50x50';
484
+      } else if (item.List.length === 1 && item.PayType === 5) {
485
+        item.ImageName = 'product_icon_gift_50x50';
486
+        if (item.List[0].ProductID === 164) item.Name = '口算礼品卡';
487
+        else if (item.List[0].ProductID === 105) item.Name = '计算题礼品卡';
488
+        else if (item.List[0].ProductID === 106) item.Name = '识字礼品卡';
489
+        else if (item.List[0].ProductID === 98) item.Name = '拼音礼品卡';
490
+        else if (item.List[0].ProductID === 99) item.Name = '自然拼读礼品卡';
449 491
       }
450
-      result2.push(list[i]);
492
+      result2.push(item);
451 493
     }
452 494
 
453 495
     ctx.body = { errcode: 10000, result: result2 };
@@ -559,26 +601,16 @@ export async function UpdateWXUsersAll500(ctx) {
559 601
         let month = 6;
560 602
         if (mustActivateList[i].BuyType === 112) month = 12;
561 603
 
562
-        // Product-specific user info - requires other models
563
-        // Default to 6 months
564
-        updateObj.ProductServiceTime = moment().add(month, 'months').format('YYYY.MM.DD HH:mm:ss');
565
-        if (updateObj.ProductServiceTime > FINAL_TIME) {
566
-          updateObj.ProductServiceTime = FINAL_TIME;
567
-        }
604
+        await setProductServiceTimeFromCurrentUser(param.CurrentProductID, updateObj, month);
568 605
       }
569 606
 
570
-      if (Number(param.CurrentProductID) === 89) {
571
-        await users.UpdateUsersByUserID(updateObj);
572
-      }
573
-      // Other product types routed to their respective models when migrated:
574
-      // 98 -> pinyin, 99 -> phonics, 106 -> hanzi, 105 -> math, 164 -> mathStar, 107 -> parentsHelper
607
+      await updateProductUser(param.CurrentProductID, updateObj);
575 608
 
576
-      // Update activation status
577 609
       const activateObj = {
578 610
         ID: mustActivateList[i].ID,
579 611
         Status: 1,
580 612
       };
581
-      // Requires payInfo.UpdateProductBuyInfo - not yet migrated
613
+      await payInfo.UpdateProductBuyInfo(activateObj);
582 614
     }
583 615
   }
584 616
 
@@ -609,9 +641,15 @@ export async function GetShareText500(ctx) {
609 641
   };
610 642
   let IsShow = 0;
611 643
   if (param.ProgramVersion) {
612
-    // Requires dataUpdateStatus.GetProductVersionList - not yet migrated
613
-    // For now, always show
614
-    IsShow = 1;
644
+    const result3 = await commonModel.GetProductVersionList(param);
645
+    if (result3 && result3[0]) {
646
+      if ((param.ProgramVersion == result3[0].Version && result3[0].IsShowPay <= 0)
647
+          || param.ProgramVersion > result3[0].Version) {
648
+        IsShow = result3[0].IsShowPay;
649
+      } else {
650
+        IsShow = 1;
651
+      }
652
+    }
615 653
   }
616 654
 
617 655
   const result = IsShow < 1

+ 75 - 50
src/api/users/usersController.js

@@ -1,23 +1,11 @@
1 1
 import axios from 'axios';
2 2
 import moment from 'moment';
3 3
 import fs from 'fs';
4
-import crypto from 'crypto';
5 4
 import * as users from '../../model/users.js';
6 5
 import * as wxusersall from '../../model/wxUsersAll.js';
6
+import notice from '../../model/notice.js';
7
+import mathStar from '../../model/mathstar.js';
7 8
 import config from '../../config/index.js';
8
-
9
-// Note: The following modules exist in the old project but are NOT yet migrated:
10
-//   - Notices (model/Notices.js)
11
-//   - MathStar (model/MathStar.js)
12
-//   - dataStatistics (model/dataStatistics.js)
13
-//   - WXBizDataCrypt (util/crypto/WXBizDataCrypt.js → exists at util/WXBizDataCrypt.js)
14
-//   - BufferMemory (util/bufferMemory.js → use util/GlobalCache.js)
15
-//   - gm (image manipulation - needs npm install)
16
-//   - COS (cos-nodejs-sdk-v5 - needs npm install)
17
-// These imports are preserved in comments for reference when those modules are migrated.
18
-// import * as notices from '../../model/notices.js';
19
-// import * as math from '../../model/mathStar.js';
20
-// import * as dataStatistics from '../../model/dataStatistics.js';
21 9
 import WXBizDataCrypt from '../../util/WXBizDataCrypt.js';
22 10
 import { stringUtils as stringClass } from '../../util/stringClass.js';
23 11
 
@@ -41,6 +29,16 @@ async function sendTemplateMessage(access_token, postBody) {
41 29
   return res.data;
42 30
 }
43 31
 
32
+async function writeStreamToFile(readStream, filePath) {
33
+  await new Promise((resolve, reject) => {
34
+    const writer = fs.createWriteStream(filePath);
35
+    readStream.pipe(writer);
36
+    writer.on('finish', resolve);
37
+    writer.on('error', reject);
38
+    readStream.on('error', reject);
39
+  });
40
+}
41
+
44 42
 export async function Login(ctx) {
45 43
   const param = ctx.request.body;
46 44
 
@@ -104,15 +102,14 @@ export async function Login(ctx) {
104 102
       userList = await users.GetUsersInfo(param);
105 103
 
106 104
       if (param.NickName === '陌生用户') {
107
-        // notices not migrated yet
108
-        // const noticeInfo = {
109
-        //   UserID: userList[0].UserID,
110
-        //   Title: '授权',
111
-        //   Content: '您现在还是"陌生用户"。小程序需要您的授权才能保证使用体验完善。点击下方按钮,将"用户信息"拨到打开状态。谢谢!',
112
-        //   Url: 'setting',
113
-        // };
114
-        // await notices.DeleteNotices(noticeInfo);
115
-        // await notices.AddNotices(noticeInfo);
105
+        const noticeInfo = {
106
+          UserID: userList[0].UserID,
107
+          Title: '授权',
108
+          Content: '您现在还是"陌生用户"。小程序需要您的授权才能保证使用体验完善。点击下方按钮,将"用户信息"拨到打开状态。谢谢!',
109
+          Url: 'setting',
110
+        };
111
+        await notice.DeleteNotices(noticeInfo);
112
+        await notice.AddNotices(noticeInfo);
116 113
       }
117 114
 
118 115
       if (param.UnionID) userList[0].UnionID = param.UnionID;
@@ -152,20 +149,21 @@ export async function Login(ctx) {
152 149
       // Sync expiry date
153 150
       if (wxUserList && wxUserList.length > 0 && wxUserList[0].UserID164) {
154 151
         obj = { UserID: wxUserList[0].UserID164 };
155
-        // mathStar not migrated yet
156
-        // const wxUserList0 = await math.GetUsersInfoByUserID(obj);
157
-        // const time1 = moment(userList[0].ProductServiceTime).format('YYYY-MM-DD HH:mm:ss');
158
-        // const time2 = moment(wxUserList0[0].ProductServiceTime).format('YYYY-MM-DD HH:mm:ss');
159
-        // const time3 = moment().format('YYYY-MM-DD HH:mm:ss');
160
-        // if (time1 > time2 && time1 > time3) {
161
-        //   obj = {
162
-        //     UserID: wxUserList0[0].UserID,
163
-        //     ProductServiceTime: time1,
164
-        //     IsPay: userList[0].IsPay,
165
-        //     IsMember: 1,
166
-        //   };
167
-        //   await math.UpdateUsersByUserID(obj);
168
-        // }
152
+        const wxUserList0 = await mathStar.GetUsersInfoByUserID(obj);
153
+        if (wxUserList0 && wxUserList0.length > 0) {
154
+          const time1 = moment(userList[0].ProductServiceTime).format('YYYY-MM-DD HH:mm:ss');
155
+          const time2 = moment(wxUserList0[0].ProductServiceTime).format('YYYY-MM-DD HH:mm:ss');
156
+          const time3 = moment().format('YYYY-MM-DD HH:mm:ss');
157
+          if (time1 > time2 && time1 > time3) {
158
+            obj = {
159
+              UserID: wxUserList0[0].UserID,
160
+              ProductServiceTime: time1,
161
+              IsPay: userList[0].IsPay,
162
+              IsMember: 1,
163
+            };
164
+            await mathStar.UpdateUsersByUserID(obj);
165
+          }
166
+        }
169 167
       }
170 168
     }
171 169
 
@@ -301,21 +299,47 @@ export async function AddGroup(ctx) {
301 299
   ctx.body = result;
302 300
 }
303 301
 
304
-// MakeWXACode - largely commented out in original; kept as stub
302
+// Old code's generation block is disabled; this endpoint keeps the same no-op response.
305 303
 export async function MakeWXACode(ctx) {
306
-  // Original code is fully commented out - QR code generation logic
307
-  // using gm, COS, etc. Not migrated.
308 304
   ctx.body = { errcode: 10000, result: 0 };
309 305
 }
310 306
 
311
-// MakeHardworkingImage - largely commented out in original; kept as stub
307
+// Old code's certificate generation block is disabled; this endpoint keeps the same no-op response.
312 308
 export async function MakeHardworkingImage(ctx) {
313
-  // Original code is fully commented out - certificate image generation
314 309
   ctx.body = { errcode: 10000, result: 0 };
315 310
 }
316 311
 
317
-// MakeWXACodeSelf - largely commented out in original; kept as stub
318 312
 export async function MakeWXACodeSelf(ctx) {
313
+  const param = {
314
+    UserID: ctx.query.UserID || 0,
315
+  };
316
+  if (param.UserID === 'undefined') param.UserID = 0;
317
+
318
+  const userInfo = await users.GetUsersInfoByUserID(param);
319
+  if (userInfo && userInfo.length > 0) {
320
+    try {
321
+      const accessToken = await getAccessToken(config.wx.appid, config.wx.appsecret);
322
+      const acodeDir = new URL('../../../public/images/acode/', import.meta.url);
323
+      fs.mkdirSync(acodeDir, { recursive: true });
324
+      const imageUrl = new URL(`${param.UserID}.png`, acodeDir);
325
+      const postBody = {
326
+        scene: 'SourceID=92',
327
+        page: 'pages/index/start',
328
+        width: 258,
329
+        auto_color: false,
330
+      };
331
+
332
+      const res = await axios.post(
333
+        `https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=${accessToken}`,
334
+        postBody,
335
+        { responseType: 'stream' },
336
+      );
337
+      await writeStreamToFile(res.data, imageUrl);
338
+    } catch (err) {
339
+      console.log('MakeWXACodeSelf error:', err.message);
340
+    }
341
+  }
342
+
319 343
   ctx.body = { errcode: 10000, result: 0 };
320 344
 }
321 345
 
@@ -573,11 +597,12 @@ export async function SendRemindToMe(ctx) {
573 597
     result = { errcode: 101, errStr: err.message };
574 598
   }
575 599
 
576
-  const param0 = { UserID: 4 };
577
-  // GetTodayRemindUsers is in WXUsersAll model, not Users
578
-  // import * as wxusersall from '../../model/wxUsersAll.js';
579
-  // const formInfo = await wxusersall.GetTodayRemindUsers(param0);
580
-  const formInfo = []; // placeholder until GetTodayRemindUsers available
600
+  const param0 = {
601
+    UserID: 4,
602
+    MessageTime: moment().format('HH:mm'),
603
+    Day: moment().day(),
604
+  };
605
+  const formInfo = await wxusersall.GetTodayRemindUsers(param0);
581 606
   const userInfo = await users.GetUsersInfoByUserID(param);
582 607
 
583 608
   if (result.errcode === 10000 && formInfo.length > 0 && userInfo.length > 0) {
@@ -596,11 +621,11 @@ export async function SendRemindToMe(ctx) {
596 621
     try {
597 622
       const sendResult = await sendTemplateMessage(access_token, postBody);
598 623
       const param2 = { UserFormID: formInfo[0].UserFormID, Flag: 1 };
599
-      // await wxusersall.UpdateFormId(param2);
624
+      await wxusersall.UpdateFormId(param2);
600 625
       result = { errcode: sendResult.errcode, result: sendResult.errmsg };
601 626
     } catch (err) {
602 627
       const param2 = { UserFormID: formInfo[0].UserFormID, Flag: -1 };
603
-      // await wxusersall.UpdateFormId(param2);
628
+      await wxusersall.UpdateFormId(param2);
604 629
       result = { errcode: err.errcode, errmsg: err.message };
605 630
     }
606 631
   }

+ 760 - 0
src/build.js

@@ -0,0 +1,760 @@
1
+import axios from 'axios';
2
+import moment from 'moment';
3
+import schedule from 'node-schedule';
4
+import config from './config/index.js';
5
+import Miaoguo from './model/miaoguo.js';
6
+import Schulte from './model/schulte.js';
7
+import * as userAll from './model/wxUsersAll.js';
8
+
9
+const miaoguo = Miaoguo;
10
+const schulte = Schulte;
11
+
12
+var headers = {
13
+    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36'
14
+}
15
+function originRequest(url, callback = () => {}) {
16
+    axios.get(url, {
17
+        headers,
18
+        responseType: 'arraybuffer',
19
+    }).then((response) => {
20
+        callback(null, response, response.data);
21
+    }).catch((error) => {
22
+        callback(error);
23
+    });
24
+}
25
+
26
+//每小时第3分钟执行一次
27
+// schedule.scheduleJob({minute: [3]}, function () {
28
+// });
29
+
30
+//每半小时执行一次
31
+schedule.scheduleJob({minute: [0,30]}, function () {
32
+    remindOldMath();//老口算提醒
33
+    buildPayUserID();//更新支付时的用户ID
34
+    remindMiaoguoUserPractice();//remindMiaoguoUserPractice
35
+    console.log(moment().format("YYYY-MM-DD HH:mm:ss"));
36
+});
37
+
38
+//每天凌晨1点半执行一次
39
+schedule.scheduleJob({hour: 1, minute: [30]}, function() {
40
+    realSchulteGame();//处理舒尔特游戏数据
41
+    UpdateMiaoguoTask();//秒过同步MiaoguoTask
42
+    deleteNullMiaoguoCard(); //当有题卡ID却没有内容时,则删除
43
+});
44
+
45
+//每天8点、12点、17点、20点各执行一次
46
+schedule.scheduleJob({second:0,minute:0,hour:[8,12,17,20]}, function () {
47
+    remindMiaoguoNewUser();//秒过开班各种通知
48
+});
49
+
50
+//每天9点、13点、18点、21点各执行一次
51
+schedule.scheduleJob({second:0,minute:0,hour:[9,13,18,21]}, function () {
52
+    updateProductServiceTimeIsNull();//解决到期时间可能为空的问题。
53
+});
54
+
55
+
56
+//每天上午8点执行一次
57
+schedule.scheduleJob({second:0,minute:0,hour:8}, function () {
58
+    buildProductServiceTimeRemind(2);//提前两天提醒续费
59
+    buildProductServiceTimeRemind(0);//最后一天提醒续费
60
+    buildDeleteThreeMonth();
61
+});
62
+
63
+//每天上午10点执行一次
64
+schedule.scheduleJob({second:0,minute:0,hour:10}, function () {
65
+    buildProductServiceTime();//给奖励用户时间
66
+});
67
+
68
+//每周二执行一次
69
+schedule.scheduleJob({second:0,minute:0,hour:1,dayOfWeek:2}, function () {
70
+    buildMiaoguoClassSchedule();//更新秒过排班表
71
+});
72
+
73
+
74
+
75
+//处理舒尔特游戏数据
76
+function realSchulteGame(){
77
+    //console.log(new Date());
78
+
79
+    var dateStart=moment().add(-1,"d").format('YYYY-MM-DD 00:00:00');
80
+    schulte.GetUserNumberByEveryDay(dateStart).then((listEveryday)=>{
81
+        schulte.GetUserScoreNumberByEveryDay({dateStart:dateStart}).then((listEverydayScore)=> {
82
+            schulte.GetUserScoreNumberByEveryDay({dateStart:dateStart,type:"share"}).then((listEverydayScoreShare)=> {
83
+                for(var i=0;i<listEveryday.length;i++){
84
+                    listEveryday[i].游戏题卡总数=0;
85
+                    listEveryday[i].生成分享图片数=0;
86
+
87
+                    for (var j = 0; j < listEverydayScore.length; j++) {
88
+                        if (listEveryday[i].日期 == listEverydayScore[j].日期) {
89
+                            listEveryday[i].游戏题卡总数 = listEverydayScore[j].count;
90
+                        }
91
+                    }
92
+                    for (var j = 0; j < listEverydayScoreShare.length; j++) {
93
+                        if (listEveryday[i].日期 == listEverydayScoreShare[j].日期) {
94
+                            listEveryday[i].生成分享图片数 = listEverydayScoreShare[j].count;
95
+                        }
96
+                    }
97
+                }
98
+
99
+
100
+                //临时导入
101
+                var obj = {
102
+                    SchulteDate: listEveryday[0].日期,
103
+                    UserNumber: listEveryday[0].注册人数,
104
+                    GameNumber: listEveryday[0].游戏题卡总数,
105
+                    ShareNumber: listEveryday[0].生成分享图片数
106
+                }
107
+
108
+                schulte.DeleteSchulteStatistics(obj).then(() => {
109
+                    // console.log(obj.SchulteDate);
110
+                    // console.log(obj.UserNumber);
111
+                    // console.log(obj.GameNumber);
112
+                    schulte.AddSchulteStatistics(obj);
113
+                    var yesterday=moment().add(-1,"d").format('YYYY-MM-DD 00:00:00');
114
+                    schulte.DeleteSchulteScores({CreateTime:yesterday});
115
+                });
116
+            });
117
+        });
118
+    });
119
+
120
+    //删除gameStartID
121
+    var today=moment().add(0,"d").format('YYYY-MM-DD 00:00:00');
122
+    var gameStartID=new Date(today).getTime()+"000000";
123
+    //console.log(gameStartID);
124
+    schulte.DeleteSchulteGameStart({GameStartID:gameStartID});
125
+}
126
+
127
+//秒过开班各种通知
128
+/**
129
+ * 发送新用户提醒消息
130
+ * 根据用户加入课程的天数和当前小时,发送对应的微信模板消息提醒
131
+ * 消息模板配置在senddata对象中,key为小时,value为对应天数发送的模板ID数组
132
+ * 使用setInterval分批发送消息,间隔500毫秒
133
+ */
134
+function remindMiaoguoNewUser() {
135
+    //console.log(new Date().getHour);
136
+    var param = {};
137
+    var result = [];
138
+    var senddata = {
139
+        8: [[],["ClassStart"],[],[],[],[],[],[],["GroupMustKnow", "NewUserPresentCoupon"]],
140
+        12: [[],[],[],[],[],[],[],["MembershipDueRemind"],[],],
141
+        17: [[],[],[],[],["LessonRemind"],[],[],[],[]],
142
+        20: [[],[],["ClassStart2"],[],[],[],[],[],[]],
143
+    };
144
+
145
+    miaoguo.GetMiaoguoClassUserList(param).then((list) => {
146
+        for(var i = 0; i <list.length;i++) {
147
+            list[i].StartDate = list[i].StartDate + " 00:00:00";
148
+            var ClassDayNumber = moment(moment().format("YYYY-MM-DD 00:00:00")).diff(list[i].StartDate, 'days') + 1;
149
+            list[i].TemplateID = senddata[new Date().getHours()][ClassDayNumber];
150
+            //list[i].TemplateID = senddata[8][4];
151
+            //console.log(JSON.stringify(list[i]));
152
+        }
153
+
154
+        send(list);
155
+
156
+    });
157
+
158
+    function send(list, temp) {
159
+        var index = 0;
160
+        var interval = setInterval(function () {
161
+
162
+            var url = "";
163
+            if (list.length>0 && list[index] && list[index].TemplateID && list[index].TemplateID.length > 0) {
164
+                if (list[index].TemplateID[0]) {
165
+                    url = "http://localhost:" + config.port + "/api/SendWXServiceTemplateMessage?UserID=" + list[index].UserID + "&TemplateID=" + list[index].TemplateID[0];
166
+                    //console.log("url:" + url);
167
+                    originRequest(encodeURI(url), function (err, response, body) {
168
+                    });
169
+                    if (list[index].TemplateID[1]) {
170
+                        url = "http://localhost:" + config.port + "/api/SendWXServiceTemplateMessage?UserID=" + list[index].UserID + "&TemplateID=" + list[index].TemplateID[1];
171
+                        //console.log("url:" + url);
172
+                        originRequest(encodeURI(url), function (err, response, body) {
173
+                        });
174
+                        if (list[index].TemplateID[2]) {
175
+                            url = "http://localhost:" + config.port + "/api/SendWXServiceTemplateMessage?UserID=" + list[index].UserID + "&TemplateID=" + list[index].TemplateID[2];
176
+                            //console.log("url:" + url);
177
+                            originRequest(encodeURI(url), function (err, response, body) {
178
+                            });
179
+                        }
180
+                    }
181
+                }
182
+            }
183
+
184
+            index++;
185
+            if (index >= list.length) {
186
+                clearInterval(interval);
187
+            }
188
+        }, 500);
189
+    }
190
+}
191
+
192
+//remindMiaoguoUserPractice();
193
+
194
+//秒过练习提醒通知
195
+function remindMiaoguoUserPractice() {
196
+
197
+    var time = moment().format('HH:mm');
198
+
199
+    var sql = "select w.UserID from MiaoguoWXUsers u inner join WechatServiceWXUsers w on u.UnionID=w.UnionID where w.Subscribe=1 and u.ProductServiceTime>=Now() and u.IsMessage=1 and u.MessageTime='"+time+"';";
200
+    //console.log(sql);
201
+    var result = [];
202
+    miaoguo.RunSql({}, sql).then((list) => {
203
+        if (list && list.length>0)
204
+            send(list);
205
+    });
206
+
207
+    function send(list) {
208
+        var index = 0;
209
+        var interval = setInterval(function () {
210
+
211
+            var url = "http://localhost:" + config.port + "/api/SendWXServiceTemplateMessage?UserID=" + list[index].UserID + "&TemplateID=PracticeTimingReminder&ParamStr={\"MessageTime\":\""+time+"\"}";
212
+            //console.log("url:" + url);
213
+            originRequest(encodeURI(url), function (err, response, body) {
214
+            });
215
+
216
+
217
+            index++;
218
+            if (index >= list.length) {
219
+                clearInterval(interval);
220
+            }
221
+        }, 500);
222
+    }
223
+}
224
+
225
+//UpdateMiaoguoTask();
226
+
227
+//秒过同步MiaoguoTask
228
+function UpdateMiaoguoTask() {
229
+    //给添加用户发送消息
230
+    var url = 'http://localhost:' + config.port + '/api/updateMiaoguoTaskData';
231
+    console.log("url1:"+url);
232
+    originRequest(url);
233
+
234
+    setTimeout(function () {
235
+        var sql = "select UserID,DATE_FORMAT(CreateTime,'%Y-%m-%d') as CreateTime,count(ID) from MiaoguoTask group by UserID,DATE_FORMAT(CreateTime,'%Y-%m-%d') having count(ID)>1;";
236
+        var result = [];
237
+        miaoguo.RunSql({}, sql).then((list) => {
238
+            var index = 0;
239
+            if (list && list.length>0) {
240
+                var interval = setInterval(function () {
241
+                    var item = list[index];
242
+                    if (item && item.UserID) {
243
+                        //console.log(item.UserID);
244
+                        var sql2 = "delete from MiaoguoTask where UserID=" + item.UserID + " and DATE_FORMAT(CreateTime,'%Y-%m-%d')='" + item.CreateTime + "' order by ID desc limit 1;";
245
+                        miaoguo.RunSql({}, sql2);
246
+                    }
247
+
248
+                    index++;
249
+                    if (index >= list.length) {
250
+                        clearInterval(interval);
251
+                        //console.log("OK");
252
+                    }
253
+                }, 200);
254
+            }
255
+        });
256
+    }, 1000 * 60 * 20);
257
+}
258
+
259
+
260
+//让秒过介绍人与秒过学习法的介绍人同步
261
+schedule.scheduleJob({minute: [5,35]}, function () {
262
+
263
+    //秒过小程序有介绍人,但公众号没有介绍人,同步数据
264
+    var sql="select w.UserID,u.Introducer from MiaoguoWXUsers u inner join WechatServiceWXUsers w on u.UnionID=w.UnionID where u.Introducer>0 and w.Introducer=0;";
265
+    miaoguo.RunSql({},sql).then((list)=> {
266
+        if (list && list.length>0){
267
+            for(var i=0;i<list.length;i++){
268
+                var sql2="update WechatServiceWXUsers set Introducer="+list[i].Introducer+" where UserID="+list[i].UserID;
269
+                miaoguo.RunSql({},sql2).then(()=> {});
270
+            }
271
+        }
272
+    });
273
+
274
+    setTimeout(function(){
275
+        //把只有公众号用户,没有秒过小程序用户的数据同步到秒过小程序
276
+        var sql="update MiaoguoWXUsers u,(select u.UserID,w.TempClassID from MiaoguoWXUsers u inner join WechatServiceWXUsers w on u.UnionID=w.UnionID where w.TempClassID>0 and u.ClassID=0) a set u.ClassID=a.TempClassID where u.UserID=a.UserID;";
277
+        miaoguo.RunSql({},sql).then(()=> {});
278
+    },10000);
279
+
280
+    miaoguo.RunSql({},"SELECT * FROM kylx365_db.MiaoguoClassSchedule where Flag=0 order by ClassID limit 1;").then((classInfo)=>{
281
+        var startTime=moment(classInfo[0].StartDate).format('YYYY-MM-DD 00:00:00');
282
+        var sevenday=moment(startTime).add(6,"days").format("YYYY-MM-DD 23:59:59");
283
+        var classID=classInfo[0].ClassID;
284
+        // console.log(sevenday);
285
+        // console.log(classID);
286
+        setTimeout(function(){
287
+            var sql="update MiaoguoWXUsers set ProductServiceTime='"+sevenday+"' where ClassID="+classID+" and IsPay=0;";
288
+            miaoguo.RunSql({},sql).then(()=> {});
289
+        },30000);
290
+    });
291
+
292
+    //如果有用户更新微信昵称而丢失,则找回(临时用)
293
+    var sql="update MiaoguoWXUsers set NickName=TempNickName,AvatarUrl=TempAvatarUrl where NickName='陌生用户' and TempNickName is not Null;";
294
+    miaoguo.RunSql({},sql).then((list)=> {});
295
+});
296
+
297
+
298
+schedule.scheduleJob({second:0}, function () {
299
+    //让已经在秒过付过费的人,在服务号上显示已经付费(每分钟检查一次)
300
+    var sql="select w.UserID from WechatServiceWXUsers w inner join MiaoguoWXUsers u on w.UnionID =u.UnionID where w.Subscribe=1 and w.IsProbation=0 and u.IsApply=1;";
301
+    miaoguo.RunSql({},sql).then((list)=> {
302
+            if (list && list.length>0){
303
+            for(var i=0;i<list.length;i++){
304
+                var sql2="update WechatServiceWXUsers set IsProbation=1,ProbationPayTime=current_timestamp() where UserID="+list[i].UserID;
305
+                miaoguo.RunSql({},sql2).then(()=> {});
306
+            }
307
+        }
308
+    });
309
+});
310
+
311
+//当有题卡ID却没有内容时,则删除
312
+function deleteNullMiaoguoCard(){
313
+
314
+    var sql3="select c.MiaoguoCardID from MiaoguoCard c left join MiaoguoContent mc on c.MiaoguoCardID=mc.MiaoguoCardID where mc.Content is null;";
315
+        miaoguo.RunSql({},sql3).then((list)=> {
316
+            for(var i=0;i<list.length;i++){
317
+            var item=list[i];
318
+            var sql4="delete from MiaoguoCard where MiaoguoCardID="+item.MiaoguoCardID+";";
319
+            miaoguo.RunSql({},sql4).then(()=> {});
320
+        }
321
+    });
322
+}
323
+
324
+
325
+//buildProductServiceTime();
326
+
327
+//每天计算到期日期,给奖励用户时间
328
+function buildProductServiceTime() {
329
+    var Today = moment().add(-30, "days").format("YYYY-MM-DD 00:00:00");
330
+    var ProductServiceTime = moment().add(7, "days").format("YYYY-MM-DD 23:59:59");
331
+    var sql1="select UserID,NickName,ProductServiceTime,PromotionDeliveryTimeIsUp from MiaoguoWXUsers where ProductServiceTime>='"+Today+"' and ProductServiceTime<='" + ProductServiceTime + "' and IsPay=1 and UserID not in (105,141,204,206,216,252,568,1355,1356,6137);";
332
+    //var sql1="select UserID,NickName,ProductServiceTime from MiaoguoWXUsers where ProductServiceTime>='"+Today+"' and ProductServiceTime<='" + ProductServiceTime + "' and IsPay=1 and UserID not in (105,141,204,206,216,252,568,1355,1356,6137) and UserID=8603;";
333
+    //console.log("sql1:"+sql1);
334
+    miaoguo.RunSql({}, sql1).then((list) => {
335
+        //console.log(list);
336
+        if (list.length>0){
337
+            var index=0;
338
+            var interval=setInterval(function(){
339
+                var item=list[index];
340
+
341
+                //结算1元返时间
342
+                var sql2="select UserID from WechatServiceWXUsers where Introducer="+item.UserID+" and IsProbation=1 and IsRebate=0;";
343
+                //console.log("sql2:"+sql2);
344
+                miaoguo.RunSql({},sql2).then((list2)=> {
345
+                    var param={};
346
+                    if (list2.length>0){
347
+                        param.UserID=item.UserID;
348
+                        param.AwardDay=list2.length*5;
349
+                        var sql3="INSERT INTO MiaoguoAward SET ?;";
350
+                        //console.log("sql3:"+sql3);
351
+                        miaoguo.RunSql(param,sql3).then((inserted)=> {
352
+                            //console.log(inserted);
353
+                            var awardID=inserted.insertId;
354
+
355
+                            var time4 = moment().add(-7, "days").format("YYYY-MM-DD 23:59:59");
356
+                            var sql7="update WechatServiceWXUsers set IsRebate="+awardID+" where Introducer="+param.UserID+" and IsRebate=0 and ProbationPayTime<'"+time4+"';";
357
+                            //console.log("sql7:"+sql7);
358
+                            miaoguo.RunSql({},sql7).then(()=> {});
359
+
360
+                            var time8 = moment(item.ProductServiceTime).add(param.AwardDay, "days").format("YYYY-MM-DD 23:59:59");
361
+                            var sql8="update MiaoguoWXUsers set ProductServiceTime='"+time8+"' where UserID="+param.UserID+";";
362
+                            //console.log("sql8:"+sql8);
363
+                            miaoguo.RunSql({},sql8).then(()=> {});
364
+
365
+                            var sql9 = "SELECT m.*,w.UserID as 'WechatUserID',w.OpenID as 'WCOpenID',w.IsProbation,w.Introducer FROM kylx365_db.WechatServiceWXUsers w inner join MiaoguoWXUsers m on w.UnionID=m.UnionID where m.UserID="+param.UserID+";";
366
+                            //console.log("sql9:"+sql9);
367
+                            miaoguo.RunSql({},sql9).then((userInfo)=> {
368
+                                var url = "http://localhost:" + config.port + "/api/SendWXServiceTemplateMessage?UserID=" + userInfo[0].WechatUserID + "&TemplateID=LimitTimeEndAwardSend&ParamStr={\"DayNumber\":"+param.AwardDay+"}";
369
+                                //console.log("url:" + url);
370
+                                originRequest(encodeURI(url), function (err, response, body) {});
371
+                            });
372
+
373
+                        });
374
+                    }
375
+
376
+                });
377
+
378
+                //结算199元返时间
379
+                var sql2="select UserID from WechatServiceWXUsers where Introducer="+item.UserID+" and IsProbation=1 and IsRebate2=0;";
380
+                //console.log("sql2:"+sql2);
381
+                miaoguo.RunSql({},sql2).then((list2)=> {
382
+                    var param={};
383
+                    if (list2.length>0){
384
+                        var time4 = moment().add(-7, "days").format("YYYY-MM-DD 23:59:59");
385
+                        var time5 = moment(item.PromotionDeliveryTimeIsUp).format("YYYY-MM-DD 23:59:59");
386
+                        var sql4="select UserID,UnionID from MiaoguoWXUsers where Introducer="+item.UserID+" and IsPay=1 and IsRebate=0 and PayTime<'"+time4+"' and PayTime<'"+time5+"';";
387
+                        //console.log("sql4:"+sql4);
388
+                        miaoguo.RunSql({}, sql4).then((list4) => {
389
+                            //console.log(list4.length>0);
390
+                            if (list4.length>0){
391
+
392
+                                param.UserID=item.UserID;
393
+                                param.AwardDay=list4.length*115;
394
+
395
+                                var sql3="INSERT INTO MiaoguoAward SET ?;";
396
+                                //console.log("sql3:"+sql3);
397
+                                miaoguo.RunSql(param,sql3).then((inserted)=> {
398
+                                    //console.log(inserted);
399
+                                    var awardID=inserted.insertId;
400
+
401
+                                    var sql6="update MiaoguoWXUsers set IsRebate="+awardID+" where Introducer="+item.UserID+" and ParentUserID=0 and IsPay=1 and IsRebate=0 and PayTime<'"+time4+"';";
402
+                                    //console.log("sql6:"+sql6);
403
+                                    miaoguo.RunSql({},sql6).then(()=> {});
404
+
405
+                                    var sql7="update WechatServiceWXUsers set IsRebate2="+awardID+" where Introducer="+item.UserID+" and IsRebate2=0 and ProbationPayTime<'"+time4+"';";
406
+                                    //console.log("sql7:"+sql7);
407
+                                    miaoguo.RunSql({},sql7).then(()=> {});
408
+
409
+                                    var time8 = moment(item.ProductServiceTime).add(param.AwardDay, "days").format("YYYY-MM-DD 23:59:59");
410
+                                    var sql8="update MiaoguoWXUsers set ProductServiceTime='"+time8+"' where UserID="+param.UserID+";";
411
+                                    //console.log("sql8:"+sql8);
412
+                                    miaoguo.RunSql({},sql8).then(()=> {});
413
+
414
+                                    var sql9 = "SELECT m.*,w.UserID as 'WechatUserID',w.OpenID as 'WCOpenID',w.IsProbation,w.Introducer FROM kylx365_db.WechatServiceWXUsers w inner join MiaoguoWXUsers m on w.UnionID=m.UnionID where m.UserID="+param.UserID+";";
415
+                                    //console.log("sql9:"+sql9);
416
+                                    miaoguo.RunSql({},sql9).then((userInfo)=> {
417
+                                        if (userInfo && userInfo[0] && userInfo[0].WechatUserID){
418
+                                            var url = "http://localhost:" + config.port + "/api/SendWXServiceTemplateMessage?UserID=" + userInfo[0].WechatUserID + "&TemplateID=LimitTimeEndAwardSend&ParamStr={\"DayNumber\":"+param.AwardDay+"}";
419
+                                            //console.log("url:" + url);
420
+                                            originRequest(encodeURI(url), function (err, response, body) {});
421
+                                        }
422
+                                    });
423
+
424
+                                });
425
+                            }
426
+                        });
427
+                    }
428
+                });
429
+
430
+                index++;
431
+                if (index>=list.length){
432
+                    clearInterval(interval);
433
+                }
434
+            },500);
435
+        }
436
+    });
437
+}
438
+
439
+
440
+
441
+//每天计算到期日期,提醒用户
442
+function buildProductServiceTimeRemind(remindDayNumber) {
443
+    var ProductServiceTime = moment().add(remindDayNumber, "days").format("YYYY-MM-DD");
444
+    //console.log("ProductServiceTime:"+ProductServiceTime);
445
+    miaoguo.RunSql({}, "select UserID,NickName,ProductServiceTime from MiaoguoWXUsers where ProductServiceTime>='" + ProductServiceTime + " 00:00:00' and ProductServiceTime<='" + ProductServiceTime + " 23:59:59' and IsPay=1 and UserID not in (105,141,204,206,216,252,568,1355,1356,6137);").then((list) => {
446
+        if (list.length>0){
447
+            //console.log(list);
448
+            var index=0;
449
+            var interval=setInterval(function(){
450
+                var item=list[index];
451
+                var sql9 = "SELECT m.*,w.UserID as 'WechatUserID',w.OpenID as 'WCOpenID',w.IsProbation,w.Introducer FROM kylx365_db.WechatServiceWXUsers w inner join MiaoguoWXUsers m on w.UnionID=m.UnionID where m.UserID="+item.UserID+";";
452
+                miaoguo.RunSql({},sql9).then((userInfo)=> {
453
+                    var endDate=moment(userInfo[0].ProductServiceTime).format("YYYY-MM-DD");
454
+                    var url = "http://localhost:" + config.port + "/api/SendWXServiceTemplateMessage?UserID=" + userInfo[0].WechatUserID + "&TemplateID=LimitTimeEndRemind&ParamStr={\"RemindDayNumber\":\""+remindDayNumber+"\"}";;
455
+                    //console.log("url:" + url);
456
+                    originRequest(encodeURI(url), function (err, response, body) {});
457
+                });
458
+                index++;
459
+                if (index>=list.length){
460
+                    clearInterval(interval);
461
+                }
462
+            },500);
463
+        }
464
+    });
465
+}
466
+
467
+//将超过三个月的题卡不显示
468
+function buildDeleteThreeMonth() {
469
+    var deleteTime = moment().add(-90, "days").format("YYYY-MM-DD");
470
+    var sql="update MiaoguoCard set Flag=-2 where Flag=-1 and DeleteTime<'"+deleteTime+"';";
471
+    miaoguo.RunSql({}, sql).then(() => {});
472
+    var sql="update MiaoguoCard set Flag=-2 where Flag=-1 and DeleteTime is null and UpdateTime<'"+deleteTime+"';";
473
+    miaoguo.RunSql({}, sql).then(() => {});
474
+}
475
+
476
+
477
+function buildMiaoguoClassSchedule(){
478
+    miaoguo.RunSql({}, "SELECT * FROM kylx365_db.MiaoguoClassSchedule where Flag=0 order by StartDate limit 1;").then((list) => {
479
+
480
+        var endDate=moment(list[0].StartDate).add(list[0].DayNumber, "days").format("YYYY-MM-DD 00:00:00");
481
+        var now=moment().format("YYYY-MM-DD HH:mm:ss");
482
+
483
+        if (endDate<now){
484
+            var sql="insert into MiaoguoClassSchedule (DayNumber,Flag) values (7,0);\n" +
485
+            "select @dt := date_add(StartDate, interval 14 day) from MiaoguoClassSchedule where Flag=0 and StartDate is not null order by ClassID desc limit 1;\n" +
486
+            "update MiaoguoClassSchedule set Name=Concat(ClassID,'班'),StartDate=@dt where Name is Null;\n" +
487
+            "update MiaoguoClassSchedule set Flag=2 where StartDate<Now() and ClassID>60;"
488
+            miaoguo.RunSql({}, sql).then(() => {});
489
+        }
490
+
491
+    });
492
+}
493
+//buildPayUserID();
494
+//更新支付时的用户ID
495
+function buildPayUserID(){
496
+    var sql="update ProductPayInfo p,WXUsers u set p.UserID=u.UserID where p.OpenID=u.OpenID and p.UserID=0 and p.ProductID=89;\n" +
497
+        "update ProductPayInfo p,PinyinWXUsers u set p.UserID=u.UserID where p.OpenID=u.OpenID and p.UserID=0 and p.ProductID=98;\n" +
498
+        "update ProductPayInfo p,PhonicsWXUsers u set p.UserID=u.UserID where p.OpenID=u.OpenID and p.UserID=0 and p.ProductID=99;\n" +
499
+        "update ProductPayInfo p,HanziWXUsers u set p.UserID=u.UserID where p.OpenID=u.OpenID and p.UserID=0 and p.ProductID=106;\n" +
500
+        "update ProductPayInfo p,MathCalculationWXUsers u set p.UserID=u.UserID where p.OpenID=u.OpenID and p.UserID=0 and p.ProductID=105;\n" +
501
+        "update ProductPayInfo p,MathStarWXUsers u set p.UserID=u.UserID where p.OpenID=u.OpenID and p.UserID=0 and p.ProductID=164;\n" +
502
+        "update ProductPayInfo p,ParentsHelperWXUsers u set p.UserID=u.UserID where p.OpenID=u.OpenID and p.UserID=0 and p.ProductID=107;\n" +
503
+        "update ProductPayInfo p,MiaoguoWXUsers u set p.UserID=u.UserID where p.OpenID=u.OpenID and p.UserID=0 and p.ProductID=166;\n" +
504
+        "update ProductPayInfo p,WechatServiceWXUsers u set p.UserID=u.UserID where p.OpenID=u.OpenID and p.UserID=0 and p.ProductID=167;";
505
+    //console.log(sql);
506
+    miaoguo.RunSql({},sql);
507
+}
508
+
509
+//老口算提醒
510
+function remindOldMath(){
511
+    var time = moment().format('HH:mm');
512
+    //console.log(time);
513
+
514
+    userAll.DeleteFormId().then(()=>{
515
+        var day=moment().day();
516
+        if (day==0)
517
+            day=7;
518
+        var param={
519
+            MessageTime:time,
520
+            Day:day,
521
+        };
522
+        userAll.GetTodayRemindUsers(param).then((list) => {
523
+            //console.log("list:" + list.length);
524
+            if (list.length>0) {
525
+                var item, index = 0;
526
+                var interval = setInterval(function () {
527
+                    item = list[index];
528
+
529
+                    ////**********************
530
+                    ////正式
531
+                    var url = "https://www.kylx365.com/api/SendRemind/?ProgramID=" + item.ProgramID + "&OpenID=" + item.OpenID + "&UserID=" + item.UserID + "&UserFormID=" + item.UserFormID + "&FormID=" + item.FormID;
532
+                    originRequest(encodeURI(url), function (err, response, body) {
533
+                        // if (body) {
534
+                        //     console.log(item.UserID + " send ok");
535
+                        // }
536
+                    });
537
+                    ////**********************
538
+
539
+                    //////**********************
540
+                    ////测试
541
+                    //var url = "http://localhost:"+config.port+"/api/SendRemind/?ProgramID=" + item.ProgramID + "&OpenID=" + item.OpenID + "&UserID=" + item.UserID + "&UserFormID=" + item.UserFormID + "&FormID=" + item.FormID;
542
+                    //console.log("url:" + url);
543
+                    //if (item.UserID==4 || item.UserID==1) {
544
+                    //    originRequest(encodeURI(url), function (err, response, body) {
545
+                    //        if (body) {
546
+                    //            console.log(item.UserID + " send ok");
547
+                    //        }
548
+                    //    });
549
+                    //}
550
+                    //////**********************
551
+
552
+                    index++;
553
+                    if (index >= list.length)
554
+                        clearInterval(interval);
555
+
556
+                }, 500);
557
+            }
558
+        });
559
+    });
560
+}
561
+
562
+function updateProductServiceTimeIsNull(){
563
+    var sql="SELECT * FROM MiaoguoWXUsers where ProductServiceTime<'2000-01-01' and IsPay=1;";
564
+    miaoguo.RunSql({},sql).then((list) => {
565
+
566
+        console.log("list:" + list.length);
567
+
568
+        if (list.length>0){
569
+            var index = 0;
570
+            var interval = setInterval(function () {
571
+                var item = list[index];
572
+
573
+                var sql="Select * FROM kylx365_db.ProductPayInfo where OpenID='"+item.OpenID+"' and Status=1 order by ID desc limit 1;";
574
+                miaoguo.RunSql({},sql).then((list2) => {
575
+                    if (list2 && list2.length>0){
576
+                    var item2 = list2[0];
577
+                    item2.ProductServiceTime=moment(item2.ProductServiceTime).format('YYYY-MM-DD HH:mm:ss');
578
+                    var sql2 = "update MiaoguoWXUsers set ProductServiceTime='" + item2.ProductServiceTime + "' where OpenID='" + item.OpenID + "' and ProductServiceTime<'2000-01-01';";
579
+                    //console.log(sql2);
580
+                    miaoguo.RunSql({},sql2);
581
+                }
582
+                //console.log(index);
583
+            });
584
+
585
+                index++;
586
+                if (index >= list.length) {
587
+                    clearInterval(interval);
588
+                    console.log("OK");
589
+                }
590
+
591
+            },300);
592
+        }
593
+
594
+    });
595
+}
596
+
597
+//(临时)秒过开班各种通知,仅在需要时使用
598
+//schedule.scheduleJob({second:0,minute:29,hour:8}, function () {
599
+//    console.log(new Date().getHour);
600
+//    var param={};
601
+//    var result=[];
602
+//    var senddata={
603
+//        8:[
604
+//            [],
605
+//            ["ClassStart"],
606
+//        ],
607
+//    };
608
+//
609
+//    miaoguo.GetMiaoguoClassUserList(param).then((list)=> {
610
+//        for(var i = 0; i < list.length; i++) {
611
+//        list[i].StartDate = list[i].StartDate + " 00:00:00";
612
+//        var ClassDayNumber = moment(moment().format("YYYY-MM-DD 00:00:00")).diff(list[i].StartDate, 'days') + 1;
613
+//        list[i].TemplateID = senddata[new Date().getHours()][ClassDayNumber];
614
+//        //list[i].TemplateID = senddata[8][4];
615
+//        console.log(JSON.stringify(list[i]));
616
+//    }
617
+//
618
+//    send(list);
619
+//
620
+//});
621
+//
622
+//function send(list,temp){
623
+//    var index=0;
624
+//    var interval=setInterval(function(){
625
+//
626
+//        var url ="";
627
+//        if (list[index].TemplateID && list[index].TemplateID.length>0){
628
+//            if (list[index].TemplateID[0]) {
629
+//                url = "http://localhost:" + config.port + "/api/SendWXServiceTemplateMessage?UserID=" + list[index].UserID + "&TemplateID=" + list[index].TemplateID[0];
630
+//                console.log("url:" + url);
631
+//                originRequest(encodeURI(url), function (err, response, body) {
632
+//                });
633
+//                if (list[index].TemplateID[1]) {
634
+//                    url = "http://localhost:" + config.port + "/api/SendWXServiceTemplateMessage?UserID=" + list[index].UserID + "&TemplateID=" + list[index].TemplateID[1];
635
+//                    console.log("url:" + url);
636
+//                    originRequest(encodeURI(url), function (err, response, body) {
637
+//                    });
638
+//                    if (list[index].TemplateID[2]) {
639
+//                        url = "http://localhost:" + config.port + "/api/SendWXServiceTemplateMessage?UserID=" + list[index].UserID + "&TemplateID=" + list[index].TemplateID[2];
640
+//                        console.log("url:" + url);
641
+//                        originRequest(encodeURI(url), function (err, response, body) {
642
+//                        });
643
+//                    }
644
+//                }
645
+//            }
646
+//        }
647
+//
648
+//        index++;
649
+//        if (index>=list.length){
650
+//            clearInterval(interval);
651
+//        }
652
+//    },500);
653
+//}
654
+//
655
+//});
656
+
657
+//schedule.scheduleJob({second:0,minute:59,hour:8,dayOfWeek:[1,2,3,4,5]}, function () {
658
+//    var date=moment().format("MMDD");
659
+//    if (date=="0101" || (date>="0501" && date<="0503") || (date>="1001" && date<="1007")){
660
+//
661
+//    }
662
+//    else{
663
+//        var time = moment().format('HH:mm:ss');
664
+//        console.log("关闭收费服务"+time);
665
+//        var param={
666
+//            ProgramID:98,
667
+//            IsShowPay:0,
668
+//        };
669
+//        parentsHelper.UpdateProductVersionList(param);
670
+//        var param={
671
+//            ProgramID:99,
672
+//            IsShowPay:0,
673
+//        };
674
+//        parentsHelper.UpdateProductVersionList(param);
675
+//        var param={
676
+//            ProgramID:89,
677
+//            IsShowPay:0,
678
+//        };
679
+//        parentsHelper.UpdateProductVersionList(param);
680
+//        var param={
681
+//            ProgramID:106,
682
+//            IsShowPay:0,
683
+//        };
684
+//        parentsHelper.UpdateProductVersionList(param);
685
+//        var param={
686
+//            ProgramID:105,
687
+//            IsShowPay:0,
688
+//        };
689
+//        parentsHelper.UpdateProductVersionList(param);
690
+//        var param={
691
+//            ProgramID:164,
692
+//            IsShowPay:0,
693
+//        };
694
+//        parentsHelper.UpdateProductVersionList(param);
695
+//
696
+//    }
697
+//
698
+//});
699
+//
700
+//schedule.scheduleJob({second:0,minute:30,hour:18,dayOfWeek:[1,2,3,4,5]}, function () {
701
+//    var time = moment().format('HH:mm');
702
+//    console.log("打开收费服务"+time);
703
+//    var param={
704
+//        ProgramID:98,
705
+//        IsShowPay:1,
706
+//    };
707
+//    parentsHelper.UpdateProductVersionList(param);
708
+//    var param={
709
+//        ProgramID:99,
710
+//        IsShowPay:1,
711
+//    };
712
+//    parentsHelper.UpdateProductVersionList(param);
713
+//    var param={
714
+//        ProgramID:89,
715
+//        IsShowPay:1,
716
+//    };
717
+//    parentsHelper.UpdateProductVersionList(param);
718
+//    var param={
719
+//        ProgramID:106,
720
+//        IsShowPay:1,
721
+//    };
722
+//    parentsHelper.UpdateProductVersionList(param);
723
+//    var param={
724
+//        ProgramID:105,
725
+//        IsShowPay:1,
726
+//    };
727
+//    parentsHelper.UpdateProductVersionList(param);
728
+//    var param={
729
+//        ProgramID:164,
730
+//        IsShowPay:1,
731
+//    };
732
+//    parentsHelper.UpdateProductVersionList(param);
733
+//
734
+//});
735
+
736
+//payInfo.GetTempMoney().then((list)=> {
737
+//    console.log("list:" + list.length);
738
+//    _(list).forEach(item=> {
739
+//        if (item.NickName2.length > 4) {
740
+//            item.NickName2 = item.NickName2.substr(0, 4) + "...";
741
+//        }
742
+//        var url = "http://localhost:"+config.port+"/api/PayUserRebate/?OpenID=" + item.OpenID + "&Money=1000&Name=" + item.NickName2;
743
+//        console.log("url:" + url);
744
+//        originRequest(encodeURI(url), function (err, response, body) {
745
+//            if (err)
746
+//                console.log(item.NickName + ": "+err);
747
+//            else {
748
+//                if (body) {
749
+//                    console.log(item.NickName + " send ok");
750
+//                    var param = {
751
+//                        UserID: item.min,
752
+//                        IsRebate: 1,
753
+//                    }
754
+//                    users.UpdateUsersByUserID(param);
755
+//                }
756
+//            }
757
+//        });
758
+//    });
759
+//});
760
+

+ 8 - 0
src/model/miaoguo.js

@@ -90,6 +90,14 @@ class Miaoguo {
90 90
         return await query(sql, [obj.UnionID]);
91 91
     }
92 92
 
93
+    static async GetMiaoguoClassUserList() {
94
+        let sql = "select wsu.UserID,wsu.NickName,mcs.StartDate,wsu.Subscribe,mcs.Name from MiaoguoClassSchedule mcs inner join MiaoguoWXUsers u on mcs.ClassID=u.ClassID inner join WechatServiceWXUsers wsu on wsu.UnionID=u.UnionID where mcs.Flag=0 and u.IsMember=1 order by mcs.ClassID asc,wsu.UserID;";
95
+        if (process.env.NODE_ENV === 'development') {
96
+            sql = "select wsu.UserID,wsu.NickName,mcs.StartDate,wsu.Subscribe,mcs.Name from MiaoguoClassSchedule mcs inner join MiaoguoWXUsers u on mcs.ClassID=u.ClassID inner join WechatServiceWXUsers wsu on wsu.UnionID=u.UnionID where mcs.Flag=0 and u.IsMember=1 and u.UserID=1 order by mcs.ClassID asc,wsu.UserID;";
97
+        }
98
+        return await query(sql);
99
+    }
100
+
93 101
     static async UpdateWechatServiceUsersByUserID(obj) {
94 102
         const sql = "Update WechatServiceWXUsers SET ? where UserID=?";
95 103
         return await query(sql, [obj, obj.UserID]);

+ 6 - 24
src/model/wxUsersAll.js

@@ -84,7 +84,8 @@ export async function GetWXUsersAllPayInfo(obj) {
84 84
     INNER JOIN [tableName] u ON ppi.OpenID=u.OpenID
85 85
     WHERE u.UserID=? AND ppi.Status=1 AND pbi.Status>=0`;
86 86
 
87
-  let parts = [];
87
+  const parts = [];
88
+  const params = [];
88 89
 
89 90
   const tables = [
90 91
     { field: 'UserID89', table: 'WXUsers' },
@@ -100,6 +101,7 @@ export async function GetWXUsersAllPayInfo(obj) {
100 101
   for (const t of tables) {
101 102
     if (obj[t.field]) {
102 103
       parts.push(sql2Template.replace('[tableName]', t.table));
104
+      params.push(obj[t.field]);
103 105
     }
104 106
   }
105 107
 
@@ -112,31 +114,11 @@ export async function GetWXUsersAllPayInfo(obj) {
112 114
       LEFT JOIN PinyinWXUsers u2 ON ppi.ExchangeUserID=u2.UserID
113 115
       WHERE u.UserID=? AND ppi.PayType=5 AND ppi.Status=1`;
114 116
     parts.push(exchangeSql);
117
+    params.push(obj.UserID98);
115 118
   }
116 119
 
117
-  let sql = parts.map(p => {
118
-    if (p.includes('?')) {
119
-      const uidField = tables.find(t => t.field === 'UserID98' && p.includes('PinyinWXUsers')) 
120
-        ? null 
121
-        : tables.find(t => p.includes(t.table));
122
-      return p;
123
-    }
124
-    return p;
125
-  }).join(' UNION ALL ');
126
-
127
-  if (sql.startsWith(' UNION ALL ')) sql = sql.substring(11);
128
-
129
-  const params = [];
130
-  if (obj.UserID89) params.push(obj.UserID89);
131
-  if (obj.UserID98) { params.push(obj.UserID98); params.push(obj.UserID98); }
132
-  if (obj.UserID99) params.push(obj.UserID99);
133
-  if (obj.UserID106) params.push(obj.UserID106);
134
-  if (obj.UserID105) params.push(obj.UserID105);
135
-  if (obj.UserID164) params.push(obj.UserID164);
136
-  if (obj.UserID107) params.push(obj.UserID107);
137
-  if (obj.UserID166) params.push(obj.UserID166);
138
-
139
-  return await query(sql, params);
120
+  if (parts.length === 0) return [];
121
+  return await query(parts.join(' UNION ALL '), params);
140 122
 }
141 123
 
142 124
 export async function DeleteFormId() {