node 接口(小滴)

113 阅读4分钟

图形验证码插件

yarn add svg-captcha@1.4.0

路由

const express = require('express')
const router = express.Router()
const NotifyController = require('../controller/NotifyController')

// 图形验证码接口
router.get('/captcha',NotifyController.catcha)

module.exports = router

控制器

const NotifyService = require('../service/NotifyService')

const NotifyController = {
    catcha: async(req,res) => {
        let hanleRes = await NotifyService.captcha()
        res.set('content-type', 'image/svg+xml')
        
        res.send(hanleRes)
    }
}

module.exports = NotifyController

服务

const svgCaptcha = require('svg-captcha')

const NotifyService = {
    captcha: async() => {
        let captcha = svgCaptcha.create({
            size:4,// 验证吗长度
            ignoreChars:'0oli', // 验证码字符中排除
            noise:1,// 干扰线
            background:'#aaa'
        })
        console.log('654654',captcha.text)

        return captcha.data
    }
}

module.exports = NotifyService

通知相关的接口

const notifyRouter = require('./router/notify')
app.use('/api/notify/v1',notifyRouter)

查询bannert图接口

image.png

路由

const express = require('express')
const router = express.Router()
const BannerController = require('../controller/BannerController')
// banner接口
router.get('/list',BannerController.list)

module.exports = router

数据层

const DB = require('../config/sequelize')
const BackCode = require('../utils/BackCode')

const BannerService = {
    list:async(location) => {
        const data = await DB.banner.findOne({wehere:{location}})
        return BackCode.buildSuccessAndData({data})
    }
}

module.exports = BannerService

控制器

/***
 * @params list banner接口
 * 
 */
const BannerService = require('../service/BannerService')

const BannerController = {
    list:async(req,res)=>{
        let {location} = req.query
        let handleRes = await BannerService.list(location)
        res.send(handleRes)
    }
}

module.exports = BannerController

请求接口

// banner 接口
const bannerRouter = require('./router/banner')
app.use('/api/banner/v1',bannerRouter)

image.png

个人信息接口

http://127.0.0.1:8081/api/user/v1/detail

路由

const express = require('express')
const router = express.Router()
const UserController = require('../controller/UserController')

//用户信息接口
router.get('/detail',UserController.detail)

module.exports = router

都差不多 查询单个

image.png

image.png

image.png

image.png

课程分类导航开发 一对多

image.png

服务层

const DB = require('../config/sequelize')
const category = require('../models/category')
const BackCode = require('../utils/BackCode')
const {Op} = require('sequelize')


const ProductService = {
    category:async()=>{
        // 无关联查询方案
        let parentList = await DB.category.findAll({where :{pid:0},order:[['id']], raw:true})
        // Op.ne]: 20,               // id != 20 查询 id不等于20的数据
        let childList = await DB.category.findAll({where:{pid:{[Op.ne]:0}},order:[['id']],raw:true})

        parentList.map((item) =>{
            item['subCategoryList'] = []
            childList.map((subItem)=>{
                if(subItem.pid == item.id){
                    return item.subCategoryList.push(subItem)
                }
            })
        })
        return BackCode.buildSuccessAndData({data:parentList})
    }
}

module.exports = ProductService

sequelize 的op模块

const Op = Sequelize.Op
 
[Op.and]: {a: 5}           // 且 (a = 5)
[Op.or]: [{a: 5}, {a: 6}]  // (a = 5 或 a = 6)
[Op.gt]: 6,                // id > 6
[Op.gte]: 6,               // id >= 6
[Op.lt]: 10,               // id < 10
[Op.lte]: 10,              // id <= 10
[Op.ne]: 20,               // id != 20
[Op.eq]: 3,                // = 3
[Op.not]: true,            // 不是 TRUE
[Op.between]: [6, 10],     // 在 6 和 10 之间
[Op.notBetween]: [11, 15], // 不在 11 和 15 之间
[Op.in]: [1, 2],           // 在 [1, 2] 之中
[Op.notIn]: [1, 2],        // 不在 [1, 2] 之中
[Op.like]: '%hat',         // 包含 '%hat'
[Op.notLike]: '%hat'       // 不包含 '%hat'
[Op.iLike]: '%hat'         // 包含 '%hat' (不区分大小写)  (仅限 PG)
[Op.notILike]: '%hat'      // 不包含 '%hat'  (仅限 PG)
[Op.regexp]: '^[h|a|t]'    // 匹配正则表达式/~ '^[h|a|t]' (仅限 MySQL/PG)
[Op.notRegexp]: '^[h|a|t]' // 不匹配正则表达式/!~ '^[h|a|t]' (仅限 MySQL/PG)
[Op.iRegexp]: '^[h|a|t]'    // ~* '^[h|a|t]' (仅限 PG)
[Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (仅限 PG)
[Op.like]: { [Op.any]: ['cat', 'hat']} // 包含任何数组['cat', 'hat'] - 同样适用于 iLike 和 notLike
[Op.overlap]: [1, 2]       // && [1, 2] (PG数组重叠运算符)
[Op.contains]: [1, 2]      // @> [1, 2] (PG数组包含运算符)
[Op.contained]: [1, 2]     // <@ [1, 2] (PG数组包含于运算符)
[Op.any]: [2,3]            // 任何数组[2, 3]::INTEGER (仅限PG)
[Op.col]: 'user.organization_id' // = 'user'.'organization_id', 使用数据库语言特定的列标识符, 


image.png

需要在 一对多关系模型


// category与自身表的一对多关系模型
models.category.hasMany(models.category,{foreignKey:'pid', as:'subCategoryList'})
models.category.belongsTo(models.category,{foreignKey:'pid'})

image.png

image.png

服务层

  card:async() =>{
        let cardList = await DB.product_card.findAll({raw:true})
        let list = cardList.map(async (item) =>{
            item.product_list = await DB.product.findAll({where:{id:item.product_list.split(',')},raw:true})
            return item
        })
        let lastList = await Promise.all(list)
        return BackCode.buildSuccessAndData({data:lastList})
    }

通过选择不同分类+⻚数查询不同课程(难点)

image.png

image.png

难点:sql写法

image.png

SELECT p.id,p.cover_img,p.title,p.course_level,p.buy_num,p.old_amount,p.amount FROM product p LEFT JOIN category_product c ON c.product_id=p.id WHERE c.category_id = 88 ORDER BY p.gmt_create DESC LIMIT 3,5

image.png

// 分类查找视频接⼝
    query_by_cid:async (req) =>{
        let {cpid,cid,page,size} = req.body
        if(!(page && size)) return BackCode.buildError({msg:'缺少必要的参数'})

        // 当前从第几个算
        page = (page - 1) * size
        // 判断分类和方向是否为空
        let sqlId = cid || cpid || null

        // 原始的关联查询课程列表
        // $ {} 判断语句 !!!
        let productListSql = `SELECT p.id,p.cover_img,p.title,p.course_level,p.buy_num,p.old_amount,p.amount FROM product p LEFT JOIN category_product c ON c.product_id=p.id ${sqlId ? 'WHERE c.category_id=?' : ''} ORDER BY p.gmt_create DESC LIMIT ?,?`
        // 传递几个参数进sql判断
        let productListQuery = sqlId ? [sqlId, page, size] : [page, size]

        // sequelize原始查询
        let productList = await DB.sequelize.query(productListSql, {
            replacements: productListQuery,
            type: QueryTypes.SELECT
        })

        // 通过子查询课程总数
        let totalSql = `select count(*) as total_record from (SELECT p.id,p.cover_img,p.title,p.course_level,p.buy_num,p.old_amount,p.amount FROM product p LEFT JOIN category_product c ON c.product_id=p.id ${sqlId ? 'WHERE c.category_id=?' : ''}) temp_table`

        // sequelize原始查询总数
        let totalRes = await DB.sequelize.query(totalSql, {
            replacements: [sqlId],
            type: QueryTypes.SELECT
        })

        // 总数
        let total_record = totalRes[0].total_record

        // 计算总页数
        let total_page = null
        total_record / size == 0 ? (total_page = total_record / size) : (total_page = Math.ceil(total_record / size))

        return BackCode.buildSuccessAndData({ data: { current_data: productList, total_page, total_record } })
    }
SELECT p.id,p.cover_img,p.title,p.course_level,p.buy_num,p.old_amount,p.amount FROM product p LEFT JOIN category_product c ON c.product_id=p.id WHERE c.category_id=88 ORDER BY p.gmt_create DESC LIMIT 0,12

select count(*) as total_record from (SELECT p.id,p.cover_img,p.title,p.course_level,p.buy_num,p.old_amount,p.amount FROM 
product p LEFT JOIN category_product c ON c.product_id=p.id WHERE c.category_id=88) temp_table

课程详情⼀对多关联查询+学员购买动态

image.png

// teacher和product表的一对多关系模型
models.Teacher.hasMany(models.Product, { foreignKey: 'teacher_id' })
models.Product.belongsTo(models.Teacher, { foreignKey: 'teacher_id', as: "teacherDetail" })
  detail: async (req) => {
    let { id } = req.query
    let productDetail = await DB.Product.findOne({
      where: { id },
      include: [{ model: DB.Teacher, as: 'teacherDetail' }],
    })
    return BackCode.buildSuccessAndData({ data: { ...productDetail.toJSON(), bd_zip_url: '', note_url: '' } })
  }

image.png

查询课程是否购买接口

image.png

const OrderService = {
    query_pay:async(req) =>{
        // 本来userId 可以在token获取
        let {id,userId} = req.query
        // 请求头获取token
        //let token = req.headers.authorization.split(' ').pop()
        // 解密
        //let userInfo = SecretTool.jwtVerify(token)
        let orderList = await DB.product_order.findAll({
            where:{product_id:id,account_id:userId,order_state:'PAY'},
            raw:true
        })
        if(orderList.length > 0){
            return BackCode.buildSuccess()
        }else{
            return BackCode.buildError(CodeEnum.PRODUCT_NOY_PAY)
        }

    }
}

image.png

查询课程是否最近购买动态接口

lastest:async (req) => {
        let {id} = req.query
        let latestList = await DB.product_order.findAll({
            where:{product_id:id},
            order:[['gmt_create','DESC']],
            limit:20
        })
        return BackCode.buildSuccessAndData({data:latestList})
    }

image.png

推荐好课+课程章集模块开发

image.png

chapter: async (req) => {
        let { id } = req.query
        if (!id) return BackCode.buildError({ msg: '缺少必要参数' })
        let chapterList = await DB.chapter.findAll({ where: { product_id: id }, order: [['ordered']], raw: true })
        let episodeList = await DB.episode.findAll({ where: { product_id: id }, order: [['ordered']], raw: true })
        // 将课程的集生层对象数组插入到章数据元素中
        chapterList.map((item) => {
          item['episodeList'] = []
          episodeList.map((subItem) => {
            if (subItem.chapter_id === item.id) {
              return item['episodeList'].push(subItem)
            }
          })
        })
        return BackCode.buildSuccessAndData({ data: chapterList })
      },
}

module.exports = ProductService

image.png

// 视频资料接口

image.png

material_by_id:async(req) =>{
        let {id,userId} = req.query
        if(!id)  return BackCode.buildError({msg:'缺少必要参数'})

        // let token = req.headers.authorization.split(' ').pop()
        // // 判断是否登录
        // if (!token) return BackCode.buildResult(CodeEnum.ACCOUNT_UNLOGIN)
        // let userInfo = SecretTool.jwtVerify(token)
        
        // 判断是否购买
        let orderList = await DB.product_order.findAll({
            where:{product_id:id,account_id:userId,order_state:'PAY'},
            raw:true
        })

        if(orderList.length > 0){
            let productDetail = await DB.product.findOne({
                attributes: ['bd_zip_url', 'note_url'], // 只输出这两个字段
                where: { id }
            })
            return BackCode.buildSuccessAndData({data:productDetail})
        }else{
            return BackCode.buildResult(CodeEnum.PRODUCT_NOY_PAY)
        }
    }

image.png

// ⽤户评论列表 分页

const CommentService = {
    page:async (req) => {
        let {page,size,id} = req.body
        if(!(page && size && id)){
            return BackCode.buildError({msg:'缺少必要的参数'})
        }

        // findAndCountAll 方法是结合了 findAll 和 count ,同时获取评论列表和总数
        let {count,rows} = await DB.comment.findAndCountAll({
            where:{product_id:id},
            order:[['gmt_create','DESC']],
            offset:Number((page -1) * size),
            limit:Number(size)
        })
        // 计算总页数
        let total_page = null
        count / size == 0 ? (total_page = count / size) : (total_page = Math.ceil(count / size))
        return BackCode.buildSuccessAndData({data:{current_data:rows,total_page:total_page,total_record:count}})
    }
}

image.png

登录接口

"jsonwebtoken": "8.5.1",

封装请求

const md5 = require('blueimp-md5')
const jwt = require('jsonwebtoken')
const { jwtSecretKey } = require('../config/jwtSecretKey')

class SecretTool {
  // md5加密密码
  static md5(query) {
    return md5(query)
  }
  // jwt生成token
  static jwtSign(query, time) {
    return jwt.sign(query, jwtSecretKey, { expiresIn: time })
  }
  // jwt解密token
  static jwtVerify(query) {
    return jwt.verify(query, jwtSecretKey)
  }
}

module.exports = SecretTool
login:async (req) =>{
        let {phone,password} = req.body
        // 参数判空
        if(!phone && password) return BackCode.buildError({msg:'缺少必要参数'})
        // 判断手机号是否注册
        let userInfo = await DB.account.findAll({where:{phone},raw:true})
        if (userInfo.length == 0) return BackCode.buildResult(CodeEnum.ACCOUNT_UNLOGIN)

        // 账号密码方式
        if(password){
            // 判断密码是否正确
            if(!userInfo[0].pwd == SecretTool.md5(password)){
                return BackCode.buildResult(CodeEnum.ACCOUNT_PWD_ERROR)
            }
        }
        // 拼接token用户信息,除去密码
        let user = {...userInfo[0],pwd:''}
        //生成token
        let token = SecretTool.jwtSign(user,'168h')
        return BackCode.buildSuccessAndData({data:`Bearer ${token}`})

    },

image.png

  duration_record:async(req)=>{
        let {productId,episodeId,duration } = req.body
        if(!(productId && episodeId && duration)){
            return BackCode.buildError({msg:'缺少必要参数'})
        }

        let token = req.headers.authorization.split(' ').pop()
        //let userInfo = SecretTool.jwtVerify(token)

        // 查询是否该用户的该章该集有上报过学习时长、有则更新学习时长、无则插入
        let isHas = await DB.duration_record.findOne({
            where: { account_id: "6814385", product_id: productId, episode_id: episodeId },
            raw:true
        })
        // 对比最新学习时长和之前的大小
        // if (!(Number(duration) > Number(isHas.duration))) {
        //     return BackCode.buildError({ msg: '最新学习时长较之前小,不做更新' })
        // }
        if(isHas){
            await DB.duration_record.update(
                {duration:Number(duration)},
                { where: { account_id: "6814385", product_id: productId, episode_id: episodeId } }
            )
            return BackCode.buildSuccess()
        }else{
            await DB.duration_record.create({
                account_id: "6814385",
                product_id: productId,
                episode_id: episodeId,
                duration: duration
            })
            return BackCode.buildSuccess()
        }
    },
   play_record: async(req) =>{
        let {page,size,userId} = req.body

        // 关联分页查询用户的播放记录列表
        let recordSql ="SELECT r.id,r.product_id,r.current_episode_id,r.account_id,r.learn_ids,r.pay_status,r.gmt_modified,p.title product_title,p.cover_img,p.episode_num,p.product_type,e.title episode_title FROM play_record r LEFT JOIN product p ON r.product_id = p.id LEFT JOIN episode e ON e.id = r.current_episode_id WHERE r.account_id=? and p.product_type not in('FOREVER_VIP','YEAR_VIP','BACKEND_ONE_TO_ONE','FRONTEND_ONE_TO_ONE')  ORDER BY r.gmt_modified desc limit ?,?"
        // 查询用户的播放记录总数
        let countSql = 'select count(*) as count from play_record where account_id=?'

        let recordList = await DB.sequelize.query(
            recordSql,{
                replacements:[userId,Number((page - 1)*size),Number(size)],
                type:QueryTypes.SELECT
            }
        )
        let count = await DB.sequelize.query(
            countSql,
            {
                replacements:[userId],
                type:QueryTypes.SELECT
            }
        )
        count = count[0].count

        let total_page = null
        count / size == 0 ? (total_page = count / size) : (total_page = Math.ceil(count / size))

        return BackCode.buildSuccessAndData({data:{current_data:recordList,total_page:total_page,total_record:count}})
    }

修改个人资料

 update:async (req) =>{
        let {id,username,slogan,sex,city} = req.body
        if(!(id && username && slogan && city)){
            return BackCode.buildError({msg:'缺少必要参数'})
        }

        let accountItem = {username,slogan,sex,city}
        await DB.account.update(accountItem,{where:{id}})
        return BackCode.buildSuccess()
    }

修改

 deleteUser:async(id) => {
        let data = await DB.account.findOne({where:{id}})
        if(!data){
            return BackCode.buildError(CodeEnum.ACCOUNT_UNREGISTER)
        }
        await data.update({del:1})
        return BackCode.buildSuccess()
    }
// 删除用户
router.delete('/user/:id',AdminController.deleteUser)

image.png

修改个人信息

服务层

   updateUser:async(id,updated_details) =>{
        let data = await DB.account.findOne({where:{id}})
        if(!data){
            return BackCode.buildError(CodeEnum.ACCOUNT_UNREGISTER)
        }
        await data.update(updated_details)
        return BackCode.buildSuccess()
    }

控制层

async updateUser(req,res){
        let {id} = req.params
        let updated_details = req.body
        let handleRes = await AdminService.updateUser(id,updated_details)
        res.send(handleRes)
    }

路由

// 修改某个用户接口
router.put('/user/:id',AdminController.updateUser)

image.png

查询订单

image.png

// product_title image.png

// order_state image.png

 searchOrder:async({condition,page,size,gmt_start,gmt_end}) =>{
        const whereOptions = DBTool.generateWhereOptions({
            condition,
            gmt_start,
            gmt_end,
            searchFields:['product_title','username','order_state'] //查询条件
        })
        let result = await DBTool.paginate(DB.product_order,{where:whereOptions,page,size})
        if(!result.current_data.length === 0){
            return BackCode.buildError(CodeEnum.ACCOUNT_UNLOGIN)
        }
        return BackCode.buildSuccessAndData({data:result})
    }

 async searchOrder(req,res){
        let {condition} = req.params
        let {page,size,gmt_start,gmt_end} = req.query
        let handleRes = await AdminService.searchOrder({condition,page,size,gmt_start,gmt_end})
        res.send(handleRes)
    }
// 查询全部订单
router.get('/order',AdminController.searchOrder)

// 条件查询订单
router.get('/order/:condition',AdminController.searchOrder)