提交 06706563 编写于 作者: DCloud_JSON's avatar DCloud_JSON 提交者: study夏羽

修复已知问题

上级 550209b8
......@@ -87,12 +87,6 @@ console.log(`
})
})
},1)
//更新 uni-starter.config agreements
let agreementsTitle = $i18n('agreementsTitle').split(',')
let agreements = uniStarterConfig.about.agreements
agreements[0].title = agreementsTitle[0]
agreements[1].title = agreementsTitle[1]
uniStarterConfig.about.agreements = agreements
}
initLanguageAfter()
uni.$on('changeLanguage', e => {
......
......@@ -108,7 +108,7 @@ export default {
},
settings:{
navigationBarTitle:"设置",
userInfo: "个人资料",
userInfo: "账号资料",
changePassword: "修改密码",
clearTmp: "清理缓存",
pushServer: "推送功能",
......
......@@ -122,12 +122,13 @@
"style": {
"navigationBarTitleText": "注销账号"
}
}, {
"path": "uni_modules/uni-id-pages/pages/userinfo/userinfo",
"style": {
"navigationBarTitleText": "个人资料"
}
}, {
},
{
"path": "uni_modules/uni-id-pages/pages/userinfo/userinfo",
"style": {
"navigationBarTitleText": "个人资料"
}
},{
"path": "uni_modules/uni-id-pages/pages/userinfo/bind-mobile/bind-mobile",
"style": {
"navigationBarTitleText": "绑定手机号码"
......
......@@ -18,12 +18,8 @@
<uni-list-item v-if="i18nEnable" :title="$t('settings.changeLanguage')" @click="changeLanguage" :rightText="currentLanguage" link></uni-list-item>
</uni-list>
<uni-list class="mt10" :border="false">
<uni-list-item @click="deactivate" :title="$t('settings.deactivate')" link="navigateTo"></uni-list-item>
</uni-list>
<!-- 退出/登录 按钮 -->
<view class="bottom-back" @click="clickLogout">
<view class="bottom-back" @click="changeLoginState">
<text class="bottom-back-text" v-if="hasLogin">{{$t('settings.logOut')}}</text>
<text class="bottom-back-text" v-else>{{$t('settings.login')}}</text>
</view>
......@@ -31,7 +27,8 @@
</template>
<script>
import pushServer from './dc-push/push.js';
import pushServer from './dc-push/push.js';
import common from '@/uni_modules/uni-id-pages/common/common.js';
export default {
data() {
return {
......@@ -77,16 +74,15 @@
//#endif
},
methods: {
toEdit() {
uni.navigateTo({
url: '/pages/ucenter/userinfo/userinfo'
});
},
deactivate(){
uni.navigateTo({
url:"/uni_modules/uni-id-pages/pages/userinfo/deactivate/deactivate"
})
},
async changeLoginState(){
if(this.hasLogin){
await common.logout()
}else{
uni.redirectTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withoutpwd',
});
}
},
/**
* 开始生物认证
*/
......@@ -163,32 +159,6 @@
})
})
},
clickLogout() {
if (this.hasLogin) {
uni.showModal({
title: this.$t('settings.tips'),
content: this.$t('settings.exitLogin'),
cancelText: this.$t('settings.cancelText'),
confirmText: this.$t('settings.confirmText'),
success: res => {
if (res.confirm) {
uni.removeStorageSync('uni_id_token');
uni.setStorageSync('uni_id_token_expired', 0)
uni.redirectTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withoutpwd',
});
}
}
});
} else {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withoutpwd',
complete: (e) => {
console.log(6369696,e);
}
});
}
},
clearTmp() {
uni.showLoading({
title: this.$t('settings.clearing'),
......
......@@ -2,7 +2,7 @@
<view class="center">
<uni-sign-in ref="signIn"></uni-sign-in>
<view class="userInfo" @click.capture="toUserInfo">
<cloud-image width="150rpx" height="150rpx" v-if="userInfo.avatar_file&&userInfo.avatar_file.url" :src="userInfo.avatar_file.url"></cloud-image>
<cloud-image width="150rpx" height="150rpx" v-if="hasLogin&&userInfo.avatar_file&&userInfo.avatar_file.url" :src="userInfo.avatar_file.url"></cloud-image>
<image v-else class="logo-img" src="@/static/uni-center/defaultAvatarUrl.png"></image>
<view class="logo-title">
<text class="uer-name" v-if="hasLogin">{{userInfo.nickname||userInfo.username||userInfo.mobile}}</text>
......@@ -133,7 +133,8 @@
"radius": "100%" // 边框圆角,支持百分比
}
},
userInfo:{}
userInfo:{},
hasLogin:false
}
},
onLoad() {
......@@ -148,12 +149,22 @@
//#endif
},
onShow() {
console.log('this.hasLogin',this.hasLogin);
this.hasLogin = uniCloud.getCurrentUserInfo().tokenExpired > Date.now()
if(this.hasLogin){
this.getUserInfo()
}
},
computed: {
test:{
get(){
console.log(this._test,Date.now());
this._test = Date.now()
return ''
},
set(){
}
},
// #ifdef APP-PLUS
appVersion() {
return getApp().appVersion
......@@ -161,19 +172,10 @@
// #endif
appConfig() {
return getApp().globalData.config
},
hasLogin(){
return uniCloud.getCurrentUserInfo().tokenExpired > Date.now()
}
},
methods: {
setUserInfo(data){
},
getUserInfo(e) {
uni.showLoading({
mask: true
});
const db = uniCloud.database();
db.collection('uni-id-users').where("'_id' == $cloudEnv_uid").field('mobile,nickname,avatar_file').get().then(res => {
console.log({res});
......@@ -184,7 +186,6 @@
console.log(e.message, e.errCode);
}).finally(e => {
// console.log(e);
uni.hideLoading()
})
},
toSettings() {
......
{
"schedule": {
"__UNI__xxxxxx": {
"enable": true,
"h5-weixin": {
"enable": false,
"tasks": ["ticket"]
}
}
},
"ipWhitelist": ["0.0.0.0"]
}
\ No newline at end of file
## 1.0.12(2022-09-07)
- 修复 getSupportedLoginType判断是否支持微信公众号、PC网页微信扫码登录方式报错的Bug
- 优化 适配pc端样式
- 新增 邮箱验证码注册
- 新增 邮箱验证码找回密码
- 新增 退出登录(全局)回调事件:`uni-id-pages-logout`,支持通过[uni.$on](https://uniapp.dcloud.net.cn/api/window/communication.html#on)监听;
- 调整 抽离退出登录方法至`/uni_modules/uni-id-pages/common/common.js`中,方便在项目其他页面中调用
- 调整 用户中心(路径:`/uni_modules/uni-id-pages/pages/userinfo/userinfo`)默认不再显示退出登录按钮。支持页面传参数`showLoginManage=true`恢复显示
## 1.0.11(2022-09-01)
- 修复 iOS端,一键登录功能卡在showLoading的问题
- 更新 合并密码强度与长度配置 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#config)
- uni-id-co 修复 调用 removeAuthorizedApp 接口报错的Bug
- uni-id-co 新增 管理端接口 updateUser [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#update-user)
- uni-id-co 调整 为兼容旧版本,未配置密码强度时提供最简单的密码规则校验(长度大于6即可)
- uni-id-co 调整 注册、登录时如果携带了token则尝试对此token进行登出操作
- uni-id-co 调整 管理端接口 addUser 增加 mobile、email等参数 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#add-user)
## 1.0.10(2022-08-25)
- 修复 导入uni-id-pages插件时未自动导入uni-open-bridge-common的Bug
## 1.0.9(2022-08-23)
......
const uniIdCo = uniCloud.importObject("uni-id-co")
export default {
async logout() {
await uniIdCo.logout()
uni.removeStorageSync('uni_id_token');
uni.setStorageSync('uni_id_token_expired', 0)
uni.redirectTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withoutpwd',
});
uni.$emit('uni-id-pages-logout')
},
}
\ No newline at end of file
......@@ -9,11 +9,11 @@ let mixin = {
},
onUnload() {
// #ifdef H5
document.onkeydown = false
document.onkeydown = false
// #endif
},
mounted() {
this.isMounted = true;
this.isMounted = true;
},
onLoad(e) {
if (e.is_weixin_redirect) {
......
......@@ -2,20 +2,54 @@
padding: 0 60rpx;
}
.login-logo {
display: none;
}
/* #ifndef APP-NVUE */
@media screen and (min-width: 690px) {
.uni-content {
padding: 0;
max-width: 400px;
margin-left: calc(50% - 200px);
/* #ifndef H5 */
padding: 0;
max-width: 300px;
margin-left: calc(50% - 200px);
/* #endif */
/* #ifdef H5 */
margin: 0 auto;
position: relative;
top: 100px;
padding: 30px 40px 80px 40px;
max-width: 450px;
max-height: 450px;
border-radius: 10px;
box-shadow: 0 0 20px #efefef;
background-color: #FFF;
/* #endif */
}
/* #ifdef H5 */
.login-logo {
display: flex;
justify-content: center;
}
.login-logo image {
width: 60px;
height: 60px;
}
.register-back{
display: none;
}
/* #endif */
}
.uni-content view {
box-sizing: border-box;
}
/* #endif */
.title {
/* #ifndef APP-NVUE */
display: flex;
......@@ -61,12 +95,9 @@
margin: 15px 0 0 0;
color: #FFF !important;
border-radius: 5px;
}
/*
.uni-btn::after{
display: none;
}
.uni-btn[disabled]{
background-color: $uni-color-primary !important;
opacity: 0.3;
}*/
\ No newline at end of file
}
.uni-body.uni_modules-uni-id-pages-pages-login-login-withoutpwd{
height: auto !important;
}
\ No newline at end of file
// 导入配置
import config from '@/uni_modules/uni-id-pages/config.js'
const passwordLength = config.password.length
const passwordStrength = config.password.strength
const {passwordStrength} = config
let minPasswordLength = 6
let maxPasswordLength = 20
if (passwordLength) {
if (passwordLength[0]) {
minPasswordLength = passwordLength[0]
}
if (passwordLength[1]) {
maxPasswordLength = passwordLength[1]
}
}
// 密码强度表达式
const passwordRules = {
// 密码必须包含大小写字母、数字和特殊符号
......@@ -33,15 +22,10 @@ const ERROR = {
rePwdErr: '两次输入密码不一致'
},
passwordStrengthError: {
superstrong: '密码必须包含大小写字母、数字和特殊符号',
strong: '密码必须包含字母、数字和特殊符号',
medium: '密码必须为字母、数字和特殊符号任意两种的组合',
weak: '密码必须包含字母'
},
passwordLengthError: {
normal: '密码长度必须在' + minPasswordLength + '-' + maxPasswordLength + '位之间',
minLimit: '密码长度不得少于' + minPasswordLength + '',
maxLimit: '密码长度不得超过' + maxPasswordLength + ''
super: '密码必须包含大小写字母、数字和特殊符号,密码长度必须在8-16位之间',
strong: '密码必须包含字母、数字和特殊符号,密码长度必须在8-16位之间',
medium: '密码必须为字母、数字和特殊符号任意两种的组合,密码长度必须在8-16位之间',
weak: '密码必须包含字母,密码长度必须在6-16位之间'
}
}
......@@ -52,16 +36,6 @@ function validPwd(password) {
return ERROR.passwordStrengthError[passwordStrength]
}
}
//长度校验
if (passwordLength) {
if (passwordLength[0] && password.length < passwordLength[0]) {
return ERROR.passwordLengthError.minLimit
}
if (passwordLength[1] && password.length > passwordLength[1]) {
return ERROR.passwordLengthError.maxLimit
}
}
return true
}
......@@ -106,8 +80,6 @@ function getPwdRules(pwdName = 'password', rePwdName = 'password2') {
export default {
ERROR,
minPasswordLength,
maxPasswordLength,
validPwd,
getPwdRules
}
......@@ -46,8 +46,8 @@
watch: {
src:{
handler(src) {
// console.log(src);
// console.log(src.substring(0, 8));
console.log(src);
console.log(src.substring(0, 8));
if (src&&src.substring(0, 8) == "cloud://") {
uniCloud.getTempFileURL({
fileList: [src]
......
<template>
<view>
<uni-captcha :focus="focusCaptchaInput" ref="captcha" scene="send-email-code" v-model="captcha" />
<view class="box">
<uni-easyinput :focus="focusEmailCodeInput" @blur="focusEmailCodeInput = false" type="number" class="input-box" :inputBorder="false" v-model="modelValue" maxlength="6"
placeholder="请输入邮箱验证码">
</uni-easyinput>
<view class="short-code-btn" hover-class="hover" @click="start">
<text class="inner-text" :class="reverseNumber==0?'inner-text-active':''">{{innerText}}</text>
</view>
</view>
</view>
</template>
<script>
function debounce(func, wait) {
let timer;
wait = wait || 500;
return function() {
let context = this;
let args = arguments;
if (timer) clearTimeout(timer);
let callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait)
if (callNow) func.apply(context, args);
}
}
/**
* email-code-form
* @description 获取邮箱验证码组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=
* @property {Number} count 倒计时时长 s
* @property {String} email 邮箱
* @property {String} type = [login-by-email-code|reset-pwd-by-email-code|bind-email] 验证码类型,用于防止不同功能的验证码混用,目前支持的类型login登录、register注册、bind绑定邮箱、unbind解绑邮箱
* @property {false} focusCaptchaInput = [true|false] 验证码输入框是否默认获取焦点
*/
export default {
name: "uni-email-code-form",
model: {
prop: 'modelValue',
event: 'update:modelValue'
},
props: {
event: ['update:modelValue'],
/**
* 倒计时时长 s
*/
count: {
type: [String, Number],
default: 60
},
/**
* 邮箱
*/
email: {
type: [String],
default: ''
},
/*
验证码类型,用于防止不同功能的验证码混用,目前支持的类型login登录、register注册、bind绑定邮箱、unbind解绑邮箱
*/
type: {
type: String,
default () {
return 'register'
}
},
/*
验证码输入框是否默认获取焦点
*/
focusCaptchaInput: {
type: Boolean,
default () {
return false
}
},
},
data() {
return {
captcha: "",
reverseNumber: 0,
reverseTimer: null,
modelValue: "",
focusEmailCodeInput:false
};
},
watch: {
captcha(value, oldValue) {
if (value.length == 4 && oldValue.length != 4) {
this.start()
}
},
modelValue(value) {
// TODO 兼容 vue2
this.$emit('input', value);
// TODO 兼容 vue3
this.$emit('update:modelValue', value)
}
},
computed: {
innerText() {
if (this.reverseNumber == 0) return "获取邮箱验证码";
return "重新发送" + '(' + this.reverseNumber + 's)';
}
},
created() {
this.initClick();
},
methods: {
getImageCaptcha(focus) {
this.$refs.captcha.getImageCaptcha(focus)
},
initClick() {
this.start = debounce(() => {
if (this.reverseNumber != 0) return;
this.sendMsg();
})
},
sendMsg() {
if (this.captcha.length != 4) {
this.$refs.captcha.focusCaptchaInput = true
return uni.showToast({
title: '请先输入图形验证码',
icon: 'none'
});
}
if(!this.email) return uni.showToast({
title: "请输入邮箱",
icon: 'none'
});
let reg_email = /@/;
if (!reg_email.test(this.email)) return uni.showToast({
title: "邮箱格式错误",
icon: 'none'
});
const uniIdCo = uniCloud.importObject("uni-id-co", {
customUI: true
})
console.log('uniIdCo', uniIdCo)
console.log('sendEmailCode',{
"email": this.email,
"scene": this.type,
"captcha": this.captcha
});
uniIdCo.sendEmailCode({
"email": this.email,
"scene": this.type,
"captcha": this.captcha
}).then(result => {
console.log(result.code);
uni.showToast({
title: "邮箱验证码发送成功",
icon: 'none'
});
this.reverseNumber = Number(this.count);
this.getCode();
}).catch(e => {
console.log(JSON.stringify(e));
if (e.code == "uni-id-invalid-mail-template") {
this.modelValue = "123456"
uni.showToast({
title: '已启动测试模式,详情【控制台信息】',
icon: 'none',
duration: 3000
});
console.warn(e.message);
} else {
this.getImageCaptcha()
this.captcha = ""
uni.showToast({
title: e.message,
icon: 'none'
});
}
})
},
getCode() {
if (this.reverseNumber == 0) {
clearTimeout(this.reverseTimer);
this.reverseTimer = null;
return;
}
this.reverseNumber--;
this.reverseTimer = setTimeout(() => {
this.getCode();
}, 1000)
}
}
}
</script>
<style lang="scss" scoped>
.box {
position: relative;
margin-top: 10px;
}
.short-code-btn {
padding: 0;
position: absolute;
top: 0;
right: 8px;
width: 260rpx;
max-width: 130px;
height: 44px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
}
.inner-text {
font-size: 14px;
color: #AAAAAA;
}
.inner-text-active {
color: #04498c;
}
.captcha {
width: 350rpx;
}
.input-box {
margin: 0;
padding: 4px;
background-color: #F8F8F8;
font-size: 14px;
}
.box ::v-deep .content-clear-icon {
margin-right: 100px;
}
.box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
</style>
......@@ -15,12 +15,12 @@
<script>
import config from '@/uni_modules/uni-id-pages/config.js'
//前一个窗口的页面地址。控制点击切换快捷登录方式是创建还是返回
import loginSuccess from '../../common/loginSuccess.js';
import loginSuccess from '../../common/loginSuccess.js';
const db = uniCloud.database();
const usersTable = db.collection('uni-id-users')
let allServicesList = []
let allServicesList = []
export default {
computed: {
agreements() {
......@@ -42,17 +42,17 @@
]
},
agree: {
get() {
get() {
return this.getParentComponent().agree
},
set(agree) {
console.log('setAgree', agree);
set(agree) {
console.log('setAgree', agree);
return this.getParentComponent().agree = agree
}
}
},
data() {
return {
data() {
return {
servicesList: [{
"id": "username",
"text": "账号登录",
......@@ -232,7 +232,7 @@
}
},
async login_before(type, navigateBack = true) {
console.log(type);
console.log(type);
//提示空实现
if (["qq",
"xiaomi",
......@@ -288,46 +288,46 @@
}
//判断是否需要弹出隐私协议授权框
console.log(type, this.agree);
let needAgreements = (config?.agreements?.scope || []).includes('register')
console.log(type, this.agree);
let needAgreements = (config?.agreements?.scope || []).includes('register')
console.log({
needAgreements
});
if (type != 'univerify' && needAgreements && !this.agree) {
if (type != 'univerify' && needAgreements && !this.agree) {
let agreementsRef = this.getParentComponent().$refs.agreements
return agreementsRef.popup(() => {
return agreementsRef.popup(() => {
console.log(type, navigateBack);
this.login_before(type, navigateBack)
})
}
// #ifdef H5
if(type == 'weixin'){
// console.log('开始微信网页登录');
let redirectUrl = location.protocol +'//'+
document.domain +
(window.location.href.includes('#')?'/#':'') +
'/uni_modules/uni-id-pages/pages/login/login-withoutpwd?is_weixin_redirect=true&type=weixin'
console.log('redirectUrl----',redirectUrl);
let ua = window.navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == 'micromessenger'){
// console.log('在微信公众号内');
return window.open(`https://open.weixin.qq.com/connect/oauth2/authorize?
appid=${config.appid.weixin.h5}
&redirect_uri=${encodeURIComponent(redirectUrl)}
&response_type=code
&scope=snsapi_userinfo
&state=STATE&connect_redirect=1#wechat_redirect`);
}else{
// console.log('非微信公众号内');
return location.href = `https://open.weixin.qq.com/connect/qrconnect?appid=${config.appid.weixin.web}
&redirect_uri=${encodeURIComponent(redirectUrl)}
&response_type=code&scope=snsapi_login&state=STATE#wechat_redirect`
}
}
// #endif
}
// #ifdef H5
if(type == 'weixin'){
// console.log('开始微信网页登录');
let redirectUrl = location.protocol +'//'+
document.domain +
(window.location.href.includes('#')?'/#':'') +
'/uni_modules/uni-id-pages/pages/login/login-withoutpwd?is_weixin_redirect=true&type=weixin'
console.log('redirectUrl----',redirectUrl);
let ua = window.navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == 'micromessenger'){
// console.log('在微信公众号内');
return window.open(`https://open.weixin.qq.com/connect/oauth2/authorize?
appid=${config.appid.weixin.h5}
&redirect_uri=${encodeURIComponent(redirectUrl)}
&response_type=code
&scope=snsapi_userinfo
&state=STATE&connect_redirect=1#wechat_redirect`);
}else{
// console.log('非微信公众号内');
return location.href = `https://open.weixin.qq.com/connect/qrconnect?appid=${config.appid.weixin.web}
&redirect_uri=${encodeURIComponent(redirectUrl)}
&response_type=code&scope=snsapi_login&state=STATE#wechat_redirect`
}
}
// #endif
uni.showLoading({
mask: true
......@@ -419,7 +419,7 @@
}
})
},
login(params, type) { //联网验证登录
login(params, type) { //联网验证登录
console.log('执行登录开始----');
console.log({
params,
......@@ -446,9 +446,9 @@
})
return this.$refs.userProfile.open(result.uid)
}
// #endif
// #ifdef H5
result.loginType = type
// #endif
// #ifdef H5
result.loginType = type
// #endif
loginSuccess(result)
})
......@@ -511,10 +511,17 @@
flex-wrap: wrap;
width: 750rpx;
justify-content: space-around;
position: fixed;
bottom: 10rpx;
position: fixed;
left: 0;
}
.item {
flex-direction: column;
justify-content: center;
align-items: center;
height: 200rpx;
cursor: pointer;
}
/* #ifndef APP-NVUE */
@media screen and (min-width: 690px) {
......@@ -522,17 +529,20 @@
max-width: 500px;
margin-left: calc(50% - 250px);
}
.item {
height: 160rpx;
}
}
@media screen and (max-width: 690px) {
.fab-login-box {
bottom: 10rpx;
}
}
/* #endif */
.item {
flex-direction: column;
justify-content: center;
align-items: center;
height: 200rpx;
cursor: pointer;
}
.logo {
width: 60rpx;
......
export default {
//调试模式
"debug": true,
"debug": false,
/*
登录类型 未列举到的或运行环境不支持的,将被自动隐藏。
如果需要在不同平台有不同的配置,直接用条件编译即可
......@@ -43,16 +43,13 @@ export default {
"web": "xxxxxx"
}
},
/**
* 密码强度
* superstrong(超强:密码必须包含大小写字母、数字和特殊符号)
* strong(强: 密码必须包含字母、数字和特殊符号)
* medium (中:密码必须为字母、数字和特殊符号任意两种的组合)
* weak(弱:密码必须包含字母)
* 为空或false则不验证密码强度
*/
"password": {
// "strength": "strong",
"length": [6, 20] //密码长度,默认在6-20位之间
}
/**
* 密码强度
* super(超强:密码必须包含大小写字母、数字和特殊符号,长度范围:8-16位之间)
* strong(强: 密密码必须包含字母、数字和特殊符号,长度范围:8-16位之间)
* medium (中:密码必须为字母、数字和特殊符号任意两种的组合,长度范围:8-16位之间)
* weak(弱:密码必须包含字母和数字,长度范围:6-16位之间)
* 为空或false则不验证密码强度
*/
"passwordStrength":"medium"
}
<!-- 短信验证码登录页 -->
<template>
<view class="uni-content">
<view class="login-logo">
<image :src="logo"></image>
</view>
<!-- 顶部文字 -->
<text class="title">请输入验证码</text>
<text class="tip">先输入图形验证码,再获取短信验证码</text>
......@@ -21,6 +24,7 @@
"code": "",
"phone": "",
"captcha": "",
"logo": "/static/logo.png"
}
},
computed: {
......
<!-- 免密登录页 -->
<template>
<view class="uni-content">
<view class="login-logo">
<image :src="logo"></image>
</view>
<!-- 顶部文字 -->
<text class="title">请选择登录方式</text>
<!-- 快捷登录框 当url带参数时有效 -->
......@@ -36,7 +39,8 @@
return {
type: "", //快捷登录方式
phone: "", //手机号码
focusPhone:false
focusPhone:false,
logo: "/static/logo.png"
}
},
computed: {
......@@ -177,7 +181,7 @@
}
.quickLogin {
width: 650rpx;
// width: 650rpx;
height: 350px;
align-items: center;
justify-content: center;
......@@ -186,6 +190,9 @@
.quickLoginBtn {
margin: 20px 0;
width: 450rpx;
/* #ifndef APP-NVUE */
max-width: 230px;
/* #endif */
height: 82rpx;
}
......@@ -193,4 +200,10 @@
margin-top: -15px;
margin-bottom: 20px;
}
@media screen and (min-width: 690px) {
.quickLogin{
height: auto;
}
}
</style>
<!-- 账号密码登录页 -->
<template>
<view class="uni-content">
<view class="login-logo">
<image :src="logo"></image>
</view>
<!-- 顶部文字 -->
<text class="title title-box">账号密码登录</text>
<uni-forms>
<uni-forms-item name="username">
<uni-easyinput :focus="focusUsername" @blur="focusUsername = false" class="input-box" :inputBorder="false" v-model="username" placeholder="请输入手机号/用户名" />
<uni-easyinput :focus="focusUsername" @blur="focusUsername = false" class="input-box" :inputBorder="false" v-model="username" placeholder="请输入手机号/用户名/邮箱" />
</uni-forms-item>
<uni-forms-item name="password">
<uni-easyinput :focus="focusPassword" @blur="focusPassword = false" class="input-box" clearable type="password" :inputBorder="false" v-model="password"
......@@ -45,7 +48,8 @@
"captcha": "",
"needCaptcha": false,
"focusUsername":false,
"focusPassword":false
"focusPassword":false,
"logo": "/static/logo.png"
}
},
onShow() {
......@@ -84,7 +88,7 @@
if(!this.username.length){
this.focusUsername = true
return uni.showToast({
title: '请输入手机号/用户名',
title: '请输入手机号/用户名/邮箱',
icon: 'none'
});
}
......@@ -107,6 +111,8 @@
if (/^1\d{10}$/.test(this.username)) {
data.mobile = this.username
}else if(/@/.test(this.username)) {
data.email = this.username
}else{
data.username = this.username
}
......@@ -135,7 +141,9 @@
<style lang="scss" scoped>
@import "@/uni_modules/uni-id-pages/common/login-page.scss";
@media screen and (min-width: 690px) {
}
.forget{
font-size: 12px;
color: #8a8f8b;
......
<!-- 邮箱验证码注册 -->
<template>
<view class="uni-content">
<match-media :min-width="690">
<view class="login-logo">
<image :src="logo"></image>
</view>
<!-- 顶部文字 -->
<text class="title title-box">邮箱验证码注册</text>
</match-media>
<uni-forms ref="form" :value="formData" :rules="rules" validate-trigger="submit" err-show-type="toast">
<uni-forms-item name="email" required>
<uni-easyinput :inputBorder="false" :focus="focusEmail" @blur="focusEmail = false"
class="input-box" placeholder="请输入邮箱" v-model="formData.email" trim="both" />
</uni-forms-item>
<uni-forms-item name="nickname">
<uni-easyinput :inputBorder="false" :focus="focusNickname" @blur="focusNickname = false" class="input-box" placeholder="请输入用户昵称"
v-model="formData.nickname" trim="both" />
</uni-forms-item>
<uni-forms-item name="password" v-model="formData.password" required>
<uni-easyinput :inputBorder="false" :focus="focusPassword" @blur="focusPassword = false"
class="input-box" maxlength="20" :placeholder="'请输入' + (config.passwordStrength == 'weak'?'6':'8') + '-16位密码'" type="password"
v-model="formData.password" trim="both" />
</uni-forms-item>
<uni-forms-item name="password2" v-model="formData.password2" required>
<uni-easyinput :inputBorder="false" :focus="focusPassword2" @blur="focusPassword2 =false"
class="input-box" placeholder="再次输入密码" maxlength="20" type="password" v-model="formData.password2"
trim="both" />
</uni-forms-item>
<uni-forms-item name="code" >
<uni-id-pages-email-form ref="shortCode" :email="formData.email" type="register" v-model="formData.code">
</uni-id-pages-email-form>
</uni-forms-item>
<uni-id-pages-agreements scope="register" ref="agreements" ></uni-id-pages-agreements>
<button class="uni-btn" type="primary" @click="submit">注册</button>
<button @click="navigateBack" class="register-back">返回</button>
<match-media :min-width="690">
<view class="link-box">
<text class="link" @click="registerByUserName">用户名密码注册</text>
<text class="link" @click="toLogin">已有账号?点此登录</text>
</view>
</match-media>
</uni-forms>
</view>
</template>
<script>
import rules from './validator.js';
import mixin from '@/uni_modules/uni-id-pages/common/login-page.mixin.js';
import config from '@/uni_modules/uni-id-pages/config.js'
import passwordMod from '@/uni_modules/uni-id-pages/common/password.js'
const uniIdCo = uniCloud.importObject("uni-id-co")
export default {
mixins: [mixin],
data() {
return {
formData: {
email: "",
nickname: "",
password: "",
password2: "",
code: ""
},
rules: {
email: {
rules: [{
required: true,
errorMessage: '请输入邮箱',
},{
format:'email',
errorMessage: '邮箱格式不正确',
}
]
},
nickname: {
rules: [{
minLength: 3,
maxLength: 32,
errorMessage: '昵称长度在 {minLength} 到 {maxLength} 个字符',
},
{
validateFunction: function(rule, value, data, callback) {
// console.log(value);
if (/^1\d{10}$/.test(value) || /^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/.test(value)) {
callback('昵称不能是:手机号或邮箱')
};
if (/^\d+$/.test(value)) {
callback('昵称不能为纯数字')
};
if(/[\u4E00-\u9FA5\uF900-\uFA2D]{1,}/.test(value)){
callback('昵称不能包含中文')
}
return true
}
}
],
label: "昵称"
},
...passwordMod.getPwdRules(),
code: {
rules: [{
required: true,
errorMessage: '请输入邮箱验证码',
},
{
pattern: /^.{6}$/,
errorMessage: '邮箱验证码不正确',
}
]
}
},
focusEmail:false,
focusNickname:false,
focusPassword:false,
focusPassword2:false,
logo: "/static/logo.png"
}
},
onReady() {
this.$refs.form.setRules(this.rules)
},
onShow() {
// #ifdef H5
document.onkeydown = event => {
var e = event || window.event;
if (e && e.keyCode == 13) { //回车键的键值为13
this.submit()
}
};
// #endif
},
methods: {
/**
* 触发表单提交
*/
submit() {
this.$refs.form.validate().then((res) => {
if (this.needAgreements && !this.agree) {
return this.$refs.agreements.popup(()=>{
this.submitForm(res)
})
}
this.submitForm(res)
}).catch((errors) => {
let key = errors[0].key
key = key.replace(key[0], key[0].toUpperCase())
console.log(key);
this['focus'+key] = true
})
},
submitForm(params) {
uniIdCo.registerUserByEmail(this.formData).then(e => {
console.log(e);
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withpwd',
complete: (e) => {
console.log(e);
}
})
})
.catch(e => {
console.log(e);
console.log(e.message);
})
},
navigateBack() {
uni.navigateBack()
},
toLogin() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withpwd'
})
},
registerByUserName() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/register/register'
})
}
}
}
</script>
<style lang="scss">
@import "@/uni_modules/uni-id-pages/common/login-page.scss";
@media screen and (max-width: 690px) {
.uni-content{
margin-top: 15px;
}
}
@media screen and (min-width: 690px) {
.uni-content{
padding: 30px 40px;
max-height: 550px;
}
.link-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: space-between;
margin-top: 10px;
}
.link {
font-size: 12px;
}
}
.uni-content ::v-deep .uni-forms-item__label {
position: absolute;
left: -15px;
}
button {
margin-top: 15px;
}
</style>
<!-- 账号注册页 -->
<template>
<view class="uni-content">
<match-media :min-width="690">
<view class="login-logo">
<image :src="logo"></image>
</view>
<!-- 顶部文字 -->
<text class="title title-box">用户名密码注册</text>
</match-media>
<uni-forms ref="form" :value="formData" :rules="rules" validate-trigger="submit" err-show-type="toast">
<uni-forms-item name="username" required>
<uni-easyinput :inputBorder="false" :focus="focusUsername" @blur="focusUsername = false"
......@@ -12,7 +19,7 @@
</uni-forms-item>
<uni-forms-item name="password" v-model="formData.password" required>
<uni-easyinput :inputBorder="false" :focus="focusPassword" @blur="focusPassword = false"
class="input-box" maxlength="20" :placeholder="'请输入' + passwordLength[0] + '-' + passwordLength[1] + '位密码'" type="password"
class="input-box" maxlength="20" :placeholder="'请输入' + (config.passwordStrength == 'weak'?'6':'8') + '-16位密码'" type="password"
v-model="formData.password" trim="both" />
</uni-forms-item>
<uni-forms-item name="password2" v-model="formData.password2" required>
......@@ -25,7 +32,13 @@
</uni-forms-item>
<uni-id-pages-agreements scope="register" ref="agreements" ></uni-id-pages-agreements>
<button class="uni-btn" type="primary" @click="submit">注册</button>
<button @click="navigateBack">返回</button>
<button @click="navigateBack" class="register-back">返回</button>
<match-media :min-width="690">
<view class="link-box">
<text class="link" @click="registerByEmail">邮箱验证码注册</text>
<text class="link" @click="toLogin">已有账号?点此登录</text>
</view>
</match-media>
</uni-forms>
</view>
</template>
......@@ -50,12 +63,8 @@
focusUsername:false,
focusNickname:false,
focusPassword:false,
focusPassword2:false
}
},
computed:{
passwordLength(){
return config.password.length
focusPassword2:false,
logo: "/static/logo.png"
}
},
onReady() {
......@@ -111,6 +120,16 @@
},
navigateBack() {
uni.navigateBack()
},
toLogin() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withpwd'
})
},
registerByEmail() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/register/register-by-email'
})
}
}
}
......@@ -118,9 +137,33 @@
<style lang="scss">
@import "@/uni_modules/uni-id-pages/common/login-page.scss";
.uni-content{
margin-top: 15px;
@media screen and (max-width: 690px) {
.uni-content{
margin-top: 15px;
height: 100%;
background-color: #fff;
}
}
@media screen and (min-width: 690px) {
.uni-content{
padding: 30px 40px 60px;
}
.link-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: space-between;
margin-top: 10px;
}
.link {
font-size: 12px;
}
}
.uni-content ::v-deep .uni-forms-item__label {
position: absolute;
left: -15px;
......
<!-- 找回密码页 -->
<template>
<view class="uni-content">
<match-media :min-width="690">
<view class="login-logo">
<image :src="logo"></image>
</view>
<!-- 顶部文字 -->
<text class="title title-box">通过邮箱验证码找回密码</text>
</match-media>
<uni-forms ref="form" :value="formData" err-show-type="toast">
<uni-forms-item name="email">
<uni-easyinput :focus="focusEmail" @blur="focusEmail = false" class="input-box" :disabled="lock" :inputBorder="false"
v-model="formData.email" placeholder="请输入邮箱">
</uni-easyinput>
</uni-forms-item>
<uni-forms-item name="code">
<uni-id-pages-email-form ref="shortCode" :email="formData.email" type="reset-pwd-by-email" v-model="formData.code">
</uni-id-pages-email-form>
</uni-forms-item>
<uni-forms-item name="password">
<uni-easyinput :focus="focusPassword" @blur="focusPassword = false" class="input-box" type="password" :inputBorder="false" v-model="formData.password"
placeholder="请输入新密码"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="password2">
<uni-easyinput :focus="focusPassword2" @blur="focusPassword2 = false" class="input-box" type="password" :inputBorder="false" v-model="formData.password2"
placeholder="请再次输入新密码"></uni-easyinput>
</uni-forms-item>
<button class="uni-btn send-btn-box" type="primary" @click="submit">提交</button>
<match-media :min-width="690">
<view class="link-box">
<text class="link" @click="retrieveByPhone">通过手机验证码找回密码</text>
<view></view>
</view>
</match-media>
</uni-forms>
<uni-popup-captcha @confirm="submit" v-model="formData.captcha" scene="reset-pwd-by-sms" ref="popup"></uni-popup-captcha>
</view>
</template>
<script>
import mixin from '@/uni_modules/uni-id-pages/common/login-page.mixin.js';
import passwordMod from '@/uni_modules/uni-id-pages/common/password.js'
const uniIdCo = uniCloud.importObject("uni-id-co",{
errorOptions:{
type:'toast'
}
})
export default {
mixins: [mixin],
data() {
return {
lock: false,
focusEmail:true,
focusPassword:false,
focusPassword2:false,
formData: {
"email": "",
"code": "",
'password': '',
'password2': '',
"captcha": ""
},
rules: {
email: {
rules: [{
required: true,
errorMessage: '请输入邮箱',
},
{
format:'email',
errorMessage: '邮箱格式不正确',
}
]
},
code: {
rules: [{
required: true,
errorMessage: '请输入邮箱验证码',
},
{
pattern: /^.{6}$/,
errorMessage: '请输入6位验证码',
}
]
},
...passwordMod.getPwdRules()
},
logo: "/static/logo.png"
}
},
computed: {
isEmail() {
let reg_email = /@/;
let isEmail = reg_email.test(this.formData.email);
return isEmail;
},
isPwd() {
let reg_pwd = /^.{6,20}$/;
let isPwd = reg_pwd.test(this.formData.password);
return isPwd;
},
isCode() {
let reg_code = /^\d{6}$/;
let isCode = reg_code.test(this.formData.code);
return isCode;
}
},
onLoad(event) {
if (event && event.emailNumber) {
this.formData.email = event.emailNumber;
if(event.lock){
this.lock = event.lock //如果是已经登录的账号,点击找回密码就锁定指定的账号绑定的邮箱码
this.focusEmail = true
}
}
},
onReady() {
if (this.formData.email) {
this.$refs.shortCode.start();
}
this.$refs.form.setRules(this.rules)
},
onShow() {
// #ifdef H5
document.onkeydown = event => {
var e = event || window.event;
if (e && e.keyCode == 13) { //回车键的键值为13
this.submit()
}
};
// #endif
},
methods: {
/**
* 完成并提交
*/
submit() {
console.log("formData", this.formData);
console.log('rules', this.rules);
this.$refs.form.validate()
.then(res => {
let {
email,
password: password,
captcha,
code
} = this.formData
uniIdCo.resetPwdByEmail({
email,
code,
password,
captcha
}).then(e => {
console.log(e);
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withpwd',
complete: (e) => {
console.log(e);
}
})
})
.catch(e => {
if (e.errCode == 'uni-id-captcha-required') {
this.$refs.popup.open()
}
}).finally(e => {
this.formData.captcha = ""
})
}).catch(errors=>{
let key = errors[0].key
if(key == 'code'){
console.log(this.$refs.shortCode);
return this.$refs.shortCode.focusSmsCodeInput = true
}
key = key.replace(key[0], key[0].toUpperCase())
console.log(key,'focus'+key);
this['focus'+key] = true
})
},
retrieveByPhone() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/retrieve/retrieve'
})
}
}
}
</script>
<style lang="scss">
@import "@/uni_modules/uni-id-pages/common/login-page.scss";
@media screen and (max-width: 690px) {
.uni-content{
margin-top: 15px;
}
}
@media screen and (min-width: 690px) {
.uni-content{
padding: 30px 40px 40px;
}
.link-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: space-between;
margin-top: 10px;
}
.link {
font-size: 12px;
}
}
</style>
<!-- 找回密码页 -->
<template>
<view class="uni-content">
<match-media :min-width="690">
<view class="login-logo">
<image :src="logo"></image>
</view>
<!-- 顶部文字 -->
<text class="title title-box">通过手机验证码找回密码</text>
</match-media>
<uni-forms ref="form" :value="formData" err-show-type="toast">
<uni-forms-item name="phone">
<uni-easyinput :focus="focusPhone" @blur="focusPhone = false" class="input-box" :disabled="lock" type="number" :inputBorder="false"
......@@ -20,6 +27,12 @@
placeholder="请再次输入新密码"></uni-easyinput>
</uni-forms-item>
<button class="uni-btn send-btn-box" type="primary" @click="submit">提交</button>
<match-media :min-width="690">
<view class="link-box">
<text class="link" @click="retrieveByEmail">通过邮箱验证码找回密码</text>
<view></view>
</view>
</match-media>
</uni-forms>
<uni-popup-captcha @confirm="submit" v-model="formData.captcha" scene="reset-pwd-by-sms" ref="popup"></uni-popup-captcha>
</view>
......@@ -101,7 +114,8 @@
}
]
}
}
},
logo: "/static/logo.png"
}
},
computed: {
......@@ -187,6 +201,11 @@
console.log(key,'focus'+key);
this['focus'+key] = true
})
},
retrieveByEmail() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/retrieve/retrieve-by-email'
})
}
}
}
......@@ -195,7 +214,26 @@
<style lang="scss">
@import "@/uni_modules/uni-id-pages/common/login-page.scss";
.uni-content {
margin-top: 15px;
@media screen and (max-width: 690px) {
.uni-content{
margin-top: 15px;
}
}
@media screen and (min-width: 690px) {
.uni-content{
padding: 30px 40px 40px;
}
.link-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: space-between;
margin-top: 10px;
}
.link {
font-size: 12px;
}
}
</style>
<!-- 绑定手机号码页 -->
<template>
<view class="uni-content">
<match-media :min-width="690">
<view class="login-logo">
<image :src="logo"></image>
</view>
<!-- 顶部文字 -->
<text class="title title-box">绑定手机号</text>
</match-media>
<!-- 登录框 (选择手机号所属国家和地区需要另行实现) -->
<uni-easyinput clearable :focus="focusMobile" @blur="focusMobile = false" type="number" class="input-box" :inputBorder="false" v-model="formData.mobile"
maxlength="11" placeholder="请输入手机号"></uni-easyinput>
......@@ -20,7 +27,8 @@
code: "",
captcha: ""
},
focusMobile:true
focusMobile:true,
logo: "/static/logo.png"
}
},
computed: {
......@@ -90,6 +98,13 @@
padding: 50rpx;
padding-top: 10px;
}
@media screen and (min-width: 690px) {
.uni-content{
padding: 30px 40px 40px;
}
}
/* #ifndef APP-NVUE || VUE3 */
.uni-content ::v-deep .uni-easyinput__content {}
......
<!-- 修改密码 -->
<template>
<view class="uni-content">
<match-media :min-width="690">
<view class="login-logo">
<image :src="logo"></image>
</view>
<!-- 顶部文字 -->
<text class="title title-box">修改密码</text>
</match-media>
<uni-forms ref="form" :value="formData" err-show-type="toast">
<uni-forms-item name="oldPassword">
<uni-easyinput :focus="focusOldPassword" @blur="focusOldPassword = false" class="input-box"
......@@ -81,7 +88,8 @@
}
]
}
}
},
logo: "/static/logo.png"
}
},
onReady() {
......@@ -140,7 +148,15 @@
<style lang="scss">
@import "@/uni_modules/uni-id-pages/common/login-page.scss";
.uni-content {
margin-top: 15px;
@media screen and (max-width: 690px) {
.uni-content{
margin-top: 15px;
}
}
@media screen and (min-width: 690px) {
.uni-content{
padding: 30px 40px 40px;
}
}
</style>
<!-- 注销(销毁)账号 -->
<template>
<view class="content">
<view class="uni-content">
<text class="words" space="emsp">
一、注销是不可逆操作,注销后:\n
1.帐号将无法登录、无法找回。\n
......@@ -64,7 +64,7 @@
</script>
<style>
.content {
.uni-content {
display: flex;
flex-direction: column;
font-size: 28rpx;
......@@ -77,20 +77,6 @@
margin-bottom: 80px;
}
.button-group {
display: flex;
flex-direction: row;
position: fixed;
height: 50px;
bottom: 10px;
width: 750rpx;
justify-content: center;
align-items: center;
border-top: solid 1px #e4e6ec;
padding-top: 10px;
background-color: #FFFFFF;
}
.button-group button {
border-radius: 100px;
border: none;
......@@ -108,4 +94,26 @@
color: #e64340;
border: solid 1px #e64340;
}
.button-group {
display: flex;
flex-direction: row;
position: fixed;
height: 50px;
bottom: 10px;
width: 750rpx;
justify-content: center;
align-items: center;
border-top: solid 1px #e4e6ec;
padding-top: 10px;
background-color: #FFFFFF;
max-width: 690px;
}
@media screen and (min-width: 690px) {
.uni-content{
max-width: 690px;
margin-left: calc(50% - 345px);
}
}
</style>
const db = uniCloud.database();
const usersTable = db.collection('uni-id-users')
const uniIdCo = uniCloud.importObject("uni-id-co")
export default {
data() {
return {
univerifyStyle: {
authButton: {
"title": "本机号码一键绑定", // 授权按钮文案
},
otherLoginButton: {
"title": "其他号码绑定",
}
},
userInfo: {
mobile: '',
nickname: ''
},
hasLogin: false,
hasPwd: false
}
},
async onShow() {
this.univerifyStyle.authButton.title = "本机号码一键绑定"
this.univerifyStyle.otherLoginButton.title = "其他号码绑定"
},
async onLoad() {
this.getUserInfo()
//判断当前用户是否有密码,否则就不显示密码修改功能
let res = await uniIdCo.getAccountInfo()
this.hasPwd = res.isPasswordSet
},
methods: {
login() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withoutpwd',
complete: (e) => {
console.log(e);
}
})
},
async logout() {
await uniIdCo.logout()
uni.removeStorageSync('uni_id_token');
uni.setStorageSync('uni_id_token_expired', 0)
uni.redirectTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withoutpwd',
});
},
changePassword() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/userinfo/change_pwd/change_pwd',
complete: (e) => {
console.log(e);
}
})
},
getUserInfo(e) {
uni.showLoading({
mask: true
});
usersTable.where("'_id' == $cloudEnv_uid").field('mobile,nickname').get().then(res => {
console.log({
res
});
this.userInfo = res.result.data[0]
console.log('this.userInfo', this.userInfo);
this.hasLogin = true
}).catch(e => {
this.userInfo = {}
this.hasLogin = false
console.log(e.message, e.errCode);
}).finally(e => {
// console.log(e);
uni.hideLoading()
})
},
bindMobile() {
// #ifdef APP-PLUS
uni.preLogin({
provider: 'univerify',
success: this.univerify(), //预登录成功
fail: (res) => { // 预登录失败
// 不显示一键登录选项(或置灰)
console.log(res)
this.bindMobileBySmsCode()
}
})
// #endif
// #ifdef MP-WEIXIN
this.$refs['bind-mobile'].open()
// #endif
// #ifdef H5
//...去用验证码绑定
this.bindMobileBySmsCode()
// #endif
},
univerify() {
uni.login({
"provider": 'univerify',
"univerifyStyle": this.univerifyStyle,
success: async e => {
console.log(e.authResult);
uniIdCo.bindMobileByUniverify(e.authResult).then(res => {
console.log(res);
this.getUserInfo()
}).catch(e => {
console.log(e);
}).finally(e => {
console.log(e);
uni.closeAuthView()
})
},
fail: (err) => {
console.log(err);
if (err.code == '30002' || err.code == '30001') {
this.bindMobileBySmsCode()
}
}
})
},
bindMobileBySmsCode() {
uni.navigateTo({
url: './bind-mobile/bind-mobile',
events: {
getUserInfo: () => {
this.getUserInfo()
}
},
complete(e) {
console.log(e);
}
})
},
setNickname(nickname) {
console.log(nickname);
if (nickname) {
usersTable.where('_id==$env.uid').update({
nickname
}).then(e => {
console.log(e);
if (e.result.updated) {
uni.showToast({
title: "更新成功",
icon: 'none'
});
this.userInfo.nickname = nickname
} else {
uni.showToast({
title: "没有改变",
icon: 'none'
});
}
})
this.$refs.dialog.close()
} else {
this.$refs.dialog.open()
}
},
deactivate() {
uni.navigateTo({
url: "/uni_modules/uni-id-pages/pages/userinfo/deactivate/deactivate"
})
}
}
}
<!-- 用户资料页 -->
<template>
<view class="uni-content">
<view class="avatar">
<uni-id-pages-avatar width="260rpx" height="260rpx"></uni-id-pages-avatar>
</view>
<uni-list>
<uni-list-item class="item" @click="setNickname('')" title="昵称" :rightText="userInfo.nickname||'未设置'" link>
</uni-list-item>
<template>
<view class="uni-content">
<view class="avatar">
<uni-id-pages-avatar width="260rpx" height="260rpx"></uni-id-pages-avatar>
</view>
<uni-list>
<uni-list-item class="item" @click="setNickname('')" title="昵称" :rightText="userInfo.nickname||'未设置'" link>
</uni-list-item>
<uni-list-item class="item" @click="bindMobile" title="手机号" :rightText="userInfo.mobile||'未绑定'" link>
</uni-list-item>
<uni-list-item v-if="hasPwd" class="item" @click="changePassword" title="修改密码" link>
</uni-list-item>
</uni-list>
<uni-list class="mt10">
<uni-list-item @click="deactivate" title="注销账号" link="navigateTo"></uni-list-item>
</uni-list>
<uni-popup ref="dialog" type="dialog">
<uni-popup-dialog mode="input" :value="userInfo.nickname" @confirm="setNickname" title="设置昵称"
placeholder="请输入要设置的昵称">
</uni-popup-dialog>
</uni-popup>
<uni-id-pages-bind-mobile ref="bind-mobile-by-sms" @success="getUserInfo"></uni-id-pages-bind-mobile>
<button v-if="hasLogin" @click="logout">退出登录</button>
<button v-else @click="login">去登录</button>
</view>
</template>
<script>
const db = uniCloud.database();
const usersTable = db.collection('uni-id-users')
const uniIdCo = uniCloud.importObject("uni-id-co")
export default {
data() {
return {
univerifyStyle: {
authButton: {
"title": "本机号码一键绑定", // 授权按钮文案
},
otherLoginButton: {
"title": "其他号码绑定",
}
},
userInfo: {
mobile:'',
nickname:''
},
</uni-list-item>
</uni-list>
<uni-list class="mt10">
<uni-list-item @click="deactivate" title="注销账号" link="navigateTo"></uni-list-item>
</uni-list>
<uni-popup ref="dialog" type="dialog">
<uni-popup-dialog mode="input" :value="userInfo.nickname" @confirm="setNickname" title="设置昵称"
placeholder="请输入要设置的昵称">
</uni-popup-dialog>
</uni-popup>
<uni-id-pages-bind-mobile ref="bind-mobile-by-sms" @success="getUserInfo"></uni-id-pages-bind-mobile>
<template v-if="showLoginManage">
<button v-if="hasLogin" @click="logout">退出登录</button>
<button v-else @click="login">去登录</button>
</template>
</view>
</template>
<script>
const db = uniCloud.database();
const usersTable = db.collection('uni-id-users')
const uniIdCo = uniCloud.importObject("uni-id-co")
import common from '@/uni_modules/uni-id-pages/common/common.js';
export default {
data() {
return {
univerifyStyle: {
authButton: {
"title": "本机号码一键绑定", // 授权按钮文案
},
otherLoginButton: {
"title": "其他号码绑定",
}
},
userInfo: {
mobile:'',
nickname:''
},
hasLogin: false,
hasPwd:false
}
},
async onShow() {
this.univerifyStyle.authButton.title = "本机号码一键绑定"
this.univerifyStyle.otherLoginButton.title = "其他号码绑定"
},
async onLoad() {
hasPwd:false,
showLoginManage:false//通过页面传参隐藏登录&退出登录按钮
}
},
async onShow() {
this.univerifyStyle.authButton.title = "本机号码一键绑定"
this.univerifyStyle.otherLoginButton.title = "其他号码绑定"
},
async onLoad(e) {
if(e.showLoginManage){
this.showLoginManage = true//通过页面传参隐藏登录&退出登录按钮
}
this.getUserInfo()
//判断当前用户是否有密码,否则就不显示密码修改功能
let res = await uniIdCo.getAccountInfo()
this.hasPwd = res.isPasswordSet
},
methods: {
login() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withoutpwd',
complete: (e) => {
console.log(e);
}
})
},
async logout() {
await uniIdCo.logout()
uni.removeStorageSync('uni_id_token');
uni.setStorageSync('uni_id_token_expired', 0)
uni.redirectTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withoutpwd',
});
this.hasPwd = res.isPasswordSet
},
methods: {
login() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withoutpwd',
complete: (e) => {
console.log(e);
}
})
},
logout:common.logout,
changePassword(){
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/userinfo/change_pwd/change_pwd',
......@@ -82,155 +82,165 @@
console.log(e);
}
})
},
getUserInfo(e) {
uni.showLoading({
mask: true
});
},
getUserInfo(e) {
uni.showLoading({
mask: true
});
usersTable.where("'_id' == $cloudEnv_uid").field('mobile,nickname').get().then(res => {
console.log({res});
this.userInfo = res.result.data[0]
console.log('this.userInfo', this.userInfo);
this.hasLogin = true
}).catch(e => {
this.userInfo = {}
this.hasLogin = false
console.log(e.message, e.errCode);
}).finally(e => {
// console.log(e);
uni.hideLoading()
})
},
bindMobile() {
// #ifdef APP-PLUS
uni.preLogin({
provider: 'univerify',
success: this.univerify(), //预登录成功
fail: (res) => { // 预登录失败
// 不显示一键登录选项(或置灰)
console.log(res)
this.bindMobileBySmsCode()
}
})
// #endif
// #ifdef MP-WEIXIN
this.$refs['bind-mobile'].open()
// #endif
// #ifdef H5
//...去用验证码绑定
this.bindMobileBySmsCode()
// #endif
},
univerify() {
uni.login({
"provider": 'univerify',
"univerifyStyle": this.univerifyStyle,
success: async e => {
console.log(e.authResult);
uniIdCo.bindMobileByUniverify(e.authResult).then(res => {
console.log(res);
this.getUserInfo()
}).catch(e => {
console.log(e);
}).finally(e=>{
console.log(e);
uni.closeAuthView()
})
},
fail: (err) => {
console.log(err);
if (err.code == '30002' || err.code == '30001') {
this.bindMobileBySmsCode()
}
}
})
},
bindMobileBySmsCode() {
uni.navigateTo({
url: './bind-mobile/bind-mobile',
events: {
getUserInfo: () => {
this.getUserInfo()
}
},
complete(e) {
console.log(e);
}
})
},
setNickname(nickname) {
console.log(nickname);
if (nickname) {
usersTable.where('_id==$env.uid').update({
nickname
}).then(e => {
console.log(e);
if (e.result.updated) {
uni.showToast({
title: "更新成功",
icon: 'none'
});
this.userInfo.nickname = nickname
} else {
uni.showToast({
title: "没有改变",
icon: 'none'
});
}
})
this.$refs.dialog.close()
} else {
this.$refs.dialog.open()
}
},
deactivate(){
uni.navigateTo({
url:"/uni_modules/uni-id-pages/pages/userinfo/deactivate/deactivate"
})
}
}
}
</script>
<style lang="scss" scoped>
@import url("/uni_modules/uni-id-pages/common/login-page.scss");
.uni-content {
padding: 0;
}
/* #ifndef APP-NVUE */
view {
display: flex;
box-sizing: border-box;
flex-direction: column;
}
/* #endif */
.avatar {
align-items: center;
justify-content: center;
margin: 22px 0;
width: 100%;
}
.item {
flex: 1;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
button {
margin: 10%;
margin-top: 40px;
border-radius: 0;
background-color: #FFFFFF;
width: 80%;
}
.mt10{
margin-top: 10px;
}
</style>
console.log({res});
this.userInfo = res.result.data[0]
console.log('this.userInfo', this.userInfo);
this.hasLogin = true
}).catch(e => {
this.userInfo = {}
this.hasLogin = false
console.log(e.message, e.errCode);
}).finally(e => {
// console.log(e);
uni.hideLoading()
})
},
bindMobile() {
// #ifdef APP-PLUS
uni.preLogin({
provider: 'univerify',
success: this.univerify(), //预登录成功
fail: (res) => { // 预登录失败
// 不显示一键登录选项(或置灰)
console.log(res)
this.bindMobileBySmsCode()
}
})
// #endif
// #ifdef MP-WEIXIN
this.$refs['bind-mobile'].open()
// #endif
// #ifdef H5
//...去用验证码绑定
this.bindMobileBySmsCode()
// #endif
},
univerify() {
uni.login({
"provider": 'univerify',
"univerifyStyle": this.univerifyStyle,
success: async e => {
console.log(e.authResult);
uniIdCo.bindMobileByUniverify(e.authResult).then(res => {
console.log(res);
this.getUserInfo()
}).catch(e => {
console.log(e);
}).finally(e=>{
console.log(e);
uni.closeAuthView()
})
},
fail: (err) => {
console.log(err);
if (err.code == '30002' || err.code == '30001') {
this.bindMobileBySmsCode()
}
}
})
},
bindMobileBySmsCode() {
uni.navigateTo({
url: './bind-mobile/bind-mobile',
events: {
getUserInfo: () => {
this.getUserInfo()
}
},
complete(e) {
console.log(e);
}
})
},
setNickname(nickname) {
console.log(nickname);
if (nickname) {
usersTable.where('_id==$env.uid').update({
nickname
}).then(e => {
console.log(e);
if (e.result.updated) {
uni.showToast({
title: "更新成功",
icon: 'none'
});
this.userInfo.nickname = nickname
} else {
uni.showToast({
title: "没有改变",
icon: 'none'
});
}
})
this.$refs.dialog.close()
} else {
this.$refs.dialog.open()
}
},
deactivate(){
uni.navigateTo({
url:"/uni_modules/uni-id-pages/pages/userinfo/deactivate/deactivate"
})
}
}
}
</script>
<style lang="scss" scoped>
@import url("/uni_modules/uni-id-pages/common/login-page.scss");
.uni-content {
padding: 0;
}
/* #ifndef APP-NVUE */
view {
display: flex;
box-sizing: border-box;
flex-direction: column;
}
@media screen and (min-width: 690px) {
.uni-content {
padding: 0;
max-width: 690px;
margin-left: calc(50% - 345px);
border: none;
max-height: none;
border-radius: 0;
box-shadow: none;
}
}
/* #endif */
.avatar {
align-items: center;
justify-content: center;
margin: 22px 0;
width: 100%;
}
.item {
flex: 1;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
button {
margin: 10%;
margin-top: 40px;
border-radius: 0;
background-color: #FFFFFF;
width: 80%;
}
.mt10{
margin-top: 10px;
}
</style>
{
"pages": [
{
"path": "uni_modules/uni-id-pages/pages/userinfo/deactivate/deactivate",
"style": {
"navigationBarTitleText": "注销账号"
}
},
{
"path": "uni_modules/uni-id-pages/pages/userinfo/userinfo",
"style": {
"navigationBarTitleText": "个人资料"
}
},
{
"path": "uni_modules/uni-id-pages/pages/userinfo/bind-mobile/bind-mobile",
"style": {
"navigationBarTitleText": "绑定手机号码"
}
},
{
"path": "uni_modules/uni-id-pages/pages/userinfo/cropImage/cropImage",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "uni_modules/uni-id-pages/pages/login/login-withoutpwd",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "uni_modules/uni-id-pages/pages/login/login-withpwd",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "uni_modules/uni-id-pages/pages/login/login-smscode",
"style": {
"navigationBarTitleText": "手机验证码登录"
}
},
{
"path": "uni_modules/uni-id-pages/pages/register/register",
"style": {
"navigationBarTitleText": "注册"
}
},
{
"path": "uni_modules/uni-id-pages/pages/register/register-by-email",
"style": {
"navigationBarTitleText": "邮箱验证码注册"
}
},
{
"path": "uni_modules/uni-id-pages/pages/retrieve/retrieve",
"style": {
"navigationBarTitleText": "重置密码"
}
},
{
"path": "uni_modules/uni-id-pages/pages/retrieve/retrieve-by-email",
"style": {
"navigationBarTitleText": "通过邮箱重置密码"
}
},
{
"path": "uni_modules/uni-id-pages/pages/common/webview/webview",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}
,{
"path" : "uni_modules/uni-id-pages/pages/userinfo/change_pwd/change_pwd",
"style" :
{
"navigationBarTitleText": "修改密码",
"enablePullDownRefresh": false
}
}
]
}
\ No newline at end of file
......@@ -29,6 +29,21 @@ baseValidator.username = function (username) {
}
}
baseValidator.password = function (password) {
const errCode = ERROR.INVALID_PASSWORD
if (!isValidString(password)) {
return {
errCode
}
}
if (password.length < 6) {
// 密码长度不能小于6
return {
errCode
}
}
}
baseValidator.mobile = function (mobile) {
const errCode = ERROR.INVALID_MOBILE
if (!isValidString(mobile)) {
......@@ -92,7 +107,7 @@ baseType.forEach((type) => {
}
})
function tokenize (name) {
function tokenize(name) {
let i = 0
const result = []
let token = ''
......@@ -122,7 +137,7 @@ function tokenize (name) {
* 处理validator名
* @param {string} name
*/
function parseValidatorName (name) {
function parseValidatorName(name) {
const tokenList = tokenize(name)
let i = 0
let currentToken = tokenList[i]
......@@ -172,7 +187,7 @@ function parseValidatorName (name) {
return result
}
function getRuleCategory (rule) {
function getRuleCategory(rule) {
switch (rule.type) {
case 'array':
return 'array'
......@@ -183,7 +198,7 @@ function getRuleCategory (rule) {
}
}
function isMatchUnionType (val, rule) {
function isMatchUnionType(val, rule) {
if (!rule.children || rule.children.length === 0) {
return true
}
......@@ -209,7 +224,7 @@ function isMatchUnionType (val, rule) {
return false
}
function isMatchBaseType (val, rule) {
function isMatchBaseType(val, rule) {
if (typeof baseValidator[rule.type] !== 'function') {
throw new Error(`invalid schema type: ${rule.type}`)
}
......@@ -220,7 +235,7 @@ function isMatchBaseType (val, rule) {
return true
}
function isMatchArrayType (arr, rule) {
function isMatchArrayType(arr, rule) {
if (getType(arr) !== 'array') {
return false
}
......@@ -249,11 +264,12 @@ const passwordRules = {
// 密码必须为字母、数字和特殊符号任意两种的组合
medium: /^(?![0-9]+$)(?![a-zA-Z]+$)(?![~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]+$)[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
// 密码必须包含字母和数字
weak: /^(?=.*[0-9])(?=.*[a-zA-Z])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{6,16}$/
weak: /^(?=.*[0-9])(?=.*[a-zA-Z])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{6,16}$/,
}
function createPasswordVerifier ({
passwordStrength = 'medium'
function createPasswordVerifier({
passwordStrength = ''
} = {}) {
return function (password) {
const passwordRegExp = passwordRules[passwordStrength]
......@@ -275,28 +291,30 @@ function createPasswordVerifier ({
}
class Validator {
constructor ({
passwordStrength = 'medium'
constructor({
passwordStrength = ''
} = {}) {
this.baseValidator = baseValidator
this.customValidator = Object.create(null)
this.mixin(
'password',
createPasswordVerifier({
passwordStrength
})
)
if (passwordStrength) {
this.mixin(
'password',
createPasswordVerifier({
passwordStrength
})
)
}
}
mixin (type, handler) {
mixin(type, handler) {
this.customValidator[type] = handler
}
getRealBaseValidator (type) {
getRealBaseValidator(type) {
return this.customValidator[type] || this.baseValidator[type]
}
get validator () {
get validator() {
return new Proxy({}, {
get: (_, prop) => {
if (typeof prop !== 'string') {
......@@ -318,7 +336,7 @@ class Validator {
})
}
validate (value = {}, schema = {}) {
validate(value = {}, schema = {}) {
for (const schemaKey in schema) {
let schemaValue = schema[schemaKey]
if (getType(schemaValue) === 'string') {
......@@ -358,7 +376,7 @@ class Validator {
}
}
function checkClientInfo (clientInfo) {
function checkClientInfo(clientInfo) {
const stringNotRequired = {
required: false,
type: 'string'
......
const assert = require('assert')
const {
Validator
} = require('./validator')
const {
ERROR
} = require('./error')
const {
getType
} = require('./utils')
const testCaseList = [{
value: {
username: 'uname'
},
schema: {
username: 'username'
},
expected: undefined
}, {
value: {
username: '123456'
},
schema: {
username: 'username'
},
expected: {
errCode: ERROR.INVALID_USERNAME
}
}, {
value: {
username: '数字天堂'
},
schema: {
username: 'username'
},
expected: {
errCode: ERROR.INVALID_USERNAME
}
}, {
value: {
password: '123456abc'
},
schema: {
password: 'password'
}
}, {
value: {
password: '123456def'
},
schema: {
password: 'password'
},
mixin: [{
type: 'password',
handler: function (password) {
if (typeof password !== 'string' || password.length < 10) {
return {
errCode: ERROR.INVALID_PASSWORD
}
}
}
}],
expected: {
errCode: ERROR.INVALID_PASSWORD
}
}, {
value: {
numberOrString: '123456'
},
schema: {
numberOrString: 'number|string'
}
}, {
value: {
numberOrString: 123456
},
schema: {
numberOrString: 'number|string'
}
}, {
value: {
numberOrString: '123456'
},
schema: {
numberOrString: 'string|number'
}
}, {
value: {
numberOrString: 123456
},
schema: {
numberOrString: 'string|number'
}
}, {
value: {
numberOrString: ['123456']
},
schema: {
numberOrString: 'array<number|string>'
}
}, {
value: {
numberOrString: [123456]
},
schema: {
numberOrString: 'array<number|string>'
}
}, {
value: {
numberOrString: [123456, '123456']
},
schema: {
numberOrString: 'array<number|string>'
}
}, {
value: {
numberOrString: ['123456']
},
schema: {
numberOrString: 'array<string|number>'
}
}, {
value: {
numberOrString: [123456]
},
schema: {
numberOrString: 'array<string|number>'
}
}, {
value: {
numberOrString: [123456, '123456']
},
schema: {
numberOrString: 'array<string|number>'
}
}, {
value: {
numberOrString: ['123456']
},
mixin: [{
type: '1to6',
handler: function (val) {
if (val !== '123456') {
return {
errCode: ERROR.INVALID_PARAM
}
}
}
}],
schema: {
numberOrString: 'array<number|1to6>'
}
}]
function execTestCase ({
value = {},
schema = {},
mixin = [],
expected = undefined,
error = ''
} = {}) {
const validator = new Validator()
for (let i = 0; i < mixin.length; i++) {
const {
type,
handler
} = mixin[i]
validator.mixin(type, handler)
}
let validateResult,
validateError
try {
validateResult = validator.validate(value, schema)
} catch (err) {
validateError = err
}
const tag = JSON.stringify({ value, schema })
if (error) {
if (typeof error === 'string') {
assert.strictEqual(validateError.message.indexOf(error) > -1, true, `[${tag}] error message error`)
} else if (getType(error) === 'regexp') {
assert.strictEqual(error.test(validateError), true, `[${tag}] error message error`)
} else {
throw new Error(`[${tag}] invalid test case`)
}
return
}
if (expected === undefined) {
assert.strictEqual(validateResult, undefined, `[${tag}] validate result error`)
return
}
const expectedKeys = Object.keys(expected)
let passResultCheck = true
for (let i = 0; i < expectedKeys.length; i++) {
const key = expectedKeys[i]
if (expected[key] !== validateResult[key]) {
passResultCheck = false
break
}
}
assert.strictEqual(passResultCheck, true, `[${tag}] validate result error`)
}
for (let i = 0; i < testCaseList.length; i++) {
console.log(`test case: ${i}`)
execTestCase(testCaseList[i])
console.log(`test case: ${i}, pass`)
}
......@@ -7,6 +7,9 @@ module.exports = {
// permission: [] // 允许进行此操作的权限,包含任一权限均可操作。
// 权限角色均配置时,用户拥有任一权限或任一角色均可操作
},
updateUser: {
role: ['admin']
},
authorizeAppLogin: {
role: ['admin']
},
......
......@@ -18,7 +18,8 @@ const {
registerUser
} = require('./module/register/index')
const {
addUser
addUser,
updateUser
} = require('./module/admin/index')
const {
login,
......@@ -109,7 +110,7 @@ module.exports = {
this.hooks = this.configUtils.getHooks()
this.validator = new Validator({
passwordStrength: this.config.passwordStrength || 'medium'
passwordStrength: this.config.passwordStrength
})
/**
* 示例:覆盖密码验证规则
......@@ -216,6 +217,23 @@ module.exports = {
* @returns
*/
addUser,
/**
* 修改用户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#update-user
* @param {Object} params
* @param {String} params.id 要更新的用户id
* @param {String} params.username 用户名
* @param {String} params.password 密码
* @param {String} params.nickname 昵称
* @param {Array} params.authorizedApp 允许登录的AppID列表
* @param {Array} params.role 用户角色列表
* @param {String} params.mobile 手机号
* @param {String} params.email 邮箱
* @param {Array} params.tags 用户标签
* @param {Number} params.status 用户状态
* @returns
*/
updateUser,
/**
* 授权用户登录应用
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#authorize-app-login
......@@ -490,4 +508,4 @@ module.exports = {
* @returns
*/
getSupportedLoginType
}
}
......@@ -4,8 +4,8 @@ const {
const createConfig = require('uni-config-center')
const requiredConfig = {
'web.h5-weixin': ['appid', 'appsecret'],
'web.web-weixin': ['appid', 'appsecret'],
'web.weixin-h5': ['appid', 'appsecret'],
'web.weixin-web': ['appid', 'appsecret'],
'app.weixin': ['appid', 'appsecret'],
'mp-weixin.weixin': ['appid', 'appsecret'],
'app.qq': ['appid', 'appsecret'],
......
......@@ -8,6 +8,9 @@ const {
const {
ERROR
} = require('../../common/error')
const {
logout
} = require('./logout')
const PasswordUtils = require('./password')
async function realPreLogin (params = {}) {
......@@ -173,10 +176,14 @@ async function postLogin (params = {}) {
extraData,
isThirdParty = false
} = params
const {
clientIP,
uniIdToken
} = this.getClientInfo()
const uid = user._id
const updateData = {
last_login_date: Date.now(),
last_login_ip: this.getClientInfo().clientIP,
last_login_ip: clientIP,
...extraData
}
const {
......@@ -185,6 +192,13 @@ async function postLogin (params = {}) {
} = await this.uniIdCommon.createToken({
uid
})
if (uniIdToken) {
try {
await logout.call(this)
} catch (error) {}
}
await userCollection.doc(uid).update(updateData)
await this.middleware.uniIdLog({
data: {
......
const {
dbCmd,
LOG_TYPE,
deviceCollection,
userCollection
} = require('../../common/constants')
async function logout() {
const {
uniIdToken,
deviceId
} = this.getClientInfo()
const {
uid
} = await this.uniIdCommon.checkToken(
uniIdToken,
{
autoRefresh: false
}
)
// 删除token
await userCollection.doc(uid).update({
token: dbCmd.pull(uniIdToken)
})
// 仅当device表的device_id和user_id均对应时才进行更新
await deviceCollection.where({
device_id: deviceId,
user_id: uid
}).update({
token_expired: 0
})
await this.middleware.uniIdLog({
data: {
user_id: uid
},
type: LOG_TYPE.LOGOUT
})
return {
errCode: 0
}
}
module.exports = {
logout
}
\ No newline at end of file
......@@ -5,7 +5,7 @@ const {
ERROR
} = require('../../common/error')
function getQQPlatform () {
function getQQPlatform() {
const platform = this.clientPlatform
switch (platform) {
case 'app':
......@@ -18,7 +18,7 @@ function getQQPlatform () {
}
}
async function saveQQUserKey ({
async function saveQQUserKey({
openid,
sessionKey, // QQ小程序用户sessionKey
accessToken, // App端QQ用户accessToken
......@@ -45,14 +45,17 @@ async function saveQQUserKey ({
await this.uniOpenBridge.setUserAccessToken(keyObj, {
access_token: accessToken,
access_token_expired: accessTokenExpired
}, Math.floor((accessTokenExpired - Date.now()) / 1000))
}, accessTokenExpired ?
Math.floor((accessTokenExpired - Date.now()) / 1000) :
30 * 24 * 60 * 60
)
break
default:
break
}
}
function generateQQCache ({
function generateQQCache({
sessionKey, // QQ小程序用户sessionKey
accessToken, // App端QQ用户accessToken
accessTokenExpired // App端QQ用户accessToken过期时间
......@@ -81,7 +84,7 @@ function generateQQCache ({
}
}
function getQQOpenid ({
function getQQOpenid({
userRecord
} = {}) {
const qqPlatform = getQQPlatform.call(this)
......@@ -93,7 +96,7 @@ function getQQOpenid ({
return qqOpenidObj[`${qqPlatform}_${appId}`] || qqOpenidObj[qqPlatform]
}
async function getQQCacheFallback ({
async function getQQCacheFallback({
userRecord,
key
} = {}) {
......@@ -106,7 +109,7 @@ async function getQQCacheFallback ({
return qqCache && qqCache[key]
}
async function getQQCache ({
async function getQQCache({
uid,
userRecord,
key
......
......@@ -12,10 +12,13 @@ const {
getValidInviteCode,
generateInviteInfo
} = require('./fission')
const {
logout
} = require('./logout')
const PasswordUtils = require('./password')
const merge = require('lodash.merge')
async function realPreRegister (params = {}) {
async function realPreRegister(params = {}) {
const {
user
} = params
......@@ -30,7 +33,7 @@ async function realPreRegister (params = {}) {
}
}
async function preRegister (params = {}) {
async function preRegister(params = {}) {
try {
await realPreRegister.call(this, params)
} catch (error) {
......@@ -42,7 +45,7 @@ async function preRegister (params = {}) {
}
}
async function preRegisterWithPassword (params = {}) {
async function preRegisterWithPassword(params = {}) {
const {
user,
password
......@@ -69,7 +72,7 @@ async function preRegisterWithPassword (params = {}) {
}
}
async function thirdPartyRegister ({
async function thirdPartyRegister({
user = {}
} = {}) {
return {
......@@ -78,7 +81,7 @@ async function thirdPartyRegister ({
}
}
async function postRegister (params = {}) {
async function postRegister(params = {}) {
const {
user,
extraData = {},
......@@ -93,7 +96,8 @@ async function postRegister (params = {}) {
channel,
scene,
clientIP,
osName
osName,
uniIdToken
} = this.getClientInfo()
merge(user, extraData)
......@@ -147,6 +151,12 @@ async function postRegister (params = {}) {
user.invite_time = inviteTime
}
if (uniIdToken) {
try {
await logout.call(this)
} catch (error) {}
}
const beforeRegister = this.hooks.beforeRegister
let userRecord = user
if (beforeRegister) {
......
......@@ -78,7 +78,7 @@ async function saveWeixinUserKey ({
access_token: accessToken,
refresh_token: refreshToken,
access_token_expired: accessTokenExpired
}, 30 * 24 * 3600)
}, 30 * 24 * 60 * 60)
break
default:
break
......
......@@ -18,6 +18,10 @@ const PasswordUtils = require('../../lib/utils/password')
* @param {String} params.nickname 昵称
* @param {Array} params.authorizedApp 允许登录的AppID列表
* @param {Array} params.role 用户角色列表
* @param {String} params.mobile 手机号
* @param {String} params.email 邮箱
* @param {Array} params.tags 用户标签
* @param {Number} params.status 用户状态
* @returns
*/
module.exports = async function (params = {}) {
......@@ -35,6 +39,22 @@ module.exports = async function (params = {}) {
role: {
require: false,
type: 'array<string>'
},
mobile: {
required: false,
type: 'mobile'
},
email: {
required: false,
type: 'email'
},
tags: {
required: false,
type: 'array<string>'
},
status: {
required: false,
type: 'number'
}
}
this.middleware.validate(params, schema)
......@@ -43,11 +63,17 @@ module.exports = async function (params = {}) {
password,
authorizedApp,
nickname,
role
role,
mobile,
email,
tags,
status
} = params
const userMatched = await findUser({
userQuery: {
username
username,
mobile,
email
},
authorizedApp
})
......@@ -65,17 +91,28 @@ module.exports = async function (params = {}) {
} = passwordUtils.generatePasswordHash({
password
})
await userCollection.add({
const data = {
username,
password: passwordHash,
password_secret_version: version,
dcloud_appid: authorizedApp || [],
nickname,
role: role || []
})
role: role || [],
mobile,
email,
tags: tags || [],
status
}
if (email) {
data.email_confirmed = 1
}
if (mobile) {
data.mobile_confirmed = 1
}
await userCollection.add(data)
return {
errCode: 0
errCode: 0,
errMsg: ''
}
}
module.exports = {
addUser: require('./add-user')
addUser: require('./add-user'),
updateUser: require('./update-user')
}
const {
findUser
} = require('../../lib/utils/account')
const {
ERROR
} = require('../../common/error')
const {
userCollection
} = require('../../common/constants')
const PasswordUtils = require('../../lib/utils/password')
/**
* 修改用户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#update-user
* @param {Object} params
* @param {String} params.uid 要更新的用户id
* @param {String} params.username 用户名
* @param {String} params.password 密码
* @param {String} params.nickname 昵称
* @param {Array} params.authorizedApp 允许登录的AppID列表
* @param {Array} params.role 用户角色列表
* @param {String} params.mobile 手机号
* @param {String} params.email 邮箱
* @param {Array} params.tags 用户标签
* @param {Number} params.status 用户状态
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
uid: 'string',
username: 'username',
password: {
required: false,
type: 'password'
},
authorizedApp: {
required: false,
type: 'array<string>'
}, // 指定允许登录的app,传空数组或不传时表示可以不可以在任何端登录
nickname: {
required: false,
type: 'nickname'
},
role: {
require: false,
type: 'array<string>'
},
mobile: {
required: false,
type: 'mobile'
},
email: {
required: false,
type: 'email'
},
tags: {
required: false,
type: 'array<string>'
},
status: {
required: false,
type: 'number'
}
}
this.middleware.validate(params, schema)
const {
uid,
username,
password,
authorizedApp,
nickname,
role,
mobile,
email,
tags,
status
} = params
// 更新的用户数据字段
const data = {
username,
dcloud_appid: authorizedApp || [],
nickname,
role: role || [],
mobile,
email,
tags: tags || [],
status
}
// 更新用户名时验证用户名是否重新
if (username) {
const userMatched = await findUser({
userQuery: {
username
},
authorizedApp
})
if (userMatched.filter(user => user._id !== uid).length) {
throw {
errCode: ERROR.ACCOUNT_EXISTS
}
}
}
if (password) {
const passwordUtils = new PasswordUtils({
passwordSecret: this.config.passwordSecret
})
const {
passwordHash,
version
} = passwordUtils.generatePasswordHash({
password
})
data.password = passwordHash
data.password_secret_version = version
}
await userCollection.doc(uid).update(data)
return {
errCode: 0
}
}
const {
dbCmd,
LOG_TYPE,
deviceCollection,
userCollection
} = require('../../common/constants')
logout
} = require('../../lib/utils/logout')
/**
* 用户退出登录
......@@ -11,37 +8,7 @@ const {
* @returns
*/
module.exports = async function () {
const {
uniIdToken,
deviceId
} = this.getClientInfo()
const {
uid
} = await this.uniIdCommon.checkToken(
uniIdToken,
{
autoRefresh: false
}
)
// 删除token
await userCollection.doc(uid).update({
token: dbCmd.pull(uniIdToken)
})
// 仅当device表的device_id和user_id均对应时才进行更新
await deviceCollection.where({
device_id: deviceId,
user_id: uid
}).update({
token_expired: 0
})
await this.middleware.uniIdLog({
data: {
user_id: uid
},
type: LOG_TYPE.LOGOUT
})
await logout.call(this)
return {
errCode: 0
}
......
......@@ -16,7 +16,7 @@ module.exports = async function (params = {}) {
uid: 'string',
appId: 'string'
}
this.moddleware.validate(params, schema)
this.middleware.validate(params, schema)
const {
uid,
appId
......
{
"name": "uni-id-co",
"version": "1.0.10",
"version": "1.0.12",
"description": "",
"main": "index.js",
"keywords": [],
......
'use strict';
'use strict';
class BridgeError extends Error {
......@@ -20,7 +20,7 @@ class BridgeError extends Error {
return this.message
}
}
module.exports = {
BridgeError
module.exports = {
BridgeError
}
'use strict';
const {
PlatformType
} = require('./consts.js')
const configCenter = require('uni-config-center')
const OauthConfig = {
'weixin-mp': ['mp-weixin', 'oauth', 'weixin'],
'weixin-h5': ['web', 'oauth', 'weixin-h5']
}
class ConfigBase {
constructor() {
this._ready = false
this._uniId = null
const uniIdConfig = configCenter({
pluginId: 'uni-id'
})
this._uniId = uniIdConfig.config()
this._ready = true
}
getAppConfig(appid) {
if (Array.isArray(this._uniId)) {
return this._uniId.find((item) => {
return (item.dcloudAppid === appid)
})
}
return this._uniId
}
get ready() {
return this._ready
}
}
class AppConfig extends ConfigBase {
constructor() {
super()
}
get(appid, platform) {
if (!this.isSupport(platform)) {
return null
}
let appConfig = this.getAppConfig(appid)
if (!appConfig) {
return null
}
return this.getOauthConfig(appConfig, platform)
}
isSupport(platformName) {
return (AppConfig.Support_Platforms.indexOf(platformName) >= 0)
}
getOauthConfig(appConfig, platformName) {
let tree = OauthConfig[platformName]
let node = appConfig
for (let i = 0; i < tree.length; i++) {
let nodeName = tree[i]
if (node[nodeName]) {
node = node[nodeName]
} else {
node = null
break
}
}
if (node && node.appid && node.appsecret) {
return {
appid: node.appid,
secret: node.appsecret
}
}
return null
}
}
AppConfig.Support_Platforms = [PlatformType.WEIXIN_MP, PlatformType.WEIXIN_H5]
module.exports = {
AppConfig
'use strict';
const {
PlatformType
} = require('./consts.js')
const configCenter = require('uni-config-center')
const OauthConfig = {
'weixin-mp': ['mp-weixin', 'oauth', 'weixin'],
'weixin-h5': ['web', 'oauth', 'weixin-h5']
}
class ConfigBase {
constructor() {
this._ready = false
this._uniId = null
const uniIdConfig = configCenter({
pluginId: 'uni-id'
})
this._uniId = uniIdConfig.config()
this._ready = true
}
getAppConfig(appid) {
if (Array.isArray(this._uniId)) {
return this._uniId.find((item) => {
return (item.dcloudAppid === appid)
})
}
return this._uniId
}
get ready() {
return this._ready
}
}
class AppConfig extends ConfigBase {
constructor() {
super()
}
get(appid, platform) {
if (!this.isSupport(platform)) {
return null
}
let appConfig = this.getAppConfig(appid)
if (!appConfig) {
return null
}
return this.getOauthConfig(appConfig, platform)
}
isSupport(platformName) {
return (AppConfig.Support_Platforms.indexOf(platformName) >= 0)
}
getOauthConfig(appConfig, platformName) {
let tree = OauthConfig[platformName]
let node = appConfig
for (let i = 0; i < tree.length; i++) {
let nodeName = tree[i]
if (node[nodeName]) {
node = node[nodeName]
} else {
node = null
break
}
}
if (node && node.appid && node.appsecret) {
return {
appid: node.appid,
secret: node.appsecret
}
}
return null
}
}
AppConfig.Support_Platforms = [PlatformType.WEIXIN_MP, PlatformType.WEIXIN_H5]
module.exports = {
AppConfig
};
'use strict';
const HTTP_STATUS = {
SUCCESS: 200
}
const PlatformType = {
WEIXIN_MP: 'weixin-mp',
WEIXIN_H5: 'weixin-h5',
WEIXIN_APP: 'weixin-app',
WEIXIN_WEB: 'weixin-web',
QQ_MP: 'qq-mp',
QQ_APP: 'qq-app'
}
module.exports = {
HTTP_STATUS,
PlatformType
'use strict';
const TAG = "UNI_OPEN_BRIDGE"
const HTTP_STATUS = {
SUCCESS: 200
}
const PlatformType = {
WEIXIN_MP: 'weixin-mp',
WEIXIN_H5: 'weixin-h5',
WEIXIN_APP: 'weixin-app',
WEIXIN_WEB: 'weixin-web',
QQ_MP: 'qq-mp',
QQ_APP: 'qq-app'
}
const ErrorCodeType = {
SYSTEM_ERROR: TAG + "_SYSTEM_ERROR"
}
module.exports = {
HTTP_STATUS,
PlatformType,
ErrorCodeType
}
'use strict';
const {
PlatformType
} = require('./consts.js')
const {
AppConfig
} = require('./config.js')
const {
Storage,
Factory
} = require('./storage.js')
const {
BridgeError
} = require('./bridge-error.js')
const {
WeixinServer
} = require('./weixin-server.js')
const appConfig = new AppConfig()
class AccessToken extends Storage {
constructor() {
super('access-token', ['dcloudAppid', 'platform'])
}
async fallback(parameters) {
const oauthConfig = appConfig.get(parameters.dcloudAppid, parameters.platform)
const methodName = (parameters.platform === PlatformType.WEIXIN_MP) ? 'GetMPAccessTokenData' :
'GetH5AccessTokenData'
const responseData = await WeixinServer[methodName](oauthConfig)
const duration = responseData.expires_in
delete responseData.expires_in
return {
value: responseData,
duration
}
}
}
class UserAccessToken extends Storage {
constructor() {
super('user-access-token', ['dcloudAppid', 'platform', 'openid'])
}
}
class SessionKey extends Storage {
constructor() {
super('session-key', ['dcloudAppid', 'platform', 'openid'])
}
}
class Encryptkey extends Storage {
constructor() {
super('encrypt-key', ['dcloudAppid', 'platform', 'openid'])
}
getKeyString(key) {
return `${super.getKeyString(key)}-${key.version}`
}
getExpiresIn(value) {
if (value <= 0) {
return 60
}
return value
}
async fallback(parameters) {
const accessToken = await Factory.Get(AccessToken, parameters)
const userSession = await Factory.Get(SessionKey, parameters)
const responseData = await WeixinServer.GetUserEncryptKeyData({
openid: parameters.openid,
access_token: accessToken.access_token,
session_key: userSession.session_key
})
const keyInfo = responseData.key_info_list.find((item) => {
return item.version = parameters.version
})
const value = {
encrypt_key: keyInfo.encrypt_key,
iv: keyInfo.iv
}
return {
value,
duration: keyInfo.expire_in
}
}
}
class Ticket extends Storage {
constructor() {
super('ticket', ['dcloudAppid', 'platform'])
}
async fallback(parameters) {
const accessToken = await Factory.Get(AccessToken, {
dcloudAppid: parameters.dcloudAppid,
platform: PlatformType.WEIXIN_H5
})
const responseData = await WeixinServer.GetH5TicketData(accessToken)
const duration = responseData.expires_in
delete responseData.expires_in
return {
value: responseData,
duration
}
}
}
// exports
async function getAccessToken(key, fallback) {
return await Factory.Get(AccessToken, key, fallback)
}
async function setAccessToken(key, value, expiresIn) {
await Factory.Set(AccessToken, key, value, expiresIn)
}
async function removeAccessToken(key) {
await Factory.Remove(AccessToken, key)
}
async function getUserAccessToken(key, fallback) {
return await Factory.Get(UserAccessToken, key, fallback)
}
async function setUserAccessToken(key, value, expiresIn) {
await Factory.Set(UserAccessToken, key, value, expiresIn)
}
async function removeUserAccessToken(key) {
await Factory.Remove(UserAccessToken, key)
}
async function getSessionKey(key, fallback) {
return await Factory.Get(SessionKey, key, fallback)
}
async function setSessionKey(key, value, expiresIn) {
await Factory.Set(SessionKey, key, value, expiresIn)
}
async function removeSessionKey(key) {
await Factory.Remove(SessionKey, key)
}
async function getEncryptKey(key, fallback) {
return await Factory.Get(Encryptkey, key, fallback)
}
async function setEncryptKey(key, value, expiresIn) {
await Factory.Set(Encryptkey, key, value, expiresIn)
}
async function removeEncryptKey(key) {
await Factory.Remove(Encryptkey, key)
}
async function getTicket(key, fallback) {
return await Factory.Get(Ticket, key, fallback)
}
async function setTicket(key, value, expiresIn) {
await Factory.Set(Ticket, key, value, expiresIn)
}
async function removeTicket(key) {
await Factory.Remove(Ticket, key)
}
module.exports = {
getAccessToken,
setAccessToken,
removeAccessToken,
getUserAccessToken,
setUserAccessToken,
removeUserAccessToken,
getSessionKey,
setSessionKey,
removeSessionKey,
getEncryptKey,
setEncryptKey,
removeEncryptKey,
getTicket,
setTicket,
removeTicket,
PlatformType,
WeixinServer
'use strict';
const {
PlatformType,
ErrorCodeType
} = require('./consts.js')
const {
AppConfig
} = require('./config.js')
const {
Storage,
Factory
} = require('./storage.js')
const {
BridgeError
} = require('./bridge-error.js')
const {
WeixinServer
} = require('./weixin-server.js')
const appConfig = new AppConfig()
class AccessToken extends Storage {
constructor() {
super('access-token', ['dcloudAppid', 'platform'])
}
async fallback(parameters) {
const oauthConfig = appConfig.get(parameters.dcloudAppid, parameters.platform)
let methodName
if (parameters.platform === PlatformType.WEIXIN_MP) {
methodName = 'GetMPAccessTokenData'
} else if (parameters.platform === PlatformType.WEIXIN_H5) {
methodName = 'GetH5AccessTokenData'
} else {
throw new BridgeError(ErrorCodeType.SYSTEM_ERROR, "platform invalid")
}
const responseData = await WeixinServer[methodName](oauthConfig)
const duration = responseData.expires_in
delete responseData.expires_in
return {
value: responseData,
duration
}
}
}
class UserAccessToken extends Storage {
constructor() {
super('user-access-token', ['dcloudAppid', 'platform', 'openid'])
}
}
class SessionKey extends Storage {
constructor() {
super('session-key', ['dcloudAppid', 'platform', 'openid'])
}
}
class Encryptkey extends Storage {
constructor() {
super('encrypt-key', ['dcloudAppid', 'platform', 'openid'])
}
getKeyString(key) {
return `${super.getKeyString(key)}-${key.version}`
}
getExpiresIn(value) {
if (value <= 0) {
return 60
}
return value
}
async fallback(parameters) {
const accessToken = await Factory.Get(AccessToken, parameters)
const userSession = await Factory.Get(SessionKey, parameters)
const responseData = await WeixinServer.GetUserEncryptKeyData({
openid: parameters.openid,
access_token: accessToken.access_token,
session_key: userSession.session_key
})
const keyInfo = responseData.key_info_list.find((item) => {
return item.version = parameters.version
})
const value = {
encrypt_key: keyInfo.encrypt_key,
iv: keyInfo.iv
}
return {
value,
duration: keyInfo.expire_in
}
}
}
class Ticket extends Storage {
constructor() {
super('ticket', ['dcloudAppid', 'platform'])
}
async fallback(parameters) {
const accessToken = await Factory.Get(AccessToken, {
dcloudAppid: parameters.dcloudAppid,
platform: PlatformType.WEIXIN_H5
})
const responseData = await WeixinServer.GetH5TicketData(accessToken)
const duration = responseData.expires_in
delete responseData.expires_in
return {
value: responseData,
duration
}
}
}
// exports
async function getAccessToken(key, fallback) {
return await Factory.Get(AccessToken, key, fallback)
}
async function setAccessToken(key, value, expiresIn) {
await Factory.Set(AccessToken, key, value, expiresIn)
}
async function removeAccessToken(key) {
await Factory.Remove(AccessToken, key)
}
async function getUserAccessToken(key, fallback) {
return await Factory.Get(UserAccessToken, key, fallback)
}
async function setUserAccessToken(key, value, expiresIn) {
await Factory.Set(UserAccessToken, key, value, expiresIn)
}
async function removeUserAccessToken(key) {
await Factory.Remove(UserAccessToken, key)
}
async function getSessionKey(key, fallback) {
return await Factory.Get(SessionKey, key, fallback)
}
async function setSessionKey(key, value, expiresIn) {
await Factory.Set(SessionKey, key, value, expiresIn)
}
async function removeSessionKey(key) {
await Factory.Remove(SessionKey, key)
}
async function getEncryptKey(key, fallback) {
return await Factory.Get(Encryptkey, key, fallback)
}
async function setEncryptKey(key, value, expiresIn) {
await Factory.Set(Encryptkey, key, value, expiresIn)
}
async function removeEncryptKey(key) {
await Factory.Remove(Encryptkey, key)
}
async function getTicket(key, fallback) {
return await Factory.Get(Ticket, key, fallback)
}
async function setTicket(key, value, expiresIn) {
await Factory.Set(Ticket, key, value, expiresIn)
}
async function removeTicket(key) {
await Factory.Remove(Ticket, key)
}
module.exports = {
getAccessToken,
setAccessToken,
removeAccessToken,
getUserAccessToken,
setUserAccessToken,
removeUserAccessToken,
getSessionKey,
setSessionKey,
removeSessionKey,
getEncryptKey,
setEncryptKey,
removeEncryptKey,
getTicket,
setTicket,
removeTicket,
PlatformType,
WeixinServer,
ErrorCodeType
}
'use strict';
const {
Validator
} = require('./validator.js')
const {
CacheKeyCascade
} = require('./uni-cloud-cache.js')
class Storage {
constructor(type, keys) {
this._type = type || null
this._keys = keys || []
}
async get(key, fallback) {
this.validateKey(key)
const result = await this.create(key, fallback).get()
return result.value
}
async set(key, value, expiresIn) {
this.validateKey(key)
this.validateValue(value)
const expires_in = this.getExpiresIn(expiresIn)
if (expires_in !== 0) {
await this.create(key).set(this.getValue(value), expires_in)
}
}
async remove(key) {
this.validateKey(key)
await this.create(key).remove()
}
async ttl(key) {
this.validateKey(key)
// 后续考虑支持
}
getKeyString(key) {
const keyArray = [Storage.Prefix]
this._keys.forEach((name) => {
keyArray.push(key[name])
})
keyArray.push(this._type)
return keyArray.join(':')
}
getValue(value) {
return value
}
getExpiresIn(value) {
if (value !== undefined) {
return value
}
return -1
}
validateKey(key) {
Validator.Key(this._keys, key)
}
validateValue(value) {
Validator.Value(value)
}
create(key, fallback) {
const keyString = this.getKeyString(key)
const options = {
layers: [{
type: 'database',
key: keyString
}, {
type: 'redis',
key: keyString
}]
}
if (fallback !== null) {
const fallbackFunction = fallback || this.fallback
if (fallbackFunction) {
options.fallback = async () => {
return await fallbackFunction(key)
}
}
}
return new CacheKeyCascade(options)
}
}
Storage.Prefix = "uni-id"
const Factory = {
async Get(T, key, fallback) {
return await Factory.MakeUnique(T).get(key, fallback)
},
async Set(T, key, value, expiresIn) {
await Factory.MakeUnique(T).set(key, value, expiresIn)
},
async Remove(T, key) {
await Factory.MakeUnique(T).remove(key)
},
MakeUnique(T) {
return new T()
}
}
module.exports = {
Storage,
Factory
'use strict';
const {
Validator
} = require('./validator.js')
const {
CacheKeyCascade
} = require('./uni-cloud-cache.js')
class Storage {
constructor(type, keys) {
this._type = type || null
this._keys = keys || []
}
async get(key, fallback) {
this.validateKey(key)
const result = await this.create(key, fallback).get()
return result.value
}
async set(key, value, expiresIn) {
this.validateKey(key)
this.validateValue(value)
const expires_in = this.getExpiresIn(expiresIn)
if (expires_in !== 0) {
await this.create(key).set(this.getValue(value), expires_in)
}
}
async remove(key) {
this.validateKey(key)
await this.create(key).remove()
}
async ttl(key) {
this.validateKey(key)
// 后续考虑支持
}
getKeyString(key) {
const keyArray = [Storage.Prefix]
this._keys.forEach((name) => {
keyArray.push(key[name])
})
keyArray.push(this._type)
return keyArray.join(':')
}
getValue(value) {
return value
}
getExpiresIn(value) {
if (value !== undefined) {
return value
}
return -1
}
validateKey(key) {
Validator.Key(this._keys, key)
}
validateValue(value) {
Validator.Value(value)
}
create(key, fallback) {
const keyString = this.getKeyString(key)
const options = {
layers: [{
type: 'database',
key: keyString
}, {
type: 'redis',
key: keyString
}]
}
if (fallback !== null) {
const fallbackFunction = fallback || this.fallback
if (fallbackFunction) {
options.fallback = async () => {
return await fallbackFunction(key)
}
}
}
return new CacheKeyCascade(options)
}
}
Storage.Prefix = "uni-id"
const Factory = {
async Get(T, key, fallback) {
return await Factory.MakeUnique(T).get(key, fallback)
},
async Set(T, key, value, expiresIn) {
await Factory.MakeUnique(T).set(key, value, expiresIn)
},
async Remove(T, key) {
await Factory.MakeUnique(T).remove(key)
},
MakeUnique(T) {
return new T()
}
}
module.exports = {
Storage,
Factory
};
const db = uniCloud.database()
function getType(value) {
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase()
}
const validator = {
key: function(value) {
const err = new Error('Invalid key')
if (typeof value !== 'string') {
throw err
}
const valueTrim = value.trim()
if (!valueTrim || valueTrim !== value) {
throw err
}
},
value: function(value) {
// 仅作简单校验
const type = getType(value)
const validValueType = ['null', 'number', 'string', 'array', 'object']
if (validValueType.indexOf(type) === -1) {
throw new Error('Invalid value type')
}
},
duration: function(value) {
const err = new Error('Invalid duration')
if (value === undefined) {
return
}
if (typeof value !== 'number' || value === 0) {
throw err
}
}
}
/**
* 入库时 expired 为过期时间对应的时间戳,永不过期用-1表示
* 返回结果时 与redis对齐,-1表示永不过期,-2表示已过期或不存在
*/
class DatabaseCache {
constructor({
collection = 'opendb-open-data'
} = {}) {
this.type = 'db'
this.collection = db.collection(collection)
}
_serializeValue(value) {
return value === undefined ? null : JSON.stringify(value)
}
_deserializeValue(value) {
return value ? JSON.parse(value) : value
}
async set(key, value, duration) {
validator.key(key)
validator.value(value)
validator.duration(duration)
value = this._serializeValue(value)
await this.collection.doc(key).set({
value,
expired: duration && duration !== -1 ? Date.now() + duration : -1
})
}
async _getWithDuration(key) {
const getKeyRes = await this.collection.doc(key).get()
const record = getKeyRes.data[0]
if (!record) {
return {
value: null,
duration: -2
}
}
const value = this._deserializeValue(record.value)
const expired = record.expired
if (expired === -1) {
return {
value,
duration: -1
}
}
const duration = expired - Date.now()
if (duration <= 0) {
await this.remove(key)
return {
value: null,
duration: -2
}
}
return {
value,
duration: Math.floor(duration / 1000)
}
}
async get(key, {
withDuration = true
} = {}) {
const result = await this._getWithDuration(key)
if (!withDuration) {
delete result.duration
}
return result
}
async remove(key) {
await this.collection.doc(key).remove()
}
}
class RedisCache {
constructor() {
this.type = 'redis'
this.redis = uniCloud.redis()
}
_serializeValue(value) {
return value === undefined ? null : JSON.stringify(value)
}
_deserializeValue(value) {
return value ? JSON.parse(value) : value
}
async set(key, value, duration) {
validator.key(key)
validator.value(value)
validator.duration(duration)
value = this._serializeValue(value)
if (!duration || duration === -1) {
await this.redis.set(key, value)
} else {
await this.redis.set(key, value, 'EX', duration)
}
}
async get(key, {
withDuration = false
} = {}) {
let value = await this.redis.get(key)
value = this._deserializeValue(value)
if (!withDuration) {
return {
value
}
}
const durationSecond = await this.redis.ttl(key)
let duration
switch (durationSecond) {
case -1:
duration = -1
break
case -2:
duration = -2
break
default:
duration = durationSecond
break
}
return {
value,
duration
}
}
async remove(key) {
await this.redis.del(key)
}
}
class Cache {
constructor({
type,
collection
} = {}) {
if (type === 'database') {
return new DatabaseCache({
collection
})
} else if (type === 'redis') {
return new RedisCache()
} else {
throw new Error('Invalid cache type')
}
}
}
class CacheKey {
constructor({
type,
collection,
cache,
key,
fallback
} = {}) {
this.cache = cache || new Cache({
type,
collection
})
this.key = key
this.fallback = fallback
}
async set(value, duration) {
await this.cache.set(this.key, value, duration)
}
async setWithSync(value, duration, syncMethod) {
await Promise.all([
this.set(this.key, value, duration),
syncMethod(value, duration)
])
}
async get() {
let {
value,
duration
} = await this.cache.get(this.key)
if (value !== null && value !== undefined) {
return {
value,
duration
}
}
if (!this.fallback) {
return {
value: null,
duration: -2
}
}
const fallbackResult = await this.fallback()
value = fallbackResult.value
duration = fallbackResult.duration
if (value !== null && duration !== undefined) {
await this.cache.set(this.key, value, duration)
}
return {
value,
duration
}
}
async remove() {
await this.cache.remove(this.key)
}
}
class CacheKeyCascade {
constructor({
layers, // [{cache, type, collection, key}] 从低级到高级排序,[DbCacheKey, RedisCacheKey]
fallback
} = {}) {
this.layers = layers
this.cacheLayers = []
let lastCacheKey
for (let i = 0; i < layers.length; i++) {
const {
type,
cache,
collection,
key
} = layers[i]
const lastCacheKeyTemp = lastCacheKey
try {
const currentCacheKey = new CacheKey({
type,
collection,
cache,
key,
fallback: i === 0 ? fallback : function() {
return lastCacheKeyTemp.get()
}
})
this.cacheLayers.push(currentCacheKey)
lastCacheKey = currentCacheKey
} catch (e) {}
}
this.highLevelCache = lastCacheKey
}
async set(value, duration) {
return Promise.all(
this.cacheLayers.map(item => {
return item.set(value, duration)
})
)
}
async setWithSync(value, duration, syncMethod) {
const setPromise = this.cacheLayers.map(item => {
return item.set(value, duration)
})
return Promise.all(
[
...setPromise,
syncMethod(value, duration)
]
)
}
async get() {
return this.highLevelCache.get()
}
async remove() {
await Promise.all(
this.cacheLayers.map(cacheKeyItem => {
return cacheKeyItem.remove()
})
)
}
}
module.exports = {
Cache,
DatabaseCache,
RedisCache,
CacheKey,
CacheKeyCascade
const db = uniCloud.database()
function getType(value) {
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase()
}
const validator = {
key: function(value) {
const err = new Error('Invalid key')
if (typeof value !== 'string') {
throw err
}
const valueTrim = value.trim()
if (!valueTrim || valueTrim !== value) {
throw err
}
},
value: function(value) {
// 仅作简单校验
const type = getType(value)
const validValueType = ['null', 'number', 'string', 'array', 'object']
if (validValueType.indexOf(type) === -1) {
throw new Error('Invalid value type')
}
},
duration: function(value) {
const err = new Error('Invalid duration')
if (value === undefined) {
return
}
if (typeof value !== 'number' || value === 0) {
throw err
}
}
}
/**
* 入库时 expired 为过期时间对应的时间戳,永不过期用-1表示
* 返回结果时 与redis对齐,-1表示永不过期,-2表示已过期或不存在
*/
class DatabaseCache {
constructor({
collection = 'opendb-open-data'
} = {}) {
this.type = 'db'
this.collection = db.collection(collection)
}
_serializeValue(value) {
return value === undefined ? null : JSON.stringify(value)
}
_deserializeValue(value) {
return value ? JSON.parse(value) : value
}
async set(key, value, duration) {
validator.key(key)
validator.value(value)
validator.duration(duration)
value = this._serializeValue(value)
await this.collection.doc(key).set({
value,
expired: duration && duration !== -1 ? Date.now() + duration : -1
})
}
async _getWithDuration(key) {
const getKeyRes = await this.collection.doc(key).get()
const record = getKeyRes.data[0]
if (!record) {
return {
value: null,
duration: -2
}
}
const value = this._deserializeValue(record.value)
const expired = record.expired
if (expired === -1) {
return {
value,
duration: -1
}
}
const duration = expired - Date.now()
if (duration <= 0) {
await this.remove(key)
return {
value: null,
duration: -2
}
}
return {
value,
duration: Math.floor(duration / 1000)
}
}
async get(key, {
withDuration = true
} = {}) {
const result = await this._getWithDuration(key)
if (!withDuration) {
delete result.duration
}
return result
}
async remove(key) {
await this.collection.doc(key).remove()
}
}
class RedisCache {
constructor() {
this.type = 'redis'
this.redis = uniCloud.redis()
}
_serializeValue(value) {
return value === undefined ? null : JSON.stringify(value)
}
_deserializeValue(value) {
return value ? JSON.parse(value) : value
}
async set(key, value, duration) {
validator.key(key)
validator.value(value)
validator.duration(duration)
value = this._serializeValue(value)
if (!duration || duration === -1) {
await this.redis.set(key, value)
} else {
await this.redis.set(key, value, 'EX', duration)
}
}
async get(key, {
withDuration = false
} = {}) {
let value = await this.redis.get(key)
value = this._deserializeValue(value)
if (!withDuration) {
return {
value
}
}
const durationSecond = await this.redis.ttl(key)
let duration
switch (durationSecond) {
case -1:
duration = -1
break
case -2:
duration = -2
break
default:
duration = durationSecond
break
}
return {
value,
duration
}
}
async remove(key) {
await this.redis.del(key)
}
}
class Cache {
constructor({
type,
collection
} = {}) {
if (type === 'database') {
return new DatabaseCache({
collection
})
} else if (type === 'redis') {
return new RedisCache()
} else {
throw new Error('Invalid cache type')
}
}
}
class CacheKey {
constructor({
type,
collection,
cache,
key,
fallback
} = {}) {
this.cache = cache || new Cache({
type,
collection
})
this.key = key
this.fallback = fallback
}
async set(value, duration) {
await this.cache.set(this.key, value, duration)
}
async setWithSync(value, duration, syncMethod) {
await Promise.all([
this.set(this.key, value, duration),
syncMethod(value, duration)
])
}
async get() {
let {
value,
duration
} = await this.cache.get(this.key)
if (value !== null && value !== undefined) {
return {
value,
duration
}
}
if (!this.fallback) {
return {
value: null,
duration: -2
}
}
const fallbackResult = await this.fallback()
value = fallbackResult.value
duration = fallbackResult.duration
if (value !== null && duration !== undefined) {
await this.cache.set(this.key, value, duration)
}
return {
value,
duration
}
}
async remove() {
await this.cache.remove(this.key)
}
}
class CacheKeyCascade {
constructor({
layers, // [{cache, type, collection, key}] 从低级到高级排序,[DbCacheKey, RedisCacheKey]
fallback
} = {}) {
this.layers = layers
this.cacheLayers = []
let lastCacheKey
for (let i = 0; i < layers.length; i++) {
const {
type,
cache,
collection,
key
} = layers[i]
const lastCacheKeyTemp = lastCacheKey
try {
const currentCacheKey = new CacheKey({
type,
collection,
cache,
key,
fallback: i === 0 ? fallback : function() {
return lastCacheKeyTemp.get()
}
})
this.cacheLayers.push(currentCacheKey)
lastCacheKey = currentCacheKey
} catch (e) {}
}
this.highLevelCache = lastCacheKey
}
async set(value, duration) {
return Promise.all(
this.cacheLayers.map(item => {
return item.set(value, duration)
})
)
}
async setWithSync(value, duration, syncMethod) {
const setPromise = this.cacheLayers.map(item => {
return item.set(value, duration)
})
return Promise.all(
[
...setPromise,
syncMethod(value, duration)
]
)
}
async get() {
return this.highLevelCache.get()
}
async remove() {
await Promise.all(
this.cacheLayers.map(cacheKeyItem => {
return cacheKeyItem.remove()
})
)
}
}
module.exports = {
Cache,
DatabaseCache,
RedisCache,
CacheKey,
CacheKeyCascade
}
const Validator = {
Key(keyArray, parameters) {
for (let i = 0; i < keyArray.length; i++) {
const keyName = keyArray[i]
if (typeof parameters[keyName] !== 'string') {
Validator.ThrowNewError(`Invalid ${keyName}`)
}
if (parameters[keyName].length < 1) {
Validator.ThrowNewError(`Invalid ${keyName}`)
}
}
},
Value(value) {
if (value === undefined) {
Validator.ThrowNewError('Invalid Value')
}
if (typeof value !== 'object') {
Validator.ThrowNewError('Invalid Value Type')
}
},
ThrowNewError(message) {
throw new Error(message)
}
}
module.exports = {
Validator
const Validator = {
Key(keyArray, parameters) {
for (let i = 0; i < keyArray.length; i++) {
const keyName = keyArray[i]
if (typeof parameters[keyName] !== 'string') {
Validator.ThrowNewError(`Invalid ${keyName}`)
}
if (parameters[keyName].length < 1) {
Validator.ThrowNewError(`Invalid ${keyName}`)
}
}
},
Value(value) {
if (value === undefined) {
Validator.ThrowNewError('Invalid Value')
}
if (typeof value !== 'object') {
Validator.ThrowNewError('Invalid Value Type')
}
},
ThrowNewError(message) {
throw new Error(message)
}
}
module.exports = {
Validator
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册