egg-restapi 项目 学习记录 1

// egg-restfulapi

// 没有app.js 文件

// 特别的库:await-stream-ready egg-bcrypt egg-cors egg-jwt
// egg-mongoose egg-scripts image-downloader mocha moment stream-to-array
// stream-wormhole
// 应该是个很规范的例子


// image-downloader   下载图片
// Download to a directory and save with an another filename
options = {
    url: 'http://someurl.com/image2.jpg',
    dest: '/path/to/dest/photo.jpg'        // Save to /path/to/dest/photo.jpg
}

download.image(options)
    .then(({ filename, image }) => {
        console.log('File saved to', filename)
    })
    .catch((err) => {
        console.error(err)
    })


// await-stream-ready
// 以流的方式读文件
const fs = require('fs');
const awaitWriteStream = require('await-stream-ready').write;

async function write(srcStream, file) {
    const stream = srcStream.pipe(fs.createWriteStream(file));
    await awaitWriteStream(stream);
}

const fs = require('fs');
const awaitReadStream = require('await-stream-ready').read;

async function read(file) {
    const stream = fs.createReadStream(file);
    stream.on('data', buf => {
        // consume buf
    });
    await awaitReadStream(stream);
}


// egg-bcrypt  根据字符 建立hash值 类似 md5的库
// 产生
exports.bcrypt = {
    saltRounds: 10 // default 10
}

// {app_root}/config/plugin.js
exports.bcrypt = {
    enable: true,
    package: 'egg-bcrypt'
}
async generate() {
    const hash = await this.ctx.genHash(this.ctx.request.body.plainText);
    // Store hash in your password DB
}
// 比较
async compare() {
    const { hash, plainText } = this.ctx.request.body;
    const checked = await this.ctx.compare(plainText, hash);
    this.ctx.body = { checked };
}


// egg-cors  跨域返回

// {app_root}/config/plugin.js
exports.cors = {
    enable: true,
    package: 'egg-cors',
};
// 配置白名单
exports.security = {
    domainWhiteList: ['http://localhost:4200'],
};
// {app_root}/config/config.default.js
exports.cors = {
    // {string|Function} origin: '*',
    // {string|Array} allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
};


// egg-jwt
// {app_root}/config/plugin.js
exports.jwt = {
    enable: true,
    package: "egg-jwt"
};

// {app_root}/config/config.default.js
exports.jwt = {
    secret: "123456"
};

const token = app.jwt.sign({ foo: 'bar' }, app.config.jwt.secret);
module.exports = app => {
    app.get("/", app.jwt, "render.index"); // use old api app.jwt
    app.get("/login", "login.index");
    app.get("/success", "success.index"); // is setting in config.jwt.match
};


// restapi
// role
// router.post('/api/role', controller.role.create)
// router.delete('/api/role/:id', controller.role.destroy)
// router.put('/api/role/:id', controller.role.update)
// router.get('/api/role/:id', controller.role.show)
// router.get('/api/role', controller.role.index)
router.delete('/api/role', controller.role.removes)
router.resources('role', '/api/role', controller.role)


//   default config 

module.exports = appInfo => {
    const config = exports = {}

    // use for cookie sign key, should change to your own and keep security
    config.keys = appInfo.name + '_1513779989145_1674'

    // add your config here
    // 加载 errorHandler 中间件
    config.middleware = ['errorHandler']

    // 只对 /api 前缀的 url 路径生效
    // config.errorHandler = {
    //   match: '/api',
    // }

    config.security = {
        csrf: {
            enable: false,
        },
        domainWhiteList: ['http://localhost:8000'],
    }

    config.multipart = {
        fileExtensions: ['.apk', '.pptx', '.docx', '.csv', '.doc', '.ppt', '.pdf', '.pages', '.wav', '.mov'], // 增加对 .apk 扩展名的支持
    },

        config.bcrypt = {
            saltRounds: 10 // default 10
        }

    config.mongoose = {
        url: 'mongodb://127.0.0.1:27017/egg_x',
        options: {
            useMongoClient: true,
            autoReconnect: true,
            reconnectTries: Number.MAX_VALUE,
            bufferMaxEntries: 0,
        },
    }

    config.jwt = {
        secret: 'Great4-M',
        enable: true, // default is false
        match: '/jwt', // optional
    }

    return config
}


/******************************************************************************** */
//   plugin


exports.validate = {
    enable: true,
    package: 'egg-validate',
}

exports.bcrypt = {
    enable: true,
    package: 'egg-bcrypt'
}

exports.mongoose = {
    enable: true,
    package: 'egg-mongoose',
}

exports.jwt = {
    enable: true,
    package: 'egg-jwt',
}

exports.cors = {
    enable: true,
    package: 'egg-cors',
}


//   错误处理 中间件

'use strict'

module.exports = (option, app) => {
    return async function (ctx, next) {
        try {
            await next()
        } catch (err) {
            // 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志
            app.emit('error', err, this)
            const status = err.status || 500
            // 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
            const error = status === 500 && app.config.env === 'prod' ?
                'Internal Server Error' :
                err.message
            // 从 error 对象上读出各个属性,设置到响应中
            ctx.body = {
                code: status, // 服务端自身的处理逻辑错误(包含框架错误500 及 自定义业务逻辑错误533开始 ) 客户端请求参数导致的错误(4xx开始),设置不同的状态码
                error: error
                // msg: error

            }
            if (status === 422) {
                ctx.body.detail = err.errors
                // ctx.body.data = err.errors
            }
            ctx.status = 200
        }
    }
}

/******************************************************************************** */
// mongoose 的model

module.exports = app => {
    const mongoose = app.mongoose

    const RoleSchema = new mongoose.Schema({
        name: { type: String, unique: true, required: true },
        access: { type: String, required: true, default: 'user' },
        extra: { type: mongoose.Schema.Types.Mixed },//混合类型 
        createdAt: { type: Date, default: Date.now }
    })

    return mongoose.model('Role', RoleSchema)
}

/******************************************************************************** */

// 处理成功响应
exports.success = ({ ctx, res = null, msg = '请求成功' }) => {
    ctx.body = {
        code: 0,
        data: res,
        msg
    }
    ctx.status = 200
}

//   jwt 鉴权
router.get('/api/user/access/current', app.jwt, controller.userAccess.current)
/******************************************************************************** */

// 用户登入
async login() {
    const { ctx, service } = this
    // 校验参数
    ctx.validate(this.UserLoginTransfer)
    // 组装参数
    const payload = ctx.request.body || {}
    // 调用 Service 进行业务处理
    const res = await service.userAccess.login(payload)
    // 设置响应内容和响应状态码
    ctx.helper.success({ ctx, res })
}
//请求头访问
// curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
// eyJmb28iOiJiYXIiLCJpYXQiOjE0OTAwMTU0MTN9.ehQ38YsRlM8hDpUMKYq1rHt-YjBPSU11dFm0NOroPEg"

// jwt 生成token
class ActionTokenService extends Service {
    async apply(_id) {
      const {ctx} = this
      return ctx.app.jwt.sign({
        data: {
          _id: _id
        },
        // exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24 * 7)
        exp: Math.floor(Date.now() / 1000) + (60)
      }, ctx.app.config.jwt.secret)
    }
  }
// 登录
async login(payload) {
    const { ctx, service } = this
    const user = await service.user.findByMobile(payload.mobile)
    if (!user) {
        ctx.throw(404, 'user not found')
    }
    let verifyPsw = await ctx.compare(payload.password, user.password)//hash比较密码 ,后者hash
    if (!verifyPsw) {
        ctx.throw(404, 'user password is error')
    }
    // 生成Token令牌
    return { token: await service.actionToken.apply(user._id) }
}


// ctx.state.user 可以提取到JWT编码的data
const _id = ctx.state.user.data._id
const user = await service.user.find(_id)
if (!user) {
    ctx.throw(404, 'user is not found')
}

if (!verifyPsw) {
    ctx.throw(404, 'user password error')
} else {
    // 重置密码
    values.password = await ctx.genHash(values.password)
    return service.user.findByIdAndUpdate(_id, values)
}

/******************************************************************************** */

//   数据库上有不同
return ctx.model.User.findByIdAndUpdate(_id, payload) //mongo
ctx.model.ProductModel.findOne({ where: { id } }); //seqlize

/******************************************************************************** */
// 分页排序
async index(payload) {
    const { currentPage, pageSize, isPaging, search } = payload
    let res = []
    let count = 0
    let skip = ((Number(currentPage)) - 1) * Number(pageSize || 10)
    if(isPaging) {
      if(search) {
        res = await this.ctx.model.User.find({mobile: { $regex: search } }).populate('role').skip(skip).limit(Number(pageSize)).sort({ createdAt: -1 }).exec()
        count = res.length
      } else {
        res = await this.ctx.model.User.find({}).populate('role').skip(skip).limit(Number(pageSize)).sort({ createdAt: -1 }).exec()
        count = await this.ctx.model.User.count({}).exec()
      }
    } else {
      if(search) {
        res = await this.ctx.model.User.find({mobile: { $regex: search } }).populate('role').sort({ createdAt: -1 }).exec()
        count = res.length
      } else {
        res = await this.ctx.model.User.find({}).populate('role').sort({ createdAt: -1 }).exec()
        count = await this.ctx.model.User.count({}).exec()
      }
    }


     // 整理数据源 -> Ant Design Pro
     let data = res.map((e,i) => {
        const jsonObject = Object.assign({}, e._doc)
        jsonObject.key = i
        jsonObject.password = 'Are you ok?'
        jsonObject.createdAt = this.ctx.helper.formatTime(e.createdAt)
        return jsonObject
      })
  
      return { count: count, list: data, pageSize: Number(pageSize), currentPage: Number(currentPage) }



      
  // 资源上传======================================================================================================>

  // 通过URL添加单个图片: 如果网络地址不合法,EGG会返回500错误
  async url() {
    const { ctx, service } = this
    // 组装参数
    const attachment = new this.ctx.model.Attachment
    const { url } = ctx.request.body
    const filename = path.basename(url) // 文件名称
    const extname = path.extname(url).toLowerCase() // 文件扩展名称
    const options = {
      url: url,
      dest: path.join(this.config.baseDir, 'app/public/uploads', `${attachment._id.toString()}${extname}`)
    }//下载资源到静态资源文件夹  外部返回 url/uploads
    let res    
    try {
      // 写入文件 const { filename, image}
      await download.image(options)
      attachment.extname = extname
      attachment.filename = filename
      attachment.url = `/uploads/${attachment._id.toString()}${extname}`//静态资源相对根目录
      res = await service.upload.create(attachment)
    } catch (err) {
      throw err
    }
    // 设置响应内容和响应状态码
    ctx.helper.success({ctx, res}) 
  }



  // 上传单个文件
  async create() {
    const { ctx, service } = this
    // 要通过 ctx.getFileStream 便捷的获取到用户上传的文件,需要满足两个条件:
    // 只支持上传一个文件。
    // 上传文件必须在所有其他的 fields 后面,否则在拿到文件流时可能还获取不到 fields。
    const stream = await ctx.getFileStream()
    // 所有表单字段都能通过 `stream.fields` 获取到
    const filename = path.basename(stream.filename) // 文件名称
    const extname = path.extname(stream.filename).toLowerCase() // 文件扩展名称
    // 组装参数 model
    const attachment = new this.ctx.model.Attachment
    attachment.extname = extname
    attachment.filename = filename
    attachment.url = `/uploads/${attachment._id.toString()}${extname}`
    // 组装参数 stream
    const target = path.join(this.config.baseDir, 'app/public/uploads', `${attachment._id.toString()}${attachment.extname}`)
    const writeStream = fs.createWriteStream(target)
    // 文件处理,上传到云存储等等
    try {
      await awaitWriteStream(stream.pipe(writeStream))
    } catch (err) {
      // 必须将上传的文件流消费掉,要不然浏览器响应会卡死
      await sendToWormhole(stream)
      throw err
    }
    // 调用 Service 进行业务处理
    const res = await service.upload.create(attachment)
    // 设置响应内容和响应状态码
    ctx.helper.success({ctx, res})
  }


   // 上传多个文件
   async multiple() {
    // 要获取同时上传的多个文件,不能通过 ctx.getFileStream() 来获取
    const { ctx, service } = this
    const parts = ctx.multipart()
    const res = {}
    const files = []

    let part // parts() return a promise
    while ((part = await parts()) != null) {
      if (part.length) {
        // 如果是数组的话是 filed
        // console.log('field: ' + part[0])
        // console.log('value: ' + part[1])
        // console.log('valueTruncated: ' + part[2])
        // console.log('fieldnameTruncated: ' + part[3])
      } else {
        if (!part.filename) {
          // 这时是用户没有选择文件就点击了上传(part 是 file stream,但是 part.filename 为空)
          // 需要做出处理,例如给出错误提示消息
          return
        }
        // part 是上传的文件流
        // console.log('field: ' + part.fieldname)
        // console.log('filename: ' + part.filename)
        // console.log('extname: ' + part.extname)
        // console.log('encoding: ' + part.encoding)
        // console.log('mime: ' + part.mime)
        const filename = part.filename.toLowerCase() // 文件名称
        const extname = path.extname(part.filename).toLowerCase() // 文件扩展名称
        
        // 组装参数
        const attachment = new ctx.model.Attachment
        attachment.extname = extname
        attachment.filename = filename
        attachment.url = `/uploads/${attachment._id.toString()}${extname}`
        // const target = path.join(this.config.baseDir, 'app/public/uploads', filename)
        const target = path.join(this.config.baseDir, 'app/public/uploads', `${attachment._id.toString()}${extname}`)        
        const writeStream = fs.createWriteStream(target)
        // 文件处理,上传到云存储等等
        let res
        try {
          // result = await ctx.oss.put('egg-multipart-test/' + part.filename, part)
          await awaitWriteStream(part.pipe(writeStream))
          // 调用Service
          res = await service.upload.create(attachment)
        } catch (err) {
          // 必须将上传的文件流消费掉,要不然浏览器响应会卡死
          await sendToWormhole(part)
          throw err
        }
        files.push(`${attachment._id}`) // console.log(result)
      }
    }
    ctx.helper.success({ctx, res: { _ids:files }})
  }

// 分页 
  if(isPaging) {
    if(search) {
      if (kind) {
        res = await this.ctx.model.Attachment
        .find({filename: { $regex: search }, extname: { $in: attachmentKind[`${kind}`]} })
        .skip(skip).limit(Number(pageSize)).sort({ createdAt: -1 }).exec()


        

async removes(payload) {
    return this.ctx.model.User.remove({ _id: { $in: payload } })
  }
  
    // Commons======================================================================================================>
    async findByMobile(mobile) {
      return this.ctx.model.User.findOne({mobile: mobile})
    }
  
    async find(id) {
      return this.ctx.model.User.findById(id)
    }
  
    async findByIdAndUpdate(id, values) {
      return this.ctx.model.User.findByIdAndUpdate(id, values)
    }

你可能感兴趣的:(egg-restapi 项目 学习记录 1)