Bladeren bron

feat(server): 课程接口

Go 5 jaren geleden
bovenliggende
commit
ea29df6ede
33 gewijzigde bestanden met toevoegingen van 603 en 448 verwijderingen
  1. 77 51
      front/project/admin/routes/setting/index/page.js
  2. 1 1
      front/project/admin/routes/student/askCourseDetail/page.js
  3. 8 0
      front/project/admin/stores/system.js
  4. 7 0
      front/project/h5/stores/main.js
  5. 7 3
      front/project/www/components/OtherModal/index.js
  6. 2 2
      front/project/www/routes/my/answer/page.js
  7. 4 0
      front/project/www/stores/course.js
  8. 7 0
      front/project/www/stores/main.js
  9. 22 2
      front/project/www/stores/my.js
  10. 21 5
      front/project/www/stores/order.js
  11. 2 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/MessageCategory.java
  12. 1 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/SettingKey.java
  13. 4 4
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserAskCourse.java
  14. 52 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserExport.java
  15. 42 7
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserMessage.java
  16. 1 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserAskCourseMapper.xml
  17. 14 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserExportMapper.xml
  18. 2 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserMessageMapper.xml
  19. 18 12
      server/data/src/main/resources/db/migration/V1__init_table.sql
  20. 17 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java
  21. 7 0
      server/gateway-api/src/main/java/com/qxgmat/controller/api/BaseController.java
  22. 21 0
      server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java
  23. 60 24
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  24. 3 3
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserAskCourseDto.java
  25. 13 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserCourseNoIdsDto.java
  26. 0 10
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserExportDto.java
  27. 1 1
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserQuestionIdsDto.java
  28. 47 0
      server/gateway-api/src/main/java/com/qxgmat/service/UserNoteCourseService.java
  29. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/UserNoteQuestionService.java
  30. 14 14
      server/gateway-api/src/main/java/com/qxgmat/service/UsersService.java
  31. 37 303
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ExportService.java
  32. 12 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/MessageExtendService.java
  33. 78 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserAskCourseService.java

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

@@ -12,41 +12,67 @@ export default class extends Page {
   initData() {
     System.getIndex().then(result => {
       const { form } = this.props;
-      form.setFieldsValue(flattenObject(result));
-      this.setState({ load: true, data: result });
+      form.setFieldsValue(flattenObject(result, 'index'));
+      this.setState({ load: true, index: result });
+    });
+    System.getBase().then(result => {
+      const { form } = this.props;
+      form.setFieldsValue(flattenObject(result, 'base'));
+      this.setState({ load: true, base: result });
     });
   }
 
   addLength(field, info) {
-    let { data } = this.state;
-    data = data || {};
-    data[field] = data[field] || [];
-    data[field].push(info);
-    this.setState({ data });
+    let { index } = this.state;
+    index = index || {};
+    index[field] = index[field] || [];
+    index[field].push(info);
+    this.setState({ index });
   }
 
   deleteLength(field, start, length) {
-    let { data } = this.state;
-    data = data || {};
-    data[field] = data[field] || [];
-    data[field].splice(start, length);
-    this.setState({ data });
+    let { index } = this.state;
+    index = index || {};
+    index[field] = index[field] || [];
+    index[field].splice(start, length);
+    this.setState({ index });
   }
 
   submit() {
+    this.submitIndex();
+    this.submitBase();
+  }
+
+  submitIndex() {
+    const { form } = this.props;
+    form.validateFields(['index'], (err) => {
+      if (!err) {
+        const { index } = form.getFieldsValue();
+        index.class = Object.keys(index.class || {}).map((key) => index.class[key]);
+        index.activity = Object.keys(index.activity || {}).map((key) => index.activity[key]);
+        index.evaluation = Object.keys(index.evaluation || {}).map((key) => index.evaluation[key]);
+        System.setIndex(index)
+          .then(() => {
+            this.setState({ index });
+            asyncSMessage('保存成功');
+          }).catch((e) => {
+            form.setFields(formatFormError(index, e.result));
+          });
+      }
+    });
+  }
+
+  submitBase() {
     const { form } = this.props;
-    form.validateFields((err) => {
+    form.validateFields(['base'], (err) => {
       if (!err) {
-        const data = form.getFieldsValue();
-        data.class = Object.keys(data.class || {}).map((key) => data.class[key]);
-        data.activity = Object.keys(data.activity || {}).map((key) => data.activity[key]);
-        data.evaluation = Object.keys(data.evaluation || {}).map((key) => data.evaluation[key]);
-        System.setIndex(data)
+        const { base } = form.getFieldsValue();
+        System.setBase(base)
           .then(() => {
-            this.setState(data);
+            this.setState({ base });
             asyncSMessage('保存成功');
           }).catch((e) => {
-            form.setFields(formatFormError(data, e.result));
+            form.setFields(formatFormError(base, e.result));
           });
       }
     });
@@ -58,7 +84,7 @@ export default class extends Page {
       <h1>备考攻略</h1>
       <Form>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='自学-从零开始'>
-          {getFieldDecorator('prepare.first', {
+          {getFieldDecorator('index.prepare.first', {
             rules: [
               { required: false, message: '请输入跳转地址' },
             ],
@@ -67,7 +93,7 @@ export default class extends Page {
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='自学-继续练习'>
-          {getFieldDecorator('prepare.continue', {
+          {getFieldDecorator('index.prepare.continue', {
             rules: [
               { required: false, message: '请输入跳转地址' },
             ],
@@ -76,7 +102,7 @@ export default class extends Page {
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='课程-初学'>
-          {getFieldDecorator('prepare.classJunior', {
+          {getFieldDecorator('index.prepare.classJunior', {
             rules: [
               { required: false, message: '请输入跳转地址' },
             ],
@@ -85,7 +111,7 @@ export default class extends Page {
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='课程-中级'>
-          {getFieldDecorator('prepare.classMiddle', {
+          {getFieldDecorator('index.prepare.classMiddle', {
             rules: [
               { required: false, message: '请输入跳转地址' },
             ],
@@ -94,7 +120,7 @@ export default class extends Page {
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='课程-高级'>
-          {getFieldDecorator('prepare.classSenior', {
+          {getFieldDecorator('index.prepare.classSenior', {
             rules: [
               { required: false, message: '请输入跳转地址' },
             ],
@@ -112,7 +138,7 @@ export default class extends Page {
       <h1>用户数据</h1>
       <Form>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='线下用户量'>
-          {getFieldDecorator('user.numberOffline', {
+          {getFieldDecorator('index.user.numberOffline', {
             rules: [
               { required: false, message: '' },
             ],
@@ -122,7 +148,7 @@ export default class extends Page {
         </Form.Item>
 
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='700+学员数'>
-          {getFieldDecorator('user.number700', {
+          {getFieldDecorator('index.user.number700', {
             rules: [
               { required: false, message: '' },
             ],
@@ -132,7 +158,7 @@ export default class extends Page {
         </Form.Item>
 
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='学员平均分'>
-          {getFieldDecorator('user.numberScore', {
+          {getFieldDecorator('index.user.numberScore', {
             rules: [
               { required: false, message: '' },
             ],
@@ -153,7 +179,7 @@ export default class extends Page {
       <Form>
         <Row>
           {course.map((row, index) => {
-            const image = getFieldValue(`course.${index}.image`) || null;
+            const image = getFieldValue(`index.course.${index}.image`) || null;
             return <Col span={7} offset={index % 3 ? 1 : 0}><Card>
               <Button className="delete-button" size="small" onClick={() => {
                 this.deleteLength('course', index, 1);
@@ -161,7 +187,7 @@ export default class extends Page {
                 <Icon type="delete" />
               </Button>
               <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='课程名称'>
-                {getFieldDecorator(`course.${index}.title`, {
+                {getFieldDecorator(`index.course.${index}.title`, {
                   rules: [
                     { required: true, message: '输入课程名称' },
                   ],
@@ -171,7 +197,7 @@ export default class extends Page {
                 )}
               </Form.Item>
               <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='跳转链接'>
-                {getFieldDecorator(`course.${index}.link`, {
+                {getFieldDecorator(`index.course.${index}.link`, {
                   rules: [
                     { required: true, message: '输入跳转链接' },
                   ],
@@ -181,7 +207,7 @@ export default class extends Page {
                 )}
               </Form.Item>
               <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='背景图片'>
-                {getFieldDecorator(`course.${index}.image`, {
+                {getFieldDecorator(`index.course.${index}.image`, {
                   rules: [
                     { required: true, message: '上传图片' },
                   ],
@@ -190,7 +216,7 @@ export default class extends Page {
                     listType="picture-card"
                     showUploadList={false}
                     beforeUpload={(file) => System.uploadImage(file).then((result) => {
-                      setFieldsValue({ [`course.${index}.image`]: result.url });
+                      setFieldsValue({ [`index.course.${index}.image`]: result.url });
                       return Promise.reject();
                     })}
                   >
@@ -224,7 +250,7 @@ export default class extends Page {
       <Form>
         <Row>
           {activity.map((row, index) => {
-            const image = getFieldValue(`activity.${index}.image`) || null;
+            const image = getFieldValue(`index.activity.${index}.image`) || null;
             return <Col span={7} offset={index % 3 ? 1 : 0}><Card>
               <Button className="delete-button" size="small" onClick={() => {
                 this.deleteLength('activity', index, 1);
@@ -232,7 +258,7 @@ export default class extends Page {
                 <Icon type="delete" />
               </Button>
               <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='跳转链接'>
-                {getFieldDecorator(`activity.${index}.link`, {
+                {getFieldDecorator(`index.activity.${index}.link`, {
                   rules: [
                     { required: true, message: '输入跳转链接' },
                   ],
@@ -242,7 +268,7 @@ export default class extends Page {
                 )}
               </Form.Item>
               <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='活动图片'>
-                {getFieldDecorator(`activity.${index}.image`, {
+                {getFieldDecorator(`index.activity.${index}.image`, {
                   rules: [
                     { required: true, message: '上传图片' },
                   ],
@@ -251,7 +277,7 @@ export default class extends Page {
                     listType="picture-card"
                     showUploadList={false}
                     beforeUpload={(file) => System.uploadImage(file).then((result) => {
-                      setFieldsValue({ [`claactivityss.${index}.image`]: result.url });
+                      setFieldsValue({ [`index.activity.${index}.image`]: result.url });
                       return Promise.reject();
                     })}
                   >
@@ -285,7 +311,7 @@ export default class extends Page {
       <Form>
         <Row>
           {evaluation.map((row, index) => {
-            const avatar = getFieldValue(`evaluation.${index}.avatar`) || null;
+            const avatar = getFieldValue(`index.evaluation.${index}.avatar`) || null;
             return <Col span={7} offset={index % 3 ? 1 : 0}><Card>
               <Button className="delete-button" size="small" onClick={() => {
                 this.deleteLength('evaluation', index, 1);
@@ -293,7 +319,7 @@ export default class extends Page {
                 <Icon type="delete" />
               </Button>
               <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='学员昵称'>
-                {getFieldDecorator(`evaluation.${index}.nickname`, {
+                {getFieldDecorator(`index.evaluation.${index}.nickname`, {
                   rules: [
                     { required: true, message: '输入学员昵称' },
                   ],
@@ -303,7 +329,7 @@ export default class extends Page {
                 )}
               </Form.Item>
               <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='学员头像'>
-                {getFieldDecorator(`evaluation.${index}.avatar`, {
+                {getFieldDecorator(`index.evaluation.${index}.avatar`, {
                   rules: [
                     { required: true, message: '上传图片' },
                   ],
@@ -312,7 +338,7 @@ export default class extends Page {
                     listType="picture-card"
                     showUploadList={false}
                     beforeUpload={(file) => System.uploadImage(file).then((result) => {
-                      setFieldsValue({ [`evaluation.${index}.avatar`]: result.url });
+                      setFieldsValue({ [`index.evaluation.${index}.avatar`]: result.url });
                       return Promise.reject();
                     })}
                   >
@@ -324,7 +350,7 @@ export default class extends Page {
                 )}
               </Form.Item>
               <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='评价内容'>
-                {getFieldDecorator(`evaluation.${index}.content`, {
+                {getFieldDecorator(`index.evaluation.${index}.content`, {
                   rules: [
                     { required: true, message: '输入评价内容' },
                   ],
@@ -349,13 +375,13 @@ export default class extends Page {
 
   renderContact() {
     const { getFieldDecorator, setFieldsValue, getFieldValue } = this.props.form;
-    const wechatImage = getFieldValue('contact.wechatImage');
-    const weiboImage = getFieldValue('contacct.weiboImage');
+    const wechatImage = getFieldValue('base.contact.wechatImage');
+    const weiboImage = getFieldValue('base.contacct.weiboImage');
     return <Block>
       <Form>
         <h1>联系方式</h1>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='电话'>
-          {getFieldDecorator('contact.phone', {
+          {getFieldDecorator('base.contact.phone', {
             rules: [
               { required: false, message: '请输入电话' },
             ],
@@ -364,7 +390,7 @@ export default class extends Page {
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='邮箱'>
-          {getFieldDecorator('contact.email', {
+          {getFieldDecorator('base.contact.email', {
             rules: [
               { required: false, message: '请输入邮箱' },
             ],
@@ -373,7 +399,7 @@ export default class extends Page {
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='微信号'>
-          {getFieldDecorator('contact.wechat', {
+          {getFieldDecorator('base.contact.wechat', {
             rules: [
               { required: false, message: '请输入微信号' },
             ],
@@ -383,13 +409,13 @@ export default class extends Page {
         </Form.Item>
 
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='公众号二维码'>
-          {getFieldDecorator('contact.wechatImage')(
+          {getFieldDecorator('base.contact.wechatImage')(
             <Upload
               listType="picture-card"
               showUploadList={false}
               beforeUpload={(file) => {
                 System.uploadImage(file).then((result) => {
-                  setFieldsValue({ 'contact.wechatImage': result.url });
+                  setFieldsValue({ 'base.contact.wechatImage': result.url });
                   return Promise.reject();
                 });
               }
@@ -403,12 +429,12 @@ export default class extends Page {
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='微博二维码'>
-          {getFieldDecorator('contact.weiboImage')(
+          {getFieldDecorator('base.contact.weiboImage')(
             <Upload
               listType="picture-card"
               showUploadList={false}
               beforeUpload={(file) => System.uploadImage(file).then((result) => {
-                setFieldsValue({ 'contact.weiboImage': result.url });
+                setFieldsValue({ 'base.contact.weiboImage': result.url });
                 return Promise.reject();
               })}
             >

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

@@ -127,7 +127,7 @@ export default class extends Page {
           {formatDate(createTime)}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='提问位置'>
-          {`P${courseNo.no}:${position}`}
+          {`P${courseNo.no}:${position}-${position + 5}min`}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='提问内容'>
           {originContent}

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

@@ -29,6 +29,14 @@ export default class SystemStore extends BaseStore {
     return this.apiPut('/setting/index', params);
   }
 
+  getBase() {
+    return this.apiGet('/setting/base');
+  }
+
+  setBase(params) {
+    return this.apiPut('/setting/base', params);
+  }
+
   getPlace() {
     return this.apiGet('/setting/place');
   }

+ 7 - 0
front/project/h5/stores/main.js

@@ -21,6 +21,13 @@ export default class MainStore extends BaseStore {
   }
 
   /**
+   * 获取基础配置
+   */
+  getBase() {
+    return this.apiGet('/base/base');
+  }
+
+  /**
    * 获取广告列表
    */
   getAd(channel) {

+ 7 - 3
front/project/www/components/OtherModal/index.js

@@ -679,7 +679,6 @@ export class FeedbackErrorDataModal extends Component {
   onConfirm() {
     const { onConfirm } = this.props;
     const { data } = this.state;
-    console.log(data, this.props);
     if (!data.content || !data.originContent) return;
     My.addFeedbackErrorData(
       data.dataId,
@@ -819,7 +818,7 @@ export class AskCourseModal extends Component {
         onCancel={() => this.onCancel()}
       >
         <div className="t-2 m-b-1 t-s-16">
-          针对<span className="t-4">课时{courseNo.no}</span>的 <Select theme="white" list={selectList} />
+          针对<span className="t-4">课时{courseNo.no}</span>的 <Select value={data.position} theme="white" list={selectList} />
           进行提问.
         </div>
         <div className="t-2 t-s-16">老师讲解的内容是:</div>
@@ -896,7 +895,12 @@ export class CourseNoteModal extends Component {
       >
         <div className="t-2 m-b-1 t-s-16">
           {course.title}
-          <Select theme="white" value={data.courseNoId} list={courseNos} onChange={(item) => {
+          <Select theme="white" value={data.courseNoId} list={courseNos.map(row => {
+            return {
+              title: `课时${row.no}`,
+              key: row.id,
+            };
+          })} onChange={(item) => {
             data.courseNoId = item.id;
             this.setState({ data });
           }} />

+ 2 - 2
front/project/www/routes/my/answer/page.js

@@ -158,7 +158,7 @@ export default class extends Page {
     this.initData();
   }
 
-  onAction() {}
+  onAction() { }
 
   onSelect(selectList) {
     this.setState({ selectList });
@@ -295,7 +295,7 @@ export default class extends Page {
                 )}
                 {item.answerStatus > 0 && (
                   <div className="desc">
-                    <OpenText>{item.answer}</OpenText>
+                    <OpenText>{item.answerStatus === 2 ? '与题目内容无关,老师无法作出回答,敬请谅解。' : item.answer}</OpenText>
                   </div>
                 )}
               </div>

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

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

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

@@ -17,6 +17,13 @@ export default class MainStore extends BaseStore {
   }
 
   /**
+   * 获取基础配置
+   */
+  getBase() {
+    return this.apiGet('/base/base');
+  }
+
+  /**
    * 获取广告列表
    */
   getAd(channel) {

+ 22 - 2
front/project/www/stores/my.js

@@ -257,6 +257,18 @@ export default class MyStore extends BaseStore {
   }
 
   /**
+   * 获取笔记列表
+   * @param {*} page
+   * @param {*} size
+   * @param {*} startTime
+   * @param {*} endTime
+   * @param {*} order
+   */
+  listQuestionNote({ keyword, module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order }) {
+    return this.apiGet('/my/note/question/list', { keyword, module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order });
+  }
+
+  /**
    * 更新课程笔记
    * @param {*} courseId
    * @param {*} courseNoId
@@ -266,6 +278,10 @@ export default class MyStore extends BaseStore {
     return this.apiPut('/my/note/course', { courseId, courseNoId, content });
   }
 
+  clearCourseNote(courseNoIds) {
+    return this.apiPost('/my/note/course/clear', { courseNoIds });
+  }
+
   /**
    * 获取笔记列表
    * @param {*} page
@@ -274,8 +290,8 @@ export default class MyStore extends BaseStore {
    * @param {*} endTime
    * @param {*} order
    */
-  listQuestionNote({ keyword, module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order }) {
-    return this.apiGet('/my/note/question/list', { keyword, module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order });
+  listCourseNote({ keyword, courseId, page, size, order, direction }) {
+    return this.apiGet('/my/note/course/list', { keyword, courseId, page, size, order, direction });
   }
 
   /**
@@ -327,6 +343,10 @@ export default class MyStore extends BaseStore {
     return this.apiPost('/my/ask/course', { courseId, courseNoId, position, originContent, content });
   }
 
+  listCourseAsk({ keyword, courseId, courseNoId, order, direction }) {
+    return this.apiGet('/my/ask/course/list', { keyword, courseId, courseNoId, order, direction });
+  }
+
   /**
    * 添加题目勘误
    * @param {*} questionNoId

+ 21 - 5
front/project/www/stores/order.js

@@ -2,19 +2,35 @@ import BaseStore from '@src/stores/base';
 
 export default class OrderStore extends BaseStore {
   allCheckout() {
-    return this.apiGet('/order/checkout/all');
+    return this.apiGet('/order/checkout/all')
+      .then(result => {
+        this.setState({ number: result.checkouts.length });
+        return result;
+      });
   }
 
   addCheckout({ productType, productId, service, param, number }) {
-    return this.apiPost('/order/checkout/add', { productType, productId, service, param, number });
+    return this.apiPost('/order/checkout/add', { productType, productId, service, param, number })
+      .then(result => {
+        this.setState({ number: result });
+        return result;
+      });
   }
 
   changeCheckout(id, number) {
-    return this.apiPut('/order/checkout/number', { id, number });
+    return this.apiPut('/order/checkout/number', { id, number })
+      .then(result => {
+        this.setState({ number: result });
+        return result;
+      });
   }
 
   removeCheckout(id) {
-    return this.apiDel('/order/checkout/delete', { id });
+    return this.apiDel('/order/checkout/delete', { id })
+      .then(result => {
+        this.setState({ number: result });
+        return result;
+      });
   }
 
   confirmPay(courseId) {
@@ -78,4 +94,4 @@ export default class OrderStore extends BaseStore {
   }
 }
 
-export const Order = new OrderStore({ key: 'order' });
+export const Order = new OrderStore({ key: 'order', local: true });

+ 2 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/MessageCategory.java

@@ -17,6 +17,8 @@ public enum MessageCategory {
 
     INVITED("invited", "邀请好友注册"),
     EMAIL_CHANGE("email_change", "邮箱变更"),
+    EMAIL_UNNBIND("email_unbind", "邮箱解绑"),
+    EMAIL_BIND("email_bind", "邮箱绑定"),
 
     CUSTOM("custom", "自定义消息")
     ;

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

@@ -32,6 +32,7 @@ public enum SettingKey {
     WECHAT_INFO("wechat_info"), // 微信公众号信息
     READY_READ("ready_read"), // 推荐阅读设置
 
+    BASE("base"), // 基础设置
     TIPS("tips"); // 页面提示信息
 
     final static public String message = "设置key";

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

@@ -39,7 +39,7 @@ public class UserAskCourse implements Serializable {
      * 位置
      */
     @Column(name = "`position`")
-    private String position;
+    private Integer position;
 
     /**
      * 提问优先回答时间
@@ -200,7 +200,7 @@ public class UserAskCourse implements Serializable {
      *
      * @return position - 位置
      */
-    public String getPosition() {
+    public Integer getPosition() {
         return position;
     }
 
@@ -209,7 +209,7 @@ public class UserAskCourse implements Serializable {
      *
      * @param position 位置
      */
-    public void setPosition(String position) {
+    public void setPosition(Integer position) {
         this.position = position;
     }
 
@@ -513,7 +513,7 @@ public class UserAskCourse implements Serializable {
          *
          * @param position 位置
          */
-        public Builder position(String position) {
+        public Builder position(Integer position) {
             obj.setPosition(position);
             return this;
         }

+ 52 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/UserExport.java

@@ -15,6 +15,9 @@ public class UserExport implements Serializable {
     @Column(name = "`user_id`")
     private Integer userId;
 
+    @Column(name = "`no`")
+    private Integer no;
+
     /**
      * 导出类型
      */
@@ -30,6 +33,9 @@ public class UserExport implements Serializable {
     @Column(name = "`create_time`")
     private Date createTime;
 
+    @Column(name = "`content`")
+    private String content;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -61,6 +67,20 @@ public class UserExport implements Serializable {
     }
 
     /**
+     * @return no
+     */
+    public Integer getNo() {
+        return no;
+    }
+
+    /**
+     * @param no
+     */
+    public void setNo(Integer no) {
+        this.no = no;
+    }
+
+    /**
      * 获取导出类型
      *
      * @return type - 导出类型
@@ -110,6 +130,20 @@ public class UserExport implements Serializable {
         this.createTime = createTime;
     }
 
+    /**
+     * @return content
+     */
+    public String getContent() {
+        return content;
+    }
+
+    /**
+     * @param content
+     */
+    public void setContent(String content) {
+        this.content = content;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -118,9 +152,11 @@ public class UserExport implements Serializable {
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
         sb.append(", userId=").append(userId);
+        sb.append(", no=").append(no);
         sb.append(", type=").append(type);
         sb.append(", setting=").append(setting);
         sb.append(", createTime=").append(createTime);
+        sb.append(", content=").append(content);
         sb.append("]");
         return sb.toString();
     }
@@ -153,6 +189,14 @@ public class UserExport implements Serializable {
         }
 
         /**
+         * @param no
+         */
+        public Builder no(Integer no) {
+            obj.setNo(no);
+            return this;
+        }
+
+        /**
          * 设置导出类型
          *
          * @param type 导出类型
@@ -180,6 +224,14 @@ public class UserExport implements Serializable {
             return this;
         }
 
+        /**
+         * @param content
+         */
+        public Builder content(String content) {
+            obj.setContent(content);
+            return this;
+        }
+
         public UserExport build() {
             return this.obj;
         }

+ 42 - 7
server/data/src/main/java/com/qxgmat/data/dao/entity/UserMessage.java

@@ -18,12 +18,18 @@ public class UserMessage implements Serializable {
     private Integer userId;
 
     /**
-     * 消息类型
+     * 消息
      */
     @Column(name = "`type`")
     private String type;
 
     /**
+     * 消息类型
+     */
+    @Column(name = "`message_category`")
+    private String messageCategory;
+
+    /**
      * 标题
      */
     @Column(name = "`title`")
@@ -85,24 +91,42 @@ public class UserMessage implements Serializable {
     }
 
     /**
-     * 获取消息类型
+     * 获取消息
      *
-     * @return type - 消息类型
+     * @return type - 消息
      */
     public String getType() {
         return type;
     }
 
     /**
-     * 设置消息类型
+     * 设置消息
      *
-     * @param type 消息类型
+     * @param type 消息
      */
     public void setType(String type) {
         this.type = type;
     }
 
     /**
+     * 获取消息类型
+     *
+     * @return message_category - 消息类型
+     */
+    public String getMessageCategory() {
+        return messageCategory;
+    }
+
+    /**
+     * 设置消息类型
+     *
+     * @param messageCategory 消息类型
+     */
+    public void setMessageCategory(String messageCategory) {
+        this.messageCategory = messageCategory;
+    }
+
+    /**
      * 获取标题
      *
      * @return title - 标题
@@ -197,6 +221,7 @@ public class UserMessage implements Serializable {
         sb.append(", id=").append(id);
         sb.append(", userId=").append(userId);
         sb.append(", type=").append(type);
+        sb.append(", messageCategory=").append(messageCategory);
         sb.append(", title=").append(title);
         sb.append(", link=").append(link);
         sb.append(", isRead=").append(isRead);
@@ -236,9 +261,9 @@ public class UserMessage implements Serializable {
         }
 
         /**
-         * 设置消息类型
+         * 设置消息
          *
-         * @param type 消息类型
+         * @param type 消息
          */
         public Builder type(String type) {
             obj.setType(type);
@@ -246,6 +271,16 @@ public class UserMessage implements Serializable {
         }
 
         /**
+         * 设置消息类型
+         *
+         * @param messageCategory 消息类型
+         */
+        public Builder messageCategory(String messageCategory) {
+            obj.setMessageCategory(messageCategory);
+            return this;
+        }
+
+        /**
          * 设置标题
          *
          * @param title 标题

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

@@ -10,7 +10,7 @@
     <result column="course_id" jdbcType="INTEGER" property="courseId" />
     <result column="course_no_id" jdbcType="INTEGER" property="courseNoId" />
     <result column="record_id" jdbcType="INTEGER" property="recordId" />
-    <result column="position" jdbcType="VARCHAR" property="position" />
+    <result column="position" jdbcType="INTEGER" property="position" />
     <result column="ask_time" jdbcType="INTEGER" property="askTime" />
     <result column="expire_time" jdbcType="TIMESTAMP" property="expireTime" />
     <result column="answer_status" jdbcType="INTEGER" property="answerStatus" />

+ 14 - 1
server/data/src/main/java/com/qxgmat/data/dao/mapping/UserExportMapper.xml

@@ -7,14 +7,27 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="user_id" jdbcType="INTEGER" property="userId" />
+    <result column="no" jdbcType="INTEGER" property="no" />
     <result column="type" jdbcType="VARCHAR" property="type" />
     <result column="setting" jdbcType="VARCHAR" property="setting" typeHandler="com.nuliji.tools.mybatis.handler.JsonObjectHandler" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
   </resultMap>
+  <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.UserExport">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <result column="content" jdbcType="LONGVARCHAR" property="content" />
+  </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `type`, `setting`, `create_time`
+    `id`, `user_id`, `no`, `type`, `setting`, `create_time`
+  </sql>
+  <sql id="Blob_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    `content`
   </sql>
 </mapper>

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

@@ -8,6 +8,7 @@
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="user_id" jdbcType="INTEGER" property="userId" />
     <result column="type" jdbcType="VARCHAR" property="type" />
+    <result column="message_category" jdbcType="VARCHAR" property="messageCategory" />
     <result column="title" jdbcType="VARCHAR" property="title" />
     <result column="link" jdbcType="VARCHAR" property="link" />
     <result column="is_read" jdbcType="INTEGER" property="isRead" />
@@ -23,7 +24,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `type`, `title`, `link`, `is_read`, `create_time`
+    `id`, `user_id`, `type`, `message_category`, `title`, `link`, `is_read`, `create_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 18 - 12
server/data/src/main/resources/db/migration/V1__init_table.sql

@@ -730,7 +730,8 @@ VALUES
 	(21,'experience_info','{}'),
 	(22,'sentence_info','{}'),
 	(23,'wechat_info','{}'),
-	(24,'ready_read','{}');
+	(24,'ready_read','{}'),
+	(25,'base','{}');
 
 CREATE TABLE textbook_library (
   id int(11) unsigned NOT NULL AUTO_INCREMENT,
@@ -973,7 +974,7 @@ CREATE TABLE user_ask_course (
   course_id int(11) unsigned NOT NULL COMMENT '课程id',
   course_no_id int(11) unsigned NOT NULL COMMENT '课时id',
   record_id int(11) unsigned NOT NULL COMMENT '记录id',
-  position varchar(20) NOT NULL DEFAULT '' COMMENT '位置',
+  position int(11) unsigned NOT NULL DEFAULT '0' COMMENT '位置',
   origin_content text COMMENT '老师讲解内容',
   content text COMMENT '提问',
   ask_time int(11) unsigned NOT NULL DEFAULT '0' COMMENT '提问优先回答时间',
@@ -1084,7 +1085,9 @@ CREATE TABLE user_course_appointment_comment (
   name varchar(255) DEFAULT NULL,
   create_time datetime DEFAULT NULL,
   delete_time datetime DEFAULT NULL,
-  PRIMARY KEY (`id`)
+  PRIMARY KEY (`id`),
+  KEY record_id (record_id),
+  KEY appointment_id (appointment_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户-预约-聊天';
 
 CREATE TABLE user_course_progress (
@@ -1111,14 +1114,16 @@ CREATE TABLE user_course_record (
   KEY user_id (user_id,course_id,record_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',
-  `type` varchar(20) NOT NULL DEFAULT '' COMMENT '导出类型',
-  `setting` text COMMENT '导出设置',
-  `content` longtext,
-  `create_time` datetime DEFAULT NULL,
-  PRIMARY KEY (`id`)
+CREATE TABLE user_export (
+  id int(11) unsigned NOT NULL AUTO_INCREMENT,
+  user_id int(11) unsigned NOT NULL DEFAULT '0',
+  no int(11) unsigned NOT NULL DEFAULT '0',
+  type varchar(20) NOT NULL DEFAULT '' COMMENT '导出类型',
+  setting text COMMENT '导出设置',
+  content longtext,
+  create_time datetime DEFAULT NULL,
+  PRIMARY KEY (id),
+  KEY user_id (user_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户-导出-记录';
 
 CREATE TABLE user_feedback_error (
@@ -1163,7 +1168,8 @@ CREATE TABLE user_invoice (
 CREATE TABLE user_message (
   id int(11) unsigned NOT NULL AUTO_INCREMENT,
   user_id int(11) NOT NULL DEFAULT '0' COMMENT '用户id',
-  type varchar(50) NOT NULL DEFAULT '' COMMENT '消息类型',
+  type varchar(50) NOT NULL DEFAULT '' COMMENT '消息',
+  message_category varchar(50) NOT NULL DEFAULT '' COMMENT '消息类型',
   title varchar(50) NOT NULL DEFAULT '' COMMENT '标题',
   content text COMMENT '内容',
   link varchar(255) NOT NULL DEFAULT '' COMMENT '链接',

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

@@ -114,6 +114,23 @@ public class SettingController {
         return ResponseHelp.success(entity.getValue());
     }
 
+    @RequestMapping(value = "/base", method = RequestMethod.PUT)
+    @ApiOperation(value = "修改基础配置", httpMethod = "PUT")
+    private Response<Boolean> editBase(@RequestBody @Validated JSONObject dto){
+        Setting entity = settingService.getByKey(SettingKey.BASE);
+        entity.setValue(dto);
+        settingService.edit(entity);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/base", method = RequestMethod.GET)
+    @ApiOperation(value = "获取基础配置", httpMethod = "GET")
+    private Response<JSONObject> getBase(){
+        Setting entity = settingService.getByKey(SettingKey.BASE);
+        logger.debug("{}", entity);
+        return ResponseHelp.success(entity.getValue());
+    }
+
     @RequestMapping(value = "/place", method = RequestMethod.PUT)
     @ApiOperation(value = "修改考点设置", httpMethod = "PUT")
     private Response<Boolean> editPlace(@RequestBody @Validated JSONObject dto){

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

@@ -86,6 +86,13 @@ public class BaseController {
         return ResponseHelp.success(entity.getValue());
     }
 
+    @RequestMapping(value = "/base", method = RequestMethod.GET)
+    @ApiOperation(value = "获取基础配置", httpMethod = "GET")
+    private Response<JSONObject> base(){
+        Setting entity = settingService.getByKey(SettingKey.BASE);
+        return ResponseHelp.success(entity.getValue());
+    }
+
     @RequestMapping(value = "/ad", method = RequestMethod.GET)
     @ApiOperation(value = "获取广告", notes = "获取广告列表", httpMethod = "GET")
     public Response<List<Ad>> ad(

+ 21 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java

@@ -94,6 +94,9 @@ public class CourseController {
     private UserCourseRecordService userCourseRecordService;
 
     @Autowired
+    private UserAskCourseService userAskCourseService;
+
+    @Autowired
     private UserOrderRecordService userOrderRecordService;
 
     @Autowired
@@ -157,6 +160,24 @@ public class CourseController {
         return ResponseHelp.success(dto);
     }
 
+    @RequestMapping(value = "/ask/list", method = RequestMethod.GET)
+    @ApiOperation(value = "精选问答", httpMethod = "GET")
+    public Response<PageMessage<UserAskCourse>> listAsk(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Integer courseId,
+            @RequestParam(required = false) Integer courseNoId,
+            @RequestParam(required = false) Integer position,
+            @RequestParam(required = false) String order, // create_time, answer_time
+            @RequestParam(required = false) String direction
+    ) {
+        User user = (User) shiroHelp.getLoginUser();
+        Page<UserAskCourse> pr = userAskCourseService.listByCourse(page, size, keyword, courseId, courseNoId, position, true, order, DirectionStatus.ValueOf(direction));
+
+        return ResponseHelp.success(pr, page, size, pr.getTotal());
+    }
+
     @RequestMapping(value = "/simple", method = RequestMethod.GET)
     @ApiOperation(value = "课程基本信息", httpMethod = "GET")
     public Response<Course> simple(

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

@@ -12,7 +12,6 @@ import com.qxgmat.data.constants.enums.module.*;
 import com.qxgmat.data.constants.enums.status.AskStatus;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.user.DataType;
-import com.qxgmat.data.constants.enums.user.ExportType;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.inline.PaperStat;
 import com.qxgmat.data.inline.UserQuestionStat;
@@ -223,11 +222,12 @@ public class MyController {
     @ApiOperation(value = "绑定邮箱", httpMethod = "POST")
     public Response<Boolean> email(@RequestBody @Validated UserEmailDto dto, HttpSession session, HttpServletRequest request) {
         User user = (User) shiroHelp.getLoginUser();
+        User in = usersService.get(user.getId());
         usersService.edit(User.builder()
                 .id(user.getId())
                 .email(dto.getEmail())
                 .build());
-        messageExtendService.sendEmailChange(user);
+        messageExtendService.sendEmailChange(user, in.getEmail());
         return ResponseHelp.success(true);
     }
 
@@ -874,7 +874,7 @@ public class MyController {
 
     @RequestMapping(value = "/collect/question/clear", method = RequestMethod.DELETE)
     @ApiOperation(value = "移除题目收藏", notes = "移除题目收藏", httpMethod = "DELETE")
-    public Response<Boolean> deleteQuestionCollect(@RequestBody @Validated UserQuestionIdsDto dto)  {
+    public Response<Boolean> clearQuestionCollect(@RequestBody @Validated UserQuestionNoIdsDto dto)  {
         User user = (User) shiroHelp.getLoginUser();
         List<QuestionNo> questionNoList = questionNoService.select(dto.getQuestionNoIds());
         for(QuestionNo questionNo : questionNoList){
@@ -1077,7 +1077,7 @@ public class MyController {
 
     @RequestMapping(value = "/error/clear", method = RequestMethod.POST)
     @ApiOperation(value = "错题移除", notes = "错题移除", httpMethod = "POST")
-    public Response<Boolean> clearError(@RequestBody @Validated UserQuestionIdsDto dto)  {
+    public Response<Boolean> clearError(@RequestBody @Validated UserQuestionNoIdsDto dto)  {
         User user = (User) shiroHelp.getLoginUser();
 
         List<QuestionNo> questionNoList = questionNoService.select(dto.getQuestionNoIds());
@@ -1127,7 +1127,7 @@ public class MyController {
 
     @RequestMapping(value = "/note/question/clear", method = RequestMethod.POST)
     @ApiOperation(value = "笔记移除", notes = "笔记移除", httpMethod = "POST")
-    public Response<Boolean> clearNoteQuestion(@RequestBody @Validated UserQuestionIdsDto dto)  {
+    public Response<Boolean> clearNoteQuestion(@RequestBody @Validated UserQuestionNoIdsDto dto)  {
         User user = (User) shiroHelp.getLoginUser();
         List<QuestionNo> questionNoList = questionNoService.select(dto.getQuestionNoIds());
         for(QuestionNo questionNo : questionNoList){
@@ -1196,6 +1196,33 @@ public class MyController {
         return ResponseHelp.success(true);
     }
 
+    @RequestMapping(value = "/note/course/clear", method = RequestMethod.POST)
+    @ApiOperation(value = "笔记移除", notes = "笔记移除", httpMethod = "POST")
+    public Response<Boolean> clearNoteCourse(@RequestBody @Validated UserCourseNoIdsDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+        List<CourseNo> courseNoList = courseNoService.select(dto.getCourseNoIds());
+        for(CourseNo courseNo : courseNoList){
+            userNoteCourseService.deleteNote(user.getId(), courseNo.getId());
+        }
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/note/course/list", method = RequestMethod.GET)
+    @ApiOperation(value = "获取课程笔记列表", notes = "获取笔记列表", httpMethod = "GET")
+    public Response<PageMessage<UserNoteCourse>> listNoteCourse(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Integer courseId,
+            @RequestParam(required = false) String order, // update_time, no
+            @RequestParam(required = false) String direction,
+            HttpSession session)  {
+        User user = (User) shiroHelp.getLoginUser();
+        Page<UserNoteCourse> p = userNoteCourseService.listByCourse(page, size, keyword, user.getId(), courseId, order, DirectionStatus.ValueOf(direction));
+
+        return ResponseHelp.success(p, page, size, p.getTotal());
+    }
+
     @RequestMapping(value = "/report/list", method = RequestMethod.GET)
     @ApiOperation(value = "获取报告列表", notes = "获取报告列表", httpMethod = "GET")
     public Response<PageMessage<UserPaperDto>> listReport(
@@ -1405,6 +1432,24 @@ public class MyController {
         return ResponseHelp.success(true);
     }
 
+    @RequestMapping(value = "/ask/course/list", method = RequestMethod.GET)
+    @ApiOperation(value = "获取课程提问列表", notes = "获取课程提问列表", httpMethod = "GET")
+    public Response<PageMessage<UserAskCourse>> listAskCourse(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Integer courseId,
+            @RequestParam(required = false) Integer courseNoId,
+            @RequestParam(required = false) Integer askStatus,
+            @RequestParam(required = false) String order, // create_time, answer_time
+            @RequestParam(required = false) String direction,
+            HttpSession session)  {
+        User user = (User) shiroHelp.getLoginUser();
+        Page<UserAskCourse> p = userAskCourseService.listByUser(page, size, keyword, user.getId(), courseId, courseNoId, AskStatus.ValueOf(askStatus), order, DirectionStatus.ValueOf(direction));
+
+        return ResponseHelp.success(p, page, size, p.getTotal());
+    }
+
     @RequestMapping(value = "/feedback/error/question", method = RequestMethod.POST)
     @ApiOperation(value = "添加题目勘误", notes = "添加勘误", httpMethod = "POST")
     public Response<Boolean> addFeedbackErrorQuestion(@RequestBody @Validated UserFeedbackErrorQuestionDto dto)  {
@@ -1894,38 +1939,29 @@ public class MyController {
 
     @RequestMapping(value = "/export/question", method = RequestMethod.POST)
     @ApiOperation(value = "导出题目", notes = "导出题目", httpMethod = "POST")
-    public Response<UserExport> exportQuestion(@RequestBody @Validated UserExportDto dto)  {
+    public Response<Integer> exportQuestion(@RequestBody @Validated UserExportDto dto)  {
         User user = (User) shiroHelp.getLoginUser();
-        UserExport entity = Transform.dtoToEntity(dto);
-        entity.setUserId(user.getId());
-        entity.setType(ExportType.QUESTION.key);
-        entity = userExportService.add(entity);
-        return ResponseHelp.success(entity);
+        UserExport entity = exportService.addQuestion(user.getId(), JSONObject.parseObject(JSONObject.toJSONString(dto.getSetting())));
+        return ResponseHelp.success(entity.getId());
     }
 
     @RequestMapping(value = "/export/note/question", method = RequestMethod.POST)
     @ApiOperation(value = "导出题目笔记", notes = "导出题目笔记", httpMethod = "POST")
-    public Response<UserExport> exportNoteQuestion(@RequestBody @Validated UserExportDto dto)  {
+    public Response<Integer> exportNoteQuestion(@RequestBody @Validated UserExportDto dto)  {
         User user = (User) shiroHelp.getLoginUser();
-        UserExport entity = Transform.dtoToEntity(dto);
-        entity.setUserId(user.getId());
-        entity.setType(ExportType.NOTE_QUESTION.key);
-        entity = userExportService.add(entity);
-        return ResponseHelp.success(entity);
+        UserExport entity = exportService.addQuestionNote(user.getId(), JSONObject.parseObject(JSONObject.toJSONString(dto.getSetting())));
+        return ResponseHelp.success(entity.getId());
     }
 
     @RequestMapping(value = "/export/note/course", method = RequestMethod.POST)
     @ApiOperation(value = "导出课程笔记", notes = "导出课程笔记", httpMethod = "POST")
-    public Response<UserExport> exportNoteCourse(@RequestBody @Validated UserExportDto dto)  {
+    public Response<Integer> exportNoteCourse(@RequestBody @Validated UserExportDto dto)  {
         User user = (User) shiroHelp.getLoginUser();
-        UserExport entity = Transform.dtoToEntity(dto);
-        entity.setUserId(user.getId());
-        entity.setType(ExportType.NOTE_COURSE.key);
-        entity = userExportService.add(entity);
-        return ResponseHelp.success(entity);
+        UserExport entity = exportService.addCourseNote(user.getId(), JSONObject.parseObject(JSONObject.toJSONString(dto.getSetting())));
+        return ResponseHelp.success(entity.getId());
     }
 
-    @RequestMapping(value = "/export/note/course", method = RequestMethod.GET)
+    @RequestMapping(value = "/export/detail", method = RequestMethod.GET)
     @ApiOperation(value = "导出详情", notes = "导出详情", httpMethod = "GET")
     public Response<UserExport> exportDetail(int id)  {
         UserExport entity = userExportService.get(id);

+ 3 - 3
server/gateway-api/src/main/java/com/qxgmat/dto/request/UserAskCourseDto.java

@@ -9,7 +9,7 @@ public class UserAskCourseDto {
 
     private Integer courseNoId;
 
-    private String position;
+    private Integer position;
 
     private String originContent;
 
@@ -31,11 +31,11 @@ public class UserAskCourseDto {
         this.courseNoId = courseNoId;
     }
 
-    public String getPosition() {
+    public Integer getPosition() {
         return position;
     }
 
-    public void setPosition(String position) {
+    public void setPosition(Integer position) {
         this.position = position;
     }
 

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

@@ -0,0 +1,13 @@
+package com.qxgmat.dto.request;
+
+public class UserCourseNoIdsDto {
+    private Integer[] courseNoIds;
+
+    public Integer[] getCourseNoIds() {
+        return courseNoIds;
+    }
+
+    public void setCourseNoIds(Integer[] courseNoIds) {
+        this.courseNoIds = courseNoIds;
+    }
+}

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

@@ -7,8 +7,6 @@ import com.qxgmat.data.dao.entity.UserExport;
 public class UserExportDto {
     private Object setting;
 
-    private String type;
-
     public Object getSetting() {
         return setting;
     }
@@ -16,12 +14,4 @@ public class UserExportDto {
     public void setSetting(Object setting) {
         this.setting = setting;
     }
-
-    public String getType() {
-        return type;
-    }
-
-    public void setType(String type) {
-        this.type = type;
-    }
 }

+ 1 - 1
server/gateway-api/src/main/java/com/qxgmat/dto/request/UserQuestionIdsDto.java

@@ -1,6 +1,6 @@
 package com.qxgmat.dto.request;
 
-public class UserQuestionIdsDto {
+public class UserQuestionNoIdsDto {
     private Integer[] questionNoIds;
 
     public Integer[] getQuestionNoIds() {

+ 47 - 0
server/gateway-api/src/main/java/com/qxgmat/service/UserNoteCourseService.java

@@ -5,6 +5,8 @@ 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.status.AskStatus;
+import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.dao.UserNoteCourseMapper;
 import com.qxgmat.data.dao.entity.UserNoteCourse;
 import org.slf4j.Logger;
@@ -22,6 +24,30 @@ public class UserNoteCourseService extends AbstractService {
     @Resource
     private UserNoteCourseMapper userNoteCourseMapper;
 
+    public Page<UserNoteCourse> listByCourse(int page, int size, String keyword, Integer userId, Integer courseId, String order, DirectionStatus direction){
+        Example example = new Example(UserNoteCourse.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andEqualTo("courseId", courseId)
+        );
+        if (keyword != null)
+            example.and(
+                    example.createCriteria()
+                            .orLike("content", "%"+keyword+"%")
+            );
+        if(order == null || order.isEmpty()) order = "id";
+        switch(direction){
+            case ASC:
+                example.orderBy(order).asc();
+                break;
+            case DESC:
+            default:
+                example.orderBy(order).desc();
+        }
+        return select(userNoteCourseMapper, example, page, size);
+    }
+
     public List<UserNoteCourse> listByCourse(Number courseId){
         Example example = new Example(UserNoteCourse.class);
         example.and(
@@ -85,6 +111,27 @@ public class UserNoteCourseService extends AbstractService {
         return relationMap;
     }
 
+    /**
+     * 删除笔记
+     * @param userId
+     * @param courseNoId
+     * @return
+     */
+    @Transactional
+    public boolean deleteNote(Integer userId, Integer courseNoId){
+        Example example = new Example(UserNoteCourse.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andEqualTo("courseNoId", courseNoId)
+        );
+        UserNoteCourse in = one(userNoteCourseMapper, example);
+        if (in == null){
+            return true;
+        }
+        return delete(in.getId());
+    }
+
     public UserNoteCourse add(UserNoteCourse message){
         int result = insert(userNoteCourseMapper, message);
         message = one(userNoteCourseMapper, message.getId());

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

@@ -187,7 +187,7 @@ public class UserNoteQuestionService extends AbstractService {
     }
 
     /**
-     * 取消收藏题目编号
+     * 删除笔记
      * @param userId
      * @param questionId
      * @return

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

@@ -147,30 +147,30 @@ public class UsersService extends AbstractService {
                 throw new ParameterException("该微信账户已绑定其他手机号,您可直接使用微信登录");
             }
         }
-        openUser = User.builder()
-                .id(openUser != null ? openUser.getId() : user != null ? user.getId() : null)
+        User mm = User.builder()
+                .id(openUser != null ? openUser.getId() : null)
                 .build();
-        if (user ==null || user.getAvatar() == null || user.getAvatar().isEmpty()) openUser.setAvatar(data.getAvatar());
-        if (user == null || user.getNickname() == null|| user.getNickname().isEmpty() )openUser.setNickname(data.getNickName());
+        if (openUser ==null || openUser.getAvatar() == null || openUser.getAvatar().isEmpty()) mm.setAvatar(data.getAvatar());
+        if (openUser == null || openUser.getNickname() == null|| openUser.getNickname().isEmpty() )mm.setNickname(data.getNickName());
         switch(platform){
             case "wechat_pc":
-                openUser.setWechatOpenidPc(data.getOpenId());
-                openUser.setWechatUnionid(data.getUnionId());
+                mm.setWechatOpenidPc(data.getOpenId());
+                mm.setWechatUnionid(data.getUnionId());
                 break;
             case "wechat_native":
-                openUser.setWechatOpenidWechat(data.getOpenId());
-                openUser.setWechatUnionid(data.getUnionId());
-                openUser.setWechatAccessToken(data.getAccessToken());
-                openUser.setWechatRefreshToken(data.getRefreshToken());
-                openUser.setWechatExpireTime(data.getExpiresTime());
+                mm.setWechatOpenidWechat(data.getOpenId());
+                mm.setWechatUnionid(data.getUnionId());
+                mm.setWechatAccessToken(data.getAccessToken());
+                mm.setWechatRefreshToken(data.getRefreshToken());
+                mm.setWechatExpireTime(data.getExpiresTime());
                 break;
         }
-        if (openUser.getId() != null){
+        if (mm.getId() != null){
             // 直接更新数据
-            edit(openUser);
+            edit(mm);
         }
 
-        return openUser;
+        return mm;
     }
 
     /**

+ 37 - 303
server/gateway-api/src/main/java/com/qxgmat/service/extend/ExportService.java

@@ -1,325 +1,59 @@
 package com.qxgmat.service.extend;
 
-import com.nuliji.tools.Tools;
-import com.nuliji.tools.Transform;
-import com.nuliji.tools.exception.ParameterException;
-import com.qxgmat.data.constants.enums.QuestionSubject;
-import com.qxgmat.data.constants.enums.QuestionType;
-import com.qxgmat.data.constants.enums.module.VideoCourseType;
-import com.qxgmat.data.dao.entity.*;
-import com.qxgmat.data.relation.entity.UserRecordStatRelation;
+import com.alibaba.fastjson.JSONObject;
+import com.qxgmat.data.constants.enums.user.ExportType;
+import com.qxgmat.data.dao.entity.UserExport;
+import com.qxgmat.service.UserNoteCourseService;
+import com.qxgmat.service.UserNoteQuestionService;
 import com.qxgmat.service.inline.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
 
 @Service
 public class ExportService {
 
     @Autowired
-    private CourseService courseService;
+    private UserNoteQuestionService userNoteQuestionService;
 
     @Autowired
-    private UserCourseService userCourseService;
+    private UserNoteCourseService userNoteCourseService;
 
     @Autowired
-    private UserOrderRecordService userOrderRecordService;
+    private QuestionService questionService;
 
     @Autowired
-    private UserCourseRecordService userCourseRecordService;
+    private QuestionNoService questionNoService;
 
     @Resource
-    private UserCourseProgressService userCourseProgressService;
-
-    /**
-     * 计算vs课程有效期
-     * @param vsNumber
-     */
-    public int computeExpire(Integer vsNumber, Course course){
-        // day / 10
-        Integer expireDays = course.getExpirePreDays();
-
-        return Math.max(vsNumber / 10, 1) * expireDays;
-    }
-
-    /**
-     * 获取video课程有效期
-     * @param course
-     * @return
-     */
-    public int computeExpire(Course course){
-        return course.getUseExpireDays();
-    }
-
-    public void suspendCourse(Integer userId, Integer recordId){
-        UserOrderRecord record = userOrderRecordService.get(recordId);
-        if (record == null){
-            throw new ParameterException("记录不存在");
-        }
-        if (!record.getUserId().equals(userId)){
-            throw new ParameterException("记录不存在");
-        }
-        if (record.getIsUsed() == 0){
-            throw new ParameterException("课程还未开通");
-        }
-        if (record.getIsSuspend() > 0){
-            throw new ParameterException("已停课1次");
-        }
-        userOrderRecordService.edit(UserOrderRecord.builder()
-                .id(record.getId())
-                .isSuspend(1)
-                .suspendTime(new Date())
-                .build());
-    }
-
-    public void restoreCourse(Integer userId, Integer recordId){
-        UserOrderRecord record = userOrderRecordService.get(recordId);
-        if (record == null){
-            throw new ParameterException("记录不存在");
-        }
-        if (!record.getUserId().equals(userId)){
-            throw new ParameterException("记录不存在");
-        }
-        if (record.getIsUsed() == 0){
-            throw new ParameterException("课程还未开通");
-        }
-        if (record.getIsSuspend() == 0){
-            throw new ParameterException("无需恢复");
-        }
-        if (record.getRestoreTime() != null){
-            throw new ParameterException("已恢复");
-        }
-
-        userOrderRecordService.edit(UserOrderRecord.builder()
-                .id(record.getId())
-                .restoreTime(new Date())
-                .useEndTime(Tools.addDate(record.getUseEndTime(), (int)((new Date().getTime() - record.getSuspendTime().getTime()) / 86400000)))
-                .build());
-    }
-
-    public void awardCourse(Integer userId, Integer recordId, Integer day){
-        UserOrderRecord record = userOrderRecordService.get(recordId);
-        if (record == null){
-            throw new ParameterException("记录不存在");
-        }
-        if (!record.getUserId().equals(userId)){
-            throw new ParameterException("记录不存在");
-        }
-        if (record.getIsUsed() == 0){
-            throw new ParameterException("课程还未开通");
-        }
-        if (record.getCourseAward() > 0){
-            throw new ParameterException("已奖励");
-        }
-
-        userOrderRecordService.edit(UserOrderRecord.builder()
-                .id(record.getId())
-                .courseAward(day)
-                .useEndTime(Tools.addDate(record.getUseEndTime(), day))
-                .build());
-    }
-
-    /**
-     * 累计听课学习时间
-     * @param userId
-     * @return
-     */
-    public Integer studyTime(Integer userId, Date startTime, Date endTime){
-        UserRecordStatRelation record = userCourseRecordService.stat(userId, startTime != null? startTime.toString():null, endTime != null ? endTime.toString(): null);
-        return record != null ? record.getUserTime() : 0;
-    }
-
-    /**
-     * 全站平均听课时间
-     * @return
-     */
-    public Integer studyAvgTime(Date startTime, Date endTime){
-        UserRecordStatRelation record = userCourseRecordService.statAvg(startTime != null? startTime.toString():null, endTime != null ? endTime.toString(): null);
-        return record != null ? record.getUserTime() : 0;
-    }
-
-    public UserCourse userCourse(Integer userId, Integer courseId){
-        return userCourseService.getCourse(userId, courseId);
-    }
-
-    /**
-     * 根据题目类型获取课程信息
-     * @param questionType
-     * @return
-     */
-    public Integer questionRelationCourse(Integer userId, QuestionType questionType){
-        QuestionSubject subject = QuestionSubject.FromType(questionType);
-        // 只查询系统授课记录
-        List<Course> courseList = courseService.listByExtend(questionType.key, subject.key, VideoCourseType.SYSTEM.key);
-        if (courseList.size()==0)return null;
-        Collection ids = Transform.getIds(courseList, Course.class, "id");
-        List<UserCourse> userCourseList = userCourseService.listByCourse(userId, ids);
-        if (userCourseList.size() == 0) return null;
-        if (userCourseList.size() == 1) return userCourseList.get(0).getRecordId();
-        // 获取回答时间最小的记录
-        Collection recordIds = Transform.getIds(userCourseList, UserCourse.class, "recordId");
-        List<UserOrderRecord> userOrderRecordList =  userOrderRecordService.select(recordIds);
-        int min = 0;
-        Integer minId = null;
-        for(UserOrderRecord userOrderRecord : userOrderRecordList){
-            if (minId == null || min > userOrderRecord.getAskTime()){
-                min = userOrderRecord.getAskTime();
-                minId = userOrderRecord.getId();
-            }
-        }
-        return minId;
-    }
-
-    /**
-     * 获取已完成课时序号
-     * @param courseNoList
-     * @param progressList
-     * @return
-     */
-    public int computeCourseNoFinish(Collection<CourseNo> courseNoList, Collection<UserCourseProgress> progressList){
-        Map progressMap = Transform.getMap(progressList, UserCourseProgress.class, "courseNoId");
-
-        int min = 0;
-        for(CourseNo no : courseNoList){
-            UserCourseProgress progress = (UserCourseProgress)progressMap.get(no.getId());
-            if(progress == null) continue;
-            if (min != 0 && min > no.getNo()) continue;
-            if (progress.getProgress() > 50) {
-                min = no.getNo();
-            }
-        }
-        return min;
-    }
-
-    /**
-     * 获取当前课时
-     * @param courseNoList
-     * @param progressList
-     * @return
-     */
-    public int computeCourseNoCurrent(Collection<CourseNo> courseNoList, Collection<UserCourseProgress> progressList){
-        Map progressMap = Transform.getMap(progressList, UserCourseProgress.class, "courseNoId");
-
-        int min = 0;
-        for(CourseNo no : courseNoList){
-            // 如果进度不过半,按当前课时+下一课时
-            // 如果进度过半,下2次课时
-            UserCourseProgress progress = (UserCourseProgress)progressMap.get(no.getId());
-            if(progress == null) continue;
-            if (min != 0 && min > no.getNo()) continue;
-            min = no.getNo();
-        }
-        return min;
-    }
-
-
-    /**
-     * 计算用户该课程的总学习时长
-     * @param userCourseRecords
-     * @return
-     */
-    public int computeCourseTime(Collection<UserCourseRecord> userCourseRecords){
-        int time = 0;
-        for(UserCourseRecord userCourseRecord:userCourseRecords){
-            time += userCourseRecord.getUserTime();
-        }
-        return time;
-    }
-
-    /**
-     * 计算用户该课程的总学习天数
-     * @param  record
-     * @return
-     */
-    public int computeCourseDay(UserOrderRecord record){
-        if(record.getUseEndTime() == null){
-            return 0;
-        }
-        int suspend = 0;
-        if(record.getIsSuspend() > 0){
-            if(record.getRestoreTime() == null){
-                suspend = (int)(Tools.day(new Date()).getTime() - Tools.day(record.getSuspendTime()).getTime())/86400000;
-            }else{
-                suspend = (int)(Tools.day(record.getRestoreTime()).getTime() - Tools.day(record.getSuspendTime()).getTime())/86400000;
-            }
-        }
-        if (record.getUseEndTime().before(new Date())){
-            return (int)(Tools.day(record.getUseEndTime()).getTime() - Tools.day(record.getUseStartTime()).getTime())/86400000 - suspend;
-        }else{
-            return (int)(Tools.day(new Date()).getTime() - Tools.day(record.getUseStartTime()).getTime())/86400000 - suspend;
-        }
-//        Date min = null;
-//        Date max = null;
-//        for(UserCourseRecord userCourseRecord:userCourseRecords){
-//            if(min==null || min.after(userCourseRecord.getCreateTime())){
-//                min = userCourseRecord.getCreateTime();
-//            }
-//            if (max == null || max.before(userCourseRecord.getCreateTime())){
-//                max = userCourseRecord.getCreateTime();
-//            }
-//        }
-//        int suspend = 0;
-//        if (min == null) return 0;
-//        return (int)((Tools.day(max).getTime() - Tools.day(min).getTime())/86400000) - suspend;
-    }
-
-    /**
-     * 根据用户权限更新资源信息
-     * @param user
-     * @param courseId
-     * @param courseNoList
-     */
-    public void refreshNoResource(User user, Integer courseId, List<CourseNo> courseNoList){
-        if (user != null){
-            if (userCourseService.getCourse(user.getId(), courseId) == null){
-                for(CourseNo courseNo : courseNoList){
-                    courseNo.setResource(courseNo.getTrailResource());
-                }
-            }
-        }else{
-            for(CourseNo courseNo : courseNoList){
-                courseNo.setResource(courseNo.getTrailResource());
-            }
-        }
-    }
-
-    /**
-     * 根据用户权限更新资源信息
-     * @param user
-     * @param courseData
-     */
-    public void refreshDataResource(User user, CourseData courseData){
-        // 处理权限
-        if (user != null){
-            if (!userOrderRecordService.hasData(user.getId(), courseData.getId())){
-                courseData.setResource(courseData.getTrailResource());
-            }
-        }else{
-            courseData.setResource(courseData.getTrailResource());
-        }
-    }
-    /**
-     * 根据用户权限更新资源信息
-     * @param user
-     * @param courseDataList
-     */
-    public void refreshDataResource(User user, List<CourseData> courseDataList){
-        // 处理权限
-        if (user != null){
-            for(CourseData courseData : courseDataList){
-                if (!userOrderRecordService.hasData(user.getId(), courseData.getId())){
-                    courseData.setResource(courseData.getTrailResource());
-                }
-            }
-        }else{
-            for(CourseData courseData : courseDataList){
-                courseData.setResource(courseData.getTrailResource());
-            }
-        }
+    private UserExportService userExportService;
+
+    public UserExport addQuestion(Integer userId, JSONObject setting){
+        UserExport export = UserExport.builder()
+                .userId(userId)
+                .type(ExportType.QUESTION.key)
+                .setting(setting)
+                .build();
+        return userExportService.add(export);
+    }
+
+    public UserExport addQuestionNote(Integer userId, JSONObject setting){
+        UserExport export = UserExport.builder()
+                .userId(userId)
+                .type(ExportType.NOTE_QUESTION.key)
+                .setting(setting)
+                .build();
+        return userExportService.add(export);
+    }
+
+    public UserExport addCourseNote(Integer userId, JSONObject setting){
+        UserExport export = UserExport.builder()
+                .userId(userId)
+                .type(ExportType.NOTE_COURSE.key)
+                .setting(setting)
+                .build();
+
+        return userExportService.add(export);
     }
 }

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

@@ -317,10 +317,21 @@ public class MessageExtendService {
     }
 
     /**
+     * 邮箱绑定
+     * @param user
+     */
+    public void sendEmailBind(User user){
+        Map<String, String> map = new HashMap<>();
+        map.put("nickname", user.getNickname());
+        map.put("email", user.getEmail());
+        send(user, MessageCategory.EMAIL_CHANGE, map);
+    }
+
+    /**
      * 邮箱变更
      * @param user
      */
-    public void sendEmailChange(User user){
+    public void sendEmailChange(User user, String email){
         Map<String, String> map = new HashMap<>();
         map.put("nickname", user.getNickname());
         map.put("email", user.getEmail());

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

@@ -61,7 +61,7 @@ public class UserAskCourseService extends AbstractService {
         return p;
     }
 
-    public List<UserAskCourse> listByCoursePosition(Number courseNoId, String position, Boolean showStatus){
+    public List<UserAskCourse> listByCoursePosition(Number courseNoId, Integer position, Boolean showStatus){
         Example example = new Example(UserAskCourse.class);
         example.and(
                 example.createCriteria()
@@ -80,6 +80,83 @@ public class UserAskCourseService extends AbstractService {
         return select(userAskCourseMapper, example);
     }
 
+    public Page<UserAskCourse> listByCourse(int page, int size, String keyword, Integer courseId, Integer courseNoId, Integer position, Boolean showStatus, String order, DirectionStatus direction){
+        Example example = new Example(UserAskCourse.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("courseId", courseId)
+        );
+        if(courseNoId != null)
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("courseNoId", courseNoId)
+            );
+        if(position != null)
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("position", position)
+            );
+        if (keyword != null)
+            example.and(
+                    example.createCriteria()
+                            .orLike("content", "%"+keyword+"%")
+                            .orLike("answer", "%"+keyword+"%")
+            );
+
+        if (showStatus != null)
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("showStatus", showStatus?1:0)
+                            .andEqualTo("answerStatus", 1)
+
+            );
+        if(order == null || order.isEmpty()) order = "sort";
+        switch(direction){
+            case ASC:
+                example.orderBy(order).asc();
+                break;
+            case DESC:
+            default:
+                example.orderBy(order).desc();
+        }
+        return select(userAskCourseMapper, example, page, size);
+    }
+
+    public Page<UserAskCourse> listByUser(int page, int size, String keyword, Integer userId, Integer courseId, Integer courseNoId, AskStatus askStatus, String order, DirectionStatus direction){
+        Example example = new Example(UserAskCourse.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andEqualTo("courseId", courseId)
+        );
+        if(courseNoId != null)
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("courseNoId", courseNoId)
+            );
+        if (askStatus != null)
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("answerStatus", askStatus.index)
+            );
+        if (keyword != null)
+            example.and(
+                    example.createCriteria()
+                            .orLike("content", "%"+keyword+"%")
+                            .orLike("answer", "%"+keyword+"%")
+            );
+        if(order == null || order.isEmpty()) order = "id";
+        switch(direction){
+            case ASC:
+                example.orderBy(order).asc();
+                break;
+            case DESC:
+            default:
+                example.orderBy(order).desc();
+        }
+        return select(userAskCourseMapper, example, page, size);
+    }
+
     public List<UserAskCourse> listByRecord(Number recordId){
         Example example = new Example(UserAskCourse.class);
         example.and(