Browse Source

Merge branch 'master' of www.gitinn.com:zaixianjiaoyu/sourcecode

KaysonCui 5 years ago
parent
commit
3aae9d70e5
36 changed files with 3603 additions and 2595 deletions
  1. 2396 2374
      front/package-lock.json
  2. 21 10
      front/project/admin/routes/interaction/faq/page.js
  3. 1 1
      front/project/admin/routes/student/askCourseDetail/page.js
  4. 6 1
      front/project/www/routes/course/answer/page.js
  5. 1 1
      front/project/www/routes/paper/question/page.js
  6. 7 0
      front/project/www/routes/question/detail/page.js
  7. 86 15
      server/data/src/main/java/com/qxgmat/data/constants/enums/MessageCategory.java
  8. 16 4
      server/data/src/main/java/com/qxgmat/data/constants/enums/MessageType.java
  9. 7 4
      server/data/src/main/java/com/qxgmat/data/constants/enums/ServiceKey.java
  10. 1 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/trade/RecordSource.java
  11. 8 6
      server/data/src/main/java/com/qxgmat/data/constants/enums/user/AskTarget.java
  12. 24 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/user/FeedbackTarget.java
  13. 105 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookLibrary.java
  14. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserTextbookFeedback.java
  15. 7 3
      server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookLibraryMapper.xml
  16. 3 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserTextbookFeedbackMapper.xml
  17. 8 1
      server/data/src/main/resources/db/migration/V1__init_table.sql
  18. 8 5
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/CourseController.java
  19. 8 7
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/QuestionController.java
  20. 14 4
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java
  21. 2 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/TextbookController.java
  22. 20 5
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java
  23. 3 3
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  24. 0 10
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/FaqDto.java
  25. 19 2
      server/gateway-api/src/main/java/com/qxgmat/help/MailHelp.java
  26. 2 2
      server/gateway-api/src/main/java/com/qxgmat/help/WechatHelp.java
  27. 16 0
      server/gateway-api/src/main/java/com/qxgmat/service/UserServiceService.java
  28. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/UsersService.java
  29. 541 89
      server/gateway-api/src/main/java/com/qxgmat/service/extend/MessageExtendService.java
  30. 34 10
      server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java
  31. 6 3
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookLibraryService.java
  32. 74 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderRecordService.java
  33. 31 5
      server/gateway-api/src/main/java/com/qxgmat/task/AsyncTask.java
  34. 78 17
      server/gateway-api/src/main/java/com/qxgmat/task/ScheduledTask.java
  35. 3 2
      server/gateway-api/src/main/resources/application.yml
  36. 11 6
      server/tools/src/main/java/com/nuliji/tools/third/sendcloud/SendCloudMail.java

File diff suppressed because it is too large
+ 2396 - 2374
front/package-lock.json


+ 21 - 10
front/project/admin/routes/interaction/faq/page.js

@@ -3,10 +3,10 @@ import './index.less';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
 import FilterLayout from '@src/layouts/FilterLayout';
-// import ActionLayout from '@src/layouts/ActionLayout';
+import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
 import { getMap, formatDate, formatTreeData, bindSearch, flattenTree } from '@src/services/Tools';
-import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
+import { asyncSMessage, asyncForm, asyncDelConfirm } from '@src/services/AsyncTools';
 import { FaqChannel, SwitchSelect, AnswerStatus, MoneyRange } from '../../../../Constant';
 import { System } from '../../../stores/system';
 import { User } from '../../../stores/user';
@@ -25,6 +25,12 @@ const FaqChannelFlatten = flattenTree(FaqChannelTree, (row, item) => {
 const FaqChannelMap = getMap(FaqChannelFlatten, 'value', 'label');
 export default class extends Page {
   init() {
+    this.actionList = [{
+      key: 'ignore',
+      type: 'danger',
+      name: '批量忽略',
+      needSelect: 1,
+    }];
     this.formF = null;
     this.itemList = [{
       key: 'id',
@@ -146,11 +152,6 @@ export default class extends Page {
       dataIndex: 'handler',
       render: (text, record) => {
         return <div className="table-button">
-          {(
-            <a onClick={() => {
-              this.editAction(record);
-            }}>编辑</a>
-          )}
           {record.answerStatus === 0 && (
             <a onClick={() => {
               this.answerAction(record);
@@ -223,7 +224,7 @@ export default class extends Page {
 
   answerAction(row) {
     asyncForm('回复', this.answerList, row, data => {
-      data.sendUser = true;
+      data.status = 1;
       return System.editFAQ(data).then(() => {
         asyncSMessage('回复成功!');
         this.refresh();
@@ -240,6 +241,16 @@ export default class extends Page {
     });
   }
 
+  ignoreAction() {
+    const { selectedKeys } = this.state;
+    asyncDelConfirm('忽略确认', '是否忽略选中记录?', () => {
+      return Promise.all(selectedKeys.map(row => System.editFAQ({ id: row, status: 2 }))).then(() => {
+        asyncSMessage('操作成功!');
+        this.refresh();
+      });
+    });
+  }
+
   renderView() {
     const { search } = this.state;
     return <Block flex>
@@ -252,11 +263,11 @@ export default class extends Page {
           data.channel = data.channel.join('-');
           this.search(data);
         }} />
-      {/* <ActionLayout
+      <ActionLayout
         itemList={this.actionList}
         selectedKeys={this.state.selectedKeys}
         onAction={key => this.onAction(key)}
-      /> */}
+      />
       <TableLayout
         columns={this.tableSort(this.columns)}
         list={this.state.list}

+ 1 - 1
front/project/admin/routes/student/askCourseDetail/page.js

@@ -158,7 +158,7 @@ export default class extends Page {
           this.orderQuestion(oldIndex, newIndex);
         }}
         renderItem={(item) => (
-          <List.Item actions={[<Icon type='bars' className='icon' />, <Typography.Text copyable={{ text: `${PcUrl}/course/ask?askId=${item.id}` }} />]}>
+          <List.Item actions={[<Icon type='bars' className='icon' />, <Typography.Text copyable={{ text: `${PcUrl}/course/answer/${item.courseId}?courseNoId=${item.courseNoId}&askId=${item.id}` }} />]}>
             <Row style={{ width: '100%' }}>
               <Col span={11}>问题:<span dangerouslySetInnerHTML={{ __html: item.content }} /></Col>
               <Col span={11} offset={1}>答复:<span dangerouslySetInnerHTML={{ __html: item.answer }} /></Col>

+ 6 - 1
front/project/www/routes/course/answer/page.js

@@ -88,6 +88,11 @@ export default class extends Page {
     });
     // paixu
     Course.listAsk(Object.assign({ courseId: id }, this.state.search)).then(result => {
+      // 只显示单个提问
+      if (this.state.search.askId) {
+        const askId = Number(this.state.search.askId);
+        result.list = result.list.filter(row => row.id === askId);
+      }
       this.setState({ list: result.list, total: result.total });
     });
   }
@@ -121,7 +126,7 @@ export default class extends Page {
     this.initData();
   }
 
-  onAction() {}
+  onAction() { }
 
   delAsk(id) {
     My.delCourseAsk(id).then(() => {

+ 1 - 1
front/project/www/routes/paper/question/page.js

@@ -80,7 +80,7 @@ export default class extends Page {
       // 只显示单个提问
       if (search.askId) {
         const askId = Number(search.askId);
-        userQuestion.asks = (userQuestion.asks || []).filter(row => row.askId === askId);
+        userQuestion.asks = (userQuestion.asks || []).filter(row => row.id === askId);
       }
       this.setState({ userQuestion, question, questionNo, note, paper, report, questionNos, questionStatus });
     });

+ 7 - 0
front/project/www/routes/question/detail/page.js

@@ -7,6 +7,7 @@ import Detail from '../../paper/question/detail';
 export default class extends Page {
   initData() {
     const { id } = this.params;
+    const { search } = this.state;
     Question.getInfoById(id).then(userQuestion => {
       const { question, questionNos, note, questionStatus } = userQuestion;
       let { questionNo, paper } = userQuestion;
@@ -15,6 +16,12 @@ export default class extends Page {
       if (!question.answerDistributed) question.answerDistributed = { questions: [] };
       if (!userQuestion.userAnswer) userQuestion.userAnswer = { questions: [] };
       if (!paper) paper = {};
+
+      // 只显示单个提问
+      if (search.askId) {
+        const askId = Number(search.askId);
+        userQuestion.asks = (userQuestion.asks || []).filter(row => row.id === askId);
+      }
       this.setState({ userQuestion, question, questionNo, note, paper, questionNos, questionStatus });
     });
   }

+ 86 - 15
server/data/src/main/java/com/qxgmat/data/constants/enums/MessageCategory.java

@@ -5,28 +5,63 @@ package com.qxgmat.data.constants.enums;
  */
 public enum MessageCategory {
     REGISTER("register", "注册消息"),
+    REAL("real", "实名认证"),
+    PREPARE("prepare", "完善备考信息"),
+    INVITED("invited", "邀请好友注册","/", "立即注册"),
+    INVITED_SUCCESS("invited_success", "邀请成功"),
+    EMAIL_CHANGE("email_change", "邮箱变更"),
+    EMAIL_UNNBIND("email_unbind", "邮箱解绑"),
     LOGIN_ABNORMAL("login_abnormal", "登录异常"),
-    TEXTBOOK_LIBRARY("textbook_library","机经换库"),
+
+    TEXTBOOK_LIBRARY("textbook_library","机经换库", "/textbook", "获取机经"),
+    TEXTBOOK_UPDATE_SUBSCRIBE("textbook_update_subscribe","机经更新-订阅", "/textbook", "在线查看"),
+    TEXTBOOK_UPDATE("textbook_update","机经更新", "/textbook", "在线查看"),
+
     PREVIEW_NOTICE("preview_notice", "预习作业提醒"),
 
-    DATA_UPDATE("data_update", "资料更新"),
-    ASK_QUESTION("ask_question", "题目提问回复"),
-    ASK_COURSE("ask_course", "课程提问回复"),
-    FAQ_CALLBACK("faq_callback", "咨询回复"),
-    FEEDBACK_CALLBACK("feedback_callback", "纠错回复"),
+    DATA_UPDATE_PAPER("data_update_paper", "纸质资料更新", "/course/data/detail/{id}", "在线查看","/course/data?tab=history&dataId={id}", "更新日志"),
+    DATA_UPDATE_BASE("data_update_base", "资料更新", "/course/data/detail/{id}", "在线查看","/course/data?tab=history&dataId={id}", "更新日志"),
+    DATA_UPDATE_SUBSCRIBE("data_update_subscribe", "资料订阅","/course/data/detail/{id}", "在线查看","/course/data?tab=history&dataId={id}", "更新日志"),
 
-    INVITED("invited", "邀请好友注册"),
-    EMAIL_CHANGE("email_change", "邮箱变更"),
-    EMAIL_UNNBIND("email_unbind", "邮箱解绑"),
+    ASK_QUESTION_HANDLE("ask_question_handle", "题目提问回复"),
+    ASK_QUESTION_SPECIAL("ask_question_special", "题目提问回复", "/question/detail/{questionNoId}?askId={id}", "查看详情"),
+    ASK_QUESTION_IGNORE("ask_question_ignore", "题目提问忽略"),
+    ASK_COURSE_HANDLE("ask_course_handle", "课程提问回复"),
+    ASK_COURSE_SPECIAL("ask_course_special", "课程提问回复", "/course/answer/{courseId}?courseNoId={courseNoId}&askId={id}", "查看详情"),
+    ASK_COURSE_IGNORE("ask_course_ignore", "课程提问忽略"),
+
+    FAQ_HANDLE("faq_handle", "咨询回复-回答"),
+    FAQ_IGNORE("faq_ignore", "咨询回复-忽略"),
+
+    FEEDBACK_ERROR_HANDLE("feedback_error_handle", "纠错回复-采纳"),
+    FEEDBACK_ERROR_IGNORE("feedback_error_ignore", "纠错回复-忽略"),
+    FEEDBACK_ERROR_NOHANDLE("feedback_error_nohandle", "纠错回复-不处理"),
 
-    COURSE_USE_EXPIRE("course_use_expire", "课程使用到期提醒"),
-    COURSE_EXPIRE("course_expire", "课程开通到期提醒"),
+    TEXTBOOK_FEEDBACK_HANDLE("textbook_feedback_handle", "机经反馈回复-采纳"),
+    TEXTBOOK_FEEDBACK_IGNORE("textbook_feedback_ignore", "机经反馈回复-忽略"),
+    TEXTBOOK_FEEDBACK_NOHANDLE("textbook_feedback_nohandle", "机经反馈回复-不处理"),
 
-    TEXTBOOK_USE_EXPIRE("textbook_use_expire", "机经使用到期提醒"),
-    TEXTBOOK_EXPIRE("textbook_expire", "机经开通到期提醒"),
+    PAY_MULTI("pay_multi", "多个购买"),
 
-    QX_CAT_USE_EXPIRE("qx_cat_use_expire", "模考使用到期提醒"),
-    QX_CAT_EXPIRE("qx_cat_expire", "模考开通到期提醒"),
+    DATA_PAY("data_pay", "资料购买"),
+    DATA_PAY_MULTI("data_pay_multi", "多份资料购买"),
+
+    COURSE_PAY("course_pay", "课程购买"),
+    COURSE_PAY_MULTI("course_pay_multi", "多个课程购买"),
+    COURSE_USE_EXPIRE("course_use_expire", "课程使用到期提醒", "/my/course", "立刻使用", true),
+    COURSE_OPEN_EXPIRE("course_open_expire", "课程开通到期提醒", "/my/course", "立即开通"),
+    COURSE_GIFT("course_gift", "课程赠品"),
+
+    TEXTBOOK_PAY("textbook_pay", "机经购买", "/my/tools?tab=textbook", "立即开通"),
+    TEXTBOOK_USE_EXPIRE("textbook_use_expire", "机经使用到期提醒","/textbook", "立刻使用", true),
+    TEXTBOOK_OPEN_EXPIRE("textbook_open_expire", "机经开通到期提醒", "/my/tools?tab=textbook", "立即开通"),
+
+    QX_CAT_PAY("qx_cat_pay", "模考购买", "/my/tools?tab=examination", "立即开通"),
+    QX_CAT_USE_EXPIRE("qx_cat_use_expire", "模考使用到期提醒", "/examination", "立刻使用", true),
+    QX_CAT_OPEN_EXPIRE("qx_cat_open_expire", "模考开通到期提醒", "/my/tools?tab=examination", "立即开通"),
+
+    VIP_PAY("vip_pay", "vip购买"),
+    VIP_USE_EXPIRE("vip_use_expire", "vip使用到期提醒", "/my", "立刻使用", true),
 
     CUSTOM("custom", "自定义消息")
     ;
@@ -35,11 +70,47 @@ public enum MessageCategory {
 
     public String key;
     public String title;
+
+    public String link;
+    public String linkTitle;
+    // 只有email使用该link
+    public Boolean emailLink = false;
+
+    public String linkSecond;
+    public String linkSecondTitle;
     private MessageCategory(String key, String title){
         this.key = key;
         this.title = title;
     }
 
+    private MessageCategory(String key, String title, String link, String linkTitle){
+        this.key = key;
+        this.title = title;
+
+        this.link = link;
+        this.linkTitle = linkTitle;
+    }
+
+    private MessageCategory(String key, String title, String link, String linkTitle, Boolean emailLink){
+        this.key = key;
+        this.title = title;
+
+        this.link = link;
+        this.linkTitle = linkTitle;
+        this.emailLink = true;
+    }
+
+    private MessageCategory(String key, String title, String link, String linkTitle, String linkSecond, String linkSecondTitle){
+        this.key = key;
+        this.title = title;
+
+        this.link = link;
+        this.linkTitle = linkTitle;
+
+        this.linkSecond = linkSecond;
+        this.linkSecondTitle = linkSecondTitle;
+    }
+
     public static MessageCategory ValueOf(String name){
         if (name == null) return null;
         return MessageCategory.valueOf(name.toUpperCase());

+ 16 - 4
server/data/src/main/java/com/qxgmat/data/constants/enums/MessageType.java

@@ -28,10 +28,22 @@ public enum MessageType {
 
     public static MessageType FromCategory(MessageCategory messageCategory){
         switch (messageCategory){
-            case ASK_QUESTION:
-            case ASK_COURSE:
-            case FAQ_CALLBACK:
-            case FEEDBACK_CALLBACK:
+            case ASK_QUESTION_HANDLE:
+            case ASK_QUESTION_IGNORE:
+            case ASK_COURSE_HANDLE:
+            case ASK_COURSE_IGNORE:
+            case FAQ_HANDLE:
+            case FAQ_IGNORE:
+            case FEEDBACK_ERROR_HANDLE:
+            case FEEDBACK_ERROR_IGNORE:
+            case FEEDBACK_ERROR_NOHANDLE:
+            case TEXTBOOK_FEEDBACK_HANDLE:
+            case TEXTBOOK_FEEDBACK_IGNORE:
+            case TEXTBOOK_FEEDBACK_NOHANDLE:
+            case PREVIEW_NOTICE:
+            case DATA_UPDATE_BASE:
+            case DATA_UPDATE_PAPER:
+            case DATA_UPDATE_SUBSCRIBE:
                 return FEED;
             default:
                 return SYSTEM;

+ 7 - 4
server/data/src/main/java/com/qxgmat/data/constants/enums/ServiceKey.java

@@ -1,9 +1,9 @@
 package com.qxgmat.data.constants.enums;
 
 public enum ServiceKey {
-    VIP("vip", 0, 0), // 收藏和错题处的组卷、导出;笔记导出功能;部分解析只有VIP可以看;下载模考报告; 解锁完整版模考报告;“提问开放”期间有提问权限
-    TEXTBOOK("textbook", 180, 30),
-    QX_CAT("qx_cat", 180, 180), // 可以考2次
+    VIP("vip", "VIP", 0, 0), // 收藏和错题处的组卷、导出;笔记导出功能;部分解析只有VIP可以看;下载模考报告; 解锁完整版模考报告;“提问开放”期间有提问权限
+    TEXTBOOK("textbook", "千行-CAT模考(6套)", 180, 30),
+    QX_CAT("qx_cat", "千行机经", 180, 180), // 可以考2次
 
     ;
     public String key;
@@ -14,10 +14,13 @@ public enum ServiceKey {
     // 天数
     public Integer useExpireDay;
 
-    private ServiceKey(String key, Integer expireDay, Integer useExpireDay){
+    public String title;
+
+    private ServiceKey(String key, String title, Integer expireDay, Integer useExpireDay){
         this.key = key;
         this.expireDay = expireDay;
         this.useExpireDay = useExpireDay;
+        this.title = title;
     }
 
     public static ServiceKey ValueOf(String name){

+ 1 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/trade/RecordSource.java

@@ -12,6 +12,7 @@ public enum RecordSource {
     // 赠送用gift开头,方便过滤
     GIFT_COURSE("gift_course"),
     GIFT_ACTIVITY("gift_activity"),
+    GIFT_ACTION("gift_action"),
 
     ;
     public String key;

+ 8 - 6
server/data/src/main/java/com/qxgmat/data/constants/enums/user/AskTarget.java

@@ -2,15 +2,17 @@ package com.qxgmat.data.constants.enums.user;
 
 
 public enum AskTarget {
-    QUESTION("question"),
-    OFFICIAL("official"),
-    QX("qx"),
-    ASSOCIATION("association"),
-    QA("qa"),
+    QUESTION("question", "题目"),
+    OFFICIAL("official", "官方解析"),
+    QX("qx", "千行解析"),
+    ASSOCIATION("association", "题源联想"),
+    QA("qa", "相关问答"),
     ;
     public String key;
-    private AskTarget(String key){
+    public String title;
+    private AskTarget(String key, String title){
         this.key = key;
+        this.title = title;
     }
 
     public static AskTarget ValueOf(String name){

+ 24 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/user/FeedbackTarget.java

@@ -0,0 +1,24 @@
+package com.qxgmat.data.constants.enums.user;
+
+
+public enum FeedbackTarget {
+    CORRECT("correct", "纠正解析", "正确的解析和答案是", "需要修改,已更正"),
+    PERFECT("perfect", "完善已有机经", "补充内容是", "已采纳"),
+    NEW("new", "千行解析", "新机经是", "已采纳"),
+    ;
+    public String key;
+    public String title;
+    public String result;
+    public String handle;
+    private FeedbackTarget(String key, String title, String result, String handle){
+        this.key = key;
+        this.title = title;
+        this.result = result;
+        this.handle = handle;
+    }
+
+    public static FeedbackTarget ValueOf(String name){
+        if (name == null || name.isEmpty()) return null;
+        return FeedbackTarget.valueOf(name.toUpperCase());
+    }
+}

+ 105 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookLibrary.java

@@ -42,6 +42,12 @@ public class TextbookLibrary implements Serializable {
     private Date quantTime;
 
     /**
+     * 数学更新简介
+     */
+    @Column(name = "`quant_description`")
+    private String quantDescription;
+
+    /**
      * 数学题目数
      */
     @Column(name = "`quant_number`")
@@ -66,6 +72,12 @@ public class TextbookLibrary implements Serializable {
     private Date irTime;
 
     /**
+     * 综合逻辑更新简介
+     */
+    @Column(name = "`ir_description`")
+    private String irDescription;
+
+    /**
      * 综合逻辑题目数
      */
     @Column(name = "`ir_number`")
@@ -90,6 +102,12 @@ public class TextbookLibrary implements Serializable {
     private Date rcTime;
 
     /**
+     * 阅读更新简介
+     */
+    @Column(name = "`rc_description`")
+    private String rcDescription;
+
+    /**
      * 阅读题目数
      */
     @Column(name = "`rc_number`")
@@ -220,6 +238,24 @@ public class TextbookLibrary implements Serializable {
     }
 
     /**
+     * 获取数学更新简介
+     *
+     * @return quant_description - 数学更新简介
+     */
+    public String getQuantDescription() {
+        return quantDescription;
+    }
+
+    /**
+     * 设置数学更新简介
+     *
+     * @param quantDescription 数学更新简介
+     */
+    public void setQuantDescription(String quantDescription) {
+        this.quantDescription = quantDescription;
+    }
+
+    /**
      * 获取数学题目数
      *
      * @return quant_number - 数学题目数
@@ -292,6 +328,24 @@ public class TextbookLibrary implements Serializable {
     }
 
     /**
+     * 获取综合逻辑更新简介
+     *
+     * @return ir_description - 综合逻辑更新简介
+     */
+    public String getIrDescription() {
+        return irDescription;
+    }
+
+    /**
+     * 设置综合逻辑更新简介
+     *
+     * @param irDescription 综合逻辑更新简介
+     */
+    public void setIrDescription(String irDescription) {
+        this.irDescription = irDescription;
+    }
+
+    /**
      * 获取综合逻辑题目数
      *
      * @return ir_number - 综合逻辑题目数
@@ -364,6 +418,24 @@ public class TextbookLibrary implements Serializable {
     }
 
     /**
+     * 获取阅读更新简介
+     *
+     * @return rc_description - 阅读更新简介
+     */
+    public String getRcDescription() {
+        return rcDescription;
+    }
+
+    /**
+     * 设置阅读更新简介
+     *
+     * @param rcDescription 阅读更新简介
+     */
+    public void setRcDescription(String rcDescription) {
+        this.rcDescription = rcDescription;
+    }
+
+    /**
      * 获取阅读题目数
      *
      * @return rc_number - 阅读题目数
@@ -457,14 +529,17 @@ public class TextbookLibrary implements Serializable {
         sb.append(", quant=").append(quant);
         sb.append(", quantVersion=").append(quantVersion);
         sb.append(", quantTime=").append(quantTime);
+        sb.append(", quantDescription=").append(quantDescription);
         sb.append(", quantNumber=").append(quantNumber);
         sb.append(", ir=").append(ir);
         sb.append(", irVersion=").append(irVersion);
         sb.append(", irTime=").append(irTime);
+        sb.append(", irDescription=").append(irDescription);
         sb.append(", irNumber=").append(irNumber);
         sb.append(", rc=").append(rc);
         sb.append(", rcVersion=").append(rcVersion);
         sb.append(", rcTime=").append(rcTime);
+        sb.append(", rcDescription=").append(rcDescription);
         sb.append(", rcNumber=").append(rcNumber);
         sb.append(", historyNumber=").append(historyNumber);
         sb.append(", questionStatus=").append(questionStatus);
@@ -534,6 +609,16 @@ public class TextbookLibrary implements Serializable {
         }
 
         /**
+         * 设置数学更新简介
+         *
+         * @param quantDescription 数学更新简介
+         */
+        public Builder quantDescription(String quantDescription) {
+            obj.setQuantDescription(quantDescription);
+            return this;
+        }
+
+        /**
          * 设置数学时间
          *
          * @param quantTime 数学时间
@@ -574,6 +659,16 @@ public class TextbookLibrary implements Serializable {
         }
 
         /**
+         * 设置综合逻辑更新简介
+         *
+         * @param irDescription 综合逻辑更新简介
+         */
+        public Builder irDescription(String irDescription) {
+            obj.setIrDescription(irDescription);
+            return this;
+        }
+
+        /**
          * 设置综合逻辑时间
          *
          * @param irTime 综合逻辑时间
@@ -614,6 +709,16 @@ public class TextbookLibrary implements Serializable {
         }
 
         /**
+         * 设置阅读更新简介
+         *
+         * @param rcDescription 阅读更新简介
+         */
+        public Builder rcDescription(String rcDescription) {
+            obj.setRcDescription(rcDescription);
+            return this;
+        }
+
+        /**
          * 设置阅读时间
          *
          * @param rcTime 阅读时间

+ 35 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/UserTextbookFeedback.java

@@ -42,6 +42,12 @@ public class UserTextbookFeedback implements Serializable {
     private Integer libraryId;
 
     /**
+     * 处理人id
+     */
+    @Column(name = "`manager_id`")
+    private Integer managerId;
+
+    /**
      * 反馈类型
      */
     @Column(name = "`target`")
@@ -175,6 +181,24 @@ public class UserTextbookFeedback implements Serializable {
     }
 
     /**
+     * 获取处理人id
+     *
+     * @return manager_id - 处理人id
+     */
+    public Integer getManagerId() {
+        return managerId;
+    }
+
+    /**
+     * 设置处理人id
+     *
+     * @param managerId 处理人id
+     */
+    public void setManagerId(Integer managerId) {
+        this.managerId = managerId;
+    }
+
+    /**
      * 获取反馈类型
      *
      * @return target - 反馈类型
@@ -272,6 +296,7 @@ public class UserTextbookFeedback implements Serializable {
         sb.append(", no=").append(no);
         sb.append(", topicId=").append(topicId);
         sb.append(", libraryId=").append(libraryId);
+        sb.append(", managerId=").append(managerId);
         sb.append(", target=").append(target);
         sb.append(", createTime=").append(createTime);
         sb.append(", status=").append(status);
@@ -351,6 +376,16 @@ public class UserTextbookFeedback implements Serializable {
         }
 
         /**
+         * 设置处理人id
+         *
+         * @param managerId 处理人id
+         */
+        public Builder managerId(Integer managerId) {
+            obj.setManagerId(managerId);
+            return this;
+        }
+
+        /**
          * 设置反馈类型
          *
          * @param target 反馈类型

+ 7 - 3
server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookLibraryMapper.xml

@@ -11,14 +11,17 @@
     <result column="quant" jdbcType="VARCHAR" property="quant" />
     <result column="quant_version" jdbcType="INTEGER" property="quantVersion" />
     <result column="quant_time" jdbcType="TIMESTAMP" property="quantTime" />
+    <result column="quant_description" jdbcType="VARCHAR" property="quantDescription" />
     <result column="quant_number" jdbcType="INTEGER" property="quantNumber" />
     <result column="ir" jdbcType="VARCHAR" property="ir" />
     <result column="ir_version" jdbcType="INTEGER" property="irVersion" />
     <result column="ir_time" jdbcType="TIMESTAMP" property="irTime" />
+    <result column="ir_description" jdbcType="VARCHAR" property="irDescription" />
     <result column="ir_number" jdbcType="INTEGER" property="irNumber" />
     <result column="rc" jdbcType="VARCHAR" property="rc" />
     <result column="rc_version" jdbcType="INTEGER" property="rcVersion" />
     <result column="rc_time" jdbcType="TIMESTAMP" property="rcTime" />
+    <result column="rc_description" jdbcType="VARCHAR" property="rcDescription" />
     <result column="rc_number" jdbcType="INTEGER" property="rcNumber" />
     <result column="history_number" jdbcType="INTEGER" property="historyNumber" />
     <result column="question_status" jdbcType="INTEGER" property="questionStatus" />
@@ -29,8 +32,9 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `start_date`, `end_date`, `quant`, `quant_version`, `quant_time`, `quant_number`, 
-    `ir`, `ir_version`, `ir_time`, `ir_number`, `rc`, `rc_version`, `rc_time`, `rc_number`, 
-    `history_number`, `question_status`, `create_time`, `update_time`
+    `id`, `start_date`, `end_date`, `quant`, `quant_version`, `quant_time`, `quant_description`, 
+    `quant_number`, `ir`, `ir_version`, `ir_time`, `ir_description`, `ir_number`, `rc`, 
+    `rc_version`, `rc_time`, `rc_description`, `rc_number`, `history_number`, `question_status`, 
+    `create_time`, `update_time`
   </sql>
 </mapper>

+ 3 - 2
server/data/src/main/java/com/qxgmat/data/dao/mapping/UserTextbookFeedbackMapper.xml

@@ -11,6 +11,7 @@
     <result column="no" jdbcType="INTEGER" property="no" />
     <result column="topic_id" jdbcType="INTEGER" property="topicId" />
     <result column="library_id" jdbcType="INTEGER" property="libraryId" />
+    <result column="manager_id" jdbcType="INTEGER" property="managerId" />
     <result column="target" jdbcType="VARCHAR" property="target" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
     <result column="status" jdbcType="INTEGER" property="status" />
@@ -26,8 +27,8 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `question_subject`, `no`, `topic_id`, `library_id`, `target`, `create_time`, 
-    `status`, `handle_time`
+    `id`, `user_id`, `question_subject`, `no`, `topic_id`, `library_id`, `manager_id`, 
+    `target`, `create_time`, `status`, `handle_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 8 - 1
server/data/src/main/resources/db/migration/V1__init_table.sql

@@ -373,7 +373,7 @@ CREATE TABLE message_template (
   id int(11) unsigned NOT NULL AUTO_INCREMENT,
   title varchar(255) NOT NULL DEFAULT '' COMMENT '消息标题',
   message_method varchar(20) NOT NULL DEFAULT '' COMMENT '消息形式',
-  message_category varchar(20) NOT NULL DEFAULT '' COMMENT '消息类型',
+  message_category varchar(50) NOT NULL DEFAULT '' COMMENT '消息类型',
   content text COMMENT '消息内容',
   link varchar(255) NOT NULL DEFAULT '' COMMENT '消息链接',
   send_time datetime DEFAULT NULL COMMENT '发送时间',
@@ -743,14 +743,17 @@ CREATE TABLE textbook_library (
   quant varchar(255) NOT NULL DEFAULT '' COMMENT '数学',
   quant_version int(11) unsigned NOT NULL DEFAULT '0' COMMENT '数学版本',
   quant_time datetime DEFAULT NULL COMMENT '数学时间',
+  quant_description varchar(255) DEFAULT NULL COMMENT '数学更新简介',
   quant_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '数学题目数',
   ir varchar(255) NOT NULL DEFAULT '' COMMENT '综合逻辑',
   ir_version int(11) unsigned NOT NULL DEFAULT '0' COMMENT '综合逻辑版本',
   ir_time datetime DEFAULT NULL COMMENT '综合逻辑时间',
+  ir_description varchar(255) DEFAULT NULL COMMENT '综合逻辑更新简介',
   ir_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '综合逻辑题目数',
   rc varchar(255) NOT NULL DEFAULT '' COMMENT '阅读',
   rc_version int(10) unsigned NOT NULL DEFAULT '0' COMMENT '阅读版本',
   rc_time datetime DEFAULT NULL COMMENT '阅读时间',
+  rc_description varchar(255) DEFAULT NULL COMMENT '阅读更新简介',
   rc_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '阅读题目数',
   history_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新次数',
   question_status tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '提问状态',
@@ -842,12 +845,15 @@ CREATE TABLE textbook_library_history (
   quant varchar(255) DEFAULT NULL COMMENT '数学',
   quant_version int(11) unsigned NOT NULL DEFAULT '0' COMMENT '数学版本',
   quant_content text COMMENT '数学更新日志',
+  quant_description varchar(255) DEFAULT NULL COMMENT '数学更新简介',
   rc varchar(255) DEFAULT NULL COMMENT '阅读',
   rc_version int(11) unsigned NOT NULL DEFAULT '0' COMMENT '阅读版本',
   rc_content text COMMENT '阅读更新日志',
+  rc_description varchar(255) DEFAULT NULL COMMENT '阅读更新简介',
   ir varchar(255) DEFAULT NULL COMMENT '综合推理',
   ir_version int(11) unsigned NOT NULL DEFAULT '0' COMMENT '综合推理版本',
   ir_content text COMMENT '综合推理更新日志',
+  ir_description varchar(255) DEFAULT NULL COMMENT '综合逻辑更新简介',
   create_time datetime DEFAULT NULL,
   PRIMARY KEY (id),
   KEY library_id (library_id)
@@ -1461,6 +1467,7 @@ CREATE TABLE user_textbook_feedback (
   no int(11) unsigned NOT NULL DEFAULT 0 COMMENT '序号',
   topic_id int(11) unsigned NOT NULL DEFAULT 0 COMMENT '机经问题',
   library_id int(11) unsigned NOT NULL COMMENT '换库表',
+  manager_id int(11) unsigned NOT NULL DEFAULT 0 COMMENT '处理人id',
   target varchar(20) NOT NULL DEFAULT '' COMMENT '反馈类型',
   content text COMMENT '正确内容',
   create_time datetime DEFAULT NULL,

+ 8 - 5
server/gateway-api/src/main/java/com/qxgmat/controller/admin/CourseController.java

@@ -759,17 +759,20 @@ public class CourseController {
         UserAskCourse entity = Transform.dtoToEntity(dto);
         UserAskCourse in = userAskCourseService.get(entity.getId());
         // 调整回答
-        if(entity.getAnswer() != null && (!entity.getAnswer().isEmpty() || !in.getAnswer().equals(entity.getAnswer()))){
+        if(in.getAnswerStatus() == 0 && dto.getAnswer() != null || dto.getIgnoreStatus() > 0){
             entity.setAnswerTime(new Date());
-            entity.setAnswerStatus(AnswerStatus.ANSWER.index);
+
+            if (dto.getIgnoreStatus() != null && dto.getIgnoreStatus() > 0){
+                entity.setAnswerStatus(AnswerStatus.IGNORE.index);
+            }else{
+                entity.setAnswerStatus(AnswerStatus.ANSWER.index);
+            }
             Manager manager = shiroHelp.getLoginManager();
             entity.setManagerId(manager.getId());
+
             User user = usersService.get(in.getUserId());
             messageExtendService.sendAskCourse(user, entity);
         }
-        if (dto.getIgnoreStatus() != null && dto.getIgnoreStatus() > 0){
-            entity.setAnswerStatus(AnswerStatus.IGNORE.index);
-        }
 
         entity = userAskCourseService.edit(entity);
 

+ 8 - 7
server/gateway-api/src/main/java/com/qxgmat/controller/admin/QuestionController.java

@@ -181,18 +181,19 @@ public class QuestionController {
     public Response<Boolean> editAsk(@RequestBody @Validated UserAskQuestionDto dto, HttpServletRequest request) {
         UserAskQuestion entity = Transform.dtoToEntity(dto);
         UserAskQuestion in = userAskQuestionService.get(entity.getId());
+
         // 调整回答
-        if(entity.getAnswer() != null && (!entity.getAnswer().isEmpty() || !in.getAnswer().equals(entity.getAnswer()))){
+        if(in.getAnswerStatus() == 0 && dto.getAnswer() != null || dto.getIgnoreStatus() > 0){
             entity.setAnswerTime(new Date());
-            entity.setAnswerStatus(AnswerStatus.ANSWER.index);
+
+            if (dto.getIgnoreStatus() != null && dto.getIgnoreStatus() > 0){
+                entity.setAnswerStatus(AnswerStatus.IGNORE.index);
+            }else{
+                entity.setAnswerStatus(AnswerStatus.ANSWER.index);
+            }
             Manager manager = shiroHelp.getLoginManager();
             entity.setManagerId(manager.getId());
-            User user = usersService.get(in.getUserId());
-            messageExtendService.sendAskQuestion(user, entity);
-        }
 
-        if (dto.getIgnoreStatus() != null && dto.getIgnoreStatus() > 0){
-            entity.setAnswerStatus(AnswerStatus.IGNORE.index);
             User user = usersService.get(in.getUserId());
             messageExtendService.sendAskQuestion(user, entity);
         }

+ 14 - 4
server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java

@@ -19,6 +19,7 @@ import com.qxgmat.dto.admin.response.FaqInfoDto;
 import com.qxgmat.help.ShiroHelp;
 import com.qxgmat.service.UsersService;
 import com.qxgmat.service.extend.MessageExtendService;
+import com.qxgmat.service.extend.OrderFlowService;
 import com.qxgmat.service.inline.*;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -90,6 +91,9 @@ public class SettingController {
     @Autowired
     private CourseDataService courseDataService;
 
+    @Autowired
+    private OrderFlowService orderFlowService;
+
     @RequestMapping(value = "/index", method = RequestMethod.PUT)
     @ApiOperation(value = "修改首页配置", httpMethod = "PUT")
     private Response<Boolean> editIndex(@RequestBody @Validated JSONObject dto){
@@ -553,14 +557,20 @@ public class SettingController {
     private Response<Boolean> editFaq(@RequestBody @Validated FaqDto dto){
         Faq entity = Transform.dtoToEntity(dto);
         Faq in = faqService.get(entity.getId());
-        // 调整回答
-        if(entity.getAnswer() != null && (!entity.getAnswer().isEmpty() || !in.getAnswer().equals(entity.getAnswer()))){
+
+        // 回复
+        if(in.getAnswerStatus() == 0){
             entity.setAnswerTime(new Date());
-            entity.setAnswerStatus(AnswerStatus.ANSWER.index);
             Manager manager = shiroHelp.getLoginManager();
             entity.setManagerId(manager.getId());
-            User user = usersService.get(in.getUserId());
+            entity.setAnswerStatus(dto.getStatus());
+
+            in.setAnswerStatus(entity.getAnswerStatus());
+            in.setAnswer(entity.getAnswer());
             if(in.getIsSystem() == 0){
+                in.setAnswerStatus(dto.getStatus());
+                User user = usersService.get(in.getUserId());
+                UserOrderRecord record = null;
                 messageExtendService.sendFaqCallback(user, in);
             }
         }

+ 2 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/admin/TextbookController.java

@@ -159,6 +159,8 @@ public class TextbookController {
     public Response<Boolean> addLibrary(@RequestBody @Validated TextbookLibraryStartDto dto, HttpServletRequest request) {
         TextbookLibrary entity = Transform.dtoToEntity(dto);
         textbookService.replace(entity);
+        // 发送机经到用户邮箱
+        asyncTask.postTextbookLibrary();
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }

+ 20 - 5
server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java

@@ -6,6 +6,7 @@ import com.nuliji.tools.exception.ParameterException;
 import com.qxgmat.data.constants.enums.ServiceKey;
 import com.qxgmat.data.constants.enums.module.FeedbackModule;
 import com.qxgmat.data.constants.enums.module.ProductType;
+import com.qxgmat.data.constants.enums.status.AnswerStatus;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.status.FeedbackStatus;
 import com.qxgmat.data.constants.enums.trade.PayMethod;
@@ -210,7 +211,7 @@ public class UserController {
         entity.setRealTime(new Date());
         usersService.edit(entity);
 
-        orderFlowService.giveReal(in.getId());
+        orderFlowService.giveReal(in);
         return ResponseHelp.success(true);
     }
 
@@ -495,8 +496,14 @@ public class UserController {
             entity.setHandleTime(new Date());
             Manager manager = shiroHelp.getLoginManager();
             entity.setManagerId(manager.getId());
+
+            in.setStatus(dto.getStatus());
             User user = usersService.get(in.getUserId());
-            messageExtendService.sendFeedbackAnswer(user, in);
+            UserOrderRecord record = null;
+            if (dto.getStatus() == AnswerStatus.ANSWER.index){
+                record = orderFlowService.giveAction(user.getId());
+            }
+            messageExtendService.sendFeedbackError(user, in, record);
         }
 
         entity = userFeedbackErrorService.edit(entity);
@@ -547,17 +554,25 @@ public class UserController {
     @RequestMapping(value = "/textbook_feedback/edit", method = RequestMethod.PUT)
     @ApiOperation(value = "修改机经勘误信息", httpMethod = "PUT")
     public Response<Boolean> editTextbookFeedback(@RequestBody @Validated UserFeedbackErrorDto dto, HttpServletRequest request) {
-        UserFeedbackError entity = Transform.dtoToEntity(dto);
-        UserFeedbackError in = userFeedbackErrorService.get(entity.getId());
+        UserTextbookFeedback entity = Transform.dtoToEntity(dto);
+        UserTextbookFeedback in = userTextbookFeedbackService.get(entity.getId());
 
         // 处理设定
         if(in.getHandleTime() == null){
             entity.setHandleTime(new Date());
             Manager manager = shiroHelp.getLoginManager();
             entity.setManagerId(manager.getId());
+
+            in.setStatus(dto.getStatus());
+            User user = usersService.get(in.getUserId());
+            UserOrderRecord record = null;
+            if (dto.getStatus() == AnswerStatus.ANSWER.index){
+                record = orderFlowService.giveAction(user.getId());
+            }
+            messageExtendService.sendTextbookFeedback(user, in, record);
         }
 
-        entity = userFeedbackErrorService.edit(entity);
+        entity = userTextbookFeedbackService.edit(entity);
 
         managerLogService.log(request);
         return ResponseHelp.success(true);

+ 3 - 3
server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java

@@ -236,7 +236,7 @@ public class MyController {
                 .id(user.getId())
                 .email(dto.getEmail())
                 .build());
-        messageExtendService.sendEmailChange(user, in.getEmail());
+        messageExtendService.sendEmailChange(in, dto.getEmail());
         return ResponseHelp.success(true);
     }
 
@@ -364,7 +364,7 @@ public class MyController {
                 .realStatus(1)
                 .realTime(new Date())
                 .build());
-        orderFlowService.giveReal(user.getId());
+        orderFlowService.giveReal(in);
         return ResponseHelp.success(dto);
     }
 
@@ -458,7 +458,7 @@ public class MyController {
         entity.setId(user.getId());
         if (user.getPrepareTime() == null){
             // 邀请奖励
-            orderFlowService.givePrepare(user.getId());
+            orderFlowService.givePrepare(user);
         }
         entity.setPrepareTime(new Date());
         usersService.edit(entity);

+ 0 - 10
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/FaqDto.java

@@ -25,8 +25,6 @@ public class FaqDto {
 
     private Integer isSpecial;
 
-    private Boolean sendUser;
-
     private Integer status;
 
     private Date createTime;
@@ -103,14 +101,6 @@ public class FaqDto {
         this.answer = answer;
     }
 
-    public Boolean getSendUser() {
-        return sendUser;
-    }
-
-    public void setSendUser(Boolean sendUser) {
-        this.sendUser = sendUser;
-    }
-
     public Integer getIsShow() {
         return isShow;
     }

+ 19 - 2
server/gateway-api/src/main/java/com/qxgmat/help/MailHelp.java

@@ -14,7 +14,9 @@ import org.springframework.core.io.FileSystemResource;
 import org.springframework.stereotype.Service;
 
 import javax.servlet.http.HttpSession;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -39,12 +41,27 @@ public class MailHelp {
 
     public boolean sendBaseMail(String email, String subject, String body){
         if (body==null || body.isEmpty()) return false;
-        SendCloudMail.Response response = mail.sendMail(email, subject, body, from, fromName, null);
+        SendCloudMail.Response response = mail.sendMail(email, subject, body, from, fromName);
         return response.getResult();
     }
 
     public boolean sendAttachMail(String email, String subject, String body, String filePath){
-        SendCloudMail.Response response = mail.sendMail(email, subject, body, from, fromName, new FileSystemResource(filePath));
+        SendCloudMail.Response response;
+        if (filePath ==null || filePath.isEmpty()){
+            response = mail.sendMail(email, subject, body, from, fromName);
+        }else {
+            response = mail.sendMail(email, subject, body, from, fromName, new FileSystemResource(filePath));
+        }
+        return response.getResult();
+    }
+
+    public boolean sendAttachMail(String email, String subject, String body, List<String> filePaths){
+        List<FileSystemResource> atts = new ArrayList<>();
+        for(String file : filePaths){
+            if (file==null || file.isEmpty()) continue;
+            atts.add(new FileSystemResource(file));
+        }
+        SendCloudMail.Response response = mail.sendMail(email, subject, body, from, fromName, atts);
         return response.getResult();
     }
 

+ 2 - 2
server/gateway-api/src/main/java/com/qxgmat/help/WechatHelp.java

@@ -79,10 +79,10 @@ public class WechatHelp {
     public boolean sendMessage(String openid, MessageCategory messageCategory, Map<String, String> dataMap){
         String templateId = "";
         switch (messageCategory){
-            case ASK_QUESTION:
+            case ASK_QUESTION_HANDLE:
                 templateId = this.questionTemplate;
                 break;
-            case ASK_COURSE:
+            case ASK_COURSE_HANDLE:
                 templateId = this.courseTemplate;
                 break;
             default:

+ 16 - 0
server/gateway-api/src/main/java/com/qxgmat/service/UserServiceService.java

@@ -27,6 +27,22 @@ public class UserServiceService extends AbstractService {
     private UserOrderRecordService userOrderRecordService;
 
     /**
+     * 获取所有到期服务
+     * @param startTime
+     * @param endTime
+     * @return
+     */
+    public List<UserService> allExpire(ServiceKey serviceKey, String startTime, String endTime){
+        Example example = new Example(UserService.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("service", serviceKey.key)
+                        .andGreaterThanOrEqualTo("expireTime", startTime)
+                        .andLessThanOrEqualTo("expireTime", endTime)
+        );
+        return select(userServiceMapper, example);
+    }
+    /**
      * 判断是否有权限
      * @param userId
      * @param key

+ 1 - 1
server/gateway-api/src/main/java/com/qxgmat/service/UsersService.java

@@ -207,7 +207,7 @@ public class UsersService extends AbstractService {
                 user.setOriginId(origin.getId());
                 edit(User.builder().id(origin.getId()).inviteNumber(origin.getInviteNumber() + 1).build());
                 // 邀请奖励
-                orderFlowService.giveInvite(origin.getId());
+                orderFlowService.giveInvite(origin);
             }
             // 生成邀请码: 10位字符串
             user.setInviteCode(Tools.getRandomString(10));

+ 541 - 89
server/gateway-api/src/main/java/com/qxgmat/service/extend/MessageExtendService.java

@@ -1,19 +1,25 @@
 package com.qxgmat.service.extend;
 
+import com.alipay.api.domain.TransOrderDetail;
+import com.nuliji.tools.Transform;
 import com.nuliji.tools.exception.ParameterException;
-import com.qxgmat.data.constants.enums.MessageCategory;
-import com.qxgmat.data.constants.enums.MessageMethod;
-import com.qxgmat.data.constants.enums.MessageType;
+import com.qxgmat.data.constants.enums.*;
+import com.qxgmat.data.constants.enums.module.FeedbackModule;
+import com.qxgmat.data.constants.enums.module.ProductType;
 import com.qxgmat.data.constants.enums.status.AnswerStatus;
+import com.qxgmat.data.constants.enums.trade.RecordSource;
+import com.qxgmat.data.constants.enums.user.AskTarget;
+import com.qxgmat.data.constants.enums.user.DataType;
+import com.qxgmat.data.constants.enums.user.FeedbackTarget;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.help.MailHelp;
 import com.qxgmat.help.SmsHelp;
 import com.qxgmat.help.WechatHelp;
-import com.qxgmat.service.inline.MessageTemplateService;
-import com.qxgmat.service.inline.UserMessageService;
+import com.qxgmat.service.inline.*;
 import org.apache.logging.log4j.util.Strings;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
+import sun.plugin2.message.Message;
 
 import javax.annotation.Resource;
 import java.text.SimpleDateFormat;
@@ -39,6 +45,21 @@ public class MessageExtendService {
     private MessageTemplateService messageTemplateService;
 
     @Resource
+    private CourseService courseService;
+
+    @Resource
+    private CourseNoService courseNoService;
+
+    @Resource
+    private CoursePackageService coursePackageService;
+
+    @Resource
+    private QuestionNoService questionNoService;
+
+    @Resource
+    private CourseDataService courseDataService;
+
+    @Resource
     private ToolsService toolsService;
 
     @Value("${url.pc}")
@@ -47,7 +68,10 @@ public class MessageExtendService {
     @Value("${url.h5}")
     private String h5Url;
 
-    private void send(User user, MessageCategory category, Map<String, String>params){
+    @Value("${info.wechat}")
+    private String wechat;
+
+    private void send(User user, MessageCategory category, Map<String, String>params, Integer relationId){
         List<MessageTemplate> templateList = messageTemplateService.listByCategory(category);
         for(MessageTemplate template : templateList){
             MessageMethod messageMethod = MessageMethod.ValueOf(template.getMessageMethod());
@@ -55,10 +79,21 @@ public class MessageExtendService {
             String content = replaceBody(template.getContent(), params);
             switch(messageMethod){
                 case EMAIL:
-                    sendEmail(params.getOrDefault("emails", user.getEmail()), title, content);
+                    if(params.containsKey("link")){
+                        params.put("linkHtml", makeLink(params.get("link"), params.getOrDefault("linkTitle", category.linkTitle), relationId));
+                    }else if(category.link != null ){
+                        params.put("linkHtml", makeLink(category.link, category.linkTitle, relationId));
+                    }
+
+                    if (params.containsKey("linkSecond")){
+                        params.put("linkSecondHtml", makeLink(params.get("linkSecond"), params.getOrDefault("linkTitle", category.linkSecondTitle), relationId));
+                    }else if(category.linkSecond != null){
+                        params.put("linkSecondHtml", makeLink(category.linkSecond, category.linkSecondTitle, relationId));
+                    }
+                    sendEmail(params.getOrDefault("emails", user.getEmail()), title, content, params.get("attachment"), params.get("attachments"));
                     break;
                 case INSIDE:
-                    sendInside(user.getId(), title, content, template.getLink(), category);
+                    sendInside(user.getId(), title, content, relationId, params.getOrDefault("link", template.getLink()), params.getOrDefault("linkTitle", null), category);
                     break;
                 case WECHAT:
                     sendWechat(user.getWechatOpenidWechat(), category, params);
@@ -73,19 +108,26 @@ public class MessageExtendService {
         }
     }
 
-    private UserMessage sendInside(Integer userId, String title, String content, String link, MessageCategory category){
+    private UserMessage sendInside(Integer userId, String title, String content, Integer relationId, String link, String linkTitle, MessageCategory category){
         // 根据模版创建
         return userMessageService.add(UserMessage.builder()
                 .userId(userId)
                 .title(title)
                 .content(content)
+                .relationId(relationId)
                 .link(link)
+                .linkTitle(linkTitle)
                 .type(MessageType.FromCategory(category).key)
                 .isRead(0)
                 .build());
     }
 
-    private void sendEmail(String emails, String title, String body){
+    private void sendEmail(String emails, String title, String body, String attachment, String attachments){
+        if (attachment != null){
+            mailHelp.sendAttachMail(emails, title, body, attachment);
+        }else if(attachments != null){
+            mailHelp.sendAttachMail(emails, title, body, Arrays.stream(attachments.split(";")).collect(Collectors.toList()));
+        }
         mailHelp.sendBaseMail(emails, title, body);
     }
 
@@ -96,14 +138,11 @@ public class MessageExtendService {
     private void sendSms(String area, String mobile, MessageCategory category, Map<String, String> params){
         String[] template;
         Map<String, String> smsParams = new HashMap<>();
-        SimpleDateFormat sdf;
-        String time;
         switch(category){
             case LOGIN_ABNORMAL:
                 template = smsHelp.ABNORMAL_TEMPLATE;
                 // time: yyyy年MM月dd日hh:mm:ss
-                sdf = new SimpleDateFormat("yyyy年MM月dd hh:mm:ss");
-                smsParams.put("time", sdf.format(params.get("time")));
+                smsParams.put("time", params.get("time"));
                 break;
             case TEXTBOOK_LIBRARY:
                 template = smsHelp.LIBRARY_TEMPLATE;
@@ -112,7 +151,11 @@ public class MessageExtendService {
                 // 间隔%jgts%天,库长%kcts%天
                 // 获取机经: %jjdz%
                 // 也可搜索微信订阅号“%dyh%”查阅机经。
-                smsParams.put("dyh", "");
+                smsParams.put("zx_date", params.get("date"));
+                smsParams.put("sc_date", params.get("prevDate"));
+                smsParams.put("jgts", params.get("period"));
+                smsParams.put("kcts", params.get("days"));
+                smsParams.put("dyh", wechat);
                 smsParams.put("jjdz", pcUrl+"/textbook");
                 break;
             case COURSE_USE_EXPIRE:
@@ -120,20 +163,18 @@ public class MessageExtendService {
                 // yhnc: 用户昵称
                 // %kcmc%: 课程名称
                 // %date%: 格式为:yyyy年MM月dd日
-                sdf = new SimpleDateFormat("yyyy年MM月dd");
-                smsParams.put("date", sdf.format(params.get("time")));
+                smsParams.put("date", params.get("expireDate"));
                 smsParams.put("yhnc", params.get("nickname"));
                 smsParams.put("kcmc", params.get("title"));
                 break;
-            case COURSE_EXPIRE:
-            case TEXTBOOK_EXPIRE:
-            case QX_CAT_EXPIRE:
+            case COURSE_OPEN_EXPIRE:
+            case TEXTBOOK_OPEN_EXPIRE:
+            case QX_CAT_OPEN_EXPIRE:
                 template = smsHelp.EXPIRE_TEMPLATE;
                 // yhnc: 用户昵称
                 // %date%: 格式为:yyyy年MM月dd日
                 // wktfw: 未开通的服务名称
-                sdf = new SimpleDateFormat("yyyy年MM月dd");
-                smsParams.put("date", sdf.format(params.get("time")));
+                smsParams.put("date", params.get("expireDate"));
                 smsParams.put("yhnc", params.get("nickname"));
                 smsParams.put("wktfw", params.get("title"));
                 break;
@@ -145,7 +186,19 @@ public class MessageExtendService {
     }
 
     public void refreshMessage(List<UserMessage> messageList){
-
+        for(UserMessage message : messageList){
+            if (message.getLink() != null && message.getLink() != null) continue;
+            MessageCategory category = MessageCategory.ValueOf(message.getMessageCategory());
+            if(category.emailLink) continue;
+            if (category.link != null){
+                message.setLink(replaceLink(category.link, "id", message.getRelationId()));
+                message.setLinkTitle(category.linkTitle);
+            }
+            if (category.linkSecond != null){
+                message.setLinkSecond(replaceLink(category.linkSecond, "id", message.getRelationId()));
+                message.setLinkSecondTitle(category.linkSecondTitle);
+            }
+        }
     }
 
     public void sendCustom(User user, MessageTemplate template, Map<String, String>params){
@@ -155,10 +208,10 @@ public class MessageExtendService {
         String content = replaceBody(template.getContent(), params);
         switch(messageMethod){
             case EMAIL:
-                sendEmail(user.getEmail(), title, content);
+                sendEmail(user.getEmail(), title, content, null, null);
                 break;
             case INSIDE:
-                sendInside(user.getId(), title, content, template.getLink(), MessageCategory.CUSTOM);
+                sendInside(user.getId(), title, content, 0, template.getLink(), null, MessageCategory.CUSTOM);
                 break;
             default:
                 throw new ParameterException("消息发送方式错误");
@@ -175,15 +228,85 @@ public class MessageExtendService {
         map.put("code", code);
         map.put("nickname", user.getNickname());
         map.put("url", pcUrl+"/id/"+code);
-        send(user, MessageCategory.INVITED, map);
+        send(user, MessageCategory.INVITED, map, user.getId());
+    }
+
+    /**
+     * 邀请成功通知
+     * @param user
+     */
+    public void sendInviteSuccess(User user, UserOrderRecord record){
+        Map<String, String> map = new HashMap<>();
+        map.put("nickname", user.getNickname());
+        map.put("number", String.valueOf(user.getInviteNumber()));
+        map.put("useExpireDays", String.valueOf(record.getUseExpireDays()));
+        map.put("expireTime", getTime(record.getUseEndTime()));
+        send(user, MessageCategory.INVITED_SUCCESS, map, record.getId());
+    }
+
+    /**
+     * 实名认证
+     * @param user
+     */
+    public void sendReal(User user, UserOrderRecord record){
+        Map<String, String> map = new HashMap<>();
+        map.put("nickname", user.getNickname());
+        map.put("useExpireDays", String.valueOf(record.getUseExpireDays()));
+        map.put("expireTime", getTime(record.getUseEndTime()));
+        send(user, MessageCategory.REAL, map, record.getId());
+    }
+
+    /**
+     * 完善备考
+     * @param user
+     */
+    public void sendPrepare(User user, UserOrderRecord record){
+        Map<String, String> map = new HashMap<>();
+        map.put("nickname", user.getNickname());
+        map.put("useExpireDays", String.valueOf(record.getUseExpireDays()));
+        map.put("expireTime", getTime(record.getUseEndTime()));
+        send(user, MessageCategory.PREPARE, map, record.getId());
+    }
+
+    /**
+     * 发送机经换库
+     */
+    public void sendTextbookLibrary(User user, TextbookLibrary latest, TextbookLibrary second){
+        Map<String, String> map = new HashMap<>();
+        int days =(int)(latest.getStartDate().getTime() - second.getStartDate().getTime()) / 86400000;
+        map.put("date", getDate(latest.getStartDate()));
+        map.put("prevDate", getDate(second.getStartDate()));
+        map.put("period", String.valueOf(days-1));
+        map.put("days", String.valueOf(days));
+        send(user, MessageCategory.TEXTBOOK_LIBRARY, map, 0);
     }
 
     /**
      * 发送机经更新
      */
-    public void sendTextbookUpdate(User user, TextbookLibrary textbookLibrary){
+    public void sendTextbookUpdate(User user, TextbookLibrary textbookLibrary, Boolean subscribe){
         Map<String, String> map = new HashMap<>();
-        send(user, MessageCategory.TEXTBOOK_LIBRARY, map);
+        Integer version = textbookLibrary.getQuantVersion();
+        if (textbookLibrary.getIrVersion() > version){
+            version = textbookLibrary.getIrVersion();
+        }
+        if (textbookLibrary.getRcVersion() >version){
+            version = textbookLibrary.getRcVersion();
+        }
+        map.put("email", user.getEmail());
+        map.put("version", String.valueOf(version));
+        map.put("quantVersion", String.valueOf(textbookLibrary.getQuantVersion()));
+        map.put("quantDescription", String.valueOf(textbookLibrary.getQuantDescription()));
+        map.put("irVersion", String.valueOf(textbookLibrary.getIrVersion()));
+        map.put("irDescription", String.valueOf(textbookLibrary.getIrDescription()));
+        map.put("rcVersion", String.valueOf(textbookLibrary.getRcVersion()));
+        map.put("rcDescription", String.valueOf(textbookLibrary.getRcDescription()));
+        if (subscribe){
+            map.put("attachments", String.format("%s;%s;%s", textbookLibrary.getQuant(), textbookLibrary.getIr(), textbookLibrary.getRc()));
+            send(user, MessageCategory.TEXTBOOK_UPDATE_SUBSCRIBE, map, 0);
+        }else{
+            send(user, MessageCategory.TEXTBOOK_UPDATE, map, 0);
+        }
     }
 
     /**
@@ -194,7 +317,8 @@ public class MessageExtendService {
         map.put("nickname", user.getNickname());
         map.put("ip", userAbnormal.getLoginIp());
         map.put("city", userAbnormal.getLoginCity());
-        send(user, MessageCategory.LOGIN_ABNORMAL, map);
+        map.put("time", getTime(userAbnormal.getCreateTime()));
+        send(user, MessageCategory.LOGIN_ABNORMAL, map, userAbnormal.getId());
     }
 
     /**
@@ -206,7 +330,7 @@ public class MessageExtendService {
         Map<String, String> map = new HashMap<>();
         map.put("courseTitle", course.getTitle());
         map.put("title", previewAssign.getTitle());
-        send(user, MessageCategory.PREVIEW_NOTICE, map);
+        send(user, MessageCategory.PREVIEW_NOTICE, map, previewAssign.getId());
     }
 
     /**
@@ -215,10 +339,235 @@ public class MessageExtendService {
      * 支付金额:{money}
      * 开通有效期:{expireTime}
      */
-    public void sendPayed(User user, UserOrder userOrder){
+    public void sendPayed(User user, UserOrder userOrder, List<UserOrderRecord> recordList){
         Map<String, String> map = new HashMap<>();
-        map.put("money", userOrder.getMoney().toString());
-        send(user, MessageCategory.PAYED, map);
+        List<UserOrderRecord> pList = recordList.stream().filter((checkout)-> checkout.getParentId() == 0).collect(Collectors.toList());
+
+        map.put("number", String.valueOf(pList.size()));
+        if(userOrder.getProductTypes().size() == 1){
+            String productType = userOrder.getProductTypes().getString(1);
+            UserOrderRecord one = pList.get(0);
+            switch(ProductType.ValueOf(productType)){
+                case SERVICE:
+                    ServiceKey key = ServiceKey.ValueOf(one.getService());
+                    map.put("title", key.title);
+                    map.put("expireDays", String.valueOf(one.getExpireDays()));
+                    map.put("useExpireDays", String.valueOf(one.getUseExpireDays()));
+                    switch(key){
+                        case VIP:
+                            send(user, MessageCategory.VIP_PAY, map, one.getId());
+                            break;
+                        case TEXTBOOK:
+                            send(user, MessageCategory.TEXTBOOK_PAY, map, one.getId());
+                            break;
+                        case QX_CAT:
+                            send(user, MessageCategory.QX_CAT_PAY, map, one.getId());
+                            break;
+                        default:
+                            return;
+                    }
+                    break;
+                case DATA:
+                    CourseData data = courseDataService.get(one.getProductId());
+                    map.put("title", data.getTitle());
+                    if (pList.size() > 1){
+                        Collection dataIds = Transform.getIds(pList, UserOrderRecord.class, "productId");
+                        List<CourseData> courseDataList = courseDataService.select(dataIds);
+                        List<String> dataTitle = new ArrayList<>();
+                        for(CourseData c: courseDataList){
+                            dataTitle.add(c.getTitle());
+                        }
+                        map.put("titleJoin", String.join("”,“", dataTitle));
+                        send(user, MessageCategory.DATA_PAY_MULTI, map, userOrder.getId());
+                    }else{
+                        send(user, MessageCategory.DATA_PAY, map, userOrder.getId());
+                    }
+                    break;
+                case COURSE:
+                    Course course = courseService.get(one.getProductId());
+                    map.put("title", course.getTitle());
+                    if (pList.size() > 1){
+                        Collection courseIds = Transform.getIds(pList, UserOrderRecord.class, "productId");
+                        List<Course> courseList = courseService.select(courseIds);
+                        List<String> courseTitle = new ArrayList<>();
+                        for(Course c: courseList){
+                            courseTitle.add(c.getTitle());
+                        }
+                        map.put("titleJoin", String.join("”,“", courseTitle));
+                        send(user, MessageCategory.COURSE_PAY_MULTI, map, userOrder.getId());
+                    }else{
+                        map.put("expireDays", String.valueOf(one.getExpireDays()));
+                        map.put("useExpireDays", String.valueOf(one.getUseExpireDays()));
+                        send(user, MessageCategory.COURSE_PAY, map, userOrder.getId());
+                    }
+                    break;
+                case COURSE_PACKAGE:
+                    CoursePackage coursePackage = coursePackageService.get(one.getProductId());
+                    List<Course> courseList = courseService.select(coursePackage.getCourseIds());
+                    List<String> courseTitle = new ArrayList<>();
+                    for(Course c: courseList){
+                        courseTitle.add(c.getTitle());
+                    }
+                    map.put("titleJoin", String.join("”,“", courseTitle));
+                    map.put("number", String.valueOf(courseList.size()));
+                    map.put("title", courseList.get(0).getTitle());
+                    send(user, MessageCategory.COURSE_PAY_MULTI, map, userOrder.getId());
+                    break;
+                default:
+                    return;
+            }
+        }else{
+            List<String> titles = new ArrayList<>();
+
+            List<UserOrderRecord> prService = recordList.stream().filter((row)-> row.getProductType().equals(ProductType.SERVICE.key)).collect(Collectors.toList());
+            for(UserOrderRecord r : prService){
+                ServiceKey key = ServiceKey.ValueOf(r.getService());
+                titles.add(key.title);
+            }
+            // 绑定套餐
+            List<UserOrderRecord> prPackage = recordList.stream().filter((row)-> row.getProductType().equals(ProductType.COURSE_PACKAGE.key)).collect(Collectors.toList());
+            Collection packageIds = Transform.getIds(prPackage, UserOrderRecord.class, "productId");
+            List<CoursePackage> coursePackageList = coursePackageService.select(packageIds);
+            List<Integer> courseExtendIds = new ArrayList<>();
+            Map coursePackageMap = Transform.getMap(coursePackageList, CoursePackage.class, "id");
+            for(UserOrderRecord r : prPackage){
+                if (!coursePackageMap.containsKey(r.getProductId())) continue;
+                CoursePackage coursePackage = (CoursePackage) coursePackageMap.get(r.getProductId());
+                courseExtendIds.addAll(Arrays.stream(coursePackage.getCourseIds()).collect(Collectors.toList()));
+            }
+            List<Course> courseExtendList =  courseService.select(courseExtendIds);
+            Map courseExtendMap = Transform.getMap(courseExtendList, Course.class, "id");
+            for(UserOrderRecord r: prPackage){
+                if (!coursePackageMap.containsKey(r.getProductId())) continue;
+                CoursePackage coursePackage = (CoursePackage) coursePackageMap.get(r.getProductId());
+                for(Integer courseId : coursePackage.getCourseIds()){
+                    if (!courseExtendMap.containsKey(courseId)) continue;
+                    Course course = (Course) courseExtendMap.get(courseId);
+                    titles.add(course.getTitle());
+                }
+            }
+
+            // 绑定资料
+            List<UserOrderRecord> prData = recordList.stream().filter((row)-> row.getProductType().equals(ProductType.DATA.key)).collect(Collectors.toList());
+            Collection dataIds = Transform.getIds(prData, UserOrderRecord.class, "productId");
+            List<CourseData> courseDataList =  courseDataService.select(dataIds);
+            Map courseDataMap = Transform.getMap(courseDataList, CourseData.class, "id");
+            for(UserOrderRecord r: prData){
+                if (courseDataMap.containsKey(r.getProductId())) continue;
+                CourseData courseData = (CourseData) courseDataMap.get(r.getProductId());
+                titles.add(courseData.getTitle());
+            }
+
+            // 绑定课程
+            List<UserOrderRecord> prCourse = recordList.stream().filter((row)-> row.getProductType().equals(ProductType.COURSE.key)).collect(Collectors.toList());
+            Collection courseIds = Transform.getIds(prCourse, UserOrderRecord.class, "productId");
+            List<Course> courseList =  courseService.select(courseIds);
+            Map courseMap = Transform.getMap(courseList, Course.class, "id");
+            for(UserOrderRecord r: prCourse){
+                if (courseMap.containsKey(r.getProductId())) continue;
+                Course course = (Course) courseMap.get(r.getProductId());
+                titles.add(course.getTitle());
+            }
+
+            map.put("number", String.valueOf(titles.size()));
+            map.put("title", titles.get(0));
+            map.put("titleJoin", String.join("”,“", titles));
+            send(user, MessageCategory.PAY_MULTI, map, userOrder.getId());
+        }
+        // 赠品
+        List<UserOrderRecord> gifts = recordList.stream().filter((checkout)-> checkout.getSource().equals(RecordSource.GIFT_COURSE.key)).collect(Collectors.toList());
+        for(UserOrderRecord gift: gifts){
+            Map<String, String> giftMap = new HashMap<>();
+            ServiceKey key = ServiceKey.ValueOf(gift.getService());
+            map.put("title", key.title);
+            // 获取赠品链接
+            switch(key){
+                case QX_CAT:
+                    map.put("link", MessageCategory.QX_CAT_PAY.link);
+                    map.put("linkTitle", MessageCategory.QX_CAT_PAY.linkTitle);
+                    break;
+                case TEXTBOOK:
+                    map.put("link", MessageCategory.TEXTBOOK_PAY.link);
+                    map.put("linkTitle", MessageCategory.TEXTBOOK_PAY.linkTitle);
+                    break;
+            }
+            send(user, MessageCategory.COURSE_GIFT, giftMap, gift.getId());
+        }
+    }
+
+    /**
+     * 开通到期提醒
+     * @param user
+     * @param record
+     * @param days
+     */
+    public void sendOpenExpire(User user, UserOrderRecord record, Integer days){
+        Map<String, String> map = new HashMap<>();
+        map.put("days", String.valueOf(days));
+        map.put("expireDate", getDate(record.getEndTime()));
+        map.put("nickname", user.getNickname());
+        switch(ProductType.ValueOf(record.getProductType())){
+            case SERVICE:
+                ServiceKey key = ServiceKey.ValueOf(record.getService());
+                map.put("title", key.title);
+                switch(key){
+                    case TEXTBOOK:
+                        send(user, MessageCategory.TEXTBOOK_OPEN_EXPIRE, map, record.getId());
+                        break;
+                    case QX_CAT:
+                        send(user, MessageCategory.QX_CAT_OPEN_EXPIRE, map, record.getId());
+                        break;
+                    default:
+                        return;
+                }
+                break;
+            case COURSE:
+                Course course = courseService.get(record.getProductId());
+                map.put("title", course.getTitle());
+                send(user, MessageCategory.COURSE_OPEN_EXPIRE, map, record.getId());
+                break;
+            default:
+                return;
+        }
+    }
+
+    /**
+     * 使用到期提醒
+     * @param user
+     * @param record
+     * @param days
+     */
+    public void sendUseExpire(User user, UserOrderRecord record, Integer days){
+        Map<String, String> map = new HashMap<>();
+        map.put("days", String.valueOf(days));
+        map.put("expireDate", getDate(record.getUseEndTime()));
+        map.put("nickname", user.getNickname());
+        switch(ProductType.ValueOf(record.getProductType())){
+            case SERVICE:
+                ServiceKey key = ServiceKey.ValueOf(record.getService());
+                map.put("title", key.title);
+                switch(key){
+                    case VIP:
+                        send(user, MessageCategory.VIP_USE_EXPIRE, map, record.getId());
+                        break;
+                    case TEXTBOOK:
+                        send(user, MessageCategory.TEXTBOOK_USE_EXPIRE, map, record.getId());
+                        break;
+                    case QX_CAT:
+                        send(user, MessageCategory.QX_CAT_USE_EXPIRE, map, record.getId());
+                        break;
+                    default:
+                        return;
+                }
+                break;
+            case COURSE:
+                Course course = courseService.get(record.getProductId());
+                map.put("title", course.getTitle());
+                send(user, MessageCategory.COURSE_USE_EXPIRE, map, record.getId());
+                break;
+            default:
+                return;
+        }
     }
 
     /**
@@ -226,13 +575,26 @@ public class MessageExtendService {
      * 资料名称:{title}
      * 更新时间:{updateTime}
      */
-    public void sendDataUpdate(User user, CourseData data, CourseDataHistory history){
+    public void sendDataUpdate(User user, CourseData data, CourseDataHistory history, Boolean subscribe){
         Map<String, String> map = new HashMap<>();
         map.put("title", data.getTitle());
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
-        String time = sdf.format(history.getTime());
-        map.put("time", time);
-        send(user, MessageCategory.DATA_UPDATE, map);
+        map.put("nickname", user.getNickname());
+        map.put("email", user.getEmail());
+        map.put("version", history.getVersion());
+        map.put("description", String.format("位置:%s, 原内容:%s, 更改为:%s", history.getPosition(), history.getOriginContent(), history.getContent()));
+        switch(DataType.ValueOf(data.getDataType())){
+            case PAPER:
+                send(user, MessageCategory.DATA_UPDATE_PAPER, map, data.getId());
+                break;
+            case ELECTRON:
+                if (subscribe){
+                    map.put("attachment", data.getResource());
+                    send(user, MessageCategory.DATA_UPDATE_SUBSCRIBE, map, data.getId());
+                }else{
+                    send(user, MessageCategory.DATA_UPDATE_BASE, map, data.getId());
+                }
+                break;
+        }
     }
 
     /**
@@ -242,21 +604,32 @@ public class MessageExtendService {
      * 提问状态:{status}, 精选、隐藏
      */
     public void sendAskQuestion(User user,UserAskQuestion userAskQuestion){
+        // 非学员
+        if (user.getIsCourse() == 0) return;
         Map<String, String> map = new HashMap<>();
         map.put("content", userAskQuestion.getContent());
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
-        String time = sdf.format(new Date());
-        map.put("time", time);
-        AnswerStatus status = AnswerStatus.ValueOf(userAskQuestion.getAnswerStatus());
-        if (status == AnswerStatus.ANSWER){
-            map.put("status", "已回答");
-        }else if (status == AnswerStatus.IGNORE){
-            map.put("status", "已隐藏");
-        }
-        if(userAskQuestion.getShowStatus() > 0){
-            map.put("status", "精选");
+        map.put("date", getDate(userAskQuestion.getCreateTime()));
+        QuestionNo questionNo = questionNoService.get(userAskQuestion.getQuestionNoId());
+        map.put("title", questionNo.getTitle());
+
+        switch(AnswerStatus.ValueOf(userAskQuestion.getAnswerStatus())){
+            case ANSWER:
+                if(userAskQuestion.getShowStatus() > 0){
+                    // 替换内容较多
+                    String link = replaceLink(MessageCategory.ASK_QUESTION_SPECIAL.link, "id", userAskQuestion.getId());
+                    link = replaceLink(link, "questionNoId", userAskQuestion.getQuestionNoId());
+                    map.put("link", link);
+                    map.put("linkTitle", MessageCategory.ASK_QUESTION_SPECIAL.linkTitle);
+                    send(user, MessageCategory.ASK_QUESTION_SPECIAL, map, userAskQuestion.getId());
+                }else{
+                    send(user, MessageCategory.ASK_QUESTION_HANDLE, map, userAskQuestion.getId());
+                }
+                break;
+            case IGNORE:
+                send(user, MessageCategory.ASK_QUESTION_IGNORE, map, userAskQuestion.getId());
+                break;
+            default:
         }
-        send(user, MessageCategory.ASK_QUESTION, map);
     }
 
     /**
@@ -266,21 +639,34 @@ public class MessageExtendService {
      * 提问状态:{status}, 精选、隐藏
      */
     public void sendAskCourse(User user, UserAskCourse userAskCourse){
+        // 非学员
+        if (user.getIsCourse() == 0) return;
         Map<String, String> map = new HashMap<>();
         map.put("content", userAskCourse.getContent());
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
-        String time = sdf.format(new Date());
-        map.put("time", time);
-        AnswerStatus status = AnswerStatus.ValueOf(userAskCourse.getAnswerStatus());
-        if (status == AnswerStatus.ANSWER){
-            map.put("status", "已回答");
-        }else if (status == AnswerStatus.IGNORE){
-            map.put("status", "已隐藏");
-        }
-        if(userAskCourse.getShowStatus() > 0){
-            map.put("status", "精选");
+        map.put("date", getDate(userAskCourse.getCreateTime()));
+        Course course = courseService.get(userAskCourse.getCourseId());
+        map.put("title", course.getTitle());
+        CourseNo courseNo = courseNoService.get(userAskCourse.getCourseNoId());
+        map.put("no", String.valueOf(courseNo.getNo()));
+        switch(AnswerStatus.ValueOf(userAskCourse.getAnswerStatus())){
+            case ANSWER:
+                if(userAskCourse.getShowStatus() > 0){
+                    // 替换内容较多
+                    String link = replaceLink(MessageCategory.ASK_COURSE_SPECIAL.link, "id", userAskCourse.getId());
+                    link = replaceLink(link, "courseId", userAskCourse.getCourseId());
+                    link = replaceLink(link, "courseNoId", userAskCourse.getCourseNoId());
+                    map.put("link", link);
+                    map.put("linkTitle", MessageCategory.ASK_COURSE_SPECIAL.linkTitle);
+                    send(user, MessageCategory.ASK_COURSE_SPECIAL, map, userAskCourse.getId());
+                }else{
+                    send(user, MessageCategory.ASK_COURSE_HANDLE, map, userAskCourse.getId());
+                }
+                break;
+            case IGNORE:
+                send(user, MessageCategory.ASK_COURSE_IGNORE, map, userAskCourse.getId());
+                break;
+            default:
         }
-        send(user, MessageCategory.ASK_COURSE, map);
     }
 
     /**
@@ -335,7 +721,15 @@ public class MessageExtendService {
                 break;
         }
         map.put("channel", channel);
-        send(user, MessageCategory.FAQ_CALLBACK, map);
+        switch(AnswerStatus.ValueOf(faq.getAnswerStatus())){
+            case ANSWER:
+                send(user, MessageCategory.FAQ_HANDLE, map, faq.getId());
+                break;
+            case IGNORE:
+                send(user, MessageCategory.FAQ_IGNORE, map, faq.getId());
+                break;
+            default:
+        }
     }
 
     /**
@@ -344,57 +738,97 @@ public class MessageExtendService {
      * 应更正:{correct}
      * 处理结果:{status}
      */
-    public void sendFeedbackAnswer(User user, UserFeedbackError feedbackError){
+    public void sendFeedbackError(User user, UserFeedbackError feedbackError, UserOrderRecord record){
         Map<String, String> map = new HashMap<>();
-        map.put("content", feedbackError.getOriginContent());
-        AnswerStatus status = AnswerStatus.ValueOf(feedbackError.getStatus());
-        if (status == AnswerStatus.ANSWER){
-            map.put("status", "已采纳");
-        }else if (status == AnswerStatus.IGNORE){
-            map.put("status", "已忽略");
-        }else if (status == AnswerStatus.NOHANDLE){
-            map.put("status", "无需处理");
+        map.put("time", getTime(feedbackError.getCreateTime()));
+        map.put("content", feedbackError.getContent());
+        map.put("title", feedbackError.getTitle());
+        if (feedbackError.getModule().equals(FeedbackModule.DATA.key)){
+            String[] p = feedbackError.getPosition().split(",");
+            map.put("position", String.format("第%s页第%s行", p[0], p[1]));
+        }else{
+            map.put("position", AskTarget.ValueOf(feedbackError.getPosition()).title);
+        }
+        map.put("originContent", feedbackError.getOriginContent());
+        if (record != null){
+            map.put("useExpireDays", String.valueOf(record.getUseExpireDays()));
+        }
+        switch(AnswerStatus.ValueOf(feedbackError.getStatus())){
+            case ANSWER:
+                send(user, MessageCategory.FEEDBACK_ERROR_HANDLE, map, feedbackError.getId());
+                break;
+            case IGNORE:
+                send(user, MessageCategory.FEEDBACK_ERROR_IGNORE, map, feedbackError.getId());
+                break;
+            case NOHANDLE:
+                send(user, MessageCategory.FEEDBACK_ERROR_NOHANDLE, map, feedbackError.getId());
+                break;
+            default:
         }
-        map.put("correct", feedbackError.getContent());
-        map.put("object", feedbackError.getTitle());
-        send(user, MessageCategory.FEEDBACK_CALLBACK, map);
     }
 
     /**
-     * 注册成功通知
-     * @param user
+     * 纠错对象:{object}
+     * 错误内容:{content}
+     * 应更正:{correct}
+     * 处理结果:{status}
      */
-    public void sendRegister(User user){
+    public void sendTextbookFeedback(User user, UserTextbookFeedback textbookFeedback, UserOrderRecord record){
         Map<String, String> map = new HashMap<>();
-        map.put("mobile", user.getMobile());
-        send(user, MessageCategory.REGISTER, map);
+        map.put("time", getTime(textbookFeedback.getCreateTime()));
+        map.put("content", textbookFeedback.getContent());
+        map.put("subject", QuestionSubject.ValueOf(textbookFeedback.getQuestionSubject()).title);
+        map.put("no", String.valueOf(textbookFeedback.getNo()));
+        FeedbackTarget target = FeedbackTarget.ValueOf(textbookFeedback.getTarget());
+        map.put("target", target.title);
+        map.put("result", target.result);
+        map.put("handle", target.handle);
+        if (record != null){
+            map.put("useExpireDays", String.valueOf(record.getUseExpireDays()));
+        }
+        switch(AnswerStatus.ValueOf(textbookFeedback.getStatus())){
+            case ANSWER:
+                send(user, MessageCategory.TEXTBOOK_FEEDBACK_HANDLE, map, textbookFeedback.getId());
+                break;
+            case IGNORE:
+                send(user, MessageCategory.TEXTBOOK_FEEDBACK_IGNORE, map, textbookFeedback.getId());
+                break;
+            case NOHANDLE:
+                send(user, MessageCategory.TEXTBOOK_FEEDBACK_NOHANDLE, map, textbookFeedback.getId());
+                break;
+            default:
+        }
     }
 
     /**
-     * 邮箱绑定
+     * 注册成功通知
      * @param user
      */
-    public void sendEmailBind(User user){
+    public void sendRegister(User user){
         Map<String, String> map = new HashMap<>();
-        map.put("nickname", user.getNickname());
+        map.put("mobile", user.getMobile());
         map.put("email", user.getEmail());
-        send(user, MessageCategory.EMAIL_CHANGE, map);
+        send(user, MessageCategory.REGISTER, map, 0);
     }
 
     /**
      * 邮箱变更
      * @param user
      */
-    public void sendEmailChange(User user, String email){
-        if(email != null && !email.isEmpty()){
+    public void sendEmailChange(User user, String newEmail){
+        if(user.getEmail() != null && !user.getEmail().isEmpty()){
             Map<String, String> unbindMap = new HashMap<>();
             unbindMap.put("nickname", user.getNickname());
-            unbindMap.put("emails", email);
-            send(user, MessageCategory.EMAIL_UNNBIND, unbindMap);
+            unbindMap.put("emails", user.getEmail());
+            String[] emails = newEmail.split("@");
+            emails[0] = "****"+emails[0].substring(emails[0].length() - 3);
+            unbindMap.put("email", String.join("@", emails));
+            send(user, MessageCategory.EMAIL_UNNBIND, unbindMap, 0);
         }
         Map<String, String> changeMap = new HashMap<>();
         changeMap.put("nickname", user.getNickname());
-        send(user, MessageCategory.EMAIL_CHANGE, changeMap);
+        changeMap.put("emails", newEmail);
+        send(user, MessageCategory.EMAIL_CHANGE, changeMap, 0);
     }
 
     private String replaceBody(String body, Map<String, String> params){
@@ -404,4 +838,22 @@ public class MessageExtendService {
         }
         return body;
     }
+
+    private String replaceLink(String link, String p, Integer relationId){
+        return link.replace("\\{"+p+"}", String.valueOf(relationId));
+    }
+
+    private String makeLink(String link, String linkTitle, Integer relationId){
+        return String.format("<a href=\"%s\" target=\"_blank\">%s</a>", pcUrl + replaceLink(link, "id",relationId), linkTitle);
+    }
+
+    private String getDate(Date date){
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
+        return sdf.format(date);
+    }
+
+    private String getTime(Date time){
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
+        return sdf.format(time);
+    }
 }

+ 34 - 10
server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java

@@ -774,6 +774,7 @@ public class OrderFlowService {
         List<UserOrderCheckout> checkoutList = userOrderCheckoutService.allByUser(userId, orderId);
         List<UserOrderCheckout> pList = checkoutList.stream().filter((checkout)-> checkout.getParentId() == 0).collect(Collectors.toList());
         List<UserOrderCheckout> cList = checkoutList.stream().filter((checkout)-> checkout.getParentId() > 0).collect(Collectors.toList());
+
         Map<Integer, Integer> pMap = new HashMap<>();
         for(UserOrderCheckout checkout : pList){
             UserOrderRecord record = Transform.convert(checkout, UserOrderRecord.class);
@@ -833,7 +834,8 @@ public class OrderFlowService {
             // 设定标志
             usersService.edit(User.builder().id(userOrder.getUserId()).isCourse(1).build());
         }
-        messageExtendService.sendPayed(user, userOrder);
+        List<UserOrderRecord> recordList = userOrderRecordService.allByUser(user.getId(), userOrder.getId());
+        messageExtendService.sendPayed(user, userOrder, recordList);
         return true;
     }
 
@@ -1077,11 +1079,11 @@ public class OrderFlowService {
 
     /**
      * 给用户邀请奖励: 7天vip
-     * @param userId
+     * @param user
      */
-    public void giveInvite(Integer userId){
+    public void giveInvite(User user){
         UserOrderRecord record = UserOrderRecord.builder()
-                .userId(userId)
+                .userId(user.getId())
                 .productType(ProductType.SERVICE.key)
                 .service(ServiceKey.VIP.key)
                 .param(ServiceVipKey.DAY7.key)
@@ -1092,15 +1094,16 @@ public class OrderFlowService {
         // 虚拟order
         record = initRecordCallback.get(ProductType.SERVICE).callback(UserOrder.builder().build(), record);
         userOrderRecordService.add(record);
+        messageExtendService.sendInviteSuccess(user, record);
     }
 
     /**
      * 给用户备考奖励: 7天vip
-     * @param userId
+     * @param user
      */
-    public void givePrepare(Integer userId){
+    public void givePrepare(User user){
         UserOrderRecord record = UserOrderRecord.builder()
-                .userId(userId)
+                .userId(user.getId())
                 .productType(ProductType.SERVICE.key)
                 .service(ServiceKey.VIP.key)
                 .param(ServiceVipKey.DAY7.key)
@@ -1111,15 +1114,16 @@ public class OrderFlowService {
         // 虚拟order
         record = initRecordCallback.get(ProductType.SERVICE).callback(UserOrder.builder().build(), record);
         userOrderRecordService.add(record);
+        messageExtendService.sendPrepare(user, record);
     }
 
     /**
      * 给用户实名认证奖励: 180天vip
-     * @param userId
+     * @param user
      */
-    public void giveReal(Integer userId){
+    public void giveReal(User user){
         UserOrderRecord record = UserOrderRecord.builder()
-                .userId(userId)
+                .userId(user.getId())
                 .productType(ProductType.SERVICE.key)
                 .service(ServiceKey.VIP.key)
                 .param(ServiceVipKey.MONTH3.key)
@@ -1130,6 +1134,26 @@ public class OrderFlowService {
         // 虚拟order
         record = initRecordCallback.get(ProductType.SERVICE).callback(UserOrder.builder().build(), record);
         userOrderRecordService.add(record);
+        messageExtendService.sendReal(user, record);
     }
 
+    /**
+     * 反馈奖励:7天vip
+     * @param userId
+     */
+    public UserOrderRecord giveAction(Integer userId){
+        UserOrderRecord record = UserOrderRecord.builder()
+                .userId(userId)
+                .productType(ProductType.SERVICE.key)
+                .service(ServiceKey.VIP.key)
+                .param(ServiceVipKey.DAY7.key)
+                .source(RecordSource.GIFT_ACTION.key)
+                .expireDays(0)
+                .useExpireDays(ServiceVipKey.DAY7.useExpireDay)
+                .build();
+        // 虚拟order
+        record = initRecordCallback.get(ProductType.SERVICE).callback(UserOrder.builder().build(), record);
+        userOrderRecordService.add(record);
+        return record;
+    }
 }

+ 6 - 3
server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookLibraryService.java

@@ -110,19 +110,22 @@ public class TextbookLibraryService extends AbstractService {
             library.setQuant(history.getQuant());
             library.setQuantVersion(library.getQuantVersion() + 1);
             library.setQuantTime(now);
-            history.setQuantVersion(library.getQuantVersion());
+            library.setQuantDescription(history.getQuantDescription());
+            history.setQuantVersion(library.getQuantVersion() + 1);
         }
         if (!history.getIr().isEmpty()){
             library.setIr(history.getIr());
             library.setIrVersion(library.getIrVersion() + 1);
             library.setIrTime(now);
-            history.setIrVersion(library.getIrVersion());
+            library.setIrDescription(history.getIrDescription());
+            history.setIrVersion(library.getIrVersion() + 1);
         }
         if (!history.getRc().isEmpty()){
             library.setRc(history.getRc());
             library.setRcVersion(library.getRcVersion() + 1);
             library.setRcTime(now);
-            history.setRcVersion(library.getRcVersion());
+            library.setRcDescription(history.getRcDescription());
+            history.setRcVersion(library.getRcVersion() + 1);
         }
         library.setHistoryNumber(library.getHistoryNumber() + 1);
 

+ 74 - 2
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderRecordService.java

@@ -85,12 +85,12 @@ public class UserOrderRecordService extends AbstractService {
     }
 
     /**
-     * 获取所有到期的课程记录
+     * 获取所有使用到期的课程记录
      * @param startTime
      * @param endTime
      * @return
      */
-    public List<UserOrderRecord> allCourseExpire(String startTime, String endTime){
+    public List<UserOrderRecord> allCourseUseExpire(String startTime, String endTime){
         Example example = new Example(UserOrderRecord.class);
         example.and(
                 example.createCriteria()
@@ -103,6 +103,61 @@ public class UserOrderRecordService extends AbstractService {
     }
 
     /**
+     * 获取所有开通到期的课程记录
+     * @param startTime
+     * @param endTime
+     * @return
+     */
+    public List<UserOrderRecord> allCourseOpenExpire(String startTime, String endTime){
+        Example example = new Example(UserOrderRecord.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("productType", ProductType.COURSE.key)
+                        .andEqualTo("isUse", 0)
+                        .andGreaterThanOrEqualTo("endTime", startTime)
+                        .andLessThanOrEqualTo("endTime", endTime)
+        );
+        return select(userOrderRecordMapper, example);
+    }
+
+    /**
+     * 获取所有到期的课程记录
+     * @param startTime
+     * @param endTime
+     * @return
+     */
+    public List<UserOrderRecord> allServiceUseExpire(ServiceKey serviceKey, String startTime, String endTime){
+        Example example = new Example(UserOrderRecord.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("productType", ProductType.SERVICE.key)
+                        .andEqualTo("service", serviceKey.key)
+                        .andGreaterThanOrEqualTo("useEndTime", startTime)
+                        .andLessThanOrEqualTo("useEndTime", endTime)
+        );
+        return select(userOrderRecordMapper, example);
+    }
+
+    /**
+     * 获取所有到期的课程记录
+     * @param startTime
+     * @param endTime
+     * @return
+     */
+    public List<UserOrderRecord> allServiceOpenExpire(ServiceKey serviceKey, String startTime, String endTime){
+        Example example = new Example(UserOrderRecord.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("productType", ProductType.SERVICE.key)
+                        .andEqualTo("service", serviceKey.key)
+                        .andEqualTo("isUse", 0)
+                        .andGreaterThanOrEqualTo("endTime", startTime)
+                        .andLessThanOrEqualTo("endTime", endTime)
+        );
+        return select(userOrderRecordMapper, example);
+    }
+
+    /**
      * 列出购买资料的记录
      * @param page
      * @param size
@@ -145,6 +200,23 @@ public class UserOrderRecordService extends AbstractService {
 
     /**
      * 列出购买资料的记录
+     * @param dataId
+     * @param userIds
+     * @return
+     */
+    public List<UserOrderRecord> listWithDataUser(Integer dataId, Collection userIds){
+        Example example = new Example(UserOrderRecord.class);
+        example.and(
+                example.createCriteria()
+                        .andIn("userId", userIds)
+                        .andEqualTo("productType", ProductType.DATA.key)
+                        .andEqualTo("productId", dataId)
+        );
+        return select(userOrderRecordMapper, example);
+    }
+
+    /**
+     * 列出购买资料的记录
      * @param userId
      * @param dataId
      * @return

+ 31 - 5
server/gateway-api/src/main/java/com/qxgmat/task/AsyncTask.java

@@ -253,19 +253,42 @@ public class AsyncTask {
     }
 
     @Async
+    public void postTextbookLibrary(){
+        logger.info("换库");
+        long start = System.currentTimeMillis();
+        TextbookLibrary latest = textbookLibraryService.getLatest();
+        TextbookLibrary second = textbookLibraryService.getSecond();
+        List<User> userList;
+        // 所有用户
+        int page = 1;
+        int size = 20;
+        do {
+            userList = usersService.select(page, size);
+            for(User user : userList){
+                messageExtendService.sendTextbookLibrary(user, latest, second);
+            }
+        }while(userList.size() >= size);
+        long end = System.currentTimeMillis();
+        logger.info("发布机经,耗时:" + (end - start) + "毫秒");
+    }
+
+    @Async
     public void postTextbookUpdate(){
         logger.info("发布机经:发送到订阅用户邮箱");
         long start = System.currentTimeMillis();
         TextbookLibrary textbookLibrary = textbookLibraryService.getLatest();
         List<UserService> userServiceList;
+        // 所有开通机经的用户
         int page = 1;
         int size = 20;
         do {
-            userServiceList = userServiceService.listByService(page, size, ServiceKey.TEXTBOOK, true);
+            userServiceList = userServiceService.listByService(page, size, ServiceKey.TEXTBOOK, null);
+            Map userServiceMap = Transform.getMap(userServiceList, UserService.class, "userId");
             Collection userIds = Transform.getIds(userServiceList, UserService.class, "userId");
             List<User> userList = usersService.select(userIds);
             for(User user : userList){
-                 messageExtendService.sendTextbookUpdate(user, textbookLibrary);
+                UserService userService = (UserService) userServiceMap.get(user.getId());
+                messageExtendService.sendTextbookUpdate(user, textbookLibrary, userService.getIsSubscribe() > 0);
             }
         }while(userServiceList.size() >= size);
         long end = System.currentTimeMillis();
@@ -279,18 +302,21 @@ public class AsyncTask {
         CourseDataHistory history = courseDataHistoryService.get(dataHistoryId);
         CourseData courseData = courseDataService.get(history.getDataId());
         List<UserCourseDataSubscribe> subscribeList;
+        // 所有订阅资料的用户
         int page = 1;
         int size = 20;
         do {
             subscribeList = userCourseDataSubscribeService.listByData(page, size, courseData.getId());
             Collection userIds = Transform.getIds(subscribeList, UserCourseDataSubscribe.class, "userId");
             List<User> userList = usersService.select(userIds);
+            List<UserOrderRecord> records = userOrderRecordService.listWithDataUser(courseData.getId(), userIds);
+            Map recordMap = Transform.getMap(records, UserOrderRecord.class, "userId");
             for(User user : userList){
-                if (user.getDataEmailSubscribe() == 0) continue;
-                messageExtendService.sendDataUpdate(user, courseData, history);
+                // 购买并且开通邮箱订阅:才算邮箱订阅
+                // 纸质没有购买记录,发送也不区分是否订阅状态
+                messageExtendService.sendDataUpdate(user, courseData, history, user.getDataEmailSubscribe() > 0 && recordMap.get(user.getId())!= null);
             }
         }while(subscribeList.size() >= size);
-        // messageExtendService.sendDataUpdate(user, courseData, history);
         long end = System.currentTimeMillis();
         logger.info("资料更新,耗时:" + (end - start) + "毫秒");
     }

+ 78 - 17
server/gateway-api/src/main/java/com/qxgmat/task/ScheduledTask.java

@@ -6,13 +6,16 @@ import com.github.pagehelper.Page;
 import com.nuliji.tools.Tools;
 import com.nuliji.tools.Transform;
 import com.nuliji.tools.third.OauthData;
+import com.qxgmat.data.constants.enums.ServiceKey;
 import com.qxgmat.data.constants.enums.SettingKey;
 import com.qxgmat.data.constants.enums.module.CourseModule;
+import com.qxgmat.data.constants.enums.module.ProductType;
 import com.qxgmat.data.constants.enums.status.MessageStatus;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.relation.entity.UserPrepareRelation;
 import com.qxgmat.help.WechatHelp;
 import com.qxgmat.service.UserPaperService;
+import com.qxgmat.service.UserServiceService;
 import com.qxgmat.service.UsersService;
 import com.qxgmat.service.extend.CourseExtendService;
 import com.qxgmat.service.extend.MessageExtendService;
@@ -85,6 +88,9 @@ public class ScheduledTask {
     @Autowired
     private UserCourseRecordService userCourseRecordService;
 
+    @Autowired
+    private UserServiceService userServiceService;
+
     /**
      * cron表达式:* * * * * *(共6位,使用空格隔开,具体如下)
      * cron表达式:*(秒0-59) *(分钟0-59) *(小时0-23) *(日期1-31) *(月份1-12或是JAN-DEC) *(星期1-7或是SUN-SAT)
@@ -265,12 +271,12 @@ public class ScheduledTask {
     }
 
     // 课程到期:判断延期奖励,听课频率<=2天/作业100%->10天,90%以上7天
-    @Scheduled(cron="0 0 * * * *")
+//    @Scheduled(cron="0 0 * * * *")
     public void awardCourseExpire(){
         // 下一小时内到期的课程
         Date startTime = new Date();
         Date endTime = Tools.addHour(startTime, 1);
-        List<UserOrderRecord> recordList = userOrderRecordService.allCourseExpire(startTime.toString(), endTime.toString());
+        List<UserOrderRecord> recordList = userOrderRecordService.allCourseUseExpire(startTime.toString(), endTime.toString());
         for(UserOrderRecord record : recordList){
             Course course = courseService.get(record.getId());
             if (CourseModule.ValueOf(course.getCourseModule()) != CourseModule.VIDEO){
@@ -323,14 +329,38 @@ public class ScheduledTask {
         }
     }
 
+    // 每天判断VIP,使用有效期还剩10天
+    @Scheduled(cron="0 1 0 * * *")
+    public void vipUseExpire() {
+        Date endTime = Tools.today();
+        Date startTime = Tools.addDate(endTime, 10);
+        List<UserService> serviceList = userServiceService.allExpire(ServiceKey.VIP, startTime.toString(), endTime.toString());
+        Collection userIds = Transform.getIds(serviceList, UserOrderRecord.class, "userId");
+        List<User> userList = usersService.select(userIds);
+        Map userMap = Transform.getMap(userList, User.class, "id");
+        for(UserService service : serviceList){
+            User user = (User) userMap.get(service.getUserId());
+            messageExtendService.sendUseExpire(user, UserOrderRecord.builder()
+                    .userId(user.getId())
+                    .productType(ProductType.SERVICE.key)
+                    .service(ServiceKey.VIP.key)
+                    .useEndTime(service.getExpireTime())
+                    .build(), 10);
+        }
+    }
+
     // 每天判断模考一直没开通、距离开通有效期还剩30天
     @Scheduled(cron="0 1 0 * * *")
-    public void catExpire() {
+    public void catOpenExpire() {
         Date endTime = Tools.today();
         Date startTime = Tools.addDate(endTime, 30);
-        List<UserOrderRecord> recordList = userOrderRecordService.allSuspendExpire(startTime.toString(), endTime.toString());
+        List<UserOrderRecord> recordList = userOrderRecordService.allServiceOpenExpire(ServiceKey.QX_CAT, startTime.toString(), endTime.toString());
+        Collection userIds = Transform.getIds(recordList, UserOrderRecord.class, "userId");
+        List<User> userList = usersService.select(userIds);
+        Map userMap = Transform.getMap(userList, User.class, "id");
         for(UserOrderRecord record : recordList){
-            courseExtendService.restoreCourse(record.getUserId(), record.getId());
+            User user = (User) userMap.get(record.getUserId());
+            messageExtendService.sendOpenExpire(user, record, 30);
         }
     }
 
@@ -338,32 +368,59 @@ public class ScheduledTask {
     @Scheduled(cron="0 1 0 * * *")
     public void catUseExpire() {
         Date endTime = Tools.today();
-        Date startTime = Tools.addDate(endTime, 30);
-        List<UserOrderRecord> recordList = userOrderRecordService.allSuspendExpire(startTime.toString(), endTime.toString());
+        Date startTime = Tools.addDate(endTime, 10);
+        List<UserOrderRecord> recordList = userOrderRecordService.allServiceUseExpire(ServiceKey.QX_CAT, startTime.toString(), endTime.toString());
+        Collection userIds = Transform.getIds(recordList, UserOrderRecord.class, "userId");
+        List<User> userList = usersService.select(userIds);
+        Map userMap = Transform.getMap(userList, User.class, "id");
         for(UserOrderRecord record : recordList){
-            courseExtendService.restoreCourse(record.getUserId(), record.getId());
+            User user = (User) userMap.get(record.getUserId());
+            messageExtendService.sendUseExpire(user, record, 10);
         }
     }
 
     // 每天判断机经一直没开通、距离开通有效期还剩30天。
     @Scheduled(cron="0 1 0 * * *")
-    public void textbookExpire() {
+    public void textbookOpenExpire() {
         Date endTime = Tools.today();
         Date startTime = Tools.addDate(endTime, 30);
-        List<UserOrderRecord> recordList = userOrderRecordService.allSuspendExpire(startTime.toString(), endTime.toString());
+        List<UserOrderRecord> recordList = userOrderRecordService.allServiceOpenExpire(ServiceKey.TEXTBOOK, startTime.toString(), endTime.toString());
+        Collection userIds = Transform.getIds(recordList, UserOrderRecord.class, "userId");
+        List<User> userList = usersService.select(userIds);
+        Map userMap = Transform.getMap(userList, User.class, "id");
         for(UserOrderRecord record : recordList){
-            courseExtendService.restoreCourse(record.getUserId(), record.getId());
+            User user = (User) userMap.get(record.getUserId());
+            messageExtendService.sendOpenExpire(user, record, 30);
+        }
+    }
+
+    // 每天判断机经已开通,使用有效期还剩10天
+    @Scheduled(cron="0 1 0 * * *")
+    public void textbookUseExpire() {
+        Date endTime = Tools.today();
+        Date startTime = Tools.addDate(endTime, 30);
+        List<UserOrderRecord> recordList = userOrderRecordService.allServiceUseExpire(ServiceKey.TEXTBOOK, startTime.toString(), endTime.toString());
+        Collection userIds = Transform.getIds(recordList, UserOrderRecord.class, "userId");
+        List<User> userList = usersService.select(userIds);
+        Map userMap = Transform.getMap(userList, User.class, "id");
+        for(UserOrderRecord record : recordList){
+            User user = (User) userMap.get(record.getUserId());
+            messageExtendService.sendUseExpire(user, record, 10);
         }
     }
 
     // 每天判断课程一直没开通、距离开通有效期还剩30天。
     @Scheduled(cron="0 1 0 * * *")
-    public void courseExpire() {
+    public void courseOpenExpire() {
         Date endTime = Tools.today();
         Date startTime = Tools.addDate(endTime, 30);
-        List<UserOrderRecord> recordList = userOrderRecordService.allSuspendExpire(startTime.toString(), endTime.toString());
+        List<UserOrderRecord> recordList = userOrderRecordService.allCourseOpenExpire(startTime.toString(), endTime.toString());
+        Collection userIds = Transform.getIds(recordList, UserOrderRecord.class, "userId");
+        List<User> userList = usersService.select(userIds);
+        Map userMap = Transform.getMap(userList, User.class, "id");
         for(UserOrderRecord record : recordList){
-            courseExtendService.restoreCourse(record.getUserId(), record.getId());
+            User user = (User) userMap.get(record.getUserId());
+            messageExtendService.sendOpenExpire(user, record, 30);
         }
     }
 
@@ -371,10 +428,14 @@ public class ScheduledTask {
     @Scheduled(cron="0 1 0 * * *")
     public void courseUseExpire() {
         Date endTime = Tools.today();
-        Date startTime = Tools.addDate(endTime, 30);
-        List<UserOrderRecord> recordList = userOrderRecordService.allSuspendExpire(startTime.toString(), endTime.toString());
+        Date startTime = Tools.addDate(endTime, 10);
+        List<UserOrderRecord> recordList = userOrderRecordService.allCourseUseExpire(startTime.toString(), endTime.toString());
+        Collection userIds = Transform.getIds(recordList, UserOrderRecord.class, "userId");
+        List<User> userList = usersService.select(userIds);
+        Map userMap = Transform.getMap(userList, User.class, "id");
         for(UserOrderRecord record : recordList){
-            courseExtendService.restoreCourse(record.getUserId(), record.getId());
+            User user = (User) userMap.get(record.getUserId());
+            messageExtendService.sendUseExpire(user, record, 10);
         }
     }
 }

+ 3 - 2
server/gateway-api/src/main/resources/application.yml

@@ -68,8 +68,6 @@ ip:
 self:
   secret: qianxing-duoshaojiaoyu
 
-
-
 paper:
   sentenceLength: 20
   textbookLength: 31
@@ -82,3 +80,6 @@ examination:
   verbalB: 2
   quantC: 1
   quantD: 2
+
+info:
+  wechat: 千行GMAT

+ 11 - 6
server/tools/src/main/java/com/nuliji/tools/third/sendcloud/SendCloudMail.java

@@ -13,10 +13,7 @@ import org.springframework.util.MultiValueMap;
 import org.springframework.web.client.RestTemplate;
 
 import java.io.File;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.TreeMap;
+import java.util.*;
 
 public class SendCloudMail {
     private static final Logger logger = LoggerFactory.getLogger(SendCloudMail.class);
@@ -54,7 +51,15 @@ public class SendCloudMail {
         }
     }
 
-    public Response sendMail(String to, String subject, String body, String from, String fromName, FileSystemResource attachments) {
+    public Response sendMail(String to, String subject, String body, String from, String fromName) {
+        return sendMail(to, subject, body, from, fromName, new ArrayList<>());
+    }
+
+    public Response sendMail(String to, String subject, String body, String from, String fromName, FileSystemResource attachment) {
+        return sendMail(to, subject, body, from, fromName, new ArrayList<FileSystemResource>(){{add(attachment);}});
+    }
+
+    public Response sendMail(String to, String subject, String body, String from, String fromName, List<FileSystemResource> attachments) {
         MultiValueMap<String, Object> params = new LinkedMultiValueMap<String, Object>();
         params.add("apiUser", apiUser);
         params.add("apiKey", apiKey);
@@ -66,7 +71,7 @@ public class SendCloudMail {
         if (fromName != null) {
             params.add("fromName", fromName);
         }
-        if (attachments != null){
+        if (attachments != null && attachments.size() > 0){
             params.add("attachments", attachments);
         }