Docker入门使用:blue_car:
安装并配置加速地址
常用命令
docker ps 查看进程 |
docker
服务不需要考虑环境问题就能跑起服务,非常有效
docker-compose
集合命令 用于创建多个docker
镜像服务
#下载: |
创建docker-compose.yml
version: '3' |
# Use root/example as user/password credentials |
运行 docker-compose up -d
实战
service docker start 启动docker服务 |
Nginx学习:accept:
反向代理解决跨域问题
nginx.conf
server |
项目开发
Sass学习:star:
sass和之前学习的stylus相同,只不过语法方面有点不同
语法
- sass必须要有大括号和分号结尾
css函数
定义函数
@function px2rem($px){ |
@mixin center{ |
使用方法
@include
+函数名
css变量
$text-large:20px; |
使用案例
@import "./mixin"; |
Element-Ui
安装并使用
安装并且使用
npm i element-ui -S |
按需加载项目
npm install babel-plugin-component -D |
引入组件
import {Button} from 'element-ui' |
单独js文件引入
import {
Message
} from 'element-ui'
Message({
type: 'error',
message: error.response.data.msg,
duration: 4000
})
完整的组件按需引入
import { |
表单组件:baby_chick:
表单引入
model
绑定表单对象,inline
设置单行显示,:rules
设置表单的校验规则
<el-form |
表单校验常用
在
data
中定义校验规则对象,例如:rules
,然后再form
引入,并指定每个表单项的校验规则先指定规则
:rules
,然后指定元素的校验规则prop="规则里面的对象"
<el-form inline :model="data" :rules="rules" ref="form">
<el-form-item label="审批人" prop="user">
- 编写校验规则,
rules
参考async-validator
const userValidator = (rule, value, callback) => { |
校验参考
rules: { |
- 表单常见属性 参考属性
其他组件用法总结
布局
默认24分栏
一行
一列
:span
指定分布区域大小
:gutter
指定每列间隔 ,定义在行
:offset
指定分栏偏移,定义在列
<el-row :gutter="20"> |
指定flex布局
<el-row type="flex" class="row-bg"> |
弹出框
Tab
解决重复请求问题 切换到另一个
tab
才请求数据,记录对象,如果重复点击 直接退出
data() { |
选择框
<el-select v-model="collegeValue" clearable style="margin-left:6px" placeholder="请选择学院" @change="OnselectCollege"> |
消息框
this.$confirm('确定要删除这名学生的信息吗, 是否继续?', '提示', { |
分页器
<el-pagination |
Dialog
<el-dialog title="收货地址" :visible.sync="dialogFormVisible"> |
文件上传
<el-upload |
|
登录注册找回密码:hear_no_evil::hear_no_evil:
验证码接口
这个使用到了svg-capthcha
插件生成验证码
async getCode(ctx,next){ |
改造验证码接口,这里我们使用redis
验证码的值放进缓存,然后设置时效性
首先前端通过uuid
生成随机串码,然后存储到localstorage
再保存在vuex
中 ,vuex
使用mixin
混入
storeSid(){ |
后端保存
const sid = ctx.request.query.sid |
登录注册接口(jwt鉴权):loudspeaker:
引入
koa-jwt
鉴权,然后编写登陆注册接口
首先引入koa-jwt
插件帮助我们快速集成jwt鉴权
在app.js
中使用
const JWT = require('koa-jwt') |
如果接口是保护的接口,就会抛出401
状态码,这里需要对异常进行处理**,全局异常处理**参考异常处理
鉴权异常
Http异常 未知异常
const { HttpExecption } = require('../core/http-execption') |
了解jwt
的API
jwt.sign() |
登录逻辑:
- 接收用户的数据
- 验证图形验证码的有效性,正确性
- 数据库判断用户名密码(比较盐)是否正确
- 返回
token
相关业务代码
// 登录 |
明文加密
const bcryptjs = require('bcryptjs') |
注册逻辑:
- 接收数据,处理数据
- 核验验证码正确性和有效性
- 比对数据中的
userName
是否唯一,插入数据
// 注册 |
邮箱找回密码接口:relaxed:
配置公共邮箱的开启stmp服务
wsrnpqaeinswbjcc |
这里需要使用nodemailer
插件
配置
nodemailer
初始方法async function send(sendInfo) {
let transporter = nodemailer.createTransport({
// 发送的主机地址
host: 'smtp.qq.com',
port: 587,
secure: false,
//配置授权邮箱 和授权码
auth: {
user: '251205668@qq.com', // generated ethereal user
pass: 'wsrnpqaeinswbjcc', // generated ethereal password
},
})
// 配置跳转的路由
let url = 'http://www.imooc.com'
let info = await transporter.sendMail({
from: '"认证邮件" <251205668@qq.com>', // sender address
to: sendInfo.email, // 发送的邮箱账号
// 配置主题
subject:
sendInfo.user !== ''
? `你好开发者,${sendInfo.user}!《论坛》验证码`
: '《论坛》验证码', // Subject line
text: `您在《论坛》中注册,您的邀请码是${
sendInfo.code
},邀请码的过期时间: ${sendInfo.expire}`,// 模拟生成的30分钟倒计时
// 邮件主题内容
html: `
<div style="border: 1px solid #dcdcdc;color: #676767;width: 600px; margin: 0 auto; padding-bottom: 50px;position: relative;">
<div style="height: 60px; background: #393d49; line-height: 60px; color: #58a36f; font-size: 18px;padding-left: 10px;">论坛社区——欢迎来到官方社区</div>
<div style="padding: 25px">
<div>您好,${sendInfo.user}童鞋,重置链接有效时间30分钟,请在${
sendInfo.expire
}之前重置您的密码:</div>
<a href="${url}" style="padding: 10px 20px; color: #fff; background: #009e94; display: inline-block;margin: 15px 0;">立即重置密码</a>
<div style="padding: 5px; background: #f2f2f2;">如果该邮件不是由你本人操作,请勿进行激活!否则你的邮箱将会被他人绑定。</div>
</div>
<div style="background: #fafafa; color: #b4b4b4;text-align: center; line-height: 45px; height: 45px; position: absolute; left: 0; bottom: 0;width: 100%;">系统邮件,请勿直接回复</div>
</div>
`, // html body
})
return 'Message sent: %s', info.messageId
}编写接口方法
async sendEmail(ctx,next){
const v = await new SendEmailValidator().validate(ctx)
const userName = v.get('body.userName')
const result = await send({
// 验证码: 暂时模拟为1234
code:1234,
// 有效时间:创建模拟的格式化的时间
expire:moment().add(30,"minutes").format('YYYY-MM-DD HH:mm:ss'),
email:userName,
user:'努力中的杨先生'
})
ctx.body = {
code:200,
message:"邮件发送成功",
data:result
}
}优化后的忘记密码
async senEmail(ctx,next){
// 发送邮箱 接收参数userName sid
const v = await SenEmailValidator().validate(ctx)
const userName = v.get('body.userName')
const sid = v.get('body.sid')
const code = v.get('body.code')
// 校验验证码
if(code!==null && code.toLowerCase === getValue(sid).toLowerCase){
// 校验用户名是否存在
const user = await User.findOne({userName:userName})
if(user){
const key = uuid()
const token = jwt.sign({_id:user._id},JWT_SECRET,{ expiresIn:60*30})
await setValue(key,token)
const result = await send({
data:{
token,
userName
},
expire:moment().add(30,"minutes").format('YYYY-MM-DD HH:mm:ss'),
email:userName,
user:user.name
})
ctx.body = {
msg:'邮件发送成功,请注意查收',
code:200,
data:result
}
}else{
ctx.body = {
code:500,
msg:'用户名不存在'
}
}
}else{
ctx.body = {
code:500,
msg: '验证码错误'
}
}
}
// 链接路由:localhost:8080/忘记密码接口
async forgetPassword(ctx
const v = await new ForgetPasswordValidator().valdate(ctx)
const newPassword = v.get('body.newPassword')
// 接收参数 newPassword
const playLoad = await getJwtplayLoad(ctx.request.header.authorization)
const _id = playLoad._id
const user = await User.findOne({_id})
// 加密
const sault = bcryptjs.genSaltSync(10)
password = bcryptjs.hashSync(newPassword, sault)
// 更新
const result = await User.updateOne({_id},{password})
if(result.n === 1 && result.ok === 1){
ctx.body = {
msg:'重置密码成功',
code:200
}
}else{
ctx.body = {
msg:'重置密码失败',
code:500
}
}
}另一种方法: 发送邮件时 生成一个
随机四位数
,然后存储在redis中.当用户忘记密码需要重置密码时,传入新密码和验证码,后端查询redis
,如果正确就可以重置密码
配置项目
封装axios:alien::alien:
初步封装
// 封装 axios |
异常处理初步封装
import { |
代理请求和路径代理(解决跨域):weary:
配置
vue.conf.js
const path = require('path')
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000/v1'
}
}
},
chainWebpack: (config) => {
config.resolve.alias
.set('scss', path.join(__dirname, './src/assets/scss/'))
.set('@', path.join(__dirname, './src/'))
}
}
环境变量
创建
.env.development
和.env.production
VUE_APP_BASE_URL=http://localhost:3000默认的环境变量
开发环境 process.env.NODE_ENV = 'development'
生产环境 process.env.NODE_ENV = 'production'
axios.defaults.baseURL =
process.env.NODE_ENV !== 'production'
? 'http://localhost:3000'
: 'http://your.domain.com'然后使用环境变量
process.env.VUE_APP_BASE_URL
路由懒加载:accept:
安装插件syntax-dynamic-import
然后书写babel.config
文件
plugins: [ |
懒加载语法 |
优化路由切换动效:blonde_woman:
使用到了iview
的LoadingBar
组件
import { LoadingBar } from 'iview' |
集成Mongdb和redis
mongdb入门
mongdb是属于典型的非关系型数据库,很好的处理了分布式储存,以对象的形式存储数据
Docker安装
# Use root/example as user/password credentials |
增删改查
# 使用test数据库 |
Mongoose:cocktail::cocktail::eight_pointed_black_star:
mongdb的orm
框架,让node操作mongdb
变得更加方便
mongoose操作的对象是
Schema
模式对象,对字段约束Model
相当于Mongdb的集合collections
Document
文档
使用mongoose
连接数据库
const mongoose = require('mongoose') |
具体使用案例
首先明确这几个点mongoose需要通过Schema约束模型,然后通过模型对象进行增删改查
所有首先需要创建Schema对象,再创建Model,再编写业务
创建模型
const mongoose = require('mongoose') |
这里Schema
约束规范实例参考
const UserSchema =Schema({ |
如果集合已经存在 ,这里是个坑
>const Schema = Mongoose.Schema()
>const ci = Mongoose.model('ci',Schema,'ci')
>// 删除一条数据
>// ci.remove({'':'5ebe3c8de468cc2157db610b'},(err,res)=>{
>// console.log('删除成功',res)
>// })
>ci.find({author:'和岘'},(err,res)=>{
console.log(res)
>})
mongoose增删改查
**插入保存在数据库**// 创建好mongoose模型 |
更新数据
cosnt user = new UserModel({ |
删除数据
UserModel.remove({ |
查询
//普通查询 |
async function query(){ |
除了这些 mongoose
还支持在初始化Schema后添加一些静态方法,相当于添加原型链
const PostSchema = new Schema({ |
redis
开启redis服务
# 创建持久化redis服务 |
redis相关命令
进入cli |
发布订阅 |
node.js操作redis:strawberry::strawberry:
安装依赖redis
,并配置相关
const redis = require('redis') |
操作redis
通过
client
操作redis
一般分为设置键值,区间值,而值可能存在string
或者Object
两种,所以需要对其封装
// 创建客户端 |
首页:heart:
前端部分技巧:fist_raised:
时间格式处理
filters:{ |
加载更多基本逻辑
|
组件和路由拆分
抽离组件
头部导航 挂载APP
panel 分类面板组件 控制路由
// 相关路由配置
{
path:'/',
component:Home,
children:[
// 分类动态路由和默认的路由
{
path:'',
name:'index',
component:Index
},
{
path:'/index/:categroy',
name:'catelog',
component:Template1
}
]
}
sort排序 tab面板 请求数据,不改变路由
content 长列表面板
使用
iview-LoadingBar
组件优化路由跳转
router入口文件
router.beforeEach((to,from,next)=>{
LoadingBar.start()
next()
})
router.afterEach((to,from)=>{
LoadingBar.finish()
})
qs库的作用处理get请求url传对象参数解析
将 |
后端模型和开发细节:articulated_lorry:
文章模型 定义schema 联合user表查询 获取文章列表(筛选) 通过id查询文章 通过uid查询文章列表 通过uid查询文章数量 |
首页后端开发细节概要
定义模型时 规范定义 例如文章
const PostSchema = new Schema({ |
连表查询 : 父表需要有
ref
指定字表,字表需要定义唯一索引//父表
userInfo{
type:String,ref:"users"
}
// 子表
userName:{
type:String,
index:{
unique:true
},
// 当集合没有userName 不执行查询
sparse: true
}
// 查询时附带字表相关内容
userModel.find(...).populate({
// 指定替代
'path':"userInfo",
// 指定返回的字段
'select':"isvip avatar name"
})
多种查询条件查询 最好定义静态方法
// 添加静态方法 |
利用校验器 设置默认值
const options = {} |
利用mongose
的钩子函数 在查询语句执行前执行操作
// 设置调用save和update 保存的属性 |
防止重复点击
x:{ |
当数据有很多种分类,但是默认是返回全部分类的数据时,需要删除掉默认对象的值
const options = {} |
个人中心页面和设置:bust_in_silhouette:
前端细节:imp::imp::imp::imp:
头像区菜单项显隐原理
当鼠标移入头像区域或者移入菜单
li
区域时触发显示,当鼠标移出时触发隐藏事件,但是考虑到头像区域和菜单区域有间距,必须要设置定时器触发事件防止显示事件立即隐藏
show(){
seTimeout(()=>{
this.IsShow = true
},200)
},
hide(){
seTimeout(()=>{
this.isShow = false
},500)
}
路由拆分
个人中心路由
>{
// 显示信息分为:全部 文章 讨论 分享 求助 收藏集 点过赞的帖子 关注列表 粉丝
path:'/user/:uid/:type',
name:'UserCenter',
components:'UserCenter'
>},
>{
// 个人设置 profile 个人资料 password 密码修改 账号关联 account
path:'/setting/:type'
>}
组件划分
个人中心组件:
userBanner
,路由是当前登录用户的uid
时,显示编辑资料按钮,不是则显示关注按钮
tabs
,切换不同的type
的tab
组件,切换路由监听路由的变化加载不同的请求数据,需要提供全部 文章 讨论 分享 求助 收藏集 点过赞的帖子 关注列表 粉丝
user-postContent
,文章列表组件
userInfo
组件: 显示关注了多少人,粉丝数量,获取点赞数量,签到天数,个人积分
创建路由守卫,在用户未登录状态不允许进入用户设置页面:imp::imp:
{ |
sessionStorage
和localstorage
区别
sessionStorage
是指当会话存在时的缓存,当关闭浏览器或者tab
,缓存会清空
localstorage
指保存在本地的缓存
添加404导航页面
在router
配置文件最后一行添加这样的路由配置
{ |
上传头像
原生做法
<input id="pic" type="file" name="file" accept="image/png.image/jpg,image/gif" @change="changeImage" /> |
后端接口:kiss:
获取用户基本信息
async getUserInfo(ctx){ |
修改基本信息
async updateUserInfo(ctx){ |
修改邮箱绑定
发送一封确认更改邮箱绑定的邮箱
const token = jwt.sign({_id:playload._id},JWT-SERCERT,{ expiresIn: 60 * 30 })
// 缓存在redis中
const key = uuid()
setValue(key,token)
await send({
expire:moment().add(30,'minutes').format('YYYY-MM-DD HH:mm:ss'),
email:user.userName,
name:user.name ,
userName:body.userName,
key
})然后邮箱的重置链接需要携带数据-跳转的页面路由
ep
:localhost:8080/resetEmail?key=uuid()&userName=email
最后需要定义用户修改邮箱的接口
async updateUserName(ctx){
const v = await new UserNameValidator().validate(ctx)
const key = v.get('query.key')
const userName = v.get('query.userName')
// 获取redis数据 获取_id
if(key){
const token = await getValue(key)
const playload = await getJwtplayLoad('Bearer '+token)
// 判断邮箱是否存在
const user = await User.finOne({userName})
if(user && user.password){
throw new UserExistedException('邮箱已存在',200)
}else{
await User.updateOne({_id:playload._id},{userName})
ctx.body = {
code:200,
msg:'换绑成功'
}
}
}
}
修改密码
async modifyPassword(ctx){ |
上传文件
定义上传的路径
const uploadPath = `${pwd.process()}/static` |
Node.js判断路径是否目录存在 :
statis.isDirectory()
const fs = require('fs') |
判断目录是否存在,如果觉得麻烦就直接用
make-dir
第三方库
// D:User/1212/121.jpg |
签到系统
设计签到记录表
字段 | 意义 |
---|---|
created_time | 创建时间 |
last_signTime | 上一次签到时间 |
user | 用户id |
favs | 当前签到积分 |
签到系统基本逻辑
查询用户签到信息
- 是否是连续签到 ,连表查询出
user
中的count
和总积分favs
sign_record
只记录用户单次签到的情况
无签到记录
一次积分是5,然后保存当前的记录表,更新用户表总积分和连续签到数量。
如果当前用户有签到记录
二种情况:
上一次签到时间是今天,此时抛出异常
上一次签到时间不是今天
上一次签到时间是昨天
此时先将用户连续签到的数量获取 然后设置积分逻辑,更新用户的
count
和总积分间隔时间签到
此时一次签到积分又变回5,用户连续签到天数设置为1
具体实现
定义Schema
const SignRecordSchema = new Schema({ |
签到逻辑具体
获取jwt
身份验证信息
async getJwtPlayLoad(AuthHeader){ |
积分签到
const SignRecord = require('../models/sign_record') |
前端部分逻辑
每次取缓存中的userInfo数据,保存在变量userObject,如果缓存中有`count`就为count,否则为0 |