Koa 手把手用做一个聊天室(koa+vue)

目录结构:

im server

controllers

index.js    
   

 

     
   

app.js

controller.js

package.json

node_modules

     
           
  vue src api index.js  
           
      assets css index.css
           
     

config

index.js

dev.js

prod.js

 
           
     

lang

index.js

en.js

zh.js

 
           
      router index.js  
           
      service index.js  
           
     

store

modules

index.js

login.js

 

           
      util locale.js  
           
      views

home.vue

login.vue

 
     

 

 

 
      App.vue    
           
      main.js    
           
    static

css

normalize.css  
           
     

img

0.png

1.png

2.png

3.png

4.png

5.png

6.png

7.png

8.png

9.png

 
           
    package.json      

 

im\server\controllers\index.js:

var userIndex = 0
async function loginName (ctx, next) {
  let locale = ctx.cookies.get('locale')
  let langMap = {
    zh: '甲乙丙丁戊己庚辛壬癸',
    en: 'ABCDEFGHIJ',
  }
  let names = langMap[`${locale}`]
  let name = names[userIndex % 10]
  ctx.response.body = JSON.stringify({
    code: 1,
    data: {name},
    message: 'ok'
  })
  ctx.response.type = 'json'
}
async function login (ctx, next) {
  let name = ctx.request.body.name
  let user = {
    id: ++userIndex,
    name: name,
    time: new Date().getTime(),
    img: userIndex % 10
  }
  let value = Buffer.from(JSON.stringify(user)).toString('base64')
  console.log(`Set cookie value: ${value}`)
  ctx.cookies.set('name', value)
  ctx.response.body = JSON.stringify({
    code: 1,
    data: user,
    message: 'ok'
  })
  ctx.response.type = 'json'
}
async function logout (ctx, next) {
  ctx.cookies.set('name', '')
}
module.exports = [
  {
    method: 'GET',
    path: '/loginName',
    fn: loginName
  },
  {
    method: 'POST',
    path: '/login',
    fn: login
  },
  {
    method: 'POST',
    path: '/logout',
    fn: logout
  }
]

im\server\app.js:

const Koa = require('koa')

const app = new Koa()

const Cookies = require('cookies');

const bodyParser = require('koa-bodyparser')

const controller = require('./controller')

const server = app.listen(3000)

// 导入WebSocket模块
const WebSocket = require('ws')

// 引用Server类
const WebSocketServer = WebSocket.Server

// parse user from cookie:
app.use(async (ctx, next) => {
  ctx.state.user = parseUser(ctx.cookies.get('name') || '')
  ctx.state.userLocale = ctx.cookies.get('locale') || ''
  console.log('ctx.state.user: ', ctx.state.user)
  console.log('ctx.state.userLocale: ', ctx.state.userLocale)
  await next()
})

function createWebSocketServer(server, onConnection, onMessage, onClose, onError) {
  let wss = new WebSocketServer({server}) // 实例化
  wss.broadcast = data => {
    wss.clients.forEach(client => {
      client.send(data)
    })
  }
  wss.on('connection', ws => {
    onConnection = onConnection || function () {
      console.log('[WebSocket] connected.')
    }
    onMessage = onMessage || function (msg) {
      console.log('[WebSocket] message received: ' + msg)
    }
    onClose = onClose || function (code, message) {
      console.log(`[WebSocket] closed: ${code} - ${message}`)
    }
    onError = onError || function (err) {
      console.log('[WebSocket] error: ' + err)
    }
    ws.on('message', onMessage)
    ws.on('close', onClose)
    ws.on('error', onError)
    let user = parseUser(ws.upgradeReq)
    if (!user) {
      ws.close(4001, 'Invalid user')
    }
    ws.user = user
    ws.wss = wss
    onConnection.apply(ws)
  })
  console.log('WebSocketServer was attached.')
  return wss
}

var messageIndex = 0

function createMessage(type, user, data) {
  return JSON.stringify({
    id: ++messageIndex,
    type: type,
    time: new Date().getTime(),
    user: user,
    data: data
  })
}

function onConnect() {
  let tip = {
    zh: '加入聊天室',
    en: 'Join the chat room'
  }
  let msg = createMessage('add', this.user, tip)
  this.wss.broadcast(msg)
  // build user list:
  let users = this.wss.clients.map(client => client.user)
  msg = createMessage('list', this.user, users)
  this.wss.broadcast(msg)
}

function onMessage(message) {
  console.log(message)
  let msg = createMessage('chat', this.user, message)
  this.wss.broadcast(msg)
}

function onClose() {
  let tip = {
    zh: '退出聊天室',
    en: 'Quit chat room'
  }
  let msg = createMessage('del', this.user, tip)
  this.wss.broadcast(msg)
}

function parseUser(obj) { // 解析用户
  if (!obj) {
    return
  }
  console.log('try parse: ' + obj)
  let s = ''
  if (typeof obj === 'string') {
    s = obj
  } else if (obj.headers) {
    let cookies = new Cookies(obj, null)
    s = cookies.get('name')
  }
  if (s) {
    try {
      let user = JSON.parse(Buffer.from(s, 'base64').toString())
      console.log(`User: ${user.name}, ID: ${user.id}`)
      return user
    } catch (e) {
      console.log(e)
    }
  }
}
app.use(bodyParser())
app.use(controller())
app.wss = createWebSocketServer(server, onConnect, onMessage, onClose)
console.log('http://127.0.0.1:3000')

im\server\controller.js:

// 导入fs
const fs = require('fs')

function addControllers(router, dir) {
  fs.readdir(dir, (err, files) => {
    if (err) throw err
    files.filter(f => f.endsWith('.js')).forEach(f => {
      let mappingList = require('./' + dir + '/' + f)
      console.log(`Loading file: ${f} for router`)
      mappingList.forEach(params => addMapping({router, ...params}))
    })
  })
}

function addMapping({router, method, path, fn}) {
  switch(method) {
    case 'GET':
      router.get(path, fn)
      console.log(`register URL mapping: GET ${path}`)
      return
    case 'POST':
      router.post(path, fn)
      console.log(`register URL mapping: POST ${path}`)
      return
    case 'PUT':
      router.put(path, fn)
      console.log(`register URL mapping: PUT ${path}`)
      return
    case 'DELETE':
      router.del(path, fn)
      console.log(`register URL mapping: DELETE ${path}`)
      return
    default:
      console.log(`Invalid method: ${method} with path: ${path}`)
      return
  }
}

module.exports = function (dir) {
  let controllers_dir = dir || 'controllers',
  router = require('koa-router')()
  addControllers(router, controllers_dir)
  return router.routes()
}

im\server\package.json:

{
  "name": "my-koa",
  "version": "1.0.0",
  "description": "koa",
  "main": "app.js",
  "scripts": {
    "dev": "node app.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Gulinling",
  "license": "ISC",
  "dependencies": {
    "ws": "1.1.1",
    "koa": "2.0.0",
    "koa-bodyparser": "3.2.0",
    "koa-router": "7.0.0",
    "nunjucks": "2.4.2",
    "mime": "1.3.4",
    "mz": "2.4.0"
  }
}

im\vue\src\api\index.js:

import service from '@/service'
import config from '@/config'
export default {
  login (params) {
    return service.loop(config.login, params, 'post')
  },
  loginName () {
    return service.loop(config.loginName, {}, 'get')
  }
}

m\vue\src\assets\css\index.css:

html, body, #app{
  height: 100%;
}
.container{
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

im\vue\src\config\dev.js:

export const dev = {
  login: 'login',
  loginName: 'loginName'
}

im\vue\src\config\index.js:

import {dev} from './dev'
import {prod} from './prod'
const env = process.env.NODE_ENV
function getConfig (env) {
  let config = null
  switch (env) {
    case 'development':
      config = dev
      break
    case 'production':
      config = prod
      break
    default:
      config = dev
  }
  return config
}
export default {
  ...getConfig(env)
}

im\vue\src\config\prod.js:

export const prod = {
  login: 'login',
  loginName: 'loginName'
}

im\vue\src\lang\en.js:

export default {
  home: {
    title: 'chat room',
    userList: 'User list',
    chatRecord: 'Chat record'
  },
  login: {
    title: 'Your Name'
  }
}

im\vue\src\lang\index.js:

import Vue from 'vue'
import Element from 'element-ui'
import VueI18n from 'vue-i18n'
import locale from '@/util/locale'
import en from './en'
import zh from './zh'
import 'element-ui/lib/theme-chalk/index.css'
import './../../static/css/normalize.css'
import './../assets/css/index.css'
document.cookie = `locale = ${locale}`
Vue.use(VueI18n)
Vue.use(Element)

const i18n = new VueI18n({
  locale,
  messages: {
    en,
    zh
  }
})
export default i18n

im\vue\src\lang\zh.js:

export default {
  home: {
    title: '聊天室',
    userList: '用户列表',
    chatRecord: '聊天记录'
  },
  login: {
    title: '你的名字'
  }
}

im\vue\src\router\index.js:

import Vue from 'vue'
import Router from 'vue-router'
import home from '@/views/home'
import login from '@/views/login'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: home.name,
      component: home
    },
    {
      path: '/login',
      name: login.name,
      component: login
    }
  ]
})

im\vue\src\service\index.js:

import axios from 'axios' // 导入axios

const service = axios.create({ // 创建实例
  baseURL: '/api/', // url 前缀
  timeout: 3000 // 请求超时时间
})

service.interceptors.request.use( // 请求拦截器
  config => {
    config.headers['User-Token'] = 'toKen' // 让每个请求携带token
    return config
  },
  error => {
    Promise.reject(error) // 请求错误处理
  }
)

service.interceptors.response.use( // 响应拦截器
  response => { // 统一处理状态
    const res = response.data
    if (res.code !== 1 || response.status !== 200) { // 需自定义
      return Promise.reject({ // 返回异常
        code: res.code,
        message: res.message
      })
    } else {
      return res
    }
  },
  error => { // 处理处理
    return Promise.reject(error)
  }
)

function get (url, params = {}) { // get请求
  return new Promise((resolve, reject) => {
    service({
      url,
      params,
      method: 'get'
    })
    .then(res => {
      resolve(res)
    })
    .catch(err => {
      reject(err)
    })
  })
}

function post (url, data = {}) { // post请求
  return new Promise((resolve, reject) => {
    service({
      url,
      data,
      method: 'post'
    })
    .then(res => {
      resolve(res)
    })
    .catch(err => {
      reject(err)
    })
  })
}

function loop (url, params = {}, method = 'get') { // 循环请求(get post)
  return new Promise((resolve, reject) => {
    serviceLoop({url, params, method, resolve, reject})
  })
}

function serviceLoop ({url, params, method, resolve, reject}) {
  let option = {
    url,
    method
  }
  method = method.toLocaleLowerCase()
  if (method === 'get') {
    option.params = params
  } else if (method === 'post') {
    option.data = params
  } else {
    throw new Error('目前只支持get和post')
  }
  service(option)
  .then(res => {
    resolve(res)
  })
  .catch(err => {
    setTimeout(() => {
      serviceLoop({url, params, method, resolve, reject})
    }, 1000)
  })
}

function fileUpload(url, data = {}) { // 文件上传
  return new Promise((resolve, reject) => {
    service({
      url,
      data,
      method: 'post',
      headers: { 'Content-Type': 'multipart/form-data' }
    })
    .then(res => {
      resolve(res)
    })
    .catch(err => {
      reject(err)
    })
  })
}

export default {
  get,
  post,
  loop,
  fileUpload
}

im\vue\src\store\index.js:

// 引入vue
import Vue from 'vue'
// 引入vuex
import Vuex from 'vuex'
// 导入仓库模块
import {login} from './modules/login.js'

// 使用vuex
Vue.use(Vuex)

const store = new Vuex.Store({
  state: { // 1、state:创建初始化状态
    count: 1 // 放置初始状态
  },
  mutations: { // 2、mutations:创建改变状态的方法
    ADD (state, n) { // 状态变更函数一般大写
      state.count += n
    }
  },
  getters: { // 3、getters:提供外部获取state
    count (state) {
      return state.count
    }
  },
  actions: { // 4、actions:创建驱动方法改变mutations
    add ({commit}, data) {  // 触发mutations中相应的方法一般小写
      commit('ADD', data)
    }
  },
  modules: {
    login
  }
})

// 5、输出store
export default store

im\vue\src\store\modules\login.js:

export const login = {
  state: { // 1、state:创建初始化状态
    user: {} // 放置初始状态
  },
  mutations: { // 2、mutations:创建改变状态的方法
    updateUser (state, newVal) {
      state.user = newVal
    }
  },
  getters: { // 3、getters:提供外部获取state
    user (state) {
      return state.user
    }
  },
  actions: { // 4、actions:创建驱动方法改变mutations
    updateUser ({commit}, data) {  // 触发mutations中相应的方法
      commit('updateUser', data)
    }
  }
}

im\vue\src\util\locale.js:

function getBrowserLang () {
  let locale = null
  if (navigator.languages) {
    locale = navigator.languages[0].toLocaleLowerCase()
  } else {
    locale = (navigator.language || navigator.userLanguage || navigator.browserLanguage || navigator.systemLanguage).toLocaleLowerCase()
  }
  if (~locale.indexOf('zh')) {
    locale = 'zh'
  } else if (~locale.indexOf('en')) {
    locale = 'en'
  } else {
    locale = 'zh'
  }
  return locale
}
export default getBrowserLang()

im\vue\src\views\home.vue:






im\vue\src\views\login.vue:






im\vue\src\App.vue:






im\vue\src\main.js:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import i18n from './lang'
Vue.config.productionTip = false

/* eslint-disable no-new */
window.vue = new Vue({
  el: '#app',
  router,
  store,
  i18n,
  components: { App },
  template: ''
})

im\vue\static\css\normalize.css:http://necolas.github.io/normalize.css/

im\vue\static\img:0-9图片自己网上下载一些头像喽

im\vue\package.json:

{
  "name": "vue",
  "version": "1.0.0",
  "description": "A Vue.js project",
  "author": "",
  "private": true,
  "scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --host 10.17.143.11",
    "start": "npm run dev",
    "e2e": "node test/e2e/runner.js",
    "test": "npm run unit && npm run e2e",
    "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs",
    "build": "node build/build.js"
  },
  "dependencies": {
    "axios": "^0.18.0",
    "element-ui": "^2.4.7",
    "vue": "^2.5.2",
    "vue-i18n": "^8.1.0",
    "vue-router": "^3.0.1",
    "vuex": "^3.0.1"
  },
  "devDependencies": {
    "autoprefixer": "^7.1.2",
    "babel-core": "^6.22.1",
    "babel-eslint": "^7.1.1",
    "babel-helper-vue-jsx-merge-props": "^2.0.3",
    "babel-loader": "^7.1.1",
    "babel-plugin-syntax-jsx": "^6.18.0",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-plugin-transform-vue-jsx": "^3.5.0",
    "babel-preset-env": "^1.3.2",
    "babel-preset-stage-2": "^6.22.0",
    "babel-register": "^6.22.0",
    "chalk": "^2.0.1",
    "copy-webpack-plugin": "^4.0.1",
    "cross-spawn": "^5.0.1",
    "css-loader": "^0.28.0",
    "eslint": "^4.15.0",
    "eslint-config-standard": "^10.2.1",
    "eslint-friendly-formatter": "^3.0.0",
    "eslint-loader": "^1.7.1",
    "eslint-plugin-import": "^2.7.0",
    "eslint-plugin-node": "^5.2.0",
    "eslint-plugin-promise": "^3.4.0",
    "eslint-plugin-standard": "^3.0.1",
    "eslint-plugin-vue": "^4.0.0",
    "extract-text-webpack-plugin": "^3.0.0",
    "file-loader": "^1.1.4",
    "friendly-errors-webpack-plugin": "^1.6.1",
    "html-webpack-plugin": "^2.30.1",
    "nightwatch": "^0.9.12",
    "node-notifier": "^5.1.2",
    "optimize-css-assets-webpack-plugin": "^3.2.0",
    "ora": "^1.2.0",
    "portfinder": "^1.0.13",
    "postcss-import": "^11.0.0",
    "postcss-loader": "^2.0.8",
    "postcss-url": "^7.2.1",
    "rimraf": "^2.6.0",
    "selenium-server": "^3.0.1",
    "semver": "^5.3.0",
    "shelljs": "^0.7.6",
    "uglifyjs-webpack-plugin": "^1.1.1",
    "url-loader": "^0.5.8",
    "vue-loader": "^13.3.0",
    "vue-style-loader": "^3.0.1",
    "vue-template-compiler": "^2.5.2",
    "webpack": "^3.6.0",
    "webpack-bundle-analyzer": "^2.9.0",
    "webpack-dev-server": "^2.9.1",
    "webpack-merge": "^4.1.0"
  },
  "engines": {
    "node": ">= 6.0.0",
    "npm": ">= 3.0.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}

需要你切换到server目录和vue目录安装依赖,运行需要先切换server执行npm run dev再切换vue执行同样的操作,至此完结撒花

你可能感兴趣的:(Vue,Koa)