Browse Source

feat(server): 计划任务

Go 5 years ago
parent
commit
7f11914516
85 changed files with 1043 additions and 241 deletions
  1. 4 4
      front/config/index.js
  2. 1 1
      front/project/admin/routes/interaction/faq/index.less
  3. 3 3
      front/project/admin/routes/setting/index/page.js
  4. 9 0
      front/project/admin/routes/setting/service/page.js
  5. 44 2
      front/project/admin/routes/setting/time/page.js
  6. 8 0
      front/project/admin/stores/system.js
  7. 6 3
      front/project/h5/components/Block/index.js
  8. 6 5
      front/project/h5/components/Item/index.js
  9. 5 1
      front/project/h5/routes/product/bought/page.js
  10. 1 0
      front/project/h5/routes/product/courseDetail/index.js
  11. 23 1
      front/project/h5/routes/product/courseDetail/page.js
  12. 1 0
      front/project/h5/routes/product/coursePackage/index.js
  13. 1 0
      front/project/h5/routes/product/courseVideo/index.js
  14. 1 0
      front/project/h5/routes/product/courseVs/index.js
  15. 24 2
      front/project/h5/routes/product/courseVs/page.js
  16. 1 0
      front/project/h5/routes/product/data/index.js
  17. 1 0
      front/project/h5/routes/product/dataDetail/index.js
  18. 24 7
      front/project/h5/routes/product/dataDetail/page.js
  19. 1 0
      front/project/h5/routes/product/dataHistory/index.js
  20. 1 0
      front/project/h5/routes/product/main/index.js
  21. 1 1
      front/project/h5/routes/product/main/index.less
  22. 2 2
      front/project/h5/routes/product/main/page.js
  23. 1 0
      front/project/h5/routes/product/serviceDetail/index.js
  24. 1 0
      front/project/h5/routes/textbook/detail/index.js
  25. 1 0
      front/project/h5/routes/textbook/library/index.js
  26. 1 0
      front/project/h5/routes/textbook/main/index.js
  27. 5 1
      front/project/h5/stores/course.js
  28. 1 0
      front/project/www/routes/examination/list/index.js
  29. 1 0
      front/project/www/routes/examination/main/index.js
  30. 1 0
      front/project/www/routes/exercise/list/index.js
  31. 1 0
      front/project/www/routes/exercise/main/index.js
  32. 1 0
      front/project/www/routes/page/home/index.js
  33. 1 0
      front/project/www/routes/paper/process/index.js
  34. 1 0
      front/project/www/routes/paper/question/index.js
  35. 1 0
      front/project/www/routes/paper/report/index.js
  36. 1 0
      front/project/www/routes/question/detail/index.js
  37. 1 0
      front/project/www/routes/sentence/read/index.js
  38. 1 0
      front/project/www/routes/textbook/list/index.js
  39. 26 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/SentenceQuestion.java
  40. 26 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookQuestion.java
  41. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserOrderRecord.java
  42. 2 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/SentenceQuestionMapper.xml
  43. 2 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookQuestionMapper.xml
  44. 2 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserOrderRecordMapper.xml
  45. 1 1
      server/data/src/main/java/com/qxgmat/data/relation/CommentRelationMapper.java
  46. 1 1
      server/data/src/main/java/com/qxgmat/data/relation/FaqRelationMapper.java
  47. 5 8
      server/data/src/main/java/com/qxgmat/data/relation/entity/UserPreviewPaperRelation.java
  48. 10 8
      server/data/src/main/java/com/qxgmat/data/relation/mapping/CommentRelationMapper.xml
  49. 10 8
      server/data/src/main/java/com/qxgmat/data/relation/mapping/FaqRelationMapper.xml
  50. 3 5
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/PreviewController.java
  51. 17 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java
  52. 1 1
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java
  53. 2 15
      server/gateway-api/src/main/java/com/qxgmat/controller/api/AuthController.java
  54. 3 2
      server/gateway-api/src/main/java/com/qxgmat/controller/api/BaseController.java
  55. 20 10
      server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java
  56. 55 2
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  57. 2 1
      server/gateway-api/src/main/java/com/qxgmat/controller/api/OrderController.java
  58. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/CommentDto.java
  59. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/FaqDto.java
  60. 0 10
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CommentDto.java
  61. 0 18
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseExperienceDetailDto.java
  62. 0 10
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseExperienceListDto.java
  63. 5 3
      server/gateway-api/src/main/java/com/qxgmat/dto/response/MyDto.java
  64. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserCourseDetailDto.java
  65. 2 1
      server/gateway-api/src/main/java/com/qxgmat/service/UserPaperService.java
  66. 50 8
      server/gateway-api/src/main/java/com/qxgmat/service/UserServiceService.java
  67. 41 0
      server/gateway-api/src/main/java/com/qxgmat/service/extend/CourseExtendService.java
  68. 0 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ExerciseService.java
  69. 17 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/MessageExtendService.java
  70. 14 7
      server/gateway-api/src/main/java/com/qxgmat/service/extend/PreviewService.java
  71. 0 2
      server/gateway-api/src/main/java/com/qxgmat/service/extend/SentenceService.java
  72. 22 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/CommentService.java
  73. 20 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/CourseExperienceService.java
  74. 1 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/FaqService.java
  75. 29 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/PreviewAssignService.java
  76. 14 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/PreviewPaperService.java
  77. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookLibraryService.java
  78. 4 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseProgressService.java
  79. 36 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseRecordService.java
  80. 8 8
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseService.java
  81. 105 46
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderRecordService.java
  82. 14 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserReportService.java
  83. 34 3
      server/gateway-api/src/main/java/com/qxgmat/task/AsyncTask.java
  84. 149 21
      server/gateway-api/src/main/java/com/qxgmat/task/ScheduledTask.java
  85. 6 0
      server/tools/src/main/java/com/nuliji/tools/Tools.java

+ 4 - 4
front/config/index.js

@@ -53,10 +53,10 @@ config.globals = {
   __API_PATH__: `\'${config.api_path}\'`,
   __BASE_NAME__: `\'${config.basename}\'`,
 
-  __PcUrl__: `\'config.PcUrl}\'`,
-  __WechatPcAppId__: `\'config.WechatPcAppId}\'`,
-  __H5Url__: `\'config.H5Url}\'`,
-  __WechatH5AppId__: `\'config.WechatH5AppId}\'`,
+  __PcUrl__: `\'${config.PcUrl}\'`,
+  __WechatPcAppId__: `\'${config.WechatPcAppId}\'`,
+  __H5Url__: `\'${config.H5Url}\'`,
+  __WechatH5AppId__: `\'${config.WechatH5AppId}\'`,
 };
 
 debug(`Looking for environment overrides for NODE_ENV '${config.env}'.`);

+ 1 - 1
front/project/admin/routes/interaction/faq/index.less

@@ -1,3 +1,3 @@
 @charset "utf-8";
 
-#innteraction-faq {}
+#interaction-faq {}

+ 3 - 3
front/project/admin/routes/setting/index/page.js

@@ -38,9 +38,9 @@ export default class extends Page {
     form.validateFields((err) => {
       if (!err) {
         const data = form.getFieldsValue();
-        data.class = Object.keys(data.class).map((key) => data.class[key]);
-        data.activity = Object.keys(data.activity).map((key) => data.activity[key]);
-        data.evaluation = Object.keys(data.evaluation).map((key) => data.evaluation[key]);
+        data.class = Object.keys(data.class || {}).map((key) => data.class[key]);
+        data.activity = Object.keys(data.activity || {}).map((key) => data.activity[key]);
+        data.evaluation = Object.keys(data.evaluation || {}).map((key) => data.evaluation[key]);
         System.setIndex(data)
           .then(() => {
             this.setState(data);

+ 9 - 0
front/project/admin/routes/setting/service/page.js

@@ -64,6 +64,12 @@ export default class extends Page {
     this.setState({ [field]: data });
   }
 
+  changeValue(field, key, value) {
+    const data = this.state[field] || {};
+    data[key] = value;
+    this.setStaet({ [field]: data });
+  }
+
   submit(tab) {
     let handler;
     if (tab === 'qx_cat') {
@@ -178,6 +184,7 @@ export default class extends Page {
               showUploadList={false}
               beforeUpload={(file) => System.uploadImage(file).then((result) => {
                 setFieldsValue({ 'qx_cat.image': result.url });
+                this.changeValue('qx_cat', 'image', result.url);
                 return Promise.reject();
               })}
             >
@@ -208,6 +215,7 @@ export default class extends Page {
               showUploadList={false}
               beforeUpload={(file) => System.uploadImage(file).then((result) => {
                 setFieldsValue({ 'textbook.image': result.url });
+                this.changeValue('textbook', 'image', result.url);
                 return Promise.reject();
               })}
             >
@@ -303,6 +311,7 @@ export default class extends Page {
             showUploadList={false}
             beforeUpload={(file) => System.uploadImage(file).then((result) => {
               setFieldsValue({ 'vip.image': result.url });
+              this.changeValue('vip', 'image', result.url);
               return Promise.reject();
             })}
           >

+ 44 - 2
front/project/admin/routes/setting/time/page.js

@@ -1,5 +1,5 @@
 import React from 'react';
-import { Tabs, Form, Row, Col, InputNumber, Button, Switch } from 'antd';
+import { Tabs, Form, Row, Col, InputNumber, Button, Switch, DatePicker } from 'antd';
 import './index.less';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
@@ -75,6 +75,9 @@ export default class extends Page {
     if (tab === 'filter') {
       return this.refreshFilter();
     }
+    if (tab === 'paper') {
+      return this.refreshExercisePaperAuto();
+    }
     return Promise.reject();
   }
 
@@ -90,6 +93,12 @@ export default class extends Page {
     });
   }
 
+  refreshExercisePaperAuto() {
+    return System.getExercisePaperAuto().then((result) => {
+      this.setState({ exercisePaperAuto: result });
+    });
+  }
+
   refreshSentence() {
     return System.getSentenceTime().then((result) => {
       this.setState({ sentence: result || {} });
@@ -165,7 +174,8 @@ export default class extends Page {
       handler = this.submitExercise()
         .then(() => {
           return this.submitTextbook();
-        }).then(() => {
+        })
+        .then(() => {
           return this.submitSentence();
         });
     }
@@ -175,6 +185,9 @@ export default class extends Page {
     if (tab === 'filter') {
       handler = this.submitFilter();
     }
+    if (tab === 'paper') {
+      handler = this.submitExercisePaperAuto();
+    }
     handler.then(() => {
       asyncSMessage('保存成功');
     });
@@ -190,6 +203,11 @@ export default class extends Page {
     return System.setExerciseTime(exercise);
   }
 
+  submitExercisePaperAuto() {
+    const { exercisePaperAuto } = this.state;
+    return System.setExercisePaperAuto(exercisePaperAuto);
+  }
+
   submitSentence() {
     const { sentence } = this.state;
     return System.setSentenceTime(sentence);
@@ -317,6 +335,27 @@ export default class extends Page {
     </Form>;
   }
 
+  renderExercisePaperAuto() {
+    const { getFieldDecorator } = this.props.form;
+    return <Form>
+      <Row>
+        <Col span={12}>
+          <Form.Item labelCol={{ span: 10 }} wrapperCol={{ span: 14 }} label='下次错误率组卷'>
+            {getFieldDecorator('exercisePaper.date', {
+              rules: [
+                { required: true, message: '请输入下次练习错误率组卷时间' },
+              ],
+            })(
+              <DatePicker placeholder='请输入下次练习错误率组卷时间' onChange={(value) => {
+                this.changeValue('exercisePaper', 'date', value);
+              }} />,
+            )}
+          </Form.Item>
+        </Col>
+      </Row>
+    </Form>;
+  }
+
   renderExamination() {
     return <TableLayout
       columns={this.examinationColumns}
@@ -344,6 +383,9 @@ export default class extends Page {
         <Tabs.TabPane tab="预估考试时间" key="examination">
           {this.renderExamination()}
         </Tabs.TabPane>
+        <Tabs.TabPane tab="组卷" key="paper">
+          {this.renderExercisePaperAuto()}
+        </Tabs.TabPane>
       </Tabs>
       {tab !== 'examination' && <Row type="flex" justify="center">
         <Col>

+ 8 - 0
front/project/admin/stores/system.js

@@ -53,6 +53,14 @@ export default class SystemStore extends BaseStore {
     return this.apiPut('/setting/filter_time', params);
   }
 
+  getExercisePaperAuto() {
+    return this.apiGet('/setting/exercise_paper_auto');
+  }
+
+  setExercisePaperAuto(params) {
+    return this.apiPut('/setting/exercise_paper_auto', params);
+  }
+
   getExerciseTime() {
     return this.apiGet('/setting/exercise_time');
   }

+ 6 - 3
front/project/h5/components/Block/index.js

@@ -171,14 +171,17 @@ export class BuyBlock extends Component {
           {data.productType === 'course' && !data.isUsed && <div className="date">有效期:{formatDate(data.endTime, 'YYYY-MM-DD')}</div>}
           {data.productType === 'course' && data.isUsed && <div className="date">课程学习时间:{formatDate(data.useStartTime, 'YYYY-MM-DD')}-{formatDate(data.useEndTime, 'YYYY-MM-DD')}</div>}
           {data.service !== 'textbook' && !data.isUsed && <div className="desc">请访问千行 GMAT 官网开通使用</div>}
+          {data.service === 'textbook' && expire && <div className="desc">您可至PC端「我的工具」查看往期机经</div>}
         </div>
         <div className="block-right">
           <div className="btn">
-            {!data.isUsed && <Button radius onClick={() => onOpen && onOpen(data)}>开通</Button>}
-            {expire && data.service === 'vip' && <Button radius onClick={() => onBuy && onBuy(data)}>立即购买</Button>}
+            {data.service === 'textbook' && !data.isUsed && <Button radius onClick={() => onOpen && onOpen(data)}>开通</Button>}
+            {expire && data.service !== 'vip' && <Button radius onClick={() => onBuy && onBuy(data)}>续费</Button>}
+            {data.service === 'vip' && <Button radius onClick={() => onBuy && onBuy(data)}>续费</Button>}
             {data.productType === 'data' && data.data.resource && <Button radius onClick={() => onRead && onRead(data)}>阅读</Button>}
+            {!expire && data.service === 'textbook' && <Button radius onClick={() => onRead && onRead(data)}>阅读</Button>}
           </div>
-          {expire && data.service === 'vip' && <div className="tip">¥{price}/{title}</div>}
+          {data.service === 'vip' && <div className="tip">¥{price}/{title}</div>}
         </div>
       </TopBlock >
     );

+ 6 - 5
front/project/h5/components/Item/index.js

@@ -1,14 +1,15 @@
 import React, { Component } from 'react';
 import './index.less';
 import Assets from '@src/components/Assets';
+import { formatDate } from '@src/services/Tools';
 
 export class FAQItem extends Component {
   render() {
     const { className = '', data = {} } = this.props;
     return (
       <div className={`g-faq-item ${className}`}>
-        <div className="g-faq-item-title">{data.title}</div>
-        <div className="g-faq-item-desc">{data.desc}</div>
+        <div className="g-faq-item-title">{data.content}</div>
+        <div className="g-faq-item-desc">答:{data.answer}</div>
       </div>
     );
   }
@@ -23,10 +24,10 @@ export class CommentItem extends Component {
         </div>
         <div className="g-comment-item-right">
           <div className="g-comment-item-right-info">
-            <div className="g-comment-item-right-info-name">{data.name}</div>
-            <div className="g-comment-item-right-info-date">{data.date}</div>
+            <div className="g-comment-item-right-info-name">{data.nickname}</div>
+            <div className="g-comment-item-right-info-date">{formatDate(data.createTime, 'YYYY-MM-DD')}</div>
           </div>
-          <div className="g-comment-item-right-desc">{data.desc}</div>
+          <div className="g-comment-item-right-desc">{data.content}</div>
         </div>
       </div>
     );

+ 5 - 1
front/project/h5/routes/product/bought/page.js

@@ -61,7 +61,11 @@ export default class extends Page {
     }} onBuy={() => {
       linkTo(`/product/service/${rowData.service}`);
     }} onRead={() => {
-      openLink(rowData.data.resource);
+      if (rowData.service === 'textbook') {
+        linkTo('/textbook');
+      } else {
+        openLink(rowData.data.resource);
+      }
     }} />;
   }
 

+ 1 - 0
front/project/h5/routes/product/courseDetail/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'product-course-detail',
   title: '课程详情',
   needLogin: false,
+  repeat: true,
   component() {
     return import('./page');
   },

+ 23 - 1
front/project/h5/routes/product/courseDetail/page.js

@@ -4,6 +4,7 @@ import { Tabs } from 'antd-mobile';
 import Page from '@src/containers/Page';
 import Money from '../../../components/Money';
 import Button from '../../../components/Button';
+import { FAQItem, CommentItem } from '../../../components/Item';
 import { Course } from '../../../stores/course';
 import { Order } from '../../../stores/order';
 import { PcUrl } from '../../../../Constant';
@@ -24,6 +25,27 @@ export default class extends Page {
     Order.speedPay({ productType: 'course', productId: this.params.id });
   }
 
+  renderText() {
+    const { tab, data } = this.state;
+    let content;
+    switch (tab) {
+      case 'teacherContent':
+      case 'baseContent':
+      case 'pointContent':
+        content = <div dangerouslySetInnerHTML={{ __html: data[tab] }} />;
+        break;
+      case 'faq':
+        content = <div>{(data.faqs || []).map(row => <FAQItem data={row} />)}</div>;
+        break;
+      case 'comment':
+        content = <div>{(data.comments || []).map(row => <CommentItem data={row} />)}</div>;
+        break;
+      default:
+        break;
+    }
+    return content;
+  }
+
   renderView() {
     const { data = {}, tab } = this.state;
     return (
@@ -46,7 +68,7 @@ export default class extends Page {
               this.setState({ tab: value.key });
             }}
           />
-          <div dangerouslySetInnerHTML={{ __html: data[tab] }} />
+          {this.renderText()}
         </div>
         <div className="fixed">
           <div className="fee">

+ 1 - 0
front/project/h5/routes/product/coursePackage/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'product-course-package',
   title: '课程套餐',
   needLogin: false,
+  repeat: true,
   component() {
     return import('./page');
   },

+ 1 - 0
front/project/h5/routes/product/courseVideo/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'product-course-video',
   title: '在线课程',
   needLogin: false,
+  repeat: true,
   component() {
     return import('./page');
   },

+ 1 - 0
front/project/h5/routes/product/courseVs/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'product-course-vs',
   title: '1vs1课程',
   needLogin: false,
+  repeat: true,
   component() {
     return import('./page');
   },

+ 24 - 2
front/project/h5/routes/product/courseVs/page.js

@@ -4,6 +4,7 @@ import { Tabs } from 'antd-mobile';
 import Page from '@src/containers/Page';
 import Money from '../../../components/Money';
 import Button from '../../../components/Button';
+import { FAQItem, CommentItem } from '../../../components/Item';
 import { Course } from '../../../stores/course';
 import { Order } from '../../../stores/order';
 
@@ -23,6 +24,27 @@ export default class extends Page {
     Order.speedPay({ productType: 'course', productId: this.params.id, number: 1 });
   }
 
+  renderText() {
+    const { tab, data } = this.state;
+    let content;
+    switch (tab) {
+      case 'serviceContent':
+      case 'crowdContent':
+      case 'processContent':
+        content = <div dangerouslySetInnerHTML={{ __html: data[tab] }} />;
+        break;
+      case 'faq':
+        content = <div>{(data.faqs || []).map(row => <FAQItem data={row} />)}</div>;
+        break;
+      case 'comment':
+        content = <div>{(data.comments || []).map(row => <CommentItem data={row} />)}</div>;
+        break;
+      default:
+        break;
+    }
+    return content;
+  }
+
   renderView() {
     const { data = {}, tab } = this.state;
     return (
@@ -36,7 +58,7 @@ export default class extends Page {
             page={tab}
             tabs={[
               { title: '服务介绍', key: 'serviceContent' },
-              { title: '适合人群', key: 'crowdContent' },
+              { title: '适合人群', key: 'crowdContent' },
               { title: '授课流程', key: 'processContent' },
               { title: 'FAQs', key: 'faq' },
               { title: '用户评价', key: 'comment' },
@@ -45,7 +67,7 @@ export default class extends Page {
               this.setState({ tab: value.key });
             }}
           />
-          <div dangerouslySetInnerHTML={{ __html: data[tab] }} />
+          {this.renderText()}
         </div>
         <div className="fixed">
           <div className="fee">

+ 1 - 0
front/project/h5/routes/product/data/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'product-data',
   title: '全部资料',
   needLogin: false,
+  repeat: true,
   component() {
     return import('./page');
   },

+ 1 - 0
front/project/h5/routes/product/dataDetail/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'product-data-detail',
   title: '资料详情',
   needLogin: true,
+  repeat: true,
   component() {
     return import('./page');
   },

+ 24 - 7
front/project/h5/routes/product/dataDetail/page.js

@@ -30,6 +30,27 @@ export default class extends Page {
     Order.speedPay({ productType: 'data', productId: this.params.id });
   }
 
+  renderText() {
+    const { tab, data } = this.state;
+    let content;
+    switch (tab) {
+      case 'content':
+      case 'authorContent':
+      case 'methodContent':
+        content = <div dangerouslySetInnerHTML={{ __html: data[tab] }} />;
+        break;
+      case 'faq':
+        content = <div>{(data.faqs || []).map(row => <FAQItem data={row} />)}</div>;
+        break;
+      case 'comment':
+        content = <div>{(data.comments || []).map(row => <CommentItem data={row} />)}</div>;
+        break;
+      default:
+        break;
+    }
+    return content;
+  }
+
   renderView() {
     const { data = {}, tab } = this.state;
     return (
@@ -53,8 +74,8 @@ export default class extends Page {
           page={tab}
           tabs={[
             { title: '资料介绍', key: 'content' },
-            { title: '作者介绍', key: 'author_content' },
-            { title: '获取方式', key: 'method_content' },
+            { title: '作者介绍', key: 'authorContent' },
+            { title: '获取方式', key: 'methodContent' },
             { title: 'FAQs', key: 'faq' },
             { title: '用户评价', key: 'comment' },
           ]}
@@ -62,11 +83,7 @@ export default class extends Page {
             this.setState({ tab: value.key });
           }}
         />
-        <div dangerouslySetInnerHTML={{ __html: data[tab] }} />
-        <FAQItem data={{ title: '123', desc: '12312312321321' }} />
-        <FAQItem data={{ title: '123', desc: '12312312321321' }} />
-        <CommentItem data={{ name: '123', desc: '12312312321', date: '1231231231' }} />
-        <CommentItem data={{ name: '123', desc: '12312312321', date: '1231231231' }} />
+        {this.renderText()}
         <div className="fixed">
           <div className="fee">
             总额: <Money value={data.price} size="lager" />

+ 1 - 0
front/project/h5/routes/product/dataHistory/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'product-data-history',
   title: '资料更新',
   needLogin: true,
+  repeat: true,
   component() {
     return import('./page');
   },

+ 1 - 0
front/project/h5/routes/product/main/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'product',
   title: '全部商品',
   needLogin: false,
+  repeat: true,
   component() {
     return import('./page');
   },

+ 1 - 1
front/project/h5/routes/product/main/index.less

@@ -18,7 +18,7 @@
 
   .list {
     padding: 0px 15px 20px 15px;
-    overflow: hidden;
+    overflow: auto;
     flex: 1;
 
     .body {

+ 2 - 2
front/project/h5/routes/product/main/page.js

@@ -19,7 +19,7 @@ export default class extends Page {
   initData() {
     Promise.all(ServiceKey.map(service => {
       return Main.getService(service.value).then(result => {
-        this.setState({ [service]: result });
+        this.setState({ [service.value]: result });
       });
     }));
     Course.allVs().then(list => {
@@ -72,7 +72,7 @@ export default class extends Page {
         <Assets name="banner" className="banner" />
         {CourseVsType.map((t, index) => {
           const course = this.courseVsMap[t.value] || {};
-          return <LinkBlock title={course.title} sub={course.comment} theme={index % 2 > 0 ? 'not' : ''} onClick={() => {
+          return <LinkBlock title={course.title} sub={course.comment} theme={index % 2 > 0 ? 'not' : 'default'} onClick={() => {
             linkTo(`/product/course/vs/${course.id}`);
           }} />;
         })}

+ 1 - 0
front/project/h5/routes/product/serviceDetail/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'product-service-detail',
   title: '服务详情',
   needLogin: true,
+  repeat: true,
   component() {
     return import('./page');
   },

+ 1 - 0
front/project/h5/routes/textbook/detail/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'textbook-detail',
   title: '机经详情',
   needLogin: true,
+  repeat: true,
   component() {
     return import('./page');
   },

+ 1 - 0
front/project/h5/routes/textbook/library/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'textbook-library',
   title: '换库表',
   needLogin: false,
+  repeat: true,
   component() {
     return import('./page');
   },

+ 1 - 0
front/project/h5/routes/textbook/main/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'textbook',
   title: '机经主页',
   needLogin: true,
+  repeat: true,
   component() {
     return import('./page');
   },

+ 5 - 1
front/project/h5/stores/course.js

@@ -12,10 +12,14 @@ export default class CourseStore extends BaseStore {
     return this.apiGet('/course/video/list', params);
   }
 
-  get(courseId) {
+  simple(courseId) {
     return this.apiGet('/course/simple', { courseId });
   }
 
+  get(courseId) {
+    return this.apiGet('/course/detail', { courseId });
+  }
+
   listPackage(params) {
     return this.apiGet('/course/package/list', params);
   }

+ 1 - 0
front/project/www/routes/examination/list/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'examination-list',
   title: '模考列表',
   needLogin: false,
+  repeat: true,
   tab: 'examination',
   component() {
     return import('./page');

+ 1 - 0
front/project/www/routes/examination/main/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'examination',
   title: '模考',
   needLogin: false,
+  repeat: true,
   tab: 'examination',
   component() {
     return import('./page');

+ 1 - 0
front/project/www/routes/exercise/list/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'exercise-list',
   title: '练习列表',
   needLogin: false,
+  repeat: true,
   tab: 'exercise',
   component() {
     return import('./page');

+ 1 - 0
front/project/www/routes/exercise/main/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'exercise',
   title: '练习',
   needLogin: false,
+  repeat: true,
   tab: 'exercise',
   component() {
     return import('./page');

+ 1 - 0
front/project/www/routes/page/home/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'index',
   title: '首页',
   needLogin: false,
+  repeat: true,
   tab: 'main',
   component() {
     return import('./page');

+ 1 - 0
front/project/www/routes/paper/process/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'paper-process',
   title: '考试',
   needLogin: true,
+  repeat: true,
   hideHeader: true,
   component() {
     return import('./page');

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

@@ -3,6 +3,7 @@ export default {
   key: 'paper-question',
   title: '查看结果',
   needLogin: true,
+  repeat: true,
   hideHeader: true,
   component() {
     return import('./page');

+ 1 - 0
front/project/www/routes/paper/report/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'paper-report',
   title: '报告',
   needLogin: true,
+  repeat: true,
   hideHeader: true,
   component() {
     return import('./page');

+ 1 - 0
front/project/www/routes/question/detail/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'question-detail',
   title: '题目详情',
   needLogin: true,
+  repeat: true,
   hideHeader: true,
   component() {
     return import('./page');

+ 1 - 0
front/project/www/routes/sentence/read/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'sentence-read',
   title: '长难句阅读',
   needLogin: true,
+  repeat: true,
   hideHeader: true,
   component() {
     return import('./page');

+ 1 - 0
front/project/www/routes/textbook/list/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'textbook-list',
   title: '数学机经',
   needLogin: false,
+  repeat: true,
   tab: 'examination',
   component() {
     return import('./page');

+ 26 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/SentenceQuestion.java

@@ -64,6 +64,9 @@ public class SentenceQuestion implements Serializable {
     @Column(name = "`total_correct`")
     private Integer totalCorrect;
 
+    @Column(name = "`collect_number`")
+    private Integer collectNumber;
+
     @Column(name = "`chinese`")
     private String chinese;
 
@@ -246,6 +249,20 @@ public class SentenceQuestion implements Serializable {
     }
 
     /**
+     * @return collect_number
+     */
+    public Integer getCollectNumber() {
+        return collectNumber;
+    }
+
+    /**
+     * @param collectNumber
+     */
+    public void setCollectNumber(Integer collectNumber) {
+        this.collectNumber = collectNumber;
+    }
+
+    /**
      * @return chinese
      */
     public String getChinese() {
@@ -275,6 +292,7 @@ public class SentenceQuestion implements Serializable {
         sb.append(", totalTime=").append(totalTime);
         sb.append(", totalNumber=").append(totalNumber);
         sb.append(", totalCorrect=").append(totalCorrect);
+        sb.append(", collectNumber=").append(collectNumber);
         sb.append(", chinese=").append(chinese);
         sb.append("]");
         return sb.toString();
@@ -390,6 +408,14 @@ public class SentenceQuestion implements Serializable {
         }
 
         /**
+         * @param collectNumber
+         */
+        public Builder collectNumber(Integer collectNumber) {
+            obj.setCollectNumber(collectNumber);
+            return this;
+        }
+
+        /**
          * @param chinese
          */
         public Builder chinese(String chinese) {

+ 26 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookQuestion.java

@@ -58,6 +58,9 @@ public class TextbookQuestion implements Serializable {
     @Column(name = "`year`")
     private String year;
 
+    @Column(name = "`collect_number`")
+    private Integer collectNumber;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -218,6 +221,20 @@ public class TextbookQuestion implements Serializable {
         this.year = year;
     }
 
+    /**
+     * @return collect_number
+     */
+    public Integer getCollectNumber() {
+        return collectNumber;
+    }
+
+    /**
+     * @param collectNumber
+     */
+    public void setCollectNumber(Integer collectNumber) {
+        this.collectNumber = collectNumber;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -233,6 +250,7 @@ public class TextbookQuestion implements Serializable {
         sb.append(", totalNumber=").append(totalNumber);
         sb.append(", totalCorrect=").append(totalCorrect);
         sb.append(", year=").append(year);
+        sb.append(", collectNumber=").append(collectNumber);
         sb.append("]");
         return sb.toString();
     }
@@ -336,6 +354,14 @@ public class TextbookQuestion implements Serializable {
             return this;
         }
 
+        /**
+         * @param collectNumber
+         */
+        public Builder collectNumber(Integer collectNumber) {
+            obj.setCollectNumber(collectNumber);
+            return this;
+        }
+
         public TextbookQuestion build() {
             return this.obj;
         }

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

@@ -177,6 +177,12 @@ public class UserOrderRecord implements Serializable {
     @Column(name = "`origin_money`")
     private BigDecimal originMoney;
 
+    /**
+     * 奖励天数
+     */
+    @Column(name = "`course_award`")
+    private Integer courseAward;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -703,6 +709,24 @@ public class UserOrderRecord implements Serializable {
         this.originMoney = originMoney;
     }
 
+    /**
+     * 获取奖励天数
+     *
+     * @return course_award - 奖励天数
+     */
+    public Integer getCourseAward() {
+        return courseAward;
+    }
+
+    /**
+     * 设置奖励天数
+     *
+     * @param courseAward 奖励天数
+     */
+    public void setCourseAward(Integer courseAward) {
+        this.courseAward = courseAward;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -739,6 +763,7 @@ public class UserOrderRecord implements Serializable {
         sb.append(", createTime=").append(createTime);
         sb.append(", money=").append(money);
         sb.append(", originMoney=").append(originMoney);
+        sb.append(", courseAward=").append(courseAward);
         sb.append("]");
         return sb.toString();
     }
@@ -1046,6 +1071,16 @@ public class UserOrderRecord implements Serializable {
             return this;
         }
 
+        /**
+         * 设置奖励天数
+         *
+         * @param courseAward 奖励天数
+         */
+        public Builder courseAward(Integer courseAward) {
+            obj.setCourseAward(courseAward);
+            return this;
+        }
+
         public UserOrderRecord build() {
             return this.obj;
         }

+ 2 - 1
server/data/src/main/java/com/qxgmat/data/dao/mapping/SentenceQuestionMapper.xml

@@ -15,6 +15,7 @@
     <result column="total_time" jdbcType="INTEGER" property="totalTime" />
     <result column="total_number" jdbcType="INTEGER" property="totalNumber" />
     <result column="total_correct" jdbcType="INTEGER" property="totalCorrect" />
+    <result column="collect_number" jdbcType="INTEGER" property="collectNumber" />
   </resultMap>
   <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.SentenceQuestion">
     <!--
@@ -27,7 +28,7 @@
       WARNING - @mbg.generated
     -->
     `id`, `title`, `is_trail`, `is_paper`, `no`, `subject`, `question_id`, `total_time`, 
-    `total_number`, `total_correct`
+    `total_number`, `total_correct`, `collect_number`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 2 - 1
server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookQuestionMapper.xml

@@ -14,12 +14,13 @@
     <result column="total_number" jdbcType="INTEGER" property="totalNumber" />
     <result column="total_correct" jdbcType="INTEGER" property="totalCorrect" />
     <result column="year" jdbcType="VARCHAR" property="year" />
+    <result column="collect_number" jdbcType="INTEGER" property="collectNumber" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
     `id`, `title`, `no`, `library_id`, `question_id`, `total_time`, `total_number`, `total_correct`, 
-    `year`
+    `year`, `collect_number`
   </sql>
 </mapper>

+ 2 - 1
server/data/src/main/java/com/qxgmat/data/dao/mapping/UserOrderRecordMapper.xml

@@ -35,6 +35,7 @@
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
     <result column="money" jdbcType="DECIMAL" property="money" />
     <result column="origin_money" jdbcType="DECIMAL" property="originMoney" />
+    <result column="course_award" jdbcType="INTEGER" property="courseAward" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
@@ -44,6 +45,6 @@
     `param`, `source`, `teacher_id`, `number`, `vs_no`, `time_id`, `ask_time`, `cctalk_name`, 
     `is_subscribe`, `start_time`, `end_time`, `use_start_time`, `use_end_time`, `is_used`, 
     `use_time`, `is_stop`, `stop_time`, `is_suspend`, `suspend_time`, `restore_time`, 
-    `create_time`, `money`, `origin_money`
+    `create_time`, `money`, `origin_money`, `course_award`
   </sql>
 </mapper>

+ 1 - 1
server/data/src/main/java/com/qxgmat/data/relation/CommentRelationMapper.java

@@ -25,7 +25,7 @@ public interface CommentRelationMapper {
 
     List<Comment> groupByPosition(
             @Param("channel") String channel,
-            @Param("position") Collection positions,
+            @Param("positions") Collection positions,
             @Param("top") Integer top
     );
 }

+ 1 - 1
server/data/src/main/java/com/qxgmat/data/relation/FaqRelationMapper.java

@@ -26,7 +26,7 @@ public interface FaqRelationMapper {
 
     List<Faq> groupByPosition(
             @Param("channel") String channel,
-            @Param("position") Collection positions,
+            @Param("positions") Collection positions,
             @Param("top") Integer top
     );
 }

+ 5 - 8
server/data/src/main/java/com/qxgmat/data/relation/entity/UserPreviewPaperRelation.java

@@ -1,15 +1,12 @@
 package com.qxgmat.data.relation.entity;
 
-import com.qxgmat.data.dao.entity.PreviewPaper;
-import com.qxgmat.data.dao.entity.User;
-import com.qxgmat.data.dao.entity.UserPaper;
-import com.qxgmat.data.dao.entity.UserReport;
+import com.qxgmat.data.dao.entity.*;
 
 /**
  * Created by gaojie on 2017/11/9.
  */
-public class UserPreviewPaperRelation extends UserPaper {
-    private PreviewPaper paper;
+public class UserPreviewPaperRelation extends PreviewAssign {
+    private UserPaper paper;
 
     private UserReport report;
 
@@ -31,11 +28,11 @@ public class UserPreviewPaperRelation extends UserPaper {
         this.report = report;
     }
 
-    public PreviewPaper getPaper() {
+    public UserPaper getPaper() {
         return paper;
     }
 
-    public void setPaper(PreviewPaper paper) {
+    public void setPaper(UserPaper paper) {
         this.paper = paper;
     }
 }

+ 10 - 8
server/data/src/main/java/com/qxgmat/data/relation/mapping/CommentRelationMapper.xml

@@ -61,18 +61,20 @@
     select id, `position`
     from
     (
-    select c.id, c.`position`, c.`order`
+    select c.id, c.`position`, c.`order`,
     (@num:=if(@group = `position`, @num +1, if(@group := `position`, 1, 1))) row_number
     from comment c
-    CROSS JOIN (select @num:=0, @group:=null) c
+    CROSS JOIN (select @num:=0, @group:=null) b
     where
     c.`channel` = #{channel,jdbcType=VARCHAR}
-    and c.`position` IN
-    <foreach collection="positions" item="item" index="index" open="(" close=")" separator=",">
-      #{item}
-    </foreach>
-    order by c.`position`, c.`order` desc
-    ) as c
+    <if test="positions != null">
+      and c.`position` IN
+      <foreach collection="positions" item="item" index="index" open="(" close=")" separator=",">
+        #{item}
+      </foreach>
+    </if>
+    order by c.`position` desc, c.`order` desc
+    ) as x
     where x.row_number &lt; #{top,jdbcType=INTEGER}
     order by x.`order` desc
   </select>

+ 10 - 8
server/data/src/main/java/com/qxgmat/data/relation/mapping/FaqRelationMapper.xml

@@ -64,17 +64,19 @@
     select id, `position`
     from
     (
-    select c.id, c.`position`, c.`order`
+    select c.id, c.`position`, c.`order`,
     (@num:=if(@group = `position`, @num +1, if(@group := `position`, 1, 1))) row_number
-    from comment c
-    CROSS JOIN (select @num:=0, @group:=null) c
+    from faq c
+    CROSS JOIN (select @num:=0, @group:=null) b
     where
     c.`channel` = #{channel,jdbcType=VARCHAR}
-    and c.`position` IN
-    <foreach collection="positions" item="item" index="index" open="(" close=")" separator=",">
-      #{item}
-    </foreach>
-    order by c.`position`, c.`order` desc
+    <if test="positions != null">
+      and c.`position` IN
+      <foreach collection="positions" item="item" index="index" open="(" close=")" separator=",">
+        #{item}
+      </foreach>
+    </if>
+    order by c.`position` desc, c.`order` desc
     ) as x
     where x.row_number &lt; #{top,jdbcType=INTEGER}
     order by x.`order` desc

+ 3 - 5
server/gateway-api/src/main/java/com/qxgmat/controller/admin/PreviewController.java

@@ -64,13 +64,11 @@ public class PreviewController {
     public Response<PreviewPaper> add(@RequestBody @Validated PreviewPaperDto dto, HttpServletRequest request) {
         PreviewPaper entity = Transform.dtoToEntity(dto);
         entity.setQuestionSubject(QuestionSubject.FromType(QuestionType.ValueOf(entity.getQuestionType())).key);
+        previewPaperService.add(entity);
         if (dto.getCourseNo() != null){
-            PreviewAssign assign = previewAssignService.getByCourseNo(dto.getCourseId(), dto.getCourseNo());
-            if (assign != null){
-                throw new ParameterException("该课时已经创建");
-            }
+            entity = previewPaperService.addCourseNo(entity);
+            previewAssignService.addCourseNo(entity.getId(), dto.getCourseId(), dto.getCourseNo());
         }
-        previewPaperService.add(entity);
         managerLogService.log(request);
         return ResponseHelp.success(entity);
     }

+ 17 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java

@@ -194,6 +194,23 @@ public class SettingController {
         return ResponseHelp.success(entity.getValue());
     }
 
+    @RequestMapping(value = "/exercise_paper_auto", method = RequestMethod.PUT)
+    @ApiOperation(value = "修改自动组卷时间设置", httpMethod = "PUT")
+    private Response<Boolean> editExercisePaperAuto(@RequestBody @Validated JSONObject dto){
+        Setting entity = settingService.getByKey(SettingKey.EXERCISE_PAPER_AUTO);
+        entity.setValue(dto);
+        settingService.edit(entity);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/exercise_paper_auto", method = RequestMethod.GET)
+    @ApiOperation(value = "获取自动组卷时间配置", httpMethod = "GET")
+    private Response<JSONObject> getExercisePaperAuto(){
+        Setting entity = settingService.getByKey(SettingKey.EXERCISE_PAPER_AUTO);
+
+        return ResponseHelp.success(entity.getValue());
+    }
+
     @RequestMapping(value = "/sentence_time", method = RequestMethod.PUT)
     @ApiOperation(value = "修改长难句时间设置", httpMethod = "PUT")
     private Response<Boolean> editSentenceTime(@RequestBody @Validated JSONObject dto){

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

@@ -730,7 +730,7 @@ public class UserController {
         List<PreviewAssign> previewAssignList = previewAssignService.listByCourseNos(courseId, courseNoIds);
         Map previewAssignMap = Transform.getMap(previewAssignList, PreviewAssign.class, "courseNo", "id");
         Collection assignIds = Transform.getIds(previewAssignList, PreviewAssign.class, "id");
-        List<UserPaper> userPaperList = userPaperService.listWithCourse(userId, assignIds);
+        List<UserPaper> userPaperList = userPaperService.listWithCourse(userId, assignIds, recordId);
         Map userPaperMap = Transform.getMap(userPaperList, UserPaper.class, "originId", "id");
         for(UserCourseProgressInfoDto dto : pr){
             dto.setPaperId((Integer)userPaperMap.get(previewAssignMap.get(dto.getId())));

+ 2 - 15
server/gateway-api/src/main/java/com/qxgmat/controller/api/AuthController.java

@@ -77,20 +77,7 @@ public class AuthController {
         }
 
         User entity = usersService.get(user.getId());
-        MyDto dto = Transform.convert(entity, MyDto.class);
-        if (!entity.getMobile().isEmpty()){
-            dto.setBindMobile(true);
-        }
-        if (!entity.getWechatUnionid().isEmpty()){
-            dto.setBindWechat(true);
-        }
-        if (entity.getRealStatus() > 0){
-            dto.setBindReal(true);
-        }
-        if(!entity.getPrepareStatus().isEmpty()){
-            dto.setBindPrepare(true);
-        }
-        dto.setVip(userServiceService.hasService(entity.getId(), ServiceKey.VIP));
+        MyDto dto = processUser(entity, request);
         return ResponseHelp.success(dto);
     }
 
@@ -244,7 +231,7 @@ public class AuthController {
         if(!user.getPrepareStatus().isEmpty()){
             dto.setBindPrepare(true);
         }
-        dto.setVip(userServiceService.hasService(user.getId(), ServiceKey.VIP));
+        dto.setVip(userServiceService.timeService(user.getId(), ServiceKey.VIP));
         return dto;
     }
 }

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

@@ -268,11 +268,12 @@ public class BaseController {
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
         Page<Comment> p = commentService.list(page, size, channel, position);
-        List<CommentDto> pr = Transform.convert(p, CommentDto.class);
 
         Collection userIds = Transform.getIds(p, Comment.class, "userId");
         List<User> userList = usersService.select(userIds);
-        Transform.combine(pr, userList, CommentDto.class, "userId", "user", User.class, "id", UserExtendDto.class);
+        commentService.replaceUser(p, userList);
+
+        List<CommentDto> pr = Transform.convert(p, CommentDto.class);
 
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }

+ 20 - 10
server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java

@@ -145,7 +145,11 @@ public class CourseController {
 
         // 评论
         List<Comment> commentList = commentService.list(1, 10, "course-"+course.getCourseModule(), course.getId().toString());
+        Collection userIds = Transform.getIds(commentList, Comment.class, "userId");
+        List<User> userList = usersService.select(userIds);
+        commentService.replaceUser(commentList, userList);
         dto.setComments(Transform.convert(commentList, CommentExtendDto.class));
+
         // faq
         List<Faq> faqList = faqService.list(1, 10, "course-"+course.getCourseModule(), course.getId().toString());
         dto.setFaqs(Transform.convert(faqList, FaqExtendDto.class));
@@ -172,17 +176,21 @@ public class CourseController {
         User user = (User) shiroHelp.getLoginUser();
         if (user == null) throw new AuthException("需要登录");
 
+        UserCourse userCourse = userCourseService.getCourse(user.getId(), dto.getCourseId());
+        if (userCourse == null) throw new AuthException("无访问权限");
+
         // 添加课时访问记录
         if (dto.getCurrentCourseNoId() != null && dto.getTime() != null && dto.getTime() > 0){
             userCourseRecordService.add(UserCourseRecord.builder()
                     .userId(user.getId())
                     .courseId(dto.getCourseId())
                     .courseNoId(dto.getCurrentCourseNoId())
+                    .recordId(userCourse.getRecordId())
                     .build());
         }
 
         // 更新课程进度
-        userCourseProgressService.updateProgress(user.getId(), dto.getCourseId(), dto.getCourseNoId(), dto.getProgress());
+        userCourseProgressService.updateProgress(user.getId(), dto.getCourseId(), dto.getCourseNoId(), dto.getProgress(), userCourse.getRecordId());
         return ResponseHelp.success(true);
     }
 
@@ -284,10 +292,13 @@ public class CourseController {
         CourseDataDetailDto dto = Transform.convert(courseData, CourseDataDetailDto.class);
 
         // 评论
-        List<Comment> commentList = commentService.list(1, 10, "course_package", courseData.getId().toString());
+        List<Comment> commentList = commentService.list(1, 10, "course_data", courseData.getId().toString());
+        Collection userIds = Transform.getIds(commentList, Comment.class, "userId");
+        List<User> userList = usersService.select(userIds);
+        commentService.replaceUser(commentList, userList);
         dto.setComments(Transform.convert(commentList, CommentExtendDto.class));
         // faq
-        List<Faq> faqList = faqService.list(1, 10, "course_package", courseData.getId().toString());
+        List<Faq> faqList = faqService.list(1, 10, "course_data", courseData.getId().toString());
         dto.setFaqs(Transform.convert(faqList, FaqExtendDto.class));
 
         return ResponseHelp.success(dto);
@@ -340,12 +351,12 @@ public class CourseController {
 
         Page<CourseExperience> p = courseExperienceService.list(page, size, prepareStatus, ExperienceScoreRange.ValueOf(experienceScore), ExperienceDayRange.ValueOf(experienceDay), experiencePercent, order, DirectionStatus.ValueOf(direction));
 
-        List<CourseExperienceListDto> pr = Transform.convert(p, CourseExperienceListDto.class);
-
         // 绑定用户
-        Collection userIds = Transform.getIds(pr, CourseExperienceListDto.class, "userId");
+        Collection userIds = Transform.getIds(p, CourseExperienceListDto.class, "userId");
         List<User> userList = usersService.select(userIds);
-        Transform.combine(pr, userList, CourseExperienceListDto.class, "userId", "user", User.class, "id", UserExtendDto.class);
+        courseExperienceService.replaceUser(p, userList);
+
+        List<CourseExperienceListDto> pr = Transform.convert(p, CourseExperienceListDto.class);
 
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
@@ -356,9 +367,8 @@ public class CourseController {
 
         CourseExperience entity = courseExperienceService.get(experienceId);
         if(entity.getUserId() > 0){
-            CourseExperienceDetailDto dto = Transform.convert(entity, CourseExperienceDetailDto.class);
-            User user = usersService.get(dto.getUserId());
-            dto.setUser(Transform.convert(user, UserExtendDto.class));
+            User user = usersService.get(entity.getUserId());
+            entity.setNickname(user.getNickname());
         }
         return ResponseHelp.success(entity);
     }

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

@@ -130,6 +130,9 @@ public class MyController {
     private CommentService commentService;
 
     @Autowired
+    private PreviewAssignService previewAssignService;
+
+    @Autowired
     private UsersService usersService;
 
     @Autowired
@@ -1507,23 +1510,37 @@ public class MyController {
         Transform.combine(pr, appointmentMap, UserCourseDetailDto.class, "productId", "appointments", UserCourseAppointmentExtendDto.class);
 
         Map<Object, Collection<UserCourseProgress>> progressMap = userCourseProgressService.groupByRecordId(recordIds);
+        Map<Object, Collection<UserCourseRecord>> recordMap = userCourseRecordService.groupByRecordId(recordIds);
         for(UserCourseDetailDto dto : pr){
             Collection<UserCourseProgress> list = progressMap.get(dto.getId());
             if (list == null || list.size() == 0) continue;
             Collection<CourseNo> courseNos = courseNoMap.get(dto.getProductId());
             dto.setCurrentNo(courseExtendService.computeCourseNoCurrent(courseNos, list));
+            dto.setTotalDays(courseExtendService.computeCourseDay(recordMap.get(dto.getId())));
         }
 
         // 获取每个科目的所有作业
         Map<Object, Collection<UserPreviewPaperRelation>> previewMap = previewService.groupByCourseId(user.getId(), recordIds, 1000);
         Transform.combine(pr, previewMap, UserCourseDetailDto.class, "productId", "papers", UserPaperBaseExtendDto.class);
+        for(UserCourseDetailDto dto : pr){
+            Collection<UserPreviewPaperRelation> list = previewMap.get(dto.getId());
+            if (list == null || list.size() == 0) continue;
+            int finish = 0;
+            for(UserPreviewPaperRelation relation : list){
+                if (relation.getPaper() == null) continue;
+                UserPaper paper = relation.getPaper();
+                if (paper.getTimes() > 0){
+                    finish += 1;
+                }
+            }
+            dto.setPreviewProgress(finish * 100 / list.size());
+        }
 
         // 绑定老师
         Collection teacherIds = Transform.getIds(p, UserOrderRecord.class, "teacherId");
         List<CourseTeacher> teacherList = courseTeacherService.select(teacherIds);
         Transform.combine(pr, teacherList, UserCourseDetailDto.class, "teacherId", "teacher", CourseTeacher.class, "id", CourseTeacherExtendDto.class);
 
-
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
@@ -1560,6 +1577,7 @@ public class MyController {
         if (!record.getUserId().equals(user.getId())){
             throw new ParameterException("记录不存在");
         }
+        Integer courseId = record.getProductId();
 
         // 获取停课记录
         Date suspend = record.getSuspendTime();
@@ -1575,8 +1593,43 @@ public class MyController {
                 suspend = Tools.addDate(suspend, 1);
             }
         }
+        List<Long> tmpList = new ArrayList<>();
 
-        // todo 获取听课记录
+        // 获取听课记录
+        List<UserCourseRecord> userCourseRecordList = userCourseRecordService.allWithRecord(id);
+        tmpList.clear();
+        for(UserCourseRecord userCourseRecord:userCourseRecordList){
+            Date day = Tools.day(userCourseRecord.getCreateTime());
+            if (!tmpList.contains(day.getTime())){
+                tmpList.add(day.getTime());
+
+                UserCourseTimeDto dto = new UserCourseTimeDto();
+                dto.setType("course");
+                dto.setDay(day);
+                dtos.add(dto);
+            }
+        }
+
+        // 预习作业
+        List<CourseNo> courseNoList = courseNoService.allCourse(courseId);
+        Collection courseNoIds = Transform.getIds(courseNoList, CourseNo.class, "id");
+        List<PreviewAssign> previewAssignList = previewAssignService.listByCourseNos(courseId, courseNoIds);
+        Collection assignIds = Transform.getIds(previewAssignList, PreviewAssign.class, "id");
+        List<UserPaper> userPaperList = userPaperService.listWithCourse(user.getId(), assignIds, id);
+        Collection paperIds = Transform.getIds(userPaperList, UserPaper.class, "id");
+        List<UserReport> userReportList = userReportService.listByPaper(paperIds);
+        tmpList.clear();
+        for(UserReport userReport:userReportList){
+            Date day = Tools.day(userReport.getCreateTime());
+            if (!tmpList.contains(day.getTime())){
+                tmpList.add(day.getTime());
+
+                UserCourseTimeDto dto = new UserCourseTimeDto();
+                dto.setType("preview");
+                dto.setDay(day);
+                dtos.add(dto);
+            }
+        }
 
         return ResponseHelp.success(dtos);
     }

+ 2 - 1
server/gateway-api/src/main/java/com/qxgmat/controller/api/OrderController.java

@@ -197,12 +197,13 @@ public class OrderController {
             @RequestParam(required = false) Integer productId,
             @RequestParam(required = false) String service,
             @RequestParam(required = false) Boolean isUsed,
+            @RequestParam(required = false) Boolean isExpire,
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session)  {
         User user = (User) shiroHelp.getLoginUser();
 
-        Page<UserOrderRecord> p = userOrderRecordService.list(page, size, user.getId(), ProductType.ValueOf(productType), productId, ServiceKey.ValueOf(service), isUsed, order, DirectionStatus.ValueOf(direction));
+        Page<UserOrderRecord> p = userOrderRecordService.list(page, size, user.getId(), ProductType.ValueOf(productType), productId, ServiceKey.ValueOf(service), isUsed, isExpire, order, DirectionStatus.ValueOf(direction));
 
         List<UserOrderRecordListDto> pr = Transform.convert(p, UserOrderRecordListDto.class);
 

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

@@ -21,6 +21,10 @@ public class CommentDto {
 
     private String content;
 
+    private Integer isShow;
+
+    private Integer isSystem;
+
     private Integer isSpecial;
 
     private Date createTime;
@@ -96,4 +100,20 @@ public class CommentDto {
     public void setCreateTime(Date createTime) {
         this.createTime = createTime;
     }
+
+    public Integer getIsShow() {
+        return isShow;
+    }
+
+    public void setIsShow(Integer isShow) {
+        this.isShow = isShow;
+    }
+
+    public Integer getIsSystem() {
+        return isSystem;
+    }
+
+    public void setIsSystem(Integer isSystem) {
+        this.isSystem = isSystem;
+    }
 }

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

@@ -19,6 +19,10 @@ public class FaqDto {
 
     private String answer;
 
+    private Integer isShow;
+
+    private Integer isSystem;
+
     private Integer isSpecial;
 
     private Boolean sendUser;
@@ -106,4 +110,20 @@ public class FaqDto {
     public void setSendUser(Boolean sendUser) {
         this.sendUser = sendUser;
     }
+
+    public Integer getIsShow() {
+        return isShow;
+    }
+
+    public void setIsShow(Integer isShow) {
+        this.isShow = isShow;
+    }
+
+    public Integer getIsSystem() {
+        return isSystem;
+    }
+
+    public void setIsSystem(Integer isSystem) {
+        this.isSystem = isSystem;
+    }
 }

+ 0 - 10
server/gateway-api/src/main/java/com/qxgmat/dto/response/CommentDto.java

@@ -12,8 +12,6 @@ public class CommentDto {
 
     private Integer userId;
 
-    private UserExtendDto user;
-
     private String nickname;
 
     private String avatar;
@@ -54,14 +52,6 @@ public class CommentDto {
         this.createTime = createTime;
     }
 
-    public UserExtendDto getUser() {
-        return user;
-    }
-
-    public void setUser(UserExtendDto user) {
-        this.user = user;
-    }
-
     public String getNickname() {
         return nickname;
     }

+ 0 - 18
server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseExperienceDetailDto.java

@@ -1,18 +0,0 @@
-package com.qxgmat.dto.response;
-
-import com.nuliji.tools.annotation.Dto;
-import com.qxgmat.data.dao.entity.CourseExperience;
-import com.qxgmat.dto.extend.UserExtendDto;
-
-@Dto(entity = CourseExperience.class)
-public class CourseExperienceDetailDto extends  CourseExperience {
-    private UserExtendDto user;
-
-    public UserExtendDto getUser() {
-        return user;
-    }
-
-    public void setUser(UserExtendDto user) {
-        this.user = user;
-    }
-}

+ 0 - 10
server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseExperienceListDto.java

@@ -12,8 +12,6 @@ public class CourseExperienceListDto {
 
     private Integer userId;
 
-    private UserExtendDto user;
-
     private String nickname;
 
     private String title;
@@ -36,14 +34,6 @@ public class CourseExperienceListDto {
 
     private Date updateTime;
 
-    public UserExtendDto getUser() {
-        return user;
-    }
-
-    public void setUser(UserExtendDto user) {
-        this.user = user;
-    }
-
     public Integer getId() {
         return id;
     }

+ 5 - 3
server/gateway-api/src/main/java/com/qxgmat/dto/response/MyDto.java

@@ -2,6 +2,8 @@ package com.qxgmat.dto.response;
 
 import io.swagger.annotations.ApiModelProperty;
 
+import java.util.Date;
+
 
 /**
  * Created by GaoJie on 2017/11/1.
@@ -25,7 +27,7 @@ public class MyDto extends UserDto {
 
     private Integer latestExercise;
 
-    private Boolean vip;
+    private Date vip;
 
     private int messageNum;
 
@@ -109,11 +111,11 @@ public class MyDto extends UserDto {
         this.latestExercise = latestExercise;
     }
 
-    public Boolean getVip() {
+    public Date getVip() {
         return vip;
     }
 
-    public void setVip(Boolean vip) {
+    public void setVip(Date vip) {
         this.vip = vip;
     }
 }

+ 20 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserCourseDetailDto.java

@@ -44,6 +44,10 @@ public class UserCourseDetailDto {
 
     private Integer currentNo;
 
+    private Integer totalDays;
+
+    private Integer previewProgress;
+
     private Collection<UserCourseAppointmentExtendDto> appointments;
 
     private Integer askNumber;
@@ -229,4 +233,20 @@ public class UserCourseDetailDto {
     public void setCurrentNo(Integer currentNo) {
         this.currentNo = currentNo;
     }
+
+    public Integer getTotalDays() {
+        return totalDays;
+    }
+
+    public void setTotalDays(Integer totalDays) {
+        this.totalDays = totalDays;
+    }
+
+    public Integer getPreviewProgress() {
+        return previewProgress;
+    }
+
+    public void setPreviewProgress(Integer previewProgress) {
+        this.previewProgress = previewProgress;
+    }
 }

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

@@ -166,12 +166,13 @@ public class UserPaperService extends AbstractService {
      * @param ids
      * @return
      */
-    public List<UserPaper> listWithCourse(Integer userId, Collection ids){
+    public List<UserPaper> listWithCourse(Integer userId, Collection ids, Integer recordId){
         if (ids == null || ids.size() == 0) return new ArrayList<>();
         Example example = new Example(UserPaper.class);
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", userId)
+                        .andEqualTo("recordId", recordId)
                         .andEqualTo("paperOrigin", PaperOrigin.PREVIEW.key)
                         .andIn("originId", ids)
         );

+ 50 - 8
server/gateway-api/src/main/java/com/qxgmat/service/UserServiceService.java

@@ -39,14 +39,56 @@ public class UserServiceService extends AbstractService {
                 example.createCriteria()
                         .andEqualTo("userId", userId)
                         .andEqualTo("service", key.key)
-                        .andGreaterThanOrEqualTo("startTime", new Date())
-                        .andLessThan("expireTime", new Date())
+                        .andLessThanOrEqualTo("startTime", new Date())
+                        .andGreaterThan("expireTime", new Date())
         );
         UserService service = one(userServiceMapper, example);
         return service != null;
     }
 
     /**
+     * 权限到期时间
+     * @param userId
+     * @param key
+     * @return
+     */
+    public Date timeService(Integer userId, ServiceKey key){
+        if (key == null) return null;
+        Example example = new Example(UserService.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andEqualTo("service", key.key)
+                        .andLessThanOrEqualTo("startTime", new Date())
+                        .andGreaterThan("expireTime", new Date())
+        );
+        UserService service = one(userServiceMapper, example);
+        return service != null ? service.getExpireTime() : null;
+    }
+
+    /**
+     * 获取当前有权限的用户信息
+     * @param key
+     * @return
+     */
+    public Page<UserService> listByService(int page, int size, ServiceKey key, Boolean isSubscribe){
+        Example example = new Example(UserService.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("service", key.key)
+                        .andLessThanOrEqualTo("startTime", new Date())
+                        .andGreaterThan("expireTime", new Date())
+        );
+        if (isSubscribe != null){
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("isSubscribe", isSubscribe ? 1:0)
+            );
+        }
+        return select(userServiceMapper, example, page, size);
+    }
+
+    /**
      * 获取用户服务
      * @param userId
      * @param key
@@ -58,8 +100,8 @@ public class UserServiceService extends AbstractService {
                 example.createCriteria()
                         .andEqualTo("userId", userId)
                         .andEqualTo("service", key.key)
-                        .andGreaterThanOrEqualTo("startTime", new Date())
-                        .andLessThan("expireTime", new Date())
+                        .andLessThanOrEqualTo("startTime", new Date())
+                        .andGreaterThan("expireTime", new Date())
         );
         return one(userServiceMapper, example);
     }
@@ -98,8 +140,8 @@ public class UserServiceService extends AbstractService {
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", userId)
-                        .andGreaterThanOrEqualTo("startTime", new Date())
-                        .andLessThan("expireTime", new Date())
+                        .andLessThanOrEqualTo("startTime", new Date())
+                        .andGreaterThan("expireTime", new Date())
         );
         return select(userServiceMapper, example);
     }
@@ -111,8 +153,8 @@ public class UserServiceService extends AbstractService {
         example.and(
                 example.createCriteria()
                         .andIn("userId", userIds)
-                        .andGreaterThanOrEqualTo("startTime", new Date())
-                        .andLessThan("expireTime", new Date())
+                        .andLessThanOrEqualTo("startTime", new Date())
+                        .andGreaterThan("expireTime", new Date())
         );
         List<UserService> userServiceList = select(userServiceMapper, example);
         if(userServiceList.size() == 0) return relationMap;

+ 41 - 0
server/gateway-api/src/main/java/com/qxgmat/service/extend/CourseExtendService.java

@@ -100,6 +100,28 @@ public class CourseExtendService {
                 .build());
     }
 
+    public void awardCourse(Integer userId, Integer recordId, Integer day){
+        UserOrderRecord record = userOrderRecordService.get(recordId);
+        if (record == null){
+            throw new ParameterException("记录不存在");
+        }
+        if (!record.getUserId().equals(userId)){
+            throw new ParameterException("记录不存在");
+        }
+        if (record.getIsUsed() == 0){
+            throw new ParameterException("课程还未开通");
+        }
+        if (record.getCourseAward() > 0){
+            throw new ParameterException("已奖励");
+        }
+
+        userOrderRecordService.edit(UserOrderRecord.builder()
+                .id(record.getId())
+                .courseAward(day)
+                .useEndTime(Tools.addDate(record.getUseEndTime(), day))
+                .build());
+    }
+
     /**
      * 累计听课学习时间
      * @param userId
@@ -194,6 +216,25 @@ public class CourseExtendService {
     }
 
     /**
+     * 计算用户该课程的总学习天数
+     * @param userCourseRecords
+     * @return
+     */
+    public int computeCourseDay(Collection<UserCourseRecord> userCourseRecords){
+        Date min = null;
+        Date max = null;
+        for(UserCourseRecord record:userCourseRecords){
+            if(min==null || min.after(record.getCreateTime())){
+                min = record.getCreateTime();
+            }
+            if (max == null || max.before(record.getCreateTime())){
+                max = record.getCreateTime();
+            }
+        }
+        return (int)(Tools.day(max).getTime() - Tools.day(min).getTime()/86400000);
+    }
+
+    /**
      * 根据用户权限更新资源信息
      * @param user
      * @param courseId

+ 0 - 1
server/gateway-api/src/main/java/com/qxgmat/service/extend/ExerciseService.java

@@ -14,7 +14,6 @@ import com.qxgmat.data.relation.QuestionNoRelationMapper;
 import com.qxgmat.data.relation.UserPaperRelationMapper;
 import com.qxgmat.data.relation.UserReportRelationMapper;
 import com.qxgmat.data.relation.entity.QuestionNoRelation;
-import com.qxgmat.data.relation.entity.UserExercisePaperRelation;
 import com.qxgmat.service.inline.ExercisePaperService;
 import com.qxgmat.service.UserPaperService;
 import com.qxgmat.service.inline.QuestionNoService;

+ 17 - 1
server/gateway-api/src/main/java/com/qxgmat/service/extend/MessageExtendService.java

@@ -78,6 +78,22 @@ public class MessageExtendService {
         wechatHelp.sendMessage(openId, category, params);
     }
 
+    public void sendCustom(User user, MessageTemplate template, Map<String, String>params){
+        MessageMethod messageMethod = MessageMethod.ValueOf(template.getMessageMethod());
+        String title = replaceBody(template.getTitle(), params);
+        String content = replaceBody(template.getContent(), params);
+        switch(messageMethod){
+            case EMAIL:
+                sendEmail(user.getEmail(), title, content);
+                break;
+            case INSIDE:
+                sendInside(user.getId(), title, content, template.getLink(), MessageCategory.CUSTOM);
+                break;
+            default:
+                throw new ParameterException("消息发送方式错误");
+        }
+    }
+
     /**
      * 发送邀请邮件
      * @param emails
@@ -110,7 +126,7 @@ public class MessageExtendService {
      * 课程名称:{courseTitle}
      * 作业名称:{assignTitle}
      */
-    public void sendPreviewNotice(User user, PreviewAssign previewAssign){
+    public void sendPreviewNotice(User user, Course course, PreviewAssign previewAssign){
         Map<String, String> map = new HashMap<>();
         send(user, MessageCategory.PREVIEW_NOTICE, map);
     }

+ 14 - 7
server/gateway-api/src/main/java/com/qxgmat/service/extend/PreviewService.java

@@ -106,7 +106,7 @@ public class PreviewService extends AbstractService {
                 break;
             case VIDEO:
                 // 获取课时,并关联当前记录的paper
-                if (endTime != null){
+                if (endTime != null || size == 2){
                     // 无论列表还是卡片,都只显示2条
                     size = 2;
                     page = 1;
@@ -125,16 +125,18 @@ public class PreviewService extends AbstractService {
                 replaceTitle(previewAssignList);
                 break;
         }
+        List<UserPreviewPaperRelation> pr = Transform.convert(previewAssignList, UserPreviewPaperRelation.class);
         Collection assignIds = Transform.getIds(previewAssignList, PreviewAssign.class, "id");
+
         // 获取详细数据
-        List<UserPaper> list = userPaperService.listWithOrigin(userId, PaperOrigin.PREVIEW, assignIds, recordId);
-        Collection ids = Transform.getIds(list, UserPaper.class, "id");
-        List<UserPreviewPaperRelation> pr = Transform.convert(list, UserPreviewPaperRelation.class);
+        List<UserPaper> userPaperList = userPaperService.listWithOrigin(userId, PaperOrigin.PREVIEW, assignIds, recordId);
+        Transform.combine(pr, userPaperList, UserPreviewPaperRelation.class, "id", "paper", UserPaper.class, "originId");
+
+        Collection ids = Transform.getIds(userPaperList, UserPaper.class, "id");
 
         // 获取最后一次作业结果
         List<UserReport> reportList = userReportService.listWithLast(ids);
-
-        Transform.combine(pr, reportList, UserPreviewPaperRelation.class, "id", "report", UserReport.class, "paperId");
+        Transform.combine(pr, reportList, UserPreviewPaperRelation.class, "id", "report", UserReport.class, "originId");
 
         return pr;
     }
@@ -204,10 +206,15 @@ public class PreviewService extends AbstractService {
         }
     }
 
-    private void replaceTitle(List<PreviewAssign> previewAssignList){
+    public void replaceTitle(List<PreviewAssign> previewAssignList){
         Collection previewIds = Transform.getIds(previewAssignList, PreviewAssign.class, "paperId");
         List<PreviewPaper> paperList = previewPaperService.select(previewIds);
         Map paperTitle = Transform.getMap(paperList, PreviewPaper.class, "id", "title");
         Transform.combine(previewAssignList, paperTitle, PreviewAssign.class, "paperId", "title");
     }
+
+    public void replaceTitle(PreviewAssign previewAssign){
+        PreviewPaper paper = previewPaperService.get(previewAssign.getPaperId());
+        previewAssign.setTitle(paper.getTitle());
+    }
 }

+ 0 - 2
server/gateway-api/src/main/java/com/qxgmat/service/extend/SentenceService.java

@@ -6,10 +6,8 @@ import com.qxgmat.data.constants.enums.QuestionType;
 import com.qxgmat.data.constants.enums.logic.SentenceLogic;
 import com.qxgmat.data.constants.enums.module.QuestionModule;
 import com.qxgmat.data.dao.entity.*;
-import com.qxgmat.data.relation.UserReportRelationMapper;
 import com.qxgmat.data.relation.entity.SentenceQuestionRelation;
 import com.qxgmat.data.relation.entity.UserRecordStatRelation;
-import com.qxgmat.data.relation.entity.UserSentencePaperRelation;
 import com.qxgmat.service.UserPaperService;
 import com.qxgmat.service.inline.*;
 import org.springframework.beans.factory.annotation.Value;

+ 22 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/CommentService.java

@@ -12,6 +12,7 @@ import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.user.MoneyRange;
 import com.qxgmat.data.dao.CommentMapper;
 import com.qxgmat.data.dao.entity.Comment;
+import com.qxgmat.data.dao.entity.User;
 import com.qxgmat.data.relation.CommentRelationMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -81,8 +82,29 @@ public class CommentService extends AbstractService {
         return select(commentMapper, example, page, size);
     }
 
+    /**
+     * 替换comment的用户信息为真实用户信息
+     * @param comments
+     * @param users
+     */
+    public void replaceUser(List<Comment> comments, List<User> users){
+        Map userMap = Transform.getMap(users, User.class, "id");
+        for(Comment comment : comments){
+            if (comment.getUserId() == 0) {
+                continue;
+            }
+            User user = (User)userMap.get(comment.getUserId());
+            if (user == null){
+                continue;
+            }
+            comment.setAvatar(user.getAvatar());
+            comment.setNickname(user.getNickname());
+        }
+    }
+
     public Map<Object, Collection<Comment>> groupByPosition(String channel, Collection positions, Integer top){
         Map<Object, Collection<Comment>> result = new HashMap<>();
+        if (positions == null || positions.size() == 0) return result;
         List<Comment> commentList = commentRelationMapper.groupByPosition(channel, positions, top);
         Collection<Comment> tmp;
         for(Comment comment : commentList){

+ 20 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/CourseExperienceService.java

@@ -15,6 +15,7 @@ import com.qxgmat.data.dao.CourseDataMapper;
 import com.qxgmat.data.dao.CourseExperienceMapper;
 import com.qxgmat.data.dao.entity.CourseData;
 import com.qxgmat.data.dao.entity.CourseExperience;
+import com.qxgmat.data.dao.entity.User;
 import com.qxgmat.data.relation.CourseExperienceRelationMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -166,6 +167,25 @@ public class CourseExperienceService extends AbstractService {
         courseExperienceRelationMapper.accumulation(experienceId, view, collect);
     }
 
+    /**
+     * 替换comment的用户信息为真实用户信息
+     * @param experiences
+     * @param users
+     */
+    public void replaceUser(List<CourseExperience> experiences, List<User> users){
+        Map userMap = Transform.getMap(users, User.class, "id");
+        for(CourseExperience experience : experiences){
+            if (experience.getUserId() == 0) {
+                continue;
+            }
+            User user = (User)userMap.get(experience.getUserId());
+            if (user == null){
+                continue;
+            }
+            experience.setNickname(user.getNickname());
+        }
+    }
+
     public CourseExperience add(CourseExperience courseData){
         int result = insert(courseExperienceMapper, courseData);
         courseData = one(courseExperienceMapper, courseData.getId());

+ 1 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/FaqService.java

@@ -83,6 +83,7 @@ public class FaqService extends AbstractService {
 
     public Map<Object, Collection<Faq>> groupByPosition(String channel, Collection positions, Integer top){
         Map<Object, Collection<Faq>> result = new HashMap<>();
+        if (positions == null || positions.size() == 0) return result;
         List<Faq> faqList = faqRelationMapper.groupByPosition(channel, positions, top);
         Collection<Faq> tmp;
         for(Faq faq : faqList){

+ 29 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/PreviewAssignService.java

@@ -64,6 +64,18 @@ public class PreviewAssignService extends AbstractService {
         return one(previewAssignMapper, example);
     }
 
+    public PreviewAssign addCourseNo(Integer paperId, Integer courseId, Integer no){
+        PreviewAssign assign = getByCourseNo(courseId, no);
+        if (assign != null){
+            throw new ParameterException("该课时已创建");
+        }
+        return add(PreviewAssign.builder()
+                .courseId(courseId)
+                .courseNo(no)
+                .paperId(paperId)
+                .build());
+    }
+
     public boolean removeCourseNo(Integer courseId, Integer no){
         Example example = new Example(PreviewAssign.class);
         example.and(
@@ -167,6 +179,23 @@ public class PreviewAssignService extends AbstractService {
         return delete(previewAssignMapper, example) > 0;
     }
 
+    /**
+     * 查询1v1即将过期的作业
+     * @param startTime
+     * @param endTime
+     * @return
+     */
+    public List<PreviewAssign> listAppointmentExpire(String startTime, String endTime){
+        Example example = new Example(PreviewAssign.class);
+        example.and(
+                example.createCriteria()
+                        .andGreaterThan("courseAppointment", 0)
+                        .andGreaterThanOrEqualTo("endTime", startTime)
+                        .andLessThan("endTime", endTime)
+        );
+        return select(previewAssignMapper, example);
+    }
+
     public PreviewAssign add(PreviewAssign assign){
         int result = insert(previewAssignMapper, assign);
         assign = one(previewAssignMapper, assign.getId());

+ 14 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/PreviewPaperService.java

@@ -74,6 +74,20 @@ public class PreviewPaperService extends AbstractService {
         return p;
     }
 
+    public PreviewPaper addCourseNo(PreviewPaper previewPaper){
+        Example example = new Example(PreviewPaper.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("courseId", previewPaper.getCourseId())
+                        .andEqualTo("courseNo", previewPaper.getCourseNo())
+        );
+        PreviewPaper in = one(previewPaperMapper, example);
+        if (in != null){
+            throw new ParameterException("该课时已经创建");
+        }
+        return add(previewPaper);
+    }
+
     public boolean removeCourseNo(Integer courseId, Integer no){
         Example example = new Example(PreviewPaper.class);
         example.and(

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

@@ -82,7 +82,7 @@ public class TextbookLibraryService extends AbstractService {
         Example example = new Example(TextbookLibrary.class);
         example.and(
                 example.createCriteria()
-                .andGreaterThanOrEqualTo("startDate", startTime)
+                .andLessThanOrEqualTo("startDate", startTime)
                 .andLessThan("startDate", endTime)
         );
         return select(textbookLibraryMapper, example);

+ 4 - 2
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseProgressService.java

@@ -32,11 +32,12 @@ public class UserCourseProgressService extends AbstractService {
      * @return
      */
     @Transactional
-    public UserCourseProgress updateProgress(Integer userId, Integer courseId, Integer courseNoId, Integer progress){
+    public UserCourseProgress updateProgress(Integer userId, Integer courseId, Integer courseNoId, Integer progress, Integer recordId){
         Example example = new Example(UserCourseProgress.class);
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", userId)
+                        .andEqualTo("recordId", recordId)
                         .andEqualTo("courseId", courseId)
                         .andEqualTo("courseNoId", courseNoId)
         );
@@ -59,6 +60,7 @@ public class UserCourseProgressService extends AbstractService {
         }else{
             entity = add(UserCourseProgress.builder()
                     .userId(userId)
+                    .recordId(recordId)
                     .courseId(courseId)
                     .courseNoId(courseNoId)
                     .times(0)
@@ -78,7 +80,7 @@ public class UserCourseProgressService extends AbstractService {
     }
 
     /**
-     * 获取课程课时分组列表
+     * 获取课程记录分组列表
      * @param recordIds
      * @return
      */

+ 36 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseRecordService.java

@@ -45,6 +45,42 @@ public class UserCourseRecordService extends AbstractService {
         return list;
     }
 
+    public List<UserCourseRecord> allWithRecord(Integer recordId){
+        Example example = new Example(UserCourseRecord.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("recordId", recordId)
+        );
+        example.orderBy("id").asc();
+        return select(userCourseRecordMapper, example);
+    }
+
+    /**
+     * 获取课程记录分组列表
+     * @param recordIds
+     * @return
+     */
+    public Map<Object, Collection<UserCourseRecord>> groupByRecordId(Collection recordIds){
+        Map<Object, Collection<UserCourseRecord>> relationMap = new HashMap<>();
+        Example example = new Example(UserCourseRecord.class);
+        example.and(
+                example.createCriteria()
+                        .andIn("recordId", recordIds)
+        );
+        List<UserCourseRecord> nos =  select(userCourseRecordMapper, example);
+        Collection<UserCourseRecord> list;
+        for(UserCourseRecord no : nos){
+            if (!relationMap.containsKey(no.getRecordId())){
+                list = new ArrayList<>();
+                relationMap.put(no.getRecordId(), list);
+            }else{
+                list = relationMap.get(no.getRecordId());
+            }
+            list.add(no);
+        }
+        return relationMap;
+    }
+
     public Map<Integer, List<UserCourseRecord>> groupByCourse(Integer recordId, Collection courseNoIds){
         Map<Integer, List<UserCourseRecord>> relationMap = new HashMap<>();
         Example example = new Example(UserCourseRecord.class);

+ 8 - 8
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseService.java

@@ -48,8 +48,8 @@ public class UserCourseService extends AbstractService {
                 example.createCriteria()
                         .andEqualTo("userId", userId)
                         .andEqualTo("courseId", courseId)
-                        .andGreaterThanOrEqualTo("startTime", new Date())
-                        .andLessThan("expireTime", new Date())
+                        .andLessThanOrEqualTo("startTime", new Date())
+                        .andGreaterThan("expireTime", new Date())
         );
         return one(userCourseMapper, example);
     }
@@ -60,8 +60,8 @@ public class UserCourseService extends AbstractService {
                 example.createCriteria()
                         .andEqualTo("userId", userId)
                         .andIn("courseId", ids)
-                        .andGreaterThanOrEqualTo("startTime", new Date())
-                        .andLessThan("expireTime", new Date())
+                        .andLessThanOrEqualTo("startTime", new Date())
+                        .andGreaterThan("expireTime", new Date())
         );
         return select(userCourseMapper, example);
     }
@@ -89,8 +89,8 @@ public class UserCourseService extends AbstractService {
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", userId)
-                        .andGreaterThanOrEqualTo("startTime", new Date())
-                        .andLessThan("expireTime", new Date())
+                        .andLessThanOrEqualTo("startTime", new Date())
+                        .andGreaterThan("expireTime", new Date())
         );
         return select(userCourseMapper, example);
     }
@@ -106,8 +106,8 @@ public class UserCourseService extends AbstractService {
         if (time){
             example.and(
                     example.createCriteria()
-                            .andGreaterThanOrEqualTo("startTime", new Date())
-                            .andLessThan("expireTime", new Date())
+                            .andLessThanOrEqualTo("startTime", new Date())
+                            .andGreaterThan("expireTime", new Date())
             );
         }
         List<UserCourse> userClassList = select(userCourseMapper, example);

+ 105 - 46
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderRecordService.java

@@ -51,6 +51,66 @@ public class UserOrderRecordService extends AbstractService {
     }
 
     /**
+     * 获取所有到期的未恢复记录
+     * @param startTime
+     * @param endTime
+     * @return
+     */
+    public List<UserOrderRecord> allSuspendExpire(String startTime, String endTime){
+        Example example = new Example(UserOrderRecord.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("isSuspend", 1)
+                        .andIsNull("restoreTime")
+                        .andGreaterThanOrEqualTo("suspendTime", startTime)
+                        .andLessThanOrEqualTo("suspendTime", endTime)
+        );
+        return select(userOrderRecordMapper, example);
+    }
+
+    /**
+     * 获取所有到期的课程记录
+     * @param startTime
+     * @param endTime
+     * @return
+     */
+    public List<UserOrderRecord> allCourseExpire(String startTime, String endTime){
+        Example example = new Example(UserOrderRecord.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("productType", ProductType.COURSE.key)
+                        .andEqualTo("courseAward", 0)
+                        .andGreaterThanOrEqualTo("useEndTime", startTime)
+                        .andLessThanOrEqualTo("useEndTime", endTime)
+        );
+        return select(userOrderRecordMapper, example);
+    }
+
+    /**
+     * 列出购买资料的记录
+     * @param page
+     * @param size
+     * @param dataId
+     * @param isSubscribe
+     * @return
+     */
+    public Page<UserOrderRecord> listWithData(int page, int size, Integer dataId, Boolean isSubscribe){
+        Example example = new Example(UserOrderRecord.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("productType", ProductType.DATA.key)
+                        .andEqualTo("productId", dataId)
+        );
+        if(isSubscribe != null){
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("isSubscribe", isSubscribe ? 1: 0)
+            );
+        }
+        return select(userOrderRecordMapper, example, page, size);
+    }
+
+    /**
      * 获取小班课程学员列表
      * @param page
      * @param size
@@ -77,6 +137,29 @@ public class UserOrderRecordService extends AbstractService {
     }
 
     /**
+     * 获取小班课程全部学员记录
+     * @param courseId
+     * @param timeId
+     * @return
+     */
+    public List<UserOrderRecord> listByOnline(Integer courseId, Integer timeId){
+        Example example = new Example(UserOrderRecord.class);
+        if(courseId != null){
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("productType", ProductType.COURSE.key)
+                            .andEqualTo("productId", courseId)
+            );
+        }
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("timeId", timeId)
+                        .andEqualTo("isStop", 0)
+        );
+        return select(userOrderRecordMapper, example);
+    }
+
+    /**
      * 更改小班课程课时时间段
      * @param courseId
      * @param timeId
@@ -114,6 +197,7 @@ public class UserOrderRecordService extends AbstractService {
                         .andEqualTo("productType", ProductType.COURSE.key)
                         .andEqualTo("productId", courseId)
                         .andEqualTo("timeId", timeId)
+                        .andEqualTo("isStop", 0)
         );
         return one(userOrderRecordMapper, example);
     }
@@ -126,27 +210,6 @@ public class UserOrderRecordService extends AbstractService {
         return add(course);
     }
 
-    public UserOrderRecord getByUserAndCourse(Integer userId, Integer courseId, Boolean process){
-        Example example = new Example(UserOrderRecord.class);
-        example.and(
-                example.createCriteria()
-                        .andEqualTo("userId", userId)
-                        .andEqualTo("productId", courseId)
-        );
-        if (process != null){
-             example.and(
-                    process?  example.createCriteria()
-                    .andEqualTo("isUsed", 1)
-                    .andGreaterThanOrEqualTo("useEndTime", new Date()):
-                            example.createCriteria()
-                     .andEqualTo("isUsed", 1)
-                     .andLessThanOrEqualTo("useEndTime", new Date())
-
-            );
-        }
-        return one(userOrderRecordMapper, example);
-    }
-
     public Page<UserOrderRecord> listAdmin(int page, int size, Integer orderId, Integer userId, ProductType productType, Integer productId, ServiceKey service, Boolean needMoney, Boolean needPackage, String order, DirectionStatus direction){
         Example example = new Example(UserOrderRecord.class);
         if(orderId != null){
@@ -210,7 +273,7 @@ public class UserOrderRecordService extends AbstractService {
      * @param direction
      * @return
      */
-    public Page<UserOrderRecord> list(int page, int size, Integer userId, ProductType productType, Integer productId, ServiceKey service, Boolean isUsed, String order, DirectionStatus direction){
+    public Page<UserOrderRecord> list(int page, int size, Integer userId, ProductType productType, Integer productId, ServiceKey service, Boolean isUsed, Boolean isExpire, String order, DirectionStatus direction){
         Example example = new Example(UserOrderRecord.class);
         example.and(
                 example.createCriteria()
@@ -224,6 +287,17 @@ public class UserOrderRecordService extends AbstractService {
                                     .andEqualTo("isUsed", isUsed ? 1 : 0)
             );
         }
+        if(isExpire != null){
+            Date now = new Date();
+            example.and(
+                    isExpire ? example.createCriteria().
+                                    andLessThan("endTime", now)
+                                    :
+                            example.createCriteria()
+                                    .andLessThanOrEqualTo("startTime", now)
+                                    .andGreaterThan("endTime", now)
+            );
+        }
         if(productType != null) {
             example.and(
                     example.createCriteria()
@@ -240,7 +314,11 @@ public class UserOrderRecordService extends AbstractService {
                     example.createCriteria()
                             .andEqualTo("service", service.key)
             );
-            if(order == null || order.isEmpty()) order = "id";
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("isStop", 0)
+        );
+        if(order == null || order.isEmpty()) order = "id";
         switch(direction){
             case ASC:
                 example.orderBy(order).asc();
@@ -265,33 +343,13 @@ public class UserOrderRecordService extends AbstractService {
                         .andEqualTo("userId", userId)
                         .andEqualTo("productType", ProductType.DATA.key)
                         .andEqualTo("productId", dataId)
+                        .andEqualTo("isStop", 0)
         );
         UserOrderRecord service = one(userOrderRecordMapper, example);
         return service != null;
     }
 
     /**
-     * 获取未使用的购买信息:根据模块
-     * @param userId
-     * @param key
-     * @return
-     */
-    public List<UserOrderRecord> listUnUseService(Integer userId, ServiceKey key){
-        Example example = new Example(UserOrderRecord.class);
-        example.and(
-                example.createCriteria()
-                        .andEqualTo("userId", userId)
-                        .andEqualTo("service", key.key)
-                        .andGreaterThanOrEqualTo("startTime", new Date())
-                        .andLessThan("endTime", new Date())
-                        .andEqualTo("isUsed", 0)
-                        .andEqualTo("isStop", 0)
-        );
-        example.orderBy("startTime").asc();
-        return select(userOrderRecordMapper, example);
-    }
-
-    /**
      * 获取未开通的服务记录
      * @param userId
      * @param key
@@ -303,8 +361,8 @@ public class UserOrderRecordService extends AbstractService {
                 example.createCriteria()
                         .andEqualTo("userId", userId)
                         .andEqualTo("service", key.key)
-                        .andGreaterThanOrEqualTo("startTime", new Date())
-                        .andLessThan("endTime", new Date())
+                        .andLessThanOrEqualTo("startTime", new Date())
+                        .andGreaterThan("endTime", new Date())
                         .andEqualTo("isUsed", 0)
                         .andEqualTo("isStop", 0)
         );
@@ -386,6 +444,7 @@ public class UserOrderRecordService extends AbstractService {
      * @return
      */
     public Page<UserOrderRecord> listWithStudyAdmin(int page, int size, String[] modules, Integer structId, Integer courseId, Integer userId, String teacher, String order, DirectionStatus direction){
+        // todo teacher
         if(order == null || order.isEmpty()){
             order = "id";
         }

+ 14 - 1
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserReportService.java

@@ -32,7 +32,20 @@ public class UserReportService extends AbstractService {
     private UserReportMapper userReportMapper;
     @Resource
     private UserReportRelationMapper userReportRelationMapper;
-
+    /**
+     * 查找userPaper最后一次做题记录
+     * @param paperIds
+     */
+    public List<UserReport> listByPaper(Collection paperIds){
+        if(paperIds == null || paperIds.size() == 0) return new ArrayList<>();
+        Example example = new Example(UserReport.class);
+        example.and(
+                example.createCriteria()
+                        .andIn("paperId", paperIds)
+        );
+        example.orderBy("id").asc();
+        return select(userReportMapper, example);
+    }
     /**
      * 查找userPaper最后一次做题记录
      * @param paperIds

+ 34 - 3
server/gateway-api/src/main/java/com/qxgmat/task/AsyncTask.java

@@ -4,12 +4,15 @@ import com.alibaba.fastjson.JSONObject;
 import com.nuliji.tools.Transform;
 import com.qxgmat.data.constants.enums.QuestionDifficult;
 import com.qxgmat.data.constants.enums.QuestionType;
+import com.qxgmat.data.constants.enums.ServiceKey;
 import com.qxgmat.data.constants.enums.SettingKey;
 import com.qxgmat.data.constants.enums.logic.ExerciseLogic;
 import com.qxgmat.data.constants.enums.logic.SentenceLogic;
 import com.qxgmat.data.constants.enums.module.StructModule;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.relation.entity.QuestionNoRelation;
+import com.qxgmat.service.UserServiceService;
+import com.qxgmat.service.UsersService;
 import com.qxgmat.service.extend.ExerciseService;
 import com.qxgmat.service.extend.MessageExtendService;
 import com.qxgmat.service.extend.SentenceService;
@@ -57,6 +60,15 @@ public class AsyncTask {
     @Autowired
     private MessageExtendService messageExtendService;
 
+    @Autowired
+    private UserServiceService userServiceService;
+
+    @Autowired
+    private UsersService usersService;
+
+    @Autowired
+    private UserOrderRecordService userOrderRecordService;
+
     @Async
     public void autoExercisePaper() {
         logger.info("自动练习组卷:顺序,考点,难易度");
@@ -242,8 +254,17 @@ public class AsyncTask {
         logger.info("发布机经:发送到订阅用户邮箱");
         long start = System.currentTimeMillis();
         TextbookLibrary textbookLibrary = textbookLibraryService.getLatest();
-        // todo 获取订阅用户
-        // messageExtendService.sendTextbookUpdate(user, textbookLibrary);
+        List<UserService> userServiceList;
+        int page = 1;
+        int size = 20;
+        do {
+            userServiceList = userServiceService.listByService(page, size, ServiceKey.TEXTBOOK, true);
+            Collection userIds = Transform.getIds(userServiceList, UserService.class, "userId");
+            List<User> userList = usersService.select(userIds);
+            for(User user : userList){
+                 messageExtendService.sendTextbookUpdate(user, textbookLibrary);
+            }
+        }while(userServiceList.size() >= size);
         long end = System.currentTimeMillis();
         logger.info("发布机经,耗时:" + (end - start) + "毫秒");
     }
@@ -254,7 +275,17 @@ public class AsyncTask {
         long start = System.currentTimeMillis();
         CourseDataHistory history = courseDataHistoryService.get(dataHistoryId);
         CourseData courseData = courseDataService.get(history.getDataId());
-        // todo 获取订阅用户
+        List<UserOrderRecord> userOrderRecordList;
+        int page = 1;
+        int size = 20;
+        do {
+            userOrderRecordList = userOrderRecordService.listWithData(page, size, courseData.getId(), true);
+            Collection userIds = Transform.getIds(userOrderRecordList, UserOrderRecord.class, "userId");
+            List<User> userList = usersService.select(userIds);
+            for(User user : userList){
+                messageExtendService.sendDataUpdate(user, courseData, history);
+            }
+        }while(userOrderRecordList.size() >= size);
         // messageExtendService.sendDataUpdate(user, courseData, history);
         long end = System.currentTimeMillis();
         logger.info("资料更新,耗时:" + (end - start) + "毫秒");

+ 149 - 21
server/gateway-api/src/main/java/com/qxgmat/task/ScheduledTask.java

@@ -3,29 +3,32 @@ package com.qxgmat.task;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 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.SettingKey;
+import com.qxgmat.data.constants.enums.module.CourseModule;
 import com.qxgmat.data.constants.enums.status.MessageStatus;
-import com.qxgmat.data.dao.entity.MessageTemplate;
-import com.qxgmat.data.dao.entity.Setting;
-import com.qxgmat.data.dao.entity.User;
+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.UsersService;
+import com.qxgmat.service.extend.CourseExtendService;
 import com.qxgmat.service.extend.MessageExtendService;
-import com.qxgmat.service.inline.MessageTemplateService;
-import com.qxgmat.service.inline.SettingService;
+import com.qxgmat.service.extend.PreviewService;
+import com.qxgmat.service.inline.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
+import javax.tools.Tool;
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 
 /**
  * Created by gaojie on 2017/11/20.
@@ -52,6 +55,36 @@ public class ScheduledTask {
     @Autowired
     private MessageExtendService messageExtendService;
 
+    @Autowired
+    private CourseService courseService;
+
+    @Autowired
+    private CourseNoService courseNoService;
+
+    @Autowired
+    private PreviewService previewService;
+
+    @Autowired
+    private PreviewAssignService previewAssignService;
+
+    @Autowired
+    private UserPaperService userPaperService;
+
+    @Autowired
+    private UserCourseAppointmentService userCourseAppointmentService;
+
+    @Autowired
+    private UserOrderRecordService userOrderRecordService;
+
+    @Autowired
+    private CourseExtendService courseExtendService;
+
+    @Autowired
+    private UserCourseProgressService userCourseProgressService;
+
+    @Autowired
+    private UserCourseRecordService userCourseRecordService;
+
     /**
      * cron表达式:* * * * * *(共6位,使用空格隔开,具体如下)
      * cron表达式:*(秒0-59) *(分钟0-59) *(小时0-23) *(日期1-31) *(月份1-12或是JAN-DEC) *(星期1-7或是SUN-SAT)
@@ -143,12 +176,22 @@ public class ScheduledTask {
         logger.info("Start auto Send message");
         List<MessageTemplate> list = messageTemplateService.listCustomerExpire();
         for(MessageTemplate message : list){
+            Map<String, String> params = new HashMap<>();
             messageTemplateService.edit(MessageTemplate.builder()
                     .id(message.getId())
                     .sendStatus(MessageStatus.SENDING.index)
                     .build());
             int number = 0;
-            // todo
+            int page = 1;
+            int size = 20;
+            List<User> userList;
+            do{
+                userList = usersService.listAdmin(page, size, null, null, null, null, null, null, null, null);
+                for(User user : userList){
+                    messageExtendService.sendCustom(user, message, params);
+                    number += 1;
+                }
+            }while(userList.size()>= size);
             messageTemplateService.edit(MessageTemplate.builder()
                     .id(message.getId())
                     .sendNumber(number)
@@ -157,14 +200,6 @@ public class ScheduledTask {
         }
     }
 
-    // 每小时判断发送预习作业消息
-    @Scheduled(cron="0 0 * * * *")
-    public void autoSendPreviewMessage(){
-
-        // messageExtendService.sendPreviewNotice(user, assign);
-        logger.info("Start auto Send Preview message");
-    }
-
     // 每天判断是否自动组卷
     @Scheduled(cron="0 1 0 * * *")
     public void autoExercisePaper() throws ParseException {
@@ -181,15 +216,108 @@ public class ScheduledTask {
         String now = sdf.format(new Date());
         if(param.equals(now)){
             // 当天
-            logger.info("auto Exercise Paper start, date is empty");
+            logger.info("auto Exercise Paper start");
             asyncTask.autoExercisePaperError();
-            logger.info("auto Exercise Paper finish, date is empty");
+            logger.info("auto Exercise Paper finish");
         }
     }
 
-    // 每天判断是否恢复停课: 最多一个月就需要恢复停课
-    @Scheduled(cron="0 1 0 * * *")
-    public void restoreSuspendCourse() throws ParseException {
+    // 每小时判断发送预习作业消息:发送预习作业到期通知:6小时,去除夜间12-7点
+    @Scheduled(cron="0 0 * * * *")
+    public void autoSendPreviewMessage(){
+        // 只考虑assign设定到期时间的作业
+        Date now = new Date();
+        Date startTime = Tools.addHour(now, 5);
+        Date endTime = Tools.addHour(now, 6);
+        int hour = Tools.hour(endTime);
+        if (hour == 0){
+            endTime = Tools.addHour(endTime, 7);
+        }else if(hour >0 && hour <= 7){
+            startTime = Tools.addHour(startTime, 7);
+            endTime = Tools.addHour(endTime, 7);
+        }else if(hour > 7 && hour <= 14){
+            // 8-14已经提早播报,暂停
+            return;
+        }
+        logger.info("Start auto Send Preview message");
+        List<PreviewAssign> previewAssignList = previewAssignService.listAppointmentExpire(startTime.toString(), endTime.toString());
+        for(PreviewAssign previewAssign : previewAssignList){
+            Course course = courseService.get(previewAssign.getCourseId());
+            if (previewAssign.getCourseAppointment() > 0){
+                // 1v1,查找对应预约用户
+                UserCourseAppointment appointment = userCourseAppointmentService.get(previewAssign.getCourseAppointment());
+                User user = usersService.get(appointment.getUserId());
+                messageExtendService.sendPreviewNotice(user, course, previewAssign);
+            }
+//            if (previewAssign.getCourseTime() > 0){
+//                previewService.replaceTitle(previewAssign);
+//                // 小班课,查找时间段用户
+//                List<UserOrderRecord> userOrderRecordList = userOrderRecordService.listByOnline(previewAssign.getCourseId(), previewAssign.getCourseTime());
+//                for(UserOrderRecord record : userOrderRecordList){
+//                    User user = usersService.get(record.getUserId());
+//                    messageExtendService.sendPreviewNotice(user, course, previewAssign);
+//                }
+//            }
+        }
+        logger.info("End auto Send Preview message");
+    }
+
+    // 课程到期:判断延期奖励,听课频率<=2天/作业100%->10天,90%以上7天
+    @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());
+        for(UserOrderRecord record : recordList){
+            Course course = courseService.get(record.getId());
+            if (CourseModule.ValueOf(course.getCourseModule()) != CourseModule.VIDEO){
+                continue;
+            }
+            List<CourseNo> courseNoList = courseNoService.allCourse(course.getId());
+            Collection courseNoIds = Transform.getIds(courseNoList, CourseNo.class, "id");
+
+            // 听课频率
+            List<UserCourseProgress> progressList = userCourseProgressService.listCourse(record.getId(), course.getId());
+            List<UserCourseRecord> records = userCourseRecordService.allWithRecord(record.getId());
+            Integer currentNo = courseExtendService.computeCourseNoCurrent(courseNoList, progressList);
+            Integer days = courseExtendService.computeCourseDay(records);
+            if (days/currentNo > 2){
+                continue;
+            }
+
+            // 获取所有作业进度
+            List<PreviewAssign> previewAssignList = previewAssignService.listByCourseNos(course.getId(), courseNoIds);
+            Collection assignIds = Transform.getIds(previewAssignList, PreviewAssign.class, "id");
+            List<UserPaper> userPaperList = userPaperService.listWithCourse(record.getUserId(), assignIds, record.getId());
+//            Collection paperIds = Transform.getIds(userPaperList, UserPaper.class, "id");
+//            List<UserReport> userReportList = userReportService.listByPaper(paperIds);
+            int finish = 0;
+            for(UserPaper userPaper: userPaperList){
+                if (userPaper.getTimes() > 0){
+                    finish += 1;
+                }
+            }
+            int percent = finish * 100 / assignIds.size();
+            if (percent <90){
+                continue;
+            }
+            int day = 7;
+            if (percent == 100){
+                day = 10;
+            }
+            courseExtendService.awardCourse(record.getUserId(), record.getId(), day);
+        }
+    }
 
+    // 每天判断是否恢复停课: 最多30就需要恢复停课
+    @Scheduled(cron="0 1 0 * * *")
+    public void restoreSuspendCourse() {
+        Date endTime = Tools.today();
+        Date startTime = Tools.addDate(endTime, 30);
+        List<UserOrderRecord> recordList = userOrderRecordService.allSuspendExpire(startTime.toString(), endTime.toString());
+        for(UserOrderRecord record : recordList){
+            courseExtendService.restoreCourse(record.getUserId(), record.getId());
+        }
     }
 }

+ 6 - 0
server/tools/src/main/java/com/nuliji/tools/Tools.java

@@ -271,6 +271,12 @@ public class Tools {
         return calendar.get(Calendar.YEAR);
     }
 
+    public static int hour(Date date){
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        return calendar.get(Calendar.HOUR_OF_DAY);
+    }
+
     public static Date addHour(Date date, int hour){
         Calendar c = Calendar.getInstance();
         c.setTime(date);