Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 好男人好资源电影在线播放,中文字幕在线不卡精品视频99 ,色女人综合网

          整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          Egg.js試水 - 文章增刪改查「前后端分離」

          Egg.js試水 - 文章增刪改查「前后端分離」

          頭條創(chuàng)作挑戰(zhàn)賽#

          本文同步本人掘金平臺的文章:https://juejin.cn/post/6871478190037336078

          上一篇文章講的是后端渲染的項目 - Egg.js 試水 - 天氣預(yù)報。但是沒有引入數(shù)據(jù)庫。這次的試水項目是文章的增刪改查,將數(shù)據(jù)庫引進,并且實現(xiàn)前后端分離。

          項目的github地址是egg-demo/article-project。

          下面直接進入正題~?

          項目結(jié)構(gòu)

          article-project
          ├── client
          ├── service
          └── README.md
          復(fù)制代碼

          因為是前后端分離的項目,那么我們就以文件夾client存放客戶端,以文件夾service存放服務(wù)端。README.md是項目說明文件。

          客戶端初始化

          為了快速演示,我們使用vue-cli腳手架幫我們生成項目,并引入了vue-ant-design。

          項目初始化

          推薦使用yarn進行包管理。

          $ npm install -g @vue/cli
          # 或者
          $ yarn global add @vue/cli
          復(fù)制代碼

          然后新建一個項目。

          $ vue create client
          復(fù)制代碼

          接著我們進入項目并啟動。

          $ cd client
          $ npm run serve
          # 或者
          $ yarn run serve
          復(fù)制代碼

          此時,我們訪問瀏覽器地址http://localhost:8080/,就會看到歡迎頁面。

          最后我們引入ant-design-vue。

          $ npm install ant-design-vue
          # 或
          $ yarn add ant-design-vue
          復(fù)制代碼

          在這里,我們?nèi)忠隺nt-design-vue的組件。實際開發(fā)中,按需引入比較友好,特別是只是使用了該UI框架部分功能組件的時候。

          // src/main.js
          
          import Vue from 'vue'
          import App from './App.vue'
          import Antd from 'ant-design-vue'
          import 'ant-design-vue/dist/antd.css'
          
          Vue.use(Antd)
          Vue.config.productionTip=false;
          
          new Vue({
            render: h=> h(App),
          }).$mount('#app');
          復(fù)制代碼

          當(dāng)然,在此項目中,還牽涉到幾種npm包,之后只寫yarn或者npm命令行操作。

          路由設(shè)置

          路由的跳轉(zhuǎn)需要vue-router的協(xié)助。

          # 路由
          $ yarn add vue-router
          
          # 進度條
          $ yarn add nprogress
          復(fù)制代碼

          這里只用到登錄頁,首頁,文章列表頁面和文章的新增/編輯頁面。所以我的路由配置如下:

          // src/router/index.js
          
          import Vue from 'vue'
          import Router from 'vue-router'
          import Index from '@/views/index'
          import { UserLayout, BlankLayout } from '@/components/layouts'
          import NProgress from 'nprogress' // progress bar
          import 'nprogress/nprogress.css' // progress bar style
          
          const whiteList=['login'] // no redirect whitelist
          
          import { getStore } from "@/utils/storage"
          
          Vue.use(Router)
          
          const router=new Router({
            routes: [
              {
                  path: '/',
                  name: 'index',
                  redirect: '/dashboard/workplace',
                  component: Index,
                  children: [
                    {
                      path: 'dashboard/workplace',
                      name: 'dashboard',
                      component: ()=> import('@/views/dashboard')
                    },
                    {
                      path: 'article/list',
                      name: 'article_list',
                      component: ()=> import('@/views/article/list')
                    },
                    {
                      path: 'article/info',
                      name: 'article_info',
                      component: ()=> import('@/views/article/info')
                    }
                  ]
              },
              {
                path: '/user',
                component: UserLayout,
                redirect: '/user/login',
                // hidden: true,
                children: [
                  {
                    path: 'login',
                    name: 'login',
                    component: ()=> import(/* webpackChunkName: "user" */ '@/views/user/login')
                  }
                ]
              },
              {
                path: '/exception',
                component: BlankLayout,
                redirect: '/exception/404',
                children: [
                  {
                    path: '404',
                    name: '404',
                    component: ()=> import(/* webpackChunkName: "user" */ '@/views/exception/404')
                  }
                ]
              },
              {
                path: '*',
                component: ()=> import(/* webpackChunkName: "user" */ '@/views/exception/404')
              }
            ],
            // base: process.env.BASE_URL,
            scrollBehavior: ()=> ({ y: 0 }),
          })
          
          router.beforeEach((to, from, next)=> {
            NProgress.start() // start progress bar
            if(getStore('token', false)) { // 有token
              if(to.name==='index' || to.path==='/index' || to.path==='/') {
                next({ path: '/dashboard/workplace'})
                NProgress.done()
                return false
              }
              next()
            } else {
              if(to.path !=='/user/login') {
                (new Vue()).$notification['error']({
                  message: '驗證失效,請重新登錄!'
                })
              }
              if(whiteList.includes(to.name)) {
                // 在免登錄白名單,直接進入
                next()
              } else {
                next({
                  path: '/user/login',
                  query: {
                    redirect: to.fullPath
                  }
                })
                NProgress.done()
              }
            }
            next()
          })
          
          router.afterEach(route=> {
            NProgress.done()
          })
          
          export default router
          復(fù)制代碼

          接口請求設(shè)置

          接口請求使用了axios,我們來集成下。

          # axios
          $ yarn add axios
          復(fù)制代碼

          我們即將要代理的后端服務(wù)的地址是127.0.0.1:7001,所以我們的配置如下:

          // vue.config.js
          
          ...
            devServer: {
              host: '0.0.0.0',
              port: '9008',
              https: false,
              hotOnly: false,
              proxy: { // 配置跨域
                '/api': {
                  //要訪問的跨域的api的域名
                  target: 'http://127.0.0.1:7001/',
                  ws: true,
                  changOrigin: true
                },
              },
            },
          ...
          復(fù)制代碼

          我們封裝下請求

          // src/utils/request.js
          
          import Vue from 'vue'
          import axios from 'axios'
          import store from '@/store'
          import notification from 'ant-design-vue/es/notification'
          import { ACCESS_TOKEN } from '@/store/mutation-types'
          import { notice } from './notice';
          
          const err=(error)=> {
            if (error.response) {}
            return Promise.reject(error)
          }
          
          function loginTimeOut () {
            notification.error({ message: '登錄信息失效', description: '請重新登錄' })
            store.dispatch('user/logout').then(()=> {
              setTimeout(()=> {
                window.location.reload()
              }, 1500)
            })
          }
          
          // 創(chuàng)建 auth axios 實例
          const auth=axios.create({
            headers: {
              'Content-Type': 'application/json;charset=UTF-8',
              'X-Requested-With': 'XMLHttpRequest'
            },
            baseURL: '/', // api base_url
            timeout: 10000 // 請求超時時間 10秒鐘
          })
          
          // request interceptor
          auth.interceptors.request.use(config=> {
            const token=Vue.ls.get(ACCESS_TOKEN)
            if (token) {
              config.headers[ 'Authorization' ]='JWT '+ token // 讓每個請求攜帶自定義 token 請根據(jù)實際情況自行修改
            }
            return config
          }, err)
          
          // response interceptor
          auth.interceptors.response.use(
            response=> {
              if (response.code===10140) {
                loginTimeOut()
              } else {
                return response.data
              }
            }, 
            error=> { // 錯誤處理
              console.log(error.response, 'come here')
              if(error.response && error.response.status===403) {
                notice({
                    title: '未授權(quán),你沒有訪問權(quán)限,請聯(lián)系管理員!',
                }, 'notice', 'error', 5)
                return
              }
              notice({
                  title: (error.response && error.response.data && error.response.data.msg) || (error.response && `${error.response.status} - ${error.response.statusText}`),
              }, 'notice', 'error', 5)
            }
          )
          
          export {
            auth
          }
          
          復(fù)制代碼

          樣式預(yù)處理器

          當(dāng)然,為了更好的管理你的頁面樣式,建議還是添加一種CSS預(yù)處理器。這里我選擇了less預(yù)處理器。

          # less 和 less-loader
          $ yarn add less --dev
          $ yarn add less-loader --dev
          復(fù)制代碼

          僅僅是安裝還不行,我們來配置下。

          // vue.config.js
          
          ...
            css: {
              loaderOptions: {
                less: {
                  modifyVars: {
                    blue: '#3a82f8',
                    'text-color': '#333'
                  },
                  javascriptEnabled: true
                }
              }
            },
          ...
          復(fù)制代碼

          布局文章頁面

          文章列表頁的骨架:

          <!--src/views/article/list.vue-->
          
          <template>
            <div class="article-list">
              <a-table
                style="border: none;" 
                bordered
                :loading="loading"
                :rowKey="row=> row.id"
                :columns="columns" 
                :data-source="data"
                :pagination="pagination" 
                @change="change"/> 
            </div>
          </template>
          復(fù)制代碼

          文章編輯/新增頁的骨架:

          <!--src/views/article/info.vue-->
          
          <template>
            <div class="article-info">
              <a-spin :spinning="loading">
                <a-row style="display: flex; justify-content: flex-end; margin-bottom: 20px;">
                  <a-button type="primary" @click="$router.go(-1)">返回</a-button>
                </a-row>
                <a-form :form="form" v-bind="formItemLayout">
                  <a-form-item
                    label="標(biāo)題">
                    <a-input 
                      placeholder="請輸入標(biāo)題"
                      v-decorator="[
                        'title',
                        {rules: [{ required: true, message: '請輸入標(biāo)題'}]}
                      ]"/>
                  </a-form-item>
                  <a-form-item
                    label="分組">
                    <a-select
                      showSearch
                      v-decorator="[
                        'group',
                        {rules: [{ required: true, message: '請選擇分組'}]}
                      ]"
                      placeholder="請選擇分組">
                      <a-select-option value="分組1">分組1</a-select-option>
                      <a-select-option value="分組2">分組2</a-select-option>
                      <a-select-option value="分組3">分組3</a-select-option>
                      <a-select-option value="分組4">分組4</a-select-option>
                    </a-select>
                  </a-form-item>
                  <a-form-item
                    label="作者">
                    <a-input 
                      placeholder="請輸入作者"
                      v-decorator="[
                        'author',
                        {rules: [{ required: true, message: '請輸入作者'}]}
                      ]"/>
                  </a-form-item>
                  <a-form-item
                    label="內(nèi)容">
                    <a-textarea 
                      :autosize="{ minRows: 10, maxRows: 12 }"
                      placeholder="請輸入文章內(nèi)容"
                      v-decorator="[
                        'content',
                        {rules: [{ required: true, message: '請輸入文章內(nèi)容'}]}
                      ]"/>
                  </a-form-item>
                </a-form>
          
                <a-row style="margin-top: 20px; display: flex; justify-content: space-around;">
                  <a-button @click="$router.go(-1)">取消</a-button>
                  <a-button type="primary" icon="upload" @click="submit">提交</a-button>
                </a-row>
              </a-spin>
            </div>
          </template>
          復(fù)制代碼

          前端的項目有了雛形,下面搭建下服務(wù)端的項目。

          服務(wù)端初始化

          這里直接使用eggjs框架來實現(xiàn)服務(wù)端。你可以考慮使用typescript方式的來初始化項目,但是我們這里直接使用javascript而不是它的超級typescript來初始化項目。

          初始化項目

          $ mkdir service
          $ cd service
          $ npm init egg --type=simple
          $ npm i
          復(fù)制代碼

          啟動項目:

          $ npm run dev
          復(fù)制代碼

          在瀏覽器中打開localhost:7001地址,我們就可以看到eggjs的歡迎頁面。當(dāng)然,我們這里基本上不會涉及到瀏覽器頁面,因為我們開發(fā)的是api接口。更多的是使用postman工具進行調(diào)試。

          引入數(shù)據(jù)庫

          這里使用的數(shù)據(jù)庫是mysql,但是我們不是直接使它,而是安裝封裝過的mysql2和egg-sequelize。

          在 Node.js 社區(qū)中,sequelize 是一個廣泛使用的 ORM 框架,它支持 MySQL、PostgreSQL、SQLite 和 MSSQL 等多個數(shù)據(jù)源。它會輔助我們將定義好的 Model 對象加載到 app 和 ctx 上。

          # 安裝mysql
          $ yarn add mysql2
          
          # 安裝sequelize
          $ yarn add egg-sequelize
          復(fù)制代碼

          當(dāng)然,我們需要一個數(shù)據(jù)庫進行連接,那就得安裝一個數(shù)據(jù)庫,如果你使用的是mac os的話,你可以通過下面的方法進行安裝:

          brew install mysql
          brew services start mysql
          復(fù)制代碼

          window系統(tǒng)的話,可以考慮下載相關(guān)的安裝包執(zhí)行就行了,這里不展開說了。

          數(shù)據(jù)庫安裝好后,我們管理數(shù)據(jù)庫,可以通過控制臺命令行進行控制,也可以通過圖形化工具進行控制。我們推薦后者,我們下載了一個Navicat Premiun的工具。

          Navicat Premiun 是一款數(shù)據(jù)庫管理工具。

          當(dāng)然還可以下載phpstudy進行輔助開發(fā)。

          連接數(shù)據(jù)庫

          配置數(shù)據(jù)庫的基本信息,前提是我們已經(jīng)創(chuàng)建好了這個數(shù)據(jù)庫。假設(shè)我們創(chuàng)建了一個名為article的數(shù)據(jù)庫,用戶是reng,密碼是123456。那么,我們就可以像下面這樣連接。

          // config/config.default.js
          ...
            config.sequelize={
              dialect: 'mysql',
              host: '127.0.0.1',
              port: 3306,
              database: 'article',
              username: 'reng',
              password: '123456',
              operatorsAliases: false
            };
          ...
          復(fù)制代碼

          當(dāng)然,這是通過包egg-sequelize處理的,我們也要將其引入,告訴eggjs去使用這個插件。

          // config/plugin.js
          ...
            sequelize: {
              enable: true,
              package: 'egg-sequelize',
            },
          ...
          復(fù)制代碼

          創(chuàng)建數(shù)據(jù)庫表

          你可以直接通過控制臺命令行執(zhí)行mysql語句創(chuàng)建。但是,我們直接使用遷移操作完成。

          在項目中,我們希望將所有的數(shù)據(jù)庫Migrations相關(guān)的內(nèi)容都放在database目錄下面,所以我們在根目錄下新建一個.sequelizerc配置文件:

          // .sequelizerc
          
          'use strict';
          
          const path=require('path');
          
          module.exports={
            config: path.join(__dirname, 'database/config.json'),
            'migrations-path': path.join(__dirname, 'database/migrations'),
            'seeders-path': path.join(__dirname, 'database/seeders'),
            'models-path': path.join(__dirname, 'app/model'),
          };
          復(fù)制代碼

          初始化Migrations配置文件和目錄。

          npx sequelize init:config
          npx sequelize init:migrations
          復(fù)制代碼

          更加詳細(xì)內(nèi)容,可見eggjs sequelize章節(jié)。

          我們按照官網(wǎng)上的操作初始化了文章列表的數(shù)據(jù)庫表articles。對應(yīng)的model內(nèi)容如下:

          // app/model/article.js
          
          'use strict';
          
          module.exports=app=> {
            const { STRING, INTEGER, DATE, NOW, TEXT }=app.Sequelize;
          
            const Article=app.model.define('articles', {
              id: {type: INTEGER, primaryKey: true, autoIncrement: true},//記錄id
              title: {type: STRING(255)},// 標(biāo)題
              group: {type: STRING(255)}, // 分組
              author: {type: STRING(255)},// 作者  
              content: {type: TEXT}, // 內(nèi)容
              created_at: {type: DATE, defaultValue: NOW},// 創(chuàng)建時間
              updated_at: {type: DATE, defaultValue: NOW}// 更新時間
            }, {
              freezeTableName: true // 不自動將表名添加復(fù)數(shù)
            });
          
            return Article;
          };
          復(fù)制代碼

          API的CRUD

          上面服務(wù)端的工作,已經(jīng)幫我們做好編寫接口的準(zhǔn)備了。那么,下面結(jié)合數(shù)據(jù)庫,我們來實現(xiàn)下文章增刪改查的操作。

          我們使用的是MVC的架構(gòu),那么我們的現(xiàn)有代碼邏輯自然會這樣流向:

          app/router.js 獲取文章路由到 -> app/controller/article.js中對應(yīng)的方法 -> 到app/service/article.js中的方法。那么,我們就主要展示在controller層和service層做的事情吧。畢竟router層沒啥好講的。

          獲取文章列表

          [get] /api/get-article-list

          // app/controller/article.js
          ...
            async getList() {
              const { ctx }=this
              const { page, page_size }=ctx.request.query
              let lists=await ctx.service.article.findArticle({ page, page_size })
              ctx.returnBody(200, '獲取文章列表成功!', {
                count: lists && lists.count || 0,
                results: lists && lists.rows || []
              }, '00000')
            }
          ...
          復(fù)制代碼
          // app/service/article.js
          ...
            async findArticle(obj) {
              const { ctx }=this
              return await ctx.model.Article.findAndCountAll({
                order: [['created_at', 'ASC']],
                offset: (parseInt(obj.page) - 1) * parseInt(obj.page_size), 
                limit: parseInt(obj.page_size)
              })
            }
          ...
          復(fù)制代碼

          獲取文章詳情

          [get] /api/get-article

          // app/controller/article.js
          ...
            async getItem() {
              const { ctx }=this
              const { id }=ctx.request.query
              let articleDetail=await ctx.service.article.getArticle(id)
              if(!articleDetail) {
                ctx.returnBody(400, '不存在此條數(shù)據(jù)!', {}, '00001')
                return
              }
              ctx.returnBody(200, '獲取文章成功!', articleDetail, '00000')
            }
          ...
          復(fù)制代碼
          // app/service/article.js
          ...
            async getArticle(id) {
              const { ctx }=this
              return await ctx.model.Article.findOne({
                where: {
                  id
                }
              })
            }
          ...
          復(fù)制代碼

          添加文章

          [post] /api/post-article

          // app/controller/article.js
          ...
            async postItem() {
              const { ctx }=this
              const { author, title, content, group }=ctx.request.body
          
              // 新文章
              let newArticle={ author, title, content, group }
          
              let article=await ctx.service.article.addArticle(newArticle)
              
              if(!article) {
                ctx.returnBody(400, '網(wǎng)絡(luò)錯誤,請稍后再試!', {}, '00001')
                return
              }
              ctx.returnBody(200, '新建文章成功!', article, '00000')
            }
          ...
          復(fù)制代碼
          // app/service/article.js
          ...
            async addArticle(data) {
              const { ctx }=this
              return await ctx.model.Article.create(data)
            }
          ...
          復(fù)制代碼

          編輯文章

          [put] /api/put-article

          // app/controller/article.js
          ...
            async putItem() {
              const { ctx }=this
              const { id }=ctx.request.query
              const { author, title, content, group }=ctx.request.body
          
              // 存在文章
              let editArticle={ author, title, content, group }
          
              let article=await ctx.service.article.editArticle(id, editArticle)
              
              if(!article) {
                ctx.returnBody(400, '網(wǎng)絡(luò)錯誤,請稍后再試!', {}, '00001')
                return
              }
              ctx.returnBody(200, '編輯文章成功!', article, '00000')
            }
          ...
          復(fù)制代碼
          // app/service/article.js
          ...
            async editArticle(id, data) {
              const { ctx }=this
              return await ctx.model.Article.update(data, {
                where: {
                  id
                }
              })
            }
          ...
          復(fù)制代碼

          刪除文章

          [delete] /api/delete-article

          // app/controller/article.js
          ...
            async deleteItem() {
              const { ctx }=this
              const { id }=ctx.request.query
              let articleDetail=await ctx.service.article.deleteArticle(id)
              if(!articleDetail) {
                ctx.returnBody(400, '不存在此條數(shù)據(jù)!', {}, '00001')
                return
              }
              ctx.returnBody(200, '刪除文章成功!', articleDetail, '00000')
            }
          ...
          復(fù)制代碼
          // app/service/article.js
          ...
            async deleteArticle(id) {
              const { ctx }=this
              return await ctx.model.Article.destroy({
                where: {
                  id
                }
              })
            }
          ...
          復(fù)制代碼

          在完成接口的編寫后,你可以通過postman 應(yīng)用去驗證下是否返回的數(shù)據(jù)。

          前端對接接口

          接下來就得切回來client文件夾進行操作了。我們在上面已經(jīng)簡單封裝了請求方法。這里來編寫文章CRUD的請求方法,我們?yōu)榱朔奖阏{(diào)用,將其統(tǒng)一掛載在Vue實例下。

          // src/api/index.js
          
          import article from './article'
          
          const api={
            article
          }
          
          export default api
          
          export const ApiPlugin={}
          
          ApiPlugin.install=function (Vue, options) {
            Vue.prototype.api=api // 掛載api在原型上
          }
          復(fù)制代碼

          獲取文章列表

          // src/api/article.js
          ...
            export function getList(params) {
              return auth({
                url: '/api/get-article-list',
                method: 'get',
                params
              })
            }
          ...
          復(fù)制代碼
          // src/views/article/list.vue
          ...
            getList() {
              let vm=this
              vm.loading=true
              vm.api.article.getList({
                page: vm.pagination.current,
                page_size: vm.pagination.pageSize
              }).then(res=> {
                if(res.code==='00000'){
                  vm.pagination.total=res.data && res.data.count || 0
                  vm.data=res.data && res.data.results || []
                } else {
                  vm.$message.warning(res.msg || '獲取文章列表失敗')
                }
              }).finally(()=> {
                vm.loading=false
              })
            }
          ...
          復(fù)制代碼

          獲取文章詳情

          // src/api/article.js
          ...
            export function getItem(params) {
              return auth({
                url: '/api/get-article',
                method: 'get',
                params
              })
            }
          ...
          復(fù)制代碼
          // src/views/article/info.vue
          ...
            getDetail(id) {
              let vm=this
              vm.loading=true
              vm.api.article.getItem({ id }).then(res=> {
                if(res.code==='00000') {
                  // 數(shù)據(jù)回填
                  vm.form.setFieldsValue({
                    title: res.data && res.data.title || undefined,
                    author: res.data && res.data.author || undefined,
                    content: res.data && res.data.content || undefined,
                    group: res.data && res.data.group || undefined,
                  })
                } else {
                  vm.$message.warning(res.msg || '獲取文章詳情失敗!')
                }
              }).finally(()=> {
                vm.loading=false
              })
            },
          ...
          復(fù)制代碼

          添加文章

          // src/api/article.js
          ...
            export function postItem(data) {
              return auth({
                url: '/api/post-article',
                method: 'post',
                data
              })
            }
          ...
          復(fù)制代碼
          // src/views/article/info.vue
          ...
            submit() {
              let vm=this
              vm.loading=true
              vm.form.validateFields((err, values)=> {
                if(err){
                  vm.loading=false
                  return
                }
                let data={
                  title: values.title,
                  group: values.group,
                  author: values.author,
                  content: values.content
                }
                vm.api.article.postItem(data).then(res=> {
                  if(res.code==='00000') {
                    vm.$message.success(res.msg || '新增成功!')
                    vm.$router.push({
                      path: '/article/list'
                    })
                  } else {
                    vm.$message.warning(res.msg || '新增失敗!')
                  }
                }).finally(()=> {
                  vm.loading=false
                })
              })
            },
          ...
          復(fù)制代碼

          編輯文章

          // src/api/article.js
          ...
            export function putItem(params, data) {
              return auth({
                url: '/api/put-article',
                method: 'put',
                params,
                data
              })
            }
          ...
          復(fù)制代碼
          // src/views/article/info.vue
          ...
            submit() {
              let vm=this
              vm.loading=true
              vm.form.validateFields((err, values)=> {
                if(err){
                  vm.loading=false
                  return
                }
                let data={
                  title: values.title,
                  group: values.group,
                  author: values.author,
                  content: values.content
                }
                vm.api.article.putItem({id: vm.$route.query.id}, data).then(res=> {
                  if(res.code==='00000') {
                    vm.$message.success(res.msg || '新增成功!')
                    vm.$router.push({
                      path: '/article/list'
                    })
                  } else {
                    vm.$message.warning(res.msg || '新增失敗!')
                  }
                }).finally(()=> {
                  vm.loading=false
                })
              })
            }
          ...
          復(fù)制代碼

          刪除文章

          // src/api/article.js
          ...
            export function deleteItem(params) {
              return auth({
                url: '/api/delete-article',
                method: 'delete',
                params
              })
            }
          ...
          復(fù)制代碼
          // src/views/article/list.vue
          ...
            delete(text, record, index) {
              let vm=this
              vm.$confirm({
                title: `確定刪除【${record.title}】`,
                content: '',
                okText: '確定',
                okType: 'danger',
                cancelText: '取消',
                onOk() {
                  vm.api.article.deleteItem({ id: record.id }).then(res=> {
                    if(res.code==='00000') {
                      vm.$message.success(res.msg || '刪除成功!')
                      vm.handlerSearch()
                    } else {
                      vm.$message.warning(res.msg || '刪除失敗!')
                    }
                  })
                },
                onCancel() {},
              })
            }
          ...
          復(fù)制代碼

          效果圖

          在egg-demo/article-project/client/前端項目中,頁面包含了登錄頁面,歡迎頁面和文章頁面。

          歡迎頁面忽略不計

          登錄頁

          文章列表

          文章編輯

          后話

          至此,整個項目已經(jīng)完成。代碼倉庫為egg-demo/article-project/,感興趣可以進行擴展學(xué)習(xí)。


          形被稱作“完美的形狀”,它可能是生物漫長進化過程的結(jié)晶。

          phys.org網(wǎng)站當(dāng)?shù)貢r間8月27日報道,英國肯特大學(xué)領(lǐng)導(dǎo)的研究團隊取得了重要突破,他們發(fā)現(xiàn)了一種通用的數(shù)學(xué)公式,可描述自然界中存在的任何蛋類外殼形狀。直到不久前,這項工作還無人成功完成。

          卵形被稱作“完美的形狀”,它一直吸引著數(shù)學(xué)家、工程師和生物學(xué)家的關(guān)注。卵形的特征對于胚胎孵化、有效脫離母體以及承受載荷等均有重要意義。研究人員使用四種幾何圖形來分析卵形:球形、橢球形、卵球形和梨形。然而,梨形的數(shù)學(xué)公式尚未導(dǎo)出。為了彌補這個缺陷,研究人員引入了一項額外的函數(shù),開發(fā)了一個數(shù)學(xué)模型來擬合一種全新的幾何形狀,這一形狀類似球體至橢球體演化的最后階段。

          新的卵形通用數(shù)學(xué)公式是基于四個參數(shù)建立的,包括:卵長、最大寬度、垂直軸的移動以及四分之一卵長直徑。研究人員指出,這個公式不僅是人類認(rèn)知卵形本身的重要工具,還有助于科學(xué)家了解卵形的進化原因與方式。

          有關(guān)卵形的數(shù)學(xué)描述已經(jīng)在食品研究、機械工程、農(nóng)業(yè)、生物科學(xué)、建筑和航空學(xué)等領(lǐng)域得到了應(yīng)用。新公式也在以下方面大有可為,例如:(1)優(yōu)化對生物體的科學(xué)描述;(2)簡化、精確化對生物體物理特性的測定。對于從事禽卵孵化、加工、儲存和分類技術(shù)研究的工程師而言,卵的外部特性至關(guān)重要。使用體積、表面積、曲率半徑等指標(biāo)描述卵的輪廓,可降低識別過程的復(fù)雜性;(3)推動未來生物學(xué)相關(guān)工程的發(fā)展。卵形在建筑中應(yīng)用廣泛,倫敦市政廳的屋頂就采用了卵形設(shè)計,這不僅能提高最大負(fù)荷,還能減少材料消耗。

          研究負(fù)責(zé)人、肯特大學(xué)遺傳學(xué)教授Darren Griffin說:“正如這個新公式證明的那樣,我們必須對卵的形成等生物進化過程進行數(shù)學(xué)描述,這是進化生物學(xué)研究的基石。新公式可應(yīng)用于基礎(chǔ)學(xué)科,特別是推動食品和家禽行業(yè)的發(fā)展。”肯特大學(xué)訪問學(xué)者Michael Romanov補充說:“這個數(shù)學(xué)公式強調(diào)了數(shù)學(xué)和生物學(xué)之間的某種哲學(xué)和諧關(guān)系。由此出發(fā),我們甚至能進一步理解宇宙。”

          編譯:雷鑫宇 審稿:西莫 責(zé)編:陳之涵

          期刊來源:《紐約科學(xué)院年報》

          期刊編號:0077-8923

          原文鏈接:https://phys.org/news/2021-08-reveals-ancient-universal-equation-egg.html

          中文內(nèi)容僅供參考,一切內(nèi)容以英文原版為準(zhǔn)。轉(zhuǎn)載請注明來源。

          為一名前端開發(fā)者,在選擇 Nodejs 后端服務(wù)框架時,第一時間會想到 Egg.js,不得不說 Egg.js 是一個非常優(yōu)秀的企業(yè)級框架,它的高擴展性和豐富的插件,極大的提高了開發(fā)效率。開發(fā)者只需要關(guān)注業(yè)務(wù)就好,比如要使用 redis,引入 egg-redis 插件,然后簡單配置就可以了。正因為如此,第一次接觸它,我便喜歡上了它,之后也用它開發(fā)過不少應(yīng)用。

          有了如此優(yōu)秀的框架,那么如何將一個 Egg.js 的服務(wù)遷移到 Serverless 架構(gòu)上呢?

          背景

          我在文章 基于 Serverless Component 的全棧解決方案 中講述了,如何將一個基于 Vue.js 的前端應(yīng)用和基于 Express 的后端服務(wù),快速部署到騰訊云上。雖然受到不少開發(fā)者的喜愛,但是很多開發(fā)者私信問我,這還是一個 Demo 性質(zhì)的項目而已,有沒有更加實用性的解決方案。而且他們實際開發(fā)中,很多使用的正是 Egg.js 框架,能不能提供一個 Egg.js 的解決方案?

          本文將手把手教你結(jié)合 Egg.js 和 Serverless 實現(xiàn)一個后臺管理系統(tǒng)。

          讀完此文你將學(xué)到:

          1. Egg.js 基本使用
          2. 如何使用 Sequelize ORM 模塊進行 Mysql 操作
          3. 如何使用 Redis
          4. 如何使用 JWT 進行用戶登錄驗證
          5. Serverless Framework 的基本使用
          6. 如何將本地開發(fā)好的 Egg.js 應(yīng)用部署到騰訊云云函數(shù)上
          7. 如何基于云端對象存儲快速部署靜態(tài)網(wǎng)站

          Egg.js 入門

          初始化 Egg.js 項目:

          $ mkdir egg-example && cd egg-example
          $ npm init egg --type=simple
          $ npm i

          啟動項目:

          $ npm run dev

          然后瀏覽器訪問 http://localhost:7001,就可以看到親切的 hi, egg 了。

          關(guān)于 Egg.js 的框架更多知識,建議閱讀 官方文檔

          準(zhǔn)備

          對 Egg.js 有了簡單了解,接下來我們來初始化我們的后臺管理系統(tǒng),新建一個項目目錄 admin-system:

          $ mkdir admin-system

          將上面創(chuàng)建的 Egg.js 項目復(fù)制到 admin-system 目錄下,重命名為 backend。然后將前端模板項目復(fù)制到 frontend 文件夾中:

          $ git clone https://github.com/PanJiaChen/vue-admin-template.git frontend

          說明: vue-admin-template 是基于 Vue2.0 的管理系統(tǒng)模板,是一個非常優(yōu)秀的項目,建議對 Vue.js 感興趣的開發(fā)者可以去學(xué)習(xí)下,當(dāng)然如果你對 Vue.js 還不是太了解,這里有個基礎(chǔ)入門學(xué)習(xí)教程 Vuejs 從入門到精通系列文章

          之后你的項目目錄結(jié)構(gòu)如下:

          .
          ├── README.md
          ├── backend     // 創(chuàng)建的 Egg.js 項目
          └── frontend    // 克隆的 Vue.js 前端項目模板

          啟動前端項目熟悉下界面:

          $ cd frontend
          $ npm install
          $ npm run dev

          然后訪問 http://localhost:9528 就可以看到登錄界面了。

          開發(fā)后端服務(wù)

          對于一個后臺管理系統(tǒng)服務(wù),我們這里只實現(xiàn)登錄鑒權(quán)和文章管理功能,剩下的其他功能大同小異,讀者可以之后自由補充擴展。

          1. 添加 Sequelize 插件

          在正式開發(fā)之前,我們需要引入數(shù)據(jù)庫插件,這里本人偏向于使用 Sequelize ORM 工具進行數(shù)據(jù)庫操作,正好 Egg.js 提供了 egg-sequelize 插件,于是直接拿來用,需要先安裝:

          $ cd frontend
          # 因為需要通過 sequelize 鏈接 mysql 所以這也同時安裝 mysql2 模塊
          $ npm install egg-sequelize mysql2 --save

          然后在 backend/config/plugin.js 中引入該插件:

          module.exports={
            // ....
            sequelize: {
              enable: true,
              package: "egg-sequelize"
            }
            // ....
          };
          

          在 backend/config/config.default.js 中配置數(shù)據(jù)庫連接參數(shù):

          // ...
          const userConfig={
            // ...
            sequelize: {
              dialect: "mysql",
          
              // 這里也可以通過 .env 文件注入環(huán)境變量,然后通過 process.env 獲取
              host: "xxx",
              port: "xxx",
              database: "xxx",
              username: "xxx",
              password: "xxx"
            }
            // ...
          };
          // ...
          

          2. 添加 JWT 插件

          系統(tǒng)將使用 JWT token 方式進行登錄鑒權(quán),安裝配置參考官方文檔,egg-jwt

          3. 添加 Redis 插件

          系統(tǒng)將使用 redis 來存儲和管理用戶 token,安裝配置參考官方文檔,egg-redis

          4. 角色 API

          定義用戶模型,創(chuàng)建 backend/app/model/role.js 文件如下:

          module.exports=app=> {
            const { STRING, INTEGER, DATE }=app.Sequelize;
          
            const Role=app.model.define("role", {
              id: { type: INTEGER, primaryKey: true, autoIncrement: true },
              name: STRING(30),
              created_at: DATE,
              updated_at: DATE
            });
          
            // 這里定義與 users 表的關(guān)系,一個角色可以含有多個用戶,外鍵相關(guān)
            Role.associate=()=> {
              app.model.Role.hasMany(app.model.User, { as: "users" });
            };
          
            return Role;
          };
          

          實現(xiàn) Role 相關(guān)服務(wù),創(chuàng)建 backend/app/service/role.js 文件如下:

          const { Service }=require("egg");
          
          class RoleService extends Service {
            // 獲取角色列表
            async list(options) {
              const {
                ctx: { model }
              }=this;
              return model.Role.findAndCountAll({
                ...options,
                order: [
                  ["created_at", "desc"],
                  ["id", "desc"]
                ]
              });
            }
          
            // 通過 id 獲取角色
            async find(id) {
              const {
                ctx: { model }
              }=this;
              const role=await model.Role.findByPk(id);
              if (!role) {
                this.ctx.throw(404, "role not found");
              }
              return role;
            }
          
            // 創(chuàng)建角色
            async create(role) {
              const {
                ctx: { model }
              }=this;
              return model.Role.create(role);
            }
          
            // 更新角色
            async update({ id, updates }) {
              const role=await this.ctx.model.Role.findByPk(id);
              if (!role) {
                this.ctx.throw(404, "role not found");
              }
              return role.update(updates);
            }
          
            // 刪除角色
            async destroy(id) {
              const role=await this.ctx.model.Role.findByPk(id);
              if (!role) {
                this.ctx.throw(404, "role not found");
              }
              return role.destroy();
            }
          }
          
          module.exports=RoleService;
          

          一個完整的 RESTful API 就該包括以上五個方法,然后實現(xiàn) RoleController, 創(chuàng)建 backend/app/controller/role.js:

          const { Controller }=require("egg");
          
          class RoleController extends Controller {
            async index() {
              const { ctx }=this;
              const { query, service, helper }=ctx;
              const options={
                limit: helper.parseInt(query.limit),
                offset: helper.parseInt(query.offset)
              };
              const data=await service.role.list(options);
              ctx.body={
                code: 0,
                data: {
                  count: data.count,
                  items: data.rows
                }
              };
            }
          
            async show() {
              const { ctx }=this;
              const { params, service, helper }=ctx;
              const id=helper.parseInt(params.id);
              ctx.body=await service.role.find(id);
            }
          
            async create() {
              const { ctx }=this;
              const { service }=ctx;
              const body=ctx.request.body;
              const role=await service.role.create(body);
              ctx.status=201;
              ctx.body=role;
            }
          
            async update() {
              const { ctx }=this;
              const { params, service, helper }=ctx;
              const body=ctx.request.body;
              const id=helper.parseInt(params.id);
              ctx.body=await service.role.update({
                id,
                updates: body
              });
            }
          
            async destroy() {
              const { ctx }=this;
              const { params, service, helper }=ctx;
              const id=helper.parseInt(params.id);
              await service.role.destroy(id);
              ctx.status=200;
            }
          }
          
          module.exports=RoleController;
          

          之后在 backend/app/route.js 路由配置文件中定義 role 的 RESTful API:

          router.resources("roles", "/roles", controller.role);
          

          通過 router.resources 方法,我們將 roles 這個資源的增刪改查接口映射到了 app/controller/roles.js 文件。詳細(xì)說明參考 官方文檔

          5. 用戶 API

          同 Role 一樣定義我們的用戶 API,這里就不復(fù)制粘貼了,可以參考項目實例源碼 admin-system。

          6. 同步數(shù)據(jù)庫表格

          上面只是定義好了 Role 和 User 兩個 Schema,那么如何同步到數(shù)據(jù)庫呢?這里先借助 Egg.js 啟動的 hooks 來實現(xiàn),Egg.js 框架提供了統(tǒng)一的入口文件(app.js)進行啟動過程自定義,這個文件返回一個 Boot 類,我們可以通過定義 Boot 類中的生命周期方法來執(zhí)行啟動應(yīng)用過程中的初始化工作。

          我們在 backend 目錄中創(chuàng)建 app.js 文件,如下:

          "use strict";
          
          class AppBootHook {
            constructor(app) {
              this.app=app;
            }
          
            async willReady() {
              // 這里只能在開發(fā)模式下同步數(shù)據(jù)庫表格
              const isDev=process.env.NODE_ENV==="development";
              if (isDev) {
                try {
                  console.log("Start syncing database models...");
                  await this.app.model.sync({ logging: console.log, force: isDev });
                  console.log("Start init database data...");
                  await this.app.model.query(
                    "INSERT INTO roles (id, name, created_at, updated_at) VALUES (1, 'admin', '2020-02-04 09:54:25', '2020-02-04 09:54:25'),(2, 'editor', '2020-02-04 09:54:30', '2020-02-04 09:54:30');"
                  );
                  await this.app.model.query(
                    "INSERT INTO users (id, name, password, age, avatar, introduction, created_at, updated_at, role_id) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 20, 'https://yugasun.com/static/avatar.jpg', 'Fullstack Engineer', '2020-02-04 09:55:23', '2020-02-04 09:55:23', 1);"
                  );
                  await this.app.model.query(
                    "INSERT INTO posts (id, title, content, created_at, updated_at, user_id) VALUES (2, 'Awesome Egg.js', 'Egg.js is a awesome framework', '2020-02-04 09:57:24', '2020-02-04 09:57:24', 1),(3, 'Awesome Serverless', 'Build web, mobile and IoT applications using Tencent Cloud and API Gateway, Tencent Cloud Functions, and more.', '2020-02-04 10:00:23', '2020-02-04 10:00:23', 1);"
                  );
                  console.log("Successfully init database data.");
                  console.log("Successfully sync database models.");
                } catch (e) {
                  console.log(e);
                  throw new Error("Database migration failed.");
                }
              }
            }
          }
          
          module.exports=AppBootHook;
          

          通過 willReady 生命周期函數(shù),我們可以執(zhí)行 this.app.model.sync() 函數(shù)來同步數(shù)據(jù)表,當(dāng)然這里同時初始化了角色和用戶數(shù)據(jù)記錄,用來做為演示用。

          注意:這的數(shù)據(jù)庫同步只是本地調(diào)試用,如果想要騰訊云的 Mysql 數(shù)據(jù)庫,建議開啟遠程連接,通過 sequelize db:migrate 實現(xiàn),而不是每次啟動 Egg 應(yīng)用時同步,示例代碼已經(jīng)完成此功能,參考 Egg Sequelize 文檔。 這里本人為了省事,直接開啟騰訊云 Mysql 公網(wǎng)連接,然后修改 config.default.js 中的 sequelize 配置,運行 npm run dev 進行開發(fā)模式同步。

          到這里,我們的用戶和角色的 API 都已經(jīng)定義好了,啟動服務(wù) npm run dev,訪問 https://127.0.0.1:7001/users 可以獲取所有用戶列表了。

          7. 用戶登錄/注銷 API

          這里登錄邏輯比較簡單,客戶端發(fā)送 用戶名 和 密碼 到 /login 路由,后端通過 login 函數(shù)接受,然后從數(shù)據(jù)庫中查詢該用戶名,同時比對密碼是否正確。如果正確則調(diào)用 app.jwt.sign() 函數(shù)生成 token,并將 token 存入到 redis 中,同時返回該 token,之后客戶端需要鑒權(quán)的請求都會攜帶 token,進行鑒權(quán)驗證。思路很簡單,我們就開始實現(xiàn)了。

          流程圖如下:



          首先,在 backend/app/controller/home.js 中新增登錄處理 login 方法:

          class HomeController extends Controller {
            // ...
            async login() {
              const { ctx, app, config }=this;
              const { service, helper }=ctx;
              const { username, password }=ctx.request.body;
              const user=await service.user.findByName(username);
              if (!user) {
                ctx.status=403;
                ctx.body={
                  code: 403,
                  message: "Username or password wrong"
                };
              } else {
                if (user.password===helper.encryptPwd(password)) {
                  ctx.status=200;
                  const token=app.jwt.sign(
                    {
                      id: user.id,
                      name: user.name,
                      role: user.role.name,
                      avatar: user.avatar
                    },
                    config.jwt.secret,
                    {
                      expiresIn: "1h"
                    }
                  );
                  try {
                    await app.redis.set(`token_${user.id}`, token);
                    ctx.body={
                      code: 0,
                      message: "Get token success",
                      token
                    };
                  } catch (e) {
                    console.error(e);
                    ctx.body={
                      code: 500,
                      message: "Server busy, please try again"
                    };
                  }
                } else {
                  ctx.status=403;
                  ctx.body={
                    code: 403,
                    message: "Username or password wrong"
                  };
                }
              }
            }
          }
          

          注釋:這里有個密碼存儲邏輯,用戶在注冊時,密碼都是通過 helper 函數(shù) encryptPwd() 進行加密的(這里用到最簡單的 md5 加密方式,實際開發(fā)中建議使用更加高級加密方式),所以在校驗密碼正確性時,也需要先加密一次。至于如何在 Egg.js 框架中新增 helper 函數(shù),只需要在 backend/app/extend 文件夾中新增 helper.js 文件,然后 modole.exports 一個包含該函數(shù)的對象就行,參考 Egg 框架擴展文檔

          然后,在 backend/app/controller/home.js 中新增 userInfo 方法,獲取用戶信息:

          async userInfo() {
            const { ctx }=this;
            const { user }=ctx.state;
            ctx.status=200;
            ctx.body={
              code: 0,
              data: user,
            };
          }
          

          egg-jwt 插件,在鑒權(quán)通過的路由對應(yīng) controller 函數(shù)中,會將 app.jwt.sign(user, secrete) 加密的用戶信息,添加到 ctx.state.user 中,所以 userInfo 函數(shù)只需要將它返回就行。

          之后,在 backend/app/controller/home.js 中新增 logout 方法:

          async logout() {
            const { ctx }=this;
            ctx.status=200;
            ctx.body={
              code: 0,
              message: 'Logout success',
            };
          }
          

          userInfo 和 logout 函數(shù)非常簡單,重點是路由中間件如何處理。

          接下來,我們來定義登錄相關(guān)路由,修改 backend/app/router.js 文件,新增 /login, /user-info, /logout 三個路由:

          const koajwt=require("koa-jwt2");
          
          module.exports=app=> {
            const { router, controller, jwt }=app;
            router.get("/", controller.home.index);
          
            router.post("/login", controller.home.login);
            router.get("/user-info", jwt, controller.home.userInfo);
            const isRevokedAsync=function(req, payload) {
              return new Promise(resolve=> {
                try {
                  const userId=payload.id;
                  const tokenKey=`token_${userId}`;
                  const token=app.redis.get(tokenKey);
                  if (token) {
                    app.redis.del(tokenKey);
                  }
                  resolve(false);
                } catch (e) {
                  resolve(true);
                }
              });
            };
            router.post(
              "/logout",
              koajwt({
                secret: app.config.jwt.secret,
                credentialsRequired: false,
                isRevoked: isRevokedAsync
              }),
              controller.home.logout
            );
          
            router.resources("roles", "/roles", controller.role);
            router.resources("users", "/users", controller.user);
            router.resources("posts", "/posts", controller.post);
          };
          

          Egg.js 框架定義路由時,router.post() 函數(shù)可以接受中間件函數(shù),用來處理一些路由相關(guān)的特殊邏輯。

          比如 /user-info,路由添加了 app.jwt 作為 JWT 鑒權(quán)中間件函數(shù),至于為什么這么用,egg-jwt 插件有明確說明。

          這里稍微復(fù)雜的是 /logout 路由,因為我們在注銷登錄時,需要將用戶的 token 從 redis 中移除,所以這里借助了 koa-jwt2 的 isRevokded 參數(shù),來進行 token 刪除。

          后端服務(wù)部署

          到這里,后端服務(wù)的登錄和注銷邏輯基本完成了。那么如何部署到云函數(shù)呢?可以直接使用 tencent-egg 組件,它是專門為 Egg.js 框架打造的 Serverless Component,使用它可以快速將我們的 Egg.js 項目部署到騰訊云云函數(shù)上。

          1. 準(zhǔn)備

          我們先創(chuàng)建一個 backend/sls.js 入口文件:

          const { Application }=require("egg");
          const app=new Application();
          module.exports=app;
          

          然后修改 backend/config/config.default.js 文件:

          const config=(exports={
            env: "prod", // 推薦云函數(shù)的 egg 運行環(huán)境變量修改為 prod
            rundir: "/tmp",
            logger: {
              dir: "/tmp"
            }
          });
          

          注釋:這里之所有需要修改運行和日志目錄,是因為云函數(shù)運行時,只有 /tmp 才有寫權(quán)限。

          全局安裝 serverless 命令:

          $ npm install serverless -g

          2. 配置 Serverless

          在項目根目錄下創(chuàng)建 serverless.yml 文件,同時新增 backend 配置:

          backend:
            component: "@serverless/tencent-egg"
            inputs:
              code: ./backend
              functionName: admin-system
              # 這里必須指定一個具有操作 mysql 和 redis 的角色,具體角色創(chuàng)建,可訪問 https://console.cloud.tencent.com/cam/role
              role: QCS_SCFFull
              functionConf:
                timeout: 120
                # 這里的私有網(wǎng)絡(luò)必須和 mysql、redis 實例一致
                vpcConfig:
                  vpcId: vpc-xxx
                  subnetId: subnet-xxx
              apigatewayConf:
                protocols:
                  - https

          此時你的項目目錄結(jié)構(gòu)如下:

          .
          ├── README.md         // 項目說明文件
          ├── serverless.yml    // serverless yml 配合文件
          ├── backend           // 創(chuàng)建的 Egg.js 項目
          └── frontend          // 克隆的 Vue.js 前端項目模板

          3. 執(zhí)行部署

          執(zhí)行部署命令:

          $ serverless --debug

          之后控制臺需要進行掃碼登錄驗證騰訊云賬號,掃碼登錄就好。等部署成功會發(fā)揮如下信息:

          backend:
              region:              ap-guangzhou
              functionName:        admin-system
              apiGatewayServiceId: service-f1bhmhk4
              url:                 https://service-f1bhmhk4-1251556596.gz.apigw.tencentcs.com/release/

          這里輸出的 url 就是部署成功的 API 網(wǎng)關(guān)接口,可以直接訪問測試。

          注釋:云函數(shù)部署時,會自動在騰訊云的 API 網(wǎng)關(guān)創(chuàng)建一個服務(wù),同時創(chuàng)建一個 API,通過該 API 就可以觸發(fā)云函數(shù)執(zhí)行了。

          4. 賬號配置(可選)

          當(dāng)前默認(rèn)支持 Serverless cli 掃描二維碼登錄,如果希望配置持久的環(huán)境變量/秘鑰信息,也可以在項目根目錄創(chuàng)建 .env 文件

          在 .env 文件中配置騰訊云的 SecretId 和 SecretKey 信息并保存,密鑰可以在 API 密鑰管理 中獲取或者創(chuàng)建.

          # .env
          TENCENT_SECRET_ID=123
          TENCENT_SECRET_KEY=123

          5. 文章 API

          跟用戶 API 類似,只需要復(fù)制粘貼上面用戶相關(guān)模塊,修改名稱為 posts, 并修改數(shù)據(jù)模型就行,這里就不粘貼代碼了。

          前端開發(fā)

          本實例直接使用的 vue-admin-template 的前端模板。

          我們需要做如下幾部分修改:

          1. 刪除接口模擬:更換為真實的后端服務(wù)接口
          2. 修改接口函數(shù):包括用戶相關(guān)的 frontend/src/api/user.js 和文章相關(guān)接口 frontend/src/api/post.js。
          3. 修改接口工具函數(shù):主要是修改 frontend/src/utils/request.js 文件,包括 axios請求的 baseURL 和請求的 header。
          4. UI 界面修改:主要是新增文章管理頁面,包括列表頁和新增頁。

          1. 刪除接口模擬

          首先刪除 frontend/mock 文件夾。然后修改前端入口文件 frontend/src/main.js:

          // 1. 引入接口變量文件,這個會依賴 @serverless/tencent-website 組件自動生成
          import "./env.js";
          
          import Vue from "vue";
          
          import "normalize.css/normalize.css";
          import ElementUI from "element-ui";
          import "element-ui/lib/theme-chalk/index.css";
          import locale from "element-ui/lib/locale/lang/en";
          import "@/styles/index.scss";
          import App from "./App";
          import store from "./store";
          import router from "./router";
          import "@/icons";
          import "@/permission";
          
          // 2. 下面這段就是 mock server 引入,刪除就好
          // if (process.env.NODE_ENV==='production') {
          //   const { mockXHR }=require('../mock')
          //   mockXHR()
          // }
          
          Vue.use(ElementUI, { locale });
          Vue.config.productionTip=false;
          
          new Vue({
            el: "#app",
            router,
            store,
            render: h=> h(App)
          });
          

          2. 修改接口函數(shù)

          修改 frontend/src/api/user.js 文件,包括登錄、注銷、獲取用戶信息和獲取用戶列表函數(shù)如下:

          import request from "@/utils/request";
          
          // 登錄
          export function login(data) {
            return request({
              url: "/login",
              method: "post",
              data
            });
          }
          
          // 獲取用戶信息
          export function getInfo(token) {
            return request({
              url: "/user-info",
              method: "get"
            });
          }
          
          // 注銷登錄
          export function logout() {
            return request({
              url: "/logout",
              method: "post"
            });
          }
          
          // 獲取用戶列表
          export function getList() {
            return request({
              url: "/users",
              method: "get"
            });
          }
          

          新增 frontend/src/api/post.js 文件如下:

          import request from "@/utils/request";
          
          // 獲取文章列表
          export function getList(params) {
            return request({
              url: "/posts",
              method: "get",
              params
            });
          }
          
          // 創(chuàng)建文章
          export function create(data) {
            return request({
              url: "/posts",
              method: "post",
              data
            });
          }
          
          // 刪除文章
          export function destroy(id) {
            return request({
              url: `/posts/${id}`,
              method: "delete"
            });
          }
          

          3. 修改接口工具函數(shù)

          因為 @serverless/tencent-website 組件可以定義 env 參數(shù),執(zhí)行成功后它會在指定 root 目錄自動生成 env.js,然后在 frontend/src/main.js 中引入使用。 它會掛載 env 中定義的接口變量到 window 對象上。比如這生成的 env.js 文件如下:

          window.env={};
          window.env.apiUrl="https://service-f1bhmhk4-1251556596.gz.apigw.tencentcs.com/release/";
          

          根據(jù)此文件我們來修改 frontend/src/utils/request.js 文件:

          import axios from "axios";
          import { MessageBox, Message } from "element-ui";
          import store from "@/store";
          import { getToken } from "@/utils/auth";
          
          // 創(chuàng)建 axios 實例
          const service=axios.create({
            // 1. 這里設(shè)置為 `env.js` 中的變量 `window.env.apiUrl`
            baseURL: window.env.apiUrl || "/", // url=base url + request url
            timeout: 5000 // request timeout
          });
          
          // request 注入
          service.interceptors.request.use(
            config=> {
              // 2. 添加鑒權(quán)token
              if (store.getters.token) {
                config.headers["Authorization"]=`Bearer ${getToken()}`;
              }
              return config;
            },
            error=> {
              console.log(error); // for debug
              return Promise.reject(error);
            }
          );
          
          // 請求 response 注入
          service.interceptors.response.use(
            response=> {
              const res=response.data;
          
              // 只有請求code為0,才是正常返回,否則需要提示接口錯誤
              if (res.code !==0) {
                Message({
                  message: res.message || "Error",
                  type: "error",
                  duration: 5 * 1000
                });
          
                if (res.code===50008 || res.code===50012 || res.code===50014) {
                  // to re-login
                  MessageBox.confirm(
                    "You have been logged out, you can cancel to stay on this page, or log in again",
                    "Confirm logout",
                    {
                      confirmButtonText: "Re-Login",
                      cancelButtonText: "Cancel",
                      type: "warning"
                    }
                  ).then(()=> {
                    store.dispatch("user/resetToken").then(()=> {
                      location.reload();
                    });
                  });
                }
                return Promise.reject(new Error(res.message || "Error"));
              } else {
                return res;
              }
            },
            error=> {
              console.log("err" + error);
              Message({
                message: error.message,
                type: "error",
                duration: 5 * 1000
              });
              return Promise.reject(error);
            }
          );
          
          export default service;
          

          4. UI 界面修改

          關(guān)于 UI 界面修改,這里就不做說明了,因為涉及到 Vue.js 的基礎(chǔ)使用,如果還不會使用 Vue.js,建議先復(fù)制示例代碼就好。如果對 Vue.js 感興趣,可以到 Vue.js 官網(wǎng) 學(xué)習(xí)。也可以閱讀本人的 Vuejs 從入門到精通系列文章,喜歡的話,可以送上您寶貴的 Star (*^▽^*)

          這里只需要復(fù)制 Demo 源碼 的 frontend/router 和 frontend/views 兩個文件夾就好。

          前端部署

          因為前端編譯后都是靜態(tài)文件,我們需要將靜態(tài)文件上傳到騰訊云的 COS(對象存儲) 服務(wù),然后開啟 COS 的靜態(tài)網(wǎng)站功能就可以了,這些都不需要你手動操作,使用 @serverless/tencent-website 組件就可以輕松搞定。

          1. 修改 Serverless 配置文件

          修改項目根目錄下 serverless.yml 文件,新增前端相關(guān)配置:

          name: admin-system
          
          # 前端配置
          frontend:
            component: "@serverless/tencent-website"
            inputs:
              code:
                src: dist
                root: frontend
                envPath: src # 相對于 root 指定目錄,這里實際就是 frontend/src
                hook: npm run build
              env:
                # 依賴后端部署成功后生成的 url
                apiUrl: ${backend.url}
              protocol: https
              # TODO: CDN 配置,請修改!!!
              hosts:
                - host: sls-admin.yugasun.com # CDN 加速域名
                  https:
                    certId: abcdedg # 為加速域名在騰訊云平臺申請的免費證書 ID
                    http2: off
                    httpsType: 4
                    forceSwitch: -2
          
          # 后端配置
          backend:
            component: "@serverless/tencent-egg"
            inputs:
              code: ./backend
              functionName: admin-system
              role: QCS_SCFFull
              functionConf:
                timeout: 120
                vpcConfig:
                  vpcId: vpc-6n5x55kb
                  subnetId: subnet-4cvr91js
              apigatewayConf:
                protocols:
                  - https

          2. 執(zhí)行部署

          執(zhí)行部署命令:

          $ serverless --debug

          輸出如下成功結(jié)果:

          frontend:
              url:  https://dtnu69vl-470dpfh-1251556596.cos-website.ap-guangzhou.myqcloud.com
              env:
                apiUrl: https://service-f1bhmhk4-1251556596.gz.apigw.tencentcs.com/release/
              host:
                - https://sls-admin.yugasun.com (CNAME: sls-admin.yugasun.com.cdn.dnsv1.com)
            backend:
              region:              ap-guangzhou
              functionName:        admin-system
              apiGatewayServiceId: service-f1bhmhk4
              url:                 https://service-f1bhmhk4-1251556596.gz.apigw.tencentcs.com/release/

          注釋:這里 frontend 中多輸出了 host,是我們的 CDN 加速域名,可以通過配置 @serverless/tencent-website 組件的 inputs.hosts 來實現(xiàn)。有關(guān) CDN 相關(guān)配置說明可以閱讀 基于 Serverless Component 的全棧解決方案 - 續(xù)集。當(dāng)然,如果你不想配置 CDN,直接刪除,然后訪問 COS 生成的靜態(tài)網(wǎng)站 url。

          部署成功后,我們就可以訪問 https://sls-admin.yugasun.com 登錄體驗了。

          源碼

          本篇涉及到所有源碼都維護在開源項目 tencent-serverless-demo 中 admin-system

          總結(jié)

          本篇文章涉及到內(nèi)容較多,推薦在閱讀時,邊看邊開發(fā),跟著文章節(jié)奏一步一步實現(xiàn)。如果遇到問題,可以參考本文源碼。如果你成功實現(xiàn)了,可以到官網(wǎng)進一步熟悉 Egg.js 框架,以便今后可以實現(xiàn)更加復(fù)雜的應(yīng)用。雖然本文使用的是 Vue.js 前端框架,但是你也可以將 frontend 更換為任何你喜歡的前端框架項目,開發(fā)時只需要將接口請求前綴使用 @serverless/tencent-website 組件生成的 env.js 文件就行。


          主站蜘蛛池模板: 无码少妇A片一区二区三区| 日韩在线观看一区二区三区| 一区二区三区福利| 综合久久一区二区三区 | 亚洲一区在线视频| 偷拍精品视频一区二区三区| 国产福利电影一区二区三区久久老子无码午夜伦不 | 少妇精品久久久一区二区三区| 一区二区高清在线观看| 亚洲一区二区精品视频| 无码少妇一区二区三区| 精品人妻一区二区三区浪潮在线| 日韩一区二区视频| 亚洲爽爽一区二区三区| 国内自拍视频一区二区三区| 香蕉久久AⅤ一区二区三区| 亚洲日本va午夜中文字幕一区| 毛片一区二区三区| 夜夜添无码一区二区三区| 中文字幕一区日韩精品| 亚洲毛片αv无线播放一区 | 免费视频精品一区二区| 久久久精品人妻一区亚美研究所| 麻豆va一区二区三区久久浪| 无码一区二区三区| 国产激情一区二区三区 | 国产成人无码aa精品一区| 一区二区三区福利视频免费观看| 国产在线不卡一区| 91精品一区二区综合在线| 国产精品一区二区久久国产| 精品国产日韩亚洲一区在线| 国产精品视频分类一区| 无码国产精品一区二区免费vr| 久久无码人妻精品一区二区三区 | 国产一区二区三区在线免费| 亚洲欧洲专线一区| 在线观看日本亚洲一区| 亚洲日韩一区二区三区| 精品黑人一区二区三区| 国产精品 视频一区 二区三区|