前言:Nodejs很火,可是好像工做中不多见到大面积的招聘岗位。我也用了nodejs写web服务不少年。分享一套我用了好久的代码,我以为挺好。有须要的朋友拿去看看,咱们不去纠结到底nodejs好很差,适不适合作Server,这些问题,吵翻天有何意义。能解决问题就行了。html
开发快也是一种优点,如今中小型创业公司,底子差,业务要求快。加上云服务商的api加持,为什么咱们还要维护一个笨重的服务体系。当我须要AI服务的时候,我就接入AI服务商的接口就行了。我须要cdn服务的时候我接云服务就行了,我须要支付的时候我调用支付服务API就行了。那么一个小巧,快速的业务方案就会让创业型企业快速铺开市场,2-3个月就能够快速上线。尤为是在小程序盛行的今天,都不用笨重的APP了,云开发也是不错的选择,可是企业总要有必定的数据和业务能力,否则也会被云开发商掣肘。node
今天分享的就是一套代码,会放到github上,github.com/vincent-li/… 你们下载还须要根据项目调整配置文件。不敢说是很是牛逼,我历来都是土狗一个,看看各位若是没有思路的朋友,是否是能够拿去直接先把业务干起来。废话很少说。代码走起。git
思路比较简单待我一步一步拆解。我把nodejs定位就是web服务程序,不要跟我讨论底层稳定性的问题,由于我利用云服务商所有解决了。服务框架用的是koa,数据库是mongodb,那么咱们来说讲一个web服务到底须要什么。github
讲了一堆估计没作过的人看懵了,不急,看看代码就明白了。入口index.js文件web
const Koa = require('koa')
const koaBodyParser = require('koa-bodyparser')
const koaViews = require('koa-views')
const koaStatic = require('koa-static')
const koaSession = require('koa-session')
const mongoStore = require('koa-session-mongo2')
const koaLogger = require('koa-logger')
const _config = require('../config')
const router = require('./controllers')
global._ = require('lodash')
global.model = require('./models')
const app = new Koa()
// 服务器运行log
app.use(koaLogger())
// 指定静态目录
app.use(koaStatic(_config.static))
// 指定views路径
app.use(
koaViews(_config.views, {
map: {
html: 'lodash',
},
})
)
// 解析报文的body
app.use(
koaBodyParser({
enableTypes: ['json', 'form', 'text'],
})
)
// session初始化
app.use(
koaSession(
{
key: 'makefuture_sess',
store: new mongoStore(_config.sessionURL),
signed: false,
// cookie过时时间,由浏览器负责到时清除,单位毫秒
maxAge: 5 * 24 * 60 * 60 * 1000,
},
app
)
)
// 使用router
app.use(router.routes(), router.allowedMethods())
console.log('启动端口:', _config.port)
app.listen(_config.port)
复制代码
入口的js就是引入koa和一堆koa的插件,而后根据api装配起来就OK了。算法
因为项目都有各类环境,因此要根据启动环境把不一样的配置项加载进来。const _config = require('../config'),配置项中包括一些敏感的数据库链接帐号和密码,我都替换掉了。因此各位在开发的时候,不建议把production的配置上传到gitlab,最好是运维持有并配置。sql
const port = Number.parseInt(process.env.PORT) || 6060
const mongoUri =
'mongodb://abcd:************@0.0.0.0:3717/?authSource=admin'
const root = '/Users/liwenqiang/codespace/git.ichoice.cc/makefuture/make'
const host = 'http://localhost:6060'
module.exports = {
host,
port,
cdn: 'https://cdn.izelas.run',
hostName: 'makefuture.app',
home: root,
static: `${root}/static`,
views: `${root}/src/views`,
mongoUri,
dbName: 'makefuture',
// 存放session的库和表配置
sessionURL: {
url: `${mongoUri}&poolSize=5&useUnifiedTopology=true&useNewUrlParser=true`,
db: 'makefuture',
collection: 'session',
// 这里设置的是数据库session按期清除的时间,与cookie的过时时间应保持一致,
// cookie由浏览器负责定时清除,须要注意的是索引一旦创建修改的时候须要删除旧的索引。
// 此处的时间是秒为单位,cookie的maxAge是毫秒为单位
maxAge: 24 * 60 * 60,
}
}
复制代码
利用global把一些通用的库加入到开发体系,就不用一遍一遍的引用了。这里把lodash引入,我觉lodash的方法足够咱们用了。还有加密算法crypto,其实作的久了,基于业务的全部开发都是各类成熟组件的装配。解决方案都很成熟,因此我不认为有难作的业务,至少难不在技术,而是产品形态。mongodb
为啥把models做为global全局,由于这块是能够独立于koa初始化的,也没有必要归入整个koa的请求上下文,可是业务处理的时候又要频繁使用,那么干脆初始化到global里面,随时取用。数据库
const mongoose = require('mongoose')
const { mongoUri, dbName } = require('../../config')
mongoose.Promise = global.Promise
const conn = mongoose.createConnection(mongoUri, {
dbName,
useNewUrlParser: true,
useUnifiedTopology: true,
})
const db = {}
const sysinfo = require('./_sysinfo')
const user = require('./_user')
const project = require('./_project')
const page = require('./_page')
const component = require('./_component')
const file = require('./_file')
const models = [sysinfo, user, project, page, component, file]
models.forEach((item) => {
let newSchema = new mongoose.Schema(
(typeof item.schema === 'function' && item.schema(mongoose.Schema)) ||
item.schema,
{ collection: item.name }
)
db[item.name] = conn.model(item.name, newSchema)
})
module.exports = db
复制代码
简单的不要不要的,就是把config里面配置的链接字符串,放到组件的方法里面,mongoose.createConnection,mongoose是用的比较好的mongodb链接组件,每中数据库都有相应的js帮助作操做。我比较喜欢mongodb是由于跟json结构完美契合,理解上比较一致,操做也基本符合js的操做规范。不用写sql真的舒服。随便找一个数据对象看下你们就明白了。npm
const model = {
name: 'user',
schema: {
nick_name: String, // 昵称
phone: String, // 手机
pass: String, // 密码
avatar_url: String, // 头像
sms_code: Number, // 短信登陆码
expire_at: Number, // 验证码过时时间
create_at: Number, // 建立时间
},
}
module.exports = model
复制代码
name就是数据库中对应的表名,也是调用的时候的名字。跟文件名能够不一致。文件名我叫_user,name:user,调用的时候是model.user,很是方便。里面的字段名称和类型对应mongo的collection,也就是表。也能够跟对象和function,具体操做能够去看www.npmjs.com/package/mon… mongo的官方操做方法都支持,具体要看mongoose的版本和mongodb的版本,官方有的均可以用。建立完model就能够在方法中使用 await model.* 的方式调用了。方法是异步的,前面要加await,不喜欢能够直接调用同步方法。不建议,毕竟有语法支持,又不难理解。
回到index.js,后面的设置静态目录,设置模板,就比较简单,模板就是js的入口页面,为啥把js的入口放到服务,而不是作成静态放cdn,nodejs就是作静态渲染的,目的就是能够很好的把数据注入页面,因此js入口本身作方便不少。session这块要讲一下,这个图省事,你们都session概念不了解的麻烦自行阅读相关资料。这里的session模块干了几件事。
bo = {
userid: u._id.toString(),
nick_name: u.nick_name,
avatar_url: u.avatar_url,
}
ctx.session = bo
复制代码
因此咱们只要作一个全局的过滤器,每次检测ctx.session中有没有登陆的信息就行了。好比userid,userid为空就直接redirect到login页面就行了。
下面就剩一个就是处理请求了,这才是web服务的重头戏。咱们利用bodyparse插件,将全部请求过滤一遍,把请求参数整理成json对象。
// 解析报文的body
app.use(
koaBodyParser({
enableTypes: ['json', 'form', 'text'],
})
)
复制代码
而后就能够在ctx.request.query或者ctx.request.body中取用。ctx.request.query表明的是get请求的参数。ctx.request.body存post请求参数。固然也能够处理head,put,delete等请求。其实我以为用不到。各位本身看。
/**
* 路由定义
*/
const Router = require('@koa/router')
const router = Router({
prefix: '/api/user',
})
/**
* 用户短信登陆,朝用户手机中发送登陆短息
* @path - /api/user/smscode
* @method - POST
* @params
* phone - 用户手机号
* @returns
* data - string | object 返回数据
* code - 0 || 500,
* success - true | false,
* message - ''
*/
router.post('/smscode', sendSmsCode)
复制代码
以上就是一个路由定义的方法,prefix表示统一的前缀。post就是方法名,意思就是初始化了一个接口,访问路径就是/api/user/smscode,具体看代码,对应的执行方法就是sendSmsCode,这就是一个向用户端发送短信验证码的接口。固然短息我不用写,直接用云服务就好。我用阿里云的短信接口,发的66的。
const sendSmsCode = async (ctx) => {
const { phone } = ctx.request.body
if (!phone) {
ctx.body = getResponse(false, 'e501')
return
}
if (!checkPhone(phone)) {
ctx.body = getResponse(false, 'e552')
return
}
let u = await model.user.findOne({ phone }).lean()
// 是否已经发过
if (u && u.sms_code && u.expire_at && u.expire_at > Date.now()) {
ctx.body = getResponse(false, 'e553')
return
}
// 获取阿里云access信息
let aliyun = await model.sysinfo.findOne({ key: 'aliyun' }).lean()
if (aliyun && aliyun.val.accessKeyId && aliyun.val.accessSecret) {
const client = new Alicloud({
accessKeyId: aliyun.val.accessKeyId,
accessKeySecret: aliyun.val.accessSecret,
endpoint: 'https://dysmsapi.aliyuncs.com',
apiVersion: '2017-05-25',
})
// 获取随机的6个数字
const code = +`${_.random(9)}${_.random(9)}${_.random(9)}${_.random(
9
)}${_.random(9)}${_.random(9)}`
const params = {
RegionId: 'cn-hangzhou',
PhoneNumbers: phone,
SignName: '码客将来',
TemplateCode: 'SMS_203670207',
TemplateParam: JSON.stringify({ code }),
}
const res = await client.request('SendSms', params, { method: 'POST' })
if (res && res.Code === 'OK') {
if (u) {
await model.user.updateOne(
{ phone },
{ sms_code: code, expire_at: Date.now() + 15 * 60 * 1000 }
)
} else {
await model.user.create({
nick_name: '码客',
phone,
create_at: Date.now(),
sms_code: code,
expire_at: Date.now() + 15 * 60 * 1000,
})
}
ctx.body = getResponse(true, '操做成功')
} else {
// fmtp('阿里云短息发送失败,缘由:', res.Message)
console.log('阿里云短息发送失败,缘由:', res.Message)
ctx.body = getResponse(false, 'e500')
}
} else {
fmtp('aliyun --->', aliyun.val)
ctx.body = getResponse(false, 'e500')
}
return
}
复制代码
你们自行看代码吧,懒得讲太多,不少东西上面提到过。这个代码基本上是能够跑起来的,可是呢,我上传的代码是修改了关键信息的,若是但愿接数据库就把本身的数据库参数输入。具体的看配置文件。固然但愿你很会玩代码,自行排查一些问题。没那个闲功夫的就看看文章就行了,其实业务层,根据不少公司的业务发展不同,须要不同的代码,仍是那句话,产品形态决定代码形态。
精髓就是,站着云厂商的肩膀上,快速把本身业务落地,AI类的我喜欢接百度,语音分析接科大讯飞,服务器、存储、云数据库、短信接阿里云,IM通常接腾讯,毕竟小程序场景仍是微信生态稳。个人创业经验就是,小程序(微信)+Nodejs服务+阿里云企业服务+腾讯智能服务,麻溜的干业务,之因此没有成功,就是特么这个操蛋的创业环境。
各位看官仔细想一想,在云服务商竞争激烈的今天,给了不少小而美的企业生机。之前要想作到的事情须要各类各样的人才,耗时耗力,关键还未必作的好。如今大部分都是调用现有的云服务,咱们小企业就作好本身的业务,方便又快捷。并且我不用讨论什么压力,什么稳定。我把安全稳定的问题所有用分润的方式,利用云服务上的能力解决了。是!我啥底层都不懂,但不妨碍我们成为一家企业的顶梁柱。未必比一些人差,不少专一于技术的人员,不但没有解决企业的问题,还增长了企业的负担,最后仍是说企业这没有那没有。想一想问题在哪?牢骚两句,看官自便。