Browse Source

feat(server): 评论、服务、faq

Go 5 years ago
parent
commit
da96a81c6a
56 changed files with 2375 additions and 139 deletions
  1. 6 0
      front/project/Constant.js
  2. 7 4
      front/project/admin/routes/setting/comment/page.js
  3. 79 21
      front/project/admin/routes/setting/faq/page.js
  4. 2 1
      front/project/admin/routes/setting/index.js
  5. 1 1
      front/project/admin/routes/setting/index/page.js
  6. 3 5
      front/project/admin/routes/setting/message/page.js
  7. 15 0
      front/project/admin/routes/setting/promote/index.js
  8. 10 0
      front/project/admin/routes/setting/promote/index.less
  9. 286 0
      front/project/admin/routes/setting/promote/page.js
  10. 3 2
      front/project/admin/routes/setting/service/page.js
  11. 1 1
      front/project/admin/routes/user/ask/page.js
  12. 1 1
      front/project/admin/routes/user/feedback/page.js
  13. 2 1
      front/project/admin/routes/user/index.js
  14. 15 0
      front/project/admin/routes/user/service/index.js
  15. 3 0
      front/project/admin/routes/user/service/index.less
  16. 225 0
      front/project/admin/routes/user/service/page.js
  17. 8 0
      front/project/admin/stores/system.js
  18. 21 1
      front/project/admin/stores/user.js
  19. 0 1
      front/project/www/routes/sentence/read/page.js
  20. 2 13
      front/src/components/FileUpload/index.js
  21. 13 13
      front/src/layouts/FormLayout/index.js
  22. 1 1
      front/src/services/Tools.js
  23. 4 1
      server/data/src/main/java/com/qxgmat/data/constants/enums/ServiceKey.java
  24. 25 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/status/MessageStatus.java
  25. 1 1
      server/data/src/main/java/com/qxgmat/data/constants/enums/user/AskTarget.java
  26. 20 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/user/ServiceSource.java
  27. 7 0
      server/data/src/main/java/com/qxgmat/data/dao/UserServiceRecordMapper.java
  28. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/Comment.java
  29. 210 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/Faq.java
  30. 237 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/Message.java
  31. 51 16
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserCourseProgress.java
  32. 335 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserServiceRecord.java
  33. 3 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/CommentMapper.xml
  34. 9 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/FaqMapper.xml
  35. 24 0
      server/data/src/main/java/com/qxgmat/data/dao/mapping/MessageMapper.xml
  36. 3 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserCourseProgressMapper.xml
  37. 25 0
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserServiceRecordMapper.xml
  38. 1 1
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/CourseController.java
  39. 1 1
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/QuestionController.java
  40. 71 9
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java
  41. 78 6
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java
  42. 1 1
      server/gateway-api/src/main/java/com/qxgmat/controller/api/AuthController.java
  43. 26 6
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/extend/UserExtendDto.java
  44. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/FaqDto.java
  45. 99 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserServiceRecordDto.java
  46. 122 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/CommentDto.java
  47. 0 8
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserFeedbackErrorDetailDto.java
  48. 1 1
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserFeedbackErrorListDto.java
  49. 19 9
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserListDto.java
  50. 100 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserServiceRecordInfoDto.java
  51. 6 5
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserServiceService.java
  52. 0 1
      server/gateway-api/src/main/java/com/qxgmat/service/UsersService.java
  53. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/TradeService.java
  54. 17 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/MessageService.java
  55. 93 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserServiceRecordService.java
  56. 26 1
      server/gateway-api/src/main/java/com/qxgmat/task/ScheduledTask.java

+ 6 - 0
front/project/Constant.js

@@ -18,6 +18,12 @@ export const PreviewMode = [{ label: '指定作业', value: 0 }, { label: '系
 
 export const ServiceKey = [{ label: 'VIP', value: 'vip' }, { label: '机经', value: 'textbook' }, { label: '千行CAT', value: 'qx_cat' }];
 
+export const ServiceParamMap = {
+  vip: [{ label: '1个月', value: '1month' }, { label: '3个月', value: '3month' }, { label: '6个月', value: '6month' }],
+};
+
+export const ServiceSource = [{ label: '线下支付', value: 'offline' }, { label: '线上支付', value: 'online' }, { label: '实名认证', value: 'real' }, { label: '邀请好友', value: 'invite' }];
+
 export const SwitchSelect = [{ value: 0, label: '否' }, { value: 1, label: '是' }];
 
 export const AskStatus = [{ value: 0, label: '新增' }, { value: 1, label: '已回答' }, { value: 2, label: '忽略' }];

+ 7 - 4
front/project/admin/routes/setting/comment/page.js

@@ -43,9 +43,11 @@ export default class extends Page {
       name: '学员昵称',
     }, {
       key: 'avatar',
-      type: 'upload',
+      type: 'image',
       name: '学员头像',
-      image: true,
+      onUpload: ({ file }) => {
+        return System.uploadImage(file).then(url => { return { url }; });
+      },
     }, {
       key: 'content',
       type: 'textarea',
@@ -74,10 +76,11 @@ export default class extends Page {
       number: true,
       placeholder: '请输入',
     }, {
-      key: '精选',
+      key: 'isSpecial',
       type: 'select',
       allowClear: true,
-      name: '展示状态',
+      name: '精选',
+      number: true,
       select: SwitchSelect,
     }];
     this.columns = [

+ 79 - 21
front/project/admin/routes/setting/faq/page.js

@@ -7,12 +7,13 @@ import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
 import { getMap, bindSearch, formatDate } from '@src/services/Tools';
 import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
-import { ChannelModule, SwitchSelect } from '../../../../Constant';
+import { ChannelModule, SwitchSelect, AskStatus } from '../../../../Constant';
 import { System } from '../../../stores/system';
 import { User } from '../../../stores/user';
 
 const SwitchSelectMap = getMap(SwitchSelect, 'value', 'label');
 const ChannelModuleMap = getMap(ChannelModule, 'value', 'label');
+const AskStatusMap = getMap(AskStatus, 'value', 'label');
 export default class extends Page {
   init() {
     this.actionList = [{
@@ -20,6 +21,7 @@ export default class extends Page {
       type: 'primary',
       name: '创建',
     }];
+    this.formF = null;
     this.itemList = [{
       key: 'id',
       type: 'hidden',
@@ -38,18 +40,35 @@ export default class extends Page {
       select: ChannelModule,
       placeholder: '请选择',
     }, {
-      key: 'nickname',
-      type: 'input',
-      name: '学员昵称',
+      key: 'content',
+      type: 'textarea',
+      name: '用户留言',
     }, {
-      key: 'avatar',
-      type: 'upload',
-      name: '学员头像',
-      image: true,
+      key: 'answer',
+      type: 'textarea',
+      name: '编辑回复',
+    }];
+    this.answerList = [{
+      key: 'id',
+      type: 'hidden',
     }, {
       key: 'content',
       type: 'textarea',
-      name: '评价内容',
+      name: '用户留言',
+    }, {
+      key: 'answer',
+      type: 'textarea',
+      name: '编辑回复',
+    }, {
+      key: 'isSpecial',
+      type: 'switch',
+      name: '是否精选',
+      select: SwitchSelect,
+    }, {
+      key: 'sendMail',
+      type: 'switch',
+      name: '发送邮件',
+      select: SwitchSelect,
     }];
     this.filterForm = [{
       key: 'channel',
@@ -74,10 +93,18 @@ export default class extends Page {
       number: true,
       placeholder: '请输入',
     }, {
-      key: '精选',
+      key: 'status',
       type: 'select',
       allowClear: true,
-      name: '展示状态',
+      number: true,
+      name: '状态',
+      select: AskStatus,
+    }, {
+      key: 'isSpecial',
+      type: 'select',
+      allowClear: true,
+      name: '精选',
+      number: true,
       select: SwitchSelect,
     }];
     this.columns = [
@@ -93,37 +120,51 @@ export default class extends Page {
         dataIndex: 'position',
       },
       {
-        title: '内容',
-        dataIndex: 'content',
+        title: '提问时间',
+        dataIndex: 'createTime',
+        render: (text) => {
+          return formatDate(text);
+        },
       },
       {
-        title: '用户',
+        title: '提问者',
         dataIndex: 'user',
         render: (text, record) => {
-          return text ? text.nickname : record.nickname;
+          if (record.isSystem) return '系统创建';
+          if (!record.userId) return '未注册';
+          return text ? text.nickname : '';
         },
+      },
+      {
+        title: '问题摘要',
+        dataIndex: 'content',
       }, {
-        title: '时间',
-        dataIndex: 'createTime',
+        title: '回答状态',
+        dataIndex: 'status',
         render: (text) => {
-          return formatDate(text);
+          return AskStatusMap[text] || '';
         },
       }, {
         title: '精选',
         dataIndex: 'isSpecial',
-        render: (text) => {
-          return SwitchSelectMap[text] || text;
+        render: (text, record) => {
+          return record.status > 0 ? SwitchSelectMap[text] || text : '-';
         },
       }, {
         title: '操作',
         dataIndex: 'handler',
         render: (text, record) => {
           return <div className="table-button">
-            {(
+            {!!record.isSystem && (
               <a onClick={() => {
                 this.editAction(record);
               }}>编辑</a>
             )}
+            {!record.isSystem && record.status === 0 && (
+              <a onClick={() => {
+                this.answerAction(record);
+              }}>回复</a>
+            )}
             {!!record.isSpecial && (
               <a onClick={() => {
                 this.special(record, 0);
@@ -160,6 +201,8 @@ export default class extends Page {
         asyncSMessage('添加成功!');
         this.refresh();
       });
+    }).then(component => {
+      this.formF = component;
     });
   }
 
@@ -169,6 +212,21 @@ export default class extends Page {
         asyncSMessage('编辑成功!');
         this.refresh();
       });
+    }).then(component => {
+      this.formF = component;
+    });
+  }
+
+  answerAction(row) {
+    asyncForm('回复', this.answerList, row, data => {
+      data.isSpecial = data.isSpecial ? 1 : 0;
+      data.sendMail = data.sendMail ? 1 : 0;
+      return System.editFAQ(data).then(() => {
+        asyncSMessage('回复成功!');
+        this.refresh();
+      });
+    }).then(component => {
+      this.formF = component;
     });
   }
 

+ 2 - 1
front/project/admin/routes/setting/index.js

@@ -7,5 +7,6 @@ import time from './time';
 import comment from './comment';
 import faq from './faq';
 import message from './message';
+import promote from './promote';
 
-export default [struct, tips, service, index, place, time, comment, faq, message];
+export default [struct, tips, service, index, place, time, comment, faq, message, promote];

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

@@ -29,7 +29,7 @@ export default class extends Page {
     let { data } = this.state;
     data = data || {};
     data[field] = data[field] || [];
-    data[field].splite(start, length);
+    data[field].splice(start, length);
     this.setState({ data });
   }
 

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

@@ -81,7 +81,9 @@ export default class extends Page {
   }
 
   refreshTemplate() {
+    const { form } = this.props;
     return System.getMessageTemplate().then(result => {
+      form.setFieldsValue(result);
       this.setState({ template: result || {} });
     });
   }
@@ -93,13 +95,9 @@ export default class extends Page {
   }
 
   submit(tab) {
-    let handler;
     if (tab === 'template') {
-      handler = this.submitTemplate();
+      this.submitTemplate();
     }
-    handler.then(() => {
-      asyncSMessage('保存成功');
-    });
   }
 
   submitTemplate() {

+ 15 - 0
front/project/admin/routes/setting/promote/index.js

@@ -0,0 +1,15 @@
+import module from '../../module';
+import group from '../group';
+
+export default {
+  path: '/setting/promote',
+  key: 'setting-promote',
+  title: '促销管理',
+  needLogin: true,
+  module,
+  group,
+  index: true,
+  component() {
+    return import('./page');
+  },
+};

+ 10 - 0
front/project/admin/routes/setting/promote/index.less

@@ -0,0 +1,10 @@
+@charset "utf-8";
+
+#setting-promote {
+  .ant-col-1 {
+    margin-bottom: 24px;
+    line-height: 40px;
+    height: 40px;
+    margin-left: 5px;
+  }
+}

+ 286 - 0
front/project/admin/routes/setting/promote/page.js

@@ -0,0 +1,286 @@
+import React from 'react';
+import { Form, Row, Col, Input, Button, Icon } from 'antd';
+import './index.less';
+import Page from '@src/containers/Page';
+import Block from '@src/components/Block';
+import { flattenObject, formatFormError } from '@src/services/Tools';
+import { asyncSMessage } from '@src/services/AsyncTools';
+import { System } from '../../../stores/system';
+
+export default class extends Page {
+  initData() {
+    System.getCoursePromote().then(result => {
+      const { form } = this.props;
+      (result.video_list || []).forEach((row, index) => {
+        form.getFieldDecorator(`video_list[${index}].number`);
+        form.getFieldDecorator(`video_list[${index}].percent`);
+      });
+      (result['1v1_list'] || []).forEach((row, index) => {
+        form.getFieldDecorator(`1v1_list[${index}].number`);
+        form.getFieldDecorator(`1v1_list[${index}].percent`);
+      });
+      (result.gifts || []).forEach((row, index) => {
+        form.getFieldDecorator(`gifts[${index}].money`);
+        form.getFieldDecorator(`gifts[${index}].hour`);
+      });
+      form.setFieldsValue(flattenObject(result));
+      this.setState({ load: true, data: result });
+    });
+  }
+
+  addLength(field, info) {
+    let { data } = this.state;
+    data = data || {};
+    data[field] = data[field] || [];
+    data[field].push(info);
+    this.setState({ data });
+  }
+
+  deleteLength(field, start, length) {
+    let { data } = this.state;
+    data = data || {};
+    data[field] = data[field] || [];
+    data[field].splice(start, length);
+    this.setState({ data });
+  }
+
+  changeMapValue(field, index, key, value) {
+    const { data } = this.state;
+    data[field] = data[field] || {};
+    data[field][index] = data[field][index] || {};
+    data[field][index][key] = value;
+    this.setState({ data });
+  }
+
+  changeValue(field, key, value) {
+    const { data } = this.state;
+    data[field] = data[field] || {};
+    data[field][key] = value;
+    this.setState({ data });
+  }
+
+  submit() {
+    const { form } = this.props;
+    form.validateFields((err) => {
+      if (!err) {
+        const data = form.getFieldsValue();
+        data.video_list = (data.video_list || []).map(row => {
+          return { number: Number(row.number), percent: Number(row.percent) };
+        });
+        data['1v1_list'] = (data['1v1_list'] || []).map(row => {
+          return { number: Number(row.number), percent: Number(row.percent) };
+        });
+        data.gifts = (data.gifts || []).map(row => {
+          return { money: Number(row.money), hour: Number(row.hour) };
+        });
+        System.setCoursePromote(data)
+          .then(() => {
+            this.setState(data);
+            asyncSMessage('保存成功');
+          }).catch((e) => {
+            form.setFields(formatFormError(data, e.result));
+          });
+      }
+    });
+  }
+
+  renderVideo() {
+    const { getFieldDecorator } = this.props.form;
+    const { data } = this.state;
+    const videos = data.video_list || [];
+    return <Block>
+      <h1>视频课折扣</h1>
+      <Form>
+        <Form.Item label='按购买课程数量计算' />
+        {videos.map((row, index) => {
+          return <Row>
+            <Col span={2}>
+              <Form.Item>
+                {getFieldDecorator(`video_list[${index}].number`, {
+                  rules: [
+                    { required: true, message: '输入数量' },
+                  ],
+                })(
+                  <Input placeholder={'输入数量'} onChange={(value) => {
+                    this.changeMapValue('video_list', index, 'number', value);
+                  }} />,
+                )}
+              </Form.Item>
+            </Col>
+            <Col span={1}>
+              门
+            </Col>
+            <Col span={2}>
+              <Form.Item>
+                {getFieldDecorator(`video_list[${index}].percent`, {
+                  rules: [
+                    { required: true, message: '输入百分比' },
+                  ],
+                })(
+                  <Input placeholder={'输入百分比'} onChange={(value) => {
+                    this.changeMapValue('video_list', index, 'percent', value);
+                  }} />,
+                )}
+              </Form.Item>
+            </Col>
+            <Col span={1}>
+              %
+            </Col>
+            <Col span={1} onClick={() => {
+              this.deleteLength('video_list', index, 1);
+            }}>
+              <Button><Icon type="minus" /></Button>
+            </Col>
+          </Row>;
+        })}
+        <Button onClick={() => {
+          this.addLength('video_list', { number: 0, precent: 0 });
+        }}><Icon type={'plus'} /></Button>
+        <Form.Item labelCol={{ span: 3 }} wrapperCol={{ span: 16 }} label='促销文案'>
+          {getFieldDecorator('video.text', {
+            rules: [
+              { required: true, message: '输入促销文案' },
+            ],
+          })(
+            <Input placeholder='2门9折,3门88折,4门85折(套餐不享受此优惠)' onChange={(value) => {
+              this.changeValue('video', 'text', value);
+            }} />,
+          )}
+        </Form.Item>
+      </Form>
+    </Block>;
+  }
+
+  render1vs1() {
+    const { getFieldDecorator } = this.props.form;
+    const { data } = this.state;
+    const vs = data['1v1_list'] || [];
+    return <Block>
+      <h1>1v1课折扣</h1>
+      <Form>
+        <Form.Item label='按购买课程数量计算' />
+        {vs.map((row, index) => {
+          return <Row>
+            <Col span={2}>
+              <Form.Item>
+                {getFieldDecorator(`1v1_list[${index}].number`, {
+                  rules: [
+                    { required: true, message: '输入数量' },
+                  ],
+                })(
+                  <Input placeholder={'输入数量'} onChange={(value) => {
+                    this.changeMapValue('1v1_list', index, 'number', value);
+                  }} />,
+                )}
+              </Form.Item>
+            </Col>
+            <Col span={1}>
+              课
+          </Col>
+            <Col span={2}>
+              <Form.Item>
+                {getFieldDecorator(`1v1_list[${index}].percent`, {
+                  rules: [
+                    { required: true, message: '输入百分比' },
+                  ],
+                })(
+                  <Input placeholder={'输入百分比'} onChange={(value) => {
+                    this.changeMapValue('1v1_list', index, 'percent', value);
+                  }} />,
+                )}
+              </Form.Item>
+            </Col>
+            <Col span={1}>
+              %
+          </Col>
+            <Col span={1} onClick={() => {
+              this.deleteLength('1v1_list', index, 1);
+            }}>
+              <Button><Icon type="minus" /></Button>
+            </Col>
+          </Row>;
+        })}
+        <Button onClick={() => {
+          this.addLength('1v1_list', { number: 0, precent: 0 });
+        }}><Icon type={'plus'} /></Button>
+        <Form.Item labelCol={{ span: 3 }} wrapperCol={{ span: 16 }} label='促销文案'>
+          {getFieldDecorator('1v1.text', {
+            rules: [
+              { required: true, message: '输入促销文案' },
+            ],
+          })(
+            <Input placeholder='满30课时95折' onChange={(value) => {
+              this.changeValue('1v1', 'text', value);
+            }} />,
+          )}
+        </Form.Item>
+      </Form>
+    </Block>;
+  }
+
+  renderGift() {
+    const { getFieldDecorator } = this.props.form;
+    const { data } = this.state;
+    const gifts = data.gifts || [];
+    return <Block>
+      <h1>课程赠品</h1>
+      <Form>
+        <Form.Item label='满额送回复时长服务' />
+        {gifts.map((row, index) => {
+          return <Row>
+            <Col span={6}>
+              <Form.Item labelCol={{ span: 10 }} wrapperCol={{ span: 14 }} label='实付金额'>
+                {getFieldDecorator(`gifts[${index}].money`, {
+                  rules: [
+                    { required: true, message: '输入金额' },
+                  ],
+                })(
+                  <Input placeholder={'输入金额'} onChange={(value) => {
+                    this.changeMapValue('gifts', index, 'money', value);
+                  }} />,
+                )}
+              </Form.Item>
+            </Col>
+            <Col span={6}>
+              <Form.Item labelCol={{ span: 10 }} wrapperCol={{ span: 14 }} label='赠送'>
+                {getFieldDecorator(`gifts[${index}].hour`, {
+                  rules: [
+                    { required: true, message: '输入小时' },
+                  ],
+                })(
+                  <Input placeholder={'输入小时'} onChange={(value) => {
+                    this.changeMapValue('gifts', index, 'hour', value);
+                  }} />,
+                )}
+              </Form.Item>
+            </Col>
+            <Col span={1} onClick={() => {
+              this.deleteLength('gifts', index, 1);
+            }}>
+              <Button><Icon type="minus" /></Button>
+            </Col>
+          </Row>;
+        })}
+        <Button onClick={() => {
+          this.addLength('gifts', { money: 0, hour: 0 });
+        }}><Icon type={'plus'} /></Button>
+      </Form>
+    </Block>;
+  }
+
+  renderView() {
+    const { tab } = this.state;
+    return <div>
+      {this.renderVideo()}
+      {this.render1vs1()}
+      {this.renderGift()}
+      <Row type="flex" justify="center">
+        <Col>
+          <Button type="primary" onClick={() => {
+            this.submit(tab);
+          }}>保存</Button>
+        </Col>
+      </Row>
+    </div>;
+  }
+}

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

@@ -5,13 +5,14 @@ import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
 import { flattenObject } from '@src/services/Tools';
 import { asyncSMessage } from '@src/services/AsyncTools';
+import { SercieParamMap } from '../../../../Constant';
 import { System } from '../../../stores/system';
 
 export default class extends Page {
   constructor(props) {
     super(props);
     this.state.tab = 'qx_cat';
-    this.vipList = ['1个月', '3个月', '6个月'];
+    this.vipList = SercieParamMap.vip;
   }
 
   initData() {
@@ -316,7 +317,7 @@ export default class extends Page {
       <Row>
         {this.vipList.map((row, index) => {
           return <Col span={12}>
-            <h1>{row}</h1>
+            <h1>{row.label}</h1>
             <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='商品价格'>
               {getFieldDecorator(`vip[${index}].price`, {
                 rules: [

+ 1 - 1
front/project/admin/routes/user/ask/page.js

@@ -142,7 +142,7 @@ export default class extends Page {
         title: row.title,
         value: row.id,
       };
-    }, this.state.search.questionNoId ? Number(this.state.search.questionNoId) : [], null);
+    }, this.state.search.questionNoId ? Number(this.state.search.questionNoId) : null, null);
   }
 
   initData() {

+ 1 - 1
front/project/admin/routes/user/feedback/page.js

@@ -37,7 +37,7 @@ export default class extends Page {
       key: 'status',
       type: 'select',
       allowClear: true,
-      name: '状态',
+      name: '处理状态',
       select: FeedbackStatus,
     }, {
       key: 'title',

+ 2 - 1
front/project/admin/routes/user/index.js

@@ -8,5 +8,6 @@ import examination from './examination';
 import student from './student';
 import pay from './pay';
 import feedback from './feedback';
+import service from './service';
 
-export default [list, detail, ask, askDetail, preview, exercise, examination, student, pay, feedback];
+export default [list, detail, ask, askDetail, preview, exercise, examination, student, pay, feedback, service];

+ 15 - 0
front/project/admin/routes/user/service/index.js

@@ -0,0 +1,15 @@
+import module from '../../module';
+import group from '../group';
+
+export default {
+  path: '/user/service',
+  key: 'user-service',
+  title: '服务管理',
+  needLogin: true,
+  module,
+  group,
+  index: true,
+  component() {
+    return import('./page');
+  },
+};

+ 3 - 0
front/project/admin/routes/user/service/index.less

@@ -0,0 +1,3 @@
+@charset "utf-8";
+
+#user-service {}

+ 225 - 0
front/project/admin/routes/user/service/page.js

@@ -0,0 +1,225 @@
+import React from 'react';
+import './index.less';
+import Page from '@src/containers/Page';
+import Block from '@src/components/Block';
+import FilterLayout from '@src/layouts/FilterLayout';
+import ActionLayout from '@src/layouts/ActionLayout';
+import TableLayout from '@src/layouts/TableLayout';
+import { getMap, formatDate, formatMoney, bindSearch } from '@src/services/Tools';
+import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
+import { ServiceParamMap, ServiceKey, ServiceSource } from '../../../../Constant';
+import { User } from '../../../stores/user';
+
+const ServiceKeyMap = getMap(ServiceKey, 'value', 'label');
+const ServiceSourceMap = getMap(ServiceSource, 'value', 'label');
+const ServiceParamList = getMap(Object.keys(ServiceParamMap).map(key => {
+  return { list: ServiceParamMap[key], key };
+}), 'key', 'list');
+const ServiceParamRelation = getMap(Object.keys(ServiceParamMap).map(key => {
+  return { map: getMap(ServiceParamMap[key], 'value', 'label'), key };
+}), 'key', 'map');
+export default class extends Page {
+  init() {
+    this.timeout = null;
+    this.mobile = null;
+    this.actionList = [{
+      key: 'add',
+      type: 'primary',
+      name: '创建',
+    }];
+    this.formF = null;
+    this.itemList = [{
+      key: 'id',
+      type: 'hidden',
+    }, {
+      key: 'mobile',
+      type: 'input',
+      name: '手机号',
+      placeholder: '请输入',
+      option: {
+        normalize: (value) => {
+          if (this.mobile === value) return value;
+          if (this.timeout) {
+            clearTimeout(this.timeout);
+            this.timeout = null;
+          }
+          this.timeout = setTimeout(() => {
+            User.validMobile({ mobile: value }).then(result => {
+              this.mobile = value;
+              this.itemList[1].suffix = result ? '已注册' : '未注册';
+              this.formF.setFieldsValue({ load: true });
+            });
+          }, 1500);
+          return value;
+        },
+      },
+    }, {
+      key: 'service',
+      type: 'select',
+      name: '开通服务',
+      select: ServiceKey,
+      placeholder: '请选择',
+      onChange: (value) => {
+        this.itemList[3].select = ServiceParamList[value] || [];
+        this.formF.setFieldsValue({ param: '' });
+      },
+    }, {
+      key: 'param',
+      type: 'select',
+      name: '服务参数',
+      select: [],
+    }, {
+      key: 'source',
+      type: 'select',
+      name: '开通方式',
+      select: ServiceSource,
+      placeholder: '请选择',
+    }];
+    this.filterForm = [{
+      key: 'userId',
+      type: 'select',
+      allowClear: true,
+      name: '用户',
+      select: [],
+      number: true,
+      placeholder: '请输入',
+    }, {
+      key: 'service',
+      type: 'select',
+      allowClear: true,
+      name: '服务',
+      select: ServiceKey,
+      onChange: (value) => {
+        this.filterForm[2].select = ServiceParamList[value] || [];
+      },
+    }, {
+      key: 'param',
+      type: 'select',
+      name: '参数',
+      select: [],
+      allowClear: true,
+      notFound: null,
+    }];
+    this.columns = [
+      {
+        title: '用户ID',
+        dataIndex: 'userId',
+      },
+      {
+        title: '用户手机',
+        dataIndex: 'user.mobile',
+      },
+      {
+        title: '用户姓名',
+        dataIndex: 'user.nickname',
+      },
+      {
+        title: '开通服务',
+        dataIndex: 'service',
+        render: (text) => {
+          return ServiceKeyMap[text];
+        },
+      }, {
+        title: '服务参数',
+        dataIndex: 'param',
+        render: (text, record) => {
+          return (ServiceParamRelation[record.service] || {})[text] || '';
+        },
+      }, {
+        title: '开通时间',
+        dataIndex: 'time',
+        render: (text, record) => {
+          return `${record.startTime ? formatDate(record.startTime) : ''} - ${record.endTime ? formatDate(record.endTime) : ''}`;
+        },
+      }, {
+        title: '开通方式',
+        dataIndex: 'source',
+        render: (text) => {
+          return ServiceSourceMap[text] || '';
+        },
+      }, {
+        title: '累计消费金额',
+        dataIndex: 'user.totalMoney',
+        render: (text) => {
+          return formatMoney(text);
+        },
+      }, {
+        title: '操作',
+        dataIndex: 'handler',
+        render: (text, record) => {
+          return <div className="table-button">
+            {!record.isUsed && (
+              <a onClick={() => {
+                this.editAction(record);
+              }}>编辑</a>
+            )}
+          </div>;
+        },
+      },
+    ];
+    bindSearch(this.filterForm, 'userId', this, (search) => {
+      return User.list(search);
+    }, (row) => {
+      return {
+        title: `${row.nickname}(${row.mobile})`,
+        value: row.id,
+      };
+    }, this.state.search.userId ? Number(this.state.search.userId) : [], null);
+  }
+
+  initData() {
+    User.listService(this.state.search).then(result => {
+      this.setTableData(result.list, result.total);
+    });
+  }
+
+  addAction() {
+    this.itemList[1].disabled = false;
+    asyncForm('新建', this.itemList, {}, data => {
+      return User.addService(data).then(() => {
+        asyncSMessage('添加成功!');
+        this.refresh();
+      });
+    }).then(component => {
+      this.formF = component;
+    });
+  }
+
+  editAction(row) {
+    this.itemList[1].disabled = true;
+    asyncForm('编辑', this.itemList, row, data => {
+      return User.editService(data).then(() => {
+        asyncSMessage('编辑成功!');
+        this.refresh();
+      });
+    }).then(component => {
+      this.formF = component;
+    });
+  }
+
+  renderView() {
+    return <Block flex>
+      <FilterLayout
+        show
+        itemList={this.filterForm}
+        data={this.state.search}
+        onChange={data => {
+          this.search(data);
+        }} />
+      <ActionLayout
+        itemList={this.actionList}
+        selectedKeys={this.state.selectedKeys}
+        onAction={key => this.onAction(key)}
+      />
+      <TableLayout
+        columns={this.columns}
+        list={this.state.list}
+        pagination={this.state.page}
+        loading={this.props.core.loading}
+        onChange={(pagination, filters, sorter) => this.tableChange(pagination, filters, sorter)}
+        onSelect={(keys, rows) => this.tableSelect(keys, rows)}
+        selectedKeys={this.state.selectedKeys}
+      />
+    </Block>;
+  }
+}

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

@@ -133,6 +133,14 @@ export default class SystemStore extends BaseStore {
     return this.apiPut('/setting/course_index', params);
   }
 
+  getCoursePromote() {
+    return this.apiGet('/setting/course_promote');
+  }
+
+  setCoursePromote(params) {
+    return this.apiPut('/setting/course_promote', params);
+  }
+
   getTips() {
     return this.apiGet('/setting/tips');
   }

+ 21 - 1
front/project/admin/stores/user.js

@@ -41,8 +41,28 @@ export default class UserStore extends BaseStore {
     return this.apiGet('/user/feedback_error/detail', params);
   }
 
+  listService(params) {
+    return this.apiGet('/user/service/list', params);
+  }
+
+  addService(params) {
+    return this.apiPost('/user/service/add', params);
+  }
+
+  editService(params) {
+    return this.apiPut('/user/service/edit', params);
+  }
+
+  delService(params) {
+    return this.apiDel('/user/service/delete', params);
+  }
+
+  validMobile(params) {
+    return this.apiGet('/user/valid/mobile', params);
+  }
+
   listPreview(params) {
-    return this.apiGet('/preview/list', params);
+    return this.apiGet('/user/preview/list', params);
   }
 
   listPay(params) {

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

@@ -219,7 +219,6 @@ export default class extends Page {
     const { chapters = [] } = sentence;
     let page = 1;
     const code = !!sentence.code;
-    // todo 鼠标移上显示需要购买后访问
     const message = '购买后访问';
     return (
       <div className="layout-menu">

+ 2 - 13
front/src/components/FileUpload/index.js

@@ -2,7 +2,7 @@ import React, { Component } from 'react';
 import { Icon, Button } from 'antd';
 import './index.less';
 import Logo from '../Logo';
-import { search, uuid } from '../../services/Tools';
+import { search, generateUUID } from '../../services/Tools';
 
 /**
  * demo
@@ -29,7 +29,7 @@ class FileUpload extends Component {
         return;
       }
     }
-    const name = uuid();
+    const name = generateUUID();
     this.setState({ uploading: true });
     if (this.props.onStart) this.props.onStart(this.state.type === 'image' ? file.preview : file.name);
     this.props
@@ -123,15 +123,4 @@ class FileUpload extends Component {
   }
 }
 
-FileUpload.propTypes = {
-  src: React.PropTypes.string,
-  onProgress: React.PropTypes.function,
-  onError: React.PropTypes.function,
-  onEnd: React.PropTypes.function,
-  onStart: React.PropTypes.function,
-  onUpload: React.PropTypes.function,
-  type: React.PropTypes.string,
-  style: React.PropTypes.any,
-};
-
 export default FileUpload;

+ 13 - 13
front/src/layouts/FormLayout/index.js

@@ -5,6 +5,7 @@ import Select from '../../components/Select';
 import Multiple from '../../components/Multiple';
 import Radio from '../../components/Radio';
 import TreeSelect from '../../components/TreeSelect';
+import FileUpload from '../../components/FileUpload';
 import './index.less';
 
 const { TextArea } = Input;
@@ -112,19 +113,18 @@ class FormLayout extends Component {
         return <DatePicker {...item} className={item.class} placeholder={item.placeholder} disabled={!!item.disabled} />;
       case 'hidden':
         return <Input type="hidden" />;
-      // case 'image':
-      // case 'logo':
-      // case 'file':
-      //   return (
-      //     <FileUpload
-      //       {...item}
-      //       onUpload={this.props.onUpload}
-      //       title={item.name}
-      //       onError={err => {
-      //         this.props.form.setFields({ [item.key]: { value: '', errors: [err] } });
-      //       }}
-      //     />
-      //   );
+      case 'image':
+      case 'logo':
+      case 'file':
+        return (
+          <FileUpload
+            {...item}
+            title={item.name}
+            onError={err => {
+              this.props.form.setFields({ [item.key]: { value: '', errors: [err] } });
+            }}
+          />
+        );
       default:
         return <div />;
     }

+ 1 - 1
front/src/services/Tools.js

@@ -288,7 +288,7 @@ export function flattenObject(ob, prefix = '') {
   const toReturn = {};
   if (prefix) prefix = `${prefix}.`;
   Object.keys(ob).forEach(i => {
-    if (typeof ob[i] === 'object' && ob[i] !== null) {
+    if (typeof ob[i] === 'object' && ob[i] !== null && !ob[i].length) {
       const flatObject = flattenObject(ob[i]);
       Object.keys(flatObject).forEach(x => {
         toReturn[`${prefix}${i}.${x}`] = flatObject[x];

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

@@ -3,13 +3,16 @@ package com.qxgmat.data.constants.enums;
 public enum ServiceKey {
     VIP("vip"), // 收藏和错题处的组卷、导出;笔记导出功能;部分解析只有VIP可以看;下载模考报告; 解锁完整版模考报告;“提问开放”期间有提问权限
     TEXTBOOK("textbook"),
-    QX_CAT("qx_cat"); // 6个月内可以考2次
+    QX_CAT("qx_cat"), // 6个月内可以考2次
+
+    ;
     public String key;
     private ServiceKey(String key){
         this.key = key;
     }
 
     public static ServiceKey ValueOf(String name){
+        if (name == null || name.isEmpty()) return null;
         return ServiceKey.valueOf(name.toUpperCase());
     }
 }

+ 25 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/status/MessageStatus.java

@@ -0,0 +1,25 @@
+package com.qxgmat.data.constants.enums.status;
+
+
+public enum MessageStatus {
+    NEW(0), SENDING(1), SENDED(2);
+    final static public String message = "状态:0新增,1发送中,2发送完";
+
+    public int index;
+    private MessageStatus(int index){
+        this.index = index;
+    }
+    public static MessageStatus ValueOf(Integer index){
+        if (index == null) return null;
+        switch (index){
+            case 0:
+                return NEW;
+            case 1:
+                return SENDING;
+            case 2:
+                return SENDED;
+            default:
+                return null;
+        }
+    }
+}

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

@@ -14,7 +14,7 @@ public enum AskTarget {
     }
 
     public static AskTarget ValueOf(String name){
-        if (name == "") return null;
+        if (name == null || name.isEmpty()) return null;
         return AskTarget.valueOf(name.toUpperCase());
     }
 }

+ 20 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/user/ServiceSource.java

@@ -0,0 +1,20 @@
+package com.qxgmat.data.constants.enums.user;
+
+
+public enum ServiceSource {
+    OFFLINE("offline"),
+    ONLINE("online"),
+    REAL("real"),
+    INVITE("invite"),
+
+    ;
+    public String key;
+    private ServiceSource(String key){
+        this.key = key;
+    }
+
+    public static ServiceSource ValueOf(String name){
+        if (name == "") return null;
+        return ServiceSource.valueOf(name.toUpperCase());
+    }
+}

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

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

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

@@ -44,6 +44,12 @@ public class Comment implements Serializable {
     @Column(name = "`is_special`")
     private Integer isSpecial;
 
+    /**
+     * 是否系统创建
+     */
+    @Column(name = "`is_system`")
+    private Integer isSystem;
+
     @Column(name = "`create_time`")
     private Date createTime;
 
@@ -171,6 +177,24 @@ public class Comment implements Serializable {
     }
 
     /**
+     * 获取是否系统创建
+     *
+     * @return is_system - 是否系统创建
+     */
+    public Integer getIsSystem() {
+        return isSystem;
+    }
+
+    /**
+     * 设置是否系统创建
+     *
+     * @param isSystem 是否系统创建
+     */
+    public void setIsSystem(Integer isSystem) {
+        this.isSystem = isSystem;
+    }
+
+    /**
      * @return create_time
      */
     public Date getCreateTime() {
@@ -211,6 +235,7 @@ public class Comment implements Serializable {
         sb.append(", channel=").append(channel);
         sb.append(", position=").append(position);
         sb.append(", isSpecial=").append(isSpecial);
+        sb.append(", isSystem=").append(isSystem);
         sb.append(", createTime=").append(createTime);
         sb.append(", content=").append(content);
         sb.append("]");
@@ -295,6 +320,16 @@ public class Comment implements Serializable {
         }
 
         /**
+         * 设置是否系统创建
+         *
+         * @param isSystem 是否系统创建
+         */
+        public Builder isSystem(Integer isSystem) {
+            obj.setIsSystem(isSystem);
+            return this;
+        }
+
+        /**
          * @param createTime
          */
         public Builder createTime(Date createTime) {

+ 210 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/Faq.java

@@ -24,6 +24,18 @@ public class Faq implements Serializable {
     private String email;
 
     /**
+     * 提问者电话
+     */
+    @Column(name = "`phone`")
+    private String phone;
+
+    /**
+     * 回复站内信
+     */
+    @Column(name = "`message`")
+    private Integer message;
+
+    /**
      * 频道
      */
     @Column(name = "`channel`")
@@ -36,6 +48,12 @@ public class Faq implements Serializable {
     private String position;
 
     /**
+     * 处理人
+     */
+    @Column(name = "`manager_id`")
+    private Integer managerId;
+
+    /**
      * 是否精选:0不是精选,1是精选
      */
     @Column(name = "`is_special`")
@@ -47,6 +65,18 @@ public class Faq implements Serializable {
     @Column(name = "`status`")
     private Integer status;
 
+    /**
+     * 回复时间
+     */
+    @Column(name = "`answer_time`")
+    private Date answerTime;
+
+    /**
+     * 是否系统创建
+     */
+    @Column(name = "`is_system`")
+    private Integer isSystem;
+
     @Column(name = "`create_time`")
     private Date createTime;
 
@@ -56,6 +86,12 @@ public class Faq implements Serializable {
     @Column(name = "`content`")
     private String content;
 
+    /**
+     * 回复内容
+     */
+    @Column(name = "`answer`")
+    private String answer;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -109,6 +145,42 @@ public class Faq implements Serializable {
     }
 
     /**
+     * 获取提问者电话
+     *
+     * @return phone - 提问者电话
+     */
+    public String getPhone() {
+        return phone;
+    }
+
+    /**
+     * 设置提问者电话
+     *
+     * @param phone 提问者电话
+     */
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    /**
+     * 获取回复站内信
+     *
+     * @return message - 回复站内信
+     */
+    public Integer getMessage() {
+        return message;
+    }
+
+    /**
+     * 设置回复站内信
+     *
+     * @param message 回复站内信
+     */
+    public void setMessage(Integer message) {
+        this.message = message;
+    }
+
+    /**
      * 获取频道
      *
      * @return channel - 频道
@@ -145,6 +217,24 @@ public class Faq implements Serializable {
     }
 
     /**
+     * 获取处理人
+     *
+     * @return manager_id - 处理人
+     */
+    public Integer getManagerId() {
+        return managerId;
+    }
+
+    /**
+     * 设置处理人
+     *
+     * @param managerId 处理人
+     */
+    public void setManagerId(Integer managerId) {
+        this.managerId = managerId;
+    }
+
+    /**
      * 获取是否精选:0不是精选,1是精选
      *
      * @return is_special - 是否精选:0不是精选,1是精选
@@ -181,6 +271,42 @@ public class Faq implements Serializable {
     }
 
     /**
+     * 获取回复时间
+     *
+     * @return answer_time - 回复时间
+     */
+    public Date getAnswerTime() {
+        return answerTime;
+    }
+
+    /**
+     * 设置回复时间
+     *
+     * @param answerTime 回复时间
+     */
+    public void setAnswerTime(Date answerTime) {
+        this.answerTime = answerTime;
+    }
+
+    /**
+     * 获取是否系统创建
+     *
+     * @return is_system - 是否系统创建
+     */
+    public Integer getIsSystem() {
+        return isSystem;
+    }
+
+    /**
+     * 设置是否系统创建
+     *
+     * @param isSystem 是否系统创建
+     */
+    public void setIsSystem(Integer isSystem) {
+        this.isSystem = isSystem;
+    }
+
+    /**
      * @return create_time
      */
     public Date getCreateTime() {
@@ -212,6 +338,24 @@ public class Faq implements Serializable {
         this.content = content;
     }
 
+    /**
+     * 获取回复内容
+     *
+     * @return answer - 回复内容
+     */
+    public String getAnswer() {
+        return answer;
+    }
+
+    /**
+     * 设置回复内容
+     *
+     * @param answer 回复内容
+     */
+    public void setAnswer(String answer) {
+        this.answer = answer;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -221,12 +365,18 @@ public class Faq implements Serializable {
         sb.append(", id=").append(id);
         sb.append(", userId=").append(userId);
         sb.append(", email=").append(email);
+        sb.append(", phone=").append(phone);
+        sb.append(", message=").append(message);
         sb.append(", channel=").append(channel);
         sb.append(", position=").append(position);
+        sb.append(", managerId=").append(managerId);
         sb.append(", isSpecial=").append(isSpecial);
         sb.append(", status=").append(status);
+        sb.append(", answerTime=").append(answerTime);
+        sb.append(", isSystem=").append(isSystem);
         sb.append(", createTime=").append(createTime);
         sb.append(", content=").append(content);
+        sb.append(", answer=").append(answer);
         sb.append("]");
         return sb.toString();
     }
@@ -271,6 +421,26 @@ public class Faq implements Serializable {
         }
 
         /**
+         * 设置提问者电话
+         *
+         * @param phone 提问者电话
+         */
+        public Builder phone(String phone) {
+            obj.setPhone(phone);
+            return this;
+        }
+
+        /**
+         * 设置回复站内信
+         *
+         * @param message 回复站内信
+         */
+        public Builder message(Integer message) {
+            obj.setMessage(message);
+            return this;
+        }
+
+        /**
          * 设置频道
          *
          * @param channel 频道
@@ -291,6 +461,16 @@ public class Faq implements Serializable {
         }
 
         /**
+         * 设置处理人
+         *
+         * @param managerId 处理人
+         */
+        public Builder managerId(Integer managerId) {
+            obj.setManagerId(managerId);
+            return this;
+        }
+
+        /**
          * 设置是否精选:0不是精选,1是精选
          *
          * @param isSpecial 是否精选:0不是精选,1是精选
@@ -311,6 +491,36 @@ public class Faq implements Serializable {
         }
 
         /**
+         * 设置回复内容
+         *
+         * @param answer 回复内容
+         */
+        public Builder answer(String answer) {
+            obj.setAnswer(answer);
+            return this;
+        }
+
+        /**
+         * 设置回复时间
+         *
+         * @param answerTime 回复时间
+         */
+        public Builder answerTime(Date answerTime) {
+            obj.setAnswerTime(answerTime);
+            return this;
+        }
+
+        /**
+         * 设置是否系统创建
+         *
+         * @param isSystem 是否系统创建
+         */
+        public Builder isSystem(Integer isSystem) {
+            obj.setIsSystem(isSystem);
+            return this;
+        }
+
+        /**
          * @param createTime
          */
         public Builder createTime(Date createTime) {

+ 237 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/Message.java

@@ -1,6 +1,7 @@
 package com.qxgmat.data.dao.entity;
 
 import java.io.Serializable;
+import java.util.Date;
 import javax.persistence.*;
 
 @Table(name = "message")
@@ -10,6 +11,45 @@ public class Message implements Serializable {
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Integer id;
 
+    /**
+     * 消息标题
+     */
+    @Column(name = "`title`")
+    private String title;
+
+    /**
+     * 消息链接
+     */
+    @Column(name = "`link`")
+    private String link;
+
+    /**
+     * 发送时间
+     */
+    @Column(name = "`send_time`")
+    private Date sendTime;
+
+    /**
+     * 发送数量
+     */
+    @Column(name = "`send_number`")
+    private Integer sendNumber;
+
+    /**
+     * 发送状态:0未发送,1发送中,2发送完成
+     */
+    @Column(name = "`send_status`")
+    private Integer sendStatus;
+
+    @Column(name = "`create_time`")
+    private Date createTime;
+
+    /**
+     * 消息内容
+     */
+    @Column(name = "`content`")
+    private String content;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -26,6 +66,128 @@ public class Message implements Serializable {
         this.id = id;
     }
 
+    /**
+     * 获取消息标题
+     *
+     * @return title - 消息标题
+     */
+    public String getTitle() {
+        return title;
+    }
+
+    /**
+     * 设置消息标题
+     *
+     * @param title 消息标题
+     */
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    /**
+     * 获取消息链接
+     *
+     * @return link - 消息链接
+     */
+    public String getLink() {
+        return link;
+    }
+
+    /**
+     * 设置消息链接
+     *
+     * @param link 消息链接
+     */
+    public void setLink(String link) {
+        this.link = link;
+    }
+
+    /**
+     * 获取发送时间
+     *
+     * @return send_time - 发送时间
+     */
+    public Date getSendTime() {
+        return sendTime;
+    }
+
+    /**
+     * 设置发送时间
+     *
+     * @param sendTime 发送时间
+     */
+    public void setSendTime(Date sendTime) {
+        this.sendTime = sendTime;
+    }
+
+    /**
+     * 获取发送数量
+     *
+     * @return send_number - 发送数量
+     */
+    public Integer getSendNumber() {
+        return sendNumber;
+    }
+
+    /**
+     * 设置发送数量
+     *
+     * @param sendNumber 发送数量
+     */
+    public void setSendNumber(Integer sendNumber) {
+        this.sendNumber = sendNumber;
+    }
+
+    /**
+     * 获取发送状态:0未发送,1发送中,2发送完成
+     *
+     * @return send_status - 发送状态:0未发送,1发送中,2发送完成
+     */
+    public Integer getSendStatus() {
+        return sendStatus;
+    }
+
+    /**
+     * 设置发送状态:0未发送,1发送中,2发送完成
+     *
+     * @param sendStatus 发送状态:0未发送,1发送中,2发送完成
+     */
+    public void setSendStatus(Integer sendStatus) {
+        this.sendStatus = sendStatus;
+    }
+
+    /**
+     * @return create_time
+     */
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    /**
+     * @param createTime
+     */
+    public void setCreateTime(Date createTime) {
+        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();
@@ -33,6 +195,13 @@ public class Message implements Serializable {
         sb.append(" [");
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
+        sb.append(", title=").append(title);
+        sb.append(", link=").append(link);
+        sb.append(", sendTime=").append(sendTime);
+        sb.append(", sendNumber=").append(sendNumber);
+        sb.append(", sendStatus=").append(sendStatus);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", content=").append(content);
         sb.append("]");
         return sb.toString();
     }
@@ -56,6 +225,74 @@ public class Message implements Serializable {
             return this;
         }
 
+        /**
+         * 设置消息标题
+         *
+         * @param title 消息标题
+         */
+        public Builder title(String title) {
+            obj.setTitle(title);
+            return this;
+        }
+
+        /**
+         * 设置消息链接
+         *
+         * @param link 消息链接
+         */
+        public Builder link(String link) {
+            obj.setLink(link);
+            return this;
+        }
+
+        /**
+         * 设置发送时间
+         *
+         * @param sendTime 发送时间
+         */
+        public Builder sendTime(Date sendTime) {
+            obj.setSendTime(sendTime);
+            return this;
+        }
+
+        /**
+         * 设置发送数量
+         *
+         * @param sendNumber 发送数量
+         */
+        public Builder sendNumber(Integer sendNumber) {
+            obj.setSendNumber(sendNumber);
+            return this;
+        }
+
+        /**
+         * 设置发送状态:0未发送,1发送中,2发送完成
+         *
+         * @param sendStatus 发送状态:0未发送,1发送中,2发送完成
+         */
+        public Builder sendStatus(Integer sendStatus) {
+            obj.setSendStatus(sendStatus);
+            return this;
+        }
+
+        /**
+         * @param createTime
+         */
+        public Builder createTime(Date createTime) {
+            obj.setCreateTime(createTime);
+            return this;
+        }
+
+        /**
+         * 设置消息内容
+         *
+         * @param content 消息内容
+         */
+        public Builder content(String content) {
+            obj.setContent(content);
+            return this;
+        }
+
         public Message build() {
             return this.obj;
         }

+ 51 - 16
server/data/src/main/java/com/qxgmat/data/dao/entity/UserCourseProgress.java

@@ -29,10 +29,16 @@ public class UserCourseProgress implements Serializable {
     private Integer courseNoId;
 
     /**
-     * 已学习时间
+     * 进度
      */
-    @Column(name = "`user_time`")
-    private Integer userTime;
+    @Column(name = "`progress`")
+    private Integer progress;
+
+    /**
+     * 次数
+     */
+    @Column(name = "`times`")
+    private Integer times;
 
     private static final long serialVersionUID = 1L;
 
@@ -105,21 +111,39 @@ public class UserCourseProgress implements Serializable {
     }
 
     /**
-     * 获取已学习时间
+     * 获取进度
+     *
+     * @return progress - 进度
+     */
+    public Integer getProgress() {
+        return progress;
+    }
+
+    /**
+     * 设置进度
      *
-     * @return user_time - 已学习时间
+     * @param progress 进度
      */
-    public Integer getUserTime() {
-        return userTime;
+    public void setProgress(Integer progress) {
+        this.progress = progress;
     }
 
     /**
-     * 设置已学习时间
+     * 获取次数
      *
-     * @param userTime 已学习时间
+     * @return times - 次数
      */
-    public void setUserTime(Integer userTime) {
-        this.userTime = userTime;
+    public Integer getTimes() {
+        return times;
+    }
+
+    /**
+     * 设置次数
+     *
+     * @param times 次数
+     */
+    public void setTimes(Integer times) {
+        this.times = times;
     }
 
     @Override
@@ -132,7 +156,8 @@ public class UserCourseProgress implements Serializable {
         sb.append(", userId=").append(userId);
         sb.append(", courseId=").append(courseId);
         sb.append(", courseNoId=").append(courseNoId);
-        sb.append(", userTime=").append(userTime);
+        sb.append(", progress=").append(progress);
+        sb.append(", times=").append(times);
         sb.append("]");
         return sb.toString();
     }
@@ -187,12 +212,22 @@ public class UserCourseProgress implements Serializable {
         }
 
         /**
-         * 设置已学习时间
+         * 设置进度
+         *
+         * @param progress 进度
+         */
+        public Builder progress(Integer progress) {
+            obj.setProgress(progress);
+            return this;
+        }
+
+        /**
+         * 设置次数
          *
-         * @param userTime 已学习时间
+         * @param times 次数
          */
-        public Builder userTime(Integer userTime) {
-            obj.setUserTime(userTime);
+        public Builder times(Integer times) {
+            obj.setTimes(times);
             return this;
         }
 

+ 335 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/UserServiceRecord.java

@@ -0,0 +1,335 @@
+package com.qxgmat.data.dao.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+import javax.persistence.*;
+
+@Table(name = "user_service_record")
+public class UserServiceRecord implements Serializable {
+    @Id
+    @Column(name = "`id`")
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Integer id;
+
+    /**
+     * 用户id
+     */
+    @Column(name = "`user_id`")
+    private Integer userId;
+
+    /**
+     * 服务
+     */
+    @Column(name = "`service`")
+    private String service;
+
+    /**
+     * 服务参数
+     */
+    @Column(name = "`param`")
+    private String param;
+
+    /**
+     * 开通方式
+     */
+    @Column(name = "`source`")
+    private String source;
+
+    /**
+     * 开通开始时间
+     */
+    @Column(name = "`start_time`")
+    private Date startTime;
+
+    /**
+     * 开通结束时间
+     */
+    @Column(name = "`end_time`")
+    private Date endTime;
+
+    /**
+     * 是否使用
+     */
+    @Column(name = "`is_used`")
+    private Integer isUsed;
+
+    @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;
+    }
+
+    /**
+     * 获取服务
+     *
+     * @return service - 服务
+     */
+    public String getService() {
+        return service;
+    }
+
+    /**
+     * 设置服务
+     *
+     * @param service 服务
+     */
+    public void setService(String service) {
+        this.service = service;
+    }
+
+    /**
+     * 获取服务参数
+     *
+     * @return param - 服务参数
+     */
+    public String getParam() {
+        return param;
+    }
+
+    /**
+     * 设置服务参数
+     *
+     * @param param 服务参数
+     */
+    public void setParam(String param) {
+        this.param = param;
+    }
+
+    /**
+     * 获取开通方式
+     *
+     * @return source - 开通方式
+     */
+    public String getSource() {
+        return source;
+    }
+
+    /**
+     * 设置开通方式
+     *
+     * @param source 开通方式
+     */
+    public void setSource(String source) {
+        this.source = source;
+    }
+
+    /**
+     * 获取开通开始时间
+     *
+     * @return start_time - 开通开始时间
+     */
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    /**
+     * 设置开通开始时间
+     *
+     * @param startTime 开通开始时间
+     */
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
+
+    /**
+     * 获取开通结束时间
+     *
+     * @return end_time - 开通结束时间
+     */
+    public Date getEndTime() {
+        return endTime;
+    }
+
+    /**
+     * 设置开通结束时间
+     *
+     * @param endTime 开通结束时间
+     */
+    public void setEndTime(Date endTime) {
+        this.endTime = endTime;
+    }
+
+    /**
+     * 获取是否使用
+     *
+     * @return is_used - 是否使用
+     */
+    public Integer getIsUsed() {
+        return isUsed;
+    }
+
+    /**
+     * 设置是否使用
+     *
+     * @param isUsed 是否使用
+     */
+    public void setIsUsed(Integer isUsed) {
+        this.isUsed = isUsed;
+    }
+
+    /**
+     * @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(", service=").append(service);
+        sb.append(", param=").append(param);
+        sb.append(", source=").append(source);
+        sb.append(", startTime=").append(startTime);
+        sb.append(", endTime=").append(endTime);
+        sb.append(", isUsed=").append(isUsed);
+        sb.append(", createTime=").append(createTime);
+        sb.append("]");
+        return sb.toString();
+    }
+
+    public static UserServiceRecord.Builder builder() {
+        return new UserServiceRecord.Builder();
+    }
+
+    public static class Builder {
+        private UserServiceRecord obj;
+
+        public Builder() {
+            this.obj = new UserServiceRecord();
+        }
+
+        /**
+         * @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;
+        }
+
+        /**
+         * 设置服务
+         *
+         * @param service 服务
+         */
+        public Builder service(String service) {
+            obj.setService(service);
+            return this;
+        }
+
+        /**
+         * 设置服务参数
+         *
+         * @param param 服务参数
+         */
+        public Builder param(String param) {
+            obj.setParam(param);
+            return this;
+        }
+
+        /**
+         * 设置开通方式
+         *
+         * @param source 开通方式
+         */
+        public Builder source(String source) {
+            obj.setSource(source);
+            return this;
+        }
+
+        /**
+         * 设置开通开始时间
+         *
+         * @param startTime 开通开始时间
+         */
+        public Builder startTime(Date startTime) {
+            obj.setStartTime(startTime);
+            return this;
+        }
+
+        /**
+         * 设置开通结束时间
+         *
+         * @param endTime 开通结束时间
+         */
+        public Builder endTime(Date endTime) {
+            obj.setEndTime(endTime);
+            return this;
+        }
+
+        /**
+         * 设置是否使用
+         *
+         * @param isUsed 是否使用
+         */
+        public Builder isUsed(Integer isUsed) {
+            obj.setIsUsed(isUsed);
+            return this;
+        }
+
+        /**
+         * @param createTime
+         */
+        public Builder createTime(Date createTime) {
+            obj.setCreateTime(createTime);
+            return this;
+        }
+
+        public UserServiceRecord build() {
+            return this.obj;
+        }
+    }
+}

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

@@ -12,6 +12,7 @@
     <result column="channel" jdbcType="VARCHAR" property="channel" />
     <result column="position" jdbcType="VARCHAR" property="position" />
     <result column="is_special" jdbcType="INTEGER" property="isSpecial" />
+    <result column="is_system" jdbcType="INTEGER" property="isSystem" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
   </resultMap>
   <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.Comment">
@@ -24,7 +25,8 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `nickname`, `avatar`, `channel`, `position`, `is_special`, `create_time`
+    `id`, `user_id`, `nickname`, `avatar`, `channel`, `position`, `is_special`, `is_system`, 
+    `create_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 9 - 2
server/data/src/main/java/com/qxgmat/data/dao/mapping/FaqMapper.xml

@@ -8,10 +8,15 @@
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="user_id" jdbcType="INTEGER" property="userId" />
     <result column="email" jdbcType="VARCHAR" property="email" />
+    <result column="phone" jdbcType="VARCHAR" property="phone" />
+    <result column="message" jdbcType="INTEGER" property="message" />
     <result column="channel" jdbcType="VARCHAR" property="channel" />
     <result column="position" jdbcType="VARCHAR" property="position" />
+    <result column="manager_id" jdbcType="INTEGER" property="managerId" />
     <result column="is_special" jdbcType="INTEGER" property="isSpecial" />
     <result column="status" jdbcType="INTEGER" property="status" />
+    <result column="answer_time" jdbcType="TIMESTAMP" property="answerTime" />
+    <result column="is_system" jdbcType="INTEGER" property="isSystem" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
   </resultMap>
   <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.Faq">
@@ -19,17 +24,19 @@
       WARNING - @mbg.generated
     -->
     <result column="content" jdbcType="LONGVARCHAR" property="content" />
+    <result column="answer" jdbcType="LONGVARCHAR" property="answer" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `email`, `channel`, `position`, `is_special`, `status`, `create_time`
+    `id`, `user_id`, `email`, `phone`, `message`, `channel`, `position`, `manager_id`, 
+    `is_special`, `status`, `answer_time`, `is_system`, `create_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
-    `content`
+    `content`, `answer`
   </sql>
 </mapper>

+ 24 - 0
server/data/src/main/java/com/qxgmat/data/dao/mapping/MessageMapper.xml

@@ -6,5 +6,29 @@
       WARNING - @mbg.generated
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
+    <result column="title" jdbcType="VARCHAR" property="title" />
+    <result column="link" jdbcType="VARCHAR" property="link" />
+    <result column="send_time" jdbcType="TIMESTAMP" property="sendTime" />
+    <result column="send_number" jdbcType="INTEGER" property="sendNumber" />
+    <result column="send_status" jdbcType="INTEGER" property="sendStatus" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
   </resultMap>
+  <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.Message">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <result column="content" jdbcType="LONGVARCHAR" property="content" />
+  </resultMap>
+  <sql id="Base_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    `id`, `title`, `link`, `send_time`, `send_number`, `send_status`, `create_time`
+  </sql>
+  <sql id="Blob_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    `content`
+  </sql>
 </mapper>

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

@@ -9,12 +9,13 @@
     <result column="user_id" jdbcType="INTEGER" property="userId" />
     <result column="course_id" jdbcType="INTEGER" property="courseId" />
     <result column="course_no_id" jdbcType="INTEGER" property="courseNoId" />
-    <result column="user_time" jdbcType="INTEGER" property="userTime" />
+    <result column="progress" jdbcType="INTEGER" property="progress" />
+    <result column="times" jdbcType="INTEGER" property="times" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `course_id`, `course_no_id`, `user_time`
+    `id`, `user_id`, `course_id`, `course_no_id`, `progress`, `times`
   </sql>
 </mapper>

+ 25 - 0
server/data/src/main/java/com/qxgmat/data/dao/mapping/UserServiceRecordMapper.xml

@@ -0,0 +1,25 @@
+<?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.UserServiceRecordMapper">
+  <resultMap id="BaseResultMap" type="com.qxgmat.data.dao.entity.UserServiceRecord">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="id" jdbcType="INTEGER" property="id" />
+    <result column="user_id" jdbcType="INTEGER" property="userId" />
+    <result column="service" jdbcType="VARCHAR" property="service" />
+    <result column="param" jdbcType="VARCHAR" property="param" />
+    <result column="source" jdbcType="VARCHAR" property="source" />
+    <result column="start_time" jdbcType="TIMESTAMP" property="startTime" />
+    <result column="end_time" jdbcType="TIMESTAMP" property="endTime" />
+    <result column="is_used" jdbcType="INTEGER" property="isUsed" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+  </resultMap>
+  <sql id="Base_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    `id`, `user_id`, `service`, `param`, `source`, `start_time`, `end_time`, `is_used`, 
+    `create_time`
+  </sql>
+</mapper>

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

@@ -70,7 +70,7 @@ public class CourseController {
         UserAskCourse entity = Transform.dtoToEntity(dto);
         UserAskCourse in = userAskCourseService.get(entity.getId());
         // 调整回答
-        if(!entity.getAnswer().isEmpty() || !in.getAnswer().equals(entity.getAnswer())){
+        if(entity.getAnswer() != null && (!entity.getAnswer().isEmpty() || !in.getAnswer().equals(entity.getAnswer()))){
             entity.setAnswerTime(new Date());
             entity.setAnswerStatus(AskStatus.ANSWER.index);
             Manager manager = shiroHelp.getLoginManager();

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

@@ -173,7 +173,7 @@ public class QuestionController {
         UserAskQuestion entity = Transform.dtoToEntity(dto);
         UserAskQuestion in = userAskQuestionService.get(entity.getId());
         // 调整回答
-        if(!entity.getAnswer().isEmpty() || !in.getAnswer().equals(entity.getAnswer())){
+        if(entity.getAnswer() != null && (!entity.getAnswer().isEmpty() || !in.getAnswer().equals(entity.getAnswer()))){
             entity.setAnswerTime(new Date());
             entity.setAnswerStatus(AskStatus.ANSWER.index);
             Manager manager = shiroHelp.getLoginManager();

+ 71 - 9
server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java

@@ -5,10 +5,15 @@ import com.github.pagehelper.Page;
 import com.nuliji.tools.*;
 import com.qxgmat.data.constants.enums.SettingKey;
 import com.qxgmat.data.constants.enums.module.ChannelModule;
+import com.qxgmat.data.constants.enums.status.AskStatus;
 import com.qxgmat.data.dao.entity.*;
+import com.qxgmat.dto.admin.extend.UserExtendDto;
 import com.qxgmat.dto.admin.request.CommentDto;
 import com.qxgmat.dto.admin.request.FaqDto;
+import com.qxgmat.dto.admin.request.MessageDto;
 import com.qxgmat.dto.admin.request.RankDto;
+import com.qxgmat.help.ShiroHelp;
+import com.qxgmat.service.UsersService;
 import com.qxgmat.service.inline.*;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -29,6 +34,8 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 
 
@@ -37,6 +44,8 @@ import java.util.List;
 @Api(tags = "配置信息接口", description = "全局独立配置设置", produces = MediaType.APPLICATION_JSON_VALUE)
 public class SettingController {
     private static final Logger logger = LoggerFactory.getLogger(SettingController.class);
+    @Autowired
+    private ShiroHelp shiroHelp;
 
     @Autowired
     private SettingService settingService;
@@ -53,6 +62,9 @@ public class SettingController {
     @Autowired
     private MessageService messageService;
 
+    @Autowired
+    private UsersService usersService;
+
     @RequestMapping(value = "/index", method = RequestMethod.PUT)
     @ApiOperation(value = "修改首页配置", httpMethod = "PUT")
     private Response<Boolean> editIndex(@RequestBody @Validated JSONObject dto){
@@ -291,6 +303,23 @@ public class SettingController {
         return ResponseHelp.success(entity.getValue());
     }
 
+    @RequestMapping(value = "/course_promote", method = RequestMethod.PUT)
+    @ApiOperation(value = "修改课程促销", httpMethod = "PUT")
+    private Response<Boolean> editCoursePromote(@RequestBody @Validated JSONObject dto){
+        Setting entity = settingService.getByKey(SettingKey.COURSE_PROMOTE);
+        entity.setValue(dto);
+        settingService.edit(entity);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/course_promote", method = RequestMethod.GET)
+    @ApiOperation(value = "获取课程促销", httpMethod = "GET")
+    private Response<JSONObject> getCoursePromote(){
+        Setting entity = settingService.getByKey(SettingKey.COURSE_PROMOTE);
+
+        return ResponseHelp.success(entity.getValue());
+    }
+
     @RequestMapping(value = "/tips", method = RequestMethod.PUT)
     @ApiOperation(value = "修改结构说明", httpMethod = "PUT")
     private Response<Boolean> editTips(@RequestBody @Validated JSONObject dto){
@@ -312,6 +341,7 @@ public class SettingController {
     @ApiOperation(value = "添加评价", httpMethod = "POST")
     private Response<Boolean> addComment(@RequestBody @Validated CommentDto dto){
         Comment entity = Transform.dtoToEntity(dto);
+        entity.setIsSystem(1);
         commentService.add(entity);
         return ResponseHelp.success(true);
     }
@@ -333,7 +363,7 @@ public class SettingController {
 
     @RequestMapping(value = "/comment/list", method = RequestMethod.GET)
     @ApiOperation(value = "获取评价列表", httpMethod = "GET")
-    private Response<PageMessage<Comment>> listComment(
+    private Response<PageMessage<CommentDto>> listComment(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false) String channel,
@@ -342,13 +372,27 @@ public class SettingController {
             @RequestParam(required = false) Boolean isSpecial
     ){
         Page<Comment> p = commentService.listAdmin(page, size, ChannelModule.ValueOf(channel), position, userId, isSpecial);
-        return ResponseHelp.success(p, page, size, p.getTotal());
+        List<CommentDto> pr = Transform.convert(p, CommentDto.class);
+
+        // 绑定用户
+        Collection userIds = Transform.getIds(p, UserAskQuestion.class, "userId");
+        List<User> userList = usersService.select(userIds);
+        Transform.combine(pr, userList, CommentDto.class, "userId", "user", User.class, "id", UserExtendDto.class);
+
+        return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
     @RequestMapping(value = "/faq/add", method = RequestMethod.POST)
     @ApiOperation(value = "添加faq", httpMethod = "POST")
     private Response<Boolean> addFaq(@RequestBody @Validated FaqDto dto){
         Faq entity = Transform.dtoToEntity(dto);
+        entity.setIsSystem(1);
+        if(!entity.getAnswer().isEmpty()){
+            entity.setAnswerTime(new Date());
+            entity.setStatus(AskStatus.ANSWER.index);
+            Manager manager = shiroHelp.getLoginManager();
+            entity.setManagerId(manager.getId());
+        }
         faqService.add(entity);
         return ResponseHelp.success(true);
     }
@@ -357,6 +401,24 @@ public class SettingController {
     @ApiOperation(value = "修改faq", httpMethod = "PUT")
     private Response<Boolean> editFaq(@RequestBody @Validated FaqDto dto){
         Faq entity = Transform.dtoToEntity(dto);
+        Faq in = faqService.get(entity.getId());
+        // 调整回答
+        if(entity.getAnswer() != null && (!entity.getAnswer().isEmpty() || !in.getAnswer().equals(entity.getAnswer()))){
+            entity.setAnswerTime(new Date());
+            entity.setStatus(AskStatus.ANSWER.index);
+            Manager manager = shiroHelp.getLoginManager();
+            entity.setManagerId(manager.getId());
+        }
+        if(in.getIsSystem() == 0 && dto.getSendMail() != null && dto.getSendMail()){
+            // 回复邮箱
+            if (in.getEmail() != null && !in.getEmail().isEmpty()){
+                //
+            }
+            // 回复站内信
+            if(in.getMessage() != null && in.getMessage() > 0){
+
+            }
+        }
         faqService.edit(entity);
         return ResponseHelp.success(true);
     }
@@ -384,24 +446,24 @@ public class SettingController {
 
     @RequestMapping(value = "/message/add", method = RequestMethod.POST)
     @ApiOperation(value = "添加消息", httpMethod = "POST")
-    private Response<Boolean> addMessage(@RequestBody @Validated FaqDto dto){
-        Faq entity = Transform.dtoToEntity(dto);
-        faqService.add(entity);
+    private Response<Boolean> addMessage(@RequestBody @Validated MessageDto dto){
+        Message entity = Transform.dtoToEntity(dto);
+        messageService.add(entity);
         return ResponseHelp.success(true);
     }
 
     @RequestMapping(value = "/message/edit", method = RequestMethod.PUT)
     @ApiOperation(value = "修改消息", httpMethod = "PUT")
-    private Response<Boolean> editMessage(@RequestBody @Validated FaqDto dto){
-        Faq entity = Transform.dtoToEntity(dto);
-        faqService.edit(entity);
+    private Response<Boolean> editMessage(@RequestBody @Validated MessageDto dto){
+        Message entity = Transform.dtoToEntity(dto);
+        messageService.edit(entity);
         return ResponseHelp.success(true);
     }
 
     @RequestMapping(value = "/message/delete", method = RequestMethod.DELETE)
     @ApiOperation(value = "删除消息", httpMethod = "DELETE")
     private Response<Boolean> deleteMessage(@RequestParam int id){
-        faqService.delete(id);
+        messageService.delete(id);
         return ResponseHelp.success(true);
     }
 

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

@@ -2,6 +2,7 @@ package com.qxgmat.controller.admin;
 
 import com.github.pagehelper.Page;
 import com.nuliji.tools.*;
+import com.qxgmat.data.constants.enums.ServiceKey;
 import com.qxgmat.data.constants.enums.module.FeedbackModule;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.status.FeedbackStatus;
@@ -9,9 +10,11 @@ import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.relation.entity.UserPreviewPaperRelation;
 import com.qxgmat.dto.admin.extend.*;
 import com.qxgmat.dto.admin.request.UserFeedbackErrorDto;
+import com.qxgmat.dto.admin.request.UserServiceRecordDto;
 import com.qxgmat.dto.admin.response.*;
 import com.qxgmat.help.ShiroHelp;
 import com.qxgmat.service.ManagerService;
+import com.qxgmat.service.UserServiceService;
 import com.qxgmat.service.extend.PreviewService;
 import com.qxgmat.service.UsersService;
 import com.qxgmat.service.inline.*;
@@ -69,6 +72,9 @@ public class UserController {
     @Autowired
     private UserFeedbackErrorService userFeedbackErrorService;
 
+    @Autowired
+    private UserServiceRecordService userServiceRecordService;
+
 //    @RequestMapping(value = "/add", method = RequestMethod.POST)
 //    @ApiOperation(value = "添加用户信息", httpMethod = "POST")
 //    public Response<User> add(@RequestBody @Validated UserDto dto, HttpServletRequest request) {
@@ -235,16 +241,19 @@ public class UserController {
 
     @RequestMapping(value = "/feedback_error/detail", method = RequestMethod.GET)
     @ApiOperation(value = "勘误详情", httpMethod = "GET")
-    public Response<UserFeedbackErrorDetailDto> detailFeedbackError(@RequestParam int id, HttpServletRequest request) {
+    public Response<UserFeedbackErrorInfoDto> detailFeedbackError(@RequestParam int id, HttpServletRequest request) {
         UserFeedbackError entity = userFeedbackErrorService.get(id);
-        UserFeedbackErrorDetailDto dto = Transform.convert(entity, UserFeedbackErrorDetailDto.class);
+        UserFeedbackErrorInfoDto dto = Transform.convert(entity, UserFeedbackErrorInfoDto.class);
+        User user = usersService.get(entity.getUserId());
+        UserExtendDto userDto = Transform.convert(user, UserExtendDto.class);
+        dto.setUser(userDto);
 
         return ResponseHelp.success(dto);
     }
 
     @RequestMapping(value = "/feedback_error/list", method = RequestMethod.GET)
     @ApiOperation(value = "勘误列表", httpMethod = "GET")
-    public Response<PageMessage<UserFeedbackErrorListDto>> listFeedbackError(
+    public Response<PageMessage<UserFeedbackError>> listFeedbackError(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false) String module,
@@ -252,14 +261,77 @@ public class UserController {
             @RequestParam(required = false) String keyword,
             HttpSession session) {
         Page<UserFeedbackError> p = userFeedbackErrorService.listAdmin(page, size, FeedbackModule.ValueOf(module), FeedbackStatus.ValueOf(status), keyword);
-        List<UserFeedbackErrorListDto> pr = Transform.convert(p, UserFeedbackErrorListDto.class);
 
         // 绑定用户
         Collection userIds = Transform.getIds(p, UserAskQuestion.class, "userId");
         List<User> userList = usersService.select(userIds);
-        List<UserExtendDto> userExtendDtoList = Transform.convert(userList, UserExtendDto.class);
-        Transform.combine(pr, userExtendDtoList, UserAskQuestionListDto.class, "userId", "user", UserExtendDto.class, "id");
+        Transform.combine(p, userList, UserAskQuestionListDto.class, "userId", "user", UserExtendDto.class, "id", UserExtendDto.class);
+
+        return ResponseHelp.success(p, page, size, p.getTotal());
+    }
+
+    @RequestMapping(value = "/service/add", method = RequestMethod.POST)
+    @ApiOperation(value = "添加服务记录", httpMethod = "POST")
+    private Response<Boolean> addService(@RequestBody @Validated UserServiceRecordDto dto){
+        UserServiceRecord entity = Transform.dtoToEntity(dto);
+        if (dto.getMobile() != null && !dto.getMobile().isEmpty()){
+            // 判断mobile
+            User user = usersService.getByMobile(dto.getMobile());
+            if (user == null){
+                // 创建user
+                user = usersService.register(dto.getMobile(), null, null);
+            }
+            entity.setUserId(user.getId());
+        }
+        userServiceRecordService.add(entity);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/service/edit", method = RequestMethod.PUT)
+    @ApiOperation(value = "修改服务记录", httpMethod = "PUT")
+    private Response<Boolean> editFaq(@RequestBody @Validated UserServiceRecordDto dto){
+        UserServiceRecord entity = Transform.dtoToEntity(dto);
+        userServiceRecordService.edit(entity);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/service/delete", method = RequestMethod.DELETE)
+    @ApiOperation(value = "删除服务记录", httpMethod = "DELETE")
+    private Response<Boolean> deleteService(@RequestParam int id){
+        userServiceRecordService.delete(id);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/service/list", method = RequestMethod.GET)
+    @ApiOperation(value = "获取faq列表", httpMethod = "GET")
+    private Response<PageMessage<UserServiceRecordInfoDto>> listService(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) String service,
+            @RequestParam(required = false) String param,
+            @RequestParam(required = false) Integer userId
+    ){
+        Page<UserServiceRecord> p = userServiceRecordService.listAdmin(page, size, ServiceKey.ValueOf(service), param, userId);
+        List<UserServiceRecordInfoDto> pr = Transform.convert(p, UserServiceRecordInfoDto.class);
+
+        // 绑定用户
+        Collection userIds = Transform.getIds(p, UserServiceRecord.class, "userId");
+        List<User> userList = usersService.select(userIds);
+        Transform.combine(pr, userList, UserServiceRecordInfoDto.class, "userId", "user", User.class, "id", UserExtendDto.class);
 
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
+
+    @RequestMapping(value = "/valid/mobile", method = RequestMethod.GET)
+    @ApiOperation(value = "验证手机号", notes="查询手机对应账号", httpMethod = "GET")
+    public Response<Boolean> validMobile(
+            @RequestParam(required = true) String mobile
+    ){
+        User user = usersService.getByMobile(mobile);
+        if(user == null){
+            return ResponseHelp.success(false);
+        }else{
+            return ResponseHelp.success(true);
+        }
+    }
 }

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

@@ -15,7 +15,7 @@ import com.qxgmat.help.CaptchaHelp;
 import com.qxgmat.help.ShiroHelp;
 import com.qxgmat.help.SmsHelp;
 import com.qxgmat.service.UsersService;
-import com.qxgmat.service.inline.UserServiceService;
+import com.qxgmat.service.UserServiceService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;

+ 26 - 6
server/gateway-api/src/main/java/com/qxgmat/dto/admin/extend/UserExtendDto.java

@@ -5,9 +5,13 @@ import com.qxgmat.data.dao.entity.User;
 
 @Dto(entity = User.class)
 public class UserExtendDto {
-
     private Integer id;
-    private String username;
+
+    private String nickname;
+
+    private String mobile;
+
+    private Integer totalMoney;
 
     public Integer getId() {
         return id;
@@ -17,11 +21,27 @@ public class UserExtendDto {
         this.id = id;
     }
 
-    public String getUsername() {
-        return username;
+    public String getNickname() {
+        return nickname;
+    }
+
+    public void setNickname(String nickname) {
+        this.nickname = nickname;
+    }
+
+    public String getMobile() {
+        return mobile;
+    }
+
+    public void setMobile(String mobile) {
+        this.mobile = mobile;
+    }
+
+    public Integer getTotalMoney() {
+        return totalMoney;
     }
 
-    public void setUsername(String username) {
-        this.username = username;
+    public void setTotalMoney(Integer totalMoney) {
+        this.totalMoney = totalMoney;
     }
 }

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

@@ -19,8 +19,12 @@ public class FaqDto {
 
     private String content;
 
+    private String answer;
+
     private Integer isSpecial;
 
+    private Boolean sendMail;
+
     private Integer status;
 
     private Date createTime;
@@ -96,4 +100,20 @@ public class FaqDto {
     public void setCreateTime(Date createTime) {
         this.createTime = createTime;
     }
+
+    public String getAnswer() {
+        return answer;
+    }
+
+    public void setAnswer(String answer) {
+        this.answer = answer;
+    }
+
+    public Boolean getSendMail() {
+        return sendMail;
+    }
+
+    public void setSendMail(Boolean sendMail) {
+        this.sendMail = sendMail;
+    }
 }

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

@@ -0,0 +1,99 @@
+package com.qxgmat.dto.admin.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserServiceRecord;
+
+import java.util.Date;
+
+@Dto(entity = UserServiceRecord.class)
+public class UserServiceRecordDto {
+    private Integer id;
+
+    private Integer userId;
+
+    private String mobile;
+
+    private String service;
+
+    private String param;
+
+    private String source;
+
+    private Integer isUsed;
+
+    private Date startTime;
+
+    private Date endTime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public String getMobile() {
+        return mobile;
+    }
+
+    public void setMobile(String mobile) {
+        this.mobile = mobile;
+    }
+
+    public String getService() {
+        return service;
+    }
+
+    public void setService(String service) {
+        this.service = service;
+    }
+
+    public String getParam() {
+        return param;
+    }
+
+    public void setParam(String param) {
+        this.param = param;
+    }
+
+    public Integer getIsUsed() {
+        return isUsed;
+    }
+
+    public void setIsUsed(Integer isUsed) {
+        this.isUsed = isUsed;
+    }
+
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
+
+    public Date getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Date endTime) {
+        this.endTime = endTime;
+    }
+
+    public String getSource() {
+        return source;
+    }
+
+    public void setSource(String source) {
+        this.source = source;
+    }
+}

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

@@ -0,0 +1,122 @@
+package com.qxgmat.dto.admin.response;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.Comment;
+import com.qxgmat.data.dao.entity.UserFeedbackError;
+import com.qxgmat.dto.admin.extend.UserExtendDto;
+
+import java.util.Date;
+
+@Dto(entity = Comment.class)
+public class CommentDto {
+
+    private Integer id;
+
+    private UserExtendDto user;
+
+    private Integer userId;
+
+    private String nickname;
+
+    private String avatar;
+
+    private String channel;
+
+    private String position;
+
+    private Integer isSpecial;
+
+    private Integer isSystem;
+
+    private Date createTime;
+
+    private String content;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public UserExtendDto getUser() {
+        return user;
+    }
+
+    public void setUser(UserExtendDto user) {
+        this.user = user;
+    }
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public String getNickname() {
+        return nickname;
+    }
+
+    public void setNickname(String nickname) {
+        this.nickname = nickname;
+    }
+
+    public String getAvatar() {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+    public String getChannel() {
+        return channel;
+    }
+
+    public void setChannel(String channel) {
+        this.channel = channel;
+    }
+
+    public String getPosition() {
+        return position;
+    }
+
+    public void setPosition(String position) {
+        this.position = position;
+    }
+
+    public Integer getIsSpecial() {
+        return isSpecial;
+    }
+
+    public void setIsSpecial(Integer isSpecial) {
+        this.isSpecial = isSpecial;
+    }
+
+    public Integer getIsSystem() {
+        return isSystem;
+    }
+
+    public void setIsSystem(Integer isSystem) {
+        this.isSystem = isSystem;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+}

+ 0 - 8
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserFeedbackErrorDetailDto.java

@@ -1,8 +0,0 @@
-package com.qxgmat.dto.admin.response;
-
-import com.nuliji.tools.annotation.Dto;
-import com.qxgmat.data.dao.entity.UserFeedbackError;
-
-@Dto(entity = UserFeedbackError.class)
-public class UserFeedbackErrorDetailDto {
-}

+ 1 - 1
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserFeedbackErrorListDto.java

@@ -5,7 +5,7 @@ import com.qxgmat.data.dao.entity.UserFeedbackError;
 import com.qxgmat.dto.admin.extend.UserExtendDto;
 
 @Dto(entity = UserFeedbackError.class)
-public class UserFeedbackErrorListDto {
+public class UserFeedbackErrorInfoDto {
 
     private Integer id;
 

+ 19 - 9
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserListDto.java

@@ -16,6 +16,8 @@ public class UserListDto {
 
     private String mobile;
 
+    private String nickname;
+
     private Date createTime;
 
     private Integer realStatus;
@@ -28,7 +30,7 @@ public class UserListDto {
 
     private Collection<UserServiceExtendDto> services;
 
-    private Collection<UserCourseExtendDto> classes;
+    private Collection<UserCourseExtendDto> courses;
 
     public Integer getId() {
         return id;
@@ -78,14 +80,6 @@ public class UserListDto {
         this.services = services;
     }
 
-    public Collection<UserCourseExtendDto> getClasses() {
-        return classes;
-    }
-
-    public void setClasses(Collection<UserCourseExtendDto> classes) {
-        this.classes = classes;
-    }
-
     public Integer getInviteNumber() {
         return inviteNumber;
     }
@@ -101,4 +95,20 @@ public class UserListDto {
     public void setPrepareStatus(Integer prepareStatus) {
         this.prepareStatus = prepareStatus;
     }
+
+    public Collection<UserCourseExtendDto> getCourses() {
+        return courses;
+    }
+
+    public void setCourses(Collection<UserCourseExtendDto> courses) {
+        this.courses = courses;
+    }
+
+    public String getNickname() {
+        return nickname;
+    }
+
+    public void setNickname(String nickname) {
+        this.nickname = nickname;
+    }
 }

+ 100 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserServiceRecordInfoDto.java

@@ -0,0 +1,100 @@
+package com.qxgmat.dto.admin.response;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserServiceRecord;
+import com.qxgmat.dto.admin.extend.UserExtendDto;
+
+import java.util.Date;
+
+@Dto(entity = UserServiceRecord.class)
+public class UserServiceRecordInfoDto {
+    private Integer id;
+
+    private Integer userId;
+
+    private UserExtendDto user;
+
+    private String service;
+
+    private String param;
+
+    private String source;
+
+    private Integer isUsed;
+
+    private Date startTime;
+
+    private Date endTime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public String getService() {
+        return service;
+    }
+
+    public void setService(String service) {
+        this.service = service;
+    }
+
+    public String getParam() {
+        return param;
+    }
+
+    public void setParam(String param) {
+        this.param = param;
+    }
+
+    public Integer getIsUsed() {
+        return isUsed;
+    }
+
+    public void setIsUsed(Integer isUsed) {
+        this.isUsed = isUsed;
+    }
+
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
+
+    public Date getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Date endTime) {
+        this.endTime = endTime;
+    }
+
+    public String getSource() {
+        return source;
+    }
+
+    public void setSource(String source) {
+        this.source = source;
+    }
+
+    public UserExtendDto getUser() {
+        return user;
+    }
+
+    public void setUser(UserExtendDto user) {
+        this.user = user;
+    }
+}

+ 6 - 5
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserServiceService.java

@@ -1,4 +1,4 @@
-package com.qxgmat.service.inline;
+package com.qxgmat.service;
 
 import com.github.pagehelper.Page;
 import com.nuliji.tools.AbstractService;
@@ -7,9 +7,8 @@ import com.nuliji.tools.exception.SystemException;
 import com.nuliji.tools.mybatis.Example;
 import com.qxgmat.data.constants.enums.ServiceKey;
 import com.qxgmat.data.dao.UserServiceMapper;
-import com.qxgmat.data.dao.entity.Pay;
-import com.qxgmat.data.dao.entity.UserPay;
 import com.qxgmat.data.dao.entity.UserService;
+import com.qxgmat.service.inline.UserServiceRecordService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
@@ -24,6 +23,9 @@ public class UserServiceService extends AbstractService {
     @Resource
     private UserServiceMapper userServiceMapper;
 
+    @Resource
+    private UserServiceRecordService userServiceRecordService;
+
     /**
      * 判断是否有权限
      * @param userId
@@ -47,9 +49,8 @@ public class UserServiceService extends AbstractService {
      * 给用户添加服务:系统自动
      * @param userId
      * @param key
-     * @param time
      */
-    public void addService(Integer userId, ServiceKey key, Integer time){
+    public void addService(Integer userId, ServiceKey key, String param){
 
     }
 

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

@@ -22,7 +22,6 @@ import com.qxgmat.help.WechatHelp;
 import com.qxgmat.service.inline.UserCourseService;
 import com.qxgmat.service.inline.UserMessageService;
 import com.qxgmat.service.inline.UserPayService;
-import com.qxgmat.service.inline.UserServiceService;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;

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

@@ -21,7 +21,7 @@ import com.qxgmat.help.PayHelp;
 import com.qxgmat.service.inline.PayService;
 import com.qxgmat.service.inline.UserCourseService;
 import com.qxgmat.service.inline.UserPayService;
-import com.qxgmat.service.inline.UserServiceService;
+import com.qxgmat.service.UserServiceService;
 import com.qxgmat.util.annotation.Callback;
 import org.springframework.stereotype.Service;
 

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

@@ -4,6 +4,7 @@ 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.dao.MessageMapper;
 import com.qxgmat.data.dao.entity.Message;
 import org.slf4j.Logger;
@@ -12,6 +13,7 @@ import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 
 @Service
@@ -21,6 +23,21 @@ public class MessageService extends AbstractService {
     @Resource
     private MessageMapper messageMapper;
 
+    /**
+     * 获取到期还未发送消息列表
+     * @return
+     */
+    public List<Message> listExpire(){
+        Example example = new Example(Message.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("sendNumber", 0)
+                        .andEqualTo("sendStatus", 0)
+                        .andLessThan("sendTime", new Date())
+        );
+        return select(messageMapper, example);
+    }
+
     public Message add(Message message){
         int result = insert(messageMapper, message);
         message = one(messageMapper, message.getId());

+ 93 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserServiceRecordService.java

@@ -0,0 +1,93 @@
+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.ServiceKey;
+import com.qxgmat.data.dao.UserServiceMapper;
+import com.qxgmat.data.dao.UserServiceRecordMapper;
+import com.qxgmat.data.dao.entity.UserService;
+import com.qxgmat.data.dao.entity.UserServiceRecord;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+@Service
+public class UserServiceRecordService extends AbstractService {
+    private static final Logger logger = LoggerFactory.getLogger(UserServiceRecordService.class);
+
+    @Resource
+    private UserServiceRecordMapper userServiceRecordMapper;
+
+    public Page<UserServiceRecord> listAdmin(int page, int size, ServiceKey service, String param, Integer userId){
+        Example example = new Example(UserServiceRecord.class);
+
+        if (service != null){
+            example.and(
+                    example.createCriteria().andEqualTo("channel", service.key)
+            );
+            if (param != null){
+                example.and(
+                        example.createCriteria().andEqualTo("param", param)
+                );
+            }
+        }
+        if (userId != null){
+            example.and(
+                    example.createCriteria().andEqualTo("userId", userId)
+            );
+        }
+
+        return select(userServiceRecordMapper, example, page, size);
+    }
+
+    public UserServiceRecord add(UserServiceRecord service){
+        int result = insert(userServiceRecordMapper, service);
+        service = one(userServiceRecordMapper, service.getId());
+        if(service == null){
+            throw new SystemException("服务记录添加失败");
+        }
+        return service;
+    }
+
+    public UserServiceRecord edit(UserServiceRecord service){
+        UserServiceRecord in = one(userServiceRecordMapper, service.getId());
+        if(in == null){
+            throw new ParameterException("服务记录不存在");
+        }
+        int result = update(userServiceRecordMapper, service);
+        return service;
+    }
+
+    public boolean delete(Number id){
+        UserServiceRecord in = one(userServiceRecordMapper, id);
+        if(in == null){
+            throw new ParameterException("服务记录不存在");
+        }
+        int result = delete(userServiceRecordMapper, id);
+        return result > 0;
+    }
+
+    public UserServiceRecord get(Number id){
+        UserServiceRecord in = one(userServiceRecordMapper, id);
+
+        if(in == null){
+            throw new ParameterException("服务记录不存在");
+        }
+        return in;
+    }
+
+    public Page<UserServiceRecord> select(int page, int pageSize){
+        return select(userServiceRecordMapper, page, pageSize);
+    }
+
+    public List<UserServiceRecord> select(Collection ids){
+        return select(userServiceRecordMapper, ids);
+    }
+
+}

+ 26 - 1
server/gateway-api/src/main/java/com/qxgmat/task/ScheduledTask.java

@@ -5,12 +5,15 @@ import com.alibaba.fastjson.JSONObject;
 import com.github.pagehelper.Page;
 import com.nuliji.tools.third.OauthData;
 import com.qxgmat.data.constants.enums.SettingKey;
+import com.qxgmat.data.constants.enums.status.MessageStatus;
+import com.qxgmat.data.dao.entity.Message;
 import com.qxgmat.data.dao.entity.Setting;
 import com.qxgmat.data.dao.entity.User;
 import com.qxgmat.data.dao.entity.UserService;
 import com.qxgmat.data.relation.entity.UserPrepareRelation;
 import com.qxgmat.help.WechatHelp;
 import com.qxgmat.service.UsersService;
+import com.qxgmat.service.inline.MessageService;
 import com.qxgmat.service.inline.SettingService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -44,6 +47,9 @@ public class ScheduledTask {
     @Autowired
     private WechatHelp wechatHelp;
 
+    @Autowired
+    private MessageService messageService;
+
     /**
      * cron表达式:* * * * * *(共6位,使用空格隔开,具体如下)
      * cron表达式:*(秒0-59) *(分钟0-59) *(小时0-23) *(日期1-31) *(月份1-12或是JAN-DEC) *(星期1-7或是SUN-SAT)
@@ -51,7 +57,6 @@ public class ScheduledTask {
      *
      * */
 
-
     // 每小时刷新快过期用户的accessToken
     @Scheduled(cron="0 0 * * * *")
     public void refreshWechatToken(){
@@ -130,6 +135,26 @@ public class ScheduledTask {
         settingService.editByKey(SettingKey.PREPARE_INFO, setting);
     }
 
+    // 每小时判断发送消息
+    @Scheduled(cron="0 0 * * * *")
+    public void autoSendMessage(){
+        logger.info("Start auto Send message");
+        List<Message> list = messageService.listExpire();
+        for(Message message : list){
+            messageService.edit(Message.builder()
+                    .id(message.getId())
+                    .sendStatus(MessageStatus.SENDING.index)
+                    .build());
+            int number = 0;
+
+            messageService.edit(Message.builder()
+                    .id(message.getId())
+                    .sendNumber(number)
+                    .sendStatus(MessageStatus.SENDED.index)
+                    .build());
+        }
+    }
+
     // 每天判断是否自动组卷
     @Scheduled(cron="0 1 0 * * *")
     public void autoExercisePaper() throws ParseException {