Browse Source

feat(server): 资料订阅

Go 6 years ago
parent
commit
ac2f222255
71 changed files with 1437 additions and 238 deletions
  1. 13 2
      front/project/admin/routes/ready/article/page.js
  2. 2 1
      front/project/admin/routes/ready/articleDetail/page.js
  3. 2 1
      front/project/admin/routes/ready/readDetail/page.js
  4. 1 1
      front/project/admin/routes/show/deploy/page.js
  5. 2 2
      front/project/admin/routes/subject/sentenceQuestion/page.js
  6. 13 11
      front/project/www/components/Other/index.js
  7. 48 0
      front/project/www/components/OtherModal/index.js
  8. 2 2
      front/project/www/components/PayModal/index.js
  9. 79 101
      front/project/www/routes/course/main/page.js
  10. 1 1
      front/project/www/routes/my/collect/page.js
  11. 3 4
      front/project/www/routes/my/error/page.js
  12. 14 3
      front/project/www/routes/my/tools/page.js
  13. 5 2
      front/project/www/routes/page/home/page.js
  14. 9 7
      front/project/www/routes/paper/process/sentence/index.js
  15. 4 0
      front/project/www/stores/course.js
  16. 4 0
      front/project/www/stores/main.js
  17. 33 4
      front/project/www/stores/my.js
  18. 8 0
      front/project/www/stores/question.js
  19. 3 0
      front/src/components/Editor/index.js
  20. 1 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/module/ChannelModule.java
  21. 2 1
      server/data/src/main/java/com/qxgmat/data/constants/enums/user/ExportType.java
  22. 7 0
      server/data/src/main/java/com/qxgmat/data/dao/UserCourseDataSubscribeMapper.java
  23. 7 0
      server/data/src/main/java/com/qxgmat/data/dao/UserSearchHistoryMapper.java
  24. 26 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/Question.java
  25. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/ReadyArticleCategory.java
  26. 0 26
      server/data/src/main/java/com/qxgmat/data/dao/entity/SentenceQuestion.java
  27. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/User.java
  28. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserAskCourse.java
  29. 160 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserCourseDataSubscribe.java
  30. 195 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserSearchHistory.java
  31. 2 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/QuestionMapper.xml
  32. 3 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/ReadyArticleCategoryMapper.xml
  33. 0 12
      server/data/src/main/java/com/qxgmat/data/dao/mapping/SentenceQuestionMapper.xml
  34. 3 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserAskCourseMapper.xml
  35. 19 0
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserCourseDataSubscribeMapper.xml
  36. 3 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserMapper.xml
  37. 20 0
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserSearchHistoryMapper.xml
  38. 2 2
      server/data/src/main/java/com/qxgmat/data/relation/CourseDataRelationMapper.java
  39. 5 0
      server/data/src/main/java/com/qxgmat/data/relation/UserAskCourseRelationMapper.java
  40. 2 2
      server/data/src/main/java/com/qxgmat/data/relation/mapping/CourseDataRelationMapper.xml
  41. 9 0
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserAskCourseRelationMapper.xml
  42. 23 1
      server/data/src/main/resources/db/migration/V1__init_table.sql
  43. 3 3
      server/gateway-api/src/main/java/com/qxgmat/controller/api/AuthController.java
  44. 12 0
      server/gateway-api/src/main/java/com/qxgmat/controller/api/BaseController.java
  45. 17 4
      server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java
  46. 97 7
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  47. 17 0
      server/gateway-api/src/main/java/com/qxgmat/controller/api/QuestionController.java
  48. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/QuestionDto.java
  49. 0 10
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/SentenceQuestionDto.java
  50. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/QuestionDetailExtendDto.java
  51. 0 10
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/SentenceQuestionDetailExtendDto.java
  52. 13 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/AskCourseViewDto.java
  53. 13 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/DataEmailSubscribeDto.java
  54. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/DataSubscribeDto.java
  55. 61 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/QuestionNoDetailDto.java
  56. 13 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/SearchHistoryDto.java
  57. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseDataDetailDto.java
  58. 29 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserSearchHistoryDto.java
  59. 4 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/CourseExtendService.java
  60. 19 2
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ExportService.java
  61. 9 4
      server/gateway-api/src/main/java/com/qxgmat/service/extend/MessageExtendService.java
  62. 13 0
      server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java
  63. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/CourseDataService.java
  64. 10 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/ReadyArticleService.java
  65. 7 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserAskCourseService.java
  66. 95 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseDataSubscribeService.java
  67. 12 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserExportService.java
  68. 17 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderRecordService.java
  69. 87 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserSearchHistoryService.java
  70. 7 4
      server/gateway-api/src/main/java/com/qxgmat/task/AsyncTask.java
  71. 1 0
      server/tools/src/main/java/com/nuliji/tools/third/sendcloud/SendCloudMail.java

+ 13 - 2
front/project/admin/routes/ready/article/page.js

@@ -131,7 +131,7 @@ export default class extends Page {
         row.value = row.id;
         row.title = <Row style={{ width: 400 }}>
           <Col span={11}>{row.title}</Col>
-          <Col span={5}>{row.parentId > 0 && <Switch checked={row.isData} checkedChildren='资料节点' unCheckedChildren='基本节点' onChange={() => this.changeCategoryData(row.id, !row.isData)} />}</Col>
+          <Col span={5}>{row.parentId > 0 && <Switch checked={row.isData} checkedChildren='资料节点' unCheckedChildren='基本节点' onChange={() => this.changeCategoryData(row.id, !row.isData)} />}{row.parentId === 0 && <Switch checked={row.isRoom} checkedChildren='考场节点' unCheckedChildren='非考场' onChange={() => this.changeCategoryRoom(row.id, !row.isRoom)} />}</Col>
           <Col span={5}>{row.parentId > 0 && row.isData > 0 && <Switch checked={row.isOfficial} checkedChildren='官方资料' unCheckedChildren='非官方资料' onChange={() => this.changeCategoryOfficial(row.id, !row.isOfficial)} />}</Col>
         </Row>;
         return row;
@@ -172,12 +172,23 @@ export default class extends Page {
     });
   }
 
+  changeCategoryRoom(id, checked) {
+    const category = this.categoryMap[id];
+    if (category.isRoom) {
+      if (checked) return;
+    } else if (!checked) return;
+    Ready.editCategory({ id, isRoom: checked ? 1 : 0, isData: 0 })
+      .then(() => {
+        this.refreshCategory();
+      });
+  }
+
   changeCategoryData(id, checked) {
     const category = this.categoryMap[id];
     if (category.isData) {
       if (checked) return;
     } else if (!checked) return;
-    Ready.editCategory({ id, isData: checked ? 1 : 0 })
+    Ready.editCategory({ id, isData: checked ? 1 : 0, isRoom: 0 })
       .then(() => {
         this.refreshCategory();
       });

+ 2 - 1
front/project/admin/routes/ready/articleDetail/page.js

@@ -9,6 +9,7 @@ import Select from '@src/components/Select';
 import { formatFormError, getMap } from '@src/services/Tools';
 import { asyncSMessage } from '@src/services/AsyncTools';
 import { Ready } from '../../../stores/ready';
+import { System } from '../../../stores/system';
 
 export default class extends Page {
   init() {
@@ -152,7 +153,7 @@ export default class extends Page {
         <Form.Item label='正文'>
           {getFieldDecorator('content', {
           })(
-            <Editor placeholder='输入内容' />,
+            <Editor placeholder='输入内容' onUpload={(file) => System.uploadImage(file)} />,
           )}
         </Form.Item>
       </Form>

+ 2 - 1
front/project/admin/routes/ready/readDetail/page.js

@@ -9,6 +9,7 @@ import Select from '@src/components/Select';
 import { formatFormError, getMap } from '@src/services/Tools';
 import { asyncSMessage } from '@src/services/AsyncTools';
 import { Ready } from '../../../stores/ready';
+import { System } from '../../../stores/system';
 
 export default class extends Page {
   init() {
@@ -95,7 +96,7 @@ export default class extends Page {
         <Form.Item label='正文'>
           {getFieldDecorator('content', {
           })(
-            <Editor placeholder='输入内容' />,
+            <Editor placeholder='输入内容' onUpload={(file) => System.uploadImage(file)} />,
           )}
         </Form.Item>
       </Form>

+ 1 - 1
front/project/admin/routes/show/deploy/page.js

@@ -139,7 +139,7 @@ export default class extends Page {
         <Form.Item label='Code说明'>
           {getFieldDecorator('sentence.detail', {
           })(
-            <Editor placeholder='输入内容' />,
+            <Editor placeholder='输入内容' onUpload={(file) => System.uploadImage(file)} />,
           )}
         </Form.Item>
       </Row>

+ 2 - 2
front/project/admin/routes/subject/sentenceQuestion/page.js

@@ -75,9 +75,9 @@ export default class extends Page {
         form.getFieldDecorator('question.stem');
         form.getFieldDecorator('question.questionType');
         form.getFieldDecorator('question.place');
+        form.getFieldDecorator('question.chineseContent');
         form.getFieldDecorator('question.id');
         form.getFieldDecorator('questionId');
-        form.getFieldDecorator('chinese');
         form.getFieldDecorator('title');
         form.getFieldDecorator('isPaper');
         form.getFieldDecorator('isTrail');
@@ -466,7 +466,7 @@ export default class extends Page {
     return <Block flex>
       <Form>
         <Form.Item label='中文解析'>
-          {getFieldDecorator('chinese', {
+          {getFieldDecorator('question.chineseContent', {
           })(
             <Editor placeholder='输入内容' />,
           )}

+ 13 - 11
front/project/www/components/Other/index.js

@@ -23,9 +23,9 @@ export class CommentFalls extends Component {
             <div className="grid-item">
               <div className="item">
                 <div className="item-header">
-                  <Assets src={row.avatar} />
-                  <div className="name">{row.nickname}</div>
-                  <div className="date">{formatDate(row.date, 'yyyy年mm月dd日')}</div>
+                  <Assets src={row.user ? row.user.avatar : row.avatar} />
+                  <div className="name">{row.user ? row.user.nickname : row.nickname}</div>
+                  <div className="date">{formatDate(row.createTime, 'yyyy年mm月dd日')}</div>
                 </div>
                 <div className="item-body">{row.content}</div>
               </div>
@@ -57,6 +57,7 @@ export class AnswerCarousel extends Component {
   }
 
   render() {
+    const { onFaq } = this.props;
     const { index } = this.state;
     const { list = [], hideBtn = false } = this.props;
     return (
@@ -69,7 +70,7 @@ export class AnswerCarousel extends Component {
                   <div className="item">
                     <div className="item-block">
                       <Assets name="question" />
-                      {item.question}
+                      {item.content}
                     </div>
                     <div className="item-block">
                       <Assets name="answer" />
@@ -85,11 +86,11 @@ export class AnswerCarousel extends Component {
           <Assets name="footer_previous_highlight_1" className="prev" onClick={() => this.onPrev()} />
         </div>
         {!hideBtn && (
-          <Button size="lager" radius>
-            <Assets name="kf" className="m-r-5" />
-            立即咨询
+          <Button size="lager" radius onClick={() => onFaq()}>
+            <Assets name="kf" className="m-r-5" />立即咨询
           </Button>
-        )}
+        )
+        }
       </div>
     );
   }
@@ -97,6 +98,7 @@ export class AnswerCarousel extends Component {
 
 export class Consultation extends Component {
   render() {
+    const { data = {} } = this.props;
     return (
       <div className="other-consultation">
         <div className="list">
@@ -114,18 +116,18 @@ export class Consultation extends Component {
         <div className="list">
           <div className="item">
             <div className="t-1 t-s-16 m-b-10">电话咨询</div>
-            <div className="t-1 t-s-16 t-w-b">(400)800-8888</div>
+            <div className="t-1 t-s-16 t-w-b">{data.phone}</div>
           </div>
           <div className="item">
             <div className="t-1 t-s-16 m-b-10">邮件咨询</div>
-            <div className="t-1 t-s-16 t-w-b">service@cat.com</div>
+            <div className="t-1 t-s-16 t-w-b">{data.email}</div>
           </div>
           <div className="item">
             <div className="t-1 t-s-16 m-b-10">微信咨询</div>
             <div className="t-1 t-s-16 t-w-b">
               <Popover content={<Assets name="qrcode" />}>
                 <span>
-                  <Assets name="erweima" />
+                  <Assets src={data.wechatImage} />
                 </span>
               </Popover>
             </div>

+ 48 - 0
front/project/www/components/OtherModal/index.js

@@ -1003,6 +1003,54 @@ export class TextbookFeedbackModal extends Component {
   }
 }
 
+export class FaqModal extends Component {
+  constructor(props) {
+    super(props);
+    this.state = { data: {} };
+  }
+
+  onConfirm() {
+    const { onConfirm } = this.props;
+    const { data } = this.state;
+    if (!data.content) return;
+    My.addFaq(data.channel, data.position, data.content).then(() => {
+      if (onConfirm) onConfirm();
+      this.setState({ data: {} });
+    });
+  }
+
+  onCancel() {
+    const { onCancel } = this.props;
+    if (onCancel) onCancel();
+    this.setState({ data: {} });
+  }
+
+  render() {
+    const { show } = this.props;
+    const { data } = this.state;
+    return (
+      <Modal
+        show={show}
+        title="咨询"
+        onConfirm={() => this.onConfirm()}
+        onCancel={() => this.onCancel()}
+      >
+        <textarea
+          value={data.content}
+          className="b-c-1 w-10 p-10"
+          rows={6}
+          placeholder="请输入您的问题!"
+          onChange={e => {
+            data.content = e.target.value;
+            this.setState({ data });
+          }}
+        />
+        <div className="b-b m-t-2" />
+      </Modal>
+    );
+  }
+}
+
 export class CommentModal extends Component {
   constructor(props) {
     super(props);

+ 2 - 2
front/project/www/components/PayModal/index.js

@@ -164,7 +164,7 @@ export class PayModal extends Component {
         checkout={order.checkouts[0]}
         onConfirm={() => this.close()}
       />,
-      show && order.checkouts.length > 1 && <PayMutilModal
+      show && (order.checkouts.length > 1 || order.productTypes.indexof('course_package') < 0) && <PayMutilModal
         show
         contract={contract}
         order={order}
@@ -173,7 +173,7 @@ export class PayModal extends Component {
         onClose={() => this.close()}
         onConfirm={() => this.confirm()}
       />,
-      show && order.checkouts.length === 1 && <PayMModal
+      show && order.checkouts.length === 1 && order.productTypes.indexof('course_package') < 0 && <PayMModal
         show
         contract={contract}
         order={order}

+ 79 - 101
front/project/www/routes/course/main/page.js

@@ -2,16 +2,44 @@ import React from 'react';
 import './index.less';
 import Assets from '@src/components/Assets';
 import Page from '@src/containers/Page';
+import { getMap } from '@src/services/Tools';
 import Footer from '../../../components/Footer';
+import { FaqModal } from '../../../components/OtherModal';
 import { CommentFalls, AnswerCarousel, Consultation, Contact } from '../../../components/Other';
 import Button from '../../../components/Button';
+import { Main } from '../../../stores/main';
+import { Course } from '../../../stores/course';
+import { ServiceKey, ServiceParamMap } from '../../../../Constant';
+import { Order } from '../../../stores/order';
 
 export default class extends Page {
   initState() {
     return {};
   }
 
+  initData() {
+    Main.getCourseIndex()
+      .then(result => {
+        this.setState({ courseIndex: result });
+      });
+    Main.getBase()
+      .then(result => {
+        this.setState({ base: result });
+      });
+    Course.listPackage({ isSpecial: true, page: 1, size: 3 })
+      .then(result => {
+        this.setState({ packages: result.list || [] });
+      });
+    Main.listFaq({ page: 1, size: 100, channel: 'course-index' }).then(result => {
+      this.setState({ faqs: result.list });
+    });
+    Main.listComment({ page: 1, size: 100, channel: 'course-index' }).then(result => {
+      this.setState({ coments: result.list });
+    });
+  }
+
   renderView() {
+    const { courseIndex = {}, base = {}, packages = [], faqs = [], comments = [], showFaq, faq = {} } = this.state;
     return (
       <div>
         <div className="block-1">
@@ -19,10 +47,10 @@ export default class extends Page {
             <Assets name="" />
             <div className="main-title">Waste Less, Learn More.</div>
             <div className="btn-list">
-              <Button width={100} size="lager">
+              <Button width={100} size="lager" onClick={() => linkTo('/course/online?tab=package')}>
                 查看套餐
               </Button>
-              <Button className="t-4" width={100} theme="default" size="lager">
+              <Button className="t-4" width={100} theme="default" size="lager" onClick={() => linkTo('/course/online?tab=single')}>
                 试听课程
               </Button>
             </div>
@@ -32,89 +60,58 @@ export default class extends Page {
           <div className="main-title">找到你的Style</div>
           <div className="video-list">
             <div className="video-item">
-              <Assets width={70} height={70} name="play" className="play" />
-              <div className="name">在线课程 ></div>
+              <Assets width={70} height={70} name="play" className="play" src={courseIndex.onlineVideo} />
+              <div className="name" onClick={() => linkTo('/course/online')}>在线课程 ></div>
             </div>
             <div className="video-item">
-              <Assets width={70} height={70} name="play" className="play" />
-              <div className="name">1v1私教 ></div>
+              <Assets width={70} height={70} name="play" className="play" src={courseIndex.vsVideo} />
+              <div className="name" onClick={() => linkTo('/course/vs')}>1v1私教 ></div>
             </div>
           </div>
           <div className="class-list">
-            <div className="class-item">
-              <Assets width={55} height={52} className="new" name="noviciate" />
-              <div className="t-s-22 t-4 m-b-5 f-w-b">基础刷题 </div>
-              <div className="t-8 m-b-1">包含最新 XXXXXXX 的全部课程XXXXX;机经券×1+VIP×3 月+模考×1</div>
-              <div className="t-1 t-s-12">包含课程</div>
-              <div className="m-b-5">
-                <div className="m-b-1 m-r-5 t-9 t-s-12 d-i-b b-c-2 p-5">OG20阅读刷题(7课时)</div>
-              </div>
-              <div className="t-1 t-s-12">配套服务</div>
-              <div className="m-b-5">
-                <div className="m-b-1 m-r-5 t-9 t-s-12 d-i-b b-c-2 p-5">OG20阅读刷题(7课时)</div>
-              </div>
-              <div className="t-1 t-s-12">赠送服务</div>
-              <div className="m-b-2">
-                <div className="m-b-1 m-r-5 t-9 t-s-12 d-i-b b-c-2 p-5">OG20阅读刷题(7课时)</div>
-              </div>
-              <div className="t-8">
-                总价值: <span className="t-d-l-t">¥18888</span>
-              </div>
-              <div className="t-1 t-s-18 f-w-b m-b-1">套餐价: ¥8888</div>
-              <div className="m-b-5">
-                <Button size="lager">立即购买</Button>
-              </div>
-            </div>
-            <div className="class-item">
-              <div className="t-s-22 t-11 m-b-5 f-w-b">系统授课 </div>
-              <div className="t-8 m-b-1">包含最新 XXXXXXX 的全部课程XXXXX;机经券×1+VIP×3 月+模考×1</div>
-              <div className="t-1 t-s-12">包含课程</div>
-              <div className="m-b-5">
-                <div className="m-b-1 m-r-5 t-9 t-s-12 d-i-b b-c-2 p-5">OG20阅读刷题(7课时)</div>
-                <div className="m-b-1 m-r-5 t-9 t-s-12 d-i-b b-c-2 p-5">OG20阅读刷题(7课时)</div>
-              </div>
-              <div className="t-1 t-s-12">配套服务</div>
-              <div className="m-b-5">
-                <div className="m-b-1 m-r-5 t-9 t-s-12 d-i-b b-c-2 p-5">OG20阅读刷题(7课时)</div>
-              </div>
-              <div className="t-1 t-s-12">赠送服务</div>
-              <div className="m-b-2">
-                <div className="m-b-1 m-r-5 t-9 t-s-12 d-i-b b-c-2 p-5">OG20阅读刷题(7课时)</div>
-              </div>
-              <div className="t-8">
-                总价值: <span className="t-d-l-t">¥18888</span>
-              </div>
-              <div className="t-1 t-s-18 f-w-b m-b-1">套餐价: ¥8888</div>
-              <div className="m-b-5">
-                <Button size="lager">立即购买</Button>
-              </div>
-            </div>
-            <div className="class-item">
-              <div className="t-s-22 t-12 m-b-5 f-w-b">思维提升 </div>
-              <div className="t-8 m-b-1">包含最新 XXXXXXX 的全部课程XXXXX;机经券×1+VIP×3 月+模考×1</div>
-              <div className="t-1 t-s-12">包含课程</div>
-              <div className="m-b-5">
-                <div className="m-b-1 m-r-5 t-9 t-s-12 d-i-b b-c-2 p-5">OG20阅读刷题(7课时)</div>
-              </div>
-              <div className="t-1 t-s-12">配套服务</div>
-              <div className="m-b-5">
-                <div className="m-b-1 m-r-5 t-9 t-s-12 d-i-b b-c-2 p-5">OG20阅读刷题(7课时)</div>
-              </div>
-              <div className="t-1 t-s-12">赠送服务</div>
-              <div className="m-b-2">
-                <div className="m-b-1 m-r-5 t-9 t-s-12 d-i-b b-c-2 p-5">OG20阅读刷题(7课时)</div>
-              </div>
-              <div className="t-8">
-                总价值: <span className="t-d-l-t">¥18888</span>
-              </div>
-              <div className="t-1 t-s-18 f-w-b m-b-1">套餐价: ¥8888</div>
-              <div className="m-b-5">
-                <Button size="lager">立即购买</Button>
-              </div>
-            </div>
+            {packages.map(data => {
+              const originMoney = data.courses.reduce((a, y) => a + y.money, 0);
+              const novice = data.courses.filter(row => row.crowd !== 'novice').length === 0;
+              return <div className="class-item">
+                {novice && <Assets width={55} height={52} className="new" name="noviciate" />}
+                <div className="t-s-22 t-4 m-b-5 f-w-b">{data.title}</div>
+                <div className="t-8 m-b-1">{data.description}</div>
+                <div className="t-1 t-s-12">包含课程</div>
+                <div className="m-b-5">
+                  {data.courses.map((course => {
+                    return <div className="m-b-1 m-r-5 t-9 t-s-12 d-i-b b-c-2 p-5">{course.title}({course.noNumber}课时)</div>;
+                  }))}
+                </div>
+                <div className="t-1 t-s-12">配套服务</div>
+                <div className="m-b-5">
+                  <div className="m-b-1 m-r-5 t-9 t-s-12 d-i-b b-c-2 p-5">预习作业</div>
+                  <div className="m-b-1 m-r-5 t-9 t-s-12 d-i-b b-c-2 p-5">课后答题</div>
+                </div>
+                <div className="t-1 t-s-12">赠送服务</div>
+                <div className="m-b-2">
+                  {data.gift &&
+                    ServiceKey.map(row => {
+                      if (!data.gift[row.value]) return null;
+                      const list = ServiceParamMap[row.value];
+                      if (list) {
+                        const map = getMap(list, 'value', 'label');
+                        return <div className="m-b-1 m-r-5 t-9 t-s-12 d-i-b b-c-2 p-5">{row.label}×{map[data.gift[row.value]]}</div>;
+                      }
+                      return <div className="m-b-1 m-r-5 t-9 t-s-12 d-i-b b-c-2 p-5">{row.label}×{data.gift[row.value]}</div>;
+                    })}
+                </div>
+                <div className="t-8">
+                  总价值: <span className="t-d-l-t">¥{originMoney}</span>
+                </div>
+                <div className="t-1 t-s-18 f-w-b m-b-1">套餐价: ¥{data.money}</div>
+                <div className="m-b-5">
+                  <Button size="lager" onClick={() => Order.addCheckout({ productType: 'course_package', productId: data.id }).then(() => linkTo('/cart'))}>立即购买</Button>
+                </div>
+              </div>;
+            })}
           </div>
         </div>
-        <Consultation />
+        <Consultation data={base.contact} />
         <div className="block-4">
           <div className="main-title">You will always find the answers</div>
           <Assets name="" />
@@ -142,30 +139,11 @@ export default class extends Page {
             </div>
           </div>
         </div>
-        <CommentFalls />
-        <AnswerCarousel
-          list={[
-            {
-              question: '如果视频课程到期了却没有听完,没听的课程可以退款么',
-              answer: '不可以的,视频课程为虚拟商品,购买成功后不接受退换。',
-            },
-            {
-              question: '学习过程中可以申请停课么?',
-              answer: '每个商品均有1次申请停课的机会,最长停课30天,停课时间不计入使用有效期内。',
-            },
-            {
-              question: '我需要一个整体的GMAT备考计划,报课程的话可以提供么?',
-              answer:
-                '报语文全科“系统授课”的同学会赠送价值900元课前辅导,老师语音一对一与同学交流沟通,为同学提供针对性建议和详细的备考计划。',
-            },
-            {
-              question: '如果视频课程到期了却没有听完,没听的课程可以退款么',
-              answer: '不可以的,视频课程为虚拟商品,购买成功后不接受退换。',
-            },
-          ]}
-        />
-        <Contact />
+        <CommentFalls list={comments} />
+        <AnswerCarousel list={faqs} onFaq={() => this.setState({ showFaq: true, faq: { channel: 'course-index' } })} />
+        <Contact data={base.contact} />
         <Footer />
+        <FaqModal show={showFaq} defaultData={faq} />
       </div>
     );
   }

+ 1 - 1
front/project/www/routes/my/collect/page.js

@@ -301,7 +301,7 @@ export default class extends Page {
   export() {
     const { exportInfo } = this.state;
     this.setState({ showExportWait: true, showExportConfirm: false, showExportAuthConfirm: false });
-    My.exportQuestion(exportInfo)
+    My.exportQuestionCollect(exportInfo)
       .then((result) => {
         openLink(`/export/${result.id}`);
         this.setState({ showExportWait: false });

+ 3 - 4
front/project/www/routes/my/error/page.js

@@ -82,7 +82,6 @@ const columns = [
   },
 ];
 
-
 const exportType = [
   { key: 'question', title: '题目' },
   { key: 'official', title: '官方解析' },
@@ -253,9 +252,9 @@ export default class extends Page {
           return;
         }
         if (info.bindReal) {
-          this.setState({ showExportConfirm: true, exportInfo: { info: exportType.map(row => row.key), questionNoIds: selectList } });
+          this.setState({ showExportConfirm: true, exportInfo: { info: exportType.map(row => row.key), questionNoIds: selectList, answer: true } });
         } else {
-          this.setState({ showExportAuthConfirm: true, exportInfo: { info: exportType.filter(row => !row.auth).map(row => row.key), questionNoIds: selectList } });
+          this.setState({ showExportAuthConfirm: true, exportInfo: { info: exportType.filter(row => !row.auth).map(row => row.key), questionNoIds: selectList, answer: true } });
         }
         break;
       default:
@@ -288,7 +287,7 @@ export default class extends Page {
   export() {
     const { exportInfo } = this.state;
     this.setState({ showExportWait: true, showExportConfirm: false, showExportAuthConfirm: false });
-    My.exportQuestion(exportInfo)
+    My.exportQuestionError(exportInfo)
       .then((result) => {
         openLink(`/export/${result.id}`);
         this.setState({ showExportWait: false });

+ 14 - 3
front/project/www/routes/my/tools/page.js

@@ -272,8 +272,8 @@ export default class extends Page {
     this.refreshQuery(data);
   }
 
-  subscribe(value) {
-    My.subscribeData(value)
+  subscribeDataEmail(value) {
+    My.subscribeDataEmail(value)
       .then(() => {
         const { info } = this.props.user;
         info.dataEmailSubscribe = value;
@@ -284,6 +284,13 @@ export default class extends Page {
       });
   }
 
+  cancelSubscribe(dataId) {
+    My.subscribeData(dataId, false)
+      .then(() => {
+        this.refresh();
+      });
+  }
+
   open(recordId) {
     Order.useRecord(recordId).then(() => {
       this.refresh();
@@ -466,7 +473,7 @@ export default class extends Page {
               <Switch
                 checked={info.dataEmailSubscribe}
                 onChange={() => {
-                  this.subscribe(!info.dataEmailSubscribe);
+                  this.subscribeEmail(!info.dataEmailSubscribe);
                 }}
               />
             </div>
@@ -489,6 +496,7 @@ export default class extends Page {
                       阅读
                     </Button>
                     <div
+                      hidden={item.resource === item.trailResource}
                       className="white"
                       onClick={() => {
                         openLink(item.resource);
@@ -508,6 +516,7 @@ export default class extends Page {
                     { label: '纠错', key: 'feedback' },
                     { label: '评价', key: 'comment' },
                     { label: '更新', key: 'update' },
+                    { label: '移除', key: 'remove' },
                   ]}
                   onClick={value => {
                     const { key } = value;
@@ -517,6 +526,8 @@ export default class extends Page {
                       this.dataHistory({ dataId: item.id, page: 1, size: 10 });
                     } else if (key === 'feedback') {
                       this.setState({ showFeedbackError: true, feedbackError: { dataId: item.id, title: item.title, position: ['', '', ''] } });
+                    } else if (key === 'remove') {
+                      this.cancelSubscribe(item.id);
                     }
                   }}
                 />

+ 5 - 2
front/project/www/routes/page/home/page.js

@@ -21,6 +21,9 @@ export default class extends Page {
     Main.getIndex().then(result => {
       this.setState(result);
     });
+    Main.getBase().then(result => {
+      this.setState({ base: result });
+    });
   }
 
   location(url) {
@@ -34,7 +37,7 @@ export default class extends Page {
   }
 
   renderView() {
-    const { prepare = {}, user = {}, course = [], activity = [], evaluation = [], contact = {} } = this.state;
+    const { prepare = {}, user = {}, course = [], activity = [], evaluation = [], base = {} } = this.state;
     return (
       <div>
         <div className="block block-1">
@@ -290,7 +293,7 @@ export default class extends Page {
             </div>
           </div>
         </div>
-        <Contact data={contact} />
+        <Contact data={base.contact} />
         <Footer />
       </div>
     );

+ 9 - 7
front/project/www/routes/paper/process/sentence/index.js

@@ -232,7 +232,9 @@ export default class extends Component {
   }
 
   renderQuestion() {
-    const { focusKey, answer = {}, stem } = this.state;
+    const { question } = this.props;
+    const { focusKey, answer = {} } = this.state;
+    const { stem } = question;
     return (
       <div className="layout-body">
         <div className="title">
@@ -301,10 +303,11 @@ export default class extends Component {
   }
 
   renderAnswer() {
-    const { mode } = this.props;
-    const { analysisTab, question, userQuestion, stem, showAnswer } = this.state;
+    const { mode, question, userQuestion } = this.props;
+    const { analysisTab, showAnswer } = this.state;
     const { userAnswer = {} } = userQuestion;
     const { answer } = question;
+    const { stem } = question;
     return (
       <div className="layout-body">
         {mode === 'question' ? (
@@ -319,8 +322,7 @@ export default class extends Component {
         ) : (<div className="title">
           <Icon name="question" />
           请分别找出句子中的主语,谓语和宾语,并做出逻辑关系判断。
-          </div>
-        )}
+          </div>)}
 
         <div className="desc" dangerouslySetInnerHTML={{ __html: stem }} />
         <div className="label">主语</div>
@@ -362,12 +364,12 @@ export default class extends Component {
   }
 
   renderText() {
-    const { analysisTab, question = {}, questionNo = {} } = this.state;
+    const { analysisTab, question = {} } = this.state;
     let content;
     switch (analysisTab) {
       case 'chinese':
         content = (
-          <div className="detail-block text-block" dangerouslySetInnerHTML={{ __html: questionNo.chineseContent }} />
+          <div className="detail-block text-block" dangerouslySetInnerHTML={{ __html: question.chineseContent }} />
         );
         break;
       case 'qx':

+ 4 - 0
front/project/www/stores/course.js

@@ -20,6 +20,10 @@ export default class CourseStore extends BaseStore {
     return this.apiGet('/course/ask/list', { keyword, courseId, courseNoId, position, order, direction });
   }
 
+  askView(id) {
+    return this.apiPost('/course/ask/view', { id });
+  }
+
   noProgress(courseId, courseNoId, progress, time, currentCourseNoId) {
     return this.apiPut('/course/no/progress', { courseId, courseNoId, progress, time, currentCourseNoId });
   }

+ 4 - 0
front/project/www/stores/main.js

@@ -178,6 +178,10 @@ export default class MainStore extends BaseStore {
     return this.apiGet('/base/ready_info');
   }
 
+  listArticle(categoryId) {
+    return this.apiGet('/base/article/list', { categoryId });
+  }
+
   listRead({ page, size, plate }) {
     return this.apiGet('/base/read/list', { page, size, plate });
   }

+ 33 - 4
front/project/www/stores/my.js

@@ -406,8 +406,17 @@ export default class MyStore extends BaseStore {
    * 资料全局订阅开关
    * @param {*} subscribe
    */
-  subscribeData(subscribe) {
-    return this.apiPost('/my/data/subscribe', { subscribe });
+  subscribeDataEmail(subscribe) {
+    return this.apiPost('/my/data/email/subscribe', { subscribe });
+  }
+
+  /**
+   * 资料订阅开关
+   * @param {*} dataId
+   * @param {*} subscribe
+   */
+  subscribeData(dataId, subscribe) {
+    return this.apiPost('/my/data/subscribe', { dataId, subscribe });
   }
 
   /**
@@ -486,8 +495,16 @@ export default class MyStore extends BaseStore {
    * 导出题目
    * @param {*} setting
    */
-  exportQuestion(setting) {
-    return this.apiPost('/my/export/question', { setting });
+  exportQuestionCollect(setting) {
+    return this.apiPost('/my/export/question/collect', { setting });
+  }
+
+  /**
+   * 导出题目
+   * @param {*} setting
+   */
+  exportQuestionError(setting) {
+    return this.apiPost('/my/export/question/error', { setting });
   }
 
   /**
@@ -517,6 +534,10 @@ export default class MyStore extends BaseStore {
     return this.apiPost('/my/export/tips', {});
   }
 
+  textbookTips() {
+    return this.apiPost('/my/textbook/tips', {});
+  }
+
   /**
    * 关闭评论提示
    * @param {*}} recordId
@@ -524,6 +545,14 @@ export default class MyStore extends BaseStore {
   courseCommentTips(recordId) {
     return this.apiPost('/my/course/comment/tips', { recordId });
   }
+
+  addSearchHistory(questionNoId) {
+    return this.apiPost('/my/search/history', { questionNoId });
+  }
+
+  listSearchHistory() {
+    return this.apiGet('/my/search/history/list', {});
+  }
 }
 
 export const My = new MyStore({ key: 'my' });

+ 8 - 0
front/project/www/stores/question.js

@@ -21,6 +21,14 @@ export default class QuestionStore extends BaseStore {
     openLink(`/paper/report/${item.prevReport.id}`);
   }
 
+  searchStem({ keyword, questionTypes, module, structIds, place, difficult, order, direction }) {
+    return this.apiGet('/question/search/stem', { keyword, questionTypes, module, structIds, place, difficult, order, direction });
+  }
+
+  searchNo({ keyword, page, size }) {
+    return this.apiGet('/question/search/no', { page, size, keyword });
+  }
+
   /**
    * 通过题目Id获取详情
    * @param {*} questionNoId

+ 3 - 0
front/src/components/Editor/index.js

@@ -50,6 +50,9 @@ class Editor extends React.Component {
     this.state = {};
     this.loading = 0;
     this.modules = Object.assign({}, modules, props.modules);
+    if (props.onUpload && !props.modules.toolbar) {
+      this.modules.toolbar.container.unshift(['image']);
+    }
     // console.log(this.modules);
     this.modules.toolbar.handlers.image = () => this.image();
     Object.keys(this.modules.toolbar.handlers).forEach(key => {

+ 1 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/module/ChannelModule.java

@@ -6,6 +6,7 @@ public enum ChannelModule {
     EXAMINATION("examination"),
     COURSE("course"),
     ;
+
     public String key;
     private ChannelModule(String key){
         this.key = key;

+ 2 - 1
server/data/src/main/java/com/qxgmat/data/constants/enums/user/ExportType.java

@@ -1,7 +1,8 @@
 package com.qxgmat.data.constants.enums.user;
 
 public enum ExportType {
-    QUESTION("question"),
+    QUESTION_COLLECT("question_collect"),
+    QUESTION_ERROR("question_error"),
     NOTE_QUESTION("note_question"),
     NOTE_COURSE("note_course")
     ;

+ 7 - 0
server/data/src/main/java/com/qxgmat/data/dao/UserCourseDataSubscribeMapper.java

@@ -0,0 +1,7 @@
+package com.qxgmat.data.dao;
+
+import com.nuliji.tools.mybatis.Mapper;
+import com.qxgmat.data.dao.entity.UserCourseDataSubscribe;
+
+public interface UserCourseDataSubscribeMapper extends Mapper<UserCourseDataSubscribe> {
+}

+ 7 - 0
server/data/src/main/java/com/qxgmat/data/dao/UserSearchHistoryMapper.java

@@ -0,0 +1,7 @@
+package com.qxgmat.data.dao;
+
+import com.nuliji.tools.mybatis.Mapper;
+import com.qxgmat.data.dao.entity.UserSearchHistory;
+
+public interface UserSearchHistoryMapper extends Mapper<UserSearchHistory> {
+}

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

@@ -129,6 +129,9 @@ public class Question implements Serializable {
     @Column(name = "`official_content`")
     private String officialContent;
 
+    @Column(name = "`chinese_content`")
+    private String chineseContent;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -541,6 +544,20 @@ public class Question implements Serializable {
         this.officialContent = officialContent;
     }
 
+    /**
+     * @return chinese_content
+     */
+    public String getChineseContent() {
+        return chineseContent;
+    }
+
+    /**
+     * @param chineseContent
+     */
+    public void setChineseContent(String chineseContent) {
+        this.chineseContent = chineseContent;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -572,6 +589,7 @@ public class Question implements Serializable {
         sb.append(", stem=").append(stem);
         sb.append(", qxContent=").append(qxContent);
         sb.append(", officialContent=").append(officialContent);
+        sb.append(", chineseContent=").append(chineseContent);
         sb.append("]");
         return sb.toString();
     }
@@ -817,6 +835,14 @@ public class Question implements Serializable {
             return this;
         }
 
+        /**
+         * @param chineseContent
+         */
+        public Builder chineseContent(String chineseContent) {
+            obj.setChineseContent(chineseContent);
+            return this;
+        }
+
         public Question build() {
             return this.obj;
         }

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

@@ -35,6 +35,12 @@ public class ReadyArticleCategory implements Serializable {
     @Column(name = "`is_official`")
     private Integer isOfficial;
 
+    /**
+     * 是否考场
+     */
+    @Column(name = "`is_room`")
+    private Integer isRoom;
+
     @Column(name = "`sort`")
     private Integer sort;
 
@@ -133,6 +139,24 @@ public class ReadyArticleCategory implements Serializable {
     }
 
     /**
+     * 获取是否考场
+     *
+     * @return is_room - 是否考场
+     */
+    public Integer getIsRoom() {
+        return isRoom;
+    }
+
+    /**
+     * 设置是否考场
+     *
+     * @param isRoom 是否考场
+     */
+    public void setIsRoom(Integer isRoom) {
+        this.isRoom = isRoom;
+    }
+
+    /**
      * @return sort
      */
     public Integer getSort() {
@@ -185,6 +209,7 @@ public class ReadyArticleCategory implements Serializable {
         sb.append(", parentId=").append(parentId);
         sb.append(", isData=").append(isData);
         sb.append(", isOfficial=").append(isOfficial);
+        sb.append(", isRoom=").append(isRoom);
         sb.append(", sort=").append(sort);
         sb.append(", createTime=").append(createTime);
         sb.append(", updateTime=").append(updateTime);
@@ -252,6 +277,16 @@ public class ReadyArticleCategory implements Serializable {
         }
 
         /**
+         * 设置是否考场
+         *
+         * @param isRoom 是否考场
+         */
+        public Builder isRoom(Integer isRoom) {
+            obj.setIsRoom(isRoom);
+            return this;
+        }
+
+        /**
          * @param sort
          */
         public Builder sort(Integer sort) {

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

@@ -73,9 +73,6 @@ public class SentenceQuestion implements Serializable {
     @Column(name = "`collect_number`")
     private Integer collectNumber;
 
-    @Column(name = "`chinese`")
-    private String chinese;
-
     private static final long serialVersionUID = 1L;
 
     /**
@@ -286,20 +283,6 @@ public class SentenceQuestion implements Serializable {
         this.collectNumber = collectNumber;
     }
 
-    /**
-     * @return chinese
-     */
-    public String getChinese() {
-        return chinese;
-    }
-
-    /**
-     * @param chinese
-     */
-    public void setChinese(String chinese) {
-        this.chinese = chinese;
-    }
-
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -318,7 +301,6 @@ public class SentenceQuestion implements Serializable {
         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();
     }
@@ -450,14 +432,6 @@ public class SentenceQuestion implements Serializable {
             return this;
         }
 
-        /**
-         * @param chinese
-         */
-        public Builder chinese(String chinese) {
-            obj.setChinese(chinese);
-            return this;
-        }
-
         public SentenceQuestion build() {
             return this.obj;
         }

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

@@ -277,6 +277,12 @@ public class User implements Serializable {
     private Integer exportTips;
 
     /**
+     * 机经提示:0展示
+     */
+    @Column(name = "`textbook_tips`")
+    private Integer textbookTips;
+
+    /**
      * 导出课时笔记次数
      */
     @Column(name = "`export_course_note_number`")
@@ -1107,6 +1113,24 @@ public class User implements Serializable {
     }
 
     /**
+     * 获取机经提示:0展示
+     *
+     * @return textbook_tips - 机经提示:0展示
+     */
+    public Integer getTextbookTips() {
+        return textbookTips;
+    }
+
+    /**
+     * 设置机经提示:0展示
+     *
+     * @param textbookTips 机经提示:0展示
+     */
+    public void setTextbookTips(Integer textbookTips) {
+        this.textbookTips = textbookTips;
+    }
+
+    /**
      * 获取导出课时笔记次数
      *
      * @return export_course_note_number - 导出课时笔记次数
@@ -1194,6 +1218,7 @@ public class User implements Serializable {
         sb.append(", exportQuestionErrorNumber=").append(exportQuestionErrorNumber);
         sb.append(", exportQuestionNoteNumber=").append(exportQuestionNoteNumber);
         sb.append(", exportTips=").append(exportTips);
+        sb.append(", textbookTips=").append(textbookTips);
         sb.append(", exportCourseNoteNumber=").append(exportCourseNoteNumber);
         sb.append(", isCourse=").append(isCourse);
         sb.append("]");
@@ -1666,6 +1691,16 @@ public class User implements Serializable {
         }
 
         /**
+         * 设置机经提示:0展示
+         *
+         * @param textbookTips 机经提示:0展示
+         */
+        public Builder textbookTips(Integer textbookTips) {
+            obj.setTextbookTips(textbookTips);
+            return this;
+        }
+
+        /**
          * 设置导出课时笔记次数
          *
          * @param exportCourseNoteNumber 导出课时笔记次数

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

@@ -78,6 +78,12 @@ public class UserAskCourse implements Serializable {
     private Date answerTime;
 
     /**
+     * 访问数
+     */
+    @Column(name = "`view_number`")
+    private Integer viewNumber;
+
+    /**
      * 排序:从大到小
      */
     @Column(name = "`sort`")
@@ -322,6 +328,24 @@ public class UserAskCourse implements Serializable {
     }
 
     /**
+     * 获取访问数
+     *
+     * @return view_number - 访问数
+     */
+    public Integer getViewNumber() {
+        return viewNumber;
+    }
+
+    /**
+     * 设置访问数
+     *
+     * @param viewNumber 访问数
+     */
+    public void setViewNumber(Integer viewNumber) {
+        this.viewNumber = viewNumber;
+    }
+
+    /**
      * 获取排序:从大到小
      *
      * @return sort - 排序:从大到小
@@ -439,6 +463,7 @@ public class UserAskCourse implements Serializable {
         sb.append(", managerId=").append(managerId);
         sb.append(", showStatus=").append(showStatus);
         sb.append(", answerTime=").append(answerTime);
+        sb.append(", viewNumber=").append(viewNumber);
         sb.append(", sort=").append(sort);
         sb.append(", createTime=").append(createTime);
         sb.append(", updateTime=").append(updateTime);
@@ -589,6 +614,16 @@ public class UserAskCourse implements Serializable {
         }
 
         /**
+         * 设置访问数
+         *
+         * @param viewNumber 访问数
+         */
+        public Builder viewNumber(Integer viewNumber) {
+            obj.setViewNumber(viewNumber);
+            return this;
+        }
+
+        /**
          * 设置排序:从大到小
          *
          * @param sort 排序:从大到小

+ 160 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/UserCourseDataSubscribe.java

@@ -0,0 +1,160 @@
+package com.qxgmat.data.dao.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+import javax.persistence.*;
+
+@Table(name = "user_course_data_subscribe")
+public class UserCourseDataSubscribe implements Serializable {
+    @Id
+    @Column(name = "`id`")
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Integer id;
+
+    /**
+     * 用户id
+     */
+    @Column(name = "`user_id`")
+    private Integer userId;
+
+    /**
+     * 资料id
+     */
+    @Column(name = "`data_id`")
+    private Integer dataId;
+
+    @Column(name = "`create_time`")
+    private Date createTime;
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * @return id
+     */
+    public Integer getId() {
+        return id;
+    }
+
+    /**
+     * @param id
+     */
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    /**
+     * 获取用户id
+     *
+     * @return user_id - 用户id
+     */
+    public Integer getUserId() {
+        return userId;
+    }
+
+    /**
+     * 设置用户id
+     *
+     * @param userId 用户id
+     */
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    /**
+     * 获取资料id
+     *
+     * @return data_id - 资料id
+     */
+    public Integer getDataId() {
+        return dataId;
+    }
+
+    /**
+     * 设置资料id
+     *
+     * @param dataId 资料id
+     */
+    public void setDataId(Integer dataId) {
+        this.dataId = dataId;
+    }
+
+    /**
+     * @return create_time
+     */
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    /**
+     * @param createTime
+     */
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", userId=").append(userId);
+        sb.append(", dataId=").append(dataId);
+        sb.append(", createTime=").append(createTime);
+        sb.append("]");
+        return sb.toString();
+    }
+
+    public static UserCourseDataSubscribe.Builder builder() {
+        return new UserCourseDataSubscribe.Builder();
+    }
+
+    public static class Builder {
+        private UserCourseDataSubscribe obj;
+
+        public Builder() {
+            this.obj = new UserCourseDataSubscribe();
+        }
+
+        /**
+         * @param id
+         */
+        public Builder id(Integer id) {
+            obj.setId(id);
+            return this;
+        }
+
+        /**
+         * 设置用户id
+         *
+         * @param userId 用户id
+         */
+        public Builder userId(Integer userId) {
+            obj.setUserId(userId);
+            return this;
+        }
+
+        /**
+         * 设置资料id
+         *
+         * @param dataId 资料id
+         */
+        public Builder dataId(Integer dataId) {
+            obj.setDataId(dataId);
+            return this;
+        }
+
+        /**
+         * @param createTime
+         */
+        public Builder createTime(Date createTime) {
+            obj.setCreateTime(createTime);
+            return this;
+        }
+
+        public UserCourseDataSubscribe build() {
+            return this.obj;
+        }
+    }
+}

+ 195 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/UserSearchHistory.java

@@ -0,0 +1,195 @@
+package com.qxgmat.data.dao.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+import javax.persistence.*;
+
+@Table(name = "user_search_history")
+public class UserSearchHistory implements Serializable {
+    @Id
+    @Column(name = "`id`")
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Integer id;
+
+    /**
+     * 用户id
+     */
+    @Column(name = "`user_id`")
+    private Integer userId;
+
+    /**
+     * 题目id
+     */
+    @Column(name = "`question_id`")
+    private Integer questionId;
+
+    /**
+     * 题目编号id
+     */
+    @Column(name = "`question_no_id`")
+    private Integer questionNoId;
+
+    @Column(name = "`create_time`")
+    private Date createTime;
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * @return id
+     */
+    public Integer getId() {
+        return id;
+    }
+
+    /**
+     * @param id
+     */
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    /**
+     * 获取用户id
+     *
+     * @return user_id - 用户id
+     */
+    public Integer getUserId() {
+        return userId;
+    }
+
+    /**
+     * 设置用户id
+     *
+     * @param userId 用户id
+     */
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    /**
+     * 获取题目id
+     *
+     * @return question_id - 题目id
+     */
+    public Integer getQuestionId() {
+        return questionId;
+    }
+
+    /**
+     * 设置题目id
+     *
+     * @param questionId 题目id
+     */
+    public void setQuestionId(Integer questionId) {
+        this.questionId = questionId;
+    }
+
+    /**
+     * 获取题目编号id
+     *
+     * @return question_no_id - 题目编号id
+     */
+    public Integer getQuestionNoId() {
+        return questionNoId;
+    }
+
+    /**
+     * 设置题目编号id
+     *
+     * @param questionNoId 题目编号id
+     */
+    public void setQuestionNoId(Integer questionNoId) {
+        this.questionNoId = questionNoId;
+    }
+
+    /**
+     * @return create_time
+     */
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    /**
+     * @param createTime
+     */
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", userId=").append(userId);
+        sb.append(", questionId=").append(questionId);
+        sb.append(", questionNoId=").append(questionNoId);
+        sb.append(", createTime=").append(createTime);
+        sb.append("]");
+        return sb.toString();
+    }
+
+    public static UserSearchHistory.Builder builder() {
+        return new UserSearchHistory.Builder();
+    }
+
+    public static class Builder {
+        private UserSearchHistory obj;
+
+        public Builder() {
+            this.obj = new UserSearchHistory();
+        }
+
+        /**
+         * @param id
+         */
+        public Builder id(Integer id) {
+            obj.setId(id);
+            return this;
+        }
+
+        /**
+         * 设置用户id
+         *
+         * @param userId 用户id
+         */
+        public Builder userId(Integer userId) {
+            obj.setUserId(userId);
+            return this;
+        }
+
+        /**
+         * 设置题目id
+         *
+         * @param questionId 题目id
+         */
+        public Builder questionId(Integer questionId) {
+            obj.setQuestionId(questionId);
+            return this;
+        }
+
+        /**
+         * 设置题目编号id
+         *
+         * @param questionNoId 题目编号id
+         */
+        public Builder questionNoId(Integer questionNoId) {
+            obj.setQuestionNoId(questionNoId);
+            return this;
+        }
+
+        /**
+         * @param createTime
+         */
+        public Builder createTime(Date createTime) {
+            obj.setCreateTime(createTime);
+            return this;
+        }
+
+        public UserSearchHistory build() {
+            return this.obj;
+        }
+    }
+}

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

@@ -35,6 +35,7 @@
     <result column="stem" jdbcType="LONGVARCHAR" property="stem" />
     <result column="qx_content" jdbcType="LONGVARCHAR" property="qxContent" />
     <result column="official_content" jdbcType="LONGVARCHAR" property="officialContent" />
+    <result column="chinese_content" jdbcType="LONGVARCHAR" property="chineseContent" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
@@ -49,6 +50,6 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `stem`, `qx_content`, `official_content`
+    `stem`, `qx_content`, `official_content`, `chinese_content`
   </sql>
 </mapper>

+ 3 - 1
server/data/src/main/java/com/qxgmat/data/dao/mapping/ReadyArticleCategoryMapper.xml

@@ -10,6 +10,7 @@
     <result column="parent_id" jdbcType="INTEGER" property="parentId" />
     <result column="is_data" jdbcType="INTEGER" property="isData" />
     <result column="is_official" jdbcType="INTEGER" property="isOfficial" />
+    <result column="is_room" jdbcType="INTEGER" property="isRoom" />
     <result column="sort" jdbcType="INTEGER" property="sort" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
     <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
@@ -18,6 +19,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `title`, `parent_id`, `is_data`, `is_official`, `sort`, `create_time`, `update_time`
+    `id`, `title`, `parent_id`, `is_data`, `is_official`, `is_room`, `sort`, `create_time`, 
+    `update_time`
   </sql>
 </mapper>

+ 0 - 12
server/data/src/main/java/com/qxgmat/data/dao/mapping/SentenceQuestionMapper.xml

@@ -18,12 +18,6 @@
     <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">
-    <!--
-      WARNING - @mbg.generated
-    -->
-    <result column="chinese" jdbcType="LONGVARCHAR" property="chinese" />
-  </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
@@ -31,10 +25,4 @@
     `id`, `title`, `is_trail`, `is_paper`, `no`, `subject`, `question_id`, `question_no_id`, 
     `total_time`, `total_number`, `total_correct`, `collect_number`
   </sql>
-  <sql id="Blob_Column_List">
-    <!--
-      WARNING - @mbg.generated
-    -->
-    `chinese`
-  </sql>
 </mapper>

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

@@ -17,6 +17,7 @@
     <result column="manager_id" jdbcType="INTEGER" property="managerId" />
     <result column="show_status" jdbcType="INTEGER" property="showStatus" />
     <result column="answer_time" jdbcType="TIMESTAMP" property="answerTime" />
+    <result column="view_number" jdbcType="INTEGER" property="viewNumber" />
     <result column="sort" jdbcType="INTEGER" property="sort" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
     <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
@@ -34,8 +35,8 @@
       WARNING - @mbg.generated
     -->
     `id`, `user_id`, `course_id`, `course_no_id`, `record_id`, `position`, `ask_time`, 
-    `expire_time`, `answer_status`, `manager_id`, `show_status`, `answer_time`, `sort`, 
-    `create_time`, `update_time`
+    `expire_time`, `answer_status`, `manager_id`, `show_status`, `answer_time`, `view_number`, 
+    `sort`, `create_time`, `update_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 19 - 0
server/data/src/main/java/com/qxgmat/data/dao/mapping/UserCourseDataSubscribeMapper.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qxgmat.data.dao.UserCourseDataSubscribeMapper">
+  <resultMap id="BaseResultMap" type="com.qxgmat.data.dao.entity.UserCourseDataSubscribe">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="id" jdbcType="INTEGER" property="id" />
+    <result column="user_id" jdbcType="INTEGER" property="userId" />
+    <result column="data_id" jdbcType="INTEGER" property="dataId" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+  </resultMap>
+  <sql id="Base_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    `id`, `user_id`, `data_id`, `create_time`
+  </sql>
+</mapper>

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

@@ -51,6 +51,7 @@
     <result column="export_question_error_number" jdbcType="INTEGER" property="exportQuestionErrorNumber" />
     <result column="export_question_note_number" jdbcType="INTEGER" property="exportQuestionNoteNumber" />
     <result column="export_tips" jdbcType="INTEGER" property="exportTips" />
+    <result column="textbook_tips" jdbcType="INTEGER" property="textbookTips" />
     <result column="export_course_note_number" jdbcType="INTEGER" property="exportCourseNoteNumber" />
     <result column="is_course" jdbcType="INTEGER" property="isCourse" />
   </resultMap>
@@ -66,7 +67,7 @@
     `latest_error`, `latest_collect`, `origin_id`, `invite_code`, `total_money`, `invite_number`, 
     `textbook_half`, `qx_cat`, `register_ip`, `register_city`, `latest_login_ip`, `latest_login_time`, 
     `is_frozen`, `create_time`, `data_email_subscribe`, `textbook_email_subscribe`, `total_alert`, 
-    `export_question_error_number`, `export_question_note_number`, `export_tips`, `export_course_note_number`, 
-    `is_course`
+    `export_question_error_number`, `export_question_note_number`, `export_tips`, `textbook_tips`, 
+    `export_course_note_number`, `is_course`
   </sql>
 </mapper>

+ 20 - 0
server/data/src/main/java/com/qxgmat/data/dao/mapping/UserSearchHistoryMapper.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qxgmat.data.dao.UserSearchHistoryMapper">
+  <resultMap id="BaseResultMap" type="com.qxgmat.data.dao.entity.UserSearchHistory">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="id" jdbcType="INTEGER" property="id" />
+    <result column="user_id" jdbcType="INTEGER" property="userId" />
+    <result column="question_id" jdbcType="INTEGER" property="questionId" />
+    <result column="question_no_id" jdbcType="INTEGER" property="questionNoId" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+  </resultMap>
+  <sql id="Base_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    `id`, `user_id`, `question_id`, `question_no_id`, `create_time`
+  </sql>
+</mapper>

+ 2 - 2
server/data/src/main/java/com/qxgmat/data/relation/CourseDataRelationMapper.java

@@ -21,7 +21,7 @@ public interface CourseDataRelationMapper {
 
     void accumulation(
             @Param("id") Number dataId,
-            @Param("view") int view,
-            @Param("sale") int sale
+            @Param("sale") int sale,
+            @Param("view") int view
     );
 }

+ 5 - 0
server/data/src/main/java/com/qxgmat/data/relation/UserAskCourseRelationMapper.java

@@ -23,4 +23,9 @@ public interface UserAskCourseRelationMapper {
             String order,
             String direction
     );
+
+    void accumulation(
+            @Param("id") Number id,
+            @Param("view") int view
+    );
 }

+ 2 - 2
server/data/src/main/java/com/qxgmat/data/relation/mapping/CourseDataRelationMapper.xml

@@ -32,12 +32,12 @@
     select
     <include refid="Id_Column_List" />
     from `course_data` cd
-    left join `user_order_record` uor on `uor`.`product_type`='data' and `uor`.`product_id` = cd.`id`
+    left join `user_course_data_subscribe` ucds on `ucds`.`data_id`= cd.`id`
     <if test="userId != null">
       and `uor`.user_id = #{userId,jdbcType=VARCHAR}
     </if>
     where
-    `uor`.`id` > 0
+    `ucds`.`id` > 0
     <if test="structId != null">
       and (cd.`struct_id` = #{structId,jdbcType=VARCHAR} or cd.`parent_struct_id` = #{structId,jdbcType=VARCHAR})
     </if>

+ 9 - 0
server/data/src/main/java/com/qxgmat/data/relation/mapping/UserAskCourseRelationMapper.xml

@@ -14,6 +14,15 @@
     uac.`id`
   </sql>
 
+  <!--累加记录-->
+  <update id="accumulation">
+    UPDATE `user_ask_course`
+    <trim prefix="set" suffixOverrides=",">
+      `view_number`=`view_number`+#{view, jdbcType=INTEGER},
+    </trim>
+    WHERE `id` = #{id, jdbcType=VARCHAR}
+  </update>
+
   <!--修改问答排序-->
   <update id="adjust">
     UPDATE `user_ask_course`

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

@@ -482,6 +482,7 @@ CREATE TABLE question (
   qx_content text,
   official_content text,
   association_content text COMMENT '题源联想:json',
+  chinese_content text,
   total_time int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总作答时间',
   total_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总作答次数',
   total_correct int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总正确次数',
@@ -548,6 +549,7 @@ CREATE TABLE ready_article_category (
   parent_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '父级id',
   is_data tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否资料节点',
   is_official tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否官方资料',
+  is_room tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否考场节点',
   sort int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序:从大到小',
   create_time datetime DEFAULT NULL,
   update_time datetime DEFAULT NULL,
@@ -688,7 +690,6 @@ CREATE TABLE sentence_question (
   subject varchar(255) NOT NULL COMMENT '根据序号生成的固定标题',
   question_id int(11) unsigned NOT NULL COMMENT '题目id',
   question_no_id int(11) unsigned NOT NULL COMMENT '题目编号id',
-  chinese text,
   total_time int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总作答时间',
   total_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总做题次数',
   total_correct int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总正确次数',
@@ -946,6 +947,7 @@ CREATE TABLE user (
   export_question_error_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '导出错题次数',
   export_question_note_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '导出题目笔记次数',
   export_tips tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '导出提示:0展示,1关闭',
+  textbook_tips tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '机经提示:0展示,1关闭',
   export_course_note_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '导出课时笔记次数',
   is_course tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否购买课程',
   PRIMARY KEY (id),
@@ -1114,6 +1116,16 @@ CREATE TABLE user_course_record (
   KEY user_id (user_id,course_id,record_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户-课程-访问记录';
 
+CREATE TABLE user_data_subscribe (
+  id int(11) unsigned NOT NULL AUTO_INCREMENT,
+  user_id int(11) unsigned NOT NULL COMMENT '用户id',
+  data_id int(11) unsigned NOT NULL COMMENT '资料id',
+  create_time datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY user_id (user_id),
+  KEY data_id (data_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户-资料-订阅';
+
 CREATE TABLE user_export (
   id int(11) unsigned NOT NULL AUTO_INCREMENT,
   user_id int(11) unsigned NOT NULL DEFAULT '0',
@@ -1383,6 +1395,16 @@ CREATE TABLE user_report (
   KEY paper_origin (paper_origin,origin_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户-做题报告';
 
+CREATE TABLE user_search_history (
+  id int(11) unsigned NOT NULL AUTO_INCREMENT,
+  user_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id',
+  question_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '题目id',
+  question_no_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '题目编号id',
+  create_time datetime DEFAULT NULL,
+  PRIMARY KEY (`id),
+  KEY user_id (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户-搜索题目-记录';
+
 CREATE TABLE user_sentence_progress (
   id int(11) unsigned NOT NULL AUTO_INCREMENT,
   user_id int(11) unsigned NOT NULL COMMENT '用户id',

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

@@ -109,9 +109,9 @@ public class AuthController {
     @RequestMapping(value = "/login", method = RequestMethod.POST)
     @ApiOperation(value = "登录/注册", httpMethod = "POST")
     public Response<MyDto> login(@RequestBody @Validated UserLoginDto userLoginDto, HttpSession session, HttpServletRequest request) {
-//        if (!smsHelp.verifyCode(userLoginDto.getArea(), userLoginDto.getMobile(), userLoginDto.getMobileVerifyCode(), session)) {
-//            throw new ParameterException("手机验证码错误!");
-//        }
+        if (!smsHelp.verifyCode(userLoginDto.getArea(), userLoginDto.getMobile(), userLoginDto.getMobileVerifyCode(), session)) {
+            throw new ParameterException("手机验证码错误!");
+        }
         try {
             String ip = Tools.getClientIp(request);
             usersService.register(userLoginDto.getArea(), userLoginDto.getMobile(), userLoginDto.getInviteCode(), userLoginDto.getEmail(), null, ip, aiHelp.parseIp(ip));

+ 12 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/api/BaseController.java

@@ -74,6 +74,9 @@ public class BaseController {
     private ReadyArticleCategoryService readyArticleCategoryService;
 
     @Autowired
+    private ReadyArticleService readyArticleService;
+
+    @Autowired
     private ReadyRoomAreaService readyRoomAreaService;
 
     @Autowired
@@ -317,6 +320,15 @@ public class BaseController {
         return ResponseHelp.success(dto);
     }
 
+    @RequestMapping(value = "/article/list", method = RequestMethod.GET)
+    @ApiOperation(value = "获取文章信息", notes = "获取文章信息", httpMethod = "GET")
+    public Response<List<ReadyArticle>> listArticle(
+            @RequestParam(required = true) Integer categoryId
+    )  {
+        List<ReadyArticle> list = readyArticleService.list(categoryId);
+        return ResponseHelp.success(list);
+    }
+
     @RequestMapping(value = "/read/list", method = RequestMethod.GET)
     @ApiOperation(value = "推荐阅读", httpMethod = "GET")
     public Response<PageMessage<ReadyRead>> listRead(

+ 17 - 4
server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java

@@ -14,10 +14,7 @@ import com.qxgmat.data.constants.enums.user.DataType;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.relation.entity.UserPreviewPaperRelation;
 import com.qxgmat.dto.extend.*;
-import com.qxgmat.dto.request.CourseDataViewDto;
-import com.qxgmat.dto.request.CourseTrailViewDto;
-import com.qxgmat.dto.request.ExperienceViewDto;
-import com.qxgmat.dto.request.UserCourseNoProgressDto;
+import com.qxgmat.dto.request.*;
 import com.qxgmat.dto.response.*;
 import com.qxgmat.help.ShiroHelp;
 import com.qxgmat.service.UsersService;
@@ -100,6 +97,9 @@ public class CourseController {
     private UserOrderRecordService userOrderRecordService;
 
     @Autowired
+    private UserCourseDataSubscribeService userCourseDataSubscribeService;
+
+    @Autowired
     private UsersService usersService;
 
 
@@ -178,6 +178,13 @@ public class CourseController {
         return ResponseHelp.success(pr, page, size, pr.getTotal());
     }
 
+    @RequestMapping(value = "/ask/view", method = RequestMethod.POST)
+    @ApiOperation(value = "精选问答查看", httpMethod = "POST")
+    public Response<Boolean> viewAsk(@RequestBody @Validated AskCourseViewDto dto, HttpSession session) {
+        userAskCourseService.accumulation(dto.getId(), 1);
+        return ResponseHelp.success(true);
+    }
+
     @RequestMapping(value = "/simple", method = RequestMethod.GET)
     @ApiOperation(value = "课程基本信息", httpMethod = "GET")
     public Response<Course> simple(
@@ -322,6 +329,12 @@ public class CourseController {
         List<Faq> faqList = faqService.list(1, 10, "course_data", courseData.getId().toString());
         dto.setFaqs(Transform.convert(faqList, FaqExtendDto.class));
 
+        // 订阅
+        if (user != null){
+            UserCourseDataSubscribe subscribe = userCourseDataSubscribeService.getByData(user.getId(), dataId);
+            dto.setSubscribe(subscribe != null);
+        }
+
         return ResponseHelp.success(dto);
     }
 

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

@@ -150,6 +150,9 @@ public class MyController {
     private UserCourseProgressService userCourseProgressService;
 
     @Autowired
+    private UserCourseDataSubscribeService userCourseDataSubscribeService;
+
+    @Autowired
     private UserSentenceRecordService userSentenceRecordService;
 
     @Autowired
@@ -218,6 +221,9 @@ public class MyController {
     @Autowired
     private ExportService exportService;
 
+    @Autowired
+    private UserSearchHistoryService userSearchHistoryService;
+
     @RequestMapping(value = "/email", method = RequestMethod.POST)
     @ApiOperation(value = "绑定邮箱", httpMethod = "POST")
     public Response<Boolean> email(@RequestBody @Validated UserEmailDto dto, HttpSession session, HttpServletRequest request) {
@@ -1531,10 +1537,9 @@ public class MyController {
         return ResponseHelp.success(true);
     }
 
-
-    @RequestMapping(value = "/data/subscribe", method = RequestMethod.POST)
+    @RequestMapping(value = "/data/email/subscribe", method = RequestMethod.POST)
     @ApiOperation(value = "资料订阅", notes = "资料订阅", httpMethod = "POST")
-    public Response<Boolean> addComment(@RequestBody @Validated DataSubscribeDto dto)  {
+    public Response<Boolean> subscribeDataEmail(@RequestBody @Validated DataEmailSubscribeDto dto)  {
         User user = (User) shiroHelp.getLoginUser();
         if (user == null){
             throw new AuthException("请先登录");
@@ -1546,6 +1551,28 @@ public class MyController {
         return ResponseHelp.success(true);
     }
 
+    @RequestMapping(value = "/data/subscribe", method = RequestMethod.GET)
+    @ApiOperation(value = "资料订阅", httpMethod = "GET")
+    public Response<Boolean> subscribeData(@RequestBody @Validated DataSubscribeDto dto) {
+        User user = (User) shiroHelp.getLoginUser();
+
+        UserCourseDataSubscribe subscribe = userCourseDataSubscribeService.getByData(user.getId(), dto.getDataId());
+        if (dto.getSubscribe()){
+            if (subscribe == null){
+                userCourseDataSubscribeService.add(UserCourseDataSubscribe.builder()
+                        .userId(user.getId())
+                        .dataId(dto.getDataId())
+                        .build());
+            }
+        }else{
+            if (subscribe!=null){
+                userCourseDataSubscribeService.delete(subscribe.getId());
+            }
+        }
+
+        return ResponseHelp.success(true);
+    }
+
     @RequestMapping(value = "/data/history", method = RequestMethod.GET)
     @ApiOperation(value = "资料更新记录", httpMethod = "GET")
     public Response<PageMessage<CourseDataHistoryInfoDto>> listDataHistory(
@@ -1566,7 +1593,7 @@ public class MyController {
     }
 
     @RequestMapping(value = "/data/list", method = RequestMethod.GET)
-    @ApiOperation(value = "购买的资料记录", httpMethod = "GET")
+    @ApiOperation(value = "订阅资料记录", httpMethod = "GET")
     public Response<PageMessage<CourseData>> listData(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
@@ -1579,6 +1606,8 @@ public class MyController {
 
         Page<CourseData> p = courseDataService.listByUser(page, size, user.getId(), structId, DataType.ValueOf(dataType),order, DirectionStatus.ValueOf(direction));
 
+        courseExtendService.refreshDataResource(user, p);
+
         return ResponseHelp.success(p, page, size, p.getTotal());
     }
 
@@ -1937,11 +1966,19 @@ public class MyController {
         return ResponseHelp.success(dtos);
     }
 
-    @RequestMapping(value = "/export/question", method = RequestMethod.POST)
+    @RequestMapping(value = "/export/question/collect", method = RequestMethod.POST)
+    @ApiOperation(value = "导出题目", notes = "导出题目", httpMethod = "POST")
+    public Response<Integer> exportQuestionCollect(@RequestBody @Validated UserExportDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+        UserExport entity = exportService.addQuestionCollect(user.getId(), JSONObject.parseObject(JSONObject.toJSONString(dto.getSetting())));
+        return ResponseHelp.success(entity.getId());
+    }
+
+    @RequestMapping(value = "/export/question/error", method = RequestMethod.POST)
     @ApiOperation(value = "导出题目", notes = "导出题目", httpMethod = "POST")
-    public Response<Integer> exportQuestion(@RequestBody @Validated UserExportDto dto)  {
+    public Response<Integer> exportQuestionError(@RequestBody @Validated UserExportDto dto)  {
         User user = (User) shiroHelp.getLoginUser();
-        UserExport entity = exportService.addQuestion(user.getId(), JSONObject.parseObject(JSONObject.toJSONString(dto.getSetting())));
+        UserExport entity = exportService.addQuestionError(user.getId(), JSONObject.parseObject(JSONObject.toJSONString(dto.getSetting())));
         return ResponseHelp.success(entity.getId());
     }
 
@@ -1981,6 +2018,19 @@ public class MyController {
         return ResponseHelp.success(true);
     }
 
+    @RequestMapping(value = "/textbook/tips", method = RequestMethod.POST)
+    @ApiOperation(value = "关闭提示", notes = "关闭提示", httpMethod = "POST")
+    public Response<Boolean> textbookTips()  {
+        User user = (User) shiroHelp.getLoginUser();
+
+        User in = usersService.get(user.getId());
+        usersService.edit(User.builder()
+                .id(user.getId())
+                .textbookTips(1)
+                .build());
+        return ResponseHelp.success(true);
+    }
+
     @RequestMapping(value = "/course/comment/tips", method = RequestMethod.POST)
     @ApiOperation(value = "关闭评论提示提示", notes = "关闭评论提示提示", httpMethod = "POST")
     public Response<Boolean> closeCommentTips(@RequestBody @Validated RecordCommentTipsDto dto)  {
@@ -1992,4 +2042,44 @@ public class MyController {
                 .build());
         return ResponseHelp.success(true);
     }
+
+    @RequestMapping(value = "/search/history", method = RequestMethod.POST)
+    @ApiOperation(value = "添加搜索记录", notes = "添加搜索记录", httpMethod = "POST")
+    public Response<Boolean> addSearchHistory(@RequestBody @Validated SearchHistoryDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+        QuestionNo questionNo = questionNoService.get(dto.getQuestionNoId());
+        userSearchHistoryService.add(UserSearchHistory.builder()
+                .userId(user.getId())
+                .questionNoId(questionNo.getId())
+                .questionId(questionNo.getQuestionId())
+                .build());
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/search/history/list", method = RequestMethod.GET)
+    @ApiOperation(value = "搜索历史记录", httpMethod = "GET")
+    public Response<List<UserSearchHistoryDto>> listSearchHistory(
+            HttpSession session) {
+        User user = (User) shiroHelp.getLoginUser();
+
+        int week = 0;
+
+        Date now = Tools.today();
+        int day = Tools.getDayOfWeek(now);
+        Date start = Tools.addDate(now, -1 * (day + week * 7));
+        Date end = Tools.addDate(start, 7);
+
+        List<UserSearchHistory> p = userSearchHistoryService.listByUser(user.getId(), start.toString(), end.toString());
+        List<UserSearchHistoryDto> pr = Transform.convert(p, UserSearchHistoryDto.class);
+
+        Collection questionIds = Transform.getIds(p, UserSearchHistory.class, "questionId");
+        List<Question> questionList = questionService.select(questionIds);
+        Transform.combine(pr, questionList, UserSearchHistoryDto.class, "questionId", "question", QuestionNo.class, "id", QuestionExtendDto.class);
+
+        Collection questionNoIds = Transform.getIds(p, UserSearchHistory.class, "questionNoId");
+        List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
+        Transform.combine(pr, questionNoList, UserSearchHistoryDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);
+
+        return ResponseHelp.success(pr);
+    }
 }

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

@@ -134,6 +134,8 @@ public class QuestionController {
             @RequestParam(required = false, defaultValue = "id") String order, // collect_number, correct, time, relation_score
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
+        User user = (User) shiroHelp.getLoginUser();
+
         // 过滤千行cat的数据
         List<ExaminationStruct> structs = examinationStructService.main();
         ExaminationStruct qxCatStruct = structs.stream().filter((row)-> row.getExtend().equals(ServiceKey.QX_CAT.key)).findFirst().get();
@@ -141,6 +143,21 @@ public class QuestionController {
         Page<QuestionNoRelation> p = questionNoService.searchStemFulltext(page, size, keyword, questionTypes, module, structIds, place, difficult, qxCatStruct.getId(), order, DirectionStatus.ValueOf(direction));
         List<QuestionNoDetailDto> pr = Transform.convert(p, QuestionNoDetailDto.class);
 
+        if(user!= null){
+            Collection questionIds = Transform.getIds(p, QuestionNoRelation.class, "questionId");
+
+            List<UserCollectQuestion> userCollectQuestionList = userCollectQuestionService.listByUserAndQuestions(user.getId(), questionIds);
+            Map collectMap = Transform.getMap(userCollectQuestionList, UserCollectQuestion.class, "questionId", "id");
+
+            List<UserNoteQuestion> userNoteQuestionList = userNoteQuestionService.listByUserAndQuestions(user.getId(), questionIds);
+            Map noteMap = Transform.getMap(userNoteQuestionList, UserNoteQuestion.class, "questionId", "id");
+
+            for(QuestionNoDetailDto dto : pr){
+                dto.setCollect(collectMap.containsKey(dto.getQuestionId()));
+                dto.setNote(noteMap.containsKey(dto.getQuestionId()));
+            }
+        }
+
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 

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

@@ -33,6 +33,8 @@ public class QuestionDto {
 
     private Integer[] associationContent;
 
+    private String chineseContent;
+
     private Object content;
 
     private Object answer;
@@ -158,4 +160,12 @@ public class QuestionDto {
     public void setQuestionType(String questionType) {
         this.questionType = questionType;
     }
+
+    public String getChineseContent() {
+        return chineseContent;
+    }
+
+    public void setChineseContent(String chineseContent) {
+        this.chineseContent = chineseContent;
+    }
 }

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

@@ -18,8 +18,6 @@ public class SentenceQuestionDto {
 
     private Integer questionId;
 
-    private String chinese;
-
     private QuestionDto question;
 
     public Integer getId() {
@@ -70,14 +68,6 @@ public class SentenceQuestionDto {
         this.questionId = questionId;
     }
 
-    public String getChinese() {
-        return chinese;
-    }
-
-    public void setChinese(String chinese) {
-        this.chinese = chinese;
-    }
-
     public QuestionDto getQuestion() {
         return question;
     }

+ 10 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/extend/QuestionDetailExtendDto.java

@@ -25,6 +25,8 @@ public class QuestionDetailExtendDto {
 
     private Integer[] associationContent;
 
+    private String chineseContent;
+
     private JSONObject answerDistributed;
 
     public Integer getId() {
@@ -114,4 +116,12 @@ public class QuestionDetailExtendDto {
     public void setAnswerDistributed(JSONObject answerDistributed) {
         this.answerDistributed = answerDistributed;
     }
+
+    public String getChineseContent() {
+        return chineseContent;
+    }
+
+    public void setChineseContent(String chineseContent) {
+        this.chineseContent = chineseContent;
+    }
 }

+ 0 - 10
server/gateway-api/src/main/java/com/qxgmat/dto/extend/SentenceQuestionDetailExtendDto.java

@@ -9,8 +9,6 @@ public class SentenceQuestionDetailExtendDto {
 
     private Integer no;
 
-    private String chinese;
-
     public String getTitle() {
         return title;
     }
@@ -26,12 +24,4 @@ public class SentenceQuestionDetailExtendDto {
     public void setNo(Integer no) {
         this.no = no;
     }
-
-    public String getChinese() {
-        return chinese;
-    }
-
-    public void setChinese(String chinese) {
-        this.chinese = chinese;
-    }
 }

+ 13 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/request/AskCourseViewDto.java

@@ -0,0 +1,13 @@
+package com.qxgmat.dto.request;
+
+public class AskCourseViewDto {
+    private Integer id;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+}

+ 13 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/request/DataEmailSubscribeDto.java

@@ -0,0 +1,13 @@
+package com.qxgmat.dto.request;
+
+public class DataEmailSubscribeDto {
+    private Boolean subscribe;
+
+    public Boolean getSubscribe() {
+        return subscribe;
+    }
+
+    public void setSubscribe(Boolean subscribe) {
+        this.subscribe = subscribe;
+    }
+}

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

@@ -1,6 +1,8 @@
 package com.qxgmat.dto.request;
 
 public class DataSubscribeDto {
+    private Integer dataId;
+
     private Boolean subscribe;
 
     public Boolean getSubscribe() {
@@ -10,4 +12,12 @@ public class DataSubscribeDto {
     public void setSubscribe(Boolean subscribe) {
         this.subscribe = subscribe;
     }
+
+    public Integer getDataId() {
+        return dataId;
+    }
+
+    public void setDataId(Integer dataId) {
+        this.dataId = dataId;
+    }
 }

+ 61 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/request/QuestionNoDetailDto.java

@@ -2,11 +2,24 @@ package com.qxgmat.dto.request;
 
 import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.QuestionNo;
+import com.qxgmat.dto.extend.QuestionExtendDto;
 
 @Dto(entity = QuestionNo.class)
 public class QuestionNoDetailDto {
+    private Integer id;
+
+    private String title;
+
+    private Integer no;
+
+    private Integer questionId;
+
+    private QuestionExtendDto question;
+
     private Boolean collect;
 
+    private Boolean note;
+
     public Boolean getCollect() {
         return collect;
     }
@@ -14,4 +27,52 @@ public class QuestionNoDetailDto {
     public void setCollect(Boolean collect) {
         this.collect = collect;
     }
+
+    public QuestionExtendDto getQuestion() {
+        return question;
+    }
+
+    public void setQuestion(QuestionExtendDto question) {
+        this.question = question;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public Integer getNo() {
+        return no;
+    }
+
+    public void setNo(Integer no) {
+        this.no = no;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Boolean getNote() {
+        return note;
+    }
+
+    public void setNote(Boolean note) {
+        this.note = note;
+    }
+
+    public Integer getQuestionId() {
+        return questionId;
+    }
+
+    public void setQuestionId(Integer questionId) {
+        this.questionId = questionId;
+    }
 }

+ 13 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/request/SearchHistoryDto.java

@@ -0,0 +1,13 @@
+package com.qxgmat.dto.request;
+
+public class SearchHistoryDto {
+    private Integer questionNoId;
+
+    public Integer getQuestionNoId() {
+        return questionNoId;
+    }
+
+    public void setQuestionNoId(Integer questionNoId) {
+        this.questionNoId = questionNoId;
+    }
+}

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

@@ -15,6 +15,8 @@ public class CourseDataDetailDto extends CourseData {
 
     private Collection<FaqExtendDto> faqs;
 
+    private Boolean subscribe;
+
     public Collection<CommentExtendDto> getComments() {
         return comments;
     }
@@ -30,4 +32,12 @@ public class CourseDataDetailDto extends CourseData {
     public void setFaqs(Collection<FaqExtendDto> faqs) {
         this.faqs = faqs;
     }
+
+    public Boolean getSubscribe() {
+        return subscribe;
+    }
+
+    public void setSubscribe(Boolean subscribe) {
+        this.subscribe = subscribe;
+    }
 }

+ 29 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserSearchHistoryDto.java

@@ -0,0 +1,29 @@
+package com.qxgmat.dto.response;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserSearchHistory;
+import com.qxgmat.dto.extend.QuestionExtendDto;
+import com.qxgmat.dto.extend.QuestionNoExtendDto;
+
+@Dto(entity = UserSearchHistory.class)
+public class UserSearchHistoryDto extends UserSearchHistory {
+    private QuestionExtendDto question;
+
+    private QuestionNoExtendDto questionNo;
+
+    public QuestionExtendDto getQuestion() {
+        return question;
+    }
+
+    public void setQuestion(QuestionExtendDto question) {
+        this.question = question;
+    }
+
+    public QuestionNoExtendDto getQuestionNo() {
+        return questionNo;
+    }
+
+    public void setQuestionNo(QuestionNoExtendDto questionNo) {
+        this.questionNo = questionNo;
+    }
+}

+ 4 - 1
server/gateway-api/src/main/java/com/qxgmat/service/extend/CourseExtendService.java

@@ -309,8 +309,11 @@ public class CourseExtendService {
     public void refreshDataResource(User user, List<CourseData> courseDataList){
         // 处理权限
         if (user != null){
+            Collection ids = Transform.getIds(courseDataList, CourseData.class, "id");
+            List<UserOrderRecord> records = userOrderRecordService.listWithUserData(user.getId(), ids);
+            Map map = Transform.getMap(records, UserOrderRecord.class, "productId");
             for(CourseData courseData : courseDataList){
-                if (!userOrderRecordService.hasData(user.getId(), courseData.getId())){
+                if (!map.containsKey(courseData.getId())){
                     courseData.setResource(courseData.getTrailResource());
                 }
             }

+ 19 - 2
server/gateway-api/src/main/java/com/qxgmat/service/extend/ExportService.java

@@ -29,29 +29,46 @@ public class ExportService {
     @Resource
     private UserExportService userExportService;
 
-    public UserExport addQuestion(Integer userId, JSONObject setting){
+    public UserExport addQuestionCollect(Integer userId, JSONObject setting){
+        int count = userExportService.countByUser(userId, ExportType.QUESTION_COLLECT);
         UserExport export = UserExport.builder()
                 .userId(userId)
-                .type(ExportType.QUESTION.key)
+                .type(ExportType.QUESTION_COLLECT.key)
                 .setting(setting)
+                .no(count)
+                .build();
+        return userExportService.add(export);
+    }
+
+    public UserExport addQuestionError(Integer userId, JSONObject setting){
+        int count = userExportService.countByUser(userId, ExportType.QUESTION_ERROR);
+        UserExport export = UserExport.builder()
+                .userId(userId)
+                .type(ExportType.QUESTION_ERROR.key)
+                .setting(setting)
+                .no(count)
                 .build();
         return userExportService.add(export);
     }
 
     public UserExport addQuestionNote(Integer userId, JSONObject setting){
+        int count = userExportService.countByUser(userId, ExportType.NOTE_QUESTION);
         UserExport export = UserExport.builder()
                 .userId(userId)
                 .type(ExportType.NOTE_QUESTION.key)
                 .setting(setting)
+                .no(count)
                 .build();
         return userExportService.add(export);
     }
 
     public UserExport addCourseNote(Integer userId, JSONObject setting){
+        int count = userExportService.countByUser(userId, ExportType.NOTE_COURSE);
         UserExport export = UserExport.builder()
                 .userId(userId)
                 .type(ExportType.NOTE_COURSE.key)
                 .setting(setting)
+                .no(count)
                 .build();
 
         return userExportService.add(export);

+ 9 - 4
server/gateway-api/src/main/java/com/qxgmat/service/extend/MessageExtendService.java

@@ -332,10 +332,15 @@ public class MessageExtendService {
      * @param user
      */
     public void sendEmailChange(User user, String email){
-        Map<String, String> map = new HashMap<>();
-        map.put("nickname", user.getNickname());
-        map.put("email", user.getEmail());
-        send(user, MessageCategory.EMAIL_CHANGE, map);
+        if(email != null && !email.isEmpty()){
+            Map<String, String> unbindMap = new HashMap<>();
+            unbindMap.put("nickname", user.getNickname());
+            unbindMap.put("emails", email);
+            send(user, MessageCategory.EMAIL_UNNBIND, unbindMap);
+        }
+        Map<String, String> changeMap = new HashMap<>();
+        changeMap.put("nickname", user.getNickname());
+        send(user, MessageCategory.EMAIL_CHANGE, changeMap);
     }
 
     private String replaceBody(String body, Map<String, String> params){

+ 13 - 0
server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java

@@ -59,6 +59,9 @@ public class OrderFlowService {
     private UserOrderRecordService userOrderRecordService;
 
     @Resource
+    private UserCourseDataSubscribeService userCourseDataSubscribeService;
+
+    @Resource
     private UsersService usersService;
 
     @Resource
@@ -551,6 +554,16 @@ public class OrderFlowService {
             Date time = new Date();
             record.setUseTime(time);
 
+            // 电子资料自动订阅
+            UserCourseDataSubscribe subscribe = userCourseDataSubscribeService.getByData(record.getUserId(), record.getProductId());
+
+            if (subscribe == null){
+                userCourseDataSubscribeService.add(UserCourseDataSubscribe.builder()
+                        .userId(record.getUserId())
+                        .dataId(record.getProductId())
+                        .build());
+            }
+
             // 记录销售数量
             courseDataService.accumulation(record.getProductId(), 1, 0);
             return record;

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

@@ -103,7 +103,7 @@ public class CourseDataService extends AbstractService {
         return select(courseDataMapper, example, page, size);
     }
     /**
-     * 获取用户购买记录
+     * 获取用户订阅记录
      * @param page
      * @param size
      * @param userId

+ 10 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/ReadyArticleService.java

@@ -25,6 +25,16 @@ public class ReadyArticleService extends AbstractService {
     @Resource
     private ReadyArticleMapper readyArticleMapper;
 
+    public List<ReadyArticle> list(Integer categoryId){
+        Example example = new Example(ReadyArticle.class);
+        example.and(
+                example.createCriteria()
+                        .orEqualTo("categoryId", categoryId)
+                        .orEqualTo("parentCategoryId", categoryId)
+        );
+        return select(readyArticleMapper, example);
+    }
+
     public void deleteByCategory(Integer categoryId){
         Example example = new Example(ReadyArticle.class);
         example.and(

+ 7 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserAskCourseService.java

@@ -219,6 +219,13 @@ public class UserAskCourseService extends AbstractService {
         return relationMap;
     }
 
+    /**
+     * 累加访问记录
+     */
+    public void accumulation(Integer id, int view){
+        userAskCourseRelationMapper.accumulation(id, view);
+    }
+
     public UserAskCourse add(UserAskCourse message){
         int result = insert(userAskCourseMapper, message);
         message = one(userAskCourseMapper, message.getId());

+ 95 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseDataSubscribeService.java

@@ -0,0 +1,95 @@
+package com.qxgmat.service.inline;
+
+import com.github.pagehelper.Page;
+import com.nuliji.tools.AbstractService;
+import com.nuliji.tools.exception.ParameterException;
+import com.nuliji.tools.exception.SystemException;
+import com.nuliji.tools.mybatis.Example;
+import com.qxgmat.data.constants.enums.user.ExportType;
+import com.qxgmat.data.dao.UserCourseDataSubscribeMapper;
+import com.qxgmat.data.dao.UserExportMapper;
+import com.qxgmat.data.dao.entity.UserCourseDataSubscribe;
+import com.qxgmat.data.dao.entity.UserExport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+
+@Service
+public class UserCourseDataSubscribeService extends AbstractService {
+    private static final Logger logger = LoggerFactory.getLogger(UserCourseDataSubscribeService.class);
+
+    @Resource
+    private UserCourseDataSubscribeMapper userCourseDataSubscribeMapper;
+
+    public Page<UserCourseDataSubscribe> listByData(int page, int size, Integer dataId){
+        Example example = new Example(UserCourseDataSubscribe.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("dataId", dataId)
+        );
+        return select(userCourseDataSubscribeMapper, example, page, size);
+    }
+
+    public UserCourseDataSubscribe getByData(Integer userId, Integer dataId){
+        Example example = new Example(UserCourseDataSubscribe.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andEqualTo("dataId", dataId)
+        );
+        return one(userCourseDataSubscribeMapper, example);
+    }
+
+    public UserCourseDataSubscribe add(UserCourseDataSubscribe entity){
+        int result = insert(userCourseDataSubscribeMapper, entity);
+        entity = one(userCourseDataSubscribeMapper, entity.getId());
+        if(entity == null){
+            throw new SystemException("记录添加失败");
+        }
+        return entity;
+    }
+
+    public UserCourseDataSubscribe edit(UserCourseDataSubscribe entity){
+        UserCourseDataSubscribe in = one(userCourseDataSubscribeMapper, entity.getId());
+        if(in == null){
+            throw new ParameterException("记录不存在");
+        }
+        int result = update(userCourseDataSubscribeMapper, entity);
+        return entity;
+    }
+
+    public boolean delete(Number id){
+        UserCourseDataSubscribe in = one(userCourseDataSubscribeMapper, id);
+        if(in == null){
+            throw new ParameterException("记录不存在");
+        }
+        int result = delete(userCourseDataSubscribeMapper, id);
+        return result > 0;
+    }
+
+    public UserCourseDataSubscribe get(Number id){
+        UserCourseDataSubscribe in = one(userCourseDataSubscribeMapper, id);
+
+        if(in == null){
+            throw new ParameterException("记录不存在");
+        }
+        return in;
+    }
+
+    public Page<UserCourseDataSubscribe> select(int page, int pageSize){
+        return select(userCourseDataSubscribeMapper, page, pageSize);
+    }
+
+    public Page<UserCourseDataSubscribe> select(Integer[] ids){
+        return page(()->select(userCourseDataSubscribeMapper, ids), 1, ids.length);
+    }
+
+    public List<UserCourseDataSubscribe> select(Collection ids){
+        return select(userCourseDataSubscribeMapper, ids);
+    }
+
+}

+ 12 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserExportService.java

@@ -4,6 +4,8 @@ import com.github.pagehelper.Page;
 import com.nuliji.tools.AbstractService;
 import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.exception.SystemException;
+import com.nuliji.tools.mybatis.Example;
+import com.qxgmat.data.constants.enums.user.ExportType;
 import com.qxgmat.data.dao.UserExportMapper;
 import com.qxgmat.data.dao.entity.UserExport;
 import org.slf4j.Logger;
@@ -22,6 +24,16 @@ public class UserExportService extends AbstractService {
     @Resource
     private UserExportMapper userExportMapper;
 
+    public int countByUser(Integer userId, ExportType type){
+        Example example = new Example(UserExport.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andEqualTo("type", type.key)
+        );
+        return count(userExportMapper, example);
+    }
+
     public UserExport add(UserExport entity){
         int result = insert(userExportMapper, entity);
         entity = one(userExportMapper, entity.getId());

+ 17 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderRecordService.java

@@ -127,6 +127,23 @@ public class UserOrderRecordService extends AbstractService {
     }
 
     /**
+     * 列出购买资料的记录
+     * @param userId
+     * @param dataIds
+     * @return
+     */
+    public List<UserOrderRecord> listWithUserData(Integer userId, Collection dataIds){
+        Example example = new Example(UserOrderRecord.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andEqualTo("productType", ProductType.DATA.key)
+                        .andIn("productId", dataIds)
+        );
+        return select(userOrderRecordMapper, example);
+    }
+
+    /**
      * 获取小班课程学员列表
      * @param page
      * @param size

+ 87 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserSearchHistoryService.java

@@ -0,0 +1,87 @@
+package com.qxgmat.service.inline;
+
+import com.github.pagehelper.Page;
+import com.nuliji.tools.AbstractService;
+import com.nuliji.tools.exception.ParameterException;
+import com.nuliji.tools.exception.SystemException;
+import com.nuliji.tools.mybatis.Example;
+import com.qxgmat.data.constants.enums.user.ExportType;
+import com.qxgmat.data.dao.UserExportMapper;
+import com.qxgmat.data.dao.UserSearchHistoryMapper;
+import com.qxgmat.data.dao.entity.UserExport;
+import com.qxgmat.data.dao.entity.UserSearchHistory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+
+@Service
+public class UserSearchHistoryService extends AbstractService {
+    private static final Logger logger = LoggerFactory.getLogger(UserSearchHistoryService.class);
+
+    @Resource
+    private UserSearchHistoryMapper userSearchHistoryMapper;
+
+    public List<UserSearchHistory> listByUser(Integer userId, String startTime, String endTime){
+        Example example = new Example(UserExport.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andGreaterThanOrEqualTo("createTime", startTime)
+                        .andLessThan("createTime", endTime)
+        );
+        return select(userSearchHistoryMapper, example);
+    }
+
+    public UserSearchHistory add(UserSearchHistory entity){
+        int result = insert(userSearchHistoryMapper, entity);
+        entity = one(userSearchHistoryMapper, entity.getId());
+        if(entity == null){
+            throw new SystemException("记录添加失败");
+        }
+        return entity;
+    }
+
+    public UserSearchHistory edit(UserSearchHistory entity){
+        UserSearchHistory in = one(userSearchHistoryMapper, entity.getId());
+        if(in == null){
+            throw new ParameterException("记录不存在");
+        }
+        int result = update(userSearchHistoryMapper, entity);
+        return entity;
+    }
+
+    public boolean delete(Number id){
+        UserSearchHistory in = one(userSearchHistoryMapper, id);
+        if(in == null){
+            throw new ParameterException("记录不存在");
+        }
+        int result = delete(userSearchHistoryMapper, id);
+        return result > 0;
+    }
+
+    public UserSearchHistory get(Number id){
+        UserSearchHistory in = one(userSearchHistoryMapper, id);
+
+        if(in == null){
+            throw new ParameterException("记录不存在");
+        }
+        return in;
+    }
+
+    public Page<UserSearchHistory> select(int page, int pageSize){
+        return select(userSearchHistoryMapper, page, pageSize);
+    }
+
+    public Page<UserSearchHistory> select(Integer[] ids){
+        return page(()->select(userSearchHistoryMapper, ids), 1, ids.length);
+    }
+
+    public List<UserSearchHistory> select(Collection ids){
+        return select(userSearchHistoryMapper, ids);
+    }
+
+}

+ 7 - 4
server/gateway-api/src/main/java/com/qxgmat/task/AsyncTask.java

@@ -69,6 +69,9 @@ public class AsyncTask {
     @Autowired
     private UserOrderRecordService userOrderRecordService;
 
+    @Autowired
+    private UserCourseDataSubscribeService userCourseDataSubscribeService;
+
     @Async
     public void autoExercisePaper() {
         logger.info("自动练习组卷:顺序,考点,难易度");
@@ -275,18 +278,18 @@ public class AsyncTask {
         long start = System.currentTimeMillis();
         CourseDataHistory history = courseDataHistoryService.get(dataHistoryId);
         CourseData courseData = courseDataService.get(history.getDataId());
-        List<UserOrderRecord> userOrderRecordList;
+        List<UserCourseDataSubscribe> subscribeList;
         int page = 1;
         int size = 20;
         do {
-            userOrderRecordList = userOrderRecordService.listWithData(page, size, courseData.getId(), true);
-            Collection userIds = Transform.getIds(userOrderRecordList, UserOrderRecord.class, "userId");
+            subscribeList = userCourseDataSubscribeService.listByData(page, size, courseData.getId());
+            Collection userIds = Transform.getIds(subscribeList, UserCourseDataSubscribe.class, "userId");
             List<User> userList = usersService.select(userIds);
             for(User user : userList){
                 if (user.getDataEmailSubscribe() == 0) continue;
                 messageExtendService.sendDataUpdate(user, courseData, history);
             }
-        }while(userOrderRecordList.size() >= size);
+        }while(subscribeList.size() >= size);
         // messageExtendService.sendDataUpdate(user, courseData, history);
         long end = System.currentTimeMillis();
         logger.info("资料更新,耗时:" + (end - start) + "毫秒");

+ 1 - 0
server/tools/src/main/java/com/nuliji/tools/third/sendcloud/SendCloudMail.java

@@ -58,6 +58,7 @@ public class SendCloudMail {
         MultiValueMap<String, Object> params = new LinkedMultiValueMap<String, Object>();
         params.add("apiUser", apiUser);
         params.add("apiKey", apiKey);
+        // 支持多个用;分隔
         params.add("to", to);
         params.add("subject", subject);
         params.add("html", body);