提交 e471f6eb 编写于 作者: DCloud_JSON's avatar DCloud_JSON

- 短信验证码登陆、绑定手机号码新增防刷逻辑。当短信验证码输入错误2次以上,弹出图形验证码进行人机校验。 -...

- 短信验证码登陆、绑定手机号码新增防刷逻辑。当短信验证码输入错误2次以上,弹出图形验证码进行人机校验。 - uni-id-cf,新增防刷机制。更改loginLog为uniIdLog 记录各类uni-id操作,并新增action字段记录操作的行为名称 - 注册账号新增需要输入图形验证码
上级 c589bfe6
## 1.1.35(2022-05-16)
- 短信验证码登陆、绑定手机号码新增防刷逻辑。当短信验证码输入错误2次以上,弹出图形验证码进行人机校验。
- uni-id-cf,新增防刷机制。更改loginLog为uniIdLog 记录各类uni-id操作,并新增action字段记录操作的行为名称
- 注册账号新增需要输入图形验证码
## 1.1.34(2022-05-12)
修复绑定手机号码,未验证空验证码的问题。注意:请确保项目依赖的uni-id版本为3.3.18+
## 1.1.33(2022-02-24)
修复微信小程序端,个人资料-绑定手机号码,一键获取微信资料中手机号码绑定授权,点击“拒绝”时toast:encryptedData 不可为空的问题
## 1.1.32(2022-02-24) ## 1.1.32(2022-02-24)
- 删除多余文件:`uniCloud/database/opendb-news-articles-detail.schema.json` - 删除多余文件:`uniCloud/database/opendb-news-articles-detail.schema.json`
- 修复当用户选择验证码登陆方式,在输入验证码页面,点击微信登陆时报“你未同意隐私政策协议”的问题 - 修复当用户选择验证码登陆方式,在输入验证码页面,点击微信登陆时报“你未同意隐私政策协议”的问题
......
...@@ -198,7 +198,8 @@ export default async function() { ...@@ -198,7 +198,8 @@ export default async function() {
complete(e) { complete(e) {
// console.log(JSON.stringify(e)); // console.log(JSON.stringify(e));
}, },
fail(e) { // 失败回调拦截 fail(e) { // 失败回调拦截
console.error('网络请求错误码:',JSON.stringify(e));
if (Debug) { if (Debug) {
uni.showModal({ uni.showModal({
content: JSON.stringify(e), content: JSON.stringify(e),
......
.uni-flex {
display: flex;
}
.uni-flex-row {
@extend .uni-flex;
flex-direction: row;
box-sizing: border-box;
}
.uni-flex-column {
@extend .uni-flex;
flex-direction: column;
}
.uni-color-gary {
color: #3b4144;
}
/* 标题 */
.uni-title {
display: flex;
margin-bottom: $uni-spacing-col-base;
font-size: $uni-font-size-lg;
font-weight: bold;
color: #3b4144;
}
.uni-title-sub {
display: flex;
font-size: $uni-font-size-base;
font-weight: 500;
color: #3b4144;
}
/* 描述 额外文本 */
.uni-note {
margin-top: 10px;
color: #999;
font-size: $uni-font-size-sm;
}
/* 列表内容 */
.uni-list-box {
@extend .uni-flex-row;
flex: 1;
margin-top: 10px;
}
/* 略缩图 */
.uni-thumb {
flex-shrink: 0;
margin-right: $uni-spacing-row-base;
width: 125px;
height: 75px;
border-radius: $uni-border-radius-lg;
overflow: hidden;
border: 1px #f5f5f5 solid;
image {
width: 100%;
height: 100%;
}
}
.uni-media-box {
@extend .uni-flex-row;
border-radius: $uni-border-radius-lg;
overflow: hidden;
.uni-thumb {
margin: 0;
margin-left: 4px;
flex-shrink: 1;
width: 33%;
border-radius:0;
&:first-child {
margin: 0;
}
}
}
/* 内容 */
.uni-content {
@extend .uni-flex-column;
justify-content: space-between;
}
/* 列表footer */
.uni-footer {
@extend .uni-flex-row;
justify-content: space-between;
margin-top: $uni-spacing-col-lg;
}
.uni-footer-text {
font-size: $uni-font-size-sm;
color: $uni-text-color-grey;
margin-left: 5px;
}
/* 标签 */
.uni-tag {
flex-shrink: 0;
padding: 0 5px;
border: 1px $uni-border-color solid;
margin-right: $uni-spacing-row-sm;
border-radius: $uni-border-radius-base;
background: $uni-bg-color-grey;
color: $uni-text-color;
font-size: $uni-font-size-sm;
}
/* 链接 */
.uni-link {
margin-left: 10px;
color: $uni-text-color;
text-decoration: underline;
}
...@@ -70,29 +70,33 @@ ...@@ -70,29 +70,33 @@
}) })
}, },
bindMobileByMpWeixin(e) { bindMobileByMpWeixin(e) {
console.log(e.detail); console.log(e.detail);
uniCloud.callFunction({ if(e.errMsg == "getPhoneNumber:ok"){
name: "uni-id-cf", uniCloud.callFunction({
data: { name: "uni-id-cf",
"action": "bindMobileByMpWeixin", data: {
"params": e.detail "action": "bindMobileByMpWeixin",
}, "params": e.detail
complete: (e) => { },
console.log(e); complete: (e) => {
}, console.log(e);
success: (e) => { },
uni.showToast({ success: (e) => {
title: e.result.msg||'绑定成功', uni.showToast({
icon: 'none' title: e.result.msg||'绑定成功',
}); icon: 'none'
if(e.result.code === 0){ });
this.setUserInfo({ if(e.result.code === 0){
"mobile": e.result.mobile this.setUserInfo({
}) "mobile": e.result.mobile
})
}
this.closeMe()
} }
this.closeMe() })
} }else{
}) this.closeMe()
}
}, },
async open(uid) { async open(uid) {
userId = uid userId = uid
......
...@@ -115,7 +115,8 @@ ...@@ -115,7 +115,8 @@
<style scoped> <style scoped>
.box { .box {
flex: 1; flex: 1;
width: 700rpx;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
...@@ -123,7 +124,6 @@ ...@@ -123,7 +124,6 @@
.uni-load-more{ .uni-load-more{
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 690rpx;
} }
.state-text { .state-text {
text-align: center; text-align: center;
......
...@@ -335,7 +335,7 @@ ...@@ -335,7 +335,7 @@
console.log({ console.log({
params, params,
type type
}); });
let action = 'loginBy' + type.trim().toLowerCase().replace(type[0], type[0].toUpperCase()) let action = 'loginBy' + type.trim().toLowerCase().replace(type[0], type[0].toUpperCase())
uniCloud.callFunction({ uniCloud.callFunction({
name: 'uni-id-cf', name: 'uni-id-cf',
......
...@@ -62,6 +62,8 @@ ...@@ -62,6 +62,8 @@
"Bluetooth": { "Bluetooth": {
}, },
"Push": { "Push": {
},
"Maps": {
} }
}, },
"distribute": { "distribute": {
...@@ -131,9 +133,23 @@ ...@@ -131,9 +133,23 @@
}, },
"push": { "push": {
"unipush": { "unipush": {
"version": "2",
"offline": true,
"meizu": {
},
"mi": {
},
"vivo": {
},
"oppo": {
},
"hms": {
}
} }
}, },
"payment": { "payment": {
},
"maps": {
} }
}, },
"icons": { "icons": {
......
{ {
"id": "uni-starter", "id": "uni-starter",
"displayName": "uni-starter", "displayName": "uni-starter",
"version": "1.1.32", "version": "1.1.35",
"description": "云端一体应用快速开发基本项目模版", "description": "云端一体应用快速开发基本项目模版",
"keywords": [ "keywords": [
"login", "login",
...@@ -38,7 +38,9 @@ ...@@ -38,7 +38,9 @@
"npmurl": "" "npmurl": ""
}, },
"uni_modules": { "uni_modules": {
"dependencies": [], "dependencies": [
"uni-id-cf"
],
"encrypt": [], "encrypt": [],
"platforms": { "platforms": {
"cloud": { "cloud": {
......
...@@ -176,7 +176,11 @@ ...@@ -176,7 +176,11 @@
"navigationBarTitleText": "uni-starter", "navigationBarTitleText": "uni-starter",
"navigationBarBackgroundColor": "#FFFFFF", "navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#F8F8F8", "backgroundColor": "#F8F8F8",
"enablePullDownRefresh": false "enablePullDownRefresh": false,
// "maxWidth":375,
"rpxCalcMaxDeviceWidth":375,
"rpxCalcBaseDeviceWidth":375
// "rpxCalcIncludeWidth":0
}, },
"condition": { "condition": {
"list": [{ "list": [{
......
...@@ -23,8 +23,7 @@ ...@@ -23,8 +23,7 @@
<!-- #endif --> <!-- #endif -->
<!-- 列表渲染 --> <!-- 列表渲染 -->
<uni-list-item :to="'/pages/list/detail?id='+item._id+'&title='+item.title" v-for="(item,index) in data" <uni-list-item :to="'/pages/list/detail?id='+item._id+'&title='+item.title" v-for="(item,index) in data" :key="index">
:key="index">
<!-- 通过header插槽定义列表左侧图片 --> <!-- 通过header插槽定义列表左侧图片 -->
<template v-slot:header> <template v-slot:header>
<image class="avatar" :src="item.avatar" mode="aspectFill"></image> <image class="avatar" :src="item.avatar" mode="aspectFill"></image>
...@@ -201,13 +200,13 @@ ...@@ -201,13 +200,13 @@
margin-right: 10rpx; margin-right: 10rpx;
} }
.main { .main {
justify-content: space-between; justify-content: space-between;
flex: 1;
} }
.title { .title {
width: 480rpx; font-size: 16px;
font-size: 32rpx;
} }
.info { .info {
...@@ -217,7 +216,7 @@ ...@@ -217,7 +216,7 @@
.author, .author,
.last_modify_date { .last_modify_date {
font-size: 28rpx; font-size: 14px;
color: #999999; color: #999999;
} }
......
...@@ -126,7 +126,7 @@ ...@@ -126,7 +126,7 @@
title title
}) { }) {
uni.navigateTo({ uni.navigateTo({
url: '/pages/common/webview/webview?url=' + url + '&title=' + title, url: '/pages/common/webview/webview?url=' + url + '&title=' + title,
success: res => {}, success: res => {},
fail: () => {}, fail: () => {},
complete: () => {} complete: () => {}
...@@ -144,8 +144,9 @@ ...@@ -144,8 +144,9 @@
} }
/* #endif */ /* #endif */
.about { .about {
width: 750rpx; flex-direction: column;
flex-direction: column; justify-content: center;
align-items: center;
} }
.box { .box {
...@@ -180,13 +181,12 @@ ...@@ -180,13 +181,12 @@
} }
.copyright { .copyright {
width: 750rpx;
font-size: 32rpx; font-size: 32rpx;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
bottom: 20px; bottom: 20px;
left: 0; // left: 0;
position: fixed; position: fixed;
} }
......
...@@ -9,7 +9,7 @@ view { ...@@ -9,7 +9,7 @@ view {
.content { .content {
padding: 0 50rpx; padding: 0 50rpx;
width: 750rpx; /* width: 750rpx; */
flex: 1; flex: 1;
} }
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<view v-if="['apple','weixin'].includes(type)" class="quickLogin"> <view v-if="['apple','weixin'].includes(type)" class="quickLogin">
<image @click="quickLogin" :src="imgSrc" mode="widthFix" class="quickLoginBtn"></image> <image @click="quickLogin" :src="imgSrc" mode="widthFix" class="quickLoginBtn"></image>
<uni-agreements @setAgree="agree = $event"></uni-agreements> <uni-agreements @setAgree="agree = $event"></uni-agreements>
</view> </view>
<template v-else> <template v-else>
<input type="number" class="input-box" :inputBorder="false" v-model="phone" maxlength="11" <input type="number" class="input-box" :inputBorder="false" v-model="phone" maxlength="11"
:placeholder="$t('common.phonePlaceholder')" /> :placeholder="$t('common.phonePlaceholder')" />
......
...@@ -14,7 +14,8 @@ ...@@ -14,7 +14,8 @@
<button class="send-btn" :disabled="!canSubmit" :type="canSubmit?'primary':'default'" <button class="send-btn" :disabled="!canSubmit" :type="canSubmit?'primary':'default'"
@click="submit">{{$t('common.login')}}</button> @click="submit">{{$t('common.login')}}</button>
</uni-forms> </uni-forms>
<uni-quick-login agree></uni-quick-login> <uni-quick-login agree></uni-quick-login>
<uni-popup-captcha @confirm="submit" ref="popup-captcha" v-model="captcha" scene="loginBySms"></uni-popup-captcha>
</view> </view>
</template> </template>
<script> <script>
...@@ -24,7 +25,8 @@ ...@@ -24,7 +25,8 @@
data() { data() {
return { return {
code:'', code:'',
phone:'' phone:'',
captcha:false
} }
}, },
computed: { computed: {
...@@ -51,18 +53,24 @@ ...@@ -51,18 +53,24 @@
action:'loginBySms', action:'loginBySms',
params:{ params:{
"mobile":this.phone, "mobile":this.phone,
"code":this.code "code":this.code,
"captcha":this.captcha
}, },
}, },
success: ({result}) => { success: ({result}) => {
uni.showToast({
title: result.msg || result.errMsg,
icon: 'none'
});
if(result.errCode == "CAPTCHA_REQUIRED"){
return this.$refs['popup-captcha'].open()
}
if(result.code === 0){ if(result.code === 0){
this.loginSuccess(result) this.loginSuccess(result)
}else{
uni.showModal({
content: result.msg,
showCancel: false
});
} }
},
complete: () => {
this.captcha = false
} }
}) })
} }
...@@ -71,4 +79,4 @@ ...@@ -71,4 +79,4 @@
</script> </script>
<style> <style>
@import url("../common/login-page.css"); @import url("../common/login-page.css");
</style> </style>
\ No newline at end of file
...@@ -4,10 +4,11 @@ ...@@ -4,10 +4,11 @@
<text class="title">{{$t('pwdLogin.pwdLogin')}}</text> <text class="title">{{$t('pwdLogin.pwdLogin')}}</text>
<input class="input-box" :inputBorder="false" v-model="username" :placeholder="$t('pwdLogin.placeholder')"/> <input class="input-box" :inputBorder="false" v-model="username" :placeholder="$t('pwdLogin.placeholder')"/>
<input type="password" class="input-box" :inputBorder="false" v-model="password" :placeholder="$t('pwdLogin.passwordPlaceholder')"/> <input type="password" class="input-box" :inputBorder="false" v-model="password" :placeholder="$t('pwdLogin.passwordPlaceholder')"/>
<view class="captcha-box" v-if="captchaBase64"> <!-- <view class="captcha-box" v-if="captchaBase64">
<image class="captcha-img" @click="createCaptcha" :src="captchaBase64" mode="widthFix"></image> <image class="captcha-img" @click="createCaptcha" :src="captchaBase64" mode="widthFix"></image>
<input type="text" class="input-box captcha" :inputBorder="false" v-model="captcha" :placeholder="$t('pwdLogin.verifyCodePlaceholder')"/> <input type="text" class="input-box captcha" :inputBorder="false" v-model="captcha" :placeholder="$t('pwdLogin.verifyCodePlaceholder')"/>
</view> </view> -->
<uni-captcha scene="login" v-model="captcha"></uni-captcha>
<uni-agreements @setAgree="agree = $event"></uni-agreements> <uni-agreements @setAgree="agree = $event"></uni-agreements>
<button class="send-btn" :disabled="!canLogin" :type="canLogin?'primary':'default'" <button class="send-btn" :disabled="!canLogin" :type="canLogin?'primary':'default'"
@click="pwdLogin">{{$t('pwdLogin.login')}}</button> @click="pwdLogin">{{$t('pwdLogin.login')}}</button>
...@@ -28,9 +29,8 @@ ...@@ -28,9 +29,8 @@
return { return {
"password": "", "password": "",
"username": "", "username": "",
"agree": false, "agree": false,
"captchaBase64":"", "captcha":false
"captcha":""
} }
}, },
computed: { computed: {
...@@ -96,27 +96,6 @@ ...@@ -96,27 +96,6 @@
} }
}) })
}, },
createCaptcha(){
uniCloud.callFunction({
name:'uni-id-cf',
data:{
action:'createCaptcha',
params:{
scene: "login"
},
},
success: ({result}) => {
if (result.code === 0) {
this.captchaBase64 = result.captchaBase64
}else{
uni.showModal({
content: result.msg,
showCancel: false
});
}
}
})
},
/* 前往注册 */ /* 前往注册 */
toRegister(e) { toRegister(e) {
console.log(e); console.log(e);
......
...@@ -5,14 +5,17 @@ ...@@ -5,14 +5,17 @@
<uni-easyinput :inputBorder="false" class="easyinput" :placeholder="$t('register.usernamePlaceholder')" v-model="formData.username" trim="both" /> <uni-easyinput :inputBorder="false" class="easyinput" :placeholder="$t('register.usernamePlaceholder')" v-model="formData.username" trim="both" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item name="nickname"> <uni-forms-item name="nickname">
<uni-easyinput :inputBorder="false" class="easyinput" :placeholder="$t('register.nicknamePlaceholder')" v-model="formData.nickname" trim="both" style="width: 660rpx;" /> <uni-easyinput :inputBorder="false" class="easyinput" :placeholder="$t('register.nicknamePlaceholder')" v-model="formData.nickname" trim="both" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item name="password" v-model="formData.password" required> <uni-forms-item name="password" v-model="formData.password" required>
<uni-easyinput :inputBorder="false" class="easyinput" :placeholder="$t('register.passwordDigitsPlaceholder')" type="password" v-model="formData.password" trim="both" /> <uni-easyinput :inputBorder="false" class="easyinput" :placeholder="$t('register.passwordDigitsPlaceholder')" type="password" v-model="formData.password" trim="both" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item name="pwd2" v-model="formData.pwd2" required> <uni-forms-item name="pwd2" v-model="formData.pwd2" required>
<uni-easyinput :inputBorder="false" class="easyinput" :placeholder="$t('register.passwordAgain')" type="password" v-model="formData.pwd2" trim="both" /> <uni-easyinput :inputBorder="false" class="easyinput" :placeholder="$t('register.passwordAgain')" type="password" v-model="formData.pwd2" trim="both" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item name="captcha" required>
<uni-captcha scene="register" v-model="formData.captcha"></uni-captcha>
</uni-forms-item>
<uni-agreements @setAgree="agree = $event"></uni-agreements> <uni-agreements @setAgree="agree = $event"></uni-agreements>
<button class="send-btn" type="primary" @click="submit">{{$t('register.registerAndLogin')}}</button> <button class="send-btn" type="primary" @click="submit">{{$t('register.registerAndLogin')}}</button>
</uni-forms> </uni-forms>
...@@ -27,10 +30,11 @@ import mixin from '../common/login-page.mixin.js'; ...@@ -27,10 +30,11 @@ import mixin from '../common/login-page.mixin.js';
data() { data() {
return { return {
formData: { formData: {
"username": "", "username": "111111",
"nickname": "", "nickname": "111111",
'password':'', "password":"111111",
'pwd2':'' "pwd2":"111111",
"captcha":false
}, },
rules, rules,
agree:false agree:false
...@@ -91,13 +95,13 @@ import mixin from '../common/login-page.mixin.js'; ...@@ -91,13 +95,13 @@ import mixin from '../common/login-page.mixin.js';
} }
</script> </script>
<style> <style lang="scss" scoped>
@import url("../common/login-page.css"); @import url("../common/login-page.css");
.uni-container { .uni-container {
padding: 15px; padding: 15px;
} }
.send-btn{ .send-btn{
margin-top: 5px; margin-top: 15px;
} }
.uni-container ::v-deep .uni-forms-item__label{ .uni-container ::v-deep .uni-forms-item__label{
width: 15px !important; width: 15px !important;
......
...@@ -364,7 +364,6 @@ ...@@ -364,7 +364,6 @@
} }
.userInfo { .userInfo {
width: 750rpx;
padding: 20rpx; padding: 20rpx;
padding-top: 50px; padding-top: 50px;
background-image: url(../../static/uni-center/headers.png); background-image: url(../../static/uni-center/headers.png);
...@@ -409,7 +408,7 @@ ...@@ -409,7 +408,7 @@
} }
.uni-grid .text { .uni-grid .text {
font-size: 30rpx; font-size: 16px;
height: 25px; height: 25px;
line-height: 25px; line-height: 25px;
color: #817f82; color: #817f82;
......
<template> <template>
<view class="box"> <view class="uni-content">
<!-- 登录框 (选择手机号所属国家和地区需要另行实现) --> <!-- 登录框 (选择手机号所属国家和地区需要另行实现) -->
<uni-easyinput clearable focus type="number" class="input-box" :inputBorder="false" v-model="formData.phone" <uni-easyinput clearable focus type="number" class="input-box" :inputBorder="false" v-model="formData.mobile"
maxlength="11" :placeholder="$t('common.phonePlaceholder')"></uni-easyinput> maxlength="11" placeholder="请输入手机号"></uni-easyinput>
<uni-easyinput clearable type="number" class="input-box" :inputBorder="false" v-model="formData.code" maxlength="6" <uni-easyinput clearable type="number" class="input-box" :inputBorder="false" v-model="formData.code"
:placeholder="$t('common.verifyCodePlaceholder')"> maxlength="6" :placeholder="$t('common.verifyCodePlaceholder')">
<template v-slot:right> <template v-slot:right>
<uni-send-sms-code ref="shortCode" code-type="bind" :phone="formData.phone"></uni-send-sms-code> <uni-send-sms-code ref="shortCode" code-type="bind" :phone="formData.mobile"></uni-send-sms-code>
</template> </template>
</uni-easyinput> </uni-easyinput>
<button class="send-btn-box" :disabled="!canSubmit" :type="canSubmit?'primary':'default'" @click="submit">{{$t('common.submit')}}</button> <button class="uni-btn send-btn-box" :disabled="!canSubmit" :type="canSubmit?'primary':'default'"
@click="submit">提交</button>
<uni-popup-captcha ref="popup-captcha" @confirm="submit" v-model="formData.captcha" scene="bindMobileBySms"></uni-popup-captcha>
</view> </view>
</template> </template>
<script> <script>
...@@ -22,26 +24,22 @@ ...@@ -22,26 +24,22 @@
return { return {
currenPhoneArea: '', currenPhoneArea: '',
formData: { formData: {
phone:"", phone: "",
code:"" code: "",
captcha: false
} }
} }
}, },
computed: { computed: {
tipText() { tipText() {
return this.$t('common.verifyCodeSend')+ `${this.currenPhoneArea} ${this.formData.phone}。` + this.$t('common.passwordDigits') return "验证码已通过短信发送至" + `${this.currenPhoneArea} ${this.formData.mobile}。` + "密码为6 - 20位"
}, },
canSubmit() { canSubmit() {
return true//this.isPhone() && this.isCode(); return this.isPhone() && this.isCode();
} }
}, },
onLoad(event) { onLoad(event) {},
uni.setNavigationBarTitle({ onReady() {},
title:this.$t('bindMobile.navigationBarTitle')
})
},
onReady() {
},
methods: { methods: {
...mapMutations({ ...mapMutations({
setUserInfo: 'user/login' setUserInfo: 'user/login'
...@@ -52,53 +50,76 @@ ...@@ -52,53 +50,76 @@
submit() { submit() {
console.log(this.formData); console.log(this.formData);
uniCloud.callFunction({ uniCloud.callFunction({
name:'uni-id-cf', name: 'uni-id-cf',
data:{ data: {
action:'bindMobileBySms', action: 'bindMobileBySms',
params:{ params: this.formData
"mobile": this.formData.phone,
"code": this.formData.code
},
}, },
success: ({result}) => { success: ({
console.log(result); result
this.setUserInfo({"mobile":result.mobile}) }) => {
uni.showToast({ console.log(result);
title: result.msg||'完成', uni.showToast({
icon: 'none' title: result.msg || result.errMsg,
}); icon: 'none'
});
if(result.errCode == "CAPTCHA_REQUIRED"){
return this.$refs['popup-captcha'].open()
}
if (result.code === 0) { if (result.code === 0) {
this.setUserInfo({"mobile":result.mobile})
uni.navigateBack() uni.navigateBack()
} }
},
complete: () => {
this.formData.captcha = false
} }
}) })
}, /*
isPhone() { const uniIdCo = uniCloud.importObject("uni-id-co")
let reg_phone = /^1\d{10}$/; uniIdCo.bindMobileBySms(this.formData).then(e => {
let isPhone = reg_phone.test(this.formData.phone); console.log(e);
return isPhone; uni.showToast({
}, title: e.errMsg,
isCode() { icon: 'none'
let reg_code = /^\d{6}$/; });
let isCode = reg_code.test(this.formData.code); uni.navigateBack()
return isCode; }).catch(e => {
if( e.errCode == 'CAPTCHA_REQUIRED'){
this.$refs.popup.open()
}
}).finally(e=>{
this.formData.captcha = false
})
*/
},
isPhone() {
let reg_phone = /^1\d{10}$/;
let isPhone = reg_phone.test(this.formData.mobile);
return isPhone;
},
isCode() {
let reg_code = /^\d{6}$/;
let isCode = reg_code.test(this.formData.code);
return isCode;
} }
} }
} }
</script> </script>
<style> <style>
.box { .uni-content {
padding: 0;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 50rpx; padding: 50rpx;
padding-top: 10px; padding-top: 10px;
} }
/* #ifndef APP-NVUE || VUE3 */
.box /deep/ .uni-easyinput__content { /* #ifndef APP-NVUE || VUE3 */
height: 50px; .uni-content /deep/ .uni-easyinput__content {}
}
/* #endif */ /* #endif */
.input-box { .input-box {
width: 100%; width: 100%;
margin-top: 16px; margin-top: 16px;
...@@ -106,10 +127,10 @@ ...@@ -106,10 +127,10 @@
border-radius: 6rpx; border-radius: 6rpx;
flex-direction: row; flex-direction: row;
flex-wrap: nowrap; flex-wrap: nowrap;
margin-bottom: 10px;
} }
.send-btn-box { .send-btn-box {
width: 650rpx;
margin-top: 15px; margin-top: 15px;
} }
</style> </style>
static/uni-center/headers.png

27.1 KB | W: | H:

static/uni-center/headers.png

32.3 KB | W: | H:

static/uni-center/headers.png
static/uni-center/headers.png
static/uni-center/headers.png
static/uni-center/headers.png
  • 2-up
  • Swipe
  • Onion skin
## 0.1.1(2021-03-04) ## 0.4.1(2022-05-16)
- refresh不再读取上一条验证码状态 - 新增示例项目
## 0.1.0(2021-03-01) ## 0.4.0(2022-05-16)
- 调整为uni_modules目录规范 - 集成创建、刷新、显示验证码的云端一体验证码组件
- 云对象`uni-captcha-co`集成获取验证码的api,`getImageCaptcha`
## 0.3.1(2022-05-13)
- 新增 返回值符合响应体规范
## 0.3.0(2022-05-13)
- 新增 支持 uni-config-center 配置
## 0.2.2(2022-04-25)
- 修复 0.2.1 版本引起的使用 image 组件验证码不显示的Bug
## 0.2.1(2022-04-18)
- 更新 优化字体
## 0.2.0(2022-04-14)
- 新增 使用 svg 表现形式更好
- 新增 使用字体,可以任意替换默认字体
- 新增 支持设置字体大小
- 新增 支持忽略某些字符
- 注意 更新之后请重新上传公共模块
## 0.1.0(2021-03-01)
- 调整为uni_modules目录规范
<template>
<view class="captcha-box">
<view class="captcha-img-box">
<uni-icons class="loding" size="20px" color="#BBB" v-if="loging" type="spinner-cycle"></uni-icons>
<image class="captcha-img" :class="{opacity:loging}" @click="getImageCaptcha" :src="captchaBase64"
mode="widthFix"></image>
</view>
<input @blur="focusCaptchaInput = false" :focus="focusCaptchaInput" type="text" class="captcha"
:inputBorder="false" maxlength="4" v-model="modelValue" placeholder="请输入验证码">
</view>
</template>
<script>
export default {
data() {
return {
focusCaptchaInput: false,
modelValue: "",
captchaBase64: "",
loging: false
};
},
model: {
prop: 'modelValue',
event: 'update:modelValue'
},
props: {
event: 'update:modelValue',
scene: {
type: String,
default () {
return ""
}
},
focus: {
type: Boolean,
default () {
return false
}
}
},
watch: {
modelValue(value) {
// TODO 兼容 vue2
this.$emit('input', value);
// TODO 兼容 vue3
this.$emit('update:modelValue', value)
},
scene: {
handler(scene) {
if (scene) {
this.getImageCaptcha(false)
} else {
uni.showToast({
title: 'scene不能为空',
icon: 'none'
});
}
},
immediate:true
}
},
methods: {
getImageCaptcha(focus = true) {
this.loging = true
this.modelValue = ''
if (focus) {
this.focusCaptchaInput = true
}
const uniIdCo = uniCloud.importObject("uni-captcha-co", {
customUI: true
})
uniIdCo.getImageCaptcha({
scene: this.scene
}).then(result => {
console.log(result);
this.captchaBase64 = result.captchaBase64
})
.catch(e => {
uni.showToast({
title: e.message,
icon: 'none'
});
}).finally(e => {
this.loging = false
})
}
}
}
</script>
<style lang="scss" scoped>
.captcha-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: flex-end;
flex: 1;
}
.captcha-img-box,
.captcha {
height: 44px;
line-height: 44px;
}
.captcha-img-box {
cursor: pointer;
position: relative;
background-color: #FEFAE7;
}
.captcha {
background-color: #F8F8F8;
font-size: 14px;
flex: 1;
padding: 0 20rpx;
margin-left: 20rpx;
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
}
.captcha-img-box,
.captcha-img,
.loding {
width: 100px;
}
.loding {
z-index: 9;
color: #bbb;
position: absolute;
text-align: center;
line-height: 45px;
animation: rotate 1s linear infinite;
}
.opacity {
opacity: 0.5;
}
@keyframes rotate {
from {
transform: rotate(0deg)
}
to {
transform: rotate(360deg)
}
}
</style>
<template>
<uni-popup ref="popup" type="center">
<view class="popup-captcha">
<view class="content">
<text class="title">{{title}}</text>
<uni-captcha :scene="scene" v-model="modelValue"></uni-captcha>
</view>
<view class="button-box">
<view @click="close" class="btn">取消</view>
<view @click="confirm" class="btn confirm">确认</view>
</view>
</view>
</uni-popup>
</template>
<script>
export default {
data() {
return {
modelValue: "",
};
},
model: {
prop: 'modelValue',
event: 'update:modelValue'
},
props: {
event: 'update:modelValue',
scene: {
type: String,
default () {
return ""
}
},
title: {
type: String,
default () {
return ""
}
},
},
watch: {
modelValue(value) {
// TODO 兼容 vue2
this.$emit('input', value);
// TODO 兼容 vue3
this.$emit('update:modelValue', value)
}
},
methods: {
open() {
this.$refs.popup.open()
},
close() {
this.$refs.popup.close()
},
confirm() {
if(this.modelValue.length < 4){
return uni.showToast({
title: '请填写验证码',
icon: 'none'
});
}
this.close()
this.$emit('confirm')
}
}
}
</script>
<style lang="scss" scoped>
/* #ifndef APP-NVUE */
view {
display: flex;
flex-direction: column;
}
/* #endif */
.popup-captcha {
/* #ifndef APP-NVUE */
display: flex;
max-width: 600px;
/* #endif */
width: 600rpx;
padding-bottom: 0;
background-color: #FFF;
border-radius: 10px;
flex-direction: column;
position: relative;
}
.popup-captcha .content {
padding: 1.3em 0.8em;
}
.popup-captcha .title {
text-align: center;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
font-weight: 400;
font-size: 18px;
overflow: hidden;
text-overflow: ellipsis;
color: #111;
margin-bottom: 15px;
}
.button-box {
height: 44px;
border-top: solid 1px #eee;
flex-direction: row;
align-items: center;
justify-content: space-around;
}
.button-box ,.btn{
height: 44px;
line-height: 44px;
}
.button-box .btn{
flex: 1;
margin: 1px;
text-align: center;
}
.button-box .confirm{
color: #007aff;
border-left: solid 1px #eee;
}
</style>
{ {
"id": "uni-captcha", "id": "uni-captcha",
"displayName": "uni-captcha", "displayName": "uni-captcha",
"version": "0.1.1", "version": "0.4.1",
"description": "简洁、高效、灵活可配置的云端验证码模块", "description": "简洁、高效、灵活可配置的云端验证码模块",
"keywords": [ "keywords": [
"uniCloud", "uniCloud",
"captcha", "captcha",
"验证码", "验证码",
"图形验证码", "图形验证码",
"人机验证" "人机验证"
], ],
"repository": "https://gitee.com/dcloud/uni-captcha", "repository": "https://gitee.com/dcloud/uni-captcha",
"engines": { "engines": {
"HBuilderX": "^3.1.0" "HBuilderX": "^3.1.0"
}, },
"dcloudext": { "dcloudext": {
"category": [ "category": [
"uniCloud", "uniCloud",
"云函数模板" "云函数模板"
], ],
"sale": { "sale": {
"regular": { "regular": {
"price": "0.00" "price": "0.00"
}, },
"sourcecode": { "sourcecode": {
"price": "0.00" "price": "0.00"
} }
}, },
"contact": { "contact": {
"qq": "" "qq": ""
}, },
"declaration": { "declaration": {
"ads": "无", "ads": "无",
"data": "无", "data": "无",
"permissions": "无" "permissions": "无"
}, },
"npmurl": "" "npmurl": ""
}, },
"uni_modules": { "uni_modules": {
"dependencies": [], "dependencies": [],
"encrypt": [], "encrypt": [],
"platforms": { "platforms": {
"cloud": { "cloud": {
"tcb": "y", "tcb": "y",
"aliyun": "y" "aliyun": "y"
}, },
"client": { "client": {
"App": { "App": {
"app-vue": "u", "app-vue": "u",
"app-nvue": "u" "app-nvue": "u"
}, },
"H5-mobile": { "H5-mobile": {
"Safari": "u", "Safari": "u",
"Android Browser": "u", "Android Browser": "u",
"微信浏览器(Android)": "u", "微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u" "QQ浏览器(Android)": "u"
}, },
"H5-pc": { "H5-pc": {
"Chrome": "u", "Chrome": "u",
"IE": "u", "IE": "u",
"Edge": "u", "Edge": "u",
"Firefox": "u", "Firefox": "u",
"Safari": "u" "Safari": "u"
}, },
"小程序": { "小程序": {
"微信": "u", "微信": "u",
"阿里": "u", "阿里": "u",
"百度": "u", "百度": "u",
"字节跳动": "u", "字节跳动": "u",
"QQ": "u" "QQ": "u"
}, },
"快应用": { "快应用": {
"华为": "u", "华为": "u",
"联盟": "u" "联盟": "u"
} },
} "Vue": {
} "vue2": "y",
} "vue3": "u"
}
}
}
}
} }
\ No newline at end of file
## uni 验证码验证文档 ## 用途说明
主要起到人机校验或其他限制调用的作用,如:
> 用途:主要使用在登录、需要人机校验或其他限制调用的场景 - 防止机器冒充人类做暴力破解
- 防止大规模在线注册滥用服务
> 验证码生成、校验都在服务端。页面使用返回的 base64 显示。[云端一体登陆模板](https://ext.dcloud.net.cn/plugin?id=13)已集成,可下载体验。 - 防止滥用在线批量操
- 防止信息被大量采集聚合
> 数据表使用[opendb-verify-codes](https://gitee.com/dcloud/opendb/blob/master/collection/opendb-verify-codes/collection.json)
常见的业务场景有:
- 注册环节:防止无效垃圾注册,从源头进行管理
- 登录环节:防止撞库攻击、暴力破解,保障用户数据案例
- 短信防刷:减少短信接口被刷情况,减少企业不必要成本
- 互动环节:防止批量垃圾互动信息破坏用户UGC内容生态
## 组成部分
- openDB数据表:[opendb-verify-codes](https://gitee.com/dcloud/opendb/blob/master/collection/opendb-verify-codes/collection.json),用于存储验证码数据。
- 集成:获取验证码、校验验证码、刷新验证码的uniCloud公共模块`uni-captcha`
- 云对象`uni-captcha-co`集成获取验证码的api,`getImageCaptcha`
- 集成创建、刷新、显示验证码的云端一体验证码组件
## 目录结构@catalogue
<pre>
├─uni_modules 存放[uni_module](/uni_modules)规范的插件。
│ └─uni-captcha
│ ├─uniCloud
│ │ ├─cloudfunctions 云函数目录
│ │ │ ├─common 公共模块目录
│ │ │ │ └─uni-captcha uni-captcha公共模块
│ │ │ └─uni-captcha-co 集成调用uni-captcha方法的云对象
│ │ └─database
│ │ ├─opendb-verify-codes.schema.json 验证码数据表
│ │ └─db_init.json 初始化数据库文件
│ └─components 组件目录
│ ├─uni-captcha
│ │ └─uni-captcha.vue 普通验证码组件
│ └─uni-popup-captcha
│ └─uni-popup-captcha.vue 弹出式验证码组件
</pre>
## 原理时序
客户端携带场景值(用于防止不同功能的验证码混用,如:`login``pay`)调用云对象`uni-captcha-co``getImageCaptcha`方法,向服务端发起获取验证码请求。
方法内部通过查询数据表`opendb-verify-codes`,判断:同一设备id、相同场景值、待验证的记录是否已存在;
- 不存在则:调用公共模块`uni-captcha``create`方法创建验证码,此时数据表会创建一条验证状态(字段名:`state`)为待验证(字段值`0`)的记录。
- 已存在则:调用公共模块`uni-captcha``refresh`方法刷新这个验证码。此时更新现存记录的验证状态(字段名:`state`)的值为已作废(字段值`2`),然后数据表也会创建一条验证状态(字段名:`state`)为待验证(字段值`0`)的记录。
并向客户端返回:格式为base64的图形验证码资源数据(响应体参数名:captchaBase64)。
客户端得到数据后,显示图形验证码,用户识别后(携带参数:场景值scene、验证码captcha)提交表单,服务端调用公共模块`uni-captcha``verify`方法验证是否正确。
补充:如果是clientDB操作,不想在扩展校验函数中依赖公共模块,还可以直接查库校验验证码。
以上即完整的流程,你如果直接使用云端一体组件,仅需配置组件的场景值属性`scene`,即可实现上述功能。
## 组件介绍
### 普通验证码组件
**组件名:uni-captcha**
云端一体组件,内置调用`uni-captcha-co`云对象创建/刷新验证码,支持双向数据绑定。
仅需传入属性`scene`:场景值即可。
组件遵从[easycom组件规范](https://uniapp.dcloud.io/component/#easycom%E7%BB%84%E4%BB%B6%E8%A7%84%E8%8C%83),使用示例:
```js
<template>
<uni-captcha scene="场景值" v-model="验证码的值"></uni-captcha>
</template>
```
#### Props:
| 字段 | 类型 | 必填 | 默认值 | 说明 |
| ------------ | ------- | ---- | ------- | ------------------------------------------------------------ |
| scene | String | 是 | - | 使用场景值,用于防止不同功能的验证码混用,如:`login``pay` |
| value/v-model| String | - | - | 验证码的值 |
### 弹出式验证码组件
**组件名:uni-popup-captcha**
验证码实现人机校验等作用的同时,降低了用户体验。为了提升用户体验:绝大部分情况下,验证码应当是非常态的,当接口被高频调用的情况下才需要。
另外验证码会使得我们的前端界面设计变得复杂。所以弹出式验证码组件,应需而生。
#### 使用示例:
```js
<template>
<uni-popup-captcha ref="popup-captcha" @confirm="verifyCaptcha" :scene="formData.scene" v-model="formData.captcha"></uni-popup-captcha>
<button @click="openPopupCaptcha" >显示弹出式验证码</button>
</template>
<script>
export default {
data() {
return {
formData:{
captcha:"",
scene:"test"
}
}
},
methods: {
verifyCaptcha(){
const uniCaptchaCo = uniCloud.importObject("uni-captcha-co")
uniCaptchaCo.verifyCaptcha(this.formData).then(e=>{
uni.showToast({
title: e.errMsg,
icon: 'none'
});
})
},
openPopupCaptcha(){
this.$refs['popup-captcha'].open()
}
}
}
</script>
```
#### Props:
| 字段 | 类型 | 必填 | 说明 |
| ------------ | ------- | ---- | ------------------------------------------------------------ |
| scene | String | 是 | 使用场景值,用于防止不同功能的验证码混用,如:`login``pay` |
| value/v-model| String | - | 验证码的值 |
#### Events
| 字段 | 类型 | 说明 |
| ------------ | ------- |---------------- |
| confirm | Function | 点击确定按钮事件 |
## 公共模块
### 获取验证码@create ### 获取验证码@create
用法:`uniCaptcha.create(Object params);` 用法:`uniCaptcha.create(Object params);`
**参数说明** **参数说明**
| 字段 | 类型 | 必填 | 默认值 | 说明 | | 字段 | 类型 | 必填 | 默认值 | 说明 |
| --------------- | ------ | ---- | ------- | ----------------------------------------------- | | ------------ | ------- | ---- | ------- | ------------------------------------------------------------ |
| scene | String | 是 | 4 | 使用场景值,用于防止不同功能的验证码混用 | | scene | String | 是 | - | 使用场景值,用于防止不同功能的验证码混用,如:`login``pay` |
| deviceId | String | - | - | 设备 id,如果不传,将自动从 uniCloud 上下文获取 | | deviceId | String | - | - | 设备 id,如果不传,将自动从 uniCloud 上下文获取 |
| width | Number | - | 100 | 图片宽度 | | width | Number | - | 150 | 图片宽度 |
| height | Number | - | 40 | 图片高度 | | height | Number | - | 40 | 图片高度 |
| backgroundColor | String | - | #FFFAE8 | 验证码背景色 | | background | String | - | #FFFAE8 | 验证码背景色,设置空字符`''`不使用背景颜色 |
| size | Number | - | 4 | 验证码长度,最多 6 个字符 | | size | Number | - | 4 | 验证码长度,最多 6 个字符 |
| noise | Number | - | 4 | 验证码干扰线条数 | | noise | Number | - | 4 | 验证码干扰线条数 |
| expiresDate | Number | - | 180 | 验证码过期时间(s) | | color | Boolean | - | false | 字体是否使用随机颜色,当设置`background`后恒为`true` |
| fontSize | Number | - | 40 | 字体大小 |
| ignoreChars | String | - | '' | 忽略那些字符 |
| mathExpr | Boolean | - | false | 是否使用数学表达式 |
| mathMin | Number | - | 1 | 表达式所使用的最小数字 |
| mathMax | Number | - | 9 | 表达式所使用的最大数字 |
| mathOperator | String | - | '' | 表达式所使用的运算符,支持 `+``-`。不传随机使用 |
| expiresDate | Number | - | 180 | 验证码过期时间(s) |
**响应参数** **响应参数**
| 字段 | 类型 | 说明 | | 字段 | 类型 | 说明 |
| ------------- | ------ | ------------------- | | ------------- | ------ | ------------------- |
| code | Number | 错误码,0 表示成功 | | errCode | Number | 错误码,0 表示成功 |
| message | String | 详细信息 | | errMsg | String | 详细信息 |
| captchaBase64 | String | 验证码:base64 格式 | | captchaBase64 | String | 验证码:base64 格式 |
`注意:` `注意:`
- 重新生成后,上条验证码作废 - 重新生成后,上条验证码不作废
- 刷新验证码,上条验证码作废
- 如果想替换字体,请保证字体格式为 `.ttf` 且包含 `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-` 字符
### 校验验证码@verify ### 校验验证码@verify
...@@ -51,8 +178,8 @@ ...@@ -51,8 +178,8 @@
| 字段 | 类型 | 说明 | | 字段 | 类型 | 说明 |
| ------- | ------ | ------------------ | | ------- | ------ | ------------------ |
| code | Number | 错误码,0 表示成功 | | errCode | Number | 错误码,0 表示成功 |
| message | String | 详细信息 | | errMsg | String | 详细信息 |
`注意:` `注意:`
...@@ -73,8 +200,8 @@ ...@@ -73,8 +200,8 @@
| 字段 | 类型 | 说明 | | 字段 | 类型 | 说明 |
| ------------- | ------ | ------------------- | | ------------- | ------ | ------------------- |
| code | Number | 错误码,0 表示成功 | | errCode | Number | 错误码,0 表示成功 |
| message | String | 详细信息 | | errMsg | String | 详细信息 |
| captchaBase64 | String | 验证码:base64 格式 | | captchaBase64 | String | 验证码:base64 格式 |
`注意:` `注意:`
...@@ -89,4 +216,4 @@ _详细信息请查看 message 中查看_ ...@@ -89,4 +216,4 @@ _详细信息请查看 message 中查看_
| :----: | :----: | :------: | :---------------------: | | :----: | :----: | :------: | :---------------------: |
| 验证码 | 100 | 01 | (10001)验证码生成失败 | | 验证码 | 100 | 01 | (10001)验证码生成失败 |
| | | 02 | (10002)验证码校验失败 | | | | 02 | (10002)验证码校验失败 |
| | | 03 | (10003)验证码刷新失败 | | | | 03 | (10003)验证码刷新失败 |
\ No newline at end of file
{ {
"name": "uni-captcha", "name": "uni-captcha",
"version": "0.1.1", "version": "0.2.2",
"description": "uni-captcha", "description": "uni-captcha",
"main": "index.js", "main": "index.js",
"homepage": "https://ext.dcloud.net.cn/plugin?id=4048", "homepage": "https://ext.dcloud.net.cn/plugin?id=4048",
...@@ -9,5 +9,8 @@ ...@@ -9,5 +9,8 @@
"url": "git+https://gitee.com/dcloud/uni-captcha" "url": "git+https://gitee.com/dcloud/uni-captcha"
}, },
"author": "DCloud", "author": "DCloud",
"license": "Apache-2.0" "license": "Apache-2.0",
"dependencies": {
"uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
} }
\ No newline at end of file
// 开发文档: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj
const uniCaptcha = require('uni-captcha')
const db = uniCloud.database();
const verifyCodes = db.collection('opendb-verify-codes')
module.exports = {
async getImageCaptcha({scene}) {
let {deviceId} = this.getClientInfo();
let res = await verifyCodes.where({scene,deviceId,state:0}).limit(1).get()
console.log("res: " + JSON.stringify(res));
let action = res.data.length?'refresh':'create'
console.log(action);
return await uniCaptcha[action]({
scene,
width:100,
height:44
})
}
}
{
"name": "uni-captcha-co",
"dependencies": {
"uni-captcha": "file:../common/uni-captcha",
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
},
"extensions": {
"uni-cloud-jql": {}
}
}
\ No newline at end of file
// 在本文件中可配置云数据库初始化,数据格式见:https://uniapp.dcloud.io/uniCloud/hellodb?id=db-init
// 编写完毕后对本文件点右键,可按配置规则创建表和添加数据
{
"opendb-verify-codes":{}
}
{ {
"bsonType": "object", "bsonType": "object",
"required": [], "properties": {
"properties": { "_id": {
"_id": { "description": "ID,系统自动生成"
"description": "ID,系统自动生成" },
}, "code": {
"mobile": { "bsonType": "string",
"bsonType": "string", "description": "验证码"
"description": "手机号码" },
}, "create_date": {
"email": { "bsonType": "timestamp",
"bsonType": "string", "description": "创建时间"
"description": "邮箱" },
}, "device_uuid": {
"code": { "bsonType": "string",
"bsonType": "string", "description": "设备UUID,常用于图片验证码"
"description": "验证码" },
}, "email": {
"type": { "bsonType": "string",
"bsonType": "string", "description": "邮箱"
"description": "验证类型:login, bind, unbind, pay" },
}, "expired_date": {
"state": { "bsonType": "timestamp",
"bsonType": "int", "description": "过期时间"
"description": "验证状态:0 未验证、1 已验证、2 已作废" },
}, "ip": {
"ip": { "bsonType": "string",
"bsonType": "string", "description": "请求时客户端IP地址"
"description": "请求时客户端IP地址" },
}, "mobile": {
"created_at": { "bsonType": "string",
"bsonType": "timestamp", "description": "手机号码"
"description": "创建时间" },
}, "scene": {
"expired_at": { "bsonType": "string",
"bsonType": "timestamp", "description": "使用验证码的场景,如:login, bind, unbind, pay"
"description": "过期时间" },
} "state": {
} "bsonType": "int",
} "description": "验证状态:0 未验证、1 已验证、2 已作废"
}
},
"required": []
}
\ No newline at end of file
{
"goEasy": {
"commonKey": "",
"clientKey": "",
"subscribeKey": "",
"restKey": "",
"secretKey": "",
"restHost":""
}
}
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
"passwordErrorRetryTime": 3600, "passwordErrorRetryTime": 3600,
"autoSetInviteCode": false, "autoSetInviteCode": false,
"forceInviteCode": false, "forceInviteCode": false,
"app-plus": { "preferedAppPlatform": "app",
"app": {
"tokenExpiresIn": 2592000, "tokenExpiresIn": 2592000,
"oauth": { "oauth": {
"weixin": { "weixin": {
......
{
"getui": {
"appId": "dCgnx0otCL8rjloSyynDu7",
"appkey": "YGUYb5yPMN67rNyElDZv7A",
"appSecret": "b0zAa92FrG6HT9Hsqk2CR8",
"mastersecret": "nNXpI9MHkS8hZjdmQUD9H3",
"packageName": "io.dcloud.hellouniapp"
}
}
## 1.0.15(2022-05-11)
- loginBySms、bindMobileBySms新增防刷机制(当短信验证码输入错误2次以上,弹出图形验证码进行人机校验)
- 更改loginLog为uniIdLog 记录各类uni-id操作,并新增action字段记录操作的行为名称
- register新增校验图形验证码逻辑
## 1.0.14(2022-01-26) ## 1.0.14(2022-01-26)
- 修复 uni-admin 的 'registerUser' 接口,注册用户含有多余字段 uid - 修复 uni-admin 的 'registerUser' 接口,注册用户含有多余字段 uid
## 1.0.13(2022-01-26) ## 1.0.13(2022-01-26)
......
...@@ -31,7 +31,7 @@ exports.main = async (event, context) => { ...@@ -31,7 +31,7 @@ exports.main = async (event, context) => {
} = event; } = event;
const deviceInfo = event.deviceInfo || {}; const deviceInfo = event.deviceInfo || {};
let params = event.params || {}, let params = event.params || {},
tokenExpired; tokenExpired,needCaptcha;
/* /*
2.在某些操作之前我们要对用户对身份进行校验(也就是要检查用户的token)再将得到的uid写入params.uid 2.在某些操作之前我们要对用户对身份进行校验(也就是要检查用户的token)再将得到的uid写入params.uid
校验用到的方法是uniID.checkToken 详情:https://uniapp.dcloud.io/uniCloud/uni-id?id=checktoken 校验用到的方法是uniID.checkToken 详情:https://uniapp.dcloud.io/uniCloud/uni-id?id=checktoken
...@@ -90,7 +90,7 @@ exports.main = async (event, context) => { ...@@ -90,7 +90,7 @@ exports.main = async (event, context) => {
await addDeviceInfo(res) await addDeviceInfo(res)
} }
//4.记录成功登录的日志方法 //4.记录成功登录的日志方法
const loginLog = async (res = {}) => { const uniIdLog = async (res = {}) => {
const now = Date.now() const now = Date.now()
const uniIdLogCollection = db.collection('uni-id-log') const uniIdLogCollection = db.collection('uni-id-log')
let logData = { let logData = {
...@@ -98,7 +98,8 @@ exports.main = async (event, context) => { ...@@ -98,7 +98,8 @@ exports.main = async (event, context) => {
ip: context.CLIENTIP, ip: context.CLIENTIP,
type: res.type, type: res.type,
ua: context.CLIENTUA, ua: context.CLIENTUA,
create_date: now create_date: now,
action
}; };
if (res.code === 0) { if (res.code === 0) {
...@@ -109,7 +110,8 @@ exports.main = async (event, context) => { ...@@ -109,7 +110,8 @@ exports.main = async (event, context) => {
} }
if (res.type == 'register') { if (res.type == 'register') {
await registerSuccess(res) await registerSuccess(res)
} else { }
if (res.type == 'login') {
if (Object.keys(deviceInfo).length) { if (Object.keys(deviceInfo).length) {
console.log(context.DEVICEID); console.log(context.DEVICEID);
//避免重复新增设备信息,先判断是否已存在 //避免重复新增设备信息,先判断是否已存在
...@@ -158,17 +160,17 @@ exports.main = async (event, context) => { ...@@ -158,17 +160,17 @@ exports.main = async (event, context) => {
}) })
} }
//5.防止恶意破解登录,连续登录失败一定次数后,需要用户提供验证码 //5.防止恶意破解操作,连续操作失败一定次数后,需要用户提供验证码
const isNeedCaptcha = async () => { const isNeedCaptcha = async () => {
//当用户最近“2小时内(recordDate)”登录失败达到2次(recordSize)时。要求用户提交验证码 //当用户最近“2小时内(recordDate)”操作失败达到2次(recordSize)时。要求用户提交验证码
const now = Date.now(), const now = Date.now(),
recordDate = 120 * 60 * 1000, recordDate = 120 * 60 * 1000,
recordSize = 2; recordSize = 2;
const uniIdLogCollection = db.collection('uni-id-log') const uniIdLogCollection = db.collection('uni-id-log')
let recentRecord = await uniIdLogCollection.where({ let recentRecord = await uniIdLogCollection.where({
deviceId: params.deviceId || context.DEVICEID, ip: context.CLIENTIP,
create_date: dbCmd.gt(now - recordDate), create_date: dbCmd.gt(now - recordDate),
type: 'login' action
}) })
.orderBy('create_date', 'desc') .orderBy('create_date', 'desc')
.limit(recordSize) .limit(recordSize)
...@@ -247,21 +249,50 @@ exports.main = async (event, context) => { ...@@ -247,21 +249,50 @@ exports.main = async (event, context) => {
} }
break; break;
case 'bindMobileBySms': case 'bindMobileBySms':
// console.log({ if(!(/^1\d{10}$/.test(params.mobile))){
// uid: params.uid, return {
// mobile: params.mobile, code: 401,
// code: params.code msg: '手机号格式错误'
// }); }
}
if(!params.code){
return {
code: 401,
msg: '短信验证码不能为空'
}
}
needCaptcha = await isNeedCaptcha()
console.log(needCaptcha)
if(needCaptcha){
let {captcha} = params
if(!captcha){
return {
errCode: 'CAPTCHA_REQUIRED',
errMsg: '操作失败达到2次,请提交验证码'
}
}
res = await uniCaptcha.verify({
captcha,
scene: action
})
console.log(8956,res);
if(res.code != 0){
console.log(res,action);
return res
}
}
res = await uniID.bindMobile({ res = await uniID.bindMobile({
uid: params.uid, uid: params.uid,
mobile: params.mobile, mobile: params.mobile,
code: params.code code: params.code
}) })
// console.log(res); uniIdLog(res)
console.log(res);
break; break;
case 'register': case 'register':
var { var {
username, password, nickname username, password, nickname,captcha
} = params } = params
if (/^1\d{10}$/.test(username)) { if (/^1\d{10}$/.test(username)) {
return { return {
...@@ -274,6 +305,19 @@ exports.main = async (event, context) => { ...@@ -274,6 +305,19 @@ exports.main = async (event, context) => {
code: 401, code: 401,
msg: '用户名不能是邮箱' msg: '用户名不能是邮箱'
} }
}
if(!captcha){
return {
code: 401,
msg: '图形验证码不能为空'
}
}
res = await uniCaptcha.verify({
captcha,
scene: action
})
if(res.code != 0){
return res
} }
res = await uniID.register({ res = await uniID.register({
username, username,
...@@ -285,16 +329,14 @@ exports.main = async (event, context) => { ...@@ -285,16 +329,14 @@ exports.main = async (event, context) => {
await registerSuccess(res) await registerSuccess(res)
} }
break; break;
case 'getNeedCaptcha': { case 'getNeedCaptcha': {
const needCaptcha = await isNeedCaptcha() const needCaptcha = await isNeedCaptcha()
res.needCaptcha = needCaptcha res.needCaptcha = needCaptcha
break; break;
} }
case 'login': case 'login':
let passed = false; let passed = false;
let needCaptcha = await isNeedCaptcha(); needCaptcha = await isNeedCaptcha();
console.log('needCaptcha', needCaptcha); console.log('needCaptcha', needCaptcha);
if (needCaptcha) { if (needCaptcha) {
res = await uniCaptcha.verify({ res = await uniCaptcha.verify({
...@@ -310,7 +352,7 @@ exports.main = async (event, context) => { ...@@ -310,7 +352,7 @@ exports.main = async (event, context) => {
queryField: ['username', 'email', 'mobile'] queryField: ['username', 'email', 'mobile']
}); });
res.type = 'login' res.type = 'login'
await loginLog(res); await uniIdLog(res);
needCaptcha = await isNeedCaptcha(); needCaptcha = await isNeedCaptcha();
} }
...@@ -380,16 +422,16 @@ exports.main = async (event, context) => { ...@@ -380,16 +422,16 @@ exports.main = async (event, context) => {
delete loginRes.accessToken delete loginRes.accessToken
delete loginRes.refreshToken delete loginRes.refreshToken
} }
await loginLog(loginRes) await uniIdLog(loginRes)
return loginRes return loginRes
break; break;
case 'loginByUniverify': case 'loginByUniverify':
res = await uniID.loginByUniverify(params) res = await uniID.loginByUniverify(params)
await loginLog(res) await uniIdLog(res)
break; break;
case 'loginByApple': case 'loginByApple':
res = await uniID.loginByApple(params) res = await uniID.loginByApple(params)
await loginLog(res) await uniIdLog(res)
break; break;
case 'checkToken': case 'checkToken':
res = await uniID.checkToken(uniIdToken); res = await uniID.checkToken(uniIdToken);
...@@ -404,11 +446,17 @@ exports.main = async (event, context) => { ...@@ -404,11 +446,17 @@ exports.main = async (event, context) => {
break; break;
case 'sendSmsCode': case 'sendSmsCode':
/* -开始- 测试期间,为节约资源。统一虚拟短信验证码为: 123456;开启以下代码块即可 */ /* -开始- 测试期间,为节约资源。统一虚拟短信验证码为: 123456;开启以下代码块即可 */
return uniID.setVerifyCode({ res = uniID.setVerifyCode({
mobile: params.mobile, mobile: params.mobile,
code: '123456', code: '123456',
type: params.type type: params.type
}) })
return {
...res,
code: 40000,
msg:
"已启动测试模式,直接使用:123456作为短信验证码即可。正式项目,请配置/common/uni-config-center/uni-id/config.json(service->sm中的密钥信息)并在uni-id-cf完成配置 "
}
/* -结束- */ /* -结束- */
// 简单限制一下客户端调用频率 // 简单限制一下客户端调用频率
...@@ -438,7 +486,25 @@ exports.main = async (event, context) => { ...@@ -438,7 +486,25 @@ exports.main = async (event, context) => {
templateId templateId
}) })
break; break;
case 'loginBySms': case 'loginBySms':
needCaptcha = await isNeedCaptcha()
if(needCaptcha){
let {captcha} = params
if(!captcha){
return {
errCode: 'CAPTCHA_REQUIRED',
errMsg: '操作失败达到2次,请提交验证码'
}
}
res = await uniCaptcha.verify({
captcha,
scene: action
})
if(res.code != 0){
console.log(res,action);
return res
}
}
if (!params.code) { if (!params.code) {
return { return {
code: 500, code: 500,
...@@ -452,7 +518,7 @@ exports.main = async (event, context) => { ...@@ -452,7 +518,7 @@ exports.main = async (event, context) => {
} }
} }
res = await uniID.loginBySms(params) res = await uniID.loginBySms(params)
await loginLog(res) await uniIdLog(res)
break; break;
case 'resetPwdBySmsCode': case 'resetPwdBySmsCode':
if (!params.code) { if (!params.code) {
......
{ {
"name": "uni-id-cf", "name": "uni-id-cf-123",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
......
## 3.3.17(2022-05-09)
- register_env内增加os_name字段用于区分注册时的客户端系统类型
## 3.3.16(2022-05-09)
- 修复 addUser接口添加的用户无法使用密码登录的Bug [详情](https://ask.dcloud.net.cn/question/144670)
## 3.3.15(2022-05-08)
- 修复config文件语法错误时报`this.t is not a function`的Bug 感谢@寒暄
## 3.3.14(2022-05-08)
- 新增 getWeixinUserInfo接口 用于获取app平台微信登录用户的用户信息 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#get-weixin-user-info)
- 新增 addUser接口 用于手动添加用户 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#add-user)
- 新增 resetPwdBySms接口 用于使用短信验证码重置密码 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#reset-pwd-by-sms)
- 新增 refreshToken接口 用于主动刷新用户token [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#refresh-token)
- 调整 用户注册时记录用户注册环境到 register_env 字段 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#user-table)
- 调整 用户注册时将注册 ip 移至 register_env 内
## 3.3.13(2022-03-04)
- createInstance方法支持传递clientInfo [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#create-instance)
- 修复`this.t is not a function`报错
## 3.3.12(2022-01-15) ## 3.3.12(2022-01-15)
- 新增 preferedAppPlatform 配置用于解决uni-app vue2版本vue3版本获取platform不一致的问题 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=prefered-app-platform) - 新增 preferedAppPlatform 配置用于解决uni-app vue2版本vue3版本获取platform不一致的问题 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=prefered-app-platform)
- 修复 checkToken 未返回自定义token内容的Bug - 修复 checkToken 未返回自定义token内容的Bug
......
{ {
"id": "uni-id", "id": "uni-id",
"displayName": "uni-id", "displayName": "uni-id",
"version": "3.3.12", "version": "3.3.17",
"description": "简单、统一、可扩展的用户中心", "description": "简单、统一、可扩展的用户中心",
"keywords": [ "keywords": [
"uniid", "uniid",
......
{ {
"name": "uni-id", "name": "uni-id",
"version": "3.3.12", "version": "3.3.17",
"description": "uni-id for uniCloud", "description": "uni-id for uniCloud",
"main": "index.js", "main": "index.js",
"homepage": "https://uniapp.dcloud.io/uniCloud/uni-id", "homepage": "https://uniapp.dcloud.io/uniCloud/uni-id",
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
}, },
"/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json": { "/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json": {
"tokenExpiresIn": 7200, "tokenExpiresIn": 7200,
"app-plus": { "app": {
"oauth": { "oauth": {
"weixin": { "weixin": {
"appid": "填写来源微信开放平台https://open.weixin.qq.com/创建的应用的appid", "appid": "填写来源微信开放平台https://open.weixin.qq.com/创建的应用的appid",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册