hapiでjs mysqlとnuxt.jsは簡単な本のプロジェクトを模倣します


前言:
プレビュー:
  • 当駅プレビュー:テンセントクラウドip、ドメイン名zhanglijian.トップ(届出中...)
  • github:https://github.com/huoguozhang/my-blog

  • 開始:
  • npm i
  • mysqlを
  • に配置する
  • npm run server or npm run dev

  • 実装機能:
  • ユーザー:登録、登録、ユーザー資料の修正、ユーザー詳細ページ、簡書に類似する、文章削除
  • 文章:文章詳細ページ、表示、評論、点賛と踏み、文章読書回数統計
  • 文章:すべての文章、ページ分けとキーワード、時間別検索をサポートする
  • 文章作成:markdownと画像ドラッグアップロード
  • をサポート
  • トップページ:記事推薦、著者推薦、トップページ輪番、トップ検索記事とユーザー
  • ssr効果プレビュー:
  • よく知っている
  • seo効果:
  • 補足対象
    1テクノロジスタック:
  • フロントエンド:axios、element-ui、nuxt.js、 ts
  • バックエンド:node.js、hapi.js、sequelize(orm)、hapi-auth(token)、hapi-swagger(オンラインapiドキュメント)、hapi-pagination(ページング)、joi(フロントエンド要求データ検査elementのフォーム検査に類似)、mysqlおよびその他のプラグイン
  • 2技術詳細:
    説明:本文は主にバックエンドに重点を置いて、最後の効果は我が社のバックエンドに似ています
    ディレクトリ構造:
    ├── assets //     ,css,    
    ├── client //      ,axios           
    ├── components // vue    
    ├── config //     
    ├── layouts // nuxt  
    ├── middleware // nuxt    
    ├── migrations // orm     
    ├── models // orm      
    ├── nuxt.config.js 
    ├── nuxt.config.ts
    ├── package-lock.json
    ├── package.json
    ├── pages // nuxt
    ├── plugins // hapi   nuxt  
    ├── routes // hapi  
    ├── seeders //     
    ├── server // app.js
    ├── static //     
    ├── store // nuxt
    ├── tsconfig.json 
    ├── uploads //         
    └── utils //     
    

    前言:なぜhapiなのか.js ?
    hapi公式ドキュメントはもうたくさん話しています(expresstohapi)、ここで一番惹かれたのは、たくさんのプラグインをインストールしなくてもいいことです(expresならxx-parseプラグインがたくさんあります...)、私のニーズを満たすことができ、hapiはビジネスに応用されています.
    注意点:
    私のこれらのコードは、私の現在のpackageです.jsonのバージョンは正常に動作することができて、hapiのバージョンの大きいバージョンは時々互換性がなくて、異なるバージョンのhapiは異なるプラグインのバージョンに対応して、だから私のバージョンと一致する必要があって、私はnuxtに出会ったことがあります.js v2.9実行加入tsが@componentを認識しない場合、2.8をインストールする.xバージョンでは問題ありません.
    2.1 Sequelizeモデリング
    開発バックグラウンドで最初に思いついたのはデータモデルの構築(テーブルの構築)です.デフォルトではmysqlをインストールする前に自分でデータベースを使いますが、ormというツールがあることを知らないときは、navicatのようなグラフィックツールでテーブルを構築するか、sql文で直接テーブルを構築するかを選択します.このようにするにはいくつかの欠点があります.
  • データベースの操作記録が明確ではありません.テーブルを新規作成したり、フィールドを新規作成したりしました.後悔しました.削除しました.ormのデータ移行はgitに似ていることを後悔しました.
  • 新しい環境を移行し、sqlで操作するのは面倒で、ormのコマンドを直接実行して自動的にテーブルを構築します.
  • データモデルは、以前バックグラウンドプログラムがmysqlに連絡していたとき、接続プール、データの関係、テーブル構造を構築しただけでは知られていませんでした.
  • 追加チェックコードを実行するより簡潔で明確な
  • その他
  • sequelize
    sequelizeはnodeです.jsのpromise ormツールは、他のデータベースもサポートする.
    使用
  • インストールプラグイン:
  • npm i sequelize-cli -D
    npm i sequelize
    npm i mysql2
  • sequelize init

  • sequelize-cliによってsequelizeを初期化すると、使いやすい初期化構造が得られます.
    //     npx
    node_modules/.bin/sequelize init
    ├── config                       #       
    |   ├── config.json              #         
    ├── models                       #     model
    |   ├── index.js                 #           
    ├── migrations                   #        
    ├── seeders                      #        

    config/config.json
    デフォルトの生成ファイルはconfigである.jsonファイルは、開発、テスト、生産の3つのデフォルトのテンプレート環境を構成しており、必要に応じてより多くの環境構成を追加することができます.ここではconfigを使います.js代替config.jsonは、より柔軟に変更するconfig/configを構成する.jsは以下のようにdevelopment(開発)とproduction(生産)の2つの環境のみを残す、開発環境と生産環境の構成パラメータを分離することができる.Envと.env.prodの2つの異なるファイルの中で、環境変数パラメータprocessを通じて.env.NODE_ENVで動的に区別します.
    // config.js
    if (process.env.NODE_ENV === 'production') {
      require('env2')('./.env.prod')
    } else {
      require('env2')('./.env.dev')
    }
    
    const { env } = process
    module.exports = {
      'development': {
        'username': env.MYSQL_USERNAME,
        'password': env.MYSQL_PASSWORD,
        'database': env.MYSQL_DB_NAME,
        'host': env.MYSQL_HOST,
        'port': env.MYSQL_PORT,
        dialect: 'mysql',
        logging: false, // mysql     
        timezone: '+08:00'
        // "operatorsAliases": false,  //         ,      sequelize     
      },
      'production': {
        'username': env.MYSQL_USERNAME,
        'password': env.MYSQL_PASSWORD,
        'database': env.MYSQL_DB_NAME,
        'host': env.MYSQL_HOST,
        'port': env.MYSQL_PORT,
        dialect: 'mysql',
        timezone: '+08:00'
        // "operatorsAliases": false, //         ,      sequelize     
      }
    }
    

    .env.dev
    #           ,         ,                      
    HOST = 127.0.0.1
    PORT = 80
    #        80,  axios url       
    # MySQL        
    MYSQL_HOST = 111.111.111.111
    MYSQL_PORT = 3306
    MYSQL_DB_NAME =     
    MYSQL_USERNAME =       
    MYSQL_PASSWORD =      
    JWT_SECRET = token  
    
  • データベース作成
  • npx sequelize db:create
  • 移行ファイル
  • を作成する
    npx migration:create --name user

    migrationsのディレクトリにタイムスタンプ-userが追加されます.jsの移行ファイルは、自動的に生成されるファイルには、upとdownの2つの空の関数が含まれており、upはテーブル構造の順方向変化の詳細を定義し、downはテーブル構造のロールバックロジックを定義するために使用されます.たとえばupにcreateTableのテーブル作成動作がある場合、downには対応するdropTable削除テーブル動作がセットされています.1つの操作記録に相当する.変更後のユーザー移行ファイルは次のとおりです.
    'use strict'
    module.exports = {
      up: (queryInterface, Sequelize) => queryInterface.createTable(
        'user',
        {
          uid: {
            type: Sequelize.UUID,
            primaryKey: true
          },
          nickname: {
            type: Sequelize.STRING,
            allowNull: false,
            unique: true
          },
          avatar: Sequelize.STRING,
          description: Sequelize.STRING,
          username: {
            type: Sequelize.STRING,
            allowNull: false,
            unique: true
          },
          password: {
            type: Sequelize.STRING,
            allowNull: false
          },
          created_time: Sequelize.DATE,
          updated_time: Sequelize.DATE
        },
        {
          charset: 'utf8'
        }
      ),
    
      down: queryInterface => queryInterface.dropTable('user')
    }
  • 移行実行
  • npx sequelize db:migrate

    sequelize db:migrateのコマンドは、最終的にmigrationsディレクトリの下での移行動作を定義し、タイムスタンプの順序で移行記述を個別に実行し、最終的にデータベース・テーブル構造の自動化を完了するのに役立ちます.また、現在のデータベースで実行されている移行履歴バージョンを記録するために、データベースにSequelizeMetaというテーブルがデフォルトで作成されます.すでに実行されているものは再実行されず、sequelize db:migrate:undoが前の移行ファイルのdownコマンドを実行できます.
  • シードデータ
  • 実行
    sequelize seed:create --name init-user

    同様にseedersディレクトリの下でファイルタイムスタンプ-init-userを生成します.js修正後
    'use strict'
    const uuid = require('uuid')
    const timeStamp = {
      created_time: new Date(),
      updated_time: new Date()
    }
    const users = []
    for (let i = 1; i < 5; i++) {
      users.push(
        {
          uid: uuid(), username: 'zlj' + i, password: '123', nickname: '  ' + 1, ...timeStamp
        }
      )
    }
    module.exports = {
      up: queryInterface => queryInterface.bulkInsert('user', users, { charset: 'utf-8' }),
      down: (queryInterface, Sequelize) => {
        const { Op } = Sequelize
        return queryInterface.bulkDelete('user', { uid: { [Op.in]: users.map(v => v.uid) } }, {})
      }
    }
    
    

    塗りつぶしコマンドの実行
    sequelize db:seed:all

    データベースuserテーブルを表示するとレコードが多くなります.他の操作は移行と似ています.ドキュメント7定義モデルuserテーブルmodels/userを参照してください.js
    const moment = require('moment')
    module.exports = (sequelize, DataTypes) => sequelize.define(
      'user',
      {
        uid: {
          type: DataTypes.UUID,
          primaryKey: true
        },
        avatar: DataTypes.STRING,
        description: DataTypes.STRING,
        nickname: {
          type: DataTypes.STRING,
          unique: true,
          allowNull: false
        },
        username: {
          type: DataTypes.STRING,
          allowNull: false,
          unique: true
        },
        password: {
          type: DataTypes.STRING,
          allowNull: false
        },
        created_time: {
          type: DataTypes.DATE,
          get () {
            return moment(this.getDataValue('created_time')).format('YYYY-MM-DD HH:mm:ss')
          }
        },
        updated_time: {
          type: DataTypes.DATE,
          get () {
            return moment(this.getDataValue('updated_time')).format('YYYY-MM-DD HH:mm:ss')
          }
        }
      },
      {
        tableName: 'user'
      }
    )
    
  • インスタンス化seqlize
  • modes/index.js
    'use strict'
    const fs = require('fs')
    const path = require('path')
    const uuid = require('uuid')
    const Sequelize = require('sequelize')
    const basename = path.basename(__filename) // eslint-disable-line
    const configs = require(path.join(__dirname, '../config/config.js'))
    const db = {}
    const env = process.env.NODE_ENV || 'development'
    const config = {
      ...configs[env],
      define: {
        underscored: true,
        timestamps: true,
        updatedAt: 'updated_time',
        createdAt: 'created_time',
        hooks: {
          beforeCreate (model) {
            model.uid = uuid()
          }
        }
      }
    }
    let sequelize
    if (config.use_env_variable) {
      sequelize = new Sequelize(process.env[config.use_env_variable], config)
    } else {
      sequelize = new Sequelize(config.database, config.username, config.password, config)
    }
    fs
      .readdirSync(__dirname)
      .filter((file) => {
        return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js')
      })
      .forEach((file) => {
        const model = sequelize.import(path.join(__dirname, file))
        db[model.name] = model
      })
    Object.keys(db).forEach((modelName) => {
      if (db[modelName].associate) {
        db[modelName].associate(db)
      }
    })
    db.sequelize = sequelize
    db.Sequelize = Sequelize
    //       
    //           
    db.user.hasMany(db.article, { foreignKey: 'uid' })
    db.article.belongsTo(db.user, { foreignKey: 'author' })
    db.user.hasMany(db.comment, { foreignKey: 'uid' })
    db.comment.belongsTo(db.user, { foreignKey: 'author' })
    db.user.hasMany(db.article_like, { foreignKey: 'uid' })
    db.article_like.belongsTo(db.user, { foreignKey: 'author' })
    db.article.hasMany(db.comment)
    db.comment.belongsTo(db.article)
    db.article.hasMany(db.article_like)
    db.article_like.belongsTo(db.article)
    module.exports = db

    2.2 Joi要求パラメータチェック
    joiはリクエストパラメータを検証できます
    次の操作を行います.
  • 取付
  • #      hapi v16   joi   
    npm i joi@14
  • 使用は2.3 config.validate、詳細は公式ドキュメント
  • を参照
    2.3 hapiでインタフェースを書く
    post:ログインインタフェース:routes/user.js
    const models = require('../models')
    const Joi = require('@hapi/joi')
    {
        method: 'POST',
        path: '/api/user/login',
        handler: async (request, h) => {
          const res = await models.user.findAll({
            attributes: {
              exclude: ['password', 'created_time', 'updated_time']
            },
            where: {
              username: request.payload.username,
              //            ,md5 
              password: request.payload.password
            }
          })
          const data = res[0]
          if (res.length > 0) {
            return h.response({
              code: 0,
              message: '    !',
              data: {
                //   token
                token: generateJWT(data.uid),
                ...data.dataValues
              }
            })
          } else {
            return h.response({
              code: 10,
              message: '        '
            })
          }
        },
        config: {
          auth: false,
          tags: ['api', 'user'],
          description: '    ',
          validate: {
            payload: {
              username: Joi.string().required(),
              password: Joi.string().required()
            }
          }
        }
      },
    

    2.4インタフェースドキュメントswagger
  • インストール:
  • npm i hapi-swagger@10
    npm i inert@5
    npm i vision@5
    npm i package@1
  • を使用
    ├── plugins                       # hapi     
    |   ├── hapi-swagger.js  

    hapi-swagger.js
    // plugins/hapi-swagger.js
    const inert = require('@hapi/inert')
    const vision = require('@hapi/vision')
    const package = require('package')
    const hapiSwagger = require('hapi-swagger')
    module.exports = [
      inert,
      vision,
      {
        plugin: hapiSwagger,
        options: {
          documentationPath: '/docs',
          info: {
            title: 'my-blog      ',
            version: package.version
          },
          //       tags        
          grouping: 'tags',
          tags: [
            { name: 'user', description: '    ' },
            { name: 'article', description: '    ' }
          ]
        }
      }
    ]

    server/index.js
    const pluginHapiSwagger = require('../plugins/hapi-swagger')
    //     
    ...
     await server.register([
        //       hapi-swagger
        ...pluginHapiSwagger
      ]
    ...

    あなたのdev.host:dev.port/docsを開いて私のオンラインを見ることができます
    2.5 token認証hapi-auth-jwt 2
    cookie hapiはすでにあなたの解析を手伝って、ファイルのアップロードも
  • インストール:
  • npm i hapi-auth-jwt2@8
  • 構成:
  • ドキュメント
    ├── plugins                       # hapi     
    │ ├── hapi-auth-jwt2.js           # jwt     

    hapi-auth-jwt2.js
    const validate = (decoded) => {
      // eslint disable
      // decoded   JWT payload        
      const { exp } = decoded
      if (new Date(exp * 1000) < new Date()) {
        const response = {
          code: 4,
          message: '    ',
          data: '    '
        }
        return { isValid: true, response }
      }
      return { isValid: true }
    }
    module.exports = (server) => {
      server.auth.strategy('jwt', 'jwt', {
        //       config/index.js     jwtSecret    ,     process.env.JWT_SECRET     .git        。
        key: process.env.JWT_SECRET,
        validate,
        verifyOptions: {
          ignoreExpiration: true
        }
      })
      server.auth.default('jwt')
    }
  • 登録プラグイン
  • server/index.js
    const hapiAuthJWT2 = require('hapi-auth-jwt2')
    ...
    await server.register(hapiAuthJWT2)
    ...
  • 効果:
  • デフォルトでは、すべてのインタフェースがtoken認証を必要とする、ログインインタフェースなどのインタフェースをconfigすることができる.auth=falseは上のログインインタフェースに戻らず、ユーザー名とパスワードの検証に成功してtokenを生成します.
    const generateJWT = (uid) => {
      const payload = {
        userId: uid,
        exp: Math.floor(new Date().getTime() / 1000) + 24 * 60 * 60
      }
      return JWT.sign(payload, process.env.JWT_SECRET)
    }
    handler () {
          const res = await models.user.findAll({
            attributes: {
              exclude: ['password', 'created_time', 'updated_time']
            },
            where: {
              username: request.payload.username,
              password: request.payload.password
            }
          })
          const data = res[0]
          if (res.length > 0) {
            return h.response({
              code: 0,
              message: '    !',
              data: {
                token: generateJWT(data.uid),
                ...data.dataValues
              }
            })
          } else {
            return h.response({
              code: 10,
              message: '        '
            })
          }
        }

    先端にtokeを持って頭に詰めればいいts
    request.interceptors.request.use((config: AxiosRequestConfig): AxiosRequestConfig => {
      const token = getToken()
      if (token) { config.headers.authorization = token }
      return config
    })
  • 要求ヘッダ追加Joiチェック
  • const jwtHeaderDefine = {
      headers: Joi.object({
        authorization: Joi.string().required()
      }).unknown()
    }
    //     
    ...
    validate: {
            ...jwtHeaderDefine,
            params: {
              uid: Joi.string().required()
            }
          }
    ...

    swaggerオンラインドキュメントの中国語から変化がわかります
    2.6ページングhapi-paginationの追加
  • 取付
  • npm i hapi-pagination@3
  • 構成
  • plugins/hapi-pagination.js
    const hapiPagination = require('hapi-pagination')
    const options = {
      query: {
        page: {
          name: 'the_page' // The page parameter will now be called the_page
        },
        limit: {
          name: 'per_page', // The limit will now be called per_page
          default: 10 // The default value will be 10
        }
      },
      meta: {
        location: 'body', // The metadata will be put in the response body
        name: 'metadata', // The meta object will be called metadata
        count: {
          active: true,
          name: 'count'
        },
        pageCount: {
          name: 'totalPages'
        },
        self: {
          active: false // Will not generate the self link
        },
        first: {
          active: false // Will not generate the first link
        },
        last: {
          active: false // Will not generate the last link
        }
      },
      routes: {
        include: ['/article'] //        
      }
    }
    module.exports = {
      plugin: hapiPagination,
      options
    }
  • 登録プラグイン
  • const pluginHapiPagination = require('./plugins/hapi-pagination');
    await server.register([
      pluginHapiPagination,
    ])
  • パラメータチェック
  • を加える
    const paginationDefine = {
      limit: Joi.number().integer().min(1).default(10)
        .description('      '),
      page: Joi.number().integer().min(1).default(1)
        .description('   '),
      pagination: Joi.boolean().description('      ,   true')
    }
    //     
    // joi  
    ...
    validate: {
            query: {
              ...paginationDefine
            }
          }
    ...
  • データベースクエリー
  •    const { rows: results, count: totalCount } = await models.xxxx.findAndCountAll({
          limit: request.query.limit,
          offset: (request.query.page - 1) * request.query.limit,
        });

    3最後
    オンラインアドレス体験フル機能へようこそ
    1踏み込みまとめ:
  • がインタフェース500に遭遇する場合、modelの操作後にエラー、例えばmodel sをキャプチャすることができる.findAll().catch(e => console.log(e))
  • バージョン互換性の問題に注意し、プラグインとhapiまたはnuxtバージョンの互換性
  • nuxt.config.tsの構成が有効でないtsc nuxtを実行することができる.config.ts手動コンパイル
  • asyncDataでデータを要求し、絶対アドレスを書かないで、デフォルトで80ポートの
  • を要求します.
    2開発収穫
  • 基本的なバックエンド開発プロセス
  • を熟知している.
  • プラグインが互換性がない場合や他のニーズがある場合は、自分で英語のドキュメントを見なければなりません.英語のドキュメントを見て
  • を薄めることができます.
  • バックエンドの開発に必要な仕事が多いので、インタフェースから導入まで、後で仕事の中でお互いに理解しなければなりません.
    3参考
    掘金小冊:葉盛飛『hapiに基づくNode.js小プログラムバックエンド開発実践ガイドライン』
    ps:いらっしゃいませstar^^;github: https://github.com/huoguozhang/my-blog