首页 » 网站建设 » phpsequelize技巧_Eggjs试水 文章增删改查前后端分离

phpsequelize技巧_Eggjs试水 文章增删改查前后端分离

访客 2024-11-18 0

扫一扫用手机浏览

文章目录 [+]

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

上一篇文章讲的是后端渲染的项目 - Egg.js 试水 - 景象预报。
但是没有引入数据库。
这次的试水项目是文章的增编削查,将数据库引进,并且实现前后端分离。

phpsequelize技巧_Eggjs试水  文章增删改查前后端分离

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

phpsequelize技巧_Eggjs试水  文章增删改查前后端分离
(图片来自网络侵删)

下面直接进入正题~‍

项目构造

article-project├── client├── service└── README.md复制代码

由于是前后端分离的项目,那么我们就以文件夹client存放客户端,以文件夹service存放做事端。
README.md是项目解释文件。

客户端初始化

为了快速演示,我们利用vue-cli脚手架帮我们天生项目,并引入了vue-ant-design。

项目初始化

推举利用yarn进行包管理。

$ npm install -g @vue/cli# 或者$ yarn global add @vue/cli复制代码

然后新建一个项目。

$ vue create client复制代码

接着我们进入项目并启动。

$ cd client$ npm run serve# 或者$ yarn run serve复制代码

此时,我们访问浏览器地址http://localhost:8080/,就会看到欢迎页面。

末了我们引入ant-design-vue。

$ npm install ant-design-vue# 或$ yarn add ant-design-vue复制代码

在这里,我们全局引入ant-design-vue的组件。
实际开拓中,按需引入比较友好,特殊是只是利用了该UI框架部分功能组件的时候。

// src/main.jsimport 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');复制代码

当然,在此项目中,还牵扯到几种npm包,之后只写yarn或者npm命令行操作。

路由设置

路由的跳转须要vue-router的帮忙。

# 路由$ yarn add vue-router# 进度条$ yarn add nprogress复制代码

这里只用到登录页,首页,文章列表页面和文章的新增/编辑页面。
以是我的路由配置如下:

// src/router/index.jsimport Vue from 'vue'import Router from 'vue-router'import Index from '@/views/index'import { UserLayout, BlankLayout } from '@/components/layouts'import NProgress from 'nprogress' // progress barimport 'nprogress/nprogress.css' // progress bar styleconst whiteList = ['login'] // no redirect whitelistimport { 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复制代码
接口要求设置

接口要求利用了axios,我们来集成下。

# axios$ yarn add axios复制代码

我们即将要代理的后端做事的地址是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 }, }, },...复制代码

我们封装下要求

// src/utils/request.jsimport 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) })}// 创建 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 interceptorauth.interceptors.request.use(config => { const token = Vue.ls.get(ACCESS_TOKEN) if (token) { config.headers[ 'Authorization' ] = 'JWT '+ token // 让每个要求携带自定义 token 请根据实际情形自行修正 } return config}, err)// response interceptorauth.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: '未授权,你没有访问权限,请联系管理员!
', }, '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}复制代码
样式预处理器

当然,为了更好的管理你的页面样式,建议还是添加一种CSS预处理器。
这里我选择了less预处理器。

# less 和 less-loader$ yarn add less --dev$ yarn add less-loader --dev复制代码

仅仅是安装还弗成,我们来配置下。

// vue.config.js... css: { loaderOptions: { less: { modifyVars: { blue: '#3a82f8', 'text-color': '#333' }, javascriptEnabled: true } } },...复制代码布局文章页面

文章列表页的骨架:

<!--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>复制代码

文章编辑/新增页的骨架:

<!--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="标题"> <a-input placeholder="请输入标题" v-decorator="[ 'title', {rules: [{ required: true, message: '请输入标题'}]} ]"/> </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="内容"> <a-textarea :autosize="{ minRows: 10, maxRows: 12 }" placeholder="请输入文章内容" v-decorator="[ 'content', {rules: [{ required: true, message: '请输入文章内容'}]} ]"/> </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>复制代码

前真个项目有了雏形,下面搭建下做事真个项目。

做事端初始化

这里直策应用eggjs框架来实现做事端。
你可以考虑利用typescript办法的来初始化项目,但是我们这里直策应用javascript而不是它的超级typescript来初始化项目。

初始化项目

$ mkdir service$ cd service$ npm init egg --type=simple$ npm i复制代码

启动项目:

$ npm run dev复制代码

在浏览器中打开localhost:7001地址,我们就可以看到eggjs的欢迎页面。
当然,我们这里基本上不会涉及到浏览器页面,由于我们开拓的是api接口。
更多的是利用postman工具进行调试。

引入数据库

这里利用的数据库是mysql,但是我们不是直接使它,而是安装封装过的mysql2和egg-sequelize。

在 Node.js 社区中,sequelize 是一个广泛利用的 ORM 框架,它支持 MySQL、PostgreSQL、SQLite 和 MSSQL 等多个数据源。
它会赞助我们将定义好的 Model 工具加载到 app 和 ctx 上。

# 安装mysql$ yarn add mysql2# 安装sequelize$ yarn add egg-sequelize复制代码

当然,我们须要一个数据库进行连接,那就得安装一个数据库,如果你利用的是mac os的话,你可以通过下面的方法进行安装:

brew install mysqlbrew services start mysql复制代码

window系统的话,可以考虑下载干系的安装包实行就行了,这里不展开说了。

数据库安装好后,我们管理数据库,可以通过掌握台命令行进行掌握,也可以通过图形化工具进行掌握。
我们推举后者,我们下载了一个Navicat Premiun的工具。

Navicat Premiun 是一款数据库管理工具。

当然还可以下载phpstudy进行赞助开拓。

连接数据库

配置数据库的基本信息,条件是我们已经创建好了这个数据库。
假设我们创建了一个名为article的数据库,用户是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 };...复制代码

当然,这是通过包egg-sequelize处理的,我们也要将其引入,见告eggjs去利用这个插件。

// config/plugin.js... sequelize: { enable: true, package: 'egg-sequelize', },...复制代码创建数据库表

你可以直接通过掌握台命令行实行mysql语句创建。
但是,我们直策应用迁移操作完成。

在项目中,我们希望将所有的数据库Migrations干系的内容都放在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'),};复制代码

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

npx sequelize init:confignpx sequelize init:migrations复制代码

更加详细内容,可见eggjs sequelize章节。

我们按照官网上的操作初始化了文章列表的数据库表articles。
对应的model内容如下:

// 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)},// 标题 group: {type: STRING(255)}, // 分组 author: {type: STRING(255)},// 作者 content: {type: TEXT}, // 内容 created_at: {type: DATE, defaultValue: NOW},// 创建韶光 updated_at: {type: DATE, defaultValue: NOW}// 更新韶光 }, { freezeTableName: true // 不自动将表名添加复数 }); return Article;};复制代码API的CRUD

上面做事真个事情,已经帮我们做好编写接口的准备了。
那么,下面结合数据库,我们来实现下文章增编削查的操作。

我们利用的是MVC的架构,那么我们的现有代码逻辑自然会这样流向:

app/router.js 获取文章路由到 -> app/controller/article.js中对应的方法 -> 到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') }...复制代码

// 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) }) }...复制代码获取文章详情

[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, '不存在此条数据!
', {}, '00001') return } ctx.returnBody(200, '获取文章成功!
', articleDetail, '00000') }...复制代码

// app/service/article.js... async getArticle(id) { const { ctx } = this return await ctx.model.Article.findOne({ where: { id } }) }...复制代码添加文章

[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, '网络缺点,请稍后再试!
', {}, '00001') return } ctx.returnBody(200, '新建文章成功!', article, '00000') }...复制代码

// app/service/article.js... async addArticle(data) { const { ctx } = this return await ctx.model.Article.create(data) }...复制代码编辑文章

[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, '网络缺点,请稍后再试!
', {}, '00001') return } ctx.returnBody(200, '编辑文章成功!', article, '00000') }...复制代码

// app/service/article.js... async editArticle(id, data) { const { ctx } = this return await ctx.model.Article.update(data, { where: { id } }) }...复制代码删除文章

[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, '不存在此条数据!
', {}, '00001') return } ctx.returnBody(200, '删除文章成功!
', articleDetail, '00000') }...复制代码

// app/service/article.js... async deleteArticle(id) { const { ctx } = this return await ctx.model.Article.destroy({ where: { id } }) }...复制代码

在完成接口的编写后,你可以通过postman 运用去验证下是否返回的数据。

前端对接接口

接下来就得切回来client文件夹进行操作了。
我们在上面已经大略封装了要求方法。
这里来编写文章CRUD的要求方法,我们为了方便调用,将其统一挂载在Vue实例下。

// src/api/index.jsimport article from './article'const api = { article}export default apiexport const ApiPlugin = {}ApiPlugin.install = function (Vue, options) { Vue.prototype.api = api // 挂载api在原型上}复制代码获取文章列表

// src/api/article.js... export function getList(params) { return auth({ url: '/api/get-article-list', method: 'get', params }) }...复制代码

// 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 }) }...复制代码获取文章详情

// src/api/article.js... export function getItem(params) { return auth({ url: '/api/get-article', method: 'get', params }) }...复制代码

// src/views/article/info.vue... getDetail(id) { let vm = this vm.loading = true vm.api.article.getItem({ id }).then(res => { if(res.code === '00000') { // 数据回填 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 }) },...复制代码
添加文章

// src/api/article.js... export function postItem(data) { return auth({ url: '/api/post-article', method: 'post', data }) }...复制代码

// 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 }) }) },...复制代码
编辑文章

// src/api/article.js... export function putItem(params, data) { return auth({ url: '/api/put-article', method: 'put', params, data }) }...复制代码

// 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 }) }) }...复制代码
删除文章

// src/api/article.js... export function deleteItem(params) { return auth({ url: '/api/delete-article', method: 'delete', params }) }...复制代码

// 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() {}, }) }...复制代码
效果图

在egg-demo/article-project/client/前端项目中,页面包含了登录页面,欢迎页面和文章页面。

欢迎页面忽略不计

登录页

文章列表

文章编辑

后话

至此,全体项目已经完成。
代码仓库为egg-demo/article-project/,感兴趣可以进行扩展学习。

标签:

相关文章