一、項(xiàng)目介紹
課程內(nèi)容項(xiàng)目頁面說明
二、創(chuàng)建項(xiàng)目、后端環(huán)境介紹
- 創(chuàng)建項(xiàng)目
- 打開HBuilderX -> 新建 uniapp 項(xiàng)目;
- 創(chuàng)建頁面
|_ index.vue 入口頁面
|_ write.vue 文章撰寫頁面
|_ my.vue 賬戶中心頁面
- 后端使用php+mysql
完善底部導(dǎo)航欄
- 下載圖標(biāo), 圖標(biāo)地址 :
https://pan.baidu.com/s/1iaWd6l_-cIJ3RNUsqNqFNQ - 將圖標(biāo)部署至 /static 目錄;
- 修改 page.json
"tabBar" : {
"color" : "#707070",
"selectedColor" : "#DE533A",
"list" : [
{
"pagePath" : "pages/index/index",
"text" : "文章",
"iconPath" : "static/nav1.png",
"selectedIconPath" : "static/nav1-a.png"
},
{
"pagePath" : "pages/write/write",
"text" : "寫作",
"iconPath" : "static/nav2.png",
"selectedIconPath" : "static/nav2-a.png"
},
{
"pagePath" : "pages/my/my",
"text" : "我的",
"iconPath" : "static/nav3.png",
"selectedIconPath" : "static/nav3-a.png"
}
]
}
- 修改項(xiàng)目名稱
"globalStyle" : {
"navigationBarTextStyle" : "black",
"navigationBarTitleText" : "悅讀",
"navigationBarBackgroundColor" : "#F8F8F8",
"backgroundColor" : "#F8F8F8"
}
5.加載 colorUI 框架
https://github.com/weilanwl/ColorUI
三、封裝全局登錄檢查函數(shù)并部署
1、在 main.js 中封裝全局登錄函數(shù)
Vue.prototype.checkLogin = function(backpage, backtype){
var SUID = uni.getStorageSync('SUID');
var SRAND = uni.getStorageSync('SRAND');
var SNAME = uni.getStorageSync('SNAME');
var SFACE = uni.getStorageSync('SFACE');
if(SUID == '' || SRAND == '' || SFACE == ''){
uni.redirectTo({url:'../login/login?backpage='+backpage+'&backtype='+backtype});
return false;
}
return [SUID, SRAND, SNAME, SFACE];
}
參數(shù)說明
backpage, backtype 2個(gè)參數(shù)分別代表:
backpage : 登錄后返回的頁面
backtype : 打開頁面的類型[1 : redirectTo 2 : switchTab]
返回值說明
已經(jīng)登錄返回?cái)?shù)組 [用戶 id, 用戶隨機(jī)碼, 用戶昵稱, 用戶表情]
2、創(chuàng)建 login 頁面
login 頁面作為登錄過度頁面,多端登錄都通過此頁面完成!
3、在頁面中應(yīng)用登錄檢查函數(shù),如 write.vue
<script>
export default {
data() {
return {
};
},
onLoad : function() {
var loginRes = this.checkLogin('../my/my', '2');
if(!loginRes){return false;}
}
}
</script>
return 或終止函數(shù)運(yùn)行哦!
四、UNI-APP端使用微信登錄原理和條件編譯
<script>
export default {
data() {
return {
};
},
onLoad:function(){
//app 端微信登錄
// 手冊(cè)位置 https://uniapp.dcloud.io/api/plugins/login?id=getuserinfo
// #ifdef APP-PLUS
uni.login({
success: (res) => {
// res 對(duì)象格式
//{"code":"***",
//"authResult":{
//"openid":"***",
//"scope":"snsapi_userinfo",
//"refresh_token":"**",
//"code":"****",
//"unionid":"***",
//"access_token":"***",
//"expires_in":7200
//},
//"errMsg":"login:ok"}
uni.getUserInfo({
success: (info) => {
// info 對(duì)象格式
// {"errMsg":"getUserInfo:ok",
// "rawData":"...
// "userInfo":{
//"openId":"***",
//"nickName":"***",
//"gender":1,
// "city":"Xi'an",
// "province":"Shaanxi",
// "country":"China",
// "avatarUrl":"頭像",
// "unionId":"oU5Yyt_agt547zWyA0v0eLfFPqxo"
//},"signature":""}
// 與服務(wù)器交互將數(shù)據(jù)提交到服務(wù)端數(shù)據(jù)庫
},
fail: () => {
uni.showToast({title:"微信登錄授權(quán)失敗"});
}
})
},
fail: () => {
uni.showToast({title:"微信登錄授權(quán)失敗"});
}
})
// #endif
}
}
</script>
四、部署php環(huán)境
1、集成環(huán)境 phpstudy 安裝
phpstudy 下載地址
http://phpstudy.php.cn/
修改時(shí)間php.ini 配置
date.timezone = PRC //中國時(shí)區(qū)
2、固定內(nèi)網(wǎng)ip
開始 > 設(shè)置 > 網(wǎng)絡(luò)和internet > 以太網(wǎng) > 更改適配器屬性 在以太網(wǎng)圖標(biāo) > 右鍵 > 狀態(tài) > 詳細(xì)信息
注意 : localhost 代表本機(jī),手機(jī)中 localhost 代表手機(jī)自己并不能代表內(nèi)網(wǎng)服務(wù)器!
獲取內(nèi)網(wǎng)ip規(guī)則,如 : 192.168.1.***。
修改ipv4地址,固定內(nèi)網(wǎng)ip:
開始 > 設(shè)置 > 網(wǎng)絡(luò)和internet > 以太網(wǎng) > 更改適配器屬性 在以太網(wǎng)圖標(biāo) > 右鍵 > 屬性 > ipv4 地址 > 點(diǎn)擊修改,如 : 192.168.1.188
修改后測試網(wǎng)絡(luò)暢通即可!
瀏覽器訪問 http://192.168.1.188/ 即可直接訪問剛剛搭建的php環(huán)境 ;
3、部署基礎(chǔ)php 代碼
我們?yōu)榇蠹揖臏?zhǔn)備了一個(gè)極小的基礎(chǔ)開發(fā)庫,請(qǐng)大家在課程內(nèi)下載并部署至 php 環(huán)境的根目錄;
4、基礎(chǔ)開發(fā)庫原理詳解
路由解析例子
<?php
namespace hsC;
class index{
public function index(){
echo 'index';
}
public function test(){
echo 'test';
}
}
//定義路由輸出
[http://192.168.199.166/index.php?token=api2018&c=member&m=t](http://192.168.199.166/index.php?token=api2018&c=member&m=t)
$res = array('name' => 'hcoder');
echo jsonCode('ok', $res);
//輸出字符串 {"status":"ok","data":{"name":"hcoder"}}
//創(chuàng)建test類 getUser函數(shù)
<?php
namespace hsModel;
class test{
public function getUser(){
echo 'get user ...';
}
}
//創(chuàng)建新類名,調(diào)用getUser函數(shù)
$mTest = new \hsModel\test();
$mTest -> getUser();
最后打開phpStudy》phpMyAdmin 默認(rèn)用戶密碼root
創(chuàng)建名字xxx的數(shù)據(jù)庫
五、創(chuàng)建用戶數(shù)據(jù)表,完成app端用戶登錄功能
1、創(chuàng)建項(xiàng)目數(shù)據(jù)庫
1.1 打開 phpMyadmin 或者其他 mysql 管理工具,phpstudy 環(huán)境下 mysql 賬戶 root 密碼 root;
1.2 創(chuàng)建數(shù)據(jù)庫 "yuedu"
CREATE TABLE `yuedu_members` (
`u_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用戶id',
`u_openid` varchar(100) NOT NULL COMMENT 'openid',
`u_name` varchar(50) NOT NULL COMMENT '用戶昵稱',
`u_face` varchar(200) NOT NULL COMMENT '用戶頭像',
`u_random` varchar(30) NOT NULL COMMENT '用戶隨機(jī)碼',
`u_integral` int(10) DEFAULT '0' COMMENT '積分',
`u_remainder` int(10) DEFAULT '0' COMMENT '余額',
`u_regtime` int(11) NOT NULL COMMENT '用戶注冊(cè)時(shí)間',
PRIMARY KEY (`u_id`),
UNIQUE KEY `u_openid` (`u_openid`),
UNIQUE KEY `u_id` (`u_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
2、phpMyAdmin 錯(cuò)誤修正
Fatal error: Cannot 'break' 2 levels in D:\phpStudy\PHPTutorial\WWW\phpMyAdmin\export.php on line 590
590 行把 break 2 改成 break;
3、配置數(shù)據(jù)庫
打開 index.php 修改以下配置
/* 數(shù)據(jù)庫配置 */
define('HS_DB_HOST' , '127.0.0.1'); // mysql 服務(wù)器地址
define('HS_DB_NAME' , 'yuedu'); // 數(shù)據(jù)庫名稱
define('HS_DB_USER' , 'root'); // 數(shù)據(jù)庫賬號(hào)
define('HS_DB_PWD' , 'root'); // 數(shù)據(jù)庫密碼
define('HS_DB_PRE' , 'yuedu_'); // 數(shù)據(jù)表統(tǒng)一前綴
define('HS_DB_CHARSET' , 'utf8mb4'); // mysql 字符集類型
4、創(chuàng)建 member 控制器,編寫以下代碼:
<?php
namespace hsC;
class member{
public function index(){
}
public function login(){
//調(diào)用模型完成用戶登錄及注冊(cè)
$memberModel = new \hsModel\member();
$memberModel->login();
}
}
// 原理見視頻教程
5、在 app 端定義全局變量,定義全局的 api 接口地址及token
var APITOKEN = 'api2018';
Vue.prototype.apiServer = 'http://192.168.1.188/index.php?token='+APITOKEN+'&c=member&m=login';
為什么這樣定義? 便于后期修改!
- 如果手機(jī)端無法訪問局域網(wǎng)ip,如何解決?
方案1、使用花生殼軟件 > 內(nèi)網(wǎng)穿透(功能收費(fèi)6元)
方案2、使用模擬器調(diào)試,模擬器下載地址 :
方案3、將后端api 部署到公網(wǎng)服務(wù)器上
6、login.vue 與api交互完成app端登錄功能
<template>
<view>
</view>
</template>
<script>
var _self;
export default {
data() {
return {
};
},
onLoad:function(options){
_self = this;
//app 端微信登錄
// 手冊(cè)位置 https://uniapp.dcloud.io/api/plugins/login?id=getuserinfo
// #ifdef APP-PLUS
uni.login({
success: (res) => {
// res 對(duì)象格式
//{"code":"***",
//"authResult":{
//"openid":"***",
//"scope":"snsapi_userinfo",
//"refresh_token":"**",
//"code":"****",
//"unionid":"***",
//"access_token":"***",
//"expires_in":7200
//},
//"errMsg":"login:ok"}
uni.getUserInfo({
success: (info) => {
// info 對(duì)象格式
// {"errMsg":"getUserInfo:ok",
// "rawData":"...
// "userInfo":{
//"openId":"***",
//"nickName":"***",
//"gender":1,
// "city":"Xi'an",
// "province":"Shaanxi",
// "country":"China",
// "avatarUrl":"頭像",
// "unionId":"oU5Yyt_agt547zWyA0v0eLfFPqxo"
//},"signature":""}
// 與服務(wù)器交互將數(shù)據(jù)提交到服務(wù)端數(shù)據(jù)庫
uni.request({
url: _self.apiServer+'member&m=login',
method: 'POST',
header: {'content-type' : "application/x-www-form-urlencoded"},
data: {
openid : info.userInfo.openId,
name : info.userInfo.nickName,
face : info.userInfo.avatarUrl,
},
success: res => {
console.log(JSON.stringify(res));
res = res.data;
// 登錄成功 記錄會(huì)員信息到本地
if(res.status == 'ok'){
uni.showToast({title:"登錄成功"});
uni.setStorageSync('SUID' , res.data.u_id + '');
uni.setStorageSync('SRAND', res.data.u_random + '');
uni.setStorageSync('SNAME', res.data.u_name + '');
uni.setStorageSync('SFACE', res.data.u_face + '');
// 跳轉(zhuǎn)
if(options.backtype == 1){
uni.redirectTo({url:options.backpage});
}else{
uni.switchTab({url:options.backpage});
}
}else{
uni.showToast({title:res.data});
}
},
fail: (e) => {
console.log(JSON.stringify(e));
}
});
},
fail: () => {
uni.showToast({title:"微信登錄授權(quán)失敗"});
}
})
},
fail: () => {
uni.showToast({title:"微信登錄授權(quán)失敗"});
uni.hideLoading();
}
})
// #endif
}
}
</script>
<style>
</style>
六、完成小程序端登錄
1、在uniapp中配置小程序appid
打開 manifest.json ,找到微信小程序配置,填寫 appid 重啟應(yīng)用;
// 您需要注冊(cè)成為微信小程序開發(fā)者才能獲取 appid
2、視圖添加登錄按鈕
<template>
<view>
<!-- #ifdef MP-WEIXIN -->
<button type="primary" open-type="getUserInfo" @getuserinfo="getUserInfo">使用微信登錄</button>
<!-- #endif -->
</view>
</template>
3、js 核心代碼
<script>
var _self, pageOptions, session_key, openid;
export default {
data() {
return {
};
},
methods:{
// #ifdef MP-WEIXIN
getUserInfo : (info) => {
info = info.mp.detail.userInfo;
//userInfo {"nickName":"深海","gender":1,...avatarUrl":"https://7tdPvkPaJlkaLFFbLAffGVApluZdanLkDVplNlAhq1EJA/132"}
// 與服務(wù)器交互將數(shù)據(jù)提交到服務(wù)端數(shù)據(jù)庫
uni.request({
url: _self.apiServer+'member&m=login',
method: 'POST',
header: {'content-type' : "application/x-www-form-urlencoded"},
data: {
openid : openid,
name : info.nickName,
face : info.avatarUrl,
},
success: res => {
console.log(res);
res = res.data;
// 登錄成功 記錄會(huì)員信息到本地
if(res.status == 'ok'){
uni.showToast({title:"登錄成功"});
uni.setStorageSync('SUID' , res.data.u_id + '');
uni.setStorageSync('SRAND', res.data.u_random + '');
uni.setStorageSync('SNAME', res.data.u_name + '');
uni.setStorageSync('SFACE', res.data.u_face + '');
// 跳轉(zhuǎn)
if(pageOptions.backtype == 1){
uni.redirectTo({url:pageOptions.backpage});
}else{
uni.switchTab({url:pageOptions.backpage});
}
}else{
uni.showToast({title:res.data});
}
},
fail: (e) => {
console.log(JSON.stringify(e));
}
});
},
// #endif
},
onLoad:function(options){
_self = this;
pageOptions = options;
// #ifdef MP-WEIXIN
// 調(diào)用 微信 login 獲取 code
uni.login({
success: (res) => {
uni.request({
url:_self.apiServer+'member&m=codeToSession&code='+res.code,
success: (sessions) => {
// sessions.date 數(shù)據(jù)格式
// expires_in:7200
// openid:"oS6of0V0rdp9nY_BuvCnQUasOHYc"
// session_key:"87sE2oDD8lc+aDJj0tB6+g=="
// 獲取 unionId
session_key = sessions.data.session_key;
openid = sessions.data.openid;
},
});
}
});
// #endif
//app 端微信登錄
// 手冊(cè)位置 https://uniapp.dcloud.io/api/plugins/login?id=getuserinfo
// #ifdef APP-PLUS
uni.login({
success: (res) => {
// res 對(duì)象格式
//{"code":"***",
//"authResult":{
//"openid":"***",
//"scope":"snsapi_userinfo",
//"refresh_token":"**",
//"code":"****",
//"unionid":"***",
//"access_token":"***",
//"expires_in":7200
//},
//"errMsg":"login:ok"}
uni.getUserInfo({
success: (info) => {
// info 對(duì)象格式
// {"errMsg":"getUserInfo:ok",
// "rawData":"...
// "userInfo":{
//"openId":"***",
//"nickName":"***",
//"gender":1,
// "city":"Xi'an",
// "province":"Shaanxi",
// "country":"China",
// "avatarUrl":"頭像",
// "unionId":"oU5Yyt_agt547zWyA0v0eLfFPqxo"
//},"signature":""}
// 與服務(wù)器交互將數(shù)據(jù)提交到服務(wù)端數(shù)據(jù)庫
uni.request({
url: _self.apiServer+'member&m=login',
method: 'POST',
header: {'content-type' : "application/x-www-form-urlencoded"},
data: {
openid : info.userInfo.openId,
name : info.userInfo.nickName,
face : info.userInfo.avatarUrl,
},
success: res => {
console.log(JSON.stringify(res));
res = res.data;
// 登錄成功 記錄會(huì)員信息到本地
if(res.status == 'ok'){
uni.showToast({title:"登錄成功"});
uni.setStorageSync('SUID' , res.data.u_id + '');
uni.setStorageSync('SRAND', res.data.u_random + '');
uni.setStorageSync('SNAME', res.data.u_name + '');
uni.setStorageSync('SFACE', res.data.u_face + '');
// 跳轉(zhuǎn)
if(options.backtype == 1){
uni.redirectTo({url:options.backpage});
}else{
uni.switchTab({url:options.backpage});
}
}else{
uni.showToast({title:res.data});
}
},
fail: (e) => {
console.log(JSON.stringify(e));
}
});
},
fail: () => {
uni.showToast({title:"微信登錄授權(quán)失敗"});
}
})
},
fail: () => {
uni.showToast({title:"微信登錄授權(quán)失敗"});
uni.hideLoading();
}
})
// #endif
}
}
</script>
<style></style>
php 后端代碼
<?php
namespace hsC;
class member{
public function index(){
}
public function login(){
//調(diào)用模型完成用戶登錄及注冊(cè)
$memberModel = new \hsModel\member();
$memberModel->login();
}
public function codeToSession(){
if(empty($_GET['code'])){exit(jsonCode('error', 'code error'));}
$url = "https://api.weixin.qq.com/sns/jscode2session?appid=".HS_APPID.
"&secret=".HS_SECRET."&js_code=".$_GET['code']."&grant_type=authorization_code";
$curl = new \hsTool\curl();
$res = $curl->get($url);
echo $res;
}
}
七、多平臺(tái)多應(yīng)用 統(tǒng)一登錄微信關(guān)系
多平臺(tái)統(tǒng)一登錄之 unionID
通過獲取用戶基本信息接口,開發(fā)者可通過OpenID來獲取用戶基本信息,而如果開發(fā)者擁有多個(gè)應(yīng)用,可使用以下辦法通過UnionID機(jī)制來在多個(gè)應(yīng)用進(jìn)行用戶帳號(hào)互通。
只要是同一個(gè)微信開放平臺(tái)帳號(hào)下的公眾號(hào),用戶的UnionID是唯一的。
換句話說,同一用戶,對(duì)同一個(gè)微信開放平臺(tái)帳號(hào)下的不同應(yīng)用,UnionID是相同的。
此前的OpenID機(jī)制,每個(gè)微信號(hào)對(duì)應(yīng)每個(gè)應(yīng)用有唯一的OpenID,所以不同應(yīng)用之間是不能共享用戶的,現(xiàn)在有了UnionID就可以了。
APP端獲取 unionID
使用 uni.login 即可;
小程序端獲取 unionID
步驟 :
1、配置小程序 appid (此appid 在微信開放平臺(tái)已經(jīng)綁定);
2、使用 uni.login 登錄時(shí)會(huì)獲取 code,用 code 換取 seesion_key;
3、在獲取用戶信息函數(shù)中獲取到加密信息;
4、利用 seesion_key 及加密信息在服務(wù)端解密獲取 unionID
php 后端注意事項(xiàng)
需要開啟 php_openssl 擴(kuò)展
前端實(shí)現(xiàn)過程代碼
export default {
data() {
return {
};
},
methods:{
// #ifdef MP-WEIXIN
getUserInfo : (info) => {
//加密數(shù)據(jù)
var encryptedData = info.mp.detail.encryptedData;
var iv = info.mp.detail.iv;
info = info.mp.detail.userInfo;
//info
//userInfo {"nickName":"深海","gender":1,...avatarUrl":"https://7tdPvkPaJlkaLFFbLAffGVApluZdanLkDVplNlAhq1EJA/132"}
//與服務(wù)器交互進(jìn)行解密
uni.request({
url: _self.apiServer+'member&m=wxaes',
method: 'POST',
header: {'content-type' : "application/x-www-form-urlencoded"},
data: {
session_key : session_key,
encryptedData : encryptedData,
iv : iv
},
success: res => {
console.log(res);
//此處可以成功獲取 unionId 利用 unionId 完成登錄即可
},
fail: () => {},
complete: () => {}
});
}
},
onLoad:function(options){
_self = this;
pageOptions = options;
// #ifdef MP-WEIXIN
// 調(diào)用 微信 login 獲取 code
uni.login({
success: (res) => {
uni.request({
url:_self.apiServer+'member&m=codeToSession&code='+res.code,
success: (sessions) => {
session_key = sessions.data.session_key;
}
}
}
});
}
});
// #endif
}
php 后端代碼
<?php
namespace hsC;
class member{
//......
public function wxaes(){
if(empty($_POST['session_key']) || empty($_POST['encryptedData']) || empty($_POST['iv'])){exit(jsonCode('error', 'data error'));}
include HS_TOOLS.'WXBizDataCrypt.php';
$pc = new \WXBizDataCrypt(HS_APPID, $_POST['session_key']);
$data = '';
$errCode = $pc->decryptData($_POST['encryptedData'], $_POST['iv'], $data);
if ($errCode == 0) {
exit($data);
} else {
exit(jsonCode('error', $errCode));
}
}
}
八、api接口安全策略 - 簽名策略
安全概述
前面章節(jié)講解的接口是裸露的、不安全的!使用post、get模擬可以輕松對(duì)api進(jìn)行請(qǐng)求,最簡單的攻擊就可以瞬間完成近萬會(huì)員的注冊(cè)!
所以在進(jìn)行api接口通訊的同時(shí)我們應(yīng)該進(jìn)行數(shù)據(jù)的驗(yàn)證工作!
加密原理及流程
1、從服務(wù)器端獲取一個(gè)唯一性的token,我們稱之為 accessToken;
2、前端對(duì)accessToken進(jìn)行隨機(jī)性拆分及md5加密,產(chǎn)生簽名(保存在本地存儲(chǔ)中);
3、前端在與后端進(jìn)行交互時(shí)傳遞簽名;
4、后端接收數(shù)據(jù)是驗(yàn)證簽名;
簽名準(zhǔn)備
1、在 commons 文件夾內(nèi)創(chuàng)建
1.1 md5.js //js md5 加密 [ 在課程內(nèi)獲取此 js文件 ]
1.2 sign.js // 簽名函數(shù)
sign.js
var md5 = require('./md5.js');
module.exports = {
sign : function(apiServer){
// 環(huán)境判斷非uni環(huán)境不支持
if(!uni){return '...';}
// 連接服務(wù)器獲取一個(gè)臨時(shí)的accessToken
uni.request({
url: apiServer+'getAccessToken',
method: 'GET',
success: res => {
if(res.data.status != 'ok'){return ;}
var data = res.data.data;
// 對(duì) accessToken 進(jìn)行md5加密
var accessToken = md5.hex_md5(data.token + data.time);
// 簽名 = md5(accessToekn + time) + '-' + 'accessToekn';
var sign = accessToken + '-' + data.token;
//console.log(sign);
// 記錄在本地
uni.setStorage({
key:"sign",
data:sign
});
}
});
}
}
數(shù)據(jù)庫
DROP TABLE IF EXISTS `yuedu_access_tokens`;
CREATE TABLE `yuedu_access_tokens` (
`token` varchar(30) NOT NULL,
`time` int(11) DEFAULT NULL,
PRIMARY KEY (`token`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
php 端代碼
<?php
//getAccessToken.php
namespace hsC;
class getAccessToken{
public function index(){
$db = \hsTool\db::getInstance('access_tokens');
$token = array(
'token' => uniqid(),
'time' => time()
);
$db->add($token);
exit(jsonCode('ok', $token));
}
}
使用說明
在數(shù)據(jù)提交頁面提交之前進(jìn)行預(yù)簽名,提交數(shù)據(jù)時(shí)攜帶此簽名!
更合理的簽名保存
因?yàn)榇蠹液蠖嘶A(chǔ)不一樣,本課程使用數(shù)據(jù)庫保存了accessToken,更好的方式是 redis 或 memcache,可以設(shè)置變量有效期并能自動(dòng)失效!
九、在登錄環(huán)節(jié)使用簽名驗(yàn)證策略
后端驗(yàn)證簽名原理
// 前端使用簽名驗(yàn)證
<script>
var sign= require('../../commons/sign.js');//引入簽名文件
……
onLoad:function(options){
sign.sign(this.apiServer); //使用生成簽名
……
var sign = uni.getStorageSync('sign');//獲取簽名
……
sign : sign, // 提交簽名
function checkSign(){
if(empty($_POST['sign'])){exit(jsonCode('error', 'sign error'));}
$sign = explode('-', $_POST['sign']);
if(count($sign) != 2){exit(jsonCode('error', 'sign error'));}
$db = \hsTool\db::getInstance('access_tokens');
$token = $db->where('token = ?', array($sign[1]))->fetch();
if(empty($token)){exit(jsonCode('error', 'sign error'));}
$signMd5 = md5($token['token'].$token['time']);
if($signMd5 != $sign[0]){exit(jsonCode('error', 'sign error'));}
// 驗(yàn)證成功則刪除
$db->where('token = ?', array($sign[1]))->delete();
}
登錄頁面簽名機(jī)制改進(jìn)
<template>
<view>
<!-- #ifdef MP-WEIXIN -->
<button type="primary" open-type="getUserInfo" @getuserinfo="getUserInfo">使用微信登錄</button>
<!-- #endif -->
</view>
</template>
<script>
var _self, pageOptions, session_key, openid;
var sign = require('../../commons/sign.js');
export default {
data() {
return {
};
},
methods:{
// #ifdef MP-WEIXIN
getUserInfo : (info) => {
info = info.mp.detail.userInfo;
//userInfo {"nickName":"深海","gender":1,...avatarUrl":"https://7tdPvkPaJlkaLFFbLAffGVApluZdanLkDVplNlAhq1EJA/132"}
// 與服務(wù)器交互將數(shù)據(jù)提交到服務(wù)端數(shù)據(jù)庫
var sign = uni.getStorageSync('sign');
uni.request({
url: _self.apiServer+'member&m=login',
method: 'POST',
header: {'content-type' : "application/x-www-form-urlencoded"},
data: {
openid : openid,
name : info.nickName,
face : info.avatarUrl,
sign : sign
},
success: res => {
console.log(res);
res = res.data;
// 登錄成功 記錄會(huì)員信息到本地
if(res.status == 'ok'){
uni.showToast({title:"登錄成功"});
uni.setStorageSync('SUID' , res.data.u_id + '');
uni.setStorageSync('SRAND', res.data.u_random + '');
uni.setStorageSync('SNAME', res.data.u_name + '');
uni.setStorageSync('SFACE', res.data.u_face + '');
// 跳轉(zhuǎn)
if(pageOptions.backtype == 1){
uni.redirectTo({url:pageOptions.backpage});
}else{
uni.switchTab({url:pageOptions.backpage});
}
}else{
uni.showToast({title:res.data});
}
},
fail: (e) => {
console.log(JSON.stringify(e));
}
});
},
// #endif
},
onLoad:function(options){
// 預(yù)先簽名
sign.sign(this.apiServer);
_self = this;
pageOptions = options;
// #ifdef MP-WEIXIN
// 調(diào)用 微信 login 獲取 code
uni.login({
success: (res) => {
uni.request({
url:_self.apiServer+'member&m=codeToSession&code='+res.code,
success: (sessions) => {
// sessions.date 數(shù)據(jù)格式
// expires_in:7200
// openid:"oS6of0V0rdp9nY_BuvCnQUasOHYc"
// session_key:"87sE2oDD8lc+aDJj0tB6+g=="
// 獲取 unionId
session_key = sessions.data.session_key;
openid = sessions.data.openid;
},
});
}
});
// #endif
//app 端微信登錄
// 手冊(cè)位置 https://uniapp.dcloud.io/api/plugins/login?id=getuserinfo
// #ifdef APP-PLUS
uni.login({
success: (res) => {
// res 對(duì)象格式
//{"code":"***",
//"authResult":{
//"openid":"***",
//"scope":"snsapi_userinfo",
//"refresh_token":"**",
//"code":"****",
//"unionid":"***",
//"access_token":"***",
//"expires_in":7200
//},
//"errMsg":"login:ok"}
uni.getUserInfo({
success: (info) => {
// info 對(duì)象格式
// {"errMsg":"getUserInfo:ok",
// "rawData":"...
// "userInfo":{
//"openId":"***",
//"nickName":"***",
//"gender":1,
// "city":"Xi'an",
// "province":"Shaanxi",
// "country":"China",
// "avatarUrl":"頭像",
// "unionId":"oU5Yyt_agt547zWyA0v0eLfFPqxo"
//},"signature":""}
// 與服務(wù)器交互將數(shù)據(jù)提交到服務(wù)端數(shù)據(jù)庫
var sign = uni.getStorageSync('sign');
uni.request({
url: _self.apiServer+'member&m=login',
method: 'POST',
header: {'content-type' : "application/x-www-form-urlencoded"},
data: {
openid : info.userInfo.openId,
uni.setStorageSync('SRAND', res.data.u_random + '');
uni.setStorageSync('SNAME', res.data.u_name + '');
face : info.userInfo.avatarUrl,
sign : sign
},
success: res => {
console.log(JSON.stringify(res));
res = res.data;
// 登錄成功 記錄會(huì)員信息到本地
if(res.status == 'ok'){
uni.showToast({title:"登錄成功"});
uni.setStorageSync('SUID' , res.data.u_id + '');
uni.setStorageSync('SRAND', res.data.u_name + '');
uni.setStorageSync('SFACE', res.data.u_face + '');
// 跳轉(zhuǎn)
if(options.backtype == 1){
uni.redirectTo({url:options.backpage});
}else{
uni.switchTab({url:options.backpage});
}
}else{
uni.showToast({title:res.data});
}
},
fail: (e) => {
console.log(JSON.stringify(e));
}
});
},
fail: () => {
uni.showToast({title:"微信登錄授權(quán)失敗"});
}
})
},
fail: () => {
uni.showToast({title:"微信登錄授權(quán)失敗"});
uni.hideLoading();
}
})
// #endif
}
}
</script>
<style></style>
十、完成寫作頁面布局
頁面功能
1、基礎(chǔ)布局
2、文章內(nèi)容展示
3、添加圖片
4、添加文本
5、動(dòng)態(tài)展示提交按鈕
完整代碼
<template>
<view class="wrap">
<view class="write_title">
<input type="text" v-model="title" placeholder="請(qǐng)輸入標(biāo)題" />
</view>
<!-- 內(nèi)容展示區(qū) -->
<view class="artList">
<block v-for="(item, index) in artList" :key="index">
<view class="item" v-if="item.type == 'image'"><image :src="item.content" :data-index="index" mode="widthFix" @tap="removeImg" /></view>
<view class="item" v-if="item.type == 'text'">
<textarea :value="item.content" placeholder="" v-model="artList[index].content" />
<view :data-index="index" class="deleteText" @tap="deleteText">刪除</view>
</view>
</block>
</view>
<!-- 輸入?yún)^(qū) -->
<form @submit="submit">
<view class="inputArea">
<view class="addImg" @tap="addImg">+圖片</view>
<view class="addText">
<textarea name="artText" maxlength="-1" v-model="inputContent" placeholder="請(qǐng)輸入文本" />
<button type="primary" form-type="submit">添加</button>
</view>
</view>
</form>
<!-- 選擇分類 -->
<view class="art-cate">
<view>文章分類</view>
<view class="">
<picker mode="selector" :range="caties" @change="cateChange">
<view>{{caties[currentCateIndex]}}</view>
</picker>
</view>
</view>
<!-- 提交按鈕 -->
<view class="submitNow" v-if="artList.length > 0" @tap="submitNow">發(fā)布文章</view>
</view>
</template>
<script>
var _self, loginRes;
var signModel = require('../../commons/sign.js');
export default {
data() {
return {
title : '',
artList : [],
inputContent : "",
needUploadImg : [],
uploadIndex : 0,
//分類
caties : ['點(diǎn)擊選擇'],
currentCateIndex : 0,
catiesFromApi : [],
// 記錄真實(shí)選擇的api接口數(shù)據(jù)的分類id
sedCateIndex : 0
};
},
onLoad : function() {
_self = this;
signModel.sign(this.apiServer);
loginRes = this.checkLogin('../write/write', '2');
if(!loginRes){return false;}
// 加載文章分類
uni.request({
url: this.apiServer+'category&m=index',
method: 'GET',
success: res => {
if(res.data.status == 'ok'){
// 把數(shù)據(jù)格式整理為 picker 支持的格式 ['分類名', ...]
var categories = res.data.data;
for(var k in categories){
_self.caties.push(categories[k]);
}
// 記錄分類信息
_self.catiesFromApi = categories;
}
}
});
},
methods:{
cateChange : function(e){
var sedIndex = e.detail.value;
this.currentCateIndex = sedIndex;
// 獲取選擇的分類名稱
if(sedIndex < 1){return ;}
var cateName = this.caties[sedIndex];
for(var k in this.catiesFromApi){
if(cateName == this.catiesFromApi[k]){
this.sedCateIndex = k;
break;
}
}
this.currentCateIndex = sedIndex;
},
removeImg : function(e){
var index = e.currentTarget.dataset.index;
uni.showModal({
content:"確定要?jiǎng)h除此圖片嗎",
title:'提示',
success(e) {
if (e.confirm) {
_self.artList.splice(index, 1);
}
}
});
},
deleteText : function(e){
var index = e.currentTarget.dataset.index;
uni.showModal({
content:"確定要?jiǎng)h除嗎",
title:'提示',
success(e) {
if (e.confirm) {
_self.artList.splice(index, 1);
}
}
});
},
submitNow : function(){
// 數(shù)據(jù)驗(yàn)證
if(this.title.length < 2){uni.showToast({title:'請(qǐng)輸入標(biāo)題', icon:"none"}); return ;}
if(this.artList.length < 1){uni.showToast({title:'請(qǐng)?zhí)顚懳恼聝?nèi)容', icon:"none"}); return ;}
if(this.sedCateIndex < 1){uni.showToast({title:'請(qǐng)選擇分類', icon:"none"}); return ;}
// 上傳圖片 一次一個(gè)多次上傳 [ 遞歸函數(shù) ]
// 上傳完成后整體提交數(shù)據(jù)
// 首先整理一下需要上傳的圖片
// this.needUploadImg = [{tmpurl : 臨時(shí)地址, index : 數(shù)據(jù)索引}]
this.needUploadImg = [];
for(var i = 0; i < this.artList.length; i++){
if(this.artList[i].type == 'image'){
this.needUploadImg.push({"tmpurl" : this.artList[i].content , "indexID" : i});
}
}
this.uploadImg();
},
uploadImg : function(){
// 如果沒有圖片 或者已經(jīng)上傳完成 則執(zhí)行提交
if(this.needUploadImg.length < 1 || this.uploadIndex >= this.needUploadImg.length){
uni.showLoading({title:"正在提交"});
// 將信息整合后提交到服務(wù)器
var sign = uni.getStorageSync('sign');
uni.request({
url: this.apiServer + 'art&m=add',
method: 'POST',
header: {'content-type' : "application/x-www-form-urlencoded"},
data: {
title : _self.title,
content : JSON.stringify(_self.artList),
uid : loginRes[0],
random : loginRes[1],
cate : _self.sedCateIndex,
sign : sign
},
success: res => {
console.log(res);
if(res.data.status == 'ok'){
uni.showToast({title:"提交成功", icon:"none"});
_self.artList = [];
// 清空數(shù)據(jù)
signModel.sign(_self.apiServer);
// 防止數(shù)據(jù)緩存
_self.currentCateIndex = 0;
_self.sedCateIndex = 0;
_self.needUploadImg = [];
_self.title = '';
setTimeout(function(){
uni.switchTab({
url:'../my/my'
})
}, 1000);
}else{
uni.showToast({title:res.data.data, icon:"none"});
}
},
fail: (res) => {
},
complete: () => {
}
});
return ;
}
// 上傳圖片
uni.showLoading({title:"上傳圖片"});
var uploader = uni.uploadFile({
url : _self.apiServer+'uploadImg&m=index',
filePath : _self.needUploadImg[_self.uploadIndex].tmpurl,
name : 'file',
success: (uploadFileRes) => {
uploadFileRes = JSON.parse(uploadFileRes.data);
if(uploadFileRes.status != 'ok'){
console.log(uploadFileRes);
uni.showToast({title:"上傳圖片失敗,請(qǐng)重試!", icon:"none"});
return false;
}
// 將已經(jīng)上傳的文件地址賦值給文章數(shù)據(jù)
var index = _self.needUploadImg[_self.uploadIndex].indexID;
_self.artList[index].content = _self.staticServer + uploadFileRes.data;
console.log(_self.artList);
_self.uploadIndex ++;
// 遞歸上傳
setTimeout(function(){_self.uploadImg();}, 1000);
},
fail: () => {
uni.showToast({title:"上傳圖片失敗,請(qǐng)重試!", icon:"none"});
}
})
},
submit : function(res){
var content = res.detail.value.artText;
if(content.length < 1){uni.showToast({title:"請(qǐng)輸入內(nèi)容",icon:'none'}); return ;}
this.artList.push({"type":"text", "content" : content});
this.inputContent = '';
},
addImg : function(){
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
success: function(res) {
_self.artList.push({"type":"image", "content" : res.tempFilePaths[0]})
}
})
}
}
}
</script>
<style></style>
分類數(shù)據(jù)表
DROP TABLE IF EXISTS `yuedu_categories`;
CREATE TABLE `yuedu_categories` (
`cate_id` int(10) NOT NULL AUTO_INCREMENT,
`cate_pid` int(10) DEFAULT '0',
`cate_name` varchar(50) DEFAULT NULL,
`cate_order` int(10) DEFAULT NULL,
PRIMARY KEY (`cate_id`),
KEY `cate_pid` (`cate_pid`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Records of yuedu_categories
-- ----------------------------
INSERT INTO `yuedu_categories` VALUES ('1', '0', '美文', '1');
INSERT INTO `yuedu_categories` VALUES ('2', '0', '互聯(lián)網(wǎng)', '2');
INSERT INTO `yuedu_categories` VALUES ('3', '0', '汽車', '3');
后端代碼
<?php
namespace hsC;
class category{
public function index(){
$_GET['pid'] = empty($_GET['pid']) ? 0 : intval($_GET['pid']); //分析pid沒有的話為0
$db = \hsTool\db::getInstance('categories'); //連接數(shù)據(jù)表
if(empty($_GET['pid'])){
$categories = $db->order('cate_order asc')->fetchAll();//沒有pid就查詢所有分類
}else{
$categories = $db->order('cate_order asc')->where('cate_pid = ?', array($_GET['pid']))->fetchAll();
}//有pid就查詢子分類
if(empty($categories)){exit(jsonCode('empty', ''));}//如果是空的就輸出空
$caties = array();
foreach($categories as $cate){
$caties[$cate['cate_id']] = $cate['cate_name']; //如果不是空就把數(shù)據(jù)傳入數(shù)組caties
}
exit(jsonCode('ok', $caties));//輸出caties
}
}