新聞中心
前言
本文主要復(fù)盤筆者的nodeJS,通過一個(gè)線上的實(shí)戰(zhàn)案例來總結(jié)node生態(tài)常用的技術(shù)點(diǎn)和最佳實(shí)踐。后面會(huì)花費(fèi)大概一個(gè)月的時(shí)間輸出3篇以實(shí)戰(zhàn)為主的nodeJs項(xiàng)目,本文是第一篇,主要介紹如何使用nodeJs開發(fā)一個(gè)圖床應(yīng)用。該項(xiàng)目對(duì)于測(cè)試和個(gè)人服務(wù)型網(wǎng)站非常實(shí)用,大家可以基于此擴(kuò)展出更強(qiáng)大的應(yīng)用。本文的圖床項(xiàng)目主要使用Koa進(jìn)行開發(fā),不熟悉的可以先研究一下koa官網(wǎng),或者看筆者之前寫的nodeJS的文章。

讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對(duì)這個(gè)行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長期合作伙伴,公司提供的服務(wù)項(xiàng)目有:國際域名空間、雅安服務(wù)器托管、營銷軟件、網(wǎng)站建設(shè)、南江網(wǎng)站維護(hù)、網(wǎng)站推廣。
你將收獲
- Node應(yīng)用基本架構(gòu)方式以及開發(fā)NodeJS應(yīng)用的流程。
- Koa + Koa-Router + glob + Node基本API使用。
- 跨域解決方案Koa Cors的使用介紹,以及如何和前協(xié)作跨域。
- 基于@koa/multer封裝文件上傳中間件。
- 使用React開發(fā)前端應(yīng)用以及xui基本使用。
正文
首先圖床應(yīng)用要保證不同域下都可以訪問我們的圖片資源,不存在跨域問題,并且可以支持在不同域下的應(yīng)用都可以上傳圖片到圖床上,如下圖所示:
結(jié)合上圖我們可以先做應(yīng)用的需求分析:
以上是一個(gè)非常簡單的圖床應(yīng)用的需求分析,我們接下來將根據(jù)這個(gè)分析來搭建項(xiàng)目架構(gòu)并開發(fā)我們的應(yīng)用程序。在開始之前我們先看看簡單的實(shí)現(xiàn)效果:
- 訪問并上傳圖片。
- 獲取圖片鏈接地址。
- 刪除圖片
這個(gè)展示界面只是一個(gè)例子,我們可以通過前端的方式設(shè)計(jì)專屬于自己的圖床管理界面。這里提供的公共API在任何域名下都是可以調(diào)用的,沒有跨域問題。
前臺(tái)地址:基于xui搭建的圖床界面前臺(tái)。
api開放地址:圖床開放地址(免費(fèi)勿黑)。
1、Node應(yīng)用基本架構(gòu)方式以及開發(fā)NodeJS應(yīng)用的流程
有關(guān)nodejs的項(xiàng)目架構(gòu)以及如何組織nodejs目錄,我在30分鐘教你優(yōu)雅的搭建nodejs開發(fā)環(huán)境及目錄設(shè)計(jì)這篇文章中有詳細(xì)的說明,大家在讀完本文之后可以學(xué)習(xí)研究一下.
開發(fā)任何一個(gè)應(yīng)用之前首先要做的就是了解需求,需求理清楚之后就可以做技術(shù)選型了,開發(fā)基于nodeJS的后端應(yīng)用的技術(shù)方案很多,如果對(duì)nodejs很熟悉,完全可以使用原生nodejs來開發(fā)應(yīng)用; 對(duì)于中小型應(yīng)用我們可以直接采用Koa來開發(fā),其中間件機(jī)制和插拔式的設(shè)計(jì)理念可以很方便的讓我們開發(fā)自己的中間件;如果是涉及到比較復(fù)雜的業(yè)務(wù)線我們可以采用egg.js或者nest.js來作為nodeJS的框架選型,由于本文的圖床應(yīng)用比較簡單,所以筆者這里直接采用koa生態(tài)來做開發(fā). 接下來先看看我們圖床應(yīng)用的目錄結(jié)構(gòu):
2.Koa + Koa-Router + glob + Node基本API使用
學(xué)習(xí)koa最快的方式就是直接看官方文檔, koa的官方文檔非常簡單也非常詳細(xì),所以不懂的可以先看看官網(wǎng).
(1)服務(wù)端路由(接口)設(shè)計(jì)
服務(wù)端路由我們主要使用koa-router, 使用方式也很簡單, 代碼如下:
const Koa = require('koa');
const Router = require('@koa/router');
const app = new Koa();
const router = new Router();
// 獲取列表的路由接口
router.get('/api/list', (ctx, next) => {
// 獲取列表的邏輯
});
// 上傳圖片的路由接口
router.post('/api/upload', (ctx, next) => {
// 上傳圖片的邏輯
});
app
.use(router.routes())
.use(router.allowedMethods());復(fù)制代碼因?yàn)閳D床的應(yīng)用非常簡單,我們這里就直接使用傳統(tǒng)的方式實(shí)現(xiàn), 有關(guān)nodeJS的MVC架構(gòu)可以參考我之前寫的node的文章.
(2)使用glob來批量獲取圖片路徑
這里批量獲取圖片路徑我們主要使用glob來通過遍歷目錄來獲取, 這種方式在圖片數(shù)據(jù)量小的時(shí)候可以使用,但是一旦圖片量指數(shù)級(jí)增長,更建議用數(shù)據(jù)庫來存取,畢竟IO操作還是比較費(fèi)性能的.筆者這里為了方便采用glob來實(shí)現(xiàn). glob是一個(gè)基于node的第三發(fā)庫,支持我們使用模式匹配的方式遍歷文件目錄, 具體用法如下:
import glob from 'glob'
// 讀取文件
router.get('/api/v0/files',
ctx => {
const files = glob.sync(`${staticPath}/uploads/*`)
const result = files.map(item => {
return `${config.staticPath}${item.split('public')[1]}`
})
ctx.body = {
state: 200,
result
}
}
);復(fù)制代碼
這樣就實(shí)現(xiàn)了批量獲取圖片的api,是不是很簡單呢? 我們只需要訪問這個(gè)接口,就可以拿到圖床的所有圖片列表了.當(dāng)我們?cè)L問這個(gè)接口時(shí),會(huì)返回如下數(shù)據(jù):
3、跨域解決方案Koa Cors的使用介紹,以及如何和前協(xié)作跨域
由于瀏覽器同源策略,凡是發(fā)送請(qǐng)求url的協(xié)議、域名、端口三者之間任意一個(gè)與當(dāng)前頁面地址不同就被算作跨域。實(shí)現(xiàn)跨域的方式也很多,比如JSONP跨域,nginx反向代理,服務(wù)器端修改header,設(shè)置document.domain,使用postMessage技術(shù)等,但是目前主流的方式還是基于cors來實(shí)現(xiàn).
為了讓圖床提供的服務(wù)給不同的域使用, 我們需要配置跨域,這里我們采用koa2-cors提供的應(yīng)答式跨域解決方案,其實(shí)原理也很簡單,就是配置http的請(qǐng)求響應(yīng)頭信息, 讓我們的服務(wù)器支持不同的ip訪問.其基本用法如下:
import cors from 'koa2-cors'
// 設(shè)置跨域
app.use(cors({
origin: function (ctx) {
console.log(111, ctx.url)
if (ctx.url.indexOf('/api/v0') > -1) {
return "*"; // 允許來自所有域名請(qǐng)求
}
return 'http://qutanqianduan.com'; // 這樣就能只允許 http://qutanqianduan.com 這個(gè)域名的請(qǐng)求了
},
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'], // 獲取額外的header信息
maxAge: 5, // 該字段可選,用來指定本次預(yù)檢請(qǐng)求的有效期,單位為秒
credentials: true,
allowMethods: ['GET', 'POST', 'DELETE'], // 請(qǐng)求允許的方法
allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'x-requested-with'] // 允許的header字段名
}))復(fù)制代碼
通過以上的配置,我們就可以實(shí)現(xiàn)基本的跨域了.如果我們想只讓某些特定的接口實(shí)現(xiàn)跨域,我們可以設(shè)置接口白名單, 也可以通過設(shè)置域名白名單來達(dá)到只讓特定的域名訪問我們的api接口.這種情況更適用于公司內(nèi)部多個(gè)子系統(tǒng)間互相協(xié)作通信的情景.
4、基于@koa/multer封裝文件上傳中間件
服務(wù)器要想接受客戶端上傳的文件,我們還需要提供文件上傳接口, 這里筆者采用koa生態(tài)比較主流的實(shí)現(xiàn)方式@koa/multer. 具體使用介紹官網(wǎng)寫的也很詳細(xì),大家可以看官網(wǎng)學(xué)習(xí)@koa/multer。
(1)實(shí)現(xiàn)文件上傳接口
接下來我們基于它實(shí)現(xiàn)文件上傳中間件.具體實(shí)現(xiàn)如下:
import multer from '@koa/multer'
import { resolve } from 'path'
import fs from 'fs'
const rootImages = resolve(__dirname, '../../public/uploads')
//上傳文件存放路徑、及文件命名
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, rootImages)
},
filename: function (req, file, cb) {
let [name, type] = file.originalname.split('.');
cb(null, `${name}_${Date.now().toString(16)}.${type}`)
}
})
//文件上傳限制
const limits = {
fields: 10,//非文件字段的數(shù)量
fileSize: 1024 * 1024 * 2,//文件大小 單位 b
files: 1//文件數(shù)量
}
export const復(fù)制代碼
由以上代碼可知我們?cè)赿estination目錄下設(shè)置了文件上傳的目標(biāo)目錄, 通過filename接口來設(shè)置上傳之后的文件名. limits是對(duì)文件操作的限制,具體的可以根據(jù)自己的需求來配置.
其次結(jié)合koa-router來實(shí)現(xiàn)文件上傳接口:
// lib/upload.js
// 為了捕獲multer的錯(cuò)誤
export const uploadSingleCatchError = async (ctx, next) => {
let err = await upload.single('file')(ctx, next).then(res => res)
.catch(err => err);
if(err) {
ctx.status = 500
ctx.body = {
state: 500,
msg: err.message
}
}
}
// index.js
// 上傳文件
router.post('/api/v0/upload', uploadSingleCatchError,
ctx => {
let { filename, path, size } = ctx.file;
let { source } = ctx.request.body || 'unknow';
let url = `${config.staticPath}${path.split('/public')[1]}`
ctx.body = {
state: 200,
filename,
url,
source,
size
}
}
);復(fù)制代碼
這樣我們就能通過任意一個(gè)客戶端上傳圖片到我們的圖床上了.
(2)刪除文件接口實(shí)現(xiàn)
我們用原生nodejs實(shí)現(xiàn)刪除文件的功能, 這里會(huì)用到fs模塊,具體實(shí)現(xiàn)如下:
// lib/upload.js
// 刪除文件
export const delFile = (path) => {
return new Promise((resolve, reject) => {
fs.unlink(path, (err) => {
if(err) {
reject(err)
}else {
resolve(null)
}
})
})
}// 刪除文件接口
router.get('/api/v0/del',
async ctx => {
const { id } = ctx.query
if(id) {
const err = await delFile(`${staticPath}/uploads/${id}`)
if(!err) {
ctx.body = {
state: 200,
result: '刪除成功'
}
}else {
ctx.code = 500
ctx.body = {
state: 500,
result: '文件不存在,刪除失敗'
}
}
}else500 ctx.body = {
state: 500,
result: 'id不能為空'
}
}
}
)復(fù)制代碼
這樣,我們?cè)谧约旱目蛻舳藨?yīng)用中點(diǎn)擊刪除按鈕就可以刪除圖床上的文件了.當(dāng)然本圖傳應(yīng)用還有很多接口實(shí)現(xiàn)細(xì)節(jié), 這里就不一一介紹了,感興趣的朋友可以研究一下.
5、使用React開發(fā)前端應(yīng)用以及xui基本使用
接下來借來實(shí)現(xiàn)我們的圖床客戶端,客戶端的實(shí)現(xiàn)以及設(shè)計(jì)風(fēng)格完全可以由自己來定,所以這里只是介紹一下筆者實(shí)現(xiàn)的客戶端,筆者將采用react全家桶以及自己開發(fā)的第三方ui庫xui——基于react的輕量級(jí)UI組件庫來實(shí)現(xiàn),關(guān)于如何開發(fā)一個(gè)專屬于自己的組件庫,可以參考筆者之前的文章. 首先我們簡單開發(fā)一個(gè)圖床應(yīng)用的界面:
我們先引入組件庫:
import React, { Component } from 'react'
import {
Notification,
message,
Layout,
Icon
} from '@alex_xu/xui'
const復(fù)制代碼接著搭建我們的頁面:
class UploadPage extends Component {
state = {
fileList: []
}
componentDidMount() {
fetch(apiUrl + '/files').then(res => res.json()).then(res => {
this.setState({
fileList: res.result
})
})
}
showAddress = (item) => {
Notification.config({
placement: 'topRight',
})
Notification.pop({
type: 'success',
message: '圖片地址',
duration: 10,
description: item
})
}
render() {
return (
XOSS
{
this.state.fileList.map((item, i) => {
return

})
}
)
}
}
export default UploadPage復(fù)制代碼關(guān)于http庫我們可以使用任何一種主流的庫比如axios, umi-request等. 本客戶端代碼已發(fā)布到github,大家可以clone本地運(yùn)行一下:
基于react+redux+redux-thunk+xui開發(fā)的todoOA管理平臺(tái)。
本文轉(zhuǎn)載自微信公眾號(hào)「趣談前端」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系趣談前端公眾號(hào)。
分享題目:30分鐘教你使用NodeJs開發(fā)自己的圖床應(yīng)用
文章地址:http://www.5511xx.com/article/cdjocip.html


咨詢
建站咨詢
