lrf 3 anos atrás
commit
eb635511f0

+ 29 - 0
.autod.conf.js

@@ -0,0 +1,29 @@
+'use strict';
+
+module.exports = {
+  write: true,
+  prefix: '^',
+  plugin: 'autod-egg',
+  test: [
+    'test',
+    'benchmark',
+  ],
+  dep: [
+    'egg',
+    'egg-scripts',
+  ],
+  devdep: [
+    'egg-ci',
+    'egg-bin',
+    'egg-mock',
+    'autod',
+    'autod-egg',
+    'eslint',
+    'eslint-config-egg',
+  ],
+  exclude: [
+    './test/fixtures',
+    './dist',
+  ],
+};
+

+ 1 - 0
.eslintignore

@@ -0,0 +1 @@
+coverage

+ 3 - 0
.eslintrc

@@ -0,0 +1,3 @@
+{
+  "extends": "eslint-config-egg"
+}

+ 46 - 0
.github/workflows/nodejs.yml

@@ -0,0 +1,46 @@
+# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
+
+name: Node.js CI
+
+on:
+  push:
+    branches:
+      - main
+      - master
+  pull_request:
+    branches:
+      - main
+      - master
+  schedule:
+    - cron: '0 2 * * *'
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      fail-fast: false
+      matrix:
+        node-version: [10]
+        os: [ubuntu-latest, windows-latest, macos-latest]
+
+    steps:
+    - name: Checkout Git Source
+      uses: actions/checkout@v2
+
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+
+    - name: Install Dependencies
+      run: npm i -g npminstall && npminstall
+
+    - name: Continuous Integration
+      run: npm run ci
+
+    - name: Code Coverage
+      uses: codecov/codecov-action@v1
+      with:
+        token: ${{ secrets.CODECOV_TOKEN }}

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+logs/
+npm-debug.log
+yarn-error.log
+node_modules/
+package-lock.json
+yarn.lock
+coverage/
+.idea/
+run/
+.DS_Store
+*.sw*
+*.un~
+typings/
+.nyc_output/

+ 12 - 0
.travis.yml

@@ -0,0 +1,12 @@
+
+language: node_js
+node_js:
+  - '10'
+before_install:
+  - npm i npminstall -g
+install:
+  - npminstall
+script:
+  - npm run ci
+after_script:
+  - npminstall codecov && codecov

+ 33 - 0
README.md

@@ -0,0 +1,33 @@
+# server-user
+
+服务-用户管理
+
+## QuickStart
+
+<!-- add docs here for user -->
+
+see [egg docs][egg] for more detail.
+
+### Development
+
+```bash
+$ npm i
+$ npm run dev
+$ open http://localhost:7001/
+```
+
+### Deploy
+
+```bash
+$ npm start
+$ npm stop
+```
+
+### npm scripts
+
+- Use `npm run lint` to check code style.
+- Use `npm test` to run unit test.
+- Use `npm run autod` to auto detect dependencies upgrade, see [autod](https://www.npmjs.com/package/autod) for more detail.
+
+
+[egg]: https://eggjs.org

+ 19 - 0
app.js

@@ -0,0 +1,19 @@
+'use strict';
+class AppBootHook {
+  constructor(app) {
+    this.app = app;
+  }
+
+  async willReady() {
+    // 应用准备启动,准备默认数据
+  }
+
+  async serverDidReady() {
+    // 应用已经启动完毕
+    // const ctx = await this.app.createAnonymousContext();
+    // // 检查种子
+    // // await ctx.service.install.index();
+    // await ctx.service.util.rabbitMq.mission();
+  }
+}
+module.exports = AppBootHook;

+ 13 - 0
app/controller/apply.js

@@ -0,0 +1,13 @@
+'use strict';
+const meta = require('./config/.apply.js');
+const Controller = require('egg').Controller;
+const { CrudController } = require('naf-framework-mongoose-free/lib/controller');
+
+// 申请
+class ApplyController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.service = this.ctx.service.apply;
+  }
+}
+module.exports = CrudController(ApplyController, meta);

+ 13 - 0
app/controller/checkRecord.js

@@ -0,0 +1,13 @@
+'use strict';
+const meta = require('./config/.checkRecord.js');
+const Controller = require('egg').Controller;
+const { CrudController } = require('naf-framework-mongoose-free/lib/controller');
+
+// 审核记录
+class CheckRecordController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.service = this.ctx.service.checkRecord;
+  }
+}
+module.exports = CrudController(CheckRecordController, meta);

+ 59 - 0
app/controller/config/.apply.js

@@ -0,0 +1,59 @@
+module.exports = {
+  create: {
+    requestBody: [
+      "user_id",
+      "status",
+      "basic",
+      "brief",
+      "research",
+      "datalist",
+      "file",
+      "remark",
+      "create_time",
+    ],
+  },
+  destroy: {
+    params: ["!id"],
+    service: "delete",
+  },
+  update: {
+    params: ["!id"],
+    requestBody: [
+      "user_id",
+      "status",
+      "basic",
+      "brief",
+      "research",
+      "datalist",
+      "file",
+      "remark",
+      "create_time",
+    ],
+  },
+  show: {
+    parameters: {
+      params: ["!id"],
+    },
+    service: "fetch",
+  },
+  index: {
+    parameters: {
+      query: {
+        user_id: "user_id",
+        status: "status",
+        "meta.createdAt@start": "meta.createdAt@start",
+        "meta.createdAt@end": "meta.createdAt@end",
+      },
+      // options: {
+      //   "meta.state": 0 // 默认条件
+      // },
+    },
+    service: "query",
+    options: {
+      query: ["skip", "limit"],
+      sort: ["meta.createdAt"],
+      desc: true,
+      count: true,
+    },
+  },
+};

+ 55 - 0
app/controller/config/.checkRecord.js

@@ -0,0 +1,55 @@
+module.exports = {
+  create: {
+    requestBody: [
+      "apply_id",
+      "desc",
+      "status",
+      "verify_id",
+      "verify_phone",
+      "verify",
+      "step",
+    ],
+  },
+  destroy: {
+    params: ["!id"],
+    service: "delete",
+  },
+  update: {
+    params: ["!id"],
+    requestBody: [
+      "apply_id",
+      "desc",
+      "status",
+      "verify_id",
+      "verify_phone",
+      "verify",
+      "step",
+    ],
+  },
+  show: {
+    parameters: {
+      params: ["!id"],
+    },
+    service: "fetch",
+  },
+  index: {
+    parameters: {
+      query: {
+        apply_id: "apply_id",
+        step: "step",
+        "meta.createdAt@start": "meta.createdAt@start",
+        "meta.createdAt@end": "meta.createdAt@end",
+      },
+      // options: {
+      //   "meta.state": 0 // 默认条件
+      // },
+    },
+    service: "query",
+    options: {
+      query: ["skip", "limit"],
+      sort: ["meta.createdAt"],
+      desc: true,
+      count: true,
+    },
+  },
+};

+ 54 - 0
app/controller/config/.reviewExpert.js

@@ -0,0 +1,54 @@
+module.exports = {
+  create: {
+    requestBody: ['expert_id', 'expert_name', 'phone', 'password', 'company', 'group_zw', 'major', 'now_major', 'zw', 'zc', 'apply_id', 'score', 'desc', 'type'],
+  },
+  destroy: {
+    params: ['!id'],
+    service: 'delete',
+  },
+  update: {
+    params: ['!id'],
+    requestBody: ['expert_id', 'expert_name', 'phone', 'password', 'company', 'group_zw', 'major', 'now_major', 'zw', 'zc', 'apply_id', 'score', 'desc', 'type'],
+  },
+  show: {
+    parameters: {
+      params: ['!id'],
+    },
+    service: 'fetch',
+  },
+  index: {
+    parameters: {
+      query: {
+        expert_id: 'expert_id',
+        expert_name: 'expert_name',
+        apply_id: 'apply_id',
+        score: 'score',
+        desc: 'desc',
+        type: 'type',
+        'meta.createdAt@start': 'meta.createdAt@start',
+        'meta.createdAt@end': 'meta.createdAt@end',
+      },
+      // options: {
+      //   "meta.state": 0 // 默认条件
+      // },
+    },
+    service: 'query',
+    options: {
+      query: ['skip', 'limit'],
+      sort: ['meta.createdAt'],
+      desc: true,
+      count: true,
+    },
+  },
+  // 登陆
+  login: {
+    requestBody: ['!phone', '!password'],
+    service: 'login',
+  },
+  //修改密码
+  password: {
+    params: ['!id'],
+    requestBody: ['password'],
+    service: 'password',
+  },
+};

+ 33 - 0
app/controller/home.js

@@ -0,0 +1,33 @@
+'use strict';
+const Controller = require('egg').Controller;
+const { CrudController } = require('naf-framework-mongoose-free/lib/controller');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+
+// 项目测试及管理员登陆
+class HomeController extends Controller {
+  async index() {
+    const { ctx } = this;
+    ctx.body = 'hi, egg';
+  }
+  /**
+   * 系统管理员登陆
+   * 太简单了,就不写service了,直接在这处理完完事了
+   */
+  async login() {
+    let admin = await this.ctx.model.Admin.findOne({}, '+password').exec();
+    if (!admin) throw new BusinessError(ErrorCode.FILE_FAULT, '未初始化管理员,拒绝请求!');
+    const { account, password } = this.ctx.request.body;
+    if (!account) throw new BusinessError(ErrorCode.BADPARAM, '未找到要登陆用户账号');
+    if (!password) throw new BusinessError(ErrorCode.BADPARAM, '未找到要登陆用户密码');
+    if (admin.account !== account) throw new BusinessError(ErrorCode.USER_NOT_EXIST, '未找到要登录的用户');
+    if (admin.password.secret !== password) throw new BusinessError(ErrorCode.BAD_PASSWORD, '密码错误');
+    admin = JSON.parse(JSON.stringify(admin));
+    delete admin.password;
+    delete admin.meta;
+    delete admin.__v;
+    delete admin._id;
+    const token = this.ctx.service.util.jwt.encrypt(admin);
+    this.ctx.ok({ data: token });
+  }
+}
+module.exports = CrudController(HomeController, {});

+ 13 - 0
app/controller/reviewExpert.js

@@ -0,0 +1,13 @@
+'use strict';
+const meta = require('./config/.reviewExpert.js');
+const Controller = require('egg').Controller;
+const { CrudController } = require('naf-framework-mongoose-free/lib/controller');
+
+// 评审专家
+class ReviewExpertController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.service = this.ctx.service.reviewExpert;
+  }
+}
+module.exports = CrudController(ReviewExpertController, meta);

+ 53 - 0
app/middleware/request-log.js

@@ -0,0 +1,53 @@
+'use strict';
+module.exports = ({ toMongoDB = false }) =>
+  async function requestLog(ctx, next) {
+    await next();
+    try {
+      const request = ctx.request;
+      // 请求路由
+      const url = request.url;
+      // 请求方法
+      const method = request.method;
+      // get 请求不发日志
+      if (method === 'GET') return;
+      // 请求的分站标识
+      const tenant = ctx.tenant || 'master';
+      // 请求的用户
+      const user = ctx.user;
+      // 当前模块
+      const module = ctx.app.config.module;
+      // 所有的请求路由
+      const routers = ctx.router.stack;
+      // 匹配路由及http请求方法
+      const route = routers.find(route => {
+        const reg = new RegExp(route.regexp);
+        // 正则验证
+        const regResult = reg.test(url);
+        // http方法验证
+        const methodResult = route.methods.includes(method);
+        if (regResult && methodResult) return true;
+        return false;
+      });
+      if (!route) return;
+      // 不往数据库里写,就回去
+      if (!toMongoDB) return;
+      // 组织数据,给MQ,让日志服务去写
+      const { id: user_id, account, name } = user;
+      const logData = { user_id, account, name, _tenant: tenant, opera: route.name, path: url, module };
+      // 先用mq发送,不行再用http发
+      if (ctx.mq) {
+        ctx.service.util.rabbitMq.sendToMqQueue(logData, 'logs');
+      } else {
+        // http请求
+        const httpPrefix = ctx.app.config.httpPrefix;
+        if (httpPrefix && httpPrefix.logs) {
+          const uri = `${httpPrefix.logs}/logs`;
+          ctx.service.util.httpUtil.cpost(uri, logData);
+        }
+      }
+    } catch (error) {
+      // 没啥可输出,别中断就行
+    }
+
+
+  };

+ 149 - 0
app/model/apply.js

@@ -0,0 +1,149 @@
+'use strict';
+const Schema = require('mongoose').Schema;
+const moment = require('moment');
+const metaPlugin = require('naf-framework-mongoose-free/lib/model/meta-plugin');
+const { ObjectId } = require('mongoose').Types;
+// 基本信息
+const basic = new Schema({
+  achieve_name: { type: String }, // 成果名称
+  achieve_type: { type: Array }, // 成果类别
+  achieve_num: { type: String }, // 成果编号
+  achieve_date: { type: String }, // 成果取得时间
+  achieve_form: { type: Array }, // 成果形式
+  apply_personal: { type: String }, // 申请人
+  apply_phone: { type: String }, // 申请人
+  apply_company: { type: String }, // 申请单位
+  address: { type: String }, // 地址
+  apply_nature: { type: String }, // 申请单位/申请人属性
+  contacts: { type: String }, // 联系人
+  phone: { type: String }, // 联系电话
+  email: { type: String }, // 邮箱
+  fax: { type: String }, // 传真
+  objective: { type: String }, // 评价目的
+  stage: { type: String }, // 成果所处阶段
+  output: { type: String }, // 经济效益产值
+  profit: { type: String }, // 经济效益利润
+  revenue: { type: String }, // 经济效益税收
+  // 2021-04-13添加
+  cert_jfh: { type: String }, // 吉发号
+  cert_num: { type: String }, // 第几号
+  cert_sign: { type: String }, // 签字
+
+  szd: { type: String }, // 所在地
+  yb: { type: String }, // 邮编
+  ls: { type: String }, // 隶属
+  oneCom_name: { type: String }, // 参加单位1——名称
+  oneCom_szd: { type: String }, // 参加单位1——所在地
+  twoCom_name: { type: String }, // 参加单位2——名称
+  twoCom_szd: { type: String }, // 参加单位2——所在地
+
+  shxy: { type: String }, // 社会效益
+  // 国家奖励
+  gjjl_num: { type: String }, // 国家奖励项
+  gjjl_name: { type: String }, // 国家奖励名称
+  gjjl_grade: { type: String }, // 国家奖励等级
+
+  sjjl_num: { type: String }, // 省级奖励项
+  sjjl_name: { type: String }, // 省级奖励名称
+  sjjl_grade: { type: String }, // 省级奖励等级
+  // 计划支持
+  gjjh_num: { type: String }, // 国家计划项
+  gjjh_money: { type: String }, // 国家计划经费
+
+  sjjh_num: { type: String }, // 省级计划项
+  sjjh_money: { type: String }, // 省级计划经费
+  achieve_influence: { type: Array }, // 成果的影响及作用
+});
+basic.index({ id: 1 });
+basic.index({ achieve_num: 1 });
+
+// 内容简介
+const brief = new Schema({
+  achieve_brief: { type: String }, // 成果简介
+  field: { type: String }, // 应用领域和技术原理
+  kpi_index: { type: String }, // 性能指标
+  compare: { type: String }, // 与国内外同类技术比较
+  advanced: { type: String }, // 成果的创造性,先进性
+  sense: { type: String }, // 作用意义
+  prospect: { type: String }, // 推广应用的范围,条件和前景
+  opinion: { type: String }, // 存在的问题和改进意见
+});
+brief.index({ id: 1 });
+
+// 主研人员名单
+const research = new Schema({
+  name: { type: String }, // 姓名
+  gender: { type: String }, // 性别
+  card: { type: String }, // 证件号码
+  birth: { type: String }, // 出生年月
+  age: { type: String }, // 年龄
+  education: { type: String }, // 文化程度
+  degree: { type: String }, // 学位
+  major: { type: String }, // 从事专业
+  zw: { type: String }, // 职务
+  zc: { type: String }, // 职称
+  company: { type: String }, // 工作单位
+  abroad: { type: String }, // 是否留学归国
+  work: { type: String }, // 项目中所承担的主要工作
+  devote: { type: String }, // 对成果创造性贡献
+  phone: { type: String }, // 电话
+  email: { type: String }, // 邮箱
+});
+research.index({ id: 1 });
+
+// 委托方提供资料清单
+const datalist = new Schema({
+  work_report: { type: Array }, // 工作报告(必备)
+  techol_report: { type: Array }, // 技术报告(必备)
+  compare_report: { type: Array }, // 国内外对比报告(必备)
+  benefit: { type: Array }, // 经济效益分析(必备)
+  science_report: { type: Array }, // 科技查新报告(必备)
+  techol_detect_report: { type: Array }, // 技术检测报告
+  user_prove: { type: Array }, // 用户证明
+  patent_cert: { type: Array }, // 专利证书
+  software_copyright: { type: Array }, // 软著
+  treatise: { type: Array }, // 论文
+  gf: { type: Array }, // 工法
+  company_standard: { type: Array }, // 企业标准等证明材料
+});
+datalist.index({ id: 1 });
+
+// 上传文件
+const file = new Schema({
+  page5: { type: Array }, // 现场测试(检测,测产)意见
+  page6: { type: Array }, // 评价意见
+  nameList: { type: Array }, // 评价专家组名单
+});
+file.index({ id: 1 });
+
+// 成果评价申请表
+const apply = {
+  user_id: { type: String }, // 关联用户
+  status: { type: String, default: '0' }, // 状态
+  // 1=>初审通过(待评分);-1=>初审失败
+  // 2=>评分通过(待缴费);-2=>评分失败
+  // 3=>已缴费(状态)
+  // 4=>补充资料
+  // 5=>会审通过;-5=>会审失败(不能改了,没机会了)
+  // 6=>证书发放
+  basic: { type: basic },
+  brief: { type: brief },
+  research: { type: [research] },
+  datalist: { type: datalist },
+  file: { type: file, default: {} }, // 上传文件
+  remark: { type: String, maxLength: 200 },
+  create_time: {
+    type: String,
+    default: moment().format('YYYY-MM-DD HH:mm:ss'),
+  },
+};
+const schema = new Schema(apply, { toJSON: { virtuals: true } });
+schema.index({ id: 1 });
+schema.index({ user_id: 1 });
+schema.index({ status: 1 });
+schema.index({ 'meta.createdAt': 1 });
+schema.plugin(metaPlugin);
+module.exports = (app) => {
+  const { mongoose } = app;
+  return mongoose.model('Apply', schema, 'apply');
+};

+ 25 - 0
app/model/checkRecord.js

@@ -0,0 +1,25 @@
+'use strict';
+const Schema = require('mongoose').Schema;
+const moment = require('moment');
+const metaPlugin = require('naf-framework-mongoose-free/lib/model/meta-plugin');
+const { ObjectId } = require('mongoose').Types;
+// 审核记录表
+const check_record = {
+  apply_id: { type: String }, // 成果申请数据id
+  desc: { type: String }, // 本次审核的意见
+  status: { type: String }, // 本次审核的结果
+  verify_id: { type: String }, // 本次审核人
+  verify_phone: { type: String }, // 本次审核人的联系电话
+  verify: { type: String }, // 本次审核人
+  step: { type: String }, // 初审=>评分=>会审=>资料/发证书
+  remark: { type: String },
+};
+const schema = new Schema(check_record, { toJSON: { virtuals: true } });
+schema.index({ id: 1 });
+schema.index({ apply_id: 1 });
+schema.index({ 'meta.createdAt': 1 });
+schema.plugin(metaPlugin);
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('CheckRecord', schema, 'checkRecord');
+};

+ 34 - 0
app/model/reviewExpert.js

@@ -0,0 +1,34 @@
+'use strict';
+const Schema = require('mongoose').Schema;
+const moment = require('moment');
+const metaPlugin = require('naf-framework-mongoose-free/lib/model/meta-plugin');
+const { Secret } = require('naf-framework-mongoose-free/lib/model/schema');
+const { ObjectId } = require('mongoose').Types;
+// 评审专家表
+const review_expert = {
+  expert_id: { type: String, required: false }, // 专家id
+  expert_name: { type: String, required: false }, // 专家姓名
+  phone: { type: String, required: false }, // 联系电话
+  password: { type: Secret, select: false }, // 密码
+  company: { type: String, required: false }, // 工作单位
+  group_zw: { type: String, required: false }, // 评价专家组职务
+  major: { type: String, required: false }, // 所学专业
+  now_major: { type: String, required: false }, // 现从事专业
+  zw: { type: String, required: false }, // 职务
+  zc: { type: String, required: false }, // 职称
+  apply_id: { type: String, required: false }, // 成果申请id
+  score: { type: String, required: false }, // 分数
+  desc: { type: String, required: false }, // 意见
+  type: { type: String }, // 工作类型:1=>评分;2=>会审
+  remark: { type: String },
+};
+const schema = new Schema(review_expert, { toJSON: { virtuals: true } });
+schema.index({ id: 1 });
+schema.index({ expert_id: 1 });
+schema.index({ apply_id: 1 });
+schema.index({ 'meta.createdAt': 1 });
+schema.plugin(metaPlugin);
+module.exports = (app) => {
+  const { mongoose } = app;
+  return mongoose.model('ReviewExpert', schema, 'reviewExpert');
+};

+ 28 - 0
app/router.js

@@ -0,0 +1,28 @@
+'use strict';
+
+/**
+ * @param {Egg.Application} app - egg application
+ */
+const os = require('os');
+function getIPAdress() {
+  const interfaces = os.networkInterfaces();
+  for (const devName in interfaces) {
+    const iface = interfaces[devName];
+    for (let i = 0; i < iface.length; i++) {
+      const alias = iface[i];
+      if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
+        return alias.address;
+      }
+    }
+  }
+}
+module.exports = app => {
+  const { router, controller } = app;
+  const { routePrefix, cluster } = app.config;
+  const ipAddress = getIPAdress();
+  console.log(`前缀:http://${ipAddress}:${cluster.listen.port}${routePrefix}`);
+  router.get('/', controller.home.index);
+  require('./z_router/apply')(app); // 申请
+  require('./z_router/checkRecord')(app); // 审核
+  require('./z_router/reviewExpert')(app); // 评审专家
+};

+ 15 - 0
app/service/apply.js

@@ -0,0 +1,15 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const _ = require('lodash');
+const assert = require('assert');
+
+// 申请
+class ApplyService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'apply');
+    this.model = this.ctx.model.Apply;
+  }
+}
+
+module.exports = ApplyService;

+ 15 - 0
app/service/checkRecord.js

@@ -0,0 +1,15 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const _ = require('lodash');
+const assert = require('assert');
+
+// 审核记录
+class CheckRecordService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'checkrecord');
+    this.model = this.ctx.model.CheckRecord;
+  }
+}
+
+module.exports = CheckRecordService;

+ 45 - 0
app/service/reviewExpert.js

@@ -0,0 +1,45 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const _ = require('lodash');
+const assert = require('assert');
+const jwt = require('jsonwebtoken');
+
+// 专家评审
+class ReviewExpertService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'reviewexpert');
+    this.model = this.ctx.model.ReviewExpert;
+  }
+
+  /**
+   * 登陆
+   * @param {Object} params 登陆信息
+   * @property phone 手机号
+   * @property password 密码
+   */
+  async login({ phone, password }) {
+    const object = await this.model.findOne({ phone }, '+password');
+    if (!object) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到用户的信息');
+    const { password: op, status } = object;
+    const { secret } = op;
+    if (status !== '1') throw new BusinessError(ErrorCode.ACCESS_DENIED, '拒绝访问!');
+    if (secret !== password) throw new BusinessError(ErrorCode.BAD_PASSWORD, '密码错误');
+    const data = _.omit(JSON.parse(JSON.stringify(object)), [ 'meta', 'password', '__v' ]);
+    const { secret: secrets } = this.config.jwt;
+    const token = jwt.sign(data, secrets);
+    return token;
+  }
+  /**
+   * 修改密码
+   * @param {Object} {id,password} 用户id和密码
+   */
+  async password({ id, password }) {
+    const object = await this.model.findById(id);
+    if (!object) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到用户的信息');
+    object.password = { secret: password };
+    await object.save();
+  }
+}
+
+module.exports = ReviewExpertService;

+ 87 - 0
app/service/util/http-util.js

@@ -0,0 +1,87 @@
+'use strict';
+const { AxiosService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const { isNullOrUndefined } = require('naf-core').Util;
+const _ = require('lodash');
+
+//
+class HttpUtilService extends AxiosService {
+  constructor(ctx) {
+    super(ctx, {}, {});
+  }
+
+
+  // 替换uri中的参数变量
+  merge(uri, query = {}) {
+    const keys = Object.keys(query);
+    const arr = [];
+    for (const k of keys) {
+      arr.push(`${k}=${query[k]}`);
+    }
+    if (arr.length > 0) {
+      uri = `${uri}?${arr.join('&')}`;
+    }
+    return uri;
+  }
+
+  /**
+   * curl-get请求
+   * @param {String} uri 接口地址
+   * @param {Object} query 地址栏参数
+   * @param {Object} options 额外参数
+   */
+  async cget(uri, query, options) {
+    return this.toRequest(uri, null, query, options);
+  }
+
+  /**
+   * curl-post请求
+   * @param {String} uri 接口地址
+   * @param {Object} data post的body
+   * @param {Object} query 地址栏参数
+   * @param {Object} options 额外参数
+   */
+  async cpost(uri, data = {}, query, options) {
+    return this.toRequest(uri, data, query, options);
+  }
+
+  async toRequest(uri, data, query, options) {
+    query = _.pickBy(
+      query,
+      val => val !== '' && val !== 'undefined' && val !== 'null'
+    );
+    if (!uri) console.error('uri不能为空');
+    if (_.isObject(query) && _.isObject(options)) {
+      const params = query.params ? query.params : query;
+      options = { ...options, params };
+    } else if (_.isObject(query) && !query.params) {
+      options = { params: query };
+    } else if (_.isObject(query) && query.params) {
+      options = query;
+    }
+    const headers = { 'content-type': 'application/json' };
+    console.log(uri);
+    const url = this.merge(`${uri}`, options.params);
+    let res = await this.ctx.curl(url, {
+      method: isNullOrUndefined(data) ? 'get' : 'post',
+      url,
+      data,
+      dataType: 'json',
+      headers,
+      ...options,
+    });
+    if (res.status === 200) {
+      res = res.data || {};
+      const { errcode, errmsg, details } = res;
+      if (errcode) {
+        console.warn(`[${uri}] fail: ${errcode}-${errmsg} ${details}`);
+        return { errcode, errmsg };
+      }
+      return res.data;
+    }
+    const { status } = res;
+    console.warn(`[${uri}] fail: ${status}-${res.data.message} `);
+  }
+}
+
+module.exports = HttpUtilService;

+ 18 - 0
app/service/util/jwt.js

@@ -0,0 +1,18 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const jwt = require('jsonwebtoken');
+
+// jsonWebToken处理
+class JwtService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'jwt');
+  }
+
+  encrypt(data) {
+    const { secret } = this.config.jwt;
+    const token = jwt.sign(data, secret);
+    return token;
+  }
+}
+
+module.exports = JwtService;

+ 74 - 0
app/service/util/rabbitMq.js

@@ -0,0 +1,74 @@
+'use strict';
+
+const Service = require('egg').Service;
+const _ = require('lodash');
+
+class RabbitmqService extends Service {
+  // mission队列处理
+  async mission() {
+    const { mq } = this.ctx;
+    const { queue } = this.ctx.app.config;
+    if (mq && queue) {
+      const ch = await mq.conn.createChannel();
+      // const queue = 'freeAdmin/server-user';
+      try {
+        // 创建队列:在没有队列的情况,直接获取会导致程序无法启动
+        await ch.assertQueue(queue, { durable: false });
+        await ch.consume(queue, msg => this.dealMission(msg), { noAck: true });
+      } catch (error) {
+        this.ctx.logger.error('未找到订阅的队列');
+      }
+    } else {
+      this.ctx.logger.error('!!!!!!没有配置MQ插件!!!!!!');
+    }
+  }
+  // 执行任务
+  async dealMission(bdata) {
+    //   if (!bdata) this.ctx.logger.error('mission队列中信息不存在');
+    //   let data = bdata.content.toString();
+    //   try {
+    //     data = JSON.parse(data);
+    //   } catch (error) {
+    //     this.ctx.logger.error('数据不是object');
+    //   }
+    //   const { service, method, project, ...others } = data;
+    //   const arr = service.split('.');
+    //   let s = this.ctx.service;
+    //   for (const key of arr) {
+    //     s = s[key];
+    //   }
+    //   s[method](others);
+  }
+
+  /**
+   * 发送队列消息
+   * @param {Any} data 消息队列数据
+   * @param {String} queueKey 消息队列可以
+   */
+  async sendToMqQueue(data, queueKey) {
+    const { mq } = this.ctx;
+    const { sendQueue } = this.ctx.app.config;
+    let queue;
+    // 获取队列名称
+    if (_.isObject(sendQueue)) {
+      queue = sendQueue[queueKey];
+    }
+    if (mq && queue) {
+      if (!_.isString(data)) data = JSON.stringify(data);
+      const ch = await mq.conn.createChannel();
+      try {
+        // 创建队列:在没有队列的情况,直接获取会导致程序无法启动
+        // await ch.assertQueue(queue, { durable: false });
+        await ch.sendToQueue(queue, Buffer.from(data));
+        await ch.close();
+      } catch (error) {
+        console.error(error);
+        this.ctx.logger.error('mq消息发送失败');
+      }
+    } else {
+      this.ctx.logger.error('!!!!!!没有配置MQ插件!!!!!!');
+    }
+  }
+}
+
+module.exports = RabbitmqService;

+ 29 - 0
app/z_router/apply.js

@@ -0,0 +1,29 @@
+'use strict';
+// 路由配置
+const rkey = 'apply';
+const ckey = 'apply';
+const keyZh = '成果申请';
+const routes = [
+  { method: 'get', path: `${rkey}`, controller: `${ckey}.index`, name: `${ckey}Query`, zh: `${keyZh}列表查询` },
+  { method: 'get', path: `${rkey}/:id`, controller: `${ckey}.show`, name: `${ckey}Show`, zh: `${keyZh}查询` },
+  { method: 'post', path: `${rkey}`, controller: `${ckey}.create`, name: `${ckey}Create`, zh: `创建${keyZh}` },
+  { method: 'post', path: `${rkey}/:id`, controller: `${ckey}.update`, name: `${ckey}Update`, zh: `修改${keyZh}` },
+  { method: 'delete', path: `${rkey}/:id`, controller: `${ckey}.destroy`, name: `${ckey}Delete`, zh: `删除${keyZh}` },
+];
+
+module.exports = app => {
+  const { router, config } = app;
+  const mwares = app.middleware;
+  console.log(`${keyZh}:  ${rkey}`);
+  for (const route of routes) {
+    const { method, path, controller: ctl, zh } = route;
+    let { middleware = [] } = route;
+    if (!method || !path || !ctl) continue;
+    // 拼全路径
+    const allPath = `${config.routePrefix}/${path}`;
+    // 处理中间件
+    if (middleware.length > 0) middleware = middleware.map(i => mwares[i]({ enable: true }));
+    // 注册路由
+    router[method](zh, allPath, ...middleware, ctl);
+  }
+};

+ 29 - 0
app/z_router/checkRecord.js

@@ -0,0 +1,29 @@
+'use strict';
+// 路由配置
+const rkey = 'checkRecord';
+const ckey = 'checkRecord';
+const keyZh = '审核记录';
+const routes = [
+  { method: 'get', path: `${rkey}`, controller: `${ckey}.index`, name: `${ckey}Query`, zh: `${keyZh}列表查询` },
+  { method: 'get', path: `${rkey}/:id`, controller: `${ckey}.show`, name: `${ckey}Show`, zh: `${keyZh}查询` },
+  { method: 'post', path: `${rkey}`, controller: `${ckey}.create`, name: `${ckey}Create`, zh: `创建${keyZh}` },
+  { method: 'post', path: `${rkey}/:id`, controller: `${ckey}.update`, name: `${ckey}Update`, zh: `修改${keyZh}` },
+  { method: 'delete', path: `${rkey}/:id`, controller: `${ckey}.destroy`, name: `${ckey}Delete`, zh: `删除${keyZh}` },
+];
+
+module.exports = app => {
+  const { router, config } = app;
+  const mwares = app.middleware;
+  console.log(`${keyZh}:  ${rkey}`);
+  for (const route of routes) {
+    const { method, path, controller: ctl, zh } = route;
+    let { middleware = [] } = route;
+    if (!method || !path || !ctl) continue;
+    // 拼全路径
+    const allPath = `${config.routePrefix}/${path}`;
+    // 处理中间件
+    if (middleware.length > 0) middleware = middleware.map(i => mwares[i]({ enable: true }));
+    // 注册路由
+    router[method](zh, allPath, ...middleware, ctl);
+  }
+};

+ 31 - 0
app/z_router/reviewExpert.js

@@ -0,0 +1,31 @@
+'use strict';
+// 路由配置
+const rkey = 'reviewExpert';
+const ckey = 'reviewExpert';
+const keyZh = '评审专家';
+const routes = [
+  { method: 'post', path: `${rkey}/login`, controller: `${ckey}.login`, name: `${ckey}Login`, zh: `${keyZh}登陆` },
+  { method: 'post', path: `${rkey}/password/:id`, controller: `${ckey}.password`, name: `${ckey}Password`, zh: `${keyZh}修改密码` },
+  { method: 'get', path: `${rkey}`, controller: `${ckey}.index`, name: `${ckey}Query`, zh: `${keyZh}列表查询` },
+  { method: 'get', path: `${rkey}/:id`, controller: `${ckey}.show`, name: `${ckey}Show`, zh: `${keyZh}查询` },
+  { method: 'post', path: `${rkey}`, controller: `${ckey}.create`, middleware: [ 'password' ], name: `${ckey}Create`, zh: `创建${keyZh}` },
+  { method: 'post', path: `${rkey}/:id`, controller: `${ckey}.update`, name: `${ckey}Update`, zh: `修改${keyZh}` },
+  { method: 'delete', path: `${rkey}/:id`, controller: `${ckey}.destroy`, name: `${ckey}Delete`, zh: `删除${keyZh}` },
+];
+
+module.exports = app => {
+  const { router, config } = app;
+  const mwares = app.middleware;
+  console.log(`${keyZh}:  ${rkey}`);
+  for (const route of routes) {
+    const { method, path, controller: ctl, zh } = route;
+    let { middleware = [] } = route;
+    if (!method || !path || !ctl) continue;
+    // 拼全路径
+    const allPath = `${config.routePrefix}/${path}`;
+    // 处理中间件
+    if (middleware.length > 0) middleware = middleware.map(i => mwares[i]({ enable: true }));
+    // 注册路由
+    router[method](zh, allPath, ...middleware, ctl);
+  }
+};

+ 14 - 0
appveyor.yml

@@ -0,0 +1,14 @@
+environment:
+  matrix:
+    - nodejs_version: '10'
+
+install:
+  - ps: Install-Product node $env:nodejs_version
+  - npm i npminstall && node_modules\.bin\npminstall
+
+test_script:
+  - node --version
+  - npm --version
+  - npm run test
+
+build: off

+ 100 - 0
config/config.default.js

@@ -0,0 +1,100 @@
+/* eslint valid-jsdoc: "off" */
+
+'use strict';
+
+/**
+ * @param {Egg.EggAppInfo} appInfo app info
+ */
+const { jwt } = require('./config.secret');
+module.exports = appInfo => {
+  /**
+   * built-in config
+   * @type {Egg.EggAppConfig}
+   **/
+  const config = (exports = {});
+
+  // use for cookie sign key, should change to your own and keep security
+  config.keys = appInfo.name + '_1640765284662_2781';
+
+  // add your middleware config here
+  config.middleware = [ 'requestLog' ];
+
+  // add your user config here
+  const userConfig = {
+    // myAppName: 'egg',
+  };
+  // 日志
+  config.logger = {
+    level: 'DEBUG',
+    allowDebugAtProd: true,
+  };
+  // mq设置
+  config.amqp = {
+    client: {
+      hostname: '127.0.0.1',
+      username: 'freeAdmin',
+      password: '1qaz2wsx',
+      vhost: 'freeAdmin',
+    },
+    app: true,
+    agent: true,
+  };
+  // 接收队列名称
+  config.queue = 'freeAdmin/server-user';
+  // 发送队列名称
+  config.sendQueue = {
+    logs: 'freeAdmin/server-logs',
+  };
+  // http请求前缀
+  config.httpPrefix = {
+    base: 'http://127.0.0.1/api/live/v1',
+    logs: 'http://localhost:13002/freeAdminLog/api',
+  };
+  // redis设置
+  config.redis = {
+    client: {
+      port: 6379, // Redis port
+      host: '127.0.0.1', // Redis host
+      password: '123456',
+      db: 0,
+    },
+  };
+  // 进程设置
+  config.cluster = {
+    listen: {
+      port: 9302,
+    },
+  };
+
+  // jwt设置
+  config.jwt = {
+    ...jwt,
+    expiresIn: '1d',
+    issuer: 'platform-v1',
+  };
+
+  // 数据库设置
+  config.dbName = 'platform-achieve-v1';
+  config.mongoose = {
+    url: `mongodb://localhost:27017/${config.dbName}`,
+    options: {
+      user: 'admin',
+      pass: 'admin',
+      authSource: 'admin',
+      useNewUrlParser: true,
+      useCreateIndex: true,
+    },
+  };
+  // 路由设置
+  config.routePrefix = '/api/achieve/v1';
+
+  // 中间件
+  config.requestLog = {
+    toMongoDB: false,
+  };
+  config.module = 'achieve';
+  return {
+    ...config,
+    ...userConfig,
+  };
+};

+ 42 - 0
config/config.prod.js

@@ -0,0 +1,42 @@
+'use strict';
+
+module.exports = () => {
+  const config = (exports = {});
+
+  config.logger = {
+    level: 'INFO',
+    consoleLevel: 'INFO',
+  };
+  // config.dbName = 'new-platform';
+  // config.mongoose = {
+  //   url: `mongodb://localhost:27017/${config.dbName}`,
+  //   options: {
+  //     user: 'admin',
+  //     pass: 'admin',
+  //     authSource: 'admin',
+  //     useNewUrlParser: true,
+  //     useCreateIndex: true,
+  //   },
+  // };
+  // // redis config
+  // config.redis = {
+  //   client: {
+  //     port: 6379, // Redis port
+  //     host: '127.0.0.1', // Redis host
+  //     password: 123456,
+  //     db: 0,
+  //   },
+  // };
+
+  // config.export = {
+  //   root_path: 'D:\\free\\workspace\\server\\service-file\\upload',
+  //   export_path: 'D:\\free\\workspace\\server\\service-file\\upload\\export',
+  //   export_dir: 'export',
+  //   patentInfo_dir: 'patentInfo',
+  //   domain: 'http://127.0.0.1',
+  // };
+  // config.import = {
+  //   root_path: 'D:\\free\\workspace\\server\\service-file\\upload',
+  // };
+  return config;
+};

+ 7 - 0
config/config.secret.js

@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = {
+  jwt: {
+    secret: 'Ziyouyanfa!@#',
+  },
+};

+ 15 - 0
config/plugin.js

@@ -0,0 +1,15 @@
+'use strict';
+// rabbitMq
+// exports.amqp = {
+//   enable: true,
+//   package: 'egg-naf-amqp',
+// };
+// // redis
+// exports.redis = {
+//   enable: true,
+//   package: 'egg-redis',
+// };
+// // 分站模式
+// exports.multiTenancy = {
+//   enable: true,
+// };

+ 17 - 0
ecosystem.config.js

@@ -0,0 +1,17 @@
+'use strict';
+
+const app = 'zkzx-v2-server-achieve';
+module.exports = {
+  apps: [{
+    name: app, // 应用名称
+    script: './server.js', // 实际启动脚本
+    out: `./logs/${app}.log`,
+    error: `./logs/${app}.err`,
+    watch: [ // 监控变化的目录,一旦变化,自动重启
+      'app', 'config',
+    ],
+    env: {
+      NODE_ENV: 'production', // 环境参数,当前指定为生产环境
+    },
+  }],
+};

+ 52 - 0
package.json

@@ -0,0 +1,52 @@
+{
+  "name": "zkzx-v2-server-achieve",
+  "version": "1.0.0",
+  "description": "服务-成果服务",
+  "private": true,
+  "egg": {
+    "framework": "naf-framework-mongoose-free"
+  },
+  "dependencies": {
+    "egg": "^2.15.1",
+    "egg-naf-amqp": "0.0.13",
+    "egg-redis": "^2.4.0",
+    "egg-scripts": "^2.11.0",
+    "lodash": "^4.17.21",
+    "moment": "^2.29.1",
+    "naf-framework-mongoose-free": "^0.0.18"
+  },
+  "devDependencies": {
+    "autod": "^3.0.1",
+    "autod-egg": "^1.1.0",
+    "egg-bin": "^4.11.0",
+    "egg-ci": "^1.11.0",
+    "egg-mock": "^3.21.0",
+    "eslint": "^5.13.0",
+    "eslint-config-egg": "^7.1.0",
+    "jsonwebtoken": "^8.5.1"
+  },
+  "engines": {
+    "node": ">=10.0.0"
+  },
+  "scripts": {
+    "start": "egg-scripts start --daemon --title=egg-server-server-user",
+    "stop": "egg-scripts stop --title=egg-server-server-user",
+    "dev": "egg-bin dev",
+    "debug": "egg-bin debug",
+    "test": "npm run lint -- --fix && npm run test-local",
+    "test-local": "egg-bin test",
+    "cov": "egg-bin cov",
+    "lint": "eslint .",
+    "ci": "npm run lint && npm run cov",
+    "autod": "autod"
+  },
+  "ci": {
+    "version": "10"
+  },
+  "repository": {
+    "type": "git",
+    "url": ""
+  },
+  "author": "lrf",
+  "license": "MIT"
+}

+ 2 - 0
readme/admin.md

@@ -0,0 +1,2 @@
+# Admin 管理员相关
+### 没有设计成分站模式,为系统管理员,不管业务方面

+ 13 - 0
readme/user.md

@@ -0,0 +1,13 @@
+# User 相关
+### 设计为分站模式,目的是作为一个持续可使用的服务
+### 使用流程:
+
+|字段|类型|说明|
+|:-:|:-:|:-:|
+|account|String|账号|
+|password|Secret|密码|
+|uid|String|关联id,与各自业务有关的用户id|
+
+### 1. 创建: 各自业务创建账号时,走接口,在用户管理项目中可以保留任何信息,但是不能保留密码.目的是为了以最小的耦合性完成身份验证;
+### 2. 修改密码:用户登陆后,知道自己的分站,自己的id后可以过来修改密码
+### 3. 删除用户:要留底在各自的业务中留底,这边删除就是删除

+ 9 - 0
server.js

@@ -0,0 +1,9 @@
+
+// eslint-disable-next-line strict
+const egg = require('egg');
+
+const workers = Number(1);
+egg.startCluster({
+  workers,
+  baseDir: __dirname,
+});

+ 20 - 0
test/app/controller/home.test.js

@@ -0,0 +1,20 @@
+'use strict';
+
+const { app, assert } = require('egg-mock/bootstrap');
+
+describe('test/app/controller/home.test.js', () => {
+  it('should assert', () => {
+    const pkg = require('../../../package.json');
+    assert(app.config.keys.startsWith(pkg.name));
+
+    // const ctx = app.mockContext({});
+    // yield ctx.service.xx();
+  });
+
+  it('should GET /', () => {
+    return app.httpRequest()
+      .get('/')
+      .expect('hi, egg')
+      .expect(200);
+  });
+});