简单的Web应用,从数据的获取到页面的展示



  • 大神请绕道,如有说的不对的地方望指正。
  • 应届毕业生在求职中,很多都是因为经验不足而不被录用(很牛的程序猿另说),在面试的时候不仅要对专业的技能熟悉掌握,最好是有自己的一些小作品小项目等等,才能博取面试官的青睐,于是很多毕业生开始自己的面试项目制作,记得回看刚出来求职的面试项目,简直惨不忍睹,不知道你们是不是也这样,为了打救像我一样选择了前端又经常逃课的应届毕业生,于是开始了长篇大论的逼逼。
  • 最近无聊抓了点数据,写了下很久没用的vue,发现了一些不错的组件。
  • 先上图

开始入坑(数据的抓取)

* 开始项目之前,必须确立主题,想完成一个什么Web应用(商城、音乐、外卖等等),今天就以外卖来举个栗子。 * 首先是定制数据的需求,需要商家->商品->商品分类...这些数据的来源当然是网上了,随便找一下一大堆关于使用Node抓取的数据,以eleme为例子,打开eleme的官网,看到Network中获取到的数据,根据请求可以知道数据的具体来源等信息,可以使用node直接调用接口来保存返回的数据。 > Eleme的地区商家列表数据获取 ?
//数据地址:https://www.ele.me/restapi/shopping/restaurants?extras%5B%5D=activities&geohash=ws0e5f7pu8xe&latitude=23.041037&limit=24&longitude=113.372243&offset=0&terminal=web

let imgCount = 0,imgIndex = 0,dataCount = 0,dataIndex = 0	
// 获取的图片总数    正在处理的图片下标      获取商家的总数     正在处理的商家数据下标
const start = () => {                       //开始调用数据接口使用UTF8编码获取数据
	https.get(url, (res, req) => {
		res.setEncoding('utf8')
		let data = ""
		res.on("data", (chunk) => {
			data += chunk
		}).on("end", () => {
			gatData(JSON.parse(data))   //JSON格式化一下,再去对数据做处理,过滤一些自己想要的数据
		})
	})
}
复制代码
Eleme的地区商家列表数据过滤与处理 ?
const gatData = (data) => {
	let arr = [],imgarr = []
	data.forEach((item, index) => {
		let obj = {
			sid:item.id,			//商家ID
			name:item.name,			//商家名字
			address: item.address,								
			latitude: item.latitude,			//商家经度
			longitude: item.longitude,			//商家纬度
			icon: getImg(item.image_path).src	//商家图标    
			//此处省略一大堆过滤数据
		}
		if(imgarr.indexOf(item.image_path) === -1){
			imgarr.push(item.image_path)
		}
		arr.push(obj)
	})
	imgCount = imgarr.length - 1
	dataCount = arr.length - 1
	console.log("共获取图片:" + imgarr.length + "张")
	console.log("共获取数据:" + arr.length + "条")
}
复制代码
Eleme商家列表的图片获取 ?
//图片获取
const saveImg = (_src,list) => {
	let src = getImgType(_src).src,         //获取图片的地址
		name = getImgType(_src).type        //根据自己需求确定保存图片的名字
	https.get(src, (res) => {
		let imgData = ''
		res.setEncoding("binary")           //注意请求返回的编码
		res.on('data', (chunk) => {
			imgData += chunk
		})
		res.on('end', () => {
			fs.writeFile("./保存图片的路径/" + _src + "." + name, imgData, 'binary', (error) => {
				if(error) {
					console.log('下载失败')
				}
				imgIndex++
				console.log('下载成功!',src)
				if(imgIndex === imgCount){
					return
				}
				download(list)
			})
		})
	})
}
复制代码
Eleme商家列表的数据库的写入-> 使用MySQL的 ?
const saveDB = (list,i) => {
	if(dataIndex === dataCount + 1){
		return console.log("全部插入完成!!!!")
	}
	//此处对数据处理 -----   必须对数据的格式做些处理
	let SQL = "INSERT INTO `ele_seller` (`sel_id`, `sel_name`,....) VALUES (NULL, '" + list[i].name + "', "....");"
	db.query(SQL, function(error, rows) {
		if(error) {
			return console.log(error)
		}
		dataIndex++
		saveDB(list,dataIndex)
		console.log("插入数据库成功!!!__")
	})
}
//这是在人家没有对返回的数据进行加密、还有没有对请求域拦截的情况下可行
复制代码
下面是关于某商城直接抓页面的数据 ?
//获取数据的来源:https://www.yohobuy.com/list/ci56-gd1.html?page=1
//先是获取左侧商品的分类,再通过分类获取具体的所属商品
// 上衣(全部)下装(全部)鞋子(全部)包类(全部)居家生活(全部)服配(全部)
const start = () => {
	let _url = "https://www.yohobuy.com/list/ci" + typeArr[typeIndex] + "-gd1.html?page=" + page
	superagent.get(_url).end((err, res) => {
		if(err) {
			return console.log("err", err)
		}
		gatData(res.text,typeArr[typeIndex] + "--" + page)
	})
}
const gatData = (html) => {
	let $ = cheerio.load(html),sknArr = []
	$(".good-info").map((index, item) => {
		let obj = {
			id:$(item).attr("data-skn"),
			img:imgForm($(item).find("img").eq(0).attr("data-original"))
			//此处省略一大堆数据
		}
		sknArr.push(obj)
	})
	console.log(sknArr)
} //使用Http直接请求页面会返回403,通过superagent模块去请求页面,再通过cheerio抓取对应数据,这种方式获取的数据不全
复制代码
  • 伤心病狂

后台搭建

* 数据有了后台就简单使用express搭建,可以考虑各种问题,数据的加密,路由的拦截,用户的注册登录权限判断等等 * 数据的返回格式根据个人而定,个人比较懒,返回的一般数据结构都是: ``` { code:状态码, msg:操作的结果, timestamp:时间戳, data:返回的数据 } ```
express的数据加密 -> AES加密 ?
//加密的模块很多,推荐使用crypto、crypto-js
//这里的加密解密是把加密向量一同发送的,每次加密的加密向量都是一段固定长度的随机字符串,所以每次请求同一个URI,返回的数据一样,但是返回的加密数据是不一样的
//加密      data为需要加密的数据
const encode = data => {
	let key = cryptojs.enc.Latin1.parse(加密的KEY),
		ivs = crypt(),                          //这只是固定长度的随机字符串
		iv = cryptojs.enc.Latin1.parse(ivs)     //加密向量
	let backdata = cryptojs.AES.encrypt(JSON.stringify(data), key, {
			iv: iv,
			mode: cryptojs.mode.CBC,
			padding: cryptojs.pad.ZeroPadding
		}).toString()
	let json = {
		data:backdata,
		iv:ivs
	}
	return json
}
//解密     
const decode = data => {
    let key = cryptojs.enc.Latin1.parse(加密的KEY),
		iv = cryptojs.enc.Latin1.parse(config.data.result.iv)   //加密向量
	let decrypted = cryptojs.AES.decrypt(config.data.result.data, key, {
		iv: iv,
		padding: cryptojs.pad.ZeroPadding
	})
	let data = config.data
	data.result.data = JSON.parse(decrypted.toString(cryptojs.enc.Utf8))
	return data
}
复制代码
路由的鉴权(可以使用session,写文件,[token](https://mp.weixin.qq.com/s/82kGtrI1QK7gkswtd-QsAQ)等等) ?
  • token一般格式为:字符串A.字符串B.字符串C,字符串A为Header(头部),字符串B为Payload(负载),字符串C为Signature(签名)

    • Header:存放用户的非敏感元信息
    • Payload:存放实际传递的数据
    • Signature:是对前两部分的签名,防止修改
  • SESSION,可以使用redis或者写文件的方式保存用户的登录方式、逾期时间等等

* 这里以Token为例子,JWT(Json Web Token)为服务器无状态token,一旦确认用户身份,就颁发token,知道token过期之前,都是有权请求资源的
* Token的加密方法很多,默认采用HMAC SHA256算法加密
const jwt = require("jsonwebtoken")
//颁发Token
const getToken = data => {      //data必须为JSON格式的对象,保存用户非敏感的元信息
    jwt.sign(data, "加密的KEY", {
		expiresIn: 60 * 60 * 24,        //token的过期时间
		algorithm: "HS256",             //token的加密方式
		subject: "webside"              //token主题     
	})      //token带的参数
}
//验证token
jwt.verify(token, "加密的KEY", (err, decoded) => {
	if(err) {
		return false
	}
	return decoded      //decoded为token前两个json的数据
})
复制代码
express路由的搭建与路由拦截鉴权 ?
  • express的路由采用由上到下的匹配模式,一旦匹配到了路由将不会继续往下个路由匹配
//设置所有路由公用的配置
app.all("*", function(req, res, next) {
    //CROS 设置跨域、请求的方式、允许携带的请求头等等
	if(req.path !== "/" && !req.path.includes(".")) {
		res.header("Access-Control-Allow-Origin", req.headers["origin"] || "*");
		res.header("Access-Control-Allow-Credentials", true);
		res.header("Access-Control-Allow-Headers", 'Content-Type,Content-Length, Authorization,\'Origin\',Accept,X-Requested-With');
		res.header("Access-Control-Allow-Methods", "POST,GET,PUT,DELETE,OPTIONS");
		res.header("Content-Type", "application/json;charset=utf-8");
	}
	next()  //路由一级一级往下传递,必须定义next才会执行传递下个路由
})
app.use("/api/eleme/catalog", require("./eleme/getCatalog"), (req, res, next) => {
	console.log("eleme => 获取商家经营分类")
})

app.use("/api/eleme/map", require("./eleme/getAddress"), (req, res, next) => {
	console.log("eleme => 获取城市地区列表")
})
//--------------
//上面路由匹配的是从/api/eleme/子路由进去
//下面路由匹配为/api/eleme => 定义相同 /api/eleme/子路由的权限鉴定
//--------------
app.use("/api/eleme", (req, res, next) => {
	let token = req.body.token || req.query.token || req.headers['x-access-token']
	if(token) {
	    //权限的鉴定
		next()      //成功才匹配/api/eleme/下的子路由
	} else {
	   //获取token失败,token的获取与存放在什么位置自定义就好
	}
})
//上面一个/api/eleme/路由开启鉴定,下面的路由必须通过上面路由的鉴权成功才能访问
app.use("/api/eleme/search", require("./eleme/getSearch"), (req, res, next) => {
	console.log("eleme => 商家搜索")
})
复制代码
  • 剩下就是各个路由路面的细节逻辑,具体业务逻辑具体操作。

最后工作交给前端了(Web的用户界面)

* 下面以Vue作为例子去演示Web的构建
Vue生命周期 ?
生命钩子函数DataDOM描述适用环境
beforeCreate未初始化未初始化loading加载效果
created初始化未初始化属性绑定DOM,不存在,$el不存在结束loading
beforeMount初始化初始化$el为元素的虚拟节点
mounted不保证组建在document中,$el被DOM替换ajax请求
beforeUpdate组件更新之前
updated组件更新之后
activated使用keep-alive,组件被激活时使用
deactivated使用keep-alive,组件被移除时使用
beforeDestory组件被销毁前调用判断组件是否应被删除
destroyed组件被销毁后调用组件移除
Vue路由的搭建 ?
//使用路由懒加载
const 页面A = resolve => require(['./../view/页面A.vue'], resolve)
const 页面B = resolve => require(['./../view/页面B.vue'], resolve)
//路有钩子          将公用的路由钩子业务扔到公用的钩子函数,也可以给组件单独设置
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!直接获取组件实例 `this`,在next中可以用参数获取间接获取实例
// 因为当钩子执行前,组件实例还没被创建
}
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。可以访问组件实例 `this`
}
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用   可以访问组件实例 `this`
}
const router = new Router({
	mode: 'history',
	routes: [{
		name: 'index',
		path: '/',
		component: ElemeView,
		meta:{          //对应路由的元信息 =>  可动态设置路由下的title、description
		    title:"首页",
		    description:"这是对首页的描述"
		}
		redirect: '/home',
		children: [{
			name: 'home',
			path: 'home',
			component: ElemeHome
		}, {
			name: 'order',
			path: 'order',
			component: ElemeOrder,
			redirect: '/order/home'
		}]
	},{
		name: 'orderInfo',
		path: '/order/:id',
		component: OrderInfo,
		meta:{          
		    title:"订单详情页",
		    description:"这是对订单详情页面的描述"
		}
	}, {
		path: '*',
		redirect: '/home'
	}]
})
export default router
//当动态对meta设置的时候
router.beforeEach((to, from, next) => {
    if (to.meta.title) {            //当有title或者其他的元信息时,可以手动动态设置(并不知道会不会对SEO有没有效)
        document.title = to.meta.title
    }
    next()      //传递下去
})
复制代码
vuex 单向数据流的模块化编写 ?
  • 当应用逐渐变的臃肿时,将状态树利用模块分开,有效管理,在调用mutations、actions时,带上模块的名字,为了防止各个模块中的状态,方法重命名,应该使用空间命名
const Store = new Vuex.Store({
	getters: {
		router: state => state.common.router,
		cartList: state => state.cart.cartList,
		cartCount: state => state.cart.cartCount,
		cartPay: state => state.cart.cartPay,
		sellerInfo: state => state.common.sellerInfo,
		sellerList: state => state.common.sellerList,
		orderList: state => state.order.orderList
	},
	mutations: {
	    getState(state){
	        console.log(state)          //state为全局state
	    }
	},
	modules: {
		common: commonStore,
		order: orderStore,
		cart: shopcartStore
	}
})
//组件中调用某模块的mutations时
this.$store.commit("order/finishOrder", e.target.dataset.id)
this.$store.commit("模块名字/该模块下的方法名字", 传过去的数据)
//组件中使用异步actions
this.$store.commit("模块名字/actions名字")
//直接调用全局的mutations或actions时,不需要加模块名便可以调用
复制代码
ajax库 -> 顺便在ajax拦截器中将后台的数据解密 ?
  • 只使用过两个,都够你用了(axios:比较好用,Fly:比较轻量可在小程序mpvue中使用)
//以axios为例子,其实都差不多的         加密解密方法上面提到,跟服务器的方法一样
axios.defaults.timeout = 10000              //请求超时时间
axios.defaults.retry = 4                    //请求失败后,继续请求,请求次数为5次

axios.interceptors.request.use((config) => {            //发送请求的拦截    
	config.headers["Content-Type"] = "application/x-www-form-urlencoded"
	//在这里加密    
	return config
})

axios.interceptors.response.use((res) => {              //响应请求的拦截
	if(res.status === 654) {
		window.alert('请求超时!')
	}
	if(res.data.code < 200 || res.data.result > 300) {
		window.alert('数据返回有误')
		return Promise.reject(res)
	}
	//在这里解密 -> 直接将解密的数据返回,其余的无关字段可以不用管
}, (error) => {             //失败的后续操作
	let config = error.config
	if(!config || !config.retry) return Promise.reject(error)
	config.__retryCount = config.__retryCount || 0

	if(config.__retryCount >= config.retry) {
		return Promise.reject(error)
	}
	config.__retryCount += 1
	let backoff = new Promise(function(resolve) {
		setTimeout(function() {
			resolve()
		}, config.retryDelay || 1)
	})
	return backoff.then(function() {
		return axios(config)
	})
})

export default axios
复制代码
一些比较好看的vue组件 ?
  • vuePutTo:可以实现上拉加载,下拉刷新的炫酷操作,无意中发小的,组件还挺小的。
  • vue-awesome-swiper:简直是vue版的swiper,有点大了,其接口与swiper一致
  • vue-msgbox:弹框
  • 如果是使用大型的组件库的时候,可以使用babel中的plugins对组件库进行按需加载
总结
  • 以上的操作后,放上Online Demo,侵删。
  • 献上我刚毕业时弄的个人网站:Billson
  • 上面的一些教程,对应届毕业生来说应该挺简单的了,应该总比拿着学校的项目去面试要好吧,如果你是比较牛的可以考虑写成SSR,这样子对SEO很友好,不管技术上还是使用上,有比这个SPA更高大上一些,肯定会在面试多加积分。

转载于:https://juejin.im/post/5b5ac88ff265da0f8c02c50d

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值