Giter VIP home page Giter VIP logo

lensh / vue-qq Goto Github PK

View Code? Open in Web Editor NEW
917.0 34.0 229.0 20.04 MB

🎨 Vue family bucket with socket.io and express/koa2 , create a web version of mobile QQ, supporting real-time group chat, real-time private chat, special care, shielding chat, smart IP geographic location, real-time display temperature and other QQ core functions

License: MIT License

JavaScript 38.24% HTML 0.44% Vue 52.37% GLSL 2.34% TSQL 6.61%
vue2 vuex vue-router axios koa2 socket-io es6 es7 webpack2 node

vue-qq's Introduction

Web手Q

Vue全家桶+Socket.io+Express/Koa2打造的网页版手机QQ(web app),高仿手机QQ7.1.0版本。为了方便大家学习,现在IP定位接口和实时气温接口也开放了!接口请在源码中查看。

预览

在线预览地址:https://qq.lenshen.com (尽量使用Chrome浏览器体验最佳效果,另外提供了3个测试账号,需要账号才能登录哦)

三个测试账号如下:

  • qq:986992484 密码:111111 (对应昵称是莫知我哀----宇文玥)

  • qq:986992483 密码:111111 (对应昵称是浅唱低吟----楚乔)

  • qq:986992482 密码:111111 (对应昵称是以梦之铭----马哲涵)

技术栈

  • Vue2.0:实现前端页面构建
  • Vuex:实现不同组件间的状态共享
  • Vue-router:页面路由切换,实现单页的核心
  • vueg:页面复杂场景切换效果
  • Socket.io:实现实时消息推送
  • axios:一个基于 Promise 的 HTTP 库,向后端发起请求
  • ExpressKoa2:开发环境使用Express,生产环境使用Koa2
  • ES6ES7ES8:服务端和客户端均使用ES6语法,promise/async/await 处理异步
  • localStorage:本地保存用户信息
  • Webpack:模块打包,前端项目构建工具首选
  • SASS(SCSS):CSS预处理语言
  • Flex:flex弹性布局,简单适配手机、PC端
  • CSS3:CSS3过渡动画及样式
  • IScroll:模拟原生app的列表滚动效果(ListView)
  • MySQL:MySQL关系型数据库持久化数据(考虑到表与表之间关系复杂,需要多表查询,最复杂的时候是六张表联查,用MySQL会比Mongodb好得多)
  • jsonp:跨域请求数据
  • pm2:服务端使用pm2部署,常驻进程,比forever好用得多(https://github.com/Unitech/pm2
  • nginx:服务端使用nginx代理端口转发

使用方式

先将根目录下的qq.sql导入到你的MySQL数据库里(可以使用Navicat),用户名为root,登录密码为空。启动MySQL服务。然后使用cnpm install 安装所有依赖(最好用cnpm安装,因为项目依赖很多,npm用的是国外的镜像,在网络不稳定的情况下很有可能会导致安装失败,而且下载速度远远慢于国内的cnpm),最后运行npm run dev。服务器部署运行项目只需要npm run pm2,这样就可以常驻进程,不过前提是得先全局安装pm2。

目前已经实现了QQ的核心功能,如消息列表、好友列表、新朋友、好友申请、实时群聊、实时私聊、聊天设置、屏蔽对方聊天、特别关心、会员等级、个性名片、添加好友、删除好友、好友分组、查找用户、登录、注销、切换用户、右滑显示侧栏、地理定位、温度等等。后期会考虑增加更多功能。如果你想体验实时聊天的酷炫效果,那么你可以打开两个浏览器,用上面不同的账号登录即可。

截图

  • 消息页面

  • 联系人页面

  • 群聊

  • 私聊

分析

  • 服务端使用ES6语法

不需要使用babel转码以及一系列的配置,只需要将node升级到V8版本,V8已经很好地支持了ES6/ES7/ES8等最新特性,这是目前最好的办法。升级到V8版本,可以直接到nodejs中文网(http://nodejs.cn/download/) 下载即可,也可以使用NVM切换node版本。

升级到V8后,还不支持通过import/export关键字来导入导出模块(因为服务端已经有了CommonJS规范,如果再使用import/export的话就有点冲突了),如果一定要使用import/export关键字,这时可以在服务端的入口文件首行添加以下代码:

require("babel-core/register")({
	presets: ['es2015', 'stage-0']
})
require("babel-polyfill")

上面的模块不可以使用import来导入,必须使用require,同时需要通过npm安装babel-core、babel-preset-es2015、babel-preset-stage-0、babel-polyfill等依赖。这样就可以愉快地使用import/export了。

服务端代码片段如下:

// ES7 async/await
import express from 'express'
import login from '../../controller/login'

const loginRouter = express.Router()

loginRouter
	.get('/:user/:pwd', async(req, res) => { // 登录
		const result = await login.login(req, res)
		res.json(result)
	})

export default loginRouter
  • Socket.io

服务端(结合Express/Koa):

// Server
import express from 'express'
import http from 'http'
import socketio from 'socket.io'

const app = express()
const server = http.createServer(app)
const io = socketio(server)
server.listen(3000)

io.on('connection', (socket)=>{
  socket.emit('news', { hello: 'world' })
  socket.on('my other event', function (data) {
    console.log(data)
  })
})

客户端:

// Client
<script src="http://localhost:3000/socket.io/socket.io.js"></script>
<script>
  const socket = io.connect('http://localhost:3000')
  socket.on('news', (data)=>{
    socket.emit('my other event', { my: 'data' })
  })
</script>

socket.io最核心的两个api就是emiton了 ,服务端和客户端都有这两个api。通过 emiton可以实现服务器与客户端之间的双向通信。

emit :发射一个事件,第一个参数为事件名,第二个参数为要发送的数据,第三个参数为回调函数(如需对方接受到信息后立即得到确认时,则需要用到回调函数)。

on :监听一个 emit 发射的事件,第一个参数为要监听的事件名,第二个参数为回调函数,用来接收对方发来的数据,该函数的第一个参数为接收的数据。

服务端常用API:

socket.emit():向建立该连接的客户端发送消息

socket.on():监听客户端发送信息

io.to(socketid).emit():向指定客户端发送消息

io.sockets.socket(socketid).emit():向指定客户端发送消息,新版本用io.sockets.socket[socketid].emit() ,数组访问

socket.broadcast.emit():向除去建立该连接的客户端的所有客户端广播

io.sockets.emit():向所有客户端广播

客户端常用API:

socket.emit():向服务端发送消息

socket.on():监听服务端发来的信息

FAQ

若使用的过程中遇到问题,可以加官方群交流:611212696。如果觉得不错,就毫不吝啬地给个star吧。后期项目还会继续更新和完善。

启动后如果报以下错误,请参考:#8

Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column \'qq.b.face\' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

vue-qq's People

Contributors

dependabot[bot] avatar lensh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

vue-qq's Issues

./server/common/sdk-phone

最近在拜读大神代码,代码规范、注释风格、�模块化方式都对我很有帮助,
不过 ./server/common/sdk-phone这个目录是做什么工作的?我看引入了淘宝的api,不过看不懂是实现的什么业务

pm2服务器部署

PM2 是一个带有负载均衡功能的 Node 应用的进程管理器。

当你要把你的独立代码利用全部的服务器上的所有 CPU,并保证进程永远都活着,0 秒的重载, PM2 是完美的。它非常适合 IaaS 结构,但不要把它用于 PaaS 方案(随后将开发 Paas 的解决方案)。

备注:

SaaS、PaaS 和 IaaS 是云服务模式
SaaS 软件即服务,例如 Google 的 Gmail 邮箱服务,面向应用型用户
PaaS 平台即服务,例如 Google 的 GAE,面向开发型用户
IaaS 基础架构即服务,例如亚马逊的 AWS,IaaS 对于不知道新推出的应用程序/网站会有多成功的创业公司来说非常有用。

主要特性
内建负载均衡(使用 Node cluster 集群模块)
后台运行
0 秒停机重载,我理解大概意思是维护升级的时候不需要停机.
具有 Ubuntu 和 CentOS 的启动脚本
停止不稳定的进程(避免无限循环)
控制台检测
提供 HTTP API
远程控制和实时的接口 API ( Nodejs 模块,允许和 PM2 进程管理器交互 )
测试过 Nodejs v0.11/v0.10/v0.8 版本,兼容 CoffeeScript,基于 Linux 和 MacOS。

安装
npm install -g pm2

用法

$ npm install pm2 -g     # 命令行安装 pm2 
$ pm2 start app.js -i 4  # 后台运行pm2,启动4个app.js 
                         # 也可以把'max' 参数传递给 start
                         # 正确的进程数目依赖于Cpu的核心数目
$ pm2 start app.js --name my-api # 命名进程
$ pm2 list               # 显示所有进程状态
$ pm2 monit              # 监视所有进程
$ pm2 logs               # 显示所有进程日志
$ pm2 stop all           # 停止所有进程
$ pm2 restart all        # 重启所有进程
$ pm2 reload all         # 0 秒停机重载进程 (用于 NETWORKED 进程)
$ pm2 stop 0             # 停止指定的进程
$ pm2 restart 0          # 重启指定的进程
$ pm2 startup            # 产生 init 脚本 保持进程活着
$ pm2 web                # 运行健壮的 computer API endpoint (http://localhost:9615)
$ pm2 delete 0           # 杀死指定的进程
$ pm2 delete all         # 杀死全部进程

同一字段根据不同条件更新的sql语句的写法

同一字段根据不同条件更新的sql语句的写法

update test    
set 字段1=case 
when 条件1 then 值1    
when 条件2 then 值2    
end  

示例:

UPDATE group_user SET unread = CASE 
WHEN is_enter =1 THEN 0 
WHEN is_enter =0 THEN unread +1 END 
WHERE group_id =6

上面这条SQl语句的意思就是,当is_enter为1时,就将unread字段置为0,否则自增1。

when,then也可用于SQL条件判断语句:
第一种:

SELECT
CASE
WHEN price IS NULL THEN 'Not yet priced'
WHEN price < 10 THEN 'Very Reasonable Title'
WHEN price >= 10 and price < 20 THEN 'Coffee Table Title'
ELSE 'Expensive book!'
END AS "Price Category",
CONVERT(varchar(20), title) AS "Shortened Title"
FROM pubs.dbo.titles
ORDER BY price

第二种:

SELECT au_fname, au_lname,
   CASE state
      WHEN 'CA' THEN 'California'
      WHEN 'KS' THEN 'Kansas'
      WHEN 'TN' THEN 'Tennessee'
      WHEN 'OR' THEN 'Oregon'
      WHEN 'MI' THEN 'Michigan'
      WHEN 'IN' THEN 'Indiana'
      WHEN 'MD' THEN 'Maryland'
      WHEN 'UT' THEN 'Utah'
        END AS StateName
FROM pubs.dbo.authors
ORDER BY au_lname

mysql命令gruop by报错this is incompatible with sql_mode=only_full_group_by

在mysql 工具 搜索或者插入数据时报下面错误:
ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'database_tl.emp.id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

原因:
看一下group by的语法:
select 选取分组中的列+聚合函数 from 表名称 group by 分组的列
从语法格式来看,是先有分组,再确定检索的列,检索的列只能在参加分组的列中选。
我当前Mysql版本5.7.17,
再看一下ONLY_FULL_GROUP_BY的意思是:对于GROUP BY聚合操作,如果在SELECT中的列,没有在GROUP BY中出现,那么这个SQL是不合法的,因为列不在GROUP BY从句中,也就是说查出来的列必须在group by后面出现否则就会报错,或者这个字段出现在聚合函数里面。

查看mysql版本命令:select version();
查看sql_model参数命令:

SELECT @@GLOBAL.sql_mode;
SELECT @@SESSION.sql_mode;

发现:
ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
第一项默认开启ONLY_FULL_GROUP_BY,

解决方法:
1.只选择出现在group by后面的列,或者给列增加聚合函数;(不推荐)
2.命令行输入:

set @@GLOBAL.sql_mode='';
set sql_mode ='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';

默认关掉ONLY_FULL_GROUP_BY!

这个时候 在用工具select 一下

SELECT @@sql_mode;
SELECT @@GLOBAL.sql_mode;

发现已经不存在ONLY_FULL_GROUP_BY ,感觉已经OK。但是如果你重启Mysql服务的话,发现ONLY_FULL_GROUP_BY还是会存在的

想要彻底解决这个问题 就得去改my.ini 配置(如果你们mysql 没有这个文件,就把my-default.ini 改成my.ini,我这个版本就是没有my.ini配置问题)

在 [mysqld]和[mysql]下添加
SET sql_mode ='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';

使用IScroll遇到的一些坑

一、IScroll移动端很卡,不流畅
只需要给wrapper元素加上:touch-action:none !important; 即可,不允许默认的touch行为。

二、在手机页面上onclick事件失效
在实例化IScroll时把click选项设置为true即可,也就是需要手动派发事件,不要设置preventDefault

移动端开发踩坑填坑

1.移动端web页面click事件失效问题
当父元素绑定了touch事件而且阻止了默认事件行为时,其子元素的click事件是不会生效的。解决方法是恢复touch事件的默认行为即可。

移动uc浏览器上的一些问题

不知道你在手机上试过没有,我在uc上发现了2个问题
1.整个界面还有浏览器的菜单栏,显得小,联系在html mate里配置上全屏模式
2.用store记录消息界面的滚动条位置,我觉得用vue-router keep-alive好一点
3.上面的只是建议,下面的是bug
4.手机上,私聊发送按钮掉了没反应,消息发不出去
5.我看代码里对于第一次时间判断message.length==0可能写错了,我觉得是this.datalist.message.length.....这个因为是在手机的github上看的,也没有实际运行佐证,你看一下吧,因为我去看你dome时候发现,发送的第一条信息也打印出时间来了。可能确实这个判断错了

截了两个图,等到了公司用电脑发出来,上面纯手机手打。感谢提供质量这么高的代码:)

mapGetters、mapMutations、mapActions用法

首先得通过import从vuex里导入:

import { mapGetters,mapMutations,mapActions } from 'vuex'
1.mapGetters是将store的getters(getters可以认为是state的计算属性)自动映射到组件的局部计算属性里,在组件的计算属性里只需如下申明,就可以直接使用this.products来获取products了:

computed: {
    ...mapGetters({
      products: 'cartProducts',
      checkoutStatus: 'checkoutStatus'
    })
}

如果你要使用的计算属性名称刚好和getters初始的名称一致(即当键名和键值一样时),那么就可以写成数组的形式。

computed: {
    ...mapGetters([
      'products',   //注意这里一定要有引号,才会自动映射
      'checkoutStatus'
    })
}

原本需要使用this.$store.getters.products来获取products的,现在只需要使用this.products就可以了,是不是更简洁方便了呢?
2.mapMutations辅助函数是将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store),只需要在组件的methods里声明:

 methods: {
    ...mapMutations([
      'increment' // 映射 this.increment() 为 this.$store.commit('increment')
    ]),
    ...mapMutations({
      add: 'increment' // 映射 this.add() 为 this.$store.commit('increment')
    })
  }

这样我们就可以在组件里使用this.add()来替代 this.$store.commit('increment')了,又简短了不少。
如果需要传递参数,则可以直接在this.add(argument)里传参数argument
3.Action 类似于 mutation,不同在于:
Action 提交的是 mutation,而不是直接变更状态。
Action 可以包含任意异步操作。
mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store),
使用方法如下:

  methods: {
    ...mapActions([
      'increment' // 映射 this.increment() 为 this.$store.dispatch('increment')
    ]),
    ...mapActions({
      add: 'increment' // 映射 this.add() 为 this.$store.dispatch('increment')
    })
  }

nginx服务器配置教程(以ubuntu 16.04为例)

lnmp安装:https://lnmp.org/install.html
一、安装nginx
注意先apt-get update一下
1.安装pcre(rewrite 模块)
sudo apt install libpcre3 libpcre3-dev
2.安装 openssl(ssl 功能)
sudo apt-get intall openssl libssl-dev
3.安装 zlib(gzip模块)
sudo apt-get install zlib1g-dev
4.下载nginx源码包
wget http://nginx.org/download/nginx-1.19.5.tar.gz
5.解压该tar包
tar zxvf nginx-1.19.5.tar.gz
6.编译参数说明
--prefix=path 定义一个目录来保存你的nginx的提供功能的文件夹,就这好比我们安装软件的时候软件存放的目录,如果我们在编译的不指定安装位置,那么默认的位置/usr/local/nginx 目录
--sbin-path=path 设置nginx执行脚本的位置,这里如果设置在path变量里面,就可以在bash环境下,任意使用nginx命令,默认位置prefix/sbin/nginx 注意这里的prefix是在配置文件里面配置的路径
--conf-path=path 配置nginx配置文件的路径,如果不指定这个选项,那么配置文件的默认路径就会是 prefix/conf/nginx.conf
--pid-path =path 配置nginx.pid file的路径,一般来说,进程在运行的时候的时候有一个进程id,这个id会保存在pid file里面,默认的pid file的放置位置是prefix/logs/nginx.pid
--error-log-path=path 设置错误日志的存放路径,如果不指定,就默认 prefix/logs/error.log
--http-log-path= path 设置http访问日志的路径,如果不指定,就默认 prefix/logs/access.log
--user=name 设置默认启动进程的用户,如果不指定,就默认 nobody
--group=name 设置这个用户所在的用户组,如果不指定,依然是nobody
这些是我们常用的编译选项,其他的可以均保持默认,如需特殊指定,可上nginx官网查阅 http://nginx.org/en/docs/configure.html

下面是一些不常用的选项
--with-http_ssl_module -开启HTTP SSL模块,使NGINX可以支持HTTPS请求。需要安装了OPENSSL
--with-http_flv_module
--with-http_stub_status_module - 启用 "server status" 页(可有可无)
--without-http_gzip_module - 禁用 ngx_http_gzip_module. 如果启用,需要 zlib 。
--without-http_ssi_module - 禁用 ngx_http_ssi_module
--without-http_referer_module - 禁用 ngx_http_referer_module
--without-http_rewrite_module - 禁用 ngx_http_rewrite_module. 如果启用需要 PCRE 。
--without-http_proxy_module - 禁用 ngx_http_proxy_module
--without-http_fastcgi_module - 禁用 ngx_http_fastcgi_module
--without-http_memcached_module - 禁用 ngx_http_memcached_module
--without-http_browser_module - 禁用 ngx_http_browser_module
--http-proxy-temp-path=PATH - Set path to the http proxy temporary files
--http-fastcgi-temp-path=PATH - Set path to the http fastcgi temporary files
--without-http - 禁用 HTTP server(用作代理或反向代理)
--with-mail - 启用 IMAP4/POP3/SMTP 代理模块
--with-mail_ssl_module - 启用 ngx_mail_ssl_module
--with-openssl=DIR - Set path to OpenSSL library sources
7.源码编译步骤
a.切换到解压目录
cd nginx-1.19.5
b.执行configure命令
sudo ./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_realip_module
c.执行make命令
sudo make
d.执行安装命令
sudo make install
8.查看端口状态:netstat -ano|grep 80
9.启动Nginx:sudo /usr/local/nginx/sbin/nginx

如果你是阿里云的ECS,则需要配置安全组,80端口才能正常访问。

image

image

image

二、nginx的相关命令
先进入到 /usr/local/nginx/sbin/ 目录下,
启动 ./nginx
停止 ./nginx -s stop
重启 ./nginx -s reload

三、修改apache2的默认端口

1.修改 /etc/apache2/ports.conf 将
NameVirtualHost *:80
Listen 80
改为自己需要的端口
NameVirtualHost *:81
Listen 81
2.修改/etc/apache2/sites-available/default 将第一行的
<VirtualHost *:81>
改为自己需要的端口
<VirtualHost *:81>

四、部署SSL证书

首先得购买证书(一般CA机构会颁发3个证书,即服务器证书、CA证书、根证书),然后生成合并后的证书(lenshen.com.crt)和私钥(lenshen.com.key),具体怎么生成可参考:
http://jingyan.baidu.com/article/154b463178eac928ca8f41a9.html
最后把证书(lenshen.com.crt)和私钥(lenshen.com.key)放在 /usr/local/nginx/conf/目录下。

五、nginx配置https

用vim打开 /usr/local/nginx/conf/nginx.conf

  1. 配置二级域名和端口转发
   server {
        listen       443 ssl;
        server_name  cet.lenshen.com;

        ssl_certificate /usr/local/nginx/conf/lenshen.com.crt;
        ssl_certificate_key /usr/local/nginx/conf/lenshen.com.key;

        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;

        location / {
           proxy_pass http://localhost:8001; #后端的web服务器
           proxy_set_header Host $host;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
        }
    }
   server {
        listen       443 ssl;
        server_name  qq.lenshen.com;

        ssl_certificate /usr/local/nginx/conf/lenshen.com.crt;
        ssl_certificate_key /usr/local/nginx/conf/lenshen.com.key;

        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;

        location / {
           proxy_pass http://localhost:8080; #后端的web服务器
           proxy_set_header Host $host;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
        }
    }
    server {
        listen       443 ssl;
        server_name  music.lenshen.com;

        ssl_certificate /usr/local/nginx/conf/lenshen.com.crt;
        ssl_certificate_key /usr/local/nginx/conf/lenshen.com.key;

        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;

        location / {
           proxy_pass http://localhost:8000; #后端的web服务器
           proxy_set_header Host $host;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
        }
    }

2.配置http重定向到https

  server {  
     listen      80;  
     server_name  lenshen.com;  
     return      301 https://$server_name$request_uri;  
   }
   server {  
     listen      80;  
     server_name    qq.lenshen.com;  
     return      301 https://$server_name$request_uri;  
   }  
   server {  
     listen      80;  
     server_name    music.lenshen.com;  
     return      301 https://$server_name$request_uri;  
   }     
   server {  
     listen      80;  
     server_name    cet.lenshen.com;  
     return      301 https://$server_name$request_uri;  
   }     

六、安装node
cd ~
image
可以看到当前目录是root目录
wget https://npm.taobao.org/mirrors/node/v14.15.1/node-v14.15.1-linux-x64.tar.xz
tar -xvf node-v14.15.1-linux-x64.tar.xz
mv node-v14.15.1-linux-x64 node // 更改目录名
ln -s /root/node/bin/node /usr/local/bin/node //配置软链接
ln -s /root/node/bin/npm /usr/local/bin/npm //配置软链接
npm config set registry https://registry.npm.taobao.org // 设置淘宝镜像源
七、nodejs中使用https

var app = require('express')();
var fs = require('fs');
var https = require('https');
var privateKey  = fs.readFileSync('/usr/local/nginx/conf/lenshen.com.key', 'utf8');
var certificate = fs.readFileSync('/usr/local/nginx/conf/lenshen.com.crt', 'utf8');
var credentials = {key: privateKey, cert: certificate};
var httpsServer = https.createServer(credentials, app);
var SSLPORT = 18081;
httpsServer.listen(SSLPORT, function() {
     console.log('HTTPS Server is running on: https://localhost:%s', SSLPORT);
});
// Welcome
app.get('/', function(req, res) {
     if(req.protocol === 'https') {
        res.status(200).send('Welcome to Safety Land!');
     }
     else {
        res.status(200).send('Welcome!');
     }
});

使用ES6 promise封装文件读写操作

使用ES6 promise封装文件读写操作

nodejs的 fs 文件系统操作都是异步的,充满了大量的回调函数,存在难以忍受的回调地狱。
现在我们可以使用ES6 promise封装,来解决这个问题。
具体代码如下:

//  fs-async.js文件

import fs from 'fs'

/**
 * [读文件]
 * @param  {[type]} filePath [文件路径]
 * @return {[type]}          [description]
 */
export const readFile = (filePath) => {
	return new Promise((reslove, reject) => {
		fs.readFile(filePath, 'utf8', (err, data) => {
			if (!err) {
				reslove(JSON.parse(data))
			} else {
				reject(err)
			}
		})
	})
}

/**
 * [写文件]
 * @param  {[type]} filePath [文件路径]
 * @param  {[type]} data     [新的文件数据]
 * @return {[type]}          [description]
 */
export const writeFile = (filePath, data) => {
	return new Promise((reslove, reject) => {
		fs.writeFile(filePath, JSON.stringify(data), (err) => {
			if (err) {
				reject(err)
			}
		})
	})
}

封装后我们可以使用 async/await以同步的方式来读取文件内容了。

import {readFile,writeFile} from './fs-async'
const getData=async()=>{
    const data=await readFile('./users.json')
   console.log(data)
}
getData()     //会打印出文件的内容

请问koa2如何实现socket.io?

我代码是这样的:

const Koa = require('koa');
const app = new Koa();
const server = require('http').Server(app.callback());
const io = require('socket.io')(server);

io.on('connection', function(socket){
  io.emit('this', { will: 'be received by everyone'});

  socket.on('private message', function (from, msg) {
    console.log('I received a private message by ', from, ' saying:', msg);
  });

  socket.on('disconnect', function () {
    io.emit('user disconnected');
  });

});

但是前端请求的时候找不到数据,该如何做呢,希望提供ko2的一些源码。

关于Array.prototype.includes

这是ES7新增的判断数组里面是否存在某个值的方法,存在时返回true,否则返回false。
例如:const arr=[1,3,4,5,6] ,那么arr.includes(3)将返回true,因为包含了数字3
值得注意的是,如果该值是一个非空对象,那么无论怎么判断,都是返回false的。

const arr=[{name:'zls'}]
console.log(arr.includes({name:'zls'}))   // false,注意这里是返回false

其它基本数据类型都是返回true,所有测试用例如下:

   const arr=[1]
   console.log(arr.includes(1))  // true
   const arr1=[true,false]
   console.log(arr1.includes(true))   //true
   const arr2=[undefined]
   console.log(arr2.includes(undefined))   // true
   const arr3=['str']
   console.log(arr3.includes('str'))   // true
   const arr4=[null]
   console.log(arr4.includes(null))   // true
   const arr5=[{name:'zls'}]
   console.log(arr5.includes({name:'zls'}))   // false

如果我们要判断数组里是否存在某个对象,我们可以使用arr.find()或者arr.findIndex(),
例如 const arr=[{name:'zls'}],如果要判断这个arr数组里是否包含 {name:'zls'},可以这样:

const index=arr.findIndex((item)=>{
   return item.name='zls'
})
//   index为0,在第0个位置,所以是存在的

获取gbk/gb2312编码的网页

爬虫有时候会遇到网页编码为gbk/gb2312的网页,这些网页爬取后,里面的中文是全部乱码的,解决方案是用iconv-lite进行转码。例如这个网页 http://1212.ip138.com/ic.asp ,就是gb2312编码的,爬取到的数据就会是中文乱码。具体转码过程如下:

import http from 'http'
import iconv from 'iconv-lite'    //引入第三方模块

const url = 'http://1212.ip138.com/ic.asp'  //获取到的会是服务器的ip地址
http.get(url, res => {
      let arrBuf = [],
      bufLength = 0
      res.on("data", chunk => {
		arrBuf.push(chunk)
		bufLength += chunk.length
      })
     .on("end", () => {
	       const chunkAll = Buffer.concat(arrBuf, bufLength),
	       strJson = iconv.decode(chunkAll, 'gb2312'), // 汉字不乱码
	       startIndex = strJson.indexOf('省'),
	       endIndex = strJson.indexOf('市'),
               city=strJson.substring(startIndex + 1, endIndex)     //城市名
               console.log(strJson,city)   //均是中文
     })
})

使用socket.io遇到的一些坑

一、給指定的用户发送消息

为了给指定的用户发送消息,我们需要建立一张hash表,键为用户的id,值为该用户连接时生成的socketid。并把这张表以json的格式存储在服务端的users.json文件里。每次登陆的时候就把用户的id和socketid存储到服务器端。具体怎么实现可以参考问题二里的服务端代码。

二、用户手动刷新或者重新打开浏览器,原来的socketid会失效

如果用户手动刷新或者重新打开浏览器(都可视为刷新),那么服务器端保存的该用户对应的socketid会失效,因为刷新会导致connection,会生成新的socketid。这样导致的问题就是服务端无法继续给指定的用户推送消息了(socketid失效)。

解决方案如下:
客户端:每次连接服务端的时候就emit用户的id。当然得先判断下用户是否已经登陆了,没有登陆的话,自然无法获取用户的id,无法emit,这种情况就只能等用户登陆了再emit用户的id。

客户端需要在两个地方emit用户的id:
(1)登录时(emit用户id)

  //登陆后的回调
 callback({code,data,message}){ 
      if(code==1){
        socket.emit('login',data.loginStatus.userId)  // emit login事件,传递用户id
        this.$store.commit('SET_LOGIN',data)  //设置store状态
        this.$router.push('message')
      }else{
        this.$store.dispatch('setShowWarn',message)
      }
  }

(2)连接时(需判断是否已经登陆)

    <script src="http://localhost:3000/socket.io/socket.io.js"></script>
    <script>
      const socket = io.connect('http://localhost:3000/')

      //解决用户手动刷新浏览器后,原来的socketid失效的问题
      const loginStatus= JSON.parse(localStorage.getItem("loginStatus") || '{}')
     // emit update事件,传递用户id
      loginStatus.isLogin && socket.emit('update',loginStatus.userId)   
    </script>

服务端:同时监听login事件和update事件

import socketHander from './socket'    //socket要实现的具体逻辑
//这里省略其它导入

// socket事件
io.on('connection', (socket) => {
	const socketId=socket.id
	//监听用户登录
	socket.on('login', (userId) => {
		//保存用户的id和socketid
		socketHander.saveUserSocketId(userId, socketId)
	})
	//监听用户刷新
	socket.on('update', (userId) => {
		//保存用户的id和socketid
		socketHander.saveUserSocketId(userId, socketId)
	})
})

socket.js:

import {
	readFile,
	writeFile
} from './fs-async'

const filePath = `${__dirname}/users.json`

// socket具体业务逻辑
export default class socketHander {
	/**
	 * [saveUserSocketId 保存用户的id和socketid]
	 * @param  {[type]} userId   [用户id]
	 * @param  {[type]} socketId [用户的socketid]
	 * @return {[type]}          [description]
	 */
	static async saveUserSocketId(userId, socketId) {
		let data = await readFile(filePath).catch((err) => {
			console.log(err)
		})
		data[userId] = socketId
		writeFile(filePath, data)
	}
}

fs-async.js:

import fs from 'fs'

/**
 * [读取文件的内容]
 * @param  {[type]} filePath [文件路径]
 * @return {[type]}          [description]
 */
export const readFile = (filePath) => {
	return new Promise((reslove, reject) => {
		fs.readFile(filePath, 'utf8', (err, data) => {
			if (!err) {
				reslove(JSON.parse(data))
			} else {
				reject(err)
			}
		})
	})
}

/**
 * [写文件]
 * @param  {[type]} filePath [文件路径]
 * @param  {[type]} data     [新的文件数据]
 * @return {[type]}          [description]
 */
export const writeFile = (filePath, data) => {
	return new Promise((reslove, reject) => {
		fs.writeFile(filePath, JSON.stringify(data), (err) => {
			if (err) {
				reject(err)
			}
		})
	})
}

users.json:

{}

三、socket事件重复监听的问题
在单页应用里,使用socket.on监听事件前一定要先移除原来的事件。不然会导致生成重复的监听器。

 //通过socket来更新消息
 updateBySocket(){
      socket.removeAllListeners()  //一定要先移除原来的事件,否则会有重复的监听器

      socket.on('receivePrivateMessage',(data)=>{
          this.$store.commit('UPDATE_MESSAGE',{
            from_user:data.from_user_beizhu,
            id:data.from_user,
            imgUrl:data.from_user_face,
            message:data.message,
            time:data.time,
            type:'single'
          })
      })
      socket.on('receiveGroupMessage',(data)=>{
          //如果不包含自己,则直接丢弃这个socket消息
          if(!data.group_member.includes(this.userId-0))  return

          this.$store.commit('UPDATE_MESSAGE',{
            from_user:data.group_name,
            id:data.group_id,
            imgUrl:data.group_avator,
            message:`${data.from_user_nick_name}:${data.message}`,
            time:data.time,
            type:'group'
          })
      })
 }

关于nginx反向代理后获取不到客户端的真实ip地址

要想在程序中取得真实的IP,在执行nginx的configure时,必须指定参数“--with-http_realip_module”,
示例:
sudo ./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_realip_module
然后修改nginx配置文件:

  location / {
           proxy_pass http://localhost:8080; #后端的web服务器
           proxy_set_header Host $host;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header REMOTE-HOST $remote_addr;
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }

重启nginx: ./nginx -s reload 即可。
然后在express服务器里,通过 req.headers['x-real-ip']即可拿到真实ip地址

几个CSS属性

(1)css设置文本超出时显示省略号:

        overflow:hidden;  /*内容超出后隐藏*/
        text-overflow:ellipsis; /*当文本溢出时显示省略标记(...)*/
        white-space:nowrap;  /*文本不换行*/

(2)word-wrap和word-break
word-wrap 属性用来标明是否允许浏览器在单词内进行断句,这是为了防止当一个字符串太长而找不到它的自然断句点时产生溢出现象。属性值为break-word时表示单词过长时强制断句换行显示。换句话说,word-wrap 是用来决定允不允许单词内断句的,如果不允许的话长单词就会溢出。最重要的一点是它还是会首先尝试挪到下一行,看看下一行的宽度够不够,不够的话就进行单词内的断句。
word-break 属性用来标明怎么样进行单词内的断句。当属性值为break-all时,它不会尝试把长单词挪到下一行,而是直接进行单词内的断句。使用break-all会更节省空间。

word-wrap:break-word;
word-break:break-all;

npm run dev 启动时报错

node build/dev-server.js

/arno/ChatDemo/build/dev-server.js:40
log: () => {}
^
SyntaxError: Unexpected token )
at Module._compile (module.js:439:25)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:902:3

npm ERR! [email protected] dev: node build/dev-server.js
npm ERR! Exit status 8
npm ERR!
npm ERR! Failed at the [email protected] dev script.
npm ERR! This is most likely a problem with the vue-qq package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR! node build/dev-server.js
npm ERR! You can get their info via:
npm ERR! npm owner ls vue-qq
npm ERR! There is likely additional logging output above.
npm ERR! System Linux 3.10.0-514.21.2.el7.x86_64
npm ERR! command "/usr/local/bin/node" "/usr/local/bin/npm" "run" "dev"
npm ERR! cwd /arno/ChatDemo
npm ERR! node -v v0.10.26
npm ERR! npm -v 1.4.3
npm ERR! code ELIFECYCLE
npm ERR!
npm ERR! Additional logging details can be found in:
npm ERR! /arno/ChatDemo/npm-debug.log
npm ERR! not ok code 0
这是什么原因?

服务端使用ES6语法

如何在服务端使用ES6?
不需要使用babel转码以及一系列的配置,只需要将node升级到V8版本,V8已经很好地支持了ES6/ES7/ES8等最新特性,这是目前最好的办法。升级到V8版本,可以直接到nodejs中文网(http://nodejs.cn/download/) 下载即可。
升级到V8后,可能还不支持通过import/export关键字来导入导出模块(服务端已经有了CommonJS规范,即使用require/exports关键字来导入导出模块),你可以继续使用module.exports来导出模块,如果一定要用import/export,那么这时可以在文件入口添加以下代码:

require("babel-core/register")({
  presets: ['es2015', 'stage-0']
})
require("babel-polyfill")

同时需要通过npm安装babel-core、babel-preset-es2015、babel-preset-stage-0、babel-polyfill等依赖。
这样就可以愉快地使用import/export了。
项目中服务端代码片段:

import express from 'express'
import loginRouter from './router/login'
import registerRouter from './router/register'
import friendRouter from './router/friend'
import messageRouter from './router/message'
import userRouter from './router/user'
import chatRouter from './router/chat'

const apiRouter = express.Router()

apiRouter
	.use('/login', loginRouter)
	.use('/register', registerRouter)
	.use('/friend', friendRouter)
	.use('/message', messageRouter)
	.use('/user', userRouter)
	.use('/chat', chatRouter)

export default apiRouter

IP地址定位和天气接口

一、IP地址定位
1.自动获取IP地址和地理位置:
(1) 接口1:http://1212.ip138.com/ic.asp
(2) 接口2(淘宝IP地址库):http://ip.taobao.com/ipSearch.php
2.通过IP地址获取地理位置
参考 http://ip.taobao.com/instructions.php ,可以得到以下接口
接口:http://ip.taobao.com/service/getIpInfo.php?ip=[ip地址字串]
二、天气接口
1.通过聚合数据,可以申请免费的天气接口
2.新浪的接口:http://php.weather.sina.com.cn/xml.php?city=%B1%B1%BE%A9&password=DJOYnieT8234jlsK&day=0
其中city是经过urlencode后的城市名称,如 北京->%B1%B1%BE%A9,会返回一个xml格式的数据

数组去重

ES6实现:
[...new Set([1,2,3,1,'a',1,'a'])]

ES5实现:

[1,2,3,1,'a',1,'a'].filter(function(ele,index,array){
    return index===array.indexOf(ele)
})

页面状态无法保存

比如说:我在消息列表将列表滚动到底部(先把窗口高度调小),随意点击列表中的一个item,进入聊天详细页面,点击返回时,列表会自动滚动回顶端。
其他有列表的页面都有这种情况。

sort排序的问题

按时间降序,之前写的是

allMessage.sort((prev, current) => { //按时间降序排列
        return prev.time <  current.time
 })

这样写是错的,应该改成下面这样:

allMessage.sort((prev, current) => { //按时间降序排列
        return current.time - prev.time
 })

return的值必须要能包含大于0,小于0和等于0的情况。不能是true或false。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.